From 2c3c1048746a4622d8c89a29670120dc8fab93c4 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 20:49:45 +0200 Subject: Adding upstream version 6.1.76. Signed-off-by: Daniel Baumann --- drivers/media/platform/Kconfig | 88 + drivers/media/platform/Makefile | 35 + drivers/media/platform/allegro-dvt/Kconfig | 19 + drivers/media/platform/allegro-dvt/Makefile | 6 + drivers/media/platform/allegro-dvt/allegro-core.c | 4021 ++++++++++++++++++++ drivers/media/platform/allegro-dvt/allegro-mail.c | 549 +++ drivers/media/platform/allegro-dvt/allegro-mail.h | 297 ++ drivers/media/platform/allegro-dvt/nal-h264.c | 605 +++ drivers/media/platform/allegro-dvt/nal-h264.h | 404 ++ drivers/media/platform/allegro-dvt/nal-hevc.c | 884 +++++ drivers/media/platform/allegro-dvt/nal-hevc.h | 519 +++ drivers/media/platform/allegro-dvt/nal-rbsp.c | 310 ++ drivers/media/platform/allegro-dvt/nal-rbsp.h | 61 + drivers/media/platform/amlogic/Kconfig | 5 + drivers/media/platform/amlogic/Makefile | 2 + drivers/media/platform/amlogic/meson-ge2d/Kconfig | 14 + drivers/media/platform/amlogic/meson-ge2d/Makefile | 3 + .../media/platform/amlogic/meson-ge2d/ge2d-regs.h | 360 ++ drivers/media/platform/amlogic/meson-ge2d/ge2d.c | 1061 ++++++ drivers/media/platform/amphion/Kconfig | 22 + drivers/media/platform/amphion/Makefile | 20 + drivers/media/platform/amphion/vdec.c | 1700 +++++++++ drivers/media/platform/amphion/venc.c | 1356 +++++++ drivers/media/platform/amphion/vpu.h | 366 ++ drivers/media/platform/amphion/vpu_cmds.c | 465 +++ drivers/media/platform/amphion/vpu_cmds.h | 25 + drivers/media/platform/amphion/vpu_codec.h | 69 + drivers/media/platform/amphion/vpu_color.c | 183 + drivers/media/platform/amphion/vpu_core.c | 885 +++++ drivers/media/platform/amphion/vpu_core.h | 16 + drivers/media/platform/amphion/vpu_dbg.c | 494 +++ drivers/media/platform/amphion/vpu_defs.h | 188 + drivers/media/platform/amphion/vpu_drv.c | 265 ++ drivers/media/platform/amphion/vpu_helpers.c | 476 +++ drivers/media/platform/amphion/vpu_helpers.h | 71 + drivers/media/platform/amphion/vpu_imx8q.c | 271 ++ drivers/media/platform/amphion/vpu_imx8q.h | 115 + drivers/media/platform/amphion/vpu_malone.c | 1682 ++++++++ drivers/media/platform/amphion/vpu_malone.h | 45 + drivers/media/platform/amphion/vpu_mbox.c | 117 + drivers/media/platform/amphion/vpu_mbox.h | 16 + drivers/media/platform/amphion/vpu_msgs.c | 421 ++ drivers/media/platform/amphion/vpu_msgs.h | 14 + drivers/media/platform/amphion/vpu_rpc.c | 259 ++ drivers/media/platform/amphion/vpu_rpc.h | 466 +++ drivers/media/platform/amphion/vpu_v4l2.c | 778 ++++ drivers/media/platform/amphion/vpu_v4l2.h | 58 + drivers/media/platform/amphion/vpu_windsor.c | 1174 ++++++ drivers/media/platform/amphion/vpu_windsor.h | 37 + drivers/media/platform/aspeed/Kconfig | 13 + drivers/media/platform/aspeed/Makefile | 2 + drivers/media/platform/aspeed/aspeed-video.c | 2051 ++++++++++ drivers/media/platform/atmel/Kconfig | 66 + drivers/media/platform/atmel/Makefile | 10 + drivers/media/platform/atmel/atmel-isc-base.c | 2011 ++++++++++ drivers/media/platform/atmel/atmel-isc-clk.c | 311 ++ drivers/media/platform/atmel/atmel-isc-regs.h | 413 ++ drivers/media/platform/atmel/atmel-isc.h | 362 ++ drivers/media/platform/atmel/atmel-isi.c | 1378 +++++++ drivers/media/platform/atmel/atmel-isi.h | 137 + drivers/media/platform/atmel/atmel-sama5d2-isc.c | 653 ++++ drivers/media/platform/atmel/atmel-sama7g5-isc.c | 616 +++ drivers/media/platform/atmel/microchip-csi2dc.c | 797 ++++ drivers/media/platform/cadence/Kconfig | 27 + drivers/media/platform/cadence/Makefile | 4 + drivers/media/platform/cadence/cdns-csi2rx.c | 509 +++ drivers/media/platform/cadence/cdns-csi2tx.c | 660 ++++ drivers/media/platform/chips-media/Kconfig | 20 + drivers/media/platform/chips-media/Makefile | 6 + drivers/media/platform/chips-media/coda-bit.c | 2666 +++++++++++++ drivers/media/platform/chips-media/coda-common.c | 3362 ++++++++++++++++ drivers/media/platform/chips-media/coda-gdi.c | 146 + drivers/media/platform/chips-media/coda-h264.c | 429 +++ drivers/media/platform/chips-media/coda-jpeg.c | 1547 ++++++++ drivers/media/platform/chips-media/coda-mpeg2.c | 87 + drivers/media/platform/chips-media/coda-mpeg4.c | 87 + drivers/media/platform/chips-media/coda.h | 403 ++ drivers/media/platform/chips-media/coda_regs.h | 563 +++ drivers/media/platform/chips-media/imx-vdoa.c | 352 ++ drivers/media/platform/chips-media/imx-vdoa.h | 50 + drivers/media/platform/chips-media/trace.h | 175 + drivers/media/platform/intel/Kconfig | 14 + drivers/media/platform/intel/Makefile | 2 + drivers/media/platform/intel/pxa_camera.c | 2471 ++++++++++++ drivers/media/platform/m2m-deinterlace.c | 1008 +++++ drivers/media/platform/marvell/Kconfig | 35 + drivers/media/platform/marvell/Makefile | 6 + drivers/media/platform/marvell/cafe-driver.c | 673 ++++ drivers/media/platform/marvell/mcam-core.c | 1994 ++++++++++ drivers/media/platform/marvell/mcam-core.h | 378 ++ drivers/media/platform/marvell/mmp-driver.c | 380 ++ drivers/media/platform/mediatek/Kconfig | 9 + drivers/media/platform/mediatek/Makefile | 6 + drivers/media/platform/mediatek/jpeg/Kconfig | 16 + drivers/media/platform/mediatek/jpeg/Makefile | 6 + .../media/platform/mediatek/jpeg/mtk_jpeg_core.c | 1528 ++++++++ .../media/platform/mediatek/jpeg/mtk_jpeg_core.h | 163 + .../media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c | 415 ++ .../media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h | 79 + .../platform/mediatek/jpeg/mtk_jpeg_dec_parse.c | 152 + .../platform/mediatek/jpeg/mtk_jpeg_dec_parse.h | 17 + .../platform/mediatek/jpeg/mtk_jpeg_dec_reg.h | 49 + .../media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c | 155 + .../media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.h | 91 + drivers/media/platform/mediatek/mdp/Kconfig | 17 + drivers/media/platform/mediatek/mdp/Makefile | 10 + drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c | 77 + drivers/media/platform/mediatek/mdp/mtk_mdp_comp.h | 46 + drivers/media/platform/mediatek/mdp/mtk_mdp_core.c | 314 ++ drivers/media/platform/mediatek/mdp/mtk_mdp_core.h | 256 ++ drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h | 120 + drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.c | 1229 ++++++ drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.h | 14 + drivers/media/platform/mediatek/mdp/mtk_mdp_regs.c | 148 + drivers/media/platform/mediatek/mdp/mtk_mdp_regs.h | 23 + drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.c | 138 + drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.h | 33 + drivers/media/platform/mediatek/mdp3/Kconfig | 19 + drivers/media/platform/mediatek/mdp3/Makefile | 6 + .../media/platform/mediatek/mdp3/mdp_reg_ccorr.h | 19 + .../media/platform/mediatek/mdp3/mdp_reg_rdma.h | 65 + drivers/media/platform/mediatek/mdp3/mdp_reg_rsz.h | 39 + .../media/platform/mediatek/mdp3/mdp_reg_wdma.h | 47 + .../media/platform/mediatek/mdp3/mdp_reg_wrot.h | 55 + drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h | 290 ++ .../media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c | 469 +++ .../media/platform/mediatek/mdp3/mtk-mdp3-cmdq.h | 43 + .../media/platform/mediatek/mdp3/mtk-mdp3-comp.c | 1048 +++++ .../media/platform/mediatek/mdp3/mtk-mdp3-comp.h | 186 + .../media/platform/mediatek/mdp3/mtk-mdp3-core.c | 360 ++ .../media/platform/mediatek/mdp3/mtk-mdp3-core.h | 94 + .../media/platform/mediatek/mdp3/mtk-mdp3-m2m.c | 730 ++++ .../media/platform/mediatek/mdp3/mtk-mdp3-m2m.h | 48 + .../media/platform/mediatek/mdp3/mtk-mdp3-regs.c | 737 ++++ .../media/platform/mediatek/mdp3/mtk-mdp3-regs.h | 373 ++ .../media/platform/mediatek/mdp3/mtk-mdp3-vpu.c | 314 ++ .../media/platform/mediatek/mdp3/mtk-mdp3-vpu.h | 78 + drivers/media/platform/mediatek/vcodec/Kconfig | 37 + drivers/media/platform/mediatek/vcodec/Makefile | 46 + .../platform/mediatek/vcodec/mtk_vcodec_dec.c | 1010 +++++ .../platform/mediatek/vcodec/mtk_vcodec_dec.h | 101 + .../platform/mediatek/vcodec/mtk_vcodec_dec_drv.c | 528 +++ .../platform/mediatek/vcodec/mtk_vcodec_dec_hw.c | 214 ++ .../platform/mediatek/vcodec/mtk_vcodec_dec_hw.h | 58 + .../platform/mediatek/vcodec/mtk_vcodec_dec_pm.c | 265 ++ .../platform/mediatek/vcodec/mtk_vcodec_dec_pm.h | 17 + .../mediatek/vcodec/mtk_vcodec_dec_stateful.c | 623 +++ .../mediatek/vcodec/mtk_vcodec_dec_stateless.c | 513 +++ .../platform/mediatek/vcodec/mtk_vcodec_drv.h | 544 +++ .../platform/mediatek/vcodec/mtk_vcodec_enc.c | 1487 ++++++++ .../platform/mediatek/vcodec/mtk_vcodec_enc.h | 50 + .../platform/mediatek/vcodec/mtk_vcodec_enc_drv.c | 485 +++ .../platform/mediatek/vcodec/mtk_vcodec_enc_pm.c | 90 + .../platform/mediatek/vcodec/mtk_vcodec_enc_pm.h | 17 + .../media/platform/mediatek/vcodec/mtk_vcodec_fw.c | 73 + .../media/platform/mediatek/vcodec/mtk_vcodec_fw.h | 44 + .../platform/mediatek/vcodec/mtk_vcodec_fw_priv.h | 52 + .../platform/mediatek/vcodec/mtk_vcodec_fw_scp.c | 73 + .../platform/mediatek/vcodec/mtk_vcodec_fw_vpu.c | 112 + .../platform/mediatek/vcodec/mtk_vcodec_intr.c | 43 + .../platform/mediatek/vcodec/mtk_vcodec_intr.h | 19 + .../platform/mediatek/vcodec/mtk_vcodec_util.c | 135 + .../platform/mediatek/vcodec/mtk_vcodec_util.h | 63 + .../platform/mediatek/vcodec/vdec/vdec_h264_if.c | 503 +++ .../mediatek/vcodec/vdec/vdec_h264_req_common.c | 322 ++ .../mediatek/vcodec/vdec/vdec_h264_req_common.h | 277 ++ .../mediatek/vcodec/vdec/vdec_h264_req_if.c | 449 +++ .../mediatek/vcodec/vdec/vdec_h264_req_multi_if.c | 855 +++++ .../platform/mediatek/vcodec/vdec/vdec_vp8_if.c | 616 +++ .../mediatek/vcodec/vdec/vdec_vp8_req_if.c | 436 +++ .../platform/mediatek/vcodec/vdec/vdec_vp9_if.c | 1029 +++++ .../mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c | 2225 +++++++++++ .../media/platform/mediatek/vcodec/vdec_drv_base.h | 46 + .../media/platform/mediatek/vcodec/vdec_drv_if.c | 120 + .../media/platform/mediatek/vcodec/vdec_drv_if.h | 103 + .../media/platform/mediatek/vcodec/vdec_ipi_msg.h | 153 + .../platform/mediatek/vcodec/vdec_msg_queue.c | 358 ++ .../platform/mediatek/vcodec/vdec_msg_queue.h | 179 + .../media/platform/mediatek/vcodec/vdec_vpu_if.c | 297 ++ .../media/platform/mediatek/vcodec/vdec_vpu_if.h | 122 + .../platform/mediatek/vcodec/venc/venc_h264_if.c | 842 ++++ .../platform/mediatek/vcodec/venc/venc_vp8_if.c | 468 +++ .../media/platform/mediatek/vcodec/venc_drv_base.h | 53 + .../media/platform/mediatek/vcodec/venc_drv_if.c | 100 + .../media/platform/mediatek/vcodec/venc_drv_if.h | 170 + .../media/platform/mediatek/vcodec/venc_ipi_msg.h | 244 ++ .../media/platform/mediatek/vcodec/venc_vpu_if.c | 361 ++ .../media/platform/mediatek/vcodec/venc_vpu_if.h | 51 + drivers/media/platform/mediatek/vpu/Kconfig | 15 + drivers/media/platform/mediatek/vpu/Makefile | 4 + drivers/media/platform/mediatek/vpu/mtk_vpu.c | 1056 +++++ drivers/media/platform/mediatek/vpu/mtk_vpu.h | 188 + drivers/media/platform/nvidia/Kconfig | 5 + drivers/media/platform/nvidia/Makefile | 3 + drivers/media/platform/nvidia/tegra-vde/Kconfig | 17 + drivers/media/platform/nvidia/tegra-vde/Makefile | 3 + .../media/platform/nvidia/tegra-vde/dmabuf-cache.c | 229 ++ drivers/media/platform/nvidia/tegra-vde/h264.c | 950 +++++ drivers/media/platform/nvidia/tegra-vde/iommu.c | 157 + drivers/media/platform/nvidia/tegra-vde/trace.h | 95 + drivers/media/platform/nvidia/tegra-vde/v4l2.c | 1018 +++++ drivers/media/platform/nvidia/tegra-vde/vde.c | 551 +++ drivers/media/platform/nvidia/tegra-vde/vde.h | 242 ++ drivers/media/platform/nxp/Kconfig | 43 + drivers/media/platform/nxp/Makefile | 8 + drivers/media/platform/nxp/dw100/Kconfig | 16 + drivers/media/platform/nxp/dw100/Makefile | 3 + drivers/media/platform/nxp/dw100/dw100.c | 1707 +++++++++ drivers/media/platform/nxp/dw100/dw100_regs.h | 117 + drivers/media/platform/nxp/imx-jpeg/Kconfig | 12 + drivers/media/platform/nxp/imx-jpeg/Makefile | 3 + drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c | 180 + drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h | 142 + drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c | 2388 ++++++++++++ drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h | 187 + drivers/media/platform/nxp/imx-mipi-csis.c | 1601 ++++++++ drivers/media/platform/nxp/imx-pxp.c | 1788 +++++++++ drivers/media/platform/nxp/imx-pxp.h | 1685 ++++++++ drivers/media/platform/nxp/mx2_emmaprp.c | 912 +++++ drivers/media/platform/qcom/Kconfig | 6 + drivers/media/platform/qcom/Makefile | 3 + drivers/media/platform/qcom/camss/Kconfig | 9 + drivers/media/platform/qcom/camss/Makefile | 23 + drivers/media/platform/qcom/camss/camss-csid-4-1.c | 328 ++ drivers/media/platform/qcom/camss/camss-csid-4-7.c | 404 ++ .../media/platform/qcom/camss/camss-csid-gen1.h | 27 + .../media/platform/qcom/camss/camss-csid-gen2.c | 630 +++ .../media/platform/qcom/camss/camss-csid-gen2.h | 39 + drivers/media/platform/qcom/camss/camss-csid.c | 908 +++++ drivers/media/platform/qcom/camss/camss-csid.h | 219 ++ .../platform/qcom/camss/camss-csiphy-2ph-1-0.c | 190 + .../platform/qcom/camss/camss-csiphy-3ph-1-0.c | 521 +++ drivers/media/platform/qcom/camss/camss-csiphy.c | 797 ++++ drivers/media/platform/qcom/camss/camss-csiphy.h | 100 + drivers/media/platform/qcom/camss/camss-ispif.c | 1453 +++++++ drivers/media/platform/qcom/camss/camss-ispif.h | 79 + drivers/media/platform/qcom/camss/camss-vfe-170.c | 774 ++++ drivers/media/platform/qcom/camss/camss-vfe-4-1.c | 1013 +++++ drivers/media/platform/qcom/camss/camss-vfe-4-7.c | 1209 ++++++ drivers/media/platform/qcom/camss/camss-vfe-4-8.c | 1194 ++++++ drivers/media/platform/qcom/camss/camss-vfe-480.c | 566 +++ drivers/media/platform/qcom/camss/camss-vfe-gen1.c | 742 ++++ drivers/media/platform/qcom/camss/camss-vfe-gen1.h | 117 + drivers/media/platform/qcom/camss/camss-vfe.c | 1619 ++++++++ drivers/media/platform/qcom/camss/camss-vfe.h | 206 + drivers/media/platform/qcom/camss/camss-video.c | 1063 ++++++ drivers/media/platform/qcom/camss/camss-video.h | 60 + drivers/media/platform/qcom/camss/camss.c | 1816 +++++++++ drivers/media/platform/qcom/camss/camss.h | 138 + drivers/media/platform/qcom/venus/Kconfig | 14 + drivers/media/platform/qcom/venus/Makefile | 15 + drivers/media/platform/qcom/venus/core.c | 906 +++++ drivers/media/platform/qcom/venus/core.h | 503 +++ drivers/media/platform/qcom/venus/dbgfs.c | 28 + drivers/media/platform/qcom/venus/dbgfs.h | 25 + drivers/media/platform/qcom/venus/firmware.c | 351 ++ drivers/media/platform/qcom/venus/firmware.h | 26 + drivers/media/platform/qcom/venus/helpers.c | 1838 +++++++++ drivers/media/platform/qcom/venus/helpers.h | 74 + drivers/media/platform/qcom/venus/hfi.c | 592 +++ drivers/media/platform/qcom/venus/hfi.h | 178 + drivers/media/platform/qcom/venus/hfi_cmds.c | 1354 +++++++ drivers/media/platform/qcom/venus/hfi_cmds.h | 296 ++ drivers/media/platform/qcom/venus/hfi_helper.h | 1237 ++++++ drivers/media/platform/qcom/venus/hfi_msgs.c | 796 ++++ drivers/media/platform/qcom/venus/hfi_msgs.h | 276 ++ drivers/media/platform/qcom/venus/hfi_parser.c | 342 ++ drivers/media/platform/qcom/venus/hfi_parser.h | 120 + drivers/media/platform/qcom/venus/hfi_plat_bufs.h | 38 + .../media/platform/qcom/venus/hfi_plat_bufs_v6.c | 1329 +++++++ drivers/media/platform/qcom/venus/hfi_platform.c | 90 + drivers/media/platform/qcom/venus/hfi_platform.h | 71 + .../media/platform/qcom/venus/hfi_platform_v4.c | 331 ++ .../media/platform/qcom/venus/hfi_platform_v6.c | 332 ++ drivers/media/platform/qcom/venus/hfi_venus.c | 1777 +++++++++ drivers/media/platform/qcom/venus/hfi_venus.h | 15 + drivers/media/platform/qcom/venus/hfi_venus_io.h | 160 + drivers/media/platform/qcom/venus/pm_helpers.c | 1207 ++++++ drivers/media/platform/qcom/venus/pm_helpers.h | 66 + drivers/media/platform/qcom/venus/vdec.c | 1847 +++++++++ drivers/media/platform/qcom/venus/vdec.h | 14 + drivers/media/platform/qcom/venus/vdec_ctrls.c | 194 + drivers/media/platform/qcom/venus/venc.c | 1557 ++++++++ drivers/media/platform/qcom/venus/venc.h | 14 + drivers/media/platform/qcom/venus/venc_ctrls.c | 642 ++++ drivers/media/platform/renesas/Kconfig | 121 + drivers/media/platform/renesas/Makefile | 15 + drivers/media/platform/renesas/rcar-fcp.c | 182 + drivers/media/platform/renesas/rcar-isp.c | 535 +++ drivers/media/platform/renesas/rcar-vin/Kconfig | 32 + drivers/media/platform/renesas/rcar-vin/Makefile | 5 + .../media/platform/renesas/rcar-vin/rcar-core.c | 1466 +++++++ .../media/platform/renesas/rcar-vin/rcar-csi2.c | 1606 ++++++++ drivers/media/platform/renesas/rcar-vin/rcar-dma.c | 1577 ++++++++ .../media/platform/renesas/rcar-vin/rcar-v4l2.c | 1093 ++++++ drivers/media/platform/renesas/rcar-vin/rcar-vin.h | 315 ++ drivers/media/platform/renesas/rcar_drif.c | 1487 ++++++++ drivers/media/platform/renesas/rcar_fdp1.c | 2462 ++++++++++++ drivers/media/platform/renesas/rcar_jpu.c | 1762 +++++++++ drivers/media/platform/renesas/renesas-ceu.c | 1749 +++++++++ drivers/media/platform/renesas/sh_vou.c | 1375 +++++++ drivers/media/platform/renesas/vsp1/Makefile | 10 + drivers/media/platform/renesas/vsp1/vsp1.h | 127 + drivers/media/platform/renesas/vsp1/vsp1_brx.c | 455 +++ drivers/media/platform/renesas/vsp1/vsp1_brx.h | 44 + drivers/media/platform/renesas/vsp1/vsp1_clu.c | 286 ++ drivers/media/platform/renesas/vsp1/vsp1_clu.h | 45 + drivers/media/platform/renesas/vsp1/vsp1_dl.c | 1169 ++++++ drivers/media/platform/renesas/vsp1/vsp1_dl.h | 79 + drivers/media/platform/renesas/vsp1/vsp1_drm.c | 996 +++++ drivers/media/platform/renesas/vsp1/vsp1_drm.h | 76 + drivers/media/platform/renesas/vsp1/vsp1_drv.c | 1015 +++++ drivers/media/platform/renesas/vsp1/vsp1_entity.c | 700 ++++ drivers/media/platform/renesas/vsp1/vsp1_entity.h | 194 + drivers/media/platform/renesas/vsp1/vsp1_hgo.c | 223 ++ drivers/media/platform/renesas/vsp1/vsp1_hgo.h | 41 + drivers/media/platform/renesas/vsp1/vsp1_hgt.c | 215 ++ drivers/media/platform/renesas/vsp1/vsp1_hgt.h | 38 + drivers/media/platform/renesas/vsp1/vsp1_histo.c | 593 +++ drivers/media/platform/renesas/vsp1/vsp1_histo.h | 77 + drivers/media/platform/renesas/vsp1/vsp1_hsit.c | 178 + drivers/media/platform/renesas/vsp1/vsp1_hsit.h | 34 + drivers/media/platform/renesas/vsp1/vsp1_lif.c | 177 + drivers/media/platform/renesas/vsp1/vsp1_lif.h | 33 + drivers/media/platform/renesas/vsp1/vsp1_lut.c | 242 ++ drivers/media/platform/renesas/vsp1/vsp1_lut.h | 42 + drivers/media/platform/renesas/vsp1/vsp1_pipe.c | 450 +++ drivers/media/platform/renesas/vsp1/vsp1_pipe.h | 175 + drivers/media/platform/renesas/vsp1/vsp1_regs.h | 860 +++++ drivers/media/platform/renesas/vsp1/vsp1_rpf.c | 382 ++ drivers/media/platform/renesas/vsp1/vsp1_rwpf.c | 287 ++ drivers/media/platform/renesas/vsp1/vsp1_rwpf.h | 89 + drivers/media/platform/renesas/vsp1/vsp1_sru.c | 396 ++ drivers/media/platform/renesas/vsp1/vsp1_sru.h | 38 + drivers/media/platform/renesas/vsp1/vsp1_uds.c | 429 +++ drivers/media/platform/renesas/vsp1/vsp1_uds.h | 37 + drivers/media/platform/renesas/vsp1/vsp1_uif.c | 268 ++ drivers/media/platform/renesas/vsp1/vsp1_uif.h | 32 + drivers/media/platform/renesas/vsp1/vsp1_video.c | 1326 +++++++ drivers/media/platform/renesas/vsp1/vsp1_video.h | 61 + drivers/media/platform/renesas/vsp1/vsp1_wpf.c | 613 +++ drivers/media/platform/rockchip/Kconfig | 6 + drivers/media/platform/rockchip/Makefile | 3 + drivers/media/platform/rockchip/rga/Kconfig | 14 + drivers/media/platform/rockchip/rga/Makefile | 4 + drivers/media/platform/rockchip/rga/rga-buf.c | 148 + drivers/media/platform/rockchip/rga/rga-hw.c | 416 ++ drivers/media/platform/rockchip/rga/rga-hw.h | 434 +++ drivers/media/platform/rockchip/rga/rga.c | 1001 +++++ drivers/media/platform/rockchip/rga/rga.h | 115 + drivers/media/platform/rockchip/rkisp1/Kconfig | 19 + drivers/media/platform/rockchip/rkisp1/Makefile | 14 + .../platform/rockchip/rkisp1/rkisp1-capture.c | 1455 +++++++ .../media/platform/rockchip/rkisp1/rkisp1-common.c | 180 + .../media/platform/rockchip/rkisp1/rkisp1-common.h | 630 +++ .../media/platform/rockchip/rkisp1/rkisp1-csi.c | 536 +++ .../media/platform/rockchip/rkisp1/rkisp1-csi.h | 28 + .../media/platform/rockchip/rkisp1/rkisp1-debug.c | 243 ++ .../media/platform/rockchip/rkisp1/rkisp1-dev.c | 661 ++++ .../media/platform/rockchip/rkisp1/rkisp1-isp.c | 1058 +++++ .../media/platform/rockchip/rkisp1/rkisp1-params.c | 1976 ++++++++++ .../media/platform/rockchip/rkisp1/rkisp1-regs.h | 1367 +++++++ .../platform/rockchip/rkisp1/rkisp1-resizer.c | 817 ++++ .../media/platform/rockchip/rkisp1/rkisp1-stats.c | 496 +++ drivers/media/platform/samsung/Kconfig | 10 + drivers/media/platform/samsung/Makefile | 7 + drivers/media/platform/samsung/exynos-gsc/Kconfig | 10 + drivers/media/platform/samsung/exynos-gsc/Makefile | 4 + .../media/platform/samsung/exynos-gsc/gsc-core.c | 1326 +++++++ .../media/platform/samsung/exynos-gsc/gsc-core.h | 521 +++ .../media/platform/samsung/exynos-gsc/gsc-m2m.c | 789 ++++ .../media/platform/samsung/exynos-gsc/gsc-regs.c | 426 +++ .../media/platform/samsung/exynos-gsc/gsc-regs.h | 169 + drivers/media/platform/samsung/exynos4-is/Kconfig | 84 + drivers/media/platform/samsung/exynos4-is/Makefile | 18 + drivers/media/platform/samsung/exynos4-is/common.c | 47 + drivers/media/platform/samsung/exynos4-is/common.h | 12 + .../platform/samsung/exynos4-is/fimc-capture.c | 1894 +++++++++ .../media/platform/samsung/exynos4-is/fimc-core.c | 1179 ++++++ .../media/platform/samsung/exynos4-is/fimc-core.h | 725 ++++ .../platform/samsung/exynos4-is/fimc-is-command.h | 134 + .../platform/samsung/exynos4-is/fimc-is-errno.c | 269 ++ .../platform/samsung/exynos4-is/fimc-is-errno.h | 245 ++ .../platform/samsung/exynos4-is/fimc-is-i2c.c | 159 + .../platform/samsung/exynos4-is/fimc-is-i2c.h | 12 + .../platform/samsung/exynos4-is/fimc-is-param.c | 893 +++++ .../platform/samsung/exynos4-is/fimc-is-param.h | 1022 +++++ .../platform/samsung/exynos4-is/fimc-is-regs.c | 230 ++ .../platform/samsung/exynos4-is/fimc-is-regs.h | 161 + .../platform/samsung/exynos4-is/fimc-is-sensor.c | 31 + .../platform/samsung/exynos4-is/fimc-is-sensor.h | 53 + .../media/platform/samsung/exynos4-is/fimc-is.c | 989 +++++ .../media/platform/samsung/exynos4-is/fimc-is.h | 359 ++ .../platform/samsung/exynos4-is/fimc-isp-video.c | 655 ++++ .../platform/samsung/exynos4-is/fimc-isp-video.h | 41 + .../media/platform/samsung/exynos4-is/fimc-isp.c | 789 ++++ .../media/platform/samsung/exynos4-is/fimc-isp.h | 197 + .../platform/samsung/exynos4-is/fimc-lite-reg.c | 346 ++ .../platform/samsung/exynos4-is/fimc-lite-reg.h | 155 + .../media/platform/samsung/exynos4-is/fimc-lite.c | 1668 ++++++++ .../media/platform/samsung/exynos4-is/fimc-lite.h | 224 ++ .../media/platform/samsung/exynos4-is/fimc-m2m.c | 773 ++++ .../media/platform/samsung/exynos4-is/fimc-reg.c | 846 ++++ .../media/platform/samsung/exynos4-is/fimc-reg.h | 338 ++ .../media/platform/samsung/exynos4-is/media-dev.c | 1606 ++++++++ .../media/platform/samsung/exynos4-is/media-dev.h | 201 + .../media/platform/samsung/exynos4-is/mipi-csis.c | 1037 +++++ .../media/platform/samsung/exynos4-is/mipi-csis.h | 23 + drivers/media/platform/samsung/s3c-camif/Kconfig | 15 + drivers/media/platform/samsung/s3c-camif/Makefile | 6 + .../platform/samsung/s3c-camif/camif-capture.c | 1650 ++++++++ .../media/platform/samsung/s3c-camif/camif-core.c | 639 ++++ .../media/platform/samsung/s3c-camif/camif-core.h | 397 ++ .../media/platform/samsung/s3c-camif/camif-regs.c | 603 +++ .../media/platform/samsung/s3c-camif/camif-regs.h | 268 ++ drivers/media/platform/samsung/s5p-g2d/Kconfig | 11 + drivers/media/platform/samsung/s5p-g2d/Makefile | 4 + drivers/media/platform/samsung/s5p-g2d/g2d-hw.c | 113 + drivers/media/platform/samsung/s5p-g2d/g2d-regs.h | 118 + drivers/media/platform/samsung/s5p-g2d/g2d.c | 792 ++++ drivers/media/platform/samsung/s5p-g2d/g2d.h | 84 + drivers/media/platform/samsung/s5p-jpeg/Kconfig | 12 + drivers/media/platform/samsung/s5p-jpeg/Makefile | 3 + .../media/platform/samsung/s5p-jpeg/jpeg-core.c | 3180 ++++++++++++++++ .../media/platform/samsung/s5p-jpeg/jpeg-core.h | 267 ++ .../platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c | 486 +++ .../platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h | 57 + .../platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c | 321 ++ .../platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h | 44 + .../media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c | 305 ++ .../media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h | 57 + .../media/platform/samsung/s5p-jpeg/jpeg-regs.h | 646 ++++ drivers/media/platform/samsung/s5p-mfc/Kconfig | 9 + drivers/media/platform/samsung/s5p-mfc/Makefile | 7 + .../media/platform/samsung/s5p-mfc/regs-mfc-v10.h | 87 + .../media/platform/samsung/s5p-mfc/regs-mfc-v6.h | 408 ++ .../media/platform/samsung/s5p-mfc/regs-mfc-v7.h | 57 + .../media/platform/samsung/s5p-mfc/regs-mfc-v8.h | 123 + drivers/media/platform/samsung/s5p-mfc/regs-mfc.h | 459 +++ drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c | 1692 ++++++++ .../media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c | 25 + .../media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h | 31 + .../platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c | 163 + .../platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h | 16 + .../platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c | 169 + .../platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h | 16 + .../platform/samsung/s5p-mfc/s5p_mfc_common.h | 792 ++++ .../media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c | 484 +++ .../media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h | 29 + .../media/platform/samsung/s5p-mfc/s5p_mfc_debug.h | 51 + .../media/platform/samsung/s5p-mfc/s5p_mfc_dec.c | 1216 ++++++ .../media/platform/samsung/s5p-mfc/s5p_mfc_dec.h | 20 + .../media/platform/samsung/s5p-mfc/s5p_mfc_enc.c | 2703 +++++++++++++ .../media/platform/samsung/s5p-mfc/s5p_mfc_enc.h | 20 + .../media/platform/samsung/s5p-mfc/s5p_mfc_intr.c | 88 + .../media/platform/samsung/s5p-mfc/s5p_mfc_intr.h | 23 + .../media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h | 28 + .../media/platform/samsung/s5p-mfc/s5p_mfc_opr.c | 124 + .../media/platform/samsung/s5p-mfc/s5p_mfc_opr.h | 339 ++ .../platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c | 1637 ++++++++ .../platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h | 82 + .../platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c | 2534 ++++++++++++ .../platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h | 57 + .../media/platform/samsung/s5p-mfc/s5p_mfc_pm.c | 118 + .../media/platform/samsung/s5p-mfc/s5p_mfc_pm.h | 20 + drivers/media/platform/st/Kconfig | 6 + drivers/media/platform/st/Makefile | 7 + drivers/media/platform/st/sti/Kconfig | 5 + drivers/media/platform/st/sti/Makefile | 6 + drivers/media/platform/st/sti/bdisp/Kconfig | 10 + drivers/media/platform/st/sti/bdisp/Makefile | 4 + drivers/media/platform/st/sti/bdisp/bdisp-debug.c | 658 ++++ drivers/media/platform/st/sti/bdisp/bdisp-filter.h | 42 + drivers/media/platform/st/sti/bdisp/bdisp-hw.c | 1118 ++++++ drivers/media/platform/st/sti/bdisp/bdisp-reg.h | 235 ++ drivers/media/platform/st/sti/bdisp/bdisp-v4l2.c | 1428 +++++++ drivers/media/platform/st/sti/bdisp/bdisp.h | 214 ++ drivers/media/platform/st/sti/c8sectpfe/Kconfig | 29 + drivers/media/platform/st/sti/c8sectpfe/Makefile | 8 + .../platform/st/sti/c8sectpfe/c8sectpfe-common.c | 262 ++ .../platform/st/sti/c8sectpfe/c8sectpfe-common.h | 60 + .../platform/st/sti/c8sectpfe/c8sectpfe-core.c | 1187 ++++++ .../platform/st/sti/c8sectpfe/c8sectpfe-core.h | 285 ++ .../platform/st/sti/c8sectpfe/c8sectpfe-debugfs.c | 244 ++ .../platform/st/sti/c8sectpfe/c8sectpfe-debugfs.h | 18 + .../platform/st/sti/c8sectpfe/c8sectpfe-dvb.c | 235 ++ .../platform/st/sti/c8sectpfe/c8sectpfe-dvb.h | 17 + drivers/media/platform/st/sti/delta/Kconfig | 36 + drivers/media/platform/st/sti/delta/Makefile | 7 + drivers/media/platform/st/sti/delta/delta-cfg.h | 64 + drivers/media/platform/st/sti/delta/delta-debug.c | 72 + drivers/media/platform/st/sti/delta/delta-debug.h | 18 + drivers/media/platform/st/sti/delta/delta-ipc.c | 591 +++ drivers/media/platform/st/sti/delta/delta-ipc.h | 76 + drivers/media/platform/st/sti/delta/delta-mem.c | 51 + drivers/media/platform/st/sti/delta/delta-mem.h | 14 + .../media/platform/st/sti/delta/delta-mjpeg-dec.c | 455 +++ .../media/platform/st/sti/delta/delta-mjpeg-fw.h | 225 ++ .../media/platform/st/sti/delta/delta-mjpeg-hdr.c | 149 + drivers/media/platform/st/sti/delta/delta-mjpeg.h | 35 + drivers/media/platform/st/sti/delta/delta-v4l2.c | 1970 ++++++++++ drivers/media/platform/st/sti/delta/delta.h | 566 +++ drivers/media/platform/st/sti/hva/Kconfig | 26 + drivers/media/platform/st/sti/hva/Makefile | 4 + drivers/media/platform/st/sti/hva/hva-debugfs.c | 396 ++ drivers/media/platform/st/sti/hva/hva-h264.c | 1063 ++++++ drivers/media/platform/st/sti/hva/hva-hw.c | 585 +++ drivers/media/platform/st/sti/hva/hva-hw.h | 45 + drivers/media/platform/st/sti/hva/hva-mem.c | 62 + drivers/media/platform/st/sti/hva/hva-mem.h | 34 + drivers/media/platform/st/sti/hva/hva-v4l2.c | 1476 +++++++ drivers/media/platform/st/sti/hva/hva.h | 409 ++ drivers/media/platform/st/stm32/Kconfig | 31 + drivers/media/platform/st/stm32/Makefile | 4 + drivers/media/platform/st/stm32/dma2d/dma2d-hw.c | 133 + drivers/media/platform/st/stm32/dma2d/dma2d-regs.h | 113 + drivers/media/platform/st/stm32/dma2d/dma2d.c | 736 ++++ drivers/media/platform/st/stm32/dma2d/dma2d.h | 135 + drivers/media/platform/st/stm32/stm32-dcmi.c | 2229 +++++++++++ drivers/media/platform/sunxi/Kconfig | 10 + drivers/media/platform/sunxi/Makefile | 8 + drivers/media/platform/sunxi/sun4i-csi/Kconfig | 16 + drivers/media/platform/sunxi/sun4i-csi/Makefile | 7 + drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c | 343 ++ drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.h | 161 + drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c | 458 +++ .../media/platform/sunxi/sun4i-csi/sun4i_v4l2.c | 380 ++ drivers/media/platform/sunxi/sun6i-csi/Kconfig | 15 + drivers/media/platform/sunxi/sun6i-csi/Makefile | 4 + drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c | 1033 +++++ drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h | 166 + .../media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h | 196 + .../media/platform/sunxi/sun6i-csi/sun6i_video.c | 733 ++++ .../media/platform/sunxi/sun6i-csi/sun6i_video.h | 35 + .../media/platform/sunxi/sun6i-mipi-csi2/Kconfig | 15 + .../media/platform/sunxi/sun6i-mipi-csi2/Makefile | 4 + .../sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c | 771 ++++ .../sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h | 52 + .../sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2_reg.h | 76 + .../platform/sunxi/sun8i-a83t-mipi-csi2/Kconfig | 13 + .../platform/sunxi/sun8i-a83t-mipi-csi2/Makefile | 4 + .../sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.c | 72 + .../sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.h | 39 + .../sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c | 838 ++++ .../sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h | 55 + .../sun8i_a83t_mipi_csi2_reg.h | 151 + drivers/media/platform/sunxi/sun8i-di/Kconfig | 14 + drivers/media/platform/sunxi/sun8i-di/Makefile | 2 + drivers/media/platform/sunxi/sun8i-di/sun8i-di.c | 1016 +++++ drivers/media/platform/sunxi/sun8i-di/sun8i-di.h | 237 ++ drivers/media/platform/sunxi/sun8i-rotate/Kconfig | 14 + drivers/media/platform/sunxi/sun8i-rotate/Makefile | 5 + .../platform/sunxi/sun8i-rotate/sun8i-formats.h | 25 + .../platform/sunxi/sun8i-rotate/sun8i-rotate.h | 135 + .../platform/sunxi/sun8i-rotate/sun8i_formats.c | 273 ++ .../platform/sunxi/sun8i-rotate/sun8i_rotate.c | 921 +++++ drivers/media/platform/ti/Kconfig | 69 + drivers/media/platform/ti/Makefile | 7 + drivers/media/platform/ti/am437x/Kconfig | 16 + drivers/media/platform/ti/am437x/Makefile | 4 + drivers/media/platform/ti/am437x/am437x-vpfe.c | 2642 +++++++++++++ drivers/media/platform/ti/am437x/am437x-vpfe.h | 288 ++ .../media/platform/ti/am437x/am437x-vpfe_regs.h | 132 + drivers/media/platform/ti/cal/Makefile | 3 + drivers/media/platform/ti/cal/cal-camerarx.c | 943 +++++ drivers/media/platform/ti/cal/cal-video.c | 1046 +++++ drivers/media/platform/ti/cal/cal.c | 1375 +++++++ drivers/media/platform/ti/cal/cal.h | 349 ++ drivers/media/platform/ti/cal/cal_regs.h | 463 +++ drivers/media/platform/ti/davinci/Kconfig | 49 + drivers/media/platform/ti/davinci/Makefile | 12 + drivers/media/platform/ti/davinci/vpbe.c | 840 ++++ drivers/media/platform/ti/davinci/vpbe_display.c | 1510 ++++++++ drivers/media/platform/ti/davinci/vpbe_osd.c | 1582 ++++++++ drivers/media/platform/ti/davinci/vpbe_osd_regs.h | 352 ++ drivers/media/platform/ti/davinci/vpbe_venc.c | 676 ++++ drivers/media/platform/ti/davinci/vpbe_venc_regs.h | 165 + drivers/media/platform/ti/davinci/vpif.c | 610 +++ drivers/media/platform/ti/davinci/vpif.h | 679 ++++ drivers/media/platform/ti/davinci/vpif_capture.c | 1821 +++++++++ drivers/media/platform/ti/davinci/vpif_capture.h | 107 + drivers/media/platform/ti/davinci/vpif_display.c | 1405 +++++++ drivers/media/platform/ti/davinci/vpif_display.h | 116 + drivers/media/platform/ti/davinci/vpss.c | 529 +++ drivers/media/platform/ti/omap/Kconfig | 17 + drivers/media/platform/ti/omap/Makefile | 9 + drivers/media/platform/ti/omap/omap_vout.c | 1742 +++++++++ drivers/media/platform/ti/omap/omap_vout_vrfb.c | 419 ++ drivers/media/platform/ti/omap/omap_vout_vrfb.h | 40 + drivers/media/platform/ti/omap/omap_voutdef.h | 219 ++ drivers/media/platform/ti/omap/omap_voutlib.c | 361 ++ drivers/media/platform/ti/omap/omap_voutlib.h | 39 + drivers/media/platform/ti/omap3isp/Kconfig | 21 + drivers/media/platform/ti/omap3isp/Makefile | 12 + .../media/platform/ti/omap3isp/cfa_coef_table.h | 48 + drivers/media/platform/ti/omap3isp/gamma_table.h | 77 + drivers/media/platform/ti/omap3isp/isp.c | 2494 ++++++++++++ drivers/media/platform/ti/omap3isp/isp.h | 360 ++ drivers/media/platform/ti/omap3isp/ispccdc.c | 2760 ++++++++++++++ drivers/media/platform/ti/omap3isp/ispccdc.h | 174 + drivers/media/platform/ti/omap3isp/ispccp2.c | 1181 ++++++ drivers/media/platform/ti/omap3isp/ispccp2.h | 85 + drivers/media/platform/ti/omap3isp/ispcsi2.c | 1322 +++++++ drivers/media/platform/ti/omap3isp/ispcsi2.h | 152 + drivers/media/platform/ti/omap3isp/ispcsiphy.c | 356 ++ drivers/media/platform/ti/omap3isp/ispcsiphy.h | 43 + drivers/media/platform/ti/omap3isp/isph3a.h | 104 + drivers/media/platform/ti/omap3isp/isph3a_aewb.c | 350 ++ drivers/media/platform/ti/omap3isp/isph3a_af.c | 405 ++ drivers/media/platform/ti/omap3isp/isphist.c | 540 +++ drivers/media/platform/ti/omap3isp/isphist.h | 27 + drivers/media/platform/ti/omap3isp/isppreview.c | 2363 ++++++++++++ drivers/media/platform/ti/omap3isp/isppreview.h | 161 + drivers/media/platform/ti/omap3isp/ispreg.h | 1518 ++++++++ drivers/media/platform/ti/omap3isp/ispresizer.c | 1801 +++++++++ drivers/media/platform/ti/omap3isp/ispresizer.h | 136 + drivers/media/platform/ti/omap3isp/ispstat.c | 1083 ++++++ drivers/media/platform/ti/omap3isp/ispstat.h | 156 + drivers/media/platform/ti/omap3isp/ispvideo.c | 1497 ++++++++ drivers/media/platform/ti/omap3isp/ispvideo.h | 214 ++ .../platform/ti/omap3isp/luma_enhance_table.h | 29 + .../platform/ti/omap3isp/noise_filter_table.h | 17 + drivers/media/platform/ti/omap3isp/omap3isp.h | 132 + drivers/media/platform/ti/vpe/Makefile | 12 + drivers/media/platform/ti/vpe/csc.c | 279 ++ drivers/media/platform/ti/vpe/csc.h | 65 + drivers/media/platform/ti/vpe/sc.c | 306 ++ drivers/media/platform/ti/vpe/sc.h | 208 + drivers/media/platform/ti/vpe/sc_coeff.h | 1339 +++++++ drivers/media/platform/ti/vpe/vpdma.c | 1176 ++++++ drivers/media/platform/ti/vpe/vpdma.h | 284 ++ drivers/media/platform/ti/vpe/vpdma_priv.h | 639 ++++ drivers/media/platform/ti/vpe/vpe.c | 2665 +++++++++++++ drivers/media/platform/ti/vpe/vpe_regs.h | 306 ++ drivers/media/platform/verisilicon/Kconfig | 54 + drivers/media/platform/verisilicon/Makefile | 38 + drivers/media/platform/verisilicon/hantro.h | 485 +++ drivers/media/platform/verisilicon/hantro_drv.c | 1153 ++++++ drivers/media/platform/verisilicon/hantro_g1.c | 39 + .../platform/verisilicon/hantro_g1_h264_dec.c | 284 ++ .../platform/verisilicon/hantro_g1_mpeg2_dec.c | 240 ++ .../media/platform/verisilicon/hantro_g1_regs.h | 356 ++ .../media/platform/verisilicon/hantro_g1_vp8_dec.c | 511 +++ drivers/media/platform/verisilicon/hantro_g2.c | 44 + .../platform/verisilicon/hantro_g2_hevc_dec.c | 627 +++ .../media/platform/verisilicon/hantro_g2_regs.h | 325 ++ .../media/platform/verisilicon/hantro_g2_vp9_dec.c | 1014 +++++ .../platform/verisilicon/hantro_h1_jpeg_enc.c | 166 + .../media/platform/verisilicon/hantro_h1_regs.h | 154 + drivers/media/platform/verisilicon/hantro_h264.c | 521 +++ drivers/media/platform/verisilicon/hantro_hevc.c | 284 ++ drivers/media/platform/verisilicon/hantro_hw.h | 441 +++ drivers/media/platform/verisilicon/hantro_jpeg.c | 348 ++ drivers/media/platform/verisilicon/hantro_jpeg.h | 15 + drivers/media/platform/verisilicon/hantro_mpeg2.c | 61 + .../media/platform/verisilicon/hantro_postproc.c | 284 ++ drivers/media/platform/verisilicon/hantro_v4l2.c | 996 +++++ drivers/media/platform/verisilicon/hantro_v4l2.h | 29 + drivers/media/platform/verisilicon/hantro_vp8.c | 201 + drivers/media/platform/verisilicon/hantro_vp9.c | 240 ++ drivers/media/platform/verisilicon/hantro_vp9.h | 102 + drivers/media/platform/verisilicon/imx8m_vpu_hw.c | 400 ++ .../verisilicon/rockchip_vpu2_hw_h264_dec.c | 491 +++ .../verisilicon/rockchip_vpu2_hw_jpeg_enc.c | 197 + .../verisilicon/rockchip_vpu2_hw_mpeg2_dec.c | 248 ++ .../verisilicon/rockchip_vpu2_hw_vp8_dec.c | 600 +++ .../platform/verisilicon/rockchip_vpu2_regs.h | 600 +++ .../media/platform/verisilicon/rockchip_vpu_hw.c | 680 ++++ .../media/platform/verisilicon/sama5d4_vdec_hw.c | 128 + drivers/media/platform/verisilicon/sunxi_vpu_hw.c | 129 + drivers/media/platform/via/Kconfig | 14 + drivers/media/platform/via/Makefile | 2 + drivers/media/platform/via/via-camera.c | 1323 +++++++ drivers/media/platform/via/via-camera.h | 94 + drivers/media/platform/video-mux.c | 517 +++ drivers/media/platform/xilinx/Kconfig | 35 + drivers/media/platform/xilinx/Makefile | 8 + drivers/media/platform/xilinx/xilinx-csi2rxss.c | 1098 ++++++ drivers/media/platform/xilinx/xilinx-dma.c | 759 ++++ drivers/media/platform/xilinx/xilinx-dma.h | 109 + drivers/media/platform/xilinx/xilinx-tpg.c | 933 +++++ drivers/media/platform/xilinx/xilinx-vip.c | 320 ++ drivers/media/platform/xilinx/xilinx-vip.h | 234 ++ drivers/media/platform/xilinx/xilinx-vipp.c | 649 ++++ drivers/media/platform/xilinx/xilinx-vipp.h | 42 + drivers/media/platform/xilinx/xilinx-vtc.c | 377 ++ drivers/media/platform/xilinx/xilinx-vtc.h | 39 + 687 files changed, 302686 insertions(+) create mode 100644 drivers/media/platform/Kconfig create mode 100644 drivers/media/platform/Makefile create mode 100644 drivers/media/platform/allegro-dvt/Kconfig create mode 100644 drivers/media/platform/allegro-dvt/Makefile create mode 100644 drivers/media/platform/allegro-dvt/allegro-core.c create mode 100644 drivers/media/platform/allegro-dvt/allegro-mail.c create mode 100644 drivers/media/platform/allegro-dvt/allegro-mail.h create mode 100644 drivers/media/platform/allegro-dvt/nal-h264.c create mode 100644 drivers/media/platform/allegro-dvt/nal-h264.h create mode 100644 drivers/media/platform/allegro-dvt/nal-hevc.c create mode 100644 drivers/media/platform/allegro-dvt/nal-hevc.h create mode 100644 drivers/media/platform/allegro-dvt/nal-rbsp.c create mode 100644 drivers/media/platform/allegro-dvt/nal-rbsp.h create mode 100644 drivers/media/platform/amlogic/Kconfig create mode 100644 drivers/media/platform/amlogic/Makefile create mode 100644 drivers/media/platform/amlogic/meson-ge2d/Kconfig create mode 100644 drivers/media/platform/amlogic/meson-ge2d/Makefile create mode 100644 drivers/media/platform/amlogic/meson-ge2d/ge2d-regs.h create mode 100644 drivers/media/platform/amlogic/meson-ge2d/ge2d.c create mode 100644 drivers/media/platform/amphion/Kconfig create mode 100644 drivers/media/platform/amphion/Makefile create mode 100644 drivers/media/platform/amphion/vdec.c create mode 100644 drivers/media/platform/amphion/venc.c create mode 100644 drivers/media/platform/amphion/vpu.h create mode 100644 drivers/media/platform/amphion/vpu_cmds.c create mode 100644 drivers/media/platform/amphion/vpu_cmds.h create mode 100644 drivers/media/platform/amphion/vpu_codec.h create mode 100644 drivers/media/platform/amphion/vpu_color.c create mode 100644 drivers/media/platform/amphion/vpu_core.c create mode 100644 drivers/media/platform/amphion/vpu_core.h create mode 100644 drivers/media/platform/amphion/vpu_dbg.c create mode 100644 drivers/media/platform/amphion/vpu_defs.h create mode 100644 drivers/media/platform/amphion/vpu_drv.c create mode 100644 drivers/media/platform/amphion/vpu_helpers.c create mode 100644 drivers/media/platform/amphion/vpu_helpers.h create mode 100644 drivers/media/platform/amphion/vpu_imx8q.c create mode 100644 drivers/media/platform/amphion/vpu_imx8q.h create mode 100644 drivers/media/platform/amphion/vpu_malone.c create mode 100644 drivers/media/platform/amphion/vpu_malone.h create mode 100644 drivers/media/platform/amphion/vpu_mbox.c create mode 100644 drivers/media/platform/amphion/vpu_mbox.h create mode 100644 drivers/media/platform/amphion/vpu_msgs.c create mode 100644 drivers/media/platform/amphion/vpu_msgs.h create mode 100644 drivers/media/platform/amphion/vpu_rpc.c create mode 100644 drivers/media/platform/amphion/vpu_rpc.h create mode 100644 drivers/media/platform/amphion/vpu_v4l2.c create mode 100644 drivers/media/platform/amphion/vpu_v4l2.h create mode 100644 drivers/media/platform/amphion/vpu_windsor.c create mode 100644 drivers/media/platform/amphion/vpu_windsor.h create mode 100644 drivers/media/platform/aspeed/Kconfig create mode 100644 drivers/media/platform/aspeed/Makefile create mode 100644 drivers/media/platform/aspeed/aspeed-video.c create mode 100644 drivers/media/platform/atmel/Kconfig create mode 100644 drivers/media/platform/atmel/Makefile create mode 100644 drivers/media/platform/atmel/atmel-isc-base.c create mode 100644 drivers/media/platform/atmel/atmel-isc-clk.c create mode 100644 drivers/media/platform/atmel/atmel-isc-regs.h create mode 100644 drivers/media/platform/atmel/atmel-isc.h create mode 100644 drivers/media/platform/atmel/atmel-isi.c create mode 100644 drivers/media/platform/atmel/atmel-isi.h create mode 100644 drivers/media/platform/atmel/atmel-sama5d2-isc.c create mode 100644 drivers/media/platform/atmel/atmel-sama7g5-isc.c create mode 100644 drivers/media/platform/atmel/microchip-csi2dc.c create mode 100644 drivers/media/platform/cadence/Kconfig create mode 100644 drivers/media/platform/cadence/Makefile create mode 100644 drivers/media/platform/cadence/cdns-csi2rx.c create mode 100644 drivers/media/platform/cadence/cdns-csi2tx.c create mode 100644 drivers/media/platform/chips-media/Kconfig create mode 100644 drivers/media/platform/chips-media/Makefile create mode 100644 drivers/media/platform/chips-media/coda-bit.c create mode 100644 drivers/media/platform/chips-media/coda-common.c create mode 100644 drivers/media/platform/chips-media/coda-gdi.c create mode 100644 drivers/media/platform/chips-media/coda-h264.c create mode 100644 drivers/media/platform/chips-media/coda-jpeg.c create mode 100644 drivers/media/platform/chips-media/coda-mpeg2.c create mode 100644 drivers/media/platform/chips-media/coda-mpeg4.c create mode 100644 drivers/media/platform/chips-media/coda.h create mode 100644 drivers/media/platform/chips-media/coda_regs.h create mode 100644 drivers/media/platform/chips-media/imx-vdoa.c create mode 100644 drivers/media/platform/chips-media/imx-vdoa.h create mode 100644 drivers/media/platform/chips-media/trace.h create mode 100644 drivers/media/platform/intel/Kconfig create mode 100644 drivers/media/platform/intel/Makefile create mode 100644 drivers/media/platform/intel/pxa_camera.c create mode 100644 drivers/media/platform/m2m-deinterlace.c create mode 100644 drivers/media/platform/marvell/Kconfig create mode 100644 drivers/media/platform/marvell/Makefile create mode 100644 drivers/media/platform/marvell/cafe-driver.c create mode 100644 drivers/media/platform/marvell/mcam-core.c create mode 100644 drivers/media/platform/marvell/mcam-core.h create mode 100644 drivers/media/platform/marvell/mmp-driver.c create mode 100644 drivers/media/platform/mediatek/Kconfig create mode 100644 drivers/media/platform/mediatek/Makefile create mode 100644 drivers/media/platform/mediatek/jpeg/Kconfig create mode 100644 drivers/media/platform/mediatek/jpeg/Makefile create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_parse.c create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_parse.h create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c create mode 100644 drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.h create mode 100644 drivers/media/platform/mediatek/mdp/Kconfig create mode 100644 drivers/media/platform/mediatek/mdp/Makefile create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_comp.h create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_core.c create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_core.h create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.c create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.h create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_regs.c create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_regs.h create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.c create mode 100644 drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.h create mode 100644 drivers/media/platform/mediatek/mdp3/Kconfig create mode 100644 drivers/media/platform/mediatek/mdp3/Makefile create mode 100644 drivers/media/platform/mediatek/mdp3/mdp_reg_ccorr.h create mode 100644 drivers/media/platform/mediatek/mdp3/mdp_reg_rdma.h create mode 100644 drivers/media/platform/mediatek/mdp3/mdp_reg_rsz.h create mode 100644 drivers/media/platform/mediatek/mdp3/mdp_reg_wdma.h create mode 100644 drivers/media/platform/mediatek/mdp3/mdp_reg_wrot.h create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.h create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.h create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.h create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.c create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.h create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-regs.c create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-regs.h create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-vpu.c create mode 100644 drivers/media/platform/mediatek/mdp3/mtk-mdp3-vpu.h create mode 100644 drivers/media/platform/mediatek/vcodec/Kconfig create mode 100644 drivers/media/platform/mediatek/vcodec/Makefile create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_drv.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_hw.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_hw.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateful.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_drv.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_drv.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_pm.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_pm.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_priv.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_scp.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_vpu.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_intr.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_intr.h create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_util.c create mode 100644 drivers/media/platform/mediatek/vcodec/mtk_vcodec_util.h create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_common.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_common.h create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_vp8_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_vp8_req_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec_drv_base.h create mode 100644 drivers/media/platform/mediatek/vcodec/vdec_drv_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec_drv_if.h create mode 100644 drivers/media/platform/mediatek/vcodec/vdec_ipi_msg.h create mode 100644 drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec_msg_queue.h create mode 100644 drivers/media/platform/mediatek/vcodec/vdec_vpu_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/vdec_vpu_if.h create mode 100644 drivers/media/platform/mediatek/vcodec/venc/venc_h264_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/venc/venc_vp8_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/venc_drv_base.h create mode 100644 drivers/media/platform/mediatek/vcodec/venc_drv_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/venc_drv_if.h create mode 100644 drivers/media/platform/mediatek/vcodec/venc_ipi_msg.h create mode 100644 drivers/media/platform/mediatek/vcodec/venc_vpu_if.c create mode 100644 drivers/media/platform/mediatek/vcodec/venc_vpu_if.h create mode 100644 drivers/media/platform/mediatek/vpu/Kconfig create mode 100644 drivers/media/platform/mediatek/vpu/Makefile create mode 100644 drivers/media/platform/mediatek/vpu/mtk_vpu.c create mode 100644 drivers/media/platform/mediatek/vpu/mtk_vpu.h create mode 100644 drivers/media/platform/nvidia/Kconfig create mode 100644 drivers/media/platform/nvidia/Makefile create mode 100644 drivers/media/platform/nvidia/tegra-vde/Kconfig create mode 100644 drivers/media/platform/nvidia/tegra-vde/Makefile create mode 100644 drivers/media/platform/nvidia/tegra-vde/dmabuf-cache.c create mode 100644 drivers/media/platform/nvidia/tegra-vde/h264.c create mode 100644 drivers/media/platform/nvidia/tegra-vde/iommu.c create mode 100644 drivers/media/platform/nvidia/tegra-vde/trace.h create mode 100644 drivers/media/platform/nvidia/tegra-vde/v4l2.c create mode 100644 drivers/media/platform/nvidia/tegra-vde/vde.c create mode 100644 drivers/media/platform/nvidia/tegra-vde/vde.h create mode 100644 drivers/media/platform/nxp/Kconfig create mode 100644 drivers/media/platform/nxp/Makefile create mode 100644 drivers/media/platform/nxp/dw100/Kconfig create mode 100644 drivers/media/platform/nxp/dw100/Makefile create mode 100644 drivers/media/platform/nxp/dw100/dw100.c create mode 100644 drivers/media/platform/nxp/dw100/dw100_regs.h create mode 100644 drivers/media/platform/nxp/imx-jpeg/Kconfig create mode 100644 drivers/media/platform/nxp/imx-jpeg/Makefile create mode 100644 drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c create mode 100644 drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h create mode 100644 drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c create mode 100644 drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h create mode 100644 drivers/media/platform/nxp/imx-mipi-csis.c create mode 100644 drivers/media/platform/nxp/imx-pxp.c create mode 100644 drivers/media/platform/nxp/imx-pxp.h create mode 100644 drivers/media/platform/nxp/mx2_emmaprp.c create mode 100644 drivers/media/platform/qcom/Kconfig create mode 100644 drivers/media/platform/qcom/Makefile create mode 100644 drivers/media/platform/qcom/camss/Kconfig create mode 100644 drivers/media/platform/qcom/camss/Makefile create mode 100644 drivers/media/platform/qcom/camss/camss-csid-4-1.c create mode 100644 drivers/media/platform/qcom/camss/camss-csid-4-7.c create mode 100644 drivers/media/platform/qcom/camss/camss-csid-gen1.h create mode 100644 drivers/media/platform/qcom/camss/camss-csid-gen2.c create mode 100644 drivers/media/platform/qcom/camss/camss-csid-gen2.h create mode 100644 drivers/media/platform/qcom/camss/camss-csid.c create mode 100644 drivers/media/platform/qcom/camss/camss-csid.h create mode 100644 drivers/media/platform/qcom/camss/camss-csiphy-2ph-1-0.c create mode 100644 drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c create mode 100644 drivers/media/platform/qcom/camss/camss-csiphy.c create mode 100644 drivers/media/platform/qcom/camss/camss-csiphy.h create mode 100644 drivers/media/platform/qcom/camss/camss-ispif.c create mode 100644 drivers/media/platform/qcom/camss/camss-ispif.h create mode 100644 drivers/media/platform/qcom/camss/camss-vfe-170.c create mode 100644 drivers/media/platform/qcom/camss/camss-vfe-4-1.c create mode 100644 drivers/media/platform/qcom/camss/camss-vfe-4-7.c create mode 100644 drivers/media/platform/qcom/camss/camss-vfe-4-8.c create mode 100644 drivers/media/platform/qcom/camss/camss-vfe-480.c create mode 100644 drivers/media/platform/qcom/camss/camss-vfe-gen1.c create mode 100644 drivers/media/platform/qcom/camss/camss-vfe-gen1.h create mode 100644 drivers/media/platform/qcom/camss/camss-vfe.c create mode 100644 drivers/media/platform/qcom/camss/camss-vfe.h create mode 100644 drivers/media/platform/qcom/camss/camss-video.c create mode 100644 drivers/media/platform/qcom/camss/camss-video.h create mode 100644 drivers/media/platform/qcom/camss/camss.c create mode 100644 drivers/media/platform/qcom/camss/camss.h create mode 100644 drivers/media/platform/qcom/venus/Kconfig create mode 100644 drivers/media/platform/qcom/venus/Makefile create mode 100644 drivers/media/platform/qcom/venus/core.c create mode 100644 drivers/media/platform/qcom/venus/core.h create mode 100644 drivers/media/platform/qcom/venus/dbgfs.c create mode 100644 drivers/media/platform/qcom/venus/dbgfs.h create mode 100644 drivers/media/platform/qcom/venus/firmware.c create mode 100644 drivers/media/platform/qcom/venus/firmware.h create mode 100644 drivers/media/platform/qcom/venus/helpers.c create mode 100644 drivers/media/platform/qcom/venus/helpers.h create mode 100644 drivers/media/platform/qcom/venus/hfi.c create mode 100644 drivers/media/platform/qcom/venus/hfi.h create mode 100644 drivers/media/platform/qcom/venus/hfi_cmds.c create mode 100644 drivers/media/platform/qcom/venus/hfi_cmds.h create mode 100644 drivers/media/platform/qcom/venus/hfi_helper.h create mode 100644 drivers/media/platform/qcom/venus/hfi_msgs.c create mode 100644 drivers/media/platform/qcom/venus/hfi_msgs.h create mode 100644 drivers/media/platform/qcom/venus/hfi_parser.c create mode 100644 drivers/media/platform/qcom/venus/hfi_parser.h create mode 100644 drivers/media/platform/qcom/venus/hfi_plat_bufs.h create mode 100644 drivers/media/platform/qcom/venus/hfi_plat_bufs_v6.c create mode 100644 drivers/media/platform/qcom/venus/hfi_platform.c create mode 100644 drivers/media/platform/qcom/venus/hfi_platform.h create mode 100644 drivers/media/platform/qcom/venus/hfi_platform_v4.c create mode 100644 drivers/media/platform/qcom/venus/hfi_platform_v6.c create mode 100644 drivers/media/platform/qcom/venus/hfi_venus.c create mode 100644 drivers/media/platform/qcom/venus/hfi_venus.h create mode 100644 drivers/media/platform/qcom/venus/hfi_venus_io.h create mode 100644 drivers/media/platform/qcom/venus/pm_helpers.c create mode 100644 drivers/media/platform/qcom/venus/pm_helpers.h create mode 100644 drivers/media/platform/qcom/venus/vdec.c create mode 100644 drivers/media/platform/qcom/venus/vdec.h create mode 100644 drivers/media/platform/qcom/venus/vdec_ctrls.c create mode 100644 drivers/media/platform/qcom/venus/venc.c create mode 100644 drivers/media/platform/qcom/venus/venc.h create mode 100644 drivers/media/platform/qcom/venus/venc_ctrls.c create mode 100644 drivers/media/platform/renesas/Kconfig create mode 100644 drivers/media/platform/renesas/Makefile create mode 100644 drivers/media/platform/renesas/rcar-fcp.c create mode 100644 drivers/media/platform/renesas/rcar-isp.c create mode 100644 drivers/media/platform/renesas/rcar-vin/Kconfig create mode 100644 drivers/media/platform/renesas/rcar-vin/Makefile create mode 100644 drivers/media/platform/renesas/rcar-vin/rcar-core.c create mode 100644 drivers/media/platform/renesas/rcar-vin/rcar-csi2.c create mode 100644 drivers/media/platform/renesas/rcar-vin/rcar-dma.c create mode 100644 drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c create mode 100644 drivers/media/platform/renesas/rcar-vin/rcar-vin.h create mode 100644 drivers/media/platform/renesas/rcar_drif.c create mode 100644 drivers/media/platform/renesas/rcar_fdp1.c create mode 100644 drivers/media/platform/renesas/rcar_jpu.c create mode 100644 drivers/media/platform/renesas/renesas-ceu.c create mode 100644 drivers/media/platform/renesas/sh_vou.c create mode 100644 drivers/media/platform/renesas/vsp1/Makefile create mode 100644 drivers/media/platform/renesas/vsp1/vsp1.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_brx.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_brx.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_clu.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_clu.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_dl.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_dl.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_drm.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_drm.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_drv.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_entity.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_entity.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_hgo.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_hgo.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_hgt.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_hgt.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_histo.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_histo.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_hsit.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_hsit.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_lif.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_lif.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_lut.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_lut.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_pipe.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_pipe.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_regs.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_rpf.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_rwpf.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_rwpf.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_sru.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_sru.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_uds.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_uds.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_uif.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_uif.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_video.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_video.h create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_wpf.c create mode 100644 drivers/media/platform/rockchip/Kconfig create mode 100644 drivers/media/platform/rockchip/Makefile create mode 100644 drivers/media/platform/rockchip/rga/Kconfig create mode 100644 drivers/media/platform/rockchip/rga/Makefile create mode 100644 drivers/media/platform/rockchip/rga/rga-buf.c create mode 100644 drivers/media/platform/rockchip/rga/rga-hw.c create mode 100644 drivers/media/platform/rockchip/rga/rga-hw.h create mode 100644 drivers/media/platform/rockchip/rga/rga.c create mode 100644 drivers/media/platform/rockchip/rga/rga.h create mode 100644 drivers/media/platform/rockchip/rkisp1/Kconfig create mode 100644 drivers/media/platform/rockchip/rkisp1/Makefile create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-common.c create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-common.h create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-csi.h create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-debug.c create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-params.c create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-regs.h create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c create mode 100644 drivers/media/platform/rockchip/rkisp1/rkisp1-stats.c create mode 100644 drivers/media/platform/samsung/Kconfig create mode 100644 drivers/media/platform/samsung/Makefile create mode 100644 drivers/media/platform/samsung/exynos-gsc/Kconfig create mode 100644 drivers/media/platform/samsung/exynos-gsc/Makefile create mode 100644 drivers/media/platform/samsung/exynos-gsc/gsc-core.c create mode 100644 drivers/media/platform/samsung/exynos-gsc/gsc-core.h create mode 100644 drivers/media/platform/samsung/exynos-gsc/gsc-m2m.c create mode 100644 drivers/media/platform/samsung/exynos-gsc/gsc-regs.c create mode 100644 drivers/media/platform/samsung/exynos-gsc/gsc-regs.h create mode 100644 drivers/media/platform/samsung/exynos4-is/Kconfig create mode 100644 drivers/media/platform/samsung/exynos4-is/Makefile create mode 100644 drivers/media/platform/samsung/exynos4-is/common.c create mode 100644 drivers/media/platform/samsung/exynos4-is/common.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-capture.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-core.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-core.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-command.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-errno.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-errno.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-param.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-param.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-regs.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-regs.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-is.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-isp-video.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-isp-video.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-isp.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-isp.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-lite.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-lite.h create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-m2m.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-reg.c create mode 100644 drivers/media/platform/samsung/exynos4-is/fimc-reg.h create mode 100644 drivers/media/platform/samsung/exynos4-is/media-dev.c create mode 100644 drivers/media/platform/samsung/exynos4-is/media-dev.h create mode 100644 drivers/media/platform/samsung/exynos4-is/mipi-csis.c create mode 100644 drivers/media/platform/samsung/exynos4-is/mipi-csis.h create mode 100644 drivers/media/platform/samsung/s3c-camif/Kconfig create mode 100644 drivers/media/platform/samsung/s3c-camif/Makefile create mode 100644 drivers/media/platform/samsung/s3c-camif/camif-capture.c create mode 100644 drivers/media/platform/samsung/s3c-camif/camif-core.c create mode 100644 drivers/media/platform/samsung/s3c-camif/camif-core.h create mode 100644 drivers/media/platform/samsung/s3c-camif/camif-regs.c create mode 100644 drivers/media/platform/samsung/s3c-camif/camif-regs.h create mode 100644 drivers/media/platform/samsung/s5p-g2d/Kconfig create mode 100644 drivers/media/platform/samsung/s5p-g2d/Makefile create mode 100644 drivers/media/platform/samsung/s5p-g2d/g2d-hw.c create mode 100644 drivers/media/platform/samsung/s5p-g2d/g2d-regs.h create mode 100644 drivers/media/platform/samsung/s5p-g2d/g2d.c create mode 100644 drivers/media/platform/samsung/s5p-g2d/g2d.h create mode 100644 drivers/media/platform/samsung/s5p-jpeg/Kconfig create mode 100644 drivers/media/platform/samsung/s5p-jpeg/Makefile create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h create mode 100644 drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/Kconfig create mode 100644 drivers/media/platform/samsung/s5p-mfc/Makefile create mode 100644 drivers/media/platform/samsung/s5p-mfc/regs-mfc-v10.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/regs-mfc-v6.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/regs-mfc-v7.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/regs-mfc-v8.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/regs-mfc.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_common.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c create mode 100644 drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h create mode 100644 drivers/media/platform/st/Kconfig create mode 100644 drivers/media/platform/st/Makefile create mode 100644 drivers/media/platform/st/sti/Kconfig create mode 100644 drivers/media/platform/st/sti/Makefile create mode 100644 drivers/media/platform/st/sti/bdisp/Kconfig create mode 100644 drivers/media/platform/st/sti/bdisp/Makefile create mode 100644 drivers/media/platform/st/sti/bdisp/bdisp-debug.c create mode 100644 drivers/media/platform/st/sti/bdisp/bdisp-filter.h create mode 100644 drivers/media/platform/st/sti/bdisp/bdisp-hw.c create mode 100644 drivers/media/platform/st/sti/bdisp/bdisp-reg.h create mode 100644 drivers/media/platform/st/sti/bdisp/bdisp-v4l2.c create mode 100644 drivers/media/platform/st/sti/bdisp/bdisp.h create mode 100644 drivers/media/platform/st/sti/c8sectpfe/Kconfig create mode 100644 drivers/media/platform/st/sti/c8sectpfe/Makefile create mode 100644 drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-common.c create mode 100644 drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-common.h create mode 100644 drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c create mode 100644 drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.h create mode 100644 drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-debugfs.c create mode 100644 drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-debugfs.h create mode 100644 drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-dvb.c create mode 100644 drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-dvb.h create mode 100644 drivers/media/platform/st/sti/delta/Kconfig create mode 100644 drivers/media/platform/st/sti/delta/Makefile create mode 100644 drivers/media/platform/st/sti/delta/delta-cfg.h create mode 100644 drivers/media/platform/st/sti/delta/delta-debug.c create mode 100644 drivers/media/platform/st/sti/delta/delta-debug.h create mode 100644 drivers/media/platform/st/sti/delta/delta-ipc.c create mode 100644 drivers/media/platform/st/sti/delta/delta-ipc.h create mode 100644 drivers/media/platform/st/sti/delta/delta-mem.c create mode 100644 drivers/media/platform/st/sti/delta/delta-mem.h create mode 100644 drivers/media/platform/st/sti/delta/delta-mjpeg-dec.c create mode 100644 drivers/media/platform/st/sti/delta/delta-mjpeg-fw.h create mode 100644 drivers/media/platform/st/sti/delta/delta-mjpeg-hdr.c create mode 100644 drivers/media/platform/st/sti/delta/delta-mjpeg.h create mode 100644 drivers/media/platform/st/sti/delta/delta-v4l2.c create mode 100644 drivers/media/platform/st/sti/delta/delta.h create mode 100644 drivers/media/platform/st/sti/hva/Kconfig create mode 100644 drivers/media/platform/st/sti/hva/Makefile create mode 100644 drivers/media/platform/st/sti/hva/hva-debugfs.c create mode 100644 drivers/media/platform/st/sti/hva/hva-h264.c create mode 100644 drivers/media/platform/st/sti/hva/hva-hw.c create mode 100644 drivers/media/platform/st/sti/hva/hva-hw.h create mode 100644 drivers/media/platform/st/sti/hva/hva-mem.c create mode 100644 drivers/media/platform/st/sti/hva/hva-mem.h create mode 100644 drivers/media/platform/st/sti/hva/hva-v4l2.c create mode 100644 drivers/media/platform/st/sti/hva/hva.h create mode 100644 drivers/media/platform/st/stm32/Kconfig create mode 100644 drivers/media/platform/st/stm32/Makefile create mode 100644 drivers/media/platform/st/stm32/dma2d/dma2d-hw.c create mode 100644 drivers/media/platform/st/stm32/dma2d/dma2d-regs.h create mode 100644 drivers/media/platform/st/stm32/dma2d/dma2d.c create mode 100644 drivers/media/platform/st/stm32/dma2d/dma2d.h create mode 100644 drivers/media/platform/st/stm32/stm32-dcmi.c create mode 100644 drivers/media/platform/sunxi/Kconfig create mode 100644 drivers/media/platform/sunxi/Makefile create mode 100644 drivers/media/platform/sunxi/sun4i-csi/Kconfig create mode 100644 drivers/media/platform/sunxi/sun4i-csi/Makefile create mode 100644 drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c create mode 100644 drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.h create mode 100644 drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c create mode 100644 drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Kconfig create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Makefile create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h create mode 100644 drivers/media/platform/sunxi/sun6i-mipi-csi2/Kconfig create mode 100644 drivers/media/platform/sunxi/sun6i-mipi-csi2/Makefile create mode 100644 drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c create mode 100644 drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h create mode 100644 drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2_reg.h create mode 100644 drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Kconfig create mode 100644 drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Makefile create mode 100644 drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.c create mode 100644 drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.h create mode 100644 drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c create mode 100644 drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h create mode 100644 drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2_reg.h create mode 100644 drivers/media/platform/sunxi/sun8i-di/Kconfig create mode 100644 drivers/media/platform/sunxi/sun8i-di/Makefile create mode 100644 drivers/media/platform/sunxi/sun8i-di/sun8i-di.c create mode 100644 drivers/media/platform/sunxi/sun8i-di/sun8i-di.h create mode 100644 drivers/media/platform/sunxi/sun8i-rotate/Kconfig create mode 100644 drivers/media/platform/sunxi/sun8i-rotate/Makefile create mode 100644 drivers/media/platform/sunxi/sun8i-rotate/sun8i-formats.h create mode 100644 drivers/media/platform/sunxi/sun8i-rotate/sun8i-rotate.h create mode 100644 drivers/media/platform/sunxi/sun8i-rotate/sun8i_formats.c create mode 100644 drivers/media/platform/sunxi/sun8i-rotate/sun8i_rotate.c create mode 100644 drivers/media/platform/ti/Kconfig create mode 100644 drivers/media/platform/ti/Makefile create mode 100644 drivers/media/platform/ti/am437x/Kconfig create mode 100644 drivers/media/platform/ti/am437x/Makefile create mode 100644 drivers/media/platform/ti/am437x/am437x-vpfe.c create mode 100644 drivers/media/platform/ti/am437x/am437x-vpfe.h create mode 100644 drivers/media/platform/ti/am437x/am437x-vpfe_regs.h create mode 100644 drivers/media/platform/ti/cal/Makefile create mode 100644 drivers/media/platform/ti/cal/cal-camerarx.c create mode 100644 drivers/media/platform/ti/cal/cal-video.c create mode 100644 drivers/media/platform/ti/cal/cal.c create mode 100644 drivers/media/platform/ti/cal/cal.h create mode 100644 drivers/media/platform/ti/cal/cal_regs.h create mode 100644 drivers/media/platform/ti/davinci/Kconfig create mode 100644 drivers/media/platform/ti/davinci/Makefile create mode 100644 drivers/media/platform/ti/davinci/vpbe.c create mode 100644 drivers/media/platform/ti/davinci/vpbe_display.c create mode 100644 drivers/media/platform/ti/davinci/vpbe_osd.c create mode 100644 drivers/media/platform/ti/davinci/vpbe_osd_regs.h create mode 100644 drivers/media/platform/ti/davinci/vpbe_venc.c create mode 100644 drivers/media/platform/ti/davinci/vpbe_venc_regs.h create mode 100644 drivers/media/platform/ti/davinci/vpif.c create mode 100644 drivers/media/platform/ti/davinci/vpif.h create mode 100644 drivers/media/platform/ti/davinci/vpif_capture.c create mode 100644 drivers/media/platform/ti/davinci/vpif_capture.h create mode 100644 drivers/media/platform/ti/davinci/vpif_display.c create mode 100644 drivers/media/platform/ti/davinci/vpif_display.h create mode 100644 drivers/media/platform/ti/davinci/vpss.c create mode 100644 drivers/media/platform/ti/omap/Kconfig create mode 100644 drivers/media/platform/ti/omap/Makefile create mode 100644 drivers/media/platform/ti/omap/omap_vout.c create mode 100644 drivers/media/platform/ti/omap/omap_vout_vrfb.c create mode 100644 drivers/media/platform/ti/omap/omap_vout_vrfb.h create mode 100644 drivers/media/platform/ti/omap/omap_voutdef.h create mode 100644 drivers/media/platform/ti/omap/omap_voutlib.c create mode 100644 drivers/media/platform/ti/omap/omap_voutlib.h create mode 100644 drivers/media/platform/ti/omap3isp/Kconfig create mode 100644 drivers/media/platform/ti/omap3isp/Makefile create mode 100644 drivers/media/platform/ti/omap3isp/cfa_coef_table.h create mode 100644 drivers/media/platform/ti/omap3isp/gamma_table.h create mode 100644 drivers/media/platform/ti/omap3isp/isp.c create mode 100644 drivers/media/platform/ti/omap3isp/isp.h create mode 100644 drivers/media/platform/ti/omap3isp/ispccdc.c create mode 100644 drivers/media/platform/ti/omap3isp/ispccdc.h create mode 100644 drivers/media/platform/ti/omap3isp/ispccp2.c create mode 100644 drivers/media/platform/ti/omap3isp/ispccp2.h create mode 100644 drivers/media/platform/ti/omap3isp/ispcsi2.c create mode 100644 drivers/media/platform/ti/omap3isp/ispcsi2.h create mode 100644 drivers/media/platform/ti/omap3isp/ispcsiphy.c create mode 100644 drivers/media/platform/ti/omap3isp/ispcsiphy.h create mode 100644 drivers/media/platform/ti/omap3isp/isph3a.h create mode 100644 drivers/media/platform/ti/omap3isp/isph3a_aewb.c create mode 100644 drivers/media/platform/ti/omap3isp/isph3a_af.c create mode 100644 drivers/media/platform/ti/omap3isp/isphist.c create mode 100644 drivers/media/platform/ti/omap3isp/isphist.h create mode 100644 drivers/media/platform/ti/omap3isp/isppreview.c create mode 100644 drivers/media/platform/ti/omap3isp/isppreview.h create mode 100644 drivers/media/platform/ti/omap3isp/ispreg.h create mode 100644 drivers/media/platform/ti/omap3isp/ispresizer.c create mode 100644 drivers/media/platform/ti/omap3isp/ispresizer.h create mode 100644 drivers/media/platform/ti/omap3isp/ispstat.c create mode 100644 drivers/media/platform/ti/omap3isp/ispstat.h create mode 100644 drivers/media/platform/ti/omap3isp/ispvideo.c create mode 100644 drivers/media/platform/ti/omap3isp/ispvideo.h create mode 100644 drivers/media/platform/ti/omap3isp/luma_enhance_table.h create mode 100644 drivers/media/platform/ti/omap3isp/noise_filter_table.h create mode 100644 drivers/media/platform/ti/omap3isp/omap3isp.h create mode 100644 drivers/media/platform/ti/vpe/Makefile create mode 100644 drivers/media/platform/ti/vpe/csc.c create mode 100644 drivers/media/platform/ti/vpe/csc.h create mode 100644 drivers/media/platform/ti/vpe/sc.c create mode 100644 drivers/media/platform/ti/vpe/sc.h create mode 100644 drivers/media/platform/ti/vpe/sc_coeff.h create mode 100644 drivers/media/platform/ti/vpe/vpdma.c create mode 100644 drivers/media/platform/ti/vpe/vpdma.h create mode 100644 drivers/media/platform/ti/vpe/vpdma_priv.h create mode 100644 drivers/media/platform/ti/vpe/vpe.c create mode 100644 drivers/media/platform/ti/vpe/vpe_regs.h create mode 100644 drivers/media/platform/verisilicon/Kconfig create mode 100644 drivers/media/platform/verisilicon/Makefile create mode 100644 drivers/media/platform/verisilicon/hantro.h create mode 100644 drivers/media/platform/verisilicon/hantro_drv.c create mode 100644 drivers/media/platform/verisilicon/hantro_g1.c create mode 100644 drivers/media/platform/verisilicon/hantro_g1_h264_dec.c create mode 100644 drivers/media/platform/verisilicon/hantro_g1_mpeg2_dec.c create mode 100644 drivers/media/platform/verisilicon/hantro_g1_regs.h create mode 100644 drivers/media/platform/verisilicon/hantro_g1_vp8_dec.c create mode 100644 drivers/media/platform/verisilicon/hantro_g2.c create mode 100644 drivers/media/platform/verisilicon/hantro_g2_hevc_dec.c create mode 100644 drivers/media/platform/verisilicon/hantro_g2_regs.h create mode 100644 drivers/media/platform/verisilicon/hantro_g2_vp9_dec.c create mode 100644 drivers/media/platform/verisilicon/hantro_h1_jpeg_enc.c create mode 100644 drivers/media/platform/verisilicon/hantro_h1_regs.h create mode 100644 drivers/media/platform/verisilicon/hantro_h264.c create mode 100644 drivers/media/platform/verisilicon/hantro_hevc.c create mode 100644 drivers/media/platform/verisilicon/hantro_hw.h create mode 100644 drivers/media/platform/verisilicon/hantro_jpeg.c create mode 100644 drivers/media/platform/verisilicon/hantro_jpeg.h create mode 100644 drivers/media/platform/verisilicon/hantro_mpeg2.c create mode 100644 drivers/media/platform/verisilicon/hantro_postproc.c create mode 100644 drivers/media/platform/verisilicon/hantro_v4l2.c create mode 100644 drivers/media/platform/verisilicon/hantro_v4l2.h create mode 100644 drivers/media/platform/verisilicon/hantro_vp8.c create mode 100644 drivers/media/platform/verisilicon/hantro_vp9.c create mode 100644 drivers/media/platform/verisilicon/hantro_vp9.h create mode 100644 drivers/media/platform/verisilicon/imx8m_vpu_hw.c create mode 100644 drivers/media/platform/verisilicon/rockchip_vpu2_hw_h264_dec.c create mode 100644 drivers/media/platform/verisilicon/rockchip_vpu2_hw_jpeg_enc.c create mode 100644 drivers/media/platform/verisilicon/rockchip_vpu2_hw_mpeg2_dec.c create mode 100644 drivers/media/platform/verisilicon/rockchip_vpu2_hw_vp8_dec.c create mode 100644 drivers/media/platform/verisilicon/rockchip_vpu2_regs.h create mode 100644 drivers/media/platform/verisilicon/rockchip_vpu_hw.c create mode 100644 drivers/media/platform/verisilicon/sama5d4_vdec_hw.c create mode 100644 drivers/media/platform/verisilicon/sunxi_vpu_hw.c create mode 100644 drivers/media/platform/via/Kconfig create mode 100644 drivers/media/platform/via/Makefile create mode 100644 drivers/media/platform/via/via-camera.c create mode 100644 drivers/media/platform/via/via-camera.h create mode 100644 drivers/media/platform/video-mux.c create mode 100644 drivers/media/platform/xilinx/Kconfig create mode 100644 drivers/media/platform/xilinx/Makefile create mode 100644 drivers/media/platform/xilinx/xilinx-csi2rxss.c create mode 100644 drivers/media/platform/xilinx/xilinx-dma.c create mode 100644 drivers/media/platform/xilinx/xilinx-dma.h create mode 100644 drivers/media/platform/xilinx/xilinx-tpg.c create mode 100644 drivers/media/platform/xilinx/xilinx-vip.c create mode 100644 drivers/media/platform/xilinx/xilinx-vip.h create mode 100644 drivers/media/platform/xilinx/xilinx-vipp.c create mode 100644 drivers/media/platform/xilinx/xilinx-vipp.h create mode 100644 drivers/media/platform/xilinx/xilinx-vtc.c create mode 100644 drivers/media/platform/xilinx/xilinx-vtc.h (limited to 'drivers/media/platform') diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig new file mode 100644 index 000000000..a9334263f --- /dev/null +++ b/drivers/media/platform/Kconfig @@ -0,0 +1,88 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Platform drivers +# Most drivers here are currently for webcam support + +menuconfig MEDIA_PLATFORM_DRIVERS + bool "Media platform devices" + default "y" + help + Say Y here to enable support for platform-specific media drivers. + +if MEDIA_PLATFORM_DRIVERS + +config V4L_PLATFORM_DRIVERS + bool "V4L platform devices" + help + Say Y here to enable support for platform-specific V4L drivers. + +config SDR_PLATFORM_DRIVERS + bool "SDR platform devices" + depends on MEDIA_SDR_SUPPORT + help + Say Y here to enable support for platform-specific SDR Drivers. + +config DVB_PLATFORM_DRIVERS + bool "DVB platform devices" + depends on MEDIA_DIGITAL_TV_SUPPORT + help + Say Y here to enable support for platform-specific Digital TV drivers. + +config V4L_MEM2MEM_DRIVERS + bool "Memory-to-memory multimedia devices" + depends on VIDEO_DEV + help + Say Y here to enable selecting drivers for V4L devices that + use system memory for both source and destination buffers, as opposed + to capture and output drivers, which use memory buffers for just + one of those. + +# Ancillary drivers + +config VIDEO_MEM2MEM_DEINTERLACE + tristate "Deinterlace support" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on HAS_DMA + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + Generic deinterlacing V4L2 driver. + +config VIDEO_MUX + tristate "Video Multiplexer" + depends on V4L_PLATFORM_DRIVERS + select MULTIPLEXER + depends on VIDEO_DEV && OF + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select REGMAP + select V4L2_FWNODE + help + This driver provides support for N:1 video bus multiplexers. + +# Platform drivers - Please keep it alphabetically sorted +source "drivers/media/platform/allegro-dvt/Kconfig" +source "drivers/media/platform/amlogic/Kconfig" +source "drivers/media/platform/amphion/Kconfig" +source "drivers/media/platform/aspeed/Kconfig" +source "drivers/media/platform/atmel/Kconfig" +source "drivers/media/platform/cadence/Kconfig" +source "drivers/media/platform/chips-media/Kconfig" +source "drivers/media/platform/intel/Kconfig" +source "drivers/media/platform/marvell/Kconfig" +source "drivers/media/platform/mediatek/Kconfig" +source "drivers/media/platform/nvidia/Kconfig" +source "drivers/media/platform/nxp/Kconfig" +source "drivers/media/platform/qcom/Kconfig" +source "drivers/media/platform/renesas/Kconfig" +source "drivers/media/platform/rockchip/Kconfig" +source "drivers/media/platform/samsung/Kconfig" +source "drivers/media/platform/st/Kconfig" +source "drivers/media/platform/sunxi/Kconfig" +source "drivers/media/platform/ti/Kconfig" +source "drivers/media/platform/verisilicon/Kconfig" +source "drivers/media/platform/via/Kconfig" +source "drivers/media/platform/xilinx/Kconfig" + +endif # MEDIA_PLATFORM_DRIVERS diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile new file mode 100644 index 000000000..a91f42024 --- /dev/null +++ b/drivers/media/platform/Makefile @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the video capture/playback device drivers. +# + +# Place here, alphabetically sorted by directory +# (e. g. LC_ALL=C sort Makefile) +obj-y += allegro-dvt/ +obj-y += amlogic/ +obj-y += amphion/ +obj-y += aspeed/ +obj-y += atmel/ +obj-y += cadence/ +obj-y += chips-media/ +obj-y += intel/ +obj-y += marvell/ +obj-y += mediatek/ +obj-y += nvidia/ +obj-y += nxp/ +obj-y += qcom/ +obj-y += renesas/ +obj-y += rockchip/ +obj-y += samsung/ +obj-y += st/ +obj-y += sunxi/ +obj-y += ti/ +obj-y += verisilicon/ +obj-y += via/ +obj-y += xilinx/ + +# Please place here only ancillary drivers that aren't SoC-specific +# Please keep it alphabetically sorted by Kconfig name +# (e. g. LC_ALL=C sort Makefile) +obj-$(CONFIG_VIDEO_MEM2MEM_DEINTERLACE) += m2m-deinterlace.o +obj-$(CONFIG_VIDEO_MUX) += video-mux.o diff --git a/drivers/media/platform/allegro-dvt/Kconfig b/drivers/media/platform/allegro-dvt/Kconfig new file mode 100644 index 000000000..2182e1277 --- /dev/null +++ b/drivers/media/platform/allegro-dvt/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Allegro DVT media platform drivers" + +config VIDEO_ALLEGRO_DVT + tristate "Allegro DVT Video IP Core" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_ZYNQMP || COMPILE_TEST + select V4L2_MEM2MEM_DEV + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + help + Support for the encoder video IP core by Allegro DVT. This core is + found for example on the Xilinx ZynqMP SoC in the EV family and is + called VCU in the reference manual. + + To compile this driver as a module, choose M here: the module + will be called allegro. diff --git a/drivers/media/platform/allegro-dvt/Makefile b/drivers/media/platform/allegro-dvt/Makefile new file mode 100644 index 000000000..66108a303 --- /dev/null +++ b/drivers/media/platform/allegro-dvt/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0 + +allegro-objs := allegro-core.o allegro-mail.o +allegro-objs += nal-rbsp.o nal-h264.o nal-hevc.o + +obj-$(CONFIG_VIDEO_ALLEGRO_DVT) += allegro.o diff --git a/drivers/media/platform/allegro-dvt/allegro-core.c b/drivers/media/platform/allegro-dvt/allegro-core.c new file mode 100644 index 000000000..2423714af --- /dev/null +++ b/drivers/media/platform/allegro-dvt/allegro-core.c @@ -0,0 +1,4021 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter + * + * Allegro DVT video encoder driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "allegro-mail.h" +#include "nal-h264.h" +#include "nal-hevc.h" + +/* + * Support up to 4k video streams. The hardware actually supports higher + * resolutions, which are specified in PG252 June 6, 2018 (H.264/H.265 Video + * Codec Unit v1.1) Chapter 3. + */ +#define ALLEGRO_WIDTH_MIN 128 +#define ALLEGRO_WIDTH_DEFAULT 1920 +#define ALLEGRO_WIDTH_MAX 3840 +#define ALLEGRO_HEIGHT_MIN 64 +#define ALLEGRO_HEIGHT_DEFAULT 1080 +#define ALLEGRO_HEIGHT_MAX 2160 + +#define ALLEGRO_FRAMERATE_DEFAULT ((struct v4l2_fract) { 30, 1 }) + +#define ALLEGRO_GOP_SIZE_DEFAULT 25 +#define ALLEGRO_GOP_SIZE_MAX 1000 + +/* + * MCU Control Registers + * + * The Zynq UltraScale+ Devices Register Reference documents the registers + * with an offset of 0x9000, which equals the size of the SRAM and one page + * gap. The driver handles SRAM and registers separately and, therefore, is + * oblivious of the offset. + */ +#define AL5_MCU_RESET 0x0000 +#define AL5_MCU_RESET_SOFT BIT(0) +#define AL5_MCU_RESET_REGS BIT(1) +#define AL5_MCU_RESET_MODE 0x0004 +#define AL5_MCU_RESET_MODE_SLEEP BIT(0) +#define AL5_MCU_RESET_MODE_HALT BIT(1) +#define AL5_MCU_STA 0x0008 +#define AL5_MCU_STA_SLEEP BIT(0) +#define AL5_MCU_WAKEUP 0x000c + +#define AL5_ICACHE_ADDR_OFFSET_MSB 0x0010 +#define AL5_ICACHE_ADDR_OFFSET_LSB 0x0014 +#define AL5_DCACHE_ADDR_OFFSET_MSB 0x0018 +#define AL5_DCACHE_ADDR_OFFSET_LSB 0x001c + +#define AL5_MCU_INTERRUPT 0x0100 +#define AL5_ITC_CPU_IRQ_MSK 0x0104 +#define AL5_ITC_CPU_IRQ_CLR 0x0108 +#define AL5_ITC_CPU_IRQ_STA 0x010C +#define AL5_ITC_CPU_IRQ_STA_TRIGGERED BIT(0) + +#define AXI_ADDR_OFFSET_IP 0x0208 + +/* + * The MCU accesses the system memory with a 2G offset compared to CPU + * physical addresses. + */ +#define MCU_CACHE_OFFSET SZ_2G + +/* + * The driver needs to reserve some space at the beginning of capture buffers, + * because it needs to write SPS/PPS NAL units. The encoder writes the actual + * frame data after the offset. + */ +#define ENCODER_STREAM_OFFSET SZ_128 + +#define SIZE_MACROBLOCK 16 + +/* Encoding options */ +#define LOG2_MAX_FRAME_NUM 4 +#define LOG2_MAX_PIC_ORDER_CNT 10 +#define BETA_OFFSET_DIV_2 -1 +#define TC_OFFSET_DIV_2 -1 + +/* + * This control allows applications to explicitly disable the encoder buffer. + * This value is Allegro specific. + */ +#define V4L2_CID_USER_ALLEGRO_ENCODER_BUFFER (V4L2_CID_USER_ALLEGRO_BASE + 0) + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +struct allegro_buffer { + void *vaddr; + dma_addr_t paddr; + size_t size; + struct list_head head; +}; + +struct allegro_dev; +struct allegro_channel; + +struct allegro_mbox { + struct allegro_dev *dev; + unsigned int head; + unsigned int tail; + unsigned int data; + size_t size; + /* protect mailbox from simultaneous accesses */ + struct mutex lock; +}; + +struct allegro_encoder_buffer { + unsigned int size; + unsigned int color_depth; + unsigned int num_cores; + unsigned int clk_rate; +}; + +struct allegro_dev { + struct v4l2_device v4l2_dev; + struct video_device video_dev; + struct v4l2_m2m_dev *m2m_dev; + struct platform_device *plat_dev; + + /* mutex protecting vb2_queue structure */ + struct mutex lock; + + struct regmap *regmap; + struct regmap *sram; + struct regmap *settings; + + struct clk *clk_core; + struct clk *clk_mcu; + + const struct fw_info *fw_info; + struct allegro_buffer firmware; + struct allegro_buffer suballocator; + bool has_encoder_buffer; + struct allegro_encoder_buffer encoder_buffer; + + struct completion init_complete; + bool initialized; + + /* The mailbox interface */ + struct allegro_mbox *mbox_command; + struct allegro_mbox *mbox_status; + + /* + * The downstream driver limits the users to 64 users, thus I can use + * a bitfield for the user_ids that are in use. See also user_id in + * struct allegro_channel. + */ + unsigned long channel_user_ids; + struct list_head channels; +}; + +static struct regmap_config allegro_regmap_config = { + .name = "regmap", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0xfff, + .cache_type = REGCACHE_NONE, +}; + +static struct regmap_config allegro_sram_config = { + .name = "sram", + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + .max_register = 0x7fff, + .cache_type = REGCACHE_NONE, +}; + +#define fh_to_channel(__fh) container_of(__fh, struct allegro_channel, fh) + +struct allegro_channel { + struct allegro_dev *dev; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; + + unsigned int width; + unsigned int height; + unsigned int stride; + struct v4l2_fract framerate; + + enum v4l2_colorspace colorspace; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + enum v4l2_xfer_func xfer_func; + + u32 pixelformat; + unsigned int sizeimage_raw; + unsigned int osequence; + + u32 codec; + unsigned int sizeimage_encoded; + unsigned int csequence; + + bool frame_rc_enable; + unsigned int bitrate; + unsigned int bitrate_peak; + + struct allegro_buffer config_blob; + + unsigned int log2_max_frame_num; + bool temporal_mvp_enable; + + bool enable_loop_filter_across_tiles; + bool enable_loop_filter_across_slices; + bool enable_deblocking_filter_override; + bool enable_reordering; + bool dbf_ovr_en; + + unsigned int num_ref_idx_l0; + unsigned int num_ref_idx_l1; + + /* Maximum range for motion estimation */ + int b_hrz_me_range; + int b_vrt_me_range; + int p_hrz_me_range; + int p_vrt_me_range; + /* Size limits of coding unit */ + int min_cu_size; + int max_cu_size; + /* Size limits of transform unit */ + int min_tu_size; + int max_tu_size; + int max_transfo_depth_intra; + int max_transfo_depth_inter; + + struct v4l2_ctrl *mpeg_video_h264_profile; + struct v4l2_ctrl *mpeg_video_h264_level; + struct v4l2_ctrl *mpeg_video_h264_i_frame_qp; + struct v4l2_ctrl *mpeg_video_h264_max_qp; + struct v4l2_ctrl *mpeg_video_h264_min_qp; + struct v4l2_ctrl *mpeg_video_h264_p_frame_qp; + struct v4l2_ctrl *mpeg_video_h264_b_frame_qp; + + struct v4l2_ctrl *mpeg_video_hevc_profile; + struct v4l2_ctrl *mpeg_video_hevc_level; + struct v4l2_ctrl *mpeg_video_hevc_tier; + struct v4l2_ctrl *mpeg_video_hevc_i_frame_qp; + struct v4l2_ctrl *mpeg_video_hevc_max_qp; + struct v4l2_ctrl *mpeg_video_hevc_min_qp; + struct v4l2_ctrl *mpeg_video_hevc_p_frame_qp; + struct v4l2_ctrl *mpeg_video_hevc_b_frame_qp; + + struct v4l2_ctrl *mpeg_video_frame_rc_enable; + struct { /* video bitrate mode control cluster */ + struct v4l2_ctrl *mpeg_video_bitrate_mode; + struct v4l2_ctrl *mpeg_video_bitrate; + struct v4l2_ctrl *mpeg_video_bitrate_peak; + }; + struct v4l2_ctrl *mpeg_video_cpb_size; + struct v4l2_ctrl *mpeg_video_gop_size; + + struct v4l2_ctrl *encoder_buffer; + + /* user_id is used to identify the channel during CREATE_CHANNEL */ + /* not sure, what to set here and if this is actually required */ + int user_id; + /* channel_id is set by the mcu and used by all later commands */ + int mcu_channel_id; + + struct list_head buffers_reference; + struct list_head buffers_intermediate; + + struct list_head source_shadow_list; + struct list_head stream_shadow_list; + /* protect shadow lists of buffers passed to firmware */ + struct mutex shadow_list_lock; + + struct list_head list; + struct completion completion; + + unsigned int error; +}; + +static inline int +allegro_channel_get_i_frame_qp(struct allegro_channel *channel) +{ + if (channel->codec == V4L2_PIX_FMT_HEVC) + return v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_i_frame_qp); + else + return v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_i_frame_qp); +} + +static inline int +allegro_channel_get_p_frame_qp(struct allegro_channel *channel) +{ + if (channel->codec == V4L2_PIX_FMT_HEVC) + return v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_p_frame_qp); + else + return v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_p_frame_qp); +} + +static inline int +allegro_channel_get_b_frame_qp(struct allegro_channel *channel) +{ + if (channel->codec == V4L2_PIX_FMT_HEVC) + return v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_b_frame_qp); + else + return v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_b_frame_qp); +} + +static inline int +allegro_channel_get_min_qp(struct allegro_channel *channel) +{ + if (channel->codec == V4L2_PIX_FMT_HEVC) + return v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_min_qp); + else + return v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_min_qp); +} + +static inline int +allegro_channel_get_max_qp(struct allegro_channel *channel) +{ + if (channel->codec == V4L2_PIX_FMT_HEVC) + return v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_max_qp); + else + return v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_max_qp); +} + +struct allegro_m2m_buffer { + struct v4l2_m2m_buffer buf; + struct list_head head; +}; + +#define to_allegro_m2m_buffer(__buf) \ + container_of(__buf, struct allegro_m2m_buffer, buf) + +struct fw_info { + unsigned int id; + unsigned int id_codec; + char *version; + unsigned int mailbox_cmd; + unsigned int mailbox_status; + size_t mailbox_size; + enum mcu_msg_version mailbox_version; + size_t suballocator_size; +}; + +static const struct fw_info supported_firmware[] = { + { + .id = 18296, + .id_codec = 96272, + .version = "v2018.2", + .mailbox_cmd = 0x7800, + .mailbox_status = 0x7c00, + .mailbox_size = 0x400 - 0x8, + .mailbox_version = MCU_MSG_VERSION_2018_2, + .suballocator_size = SZ_16M, + }, { + .id = 14680, + .id_codec = 126572, + .version = "v2019.2", + .mailbox_cmd = 0x7000, + .mailbox_status = 0x7800, + .mailbox_size = 0x800 - 0x8, + .mailbox_version = MCU_MSG_VERSION_2019_2, + .suballocator_size = SZ_32M, + }, +}; + +static inline u32 to_mcu_addr(struct allegro_dev *dev, dma_addr_t phys) +{ + if (upper_32_bits(phys) || (lower_32_bits(phys) & MCU_CACHE_OFFSET)) + v4l2_warn(&dev->v4l2_dev, + "address %pad is outside mcu window\n", &phys); + + return lower_32_bits(phys) | MCU_CACHE_OFFSET; +} + +static inline u32 to_mcu_size(struct allegro_dev *dev, size_t size) +{ + return lower_32_bits(size); +} + +static inline u32 to_codec_addr(struct allegro_dev *dev, dma_addr_t phys) +{ + if (upper_32_bits(phys)) + v4l2_warn(&dev->v4l2_dev, + "address %pad cannot be used by codec\n", &phys); + + return lower_32_bits(phys); +} + +static inline u64 ptr_to_u64(const void *ptr) +{ + return (uintptr_t)ptr; +} + +/* Helper functions for channel and user operations */ + +static unsigned long allegro_next_user_id(struct allegro_dev *dev) +{ + if (dev->channel_user_ids == ~0UL) + return -EBUSY; + + return ffz(dev->channel_user_ids); +} + +static struct allegro_channel * +allegro_find_channel_by_user_id(struct allegro_dev *dev, + unsigned int user_id) +{ + struct allegro_channel *channel; + + list_for_each_entry(channel, &dev->channels, list) { + if (channel->user_id == user_id) + return channel; + } + + return ERR_PTR(-EINVAL); +} + +static struct allegro_channel * +allegro_find_channel_by_channel_id(struct allegro_dev *dev, + unsigned int channel_id) +{ + struct allegro_channel *channel; + + list_for_each_entry(channel, &dev->channels, list) { + if (channel->mcu_channel_id == channel_id) + return channel; + } + + return ERR_PTR(-EINVAL); +} + +static inline bool channel_exists(struct allegro_channel *channel) +{ + return channel->mcu_channel_id != -1; +} + +#define AL_ERROR 0x80 +#define AL_ERR_INIT_FAILED 0x81 +#define AL_ERR_NO_FRAME_DECODED 0x82 +#define AL_ERR_RESOLUTION_CHANGE 0x85 +#define AL_ERR_NO_MEMORY 0x87 +#define AL_ERR_STREAM_OVERFLOW 0x88 +#define AL_ERR_TOO_MANY_SLICES 0x89 +#define AL_ERR_BUF_NOT_READY 0x8c +#define AL_ERR_NO_CHANNEL_AVAILABLE 0x8d +#define AL_ERR_RESOURCE_UNAVAILABLE 0x8e +#define AL_ERR_NOT_ENOUGH_CORES 0x8f +#define AL_ERR_REQUEST_MALFORMED 0x90 +#define AL_ERR_CMD_NOT_ALLOWED 0x91 +#define AL_ERR_INVALID_CMD_VALUE 0x92 + +static inline const char *allegro_err_to_string(unsigned int err) +{ + switch (err) { + case AL_ERR_INIT_FAILED: + return "initialization failed"; + case AL_ERR_NO_FRAME_DECODED: + return "no frame decoded"; + case AL_ERR_RESOLUTION_CHANGE: + return "resolution change"; + case AL_ERR_NO_MEMORY: + return "out of memory"; + case AL_ERR_STREAM_OVERFLOW: + return "stream buffer overflow"; + case AL_ERR_TOO_MANY_SLICES: + return "too many slices"; + case AL_ERR_BUF_NOT_READY: + return "buffer not ready"; + case AL_ERR_NO_CHANNEL_AVAILABLE: + return "no channel available"; + case AL_ERR_RESOURCE_UNAVAILABLE: + return "resource unavailable"; + case AL_ERR_NOT_ENOUGH_CORES: + return "not enough cores"; + case AL_ERR_REQUEST_MALFORMED: + return "request malformed"; + case AL_ERR_CMD_NOT_ALLOWED: + return "command not allowed"; + case AL_ERR_INVALID_CMD_VALUE: + return "invalid command value"; + case AL_ERROR: + default: + return "unknown error"; + } +} + +static unsigned int estimate_stream_size(unsigned int width, + unsigned int height) +{ + unsigned int offset = ENCODER_STREAM_OFFSET; + unsigned int num_blocks = DIV_ROUND_UP(width, SIZE_MACROBLOCK) * + DIV_ROUND_UP(height, SIZE_MACROBLOCK); + unsigned int pcm_size = SZ_256; + unsigned int partition_table = SZ_256; + + return round_up(offset + num_blocks * pcm_size + partition_table, 32); +} + +static enum v4l2_mpeg_video_h264_level +select_minimum_h264_level(unsigned int width, unsigned int height) +{ + unsigned int pic_width_in_mb = DIV_ROUND_UP(width, SIZE_MACROBLOCK); + unsigned int frame_height_in_mb = DIV_ROUND_UP(height, SIZE_MACROBLOCK); + unsigned int frame_size_in_mb = pic_width_in_mb * frame_height_in_mb; + enum v4l2_mpeg_video_h264_level level = V4L2_MPEG_VIDEO_H264_LEVEL_4_0; + + /* + * The level limits are specified in Rec. ITU-T H.264 Annex A.3.1 and + * also specify limits regarding bit rate and CBP size. Only approximate + * the levels using the frame size. + * + * Level 5.1 allows up to 4k video resolution. + */ + if (frame_size_in_mb <= 99) + level = V4L2_MPEG_VIDEO_H264_LEVEL_1_0; + else if (frame_size_in_mb <= 396) + level = V4L2_MPEG_VIDEO_H264_LEVEL_1_1; + else if (frame_size_in_mb <= 792) + level = V4L2_MPEG_VIDEO_H264_LEVEL_2_1; + else if (frame_size_in_mb <= 1620) + level = V4L2_MPEG_VIDEO_H264_LEVEL_2_2; + else if (frame_size_in_mb <= 3600) + level = V4L2_MPEG_VIDEO_H264_LEVEL_3_1; + else if (frame_size_in_mb <= 5120) + level = V4L2_MPEG_VIDEO_H264_LEVEL_3_2; + else if (frame_size_in_mb <= 8192) + level = V4L2_MPEG_VIDEO_H264_LEVEL_4_0; + else if (frame_size_in_mb <= 8704) + level = V4L2_MPEG_VIDEO_H264_LEVEL_4_2; + else if (frame_size_in_mb <= 22080) + level = V4L2_MPEG_VIDEO_H264_LEVEL_5_0; + else + level = V4L2_MPEG_VIDEO_H264_LEVEL_5_1; + + return level; +} + +static unsigned int h264_maximum_bitrate(enum v4l2_mpeg_video_h264_level level) +{ + switch (level) { + case V4L2_MPEG_VIDEO_H264_LEVEL_1_0: + return 64000; + case V4L2_MPEG_VIDEO_H264_LEVEL_1B: + return 128000; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_1: + return 192000; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_2: + return 384000; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_3: + return 768000; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_0: + return 2000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_1: + return 4000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_2: + return 4000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_0: + return 10000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: + return 14000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: + return 20000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + return 20000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + return 50000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + return 50000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_0: + return 135000000; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_1: + default: + return 240000000; + } +} + +static unsigned int h264_maximum_cpb_size(enum v4l2_mpeg_video_h264_level level) +{ + switch (level) { + case V4L2_MPEG_VIDEO_H264_LEVEL_1_0: + return 175; + case V4L2_MPEG_VIDEO_H264_LEVEL_1B: + return 350; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_1: + return 500; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_2: + return 1000; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_3: + return 2000; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_0: + return 2000; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_1: + return 4000; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_2: + return 4000; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_0: + return 10000; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: + return 14000; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: + return 20000; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + return 25000; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + return 62500; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + return 62500; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_0: + return 135000; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_1: + default: + return 240000; + } +} + +static enum v4l2_mpeg_video_hevc_level +select_minimum_hevc_level(unsigned int width, unsigned int height) +{ + unsigned int luma_picture_size = width * height; + enum v4l2_mpeg_video_hevc_level level; + + if (luma_picture_size <= 36864) + level = V4L2_MPEG_VIDEO_HEVC_LEVEL_1; + else if (luma_picture_size <= 122880) + level = V4L2_MPEG_VIDEO_HEVC_LEVEL_2; + else if (luma_picture_size <= 245760) + level = V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1; + else if (luma_picture_size <= 552960) + level = V4L2_MPEG_VIDEO_HEVC_LEVEL_3; + else if (luma_picture_size <= 983040) + level = V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1; + else if (luma_picture_size <= 2228224) + level = V4L2_MPEG_VIDEO_HEVC_LEVEL_4; + else if (luma_picture_size <= 8912896) + level = V4L2_MPEG_VIDEO_HEVC_LEVEL_5; + else + level = V4L2_MPEG_VIDEO_HEVC_LEVEL_6; + + return level; +} + +static unsigned int hevc_maximum_bitrate(enum v4l2_mpeg_video_hevc_level level) +{ + /* + * See Rec. ITU-T H.265 v5 (02/2018), A.4.2 Profile-specific level + * limits for the video profiles. + */ + switch (level) { + case V4L2_MPEG_VIDEO_HEVC_LEVEL_1: + return 128; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_2: + return 1500; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1: + return 3000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_3: + return 6000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1: + return 10000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_4: + return 12000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1: + return 20000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5: + return 25000; + default: + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1: + return 40000; + } +} + +static unsigned int hevc_maximum_cpb_size(enum v4l2_mpeg_video_hevc_level level) +{ + switch (level) { + case V4L2_MPEG_VIDEO_HEVC_LEVEL_1: + return 350; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_2: + return 1500; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1: + return 3000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_3: + return 6000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1: + return 10000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_4: + return 12000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1: + return 20000; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5: + return 25000; + default: + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1: + return 40000; + } +} + +static const struct fw_info * +allegro_get_firmware_info(struct allegro_dev *dev, + const struct firmware *fw, + const struct firmware *fw_codec) +{ + int i; + unsigned int id = fw->size; + unsigned int id_codec = fw_codec->size; + + for (i = 0; i < ARRAY_SIZE(supported_firmware); i++) + if (supported_firmware[i].id == id && + supported_firmware[i].id_codec == id_codec) + return &supported_firmware[i]; + + return NULL; +} + +/* + * Buffers that are used internally by the MCU. + */ + +static int allegro_alloc_buffer(struct allegro_dev *dev, + struct allegro_buffer *buffer, size_t size) +{ + buffer->vaddr = dma_alloc_coherent(&dev->plat_dev->dev, size, + &buffer->paddr, GFP_KERNEL); + if (!buffer->vaddr) + return -ENOMEM; + buffer->size = size; + + return 0; +} + +static void allegro_free_buffer(struct allegro_dev *dev, + struct allegro_buffer *buffer) +{ + if (buffer->vaddr) { + dma_free_coherent(&dev->plat_dev->dev, buffer->size, + buffer->vaddr, buffer->paddr); + buffer->vaddr = NULL; + buffer->size = 0; + } +} + +/* + * Mailbox interface to send messages to the MCU. + */ + +static void allegro_mcu_interrupt(struct allegro_dev *dev); +static void allegro_handle_message(struct allegro_dev *dev, + union mcu_msg_response *msg); + +static struct allegro_mbox *allegro_mbox_init(struct allegro_dev *dev, + unsigned int base, size_t size) +{ + struct allegro_mbox *mbox; + + mbox = devm_kmalloc(&dev->plat_dev->dev, sizeof(*mbox), GFP_KERNEL); + if (!mbox) + return ERR_PTR(-ENOMEM); + + mbox->dev = dev; + + mbox->head = base; + mbox->tail = base + 0x4; + mbox->data = base + 0x8; + mbox->size = size; + mutex_init(&mbox->lock); + + regmap_write(dev->sram, mbox->head, 0); + regmap_write(dev->sram, mbox->tail, 0); + + return mbox; +} + +static int allegro_mbox_write(struct allegro_mbox *mbox, + const u32 *src, size_t size) +{ + struct regmap *sram = mbox->dev->sram; + unsigned int tail; + size_t size_no_wrap; + int err = 0; + int stride = regmap_get_reg_stride(sram); + + if (!src) + return -EINVAL; + + if (size > mbox->size) + return -EINVAL; + + mutex_lock(&mbox->lock); + regmap_read(sram, mbox->tail, &tail); + if (tail > mbox->size) { + err = -EIO; + goto out; + } + size_no_wrap = min(size, mbox->size - (size_t)tail); + regmap_bulk_write(sram, mbox->data + tail, + src, size_no_wrap / stride); + regmap_bulk_write(sram, mbox->data, + src + (size_no_wrap / sizeof(*src)), + (size - size_no_wrap) / stride); + regmap_write(sram, mbox->tail, (tail + size) % mbox->size); + +out: + mutex_unlock(&mbox->lock); + + return err; +} + +static ssize_t allegro_mbox_read(struct allegro_mbox *mbox, + u32 *dst, size_t nbyte) +{ + struct { + u16 length; + u16 type; + } __attribute__ ((__packed__)) *header; + struct regmap *sram = mbox->dev->sram; + unsigned int head; + ssize_t size; + size_t body_no_wrap; + int stride = regmap_get_reg_stride(sram); + + regmap_read(sram, mbox->head, &head); + if (head > mbox->size) + return -EIO; + + /* Assume that the header does not wrap. */ + regmap_bulk_read(sram, mbox->data + head, + dst, sizeof(*header) / stride); + header = (void *)dst; + size = header->length + sizeof(*header); + if (size > mbox->size || size & 0x3) + return -EIO; + if (size > nbyte) + return -EINVAL; + + /* + * The message might wrap within the mailbox. If the message does not + * wrap, the first read will read the entire message, otherwise the + * first read will read message until the end of the mailbox and the + * second read will read the remaining bytes from the beginning of the + * mailbox. + * + * Skip the header, as was already read to get the size of the body. + */ + body_no_wrap = min((size_t)header->length, + (size_t)(mbox->size - (head + sizeof(*header)))); + regmap_bulk_read(sram, mbox->data + head + sizeof(*header), + dst + (sizeof(*header) / sizeof(*dst)), + body_no_wrap / stride); + regmap_bulk_read(sram, mbox->data, + dst + (sizeof(*header) + body_no_wrap) / sizeof(*dst), + (header->length - body_no_wrap) / stride); + + regmap_write(sram, mbox->head, (head + size) % mbox->size); + + return size; +} + +/** + * allegro_mbox_send() - Send a message via the mailbox + * @mbox: the mailbox which is used to send the message + * @msg: the message to send + */ +static int allegro_mbox_send(struct allegro_mbox *mbox, void *msg) +{ + struct allegro_dev *dev = mbox->dev; + ssize_t size; + int err; + u32 *tmp; + + tmp = kzalloc(mbox->size, GFP_KERNEL); + if (!tmp) { + err = -ENOMEM; + goto out; + } + + size = allegro_encode_mail(tmp, msg); + + err = allegro_mbox_write(mbox, tmp, size); + kfree(tmp); + if (err) + goto out; + + allegro_mcu_interrupt(dev); + +out: + return err; +} + +/** + * allegro_mbox_notify() - Notify the mailbox about a new message + * @mbox: The allegro_mbox to notify + */ +static void allegro_mbox_notify(struct allegro_mbox *mbox) +{ + struct allegro_dev *dev = mbox->dev; + union mcu_msg_response *msg; + ssize_t size; + u32 *tmp; + int err; + + msg = kmalloc(sizeof(*msg), GFP_KERNEL); + if (!msg) + return; + + msg->header.version = dev->fw_info->mailbox_version; + + tmp = kmalloc(mbox->size, GFP_KERNEL); + if (!tmp) + goto out; + + size = allegro_mbox_read(mbox, tmp, mbox->size); + if (size < 0) + goto out; + + err = allegro_decode_mail(msg, tmp); + if (err) + goto out; + + allegro_handle_message(dev, msg); + +out: + kfree(tmp); + kfree(msg); +} + +static int allegro_encoder_buffer_init(struct allegro_dev *dev, + struct allegro_encoder_buffer *buffer) +{ + int err; + struct regmap *settings = dev->settings; + unsigned int supports_10_bit; + unsigned int memory_depth; + unsigned int num_cores; + unsigned int color_depth; + unsigned long clk_rate; + + /* We don't support the encoder buffer pre Firmware version 2019.2 */ + if (dev->fw_info->mailbox_version < MCU_MSG_VERSION_2019_2) + return -ENODEV; + + if (!settings) + return -EINVAL; + + err = regmap_read(settings, VCU_ENC_COLOR_DEPTH, &supports_10_bit); + if (err < 0) + return err; + err = regmap_read(settings, VCU_MEMORY_DEPTH, &memory_depth); + if (err < 0) + return err; + err = regmap_read(settings, VCU_NUM_CORE, &num_cores); + if (err < 0) + return err; + + clk_rate = clk_get_rate(dev->clk_core); + if (clk_rate == 0) + return -EINVAL; + + color_depth = supports_10_bit ? 10 : 8; + /* The firmware expects the encoder buffer size in bits. */ + buffer->size = color_depth * 32 * memory_depth; + buffer->color_depth = color_depth; + buffer->num_cores = num_cores; + buffer->clk_rate = clk_rate; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "using %d bits encoder buffer with %d-bit color depth\n", + buffer->size, color_depth); + + return 0; +} + +static void allegro_mcu_send_init(struct allegro_dev *dev, + dma_addr_t suballoc_dma, size_t suballoc_size) +{ + struct mcu_msg_init_request msg; + + memset(&msg, 0, sizeof(msg)); + + msg.header.type = MCU_MSG_TYPE_INIT; + msg.header.version = dev->fw_info->mailbox_version; + + msg.suballoc_dma = to_mcu_addr(dev, suballoc_dma); + msg.suballoc_size = to_mcu_size(dev, suballoc_size); + + if (dev->has_encoder_buffer) { + msg.encoder_buffer_size = dev->encoder_buffer.size; + msg.encoder_buffer_color_depth = dev->encoder_buffer.color_depth; + msg.num_cores = dev->encoder_buffer.num_cores; + msg.clk_rate = dev->encoder_buffer.clk_rate; + } else { + msg.encoder_buffer_size = -1; + msg.encoder_buffer_color_depth = -1; + msg.num_cores = -1; + msg.clk_rate = -1; + } + + allegro_mbox_send(dev->mbox_command, &msg); +} + +static u32 v4l2_pixelformat_to_mcu_format(u32 pixelformat) +{ + switch (pixelformat) { + case V4L2_PIX_FMT_NV12: + /* AL_420_8BITS: 0x100 -> NV12, 0x88 -> 8 bit */ + return 0x100 | 0x88; + default: + return -EINVAL; + } +} + +static u32 v4l2_colorspace_to_mcu_colorspace(enum v4l2_colorspace colorspace) +{ + switch (colorspace) { + case V4L2_COLORSPACE_REC709: + return 2; + case V4L2_COLORSPACE_SMPTE170M: + return 3; + case V4L2_COLORSPACE_SMPTE240M: + return 4; + case V4L2_COLORSPACE_SRGB: + return 7; + default: + /* UNKNOWN */ + return 0; + } +} + +static u8 v4l2_profile_to_mcu_profile(enum v4l2_mpeg_video_h264_profile profile) +{ + switch (profile) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + default: + return 66; + } +} + +static u16 v4l2_level_to_mcu_level(enum v4l2_mpeg_video_h264_level level) +{ + switch (level) { + case V4L2_MPEG_VIDEO_H264_LEVEL_1_0: + return 10; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_1: + return 11; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_2: + return 12; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_3: + return 13; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_0: + return 20; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_1: + return 21; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_2: + return 22; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_0: + return 30; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: + return 31; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: + return 32; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + return 40; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + return 41; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + return 42; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_0: + return 50; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_1: + default: + return 51; + } +} + +static u8 hevc_profile_to_mcu_profile(enum v4l2_mpeg_video_hevc_profile profile) +{ + switch (profile) { + default: + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN: + return 1; + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10: + return 2; + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE: + return 3; + } +} + +static u16 hevc_level_to_mcu_level(enum v4l2_mpeg_video_hevc_level level) +{ + switch (level) { + case V4L2_MPEG_VIDEO_HEVC_LEVEL_1: + return 10; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_2: + return 20; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1: + return 21; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_3: + return 30; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1: + return 31; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_4: + return 40; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1: + return 41; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5: + return 50; + default: + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1: + return 51; + } +} + +static u8 hevc_tier_to_mcu_tier(enum v4l2_mpeg_video_hevc_tier tier) +{ + switch (tier) { + default: + case V4L2_MPEG_VIDEO_HEVC_TIER_MAIN: + return 0; + case V4L2_MPEG_VIDEO_HEVC_TIER_HIGH: + return 1; + } +} + +static u32 +v4l2_bitrate_mode_to_mcu_mode(enum v4l2_mpeg_video_bitrate_mode mode) +{ + switch (mode) { + case V4L2_MPEG_VIDEO_BITRATE_MODE_VBR: + return 2; + case V4L2_MPEG_VIDEO_BITRATE_MODE_CBR: + default: + return 1; + } +} + +static u32 v4l2_cpb_size_to_mcu(unsigned int cpb_size, unsigned int bitrate) +{ + unsigned int cpb_size_kbit; + unsigned int bitrate_kbps; + + /* + * The mcu expects the CPB size in units of a 90 kHz clock, but the + * channel follows the V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE and stores + * the CPB size in kilobytes. + */ + cpb_size_kbit = cpb_size * BITS_PER_BYTE; + bitrate_kbps = bitrate / 1000; + + return (cpb_size_kbit * 90000) / bitrate_kbps; +} + +static s16 get_qp_delta(int minuend, int subtrahend) +{ + if (minuend == subtrahend) + return -1; + else + return minuend - subtrahend; +} + +static u32 allegro_channel_get_entropy_mode(struct allegro_channel *channel) +{ +#define ALLEGRO_ENTROPY_MODE_CAVLC 0 +#define ALLEGRO_ENTROPY_MODE_CABAC 1 + + /* HEVC always uses CABAC, but this has to be explicitly set */ + if (channel->codec == V4L2_PIX_FMT_HEVC) + return ALLEGRO_ENTROPY_MODE_CABAC; + + return ALLEGRO_ENTROPY_MODE_CAVLC; +} + +static int fill_create_channel_param(struct allegro_channel *channel, + struct create_channel_param *param) +{ + int i_frame_qp = allegro_channel_get_i_frame_qp(channel); + int p_frame_qp = allegro_channel_get_p_frame_qp(channel); + int b_frame_qp = allegro_channel_get_b_frame_qp(channel); + int bitrate_mode = v4l2_ctrl_g_ctrl(channel->mpeg_video_bitrate_mode); + unsigned int cpb_size = v4l2_ctrl_g_ctrl(channel->mpeg_video_cpb_size); + + param->width = channel->width; + param->height = channel->height; + param->format = v4l2_pixelformat_to_mcu_format(channel->pixelformat); + param->colorspace = + v4l2_colorspace_to_mcu_colorspace(channel->colorspace); + param->src_mode = 0x0; + + param->codec = channel->codec; + if (channel->codec == V4L2_PIX_FMT_H264) { + enum v4l2_mpeg_video_h264_profile profile; + enum v4l2_mpeg_video_h264_level level; + + profile = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_profile); + level = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_level); + + param->profile = v4l2_profile_to_mcu_profile(profile); + param->constraint_set_flags = BIT(1); + param->level = v4l2_level_to_mcu_level(level); + } else { + enum v4l2_mpeg_video_hevc_profile profile; + enum v4l2_mpeg_video_hevc_level level; + enum v4l2_mpeg_video_hevc_tier tier; + + profile = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_profile); + level = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_level); + tier = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_tier); + + param->profile = hevc_profile_to_mcu_profile(profile); + param->level = hevc_level_to_mcu_level(level); + param->tier = hevc_tier_to_mcu_tier(tier); + } + + param->log2_max_poc = LOG2_MAX_PIC_ORDER_CNT; + param->log2_max_frame_num = channel->log2_max_frame_num; + param->temporal_mvp_enable = channel->temporal_mvp_enable; + + param->dbf_ovr_en = channel->dbf_ovr_en; + param->override_lf = channel->enable_deblocking_filter_override; + param->enable_reordering = channel->enable_reordering; + param->entropy_mode = allegro_channel_get_entropy_mode(channel); + param->rdo_cost_mode = 1; + param->custom_lda = 1; + param->lf = 1; + param->lf_x_tile = channel->enable_loop_filter_across_tiles; + param->lf_x_slice = channel->enable_loop_filter_across_slices; + + param->src_bit_depth = 8; + + param->beta_offset = BETA_OFFSET_DIV_2; + param->tc_offset = TC_OFFSET_DIV_2; + param->num_slices = 1; + param->me_range[0] = channel->b_hrz_me_range; + param->me_range[1] = channel->b_vrt_me_range; + param->me_range[2] = channel->p_hrz_me_range; + param->me_range[3] = channel->p_vrt_me_range; + param->max_cu_size = channel->max_cu_size; + param->min_cu_size = channel->min_cu_size; + param->max_tu_size = channel->max_tu_size; + param->min_tu_size = channel->min_tu_size; + param->max_transfo_depth_intra = channel->max_transfo_depth_intra; + param->max_transfo_depth_inter = channel->max_transfo_depth_inter; + + param->encoder_buffer_enabled = v4l2_ctrl_g_ctrl(channel->encoder_buffer); + param->encoder_buffer_offset = 0; + + param->rate_control_mode = channel->frame_rc_enable ? + v4l2_bitrate_mode_to_mcu_mode(bitrate_mode) : 0; + + param->cpb_size = v4l2_cpb_size_to_mcu(cpb_size, channel->bitrate_peak); + /* Shall be ]0;cpb_size in 90 kHz units]. Use maximum value. */ + param->initial_rem_delay = param->cpb_size; + param->framerate = DIV_ROUND_UP(channel->framerate.numerator, + channel->framerate.denominator); + param->clk_ratio = channel->framerate.denominator == 1001 ? 1001 : 1000; + param->target_bitrate = channel->bitrate; + param->max_bitrate = channel->bitrate_peak; + param->initial_qp = i_frame_qp; + param->min_qp = allegro_channel_get_min_qp(channel); + param->max_qp = allegro_channel_get_max_qp(channel); + param->ip_delta = get_qp_delta(i_frame_qp, p_frame_qp); + param->pb_delta = get_qp_delta(p_frame_qp, b_frame_qp); + param->golden_ref = 0; + param->golden_delta = 2; + param->golden_ref_frequency = 10; + param->rate_control_option = 0x00000000; + + param->num_pixel = channel->width + channel->height; + param->max_psnr = 4200; + param->max_pixel_value = 255; + + param->gop_ctrl_mode = 0x00000002; + param->freq_idr = v4l2_ctrl_g_ctrl(channel->mpeg_video_gop_size); + param->freq_lt = 0; + param->gdr_mode = 0x00000000; + param->gop_length = v4l2_ctrl_g_ctrl(channel->mpeg_video_gop_size); + param->subframe_latency = 0x00000000; + + param->lda_factors[0] = 51; + param->lda_factors[1] = 90; + param->lda_factors[2] = 151; + param->lda_factors[3] = 151; + param->lda_factors[4] = 151; + param->lda_factors[5] = 151; + + param->max_num_merge_cand = 5; + + return 0; +} + +static int allegro_mcu_send_create_channel(struct allegro_dev *dev, + struct allegro_channel *channel) +{ + struct mcu_msg_create_channel msg; + struct allegro_buffer *blob = &channel->config_blob; + struct create_channel_param param; + size_t size; + + memset(¶m, 0, sizeof(param)); + fill_create_channel_param(channel, ¶m); + allegro_alloc_buffer(dev, blob, sizeof(struct create_channel_param)); + param.version = dev->fw_info->mailbox_version; + size = allegro_encode_config_blob(blob->vaddr, ¶m); + + memset(&msg, 0, sizeof(msg)); + + msg.header.type = MCU_MSG_TYPE_CREATE_CHANNEL; + msg.header.version = dev->fw_info->mailbox_version; + + msg.user_id = channel->user_id; + + msg.blob = blob->vaddr; + msg.blob_size = size; + msg.blob_mcu_addr = to_mcu_addr(dev, blob->paddr); + + allegro_mbox_send(dev->mbox_command, &msg); + + return 0; +} + +static int allegro_mcu_send_destroy_channel(struct allegro_dev *dev, + struct allegro_channel *channel) +{ + struct mcu_msg_destroy_channel msg; + + memset(&msg, 0, sizeof(msg)); + + msg.header.type = MCU_MSG_TYPE_DESTROY_CHANNEL; + msg.header.version = dev->fw_info->mailbox_version; + + msg.channel_id = channel->mcu_channel_id; + + allegro_mbox_send(dev->mbox_command, &msg); + + return 0; +} + +static int allegro_mcu_send_put_stream_buffer(struct allegro_dev *dev, + struct allegro_channel *channel, + dma_addr_t paddr, + unsigned long size, + u64 dst_handle) +{ + struct mcu_msg_put_stream_buffer msg; + + memset(&msg, 0, sizeof(msg)); + + msg.header.type = MCU_MSG_TYPE_PUT_STREAM_BUFFER; + msg.header.version = dev->fw_info->mailbox_version; + + msg.channel_id = channel->mcu_channel_id; + msg.dma_addr = to_codec_addr(dev, paddr); + msg.mcu_addr = to_mcu_addr(dev, paddr); + msg.size = size; + msg.offset = ENCODER_STREAM_OFFSET; + /* copied to mcu_msg_encode_frame_response */ + msg.dst_handle = dst_handle; + + allegro_mbox_send(dev->mbox_command, &msg); + + return 0; +} + +static int allegro_mcu_send_encode_frame(struct allegro_dev *dev, + struct allegro_channel *channel, + dma_addr_t src_y, dma_addr_t src_uv, + u64 src_handle) +{ + struct mcu_msg_encode_frame msg; + bool use_encoder_buffer = v4l2_ctrl_g_ctrl(channel->encoder_buffer); + + memset(&msg, 0, sizeof(msg)); + + msg.header.type = MCU_MSG_TYPE_ENCODE_FRAME; + msg.header.version = dev->fw_info->mailbox_version; + + msg.channel_id = channel->mcu_channel_id; + msg.encoding_options = AL_OPT_FORCE_LOAD; + if (use_encoder_buffer) + msg.encoding_options |= AL_OPT_USE_L2; + msg.pps_qp = 26; /* qp are relative to 26 */ + msg.user_param = 0; /* copied to mcu_msg_encode_frame_response */ + /* src_handle is copied to mcu_msg_encode_frame_response */ + msg.src_handle = src_handle; + msg.src_y = to_codec_addr(dev, src_y); + msg.src_uv = to_codec_addr(dev, src_uv); + msg.stride = channel->stride; + + allegro_mbox_send(dev->mbox_command, &msg); + + return 0; +} + +static int allegro_mcu_wait_for_init_timeout(struct allegro_dev *dev, + unsigned long timeout_ms) +{ + unsigned long tmo; + + tmo = wait_for_completion_timeout(&dev->init_complete, + msecs_to_jiffies(timeout_ms)); + if (tmo == 0) + return -ETIMEDOUT; + + reinit_completion(&dev->init_complete); + return 0; +} + +static int allegro_mcu_push_buffer_internal(struct allegro_channel *channel, + enum mcu_msg_type type) +{ + struct allegro_dev *dev = channel->dev; + struct mcu_msg_push_buffers_internal *msg; + struct mcu_msg_push_buffers_internal_buffer *buffer; + unsigned int num_buffers = 0; + size_t size; + struct allegro_buffer *al_buffer; + struct list_head *list; + int err; + + switch (type) { + case MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE: + list = &channel->buffers_reference; + break; + case MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE: + list = &channel->buffers_intermediate; + break; + default: + return -EINVAL; + } + + list_for_each_entry(al_buffer, list, head) + num_buffers++; + size = struct_size(msg, buffer, num_buffers); + + msg = kmalloc(size, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + msg->header.type = type; + msg->header.version = dev->fw_info->mailbox_version; + + msg->channel_id = channel->mcu_channel_id; + msg->num_buffers = num_buffers; + + buffer = msg->buffer; + list_for_each_entry(al_buffer, list, head) { + buffer->dma_addr = to_codec_addr(dev, al_buffer->paddr); + buffer->mcu_addr = to_mcu_addr(dev, al_buffer->paddr); + buffer->size = to_mcu_size(dev, al_buffer->size); + buffer++; + } + + err = allegro_mbox_send(dev->mbox_command, msg); + + kfree(msg); + return err; +} + +static int allegro_mcu_push_buffer_intermediate(struct allegro_channel *channel) +{ + enum mcu_msg_type type = MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE; + + return allegro_mcu_push_buffer_internal(channel, type); +} + +static int allegro_mcu_push_buffer_reference(struct allegro_channel *channel) +{ + enum mcu_msg_type type = MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE; + + return allegro_mcu_push_buffer_internal(channel, type); +} + +static int allocate_buffers_internal(struct allegro_channel *channel, + struct list_head *list, + size_t n, size_t size) +{ + struct allegro_dev *dev = channel->dev; + unsigned int i; + int err; + struct allegro_buffer *buffer, *tmp; + + for (i = 0; i < n; i++) { + buffer = kmalloc(sizeof(*buffer), GFP_KERNEL); + if (!buffer) { + err = -ENOMEM; + goto err; + } + INIT_LIST_HEAD(&buffer->head); + + err = allegro_alloc_buffer(dev, buffer, size); + if (err) + goto err; + list_add(&buffer->head, list); + } + + return 0; + +err: + list_for_each_entry_safe(buffer, tmp, list, head) { + list_del(&buffer->head); + allegro_free_buffer(dev, buffer); + kfree(buffer); + } + return err; +} + +static void destroy_buffers_internal(struct allegro_channel *channel, + struct list_head *list) +{ + struct allegro_dev *dev = channel->dev; + struct allegro_buffer *buffer, *tmp; + + list_for_each_entry_safe(buffer, tmp, list, head) { + list_del(&buffer->head); + allegro_free_buffer(dev, buffer); + kfree(buffer); + } +} + +static void destroy_reference_buffers(struct allegro_channel *channel) +{ + return destroy_buffers_internal(channel, &channel->buffers_reference); +} + +static void destroy_intermediate_buffers(struct allegro_channel *channel) +{ + return destroy_buffers_internal(channel, + &channel->buffers_intermediate); +} + +static int allocate_intermediate_buffers(struct allegro_channel *channel, + size_t n, size_t size) +{ + return allocate_buffers_internal(channel, + &channel->buffers_intermediate, + n, size); +} + +static int allocate_reference_buffers(struct allegro_channel *channel, + size_t n, size_t size) +{ + return allocate_buffers_internal(channel, + &channel->buffers_reference, + n, PAGE_ALIGN(size)); +} + +static ssize_t allegro_h264_write_sps(struct allegro_channel *channel, + void *dest, size_t n) +{ + struct allegro_dev *dev = channel->dev; + struct nal_h264_sps *sps; + ssize_t size; + unsigned int size_mb = SIZE_MACROBLOCK; + /* Calculation of crop units in Rec. ITU-T H.264 (04/2017) p. 76 */ + unsigned int crop_unit_x = 2; + unsigned int crop_unit_y = 2; + enum v4l2_mpeg_video_h264_profile profile; + enum v4l2_mpeg_video_h264_level level; + unsigned int cpb_size; + unsigned int cpb_size_scale; + + sps = kzalloc(sizeof(*sps), GFP_KERNEL); + if (!sps) + return -ENOMEM; + + profile = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_profile); + level = v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_level); + + sps->profile_idc = nal_h264_profile(profile); + sps->constraint_set0_flag = 0; + sps->constraint_set1_flag = 1; + sps->constraint_set2_flag = 0; + sps->constraint_set3_flag = 0; + sps->constraint_set4_flag = 0; + sps->constraint_set5_flag = 0; + sps->level_idc = nal_h264_level(level); + sps->seq_parameter_set_id = 0; + sps->log2_max_frame_num_minus4 = LOG2_MAX_FRAME_NUM - 4; + sps->pic_order_cnt_type = 0; + sps->log2_max_pic_order_cnt_lsb_minus4 = LOG2_MAX_PIC_ORDER_CNT - 4; + sps->max_num_ref_frames = 3; + sps->gaps_in_frame_num_value_allowed_flag = 0; + sps->pic_width_in_mbs_minus1 = + DIV_ROUND_UP(channel->width, size_mb) - 1; + sps->pic_height_in_map_units_minus1 = + DIV_ROUND_UP(channel->height, size_mb) - 1; + sps->frame_mbs_only_flag = 1; + sps->mb_adaptive_frame_field_flag = 0; + sps->direct_8x8_inference_flag = 1; + sps->frame_cropping_flag = + (channel->width % size_mb) || (channel->height % size_mb); + if (sps->frame_cropping_flag) { + sps->crop_left = 0; + sps->crop_right = (round_up(channel->width, size_mb) - channel->width) / crop_unit_x; + sps->crop_top = 0; + sps->crop_bottom = (round_up(channel->height, size_mb) - channel->height) / crop_unit_y; + } + sps->vui_parameters_present_flag = 1; + sps->vui.aspect_ratio_info_present_flag = 0; + sps->vui.overscan_info_present_flag = 0; + + sps->vui.video_signal_type_present_flag = 1; + sps->vui.video_format = 5; /* unspecified */ + sps->vui.video_full_range_flag = nal_h264_full_range(channel->quantization); + sps->vui.colour_description_present_flag = 1; + sps->vui.colour_primaries = nal_h264_color_primaries(channel->colorspace); + sps->vui.transfer_characteristics = + nal_h264_transfer_characteristics(channel->colorspace, channel->xfer_func); + sps->vui.matrix_coefficients = + nal_h264_matrix_coeffs(channel->colorspace, channel->ycbcr_enc); + + sps->vui.chroma_loc_info_present_flag = 1; + sps->vui.chroma_sample_loc_type_top_field = 0; + sps->vui.chroma_sample_loc_type_bottom_field = 0; + + sps->vui.timing_info_present_flag = 1; + sps->vui.num_units_in_tick = channel->framerate.denominator; + sps->vui.time_scale = 2 * channel->framerate.numerator; + + sps->vui.fixed_frame_rate_flag = 1; + sps->vui.nal_hrd_parameters_present_flag = 0; + sps->vui.vcl_hrd_parameters_present_flag = 1; + sps->vui.vcl_hrd_parameters.cpb_cnt_minus1 = 0; + /* See Rec. ITU-T H.264 (04/2017) p. 410 E-53 */ + sps->vui.vcl_hrd_parameters.bit_rate_scale = + ffs(channel->bitrate_peak) - 6; + sps->vui.vcl_hrd_parameters.bit_rate_value_minus1[0] = + channel->bitrate_peak / (1 << (6 + sps->vui.vcl_hrd_parameters.bit_rate_scale)) - 1; + /* See Rec. ITU-T H.264 (04/2017) p. 410 E-54 */ + cpb_size = v4l2_ctrl_g_ctrl(channel->mpeg_video_cpb_size); + cpb_size_scale = ffs(cpb_size) - 4; + sps->vui.vcl_hrd_parameters.cpb_size_scale = cpb_size_scale; + sps->vui.vcl_hrd_parameters.cpb_size_value_minus1[0] = + (cpb_size * 1000) / (1 << (4 + cpb_size_scale)) - 1; + sps->vui.vcl_hrd_parameters.cbr_flag[0] = + !v4l2_ctrl_g_ctrl(channel->mpeg_video_frame_rc_enable); + sps->vui.vcl_hrd_parameters.initial_cpb_removal_delay_length_minus1 = 31; + sps->vui.vcl_hrd_parameters.cpb_removal_delay_length_minus1 = 31; + sps->vui.vcl_hrd_parameters.dpb_output_delay_length_minus1 = 31; + sps->vui.vcl_hrd_parameters.time_offset_length = 0; + sps->vui.low_delay_hrd_flag = 0; + sps->vui.pic_struct_present_flag = 1; + sps->vui.bitstream_restriction_flag = 0; + + size = nal_h264_write_sps(&dev->plat_dev->dev, dest, n, sps); + + kfree(sps); + + return size; +} + +static ssize_t allegro_h264_write_pps(struct allegro_channel *channel, + void *dest, size_t n) +{ + struct allegro_dev *dev = channel->dev; + struct nal_h264_pps *pps; + ssize_t size; + + pps = kzalloc(sizeof(*pps), GFP_KERNEL); + if (!pps) + return -ENOMEM; + + pps->pic_parameter_set_id = 0; + pps->seq_parameter_set_id = 0; + pps->entropy_coding_mode_flag = 0; + pps->bottom_field_pic_order_in_frame_present_flag = 0; + pps->num_slice_groups_minus1 = 0; + pps->num_ref_idx_l0_default_active_minus1 = channel->num_ref_idx_l0 - 1; + pps->num_ref_idx_l1_default_active_minus1 = channel->num_ref_idx_l1 - 1; + pps->weighted_pred_flag = 0; + pps->weighted_bipred_idc = 0; + pps->pic_init_qp_minus26 = 0; + pps->pic_init_qs_minus26 = 0; + pps->chroma_qp_index_offset = 0; + pps->deblocking_filter_control_present_flag = 1; + pps->constrained_intra_pred_flag = 0; + pps->redundant_pic_cnt_present_flag = 0; + pps->transform_8x8_mode_flag = 0; + pps->pic_scaling_matrix_present_flag = 0; + pps->second_chroma_qp_index_offset = 0; + + size = nal_h264_write_pps(&dev->plat_dev->dev, dest, n, pps); + + kfree(pps); + + return size; +} + +static void allegro_channel_eos_event(struct allegro_channel *channel) +{ + const struct v4l2_event eos_event = { + .type = V4L2_EVENT_EOS + }; + + v4l2_event_queue_fh(&channel->fh, &eos_event); +} + +static ssize_t allegro_hevc_write_vps(struct allegro_channel *channel, + void *dest, size_t n) +{ + struct allegro_dev *dev = channel->dev; + struct nal_hevc_vps *vps; + struct nal_hevc_profile_tier_level *ptl; + ssize_t size; + unsigned int num_ref_frames = channel->num_ref_idx_l0; + s32 profile = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_profile); + s32 level = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_level); + s32 tier = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_tier); + + vps = kzalloc(sizeof(*vps), GFP_KERNEL); + if (!vps) + return -ENOMEM; + + vps->base_layer_internal_flag = 1; + vps->base_layer_available_flag = 1; + vps->temporal_id_nesting_flag = 1; + + ptl = &vps->profile_tier_level; + ptl->general_profile_idc = nal_hevc_profile(profile); + ptl->general_profile_compatibility_flag[ptl->general_profile_idc] = 1; + ptl->general_tier_flag = nal_hevc_tier(tier); + ptl->general_progressive_source_flag = 1; + ptl->general_frame_only_constraint_flag = 1; + ptl->general_level_idc = nal_hevc_level(level); + + vps->sub_layer_ordering_info_present_flag = 0; + vps->max_dec_pic_buffering_minus1[0] = num_ref_frames; + vps->max_num_reorder_pics[0] = num_ref_frames; + + size = nal_hevc_write_vps(&dev->plat_dev->dev, dest, n, vps); + + kfree(vps); + + return size; +} + +static ssize_t allegro_hevc_write_sps(struct allegro_channel *channel, + void *dest, size_t n) +{ + struct allegro_dev *dev = channel->dev; + struct nal_hevc_sps *sps; + struct nal_hevc_profile_tier_level *ptl; + struct nal_hevc_vui_parameters *vui; + struct nal_hevc_hrd_parameters *hrd; + ssize_t size; + unsigned int cpb_size; + unsigned int num_ref_frames = channel->num_ref_idx_l0; + s32 profile = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_profile); + s32 level = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_level); + s32 tier = v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_tier); + + sps = kzalloc(sizeof(*sps), GFP_KERNEL); + if (!sps) + return -ENOMEM; + + sps->temporal_id_nesting_flag = 1; + + ptl = &sps->profile_tier_level; + ptl->general_profile_idc = nal_hevc_profile(profile); + ptl->general_profile_compatibility_flag[ptl->general_profile_idc] = 1; + ptl->general_tier_flag = nal_hevc_tier(tier); + ptl->general_progressive_source_flag = 1; + ptl->general_frame_only_constraint_flag = 1; + ptl->general_level_idc = nal_hevc_level(level); + + sps->seq_parameter_set_id = 0; + sps->chroma_format_idc = 1; /* Only 4:2:0 sampling supported */ + sps->pic_width_in_luma_samples = round_up(channel->width, 8); + sps->pic_height_in_luma_samples = round_up(channel->height, 8); + sps->conf_win_right_offset = + sps->pic_width_in_luma_samples - channel->width; + sps->conf_win_bottom_offset = + sps->pic_height_in_luma_samples - channel->height; + sps->conformance_window_flag = + sps->conf_win_right_offset || sps->conf_win_bottom_offset; + + sps->log2_max_pic_order_cnt_lsb_minus4 = LOG2_MAX_PIC_ORDER_CNT - 4; + + sps->sub_layer_ordering_info_present_flag = 1; + sps->max_dec_pic_buffering_minus1[0] = num_ref_frames; + sps->max_num_reorder_pics[0] = num_ref_frames; + + sps->log2_min_luma_coding_block_size_minus3 = + channel->min_cu_size - 3; + sps->log2_diff_max_min_luma_coding_block_size = + channel->max_cu_size - channel->min_cu_size; + sps->log2_min_luma_transform_block_size_minus2 = + channel->min_tu_size - 2; + sps->log2_diff_max_min_luma_transform_block_size = + channel->max_tu_size - channel->min_tu_size; + sps->max_transform_hierarchy_depth_intra = + channel->max_transfo_depth_intra; + sps->max_transform_hierarchy_depth_inter = + channel->max_transfo_depth_inter; + + sps->sps_temporal_mvp_enabled_flag = channel->temporal_mvp_enable; + sps->strong_intra_smoothing_enabled_flag = channel->max_cu_size > 4; + + sps->vui_parameters_present_flag = 1; + vui = &sps->vui; + + vui->video_signal_type_present_flag = 1; + vui->video_format = 5; /* unspecified */ + vui->video_full_range_flag = nal_hevc_full_range(channel->quantization); + vui->colour_description_present_flag = 1; + vui->colour_primaries = nal_hevc_color_primaries(channel->colorspace); + vui->transfer_characteristics = nal_hevc_transfer_characteristics(channel->colorspace, + channel->xfer_func); + vui->matrix_coeffs = nal_hevc_matrix_coeffs(channel->colorspace, channel->ycbcr_enc); + + vui->chroma_loc_info_present_flag = 1; + vui->chroma_sample_loc_type_top_field = 0; + vui->chroma_sample_loc_type_bottom_field = 0; + + vui->vui_timing_info_present_flag = 1; + vui->vui_num_units_in_tick = channel->framerate.denominator; + vui->vui_time_scale = channel->framerate.numerator; + + vui->bitstream_restriction_flag = 1; + vui->motion_vectors_over_pic_boundaries_flag = 1; + vui->restricted_ref_pic_lists_flag = 1; + vui->log2_max_mv_length_horizontal = 15; + vui->log2_max_mv_length_vertical = 15; + + vui->vui_hrd_parameters_present_flag = 1; + hrd = &vui->nal_hrd_parameters; + hrd->vcl_hrd_parameters_present_flag = 1; + + hrd->initial_cpb_removal_delay_length_minus1 = 31; + hrd->au_cpb_removal_delay_length_minus1 = 30; + hrd->dpb_output_delay_length_minus1 = 30; + + hrd->bit_rate_scale = ffs(channel->bitrate_peak) - 6; + hrd->vcl_hrd[0].bit_rate_value_minus1[0] = + (channel->bitrate_peak >> (6 + hrd->bit_rate_scale)) - 1; + + cpb_size = v4l2_ctrl_g_ctrl(channel->mpeg_video_cpb_size) * 1000; + hrd->cpb_size_scale = ffs(cpb_size) - 4; + hrd->vcl_hrd[0].cpb_size_value_minus1[0] = (cpb_size >> (4 + hrd->cpb_size_scale)) - 1; + + hrd->vcl_hrd[0].cbr_flag[0] = !v4l2_ctrl_g_ctrl(channel->mpeg_video_frame_rc_enable); + + size = nal_hevc_write_sps(&dev->plat_dev->dev, dest, n, sps); + + kfree(sps); + + return size; +} + +static ssize_t allegro_hevc_write_pps(struct allegro_channel *channel, + struct mcu_msg_encode_frame_response *msg, + void *dest, size_t n) +{ + struct allegro_dev *dev = channel->dev; + struct nal_hevc_pps *pps; + ssize_t size; + int i; + + pps = kzalloc(sizeof(*pps), GFP_KERNEL); + if (!pps) + return -ENOMEM; + + pps->pps_pic_parameter_set_id = 0; + pps->pps_seq_parameter_set_id = 0; + + if (msg->num_column > 1 || msg->num_row > 1) { + pps->tiles_enabled_flag = 1; + pps->num_tile_columns_minus1 = msg->num_column - 1; + pps->num_tile_rows_minus1 = msg->num_row - 1; + + for (i = 0; i < msg->num_column; i++) + pps->column_width_minus1[i] = msg->tile_width[i] - 1; + + for (i = 0; i < msg->num_row; i++) + pps->row_height_minus1[i] = msg->tile_height[i] - 1; + } + + pps->loop_filter_across_tiles_enabled_flag = + channel->enable_loop_filter_across_tiles; + pps->pps_loop_filter_across_slices_enabled_flag = + channel->enable_loop_filter_across_slices; + pps->deblocking_filter_control_present_flag = 1; + pps->deblocking_filter_override_enabled_flag = + channel->enable_deblocking_filter_override; + pps->pps_beta_offset_div2 = BETA_OFFSET_DIV_2; + pps->pps_tc_offset_div2 = TC_OFFSET_DIV_2; + + pps->lists_modification_present_flag = channel->enable_reordering; + + size = nal_hevc_write_pps(&dev->plat_dev->dev, dest, n, pps); + + kfree(pps); + + return size; +} + +static u64 allegro_put_buffer(struct allegro_channel *channel, + struct list_head *list, + struct vb2_v4l2_buffer *buffer) +{ + struct v4l2_m2m_buffer *b = container_of(buffer, + struct v4l2_m2m_buffer, vb); + struct allegro_m2m_buffer *shadow = to_allegro_m2m_buffer(b); + + mutex_lock(&channel->shadow_list_lock); + list_add_tail(&shadow->head, list); + mutex_unlock(&channel->shadow_list_lock); + + return ptr_to_u64(buffer); +} + +static struct vb2_v4l2_buffer * +allegro_get_buffer(struct allegro_channel *channel, + struct list_head *list, u64 handle) +{ + struct allegro_m2m_buffer *shadow, *tmp; + struct vb2_v4l2_buffer *buffer = NULL; + + mutex_lock(&channel->shadow_list_lock); + list_for_each_entry_safe(shadow, tmp, list, head) { + if (handle == ptr_to_u64(&shadow->buf.vb)) { + buffer = &shadow->buf.vb; + list_del_init(&shadow->head); + break; + } + } + mutex_unlock(&channel->shadow_list_lock); + + return buffer; +} + +static void allegro_channel_finish_frame(struct allegro_channel *channel, + struct mcu_msg_encode_frame_response *msg) +{ + struct allegro_dev *dev = channel->dev; + struct vb2_v4l2_buffer *src_buf; + struct vb2_v4l2_buffer *dst_buf; + struct { + u32 offset; + u32 size; + } *partition; + enum vb2_buffer_state state = VB2_BUF_STATE_ERROR; + char *curr; + ssize_t len; + ssize_t free; + + src_buf = allegro_get_buffer(channel, &channel->source_shadow_list, + msg->src_handle); + if (!src_buf) + v4l2_warn(&dev->v4l2_dev, + "channel %d: invalid source buffer\n", + channel->mcu_channel_id); + + dst_buf = allegro_get_buffer(channel, &channel->stream_shadow_list, + msg->dst_handle); + if (!dst_buf) + v4l2_warn(&dev->v4l2_dev, + "channel %d: invalid stream buffer\n", + channel->mcu_channel_id); + + if (!src_buf || !dst_buf) + goto err; + + if (v4l2_m2m_is_last_draining_src_buf(channel->fh.m2m_ctx, src_buf)) { + dst_buf->flags |= V4L2_BUF_FLAG_LAST; + allegro_channel_eos_event(channel); + v4l2_m2m_mark_stopped(channel->fh.m2m_ctx); + } + + dst_buf->sequence = channel->csequence++; + + if (msg->error_code & AL_ERROR) { + v4l2_err(&dev->v4l2_dev, + "channel %d: failed to encode frame: %s (%x)\n", + channel->mcu_channel_id, + allegro_err_to_string(msg->error_code), + msg->error_code); + goto err; + } + + if (msg->partition_table_size != 1) { + v4l2_warn(&dev->v4l2_dev, + "channel %d: only handling first partition table entry (%d entries)\n", + channel->mcu_channel_id, msg->partition_table_size); + } + + if (msg->partition_table_offset + + msg->partition_table_size * sizeof(*partition) > + vb2_plane_size(&dst_buf->vb2_buf, 0)) { + v4l2_err(&dev->v4l2_dev, + "channel %d: partition table outside of dst_buf\n", + channel->mcu_channel_id); + goto err; + } + + partition = + vb2_plane_vaddr(&dst_buf->vb2_buf, 0) + msg->partition_table_offset; + if (partition->offset + partition->size > + vb2_plane_size(&dst_buf->vb2_buf, 0)) { + v4l2_err(&dev->v4l2_dev, + "channel %d: encoded frame is outside of dst_buf (offset 0x%x, size 0x%x)\n", + channel->mcu_channel_id, partition->offset, + partition->size); + goto err; + } + + v4l2_dbg(2, debug, &dev->v4l2_dev, + "channel %d: encoded frame of size %d is at offset 0x%x\n", + channel->mcu_channel_id, partition->size, partition->offset); + + /* + * The payload must include the data before the partition offset, + * because we will put the sps and pps data there. + */ + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, + partition->offset + partition->size); + + curr = vb2_plane_vaddr(&dst_buf->vb2_buf, 0); + free = partition->offset; + + if (channel->codec == V4L2_PIX_FMT_HEVC && msg->is_idr) { + len = allegro_hevc_write_vps(channel, curr, free); + if (len < 0) { + v4l2_err(&dev->v4l2_dev, + "not enough space for video parameter set: %zd left\n", + free); + goto err; + } + curr += len; + free -= len; + v4l2_dbg(1, debug, &dev->v4l2_dev, + "channel %d: wrote %zd byte VPS nal unit\n", + channel->mcu_channel_id, len); + } + + if (msg->is_idr) { + if (channel->codec == V4L2_PIX_FMT_H264) + len = allegro_h264_write_sps(channel, curr, free); + else + len = allegro_hevc_write_sps(channel, curr, free); + if (len < 0) { + v4l2_err(&dev->v4l2_dev, + "not enough space for sequence parameter set: %zd left\n", + free); + goto err; + } + curr += len; + free -= len; + v4l2_dbg(1, debug, &dev->v4l2_dev, + "channel %d: wrote %zd byte SPS nal unit\n", + channel->mcu_channel_id, len); + } + + if (msg->slice_type == AL_ENC_SLICE_TYPE_I) { + if (channel->codec == V4L2_PIX_FMT_H264) + len = allegro_h264_write_pps(channel, curr, free); + else + len = allegro_hevc_write_pps(channel, msg, curr, free); + if (len < 0) { + v4l2_err(&dev->v4l2_dev, + "not enough space for picture parameter set: %zd left\n", + free); + goto err; + } + curr += len; + free -= len; + v4l2_dbg(1, debug, &dev->v4l2_dev, + "channel %d: wrote %zd byte PPS nal unit\n", + channel->mcu_channel_id, len); + } + + if (msg->slice_type != AL_ENC_SLICE_TYPE_I && !msg->is_idr) { + dst_buf->vb2_buf.planes[0].data_offset = free; + free = 0; + } else { + if (channel->codec == V4L2_PIX_FMT_H264) + len = nal_h264_write_filler(&dev->plat_dev->dev, curr, free); + else + len = nal_hevc_write_filler(&dev->plat_dev->dev, curr, free); + if (len < 0) { + v4l2_err(&dev->v4l2_dev, + "failed to write %zd filler data\n", free); + goto err; + } + curr += len; + free -= len; + v4l2_dbg(2, debug, &dev->v4l2_dev, + "channel %d: wrote %zd bytes filler nal unit\n", + channel->mcu_channel_id, len); + } + + if (free != 0) { + v4l2_err(&dev->v4l2_dev, + "non-VCL NAL units do not fill space until VCL NAL unit: %zd bytes left\n", + free); + goto err; + } + + state = VB2_BUF_STATE_DONE; + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, false); + if (msg->is_idr) + dst_buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + else + dst_buf->flags |= V4L2_BUF_FLAG_PFRAME; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "channel %d: encoded frame #%03d (%s%s, QP %d, %d bytes)\n", + channel->mcu_channel_id, + dst_buf->sequence, + msg->is_idr ? "IDR, " : "", + msg->slice_type == AL_ENC_SLICE_TYPE_I ? "I slice" : + msg->slice_type == AL_ENC_SLICE_TYPE_P ? "P slice" : "unknown", + msg->qp, partition->size); + +err: + if (src_buf) + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + + if (dst_buf) + v4l2_m2m_buf_done(dst_buf, state); +} + +static int allegro_handle_init(struct allegro_dev *dev, + struct mcu_msg_init_response *msg) +{ + complete(&dev->init_complete); + + return 0; +} + +static int +allegro_handle_create_channel(struct allegro_dev *dev, + struct mcu_msg_create_channel_response *msg) +{ + struct allegro_channel *channel; + int err = 0; + struct create_channel_param param; + + channel = allegro_find_channel_by_user_id(dev, msg->user_id); + if (IS_ERR(channel)) { + v4l2_warn(&dev->v4l2_dev, + "received %s for unknown user %d\n", + msg_type_name(msg->header.type), + msg->user_id); + return -EINVAL; + } + + if (msg->error_code) { + v4l2_err(&dev->v4l2_dev, + "user %d: mcu failed to create channel: %s (%x)\n", + channel->user_id, + allegro_err_to_string(msg->error_code), + msg->error_code); + err = -EIO; + goto out; + } + + channel->mcu_channel_id = msg->channel_id; + v4l2_dbg(1, debug, &dev->v4l2_dev, + "user %d: channel has channel id %d\n", + channel->user_id, channel->mcu_channel_id); + + err = allegro_decode_config_blob(¶m, msg, channel->config_blob.vaddr); + allegro_free_buffer(channel->dev, &channel->config_blob); + if (err) + goto out; + + channel->num_ref_idx_l0 = param.num_ref_idx_l0; + channel->num_ref_idx_l1 = param.num_ref_idx_l1; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "channel %d: intermediate buffers: %d x %d bytes\n", + channel->mcu_channel_id, + msg->int_buffers_count, msg->int_buffers_size); + err = allocate_intermediate_buffers(channel, msg->int_buffers_count, + msg->int_buffers_size); + if (err) { + v4l2_err(&dev->v4l2_dev, + "channel %d: failed to allocate intermediate buffers\n", + channel->mcu_channel_id); + goto out; + } + err = allegro_mcu_push_buffer_intermediate(channel); + if (err) + goto out; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "channel %d: reference buffers: %d x %d bytes\n", + channel->mcu_channel_id, + msg->rec_buffers_count, msg->rec_buffers_size); + err = allocate_reference_buffers(channel, msg->rec_buffers_count, + msg->rec_buffers_size); + if (err) { + v4l2_err(&dev->v4l2_dev, + "channel %d: failed to allocate reference buffers\n", + channel->mcu_channel_id); + goto out; + } + err = allegro_mcu_push_buffer_reference(channel); + if (err) + goto out; + +out: + channel->error = err; + complete(&channel->completion); + + /* Handled successfully, error is passed via channel->error */ + return 0; +} + +static int +allegro_handle_destroy_channel(struct allegro_dev *dev, + struct mcu_msg_destroy_channel_response *msg) +{ + struct allegro_channel *channel; + + channel = allegro_find_channel_by_channel_id(dev, msg->channel_id); + if (IS_ERR(channel)) { + v4l2_err(&dev->v4l2_dev, + "received %s for unknown channel %d\n", + msg_type_name(msg->header.type), + msg->channel_id); + return -EINVAL; + } + + v4l2_dbg(2, debug, &dev->v4l2_dev, + "user %d: vcu destroyed channel %d\n", + channel->user_id, channel->mcu_channel_id); + complete(&channel->completion); + + return 0; +} + +static int +allegro_handle_encode_frame(struct allegro_dev *dev, + struct mcu_msg_encode_frame_response *msg) +{ + struct allegro_channel *channel; + + channel = allegro_find_channel_by_channel_id(dev, msg->channel_id); + if (IS_ERR(channel)) { + v4l2_err(&dev->v4l2_dev, + "received %s for unknown channel %d\n", + msg_type_name(msg->header.type), + msg->channel_id); + return -EINVAL; + } + + allegro_channel_finish_frame(channel, msg); + + return 0; +} + +static void allegro_handle_message(struct allegro_dev *dev, + union mcu_msg_response *msg) +{ + switch (msg->header.type) { + case MCU_MSG_TYPE_INIT: + allegro_handle_init(dev, &msg->init); + break; + case MCU_MSG_TYPE_CREATE_CHANNEL: + allegro_handle_create_channel(dev, &msg->create_channel); + break; + case MCU_MSG_TYPE_DESTROY_CHANNEL: + allegro_handle_destroy_channel(dev, &msg->destroy_channel); + break; + case MCU_MSG_TYPE_ENCODE_FRAME: + allegro_handle_encode_frame(dev, &msg->encode_frame); + break; + default: + v4l2_warn(&dev->v4l2_dev, + "%s: unknown message %s\n", + __func__, msg_type_name(msg->header.type)); + break; + } +} + +static irqreturn_t allegro_hardirq(int irq, void *data) +{ + struct allegro_dev *dev = data; + unsigned int status; + + regmap_read(dev->regmap, AL5_ITC_CPU_IRQ_STA, &status); + if (!(status & AL5_ITC_CPU_IRQ_STA_TRIGGERED)) + return IRQ_NONE; + + regmap_write(dev->regmap, AL5_ITC_CPU_IRQ_CLR, status); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t allegro_irq_thread(int irq, void *data) +{ + struct allegro_dev *dev = data; + + /* + * The firmware is initialized after the mailbox is setup. We further + * check the AL5_ITC_CPU_IRQ_STA register, if the firmware actually + * triggered the interrupt. Although this should not happen, make sure + * that we ignore interrupts, if the mailbox is not initialized. + */ + if (!dev->mbox_status) + return IRQ_NONE; + + allegro_mbox_notify(dev->mbox_status); + + return IRQ_HANDLED; +} + +static void allegro_copy_firmware(struct allegro_dev *dev, + const u8 * const buf, size_t size) +{ + int err = 0; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "copy mcu firmware (%zu B) to SRAM\n", size); + err = regmap_bulk_write(dev->sram, 0x0, buf, size / 4); + if (err) + v4l2_err(&dev->v4l2_dev, + "failed to copy firmware: %d\n", err); +} + +static void allegro_copy_fw_codec(struct allegro_dev *dev, + const u8 * const buf, size_t size) +{ + int err; + dma_addr_t icache_offset, dcache_offset; + + /* + * The downstream allocates 600 KB for the codec firmware to have some + * extra space for "possible extensions." My tests were fine with + * allocating just enough memory for the actual firmware, but I am not + * sure that the firmware really does not use the remaining space. + */ + err = allegro_alloc_buffer(dev, &dev->firmware, size); + if (err) { + v4l2_err(&dev->v4l2_dev, + "failed to allocate %zu bytes for firmware\n", size); + return; + } + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "copy codec firmware (%zd B) to phys %pad\n", + size, &dev->firmware.paddr); + memcpy(dev->firmware.vaddr, buf, size); + + regmap_write(dev->regmap, AXI_ADDR_OFFSET_IP, + upper_32_bits(dev->firmware.paddr)); + + icache_offset = dev->firmware.paddr - MCU_CACHE_OFFSET; + v4l2_dbg(2, debug, &dev->v4l2_dev, + "icache_offset: msb = 0x%x, lsb = 0x%x\n", + upper_32_bits(icache_offset), lower_32_bits(icache_offset)); + regmap_write(dev->regmap, AL5_ICACHE_ADDR_OFFSET_MSB, + upper_32_bits(icache_offset)); + regmap_write(dev->regmap, AL5_ICACHE_ADDR_OFFSET_LSB, + lower_32_bits(icache_offset)); + + dcache_offset = + (dev->firmware.paddr & 0xffffffff00000000ULL) - MCU_CACHE_OFFSET; + v4l2_dbg(2, debug, &dev->v4l2_dev, + "dcache_offset: msb = 0x%x, lsb = 0x%x\n", + upper_32_bits(dcache_offset), lower_32_bits(dcache_offset)); + regmap_write(dev->regmap, AL5_DCACHE_ADDR_OFFSET_MSB, + upper_32_bits(dcache_offset)); + regmap_write(dev->regmap, AL5_DCACHE_ADDR_OFFSET_LSB, + lower_32_bits(dcache_offset)); +} + +static void allegro_free_fw_codec(struct allegro_dev *dev) +{ + allegro_free_buffer(dev, &dev->firmware); +} + +/* + * Control functions for the MCU + */ + +static int allegro_mcu_enable_interrupts(struct allegro_dev *dev) +{ + return regmap_write(dev->regmap, AL5_ITC_CPU_IRQ_MSK, BIT(0)); +} + +static int allegro_mcu_disable_interrupts(struct allegro_dev *dev) +{ + return regmap_write(dev->regmap, AL5_ITC_CPU_IRQ_MSK, 0); +} + +static int allegro_mcu_wait_for_sleep(struct allegro_dev *dev) +{ + unsigned long timeout; + unsigned int status; + + timeout = jiffies + msecs_to_jiffies(100); + while (regmap_read(dev->regmap, AL5_MCU_STA, &status) == 0 && + status != AL5_MCU_STA_SLEEP) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + cpu_relax(); + } + + return 0; +} + +static int allegro_mcu_start(struct allegro_dev *dev) +{ + unsigned long timeout; + unsigned int status; + int err; + + err = regmap_write(dev->regmap, AL5_MCU_WAKEUP, BIT(0)); + if (err) + return err; + + timeout = jiffies + msecs_to_jiffies(100); + while (regmap_read(dev->regmap, AL5_MCU_STA, &status) == 0 && + status == AL5_MCU_STA_SLEEP) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + cpu_relax(); + } + + err = regmap_write(dev->regmap, AL5_MCU_WAKEUP, 0); + if (err) + return err; + + return 0; +} + +static int allegro_mcu_reset(struct allegro_dev *dev) +{ + int err; + + /* + * Ensure that the AL5_MCU_WAKEUP bit is set to 0 otherwise the mcu + * does not go to sleep after the reset. + */ + err = regmap_write(dev->regmap, AL5_MCU_WAKEUP, 0); + if (err) + return err; + + err = regmap_write(dev->regmap, + AL5_MCU_RESET_MODE, AL5_MCU_RESET_MODE_SLEEP); + if (err < 0) + return err; + + err = regmap_write(dev->regmap, AL5_MCU_RESET, AL5_MCU_RESET_SOFT); + if (err < 0) + return err; + + return allegro_mcu_wait_for_sleep(dev); +} + +static void allegro_mcu_interrupt(struct allegro_dev *dev) +{ + regmap_write(dev->regmap, AL5_MCU_INTERRUPT, BIT(0)); +} + +static void allegro_destroy_channel(struct allegro_channel *channel) +{ + struct allegro_dev *dev = channel->dev; + unsigned long timeout; + + if (channel_exists(channel)) { + reinit_completion(&channel->completion); + allegro_mcu_send_destroy_channel(dev, channel); + timeout = wait_for_completion_timeout(&channel->completion, + msecs_to_jiffies(5000)); + if (timeout == 0) + v4l2_warn(&dev->v4l2_dev, + "channel %d: timeout while destroying\n", + channel->mcu_channel_id); + + channel->mcu_channel_id = -1; + } + + destroy_intermediate_buffers(channel); + destroy_reference_buffers(channel); + + v4l2_ctrl_grab(channel->mpeg_video_h264_profile, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_level, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_i_frame_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_max_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_min_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_p_frame_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_h264_b_frame_qp, false); + + v4l2_ctrl_grab(channel->mpeg_video_hevc_profile, false); + v4l2_ctrl_grab(channel->mpeg_video_hevc_level, false); + v4l2_ctrl_grab(channel->mpeg_video_hevc_tier, false); + v4l2_ctrl_grab(channel->mpeg_video_hevc_i_frame_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_hevc_max_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_hevc_min_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_hevc_p_frame_qp, false); + v4l2_ctrl_grab(channel->mpeg_video_hevc_b_frame_qp, false); + + v4l2_ctrl_grab(channel->mpeg_video_frame_rc_enable, false); + v4l2_ctrl_grab(channel->mpeg_video_bitrate_mode, false); + v4l2_ctrl_grab(channel->mpeg_video_bitrate, false); + v4l2_ctrl_grab(channel->mpeg_video_bitrate_peak, false); + v4l2_ctrl_grab(channel->mpeg_video_cpb_size, false); + v4l2_ctrl_grab(channel->mpeg_video_gop_size, false); + + v4l2_ctrl_grab(channel->encoder_buffer, false); + + if (channel->user_id != -1) { + clear_bit(channel->user_id, &dev->channel_user_ids); + channel->user_id = -1; + } +} + +/* + * Create the MCU channel + * + * After the channel has been created, the picture size, format, colorspace + * and framerate are fixed. Also the codec, profile, bitrate, etc. cannot be + * changed anymore. + * + * The channel can be created only once. The MCU will accept source buffers + * and stream buffers only after a channel has been created. + */ +static int allegro_create_channel(struct allegro_channel *channel) +{ + struct allegro_dev *dev = channel->dev; + unsigned long timeout; + + if (channel_exists(channel)) { + v4l2_warn(&dev->v4l2_dev, + "channel already exists\n"); + return 0; + } + + channel->user_id = allegro_next_user_id(dev); + if (channel->user_id < 0) { + v4l2_err(&dev->v4l2_dev, + "no free channels available\n"); + return -EBUSY; + } + set_bit(channel->user_id, &dev->channel_user_ids); + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "user %d: creating channel (%4.4s, %dx%d@%d)\n", + channel->user_id, + (char *)&channel->codec, channel->width, channel->height, + DIV_ROUND_UP(channel->framerate.numerator, + channel->framerate.denominator)); + + v4l2_ctrl_grab(channel->mpeg_video_h264_profile, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_level, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_i_frame_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_max_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_min_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_p_frame_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_h264_b_frame_qp, true); + + v4l2_ctrl_grab(channel->mpeg_video_hevc_profile, true); + v4l2_ctrl_grab(channel->mpeg_video_hevc_level, true); + v4l2_ctrl_grab(channel->mpeg_video_hevc_tier, true); + v4l2_ctrl_grab(channel->mpeg_video_hevc_i_frame_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_hevc_max_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_hevc_min_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_hevc_p_frame_qp, true); + v4l2_ctrl_grab(channel->mpeg_video_hevc_b_frame_qp, true); + + v4l2_ctrl_grab(channel->mpeg_video_frame_rc_enable, true); + v4l2_ctrl_grab(channel->mpeg_video_bitrate_mode, true); + v4l2_ctrl_grab(channel->mpeg_video_bitrate, true); + v4l2_ctrl_grab(channel->mpeg_video_bitrate_peak, true); + v4l2_ctrl_grab(channel->mpeg_video_cpb_size, true); + v4l2_ctrl_grab(channel->mpeg_video_gop_size, true); + + v4l2_ctrl_grab(channel->encoder_buffer, true); + + reinit_completion(&channel->completion); + allegro_mcu_send_create_channel(dev, channel); + timeout = wait_for_completion_timeout(&channel->completion, + msecs_to_jiffies(5000)); + if (timeout == 0) + channel->error = -ETIMEDOUT; + if (channel->error) + goto err; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "channel %d: accepting buffers\n", + channel->mcu_channel_id); + + return 0; + +err: + allegro_destroy_channel(channel); + + return channel->error; +} + +/** + * allegro_channel_adjust() - Adjust channel parameters to current format + * @channel: the channel to adjust + * + * Various parameters of a channel and their limits depend on the currently + * set format. Adjust the parameters after a format change in one go. + */ +static void allegro_channel_adjust(struct allegro_channel *channel) +{ + struct allegro_dev *dev = channel->dev; + u32 codec = channel->codec; + struct v4l2_ctrl *ctrl; + s64 min; + s64 max; + + channel->sizeimage_encoded = + estimate_stream_size(channel->width, channel->height); + + if (codec == V4L2_PIX_FMT_H264) { + ctrl = channel->mpeg_video_h264_level; + min = select_minimum_h264_level(channel->width, channel->height); + } else { + ctrl = channel->mpeg_video_hevc_level; + min = select_minimum_hevc_level(channel->width, channel->height); + } + if (ctrl->minimum > min) + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s.minimum: %lld -> %lld\n", + v4l2_ctrl_get_name(ctrl->id), ctrl->minimum, min); + v4l2_ctrl_lock(ctrl); + __v4l2_ctrl_modify_range(ctrl, min, ctrl->maximum, + ctrl->step, ctrl->default_value); + v4l2_ctrl_unlock(ctrl); + + ctrl = channel->mpeg_video_bitrate; + if (codec == V4L2_PIX_FMT_H264) + max = h264_maximum_bitrate(v4l2_ctrl_g_ctrl(channel->mpeg_video_h264_level)); + else + max = hevc_maximum_bitrate(v4l2_ctrl_g_ctrl(channel->mpeg_video_hevc_level)); + if (ctrl->maximum < max) + v4l2_dbg(1, debug, &dev->v4l2_dev, + "%s: maximum: %lld -> %lld\n", + v4l2_ctrl_get_name(ctrl->id), ctrl->maximum, max); + v4l2_ctrl_lock(ctrl); + __v4l2_ctrl_modify_range(ctrl, ctrl->minimum, max, + ctrl->step, ctrl->default_value); + v4l2_ctrl_unlock(ctrl); + + ctrl = channel->mpeg_video_bitrate_peak; + v4l2_ctrl_lock(ctrl); + __v4l2_ctrl_modify_range(ctrl, ctrl->minimum, max, + ctrl->step, ctrl->default_value); + v4l2_ctrl_unlock(ctrl); + + v4l2_ctrl_activate(channel->mpeg_video_h264_profile, + codec == V4L2_PIX_FMT_H264); + v4l2_ctrl_activate(channel->mpeg_video_h264_level, + codec == V4L2_PIX_FMT_H264); + v4l2_ctrl_activate(channel->mpeg_video_h264_i_frame_qp, + codec == V4L2_PIX_FMT_H264); + v4l2_ctrl_activate(channel->mpeg_video_h264_max_qp, + codec == V4L2_PIX_FMT_H264); + v4l2_ctrl_activate(channel->mpeg_video_h264_min_qp, + codec == V4L2_PIX_FMT_H264); + v4l2_ctrl_activate(channel->mpeg_video_h264_p_frame_qp, + codec == V4L2_PIX_FMT_H264); + v4l2_ctrl_activate(channel->mpeg_video_h264_b_frame_qp, + codec == V4L2_PIX_FMT_H264); + + v4l2_ctrl_activate(channel->mpeg_video_hevc_profile, + codec == V4L2_PIX_FMT_HEVC); + v4l2_ctrl_activate(channel->mpeg_video_hevc_level, + codec == V4L2_PIX_FMT_HEVC); + v4l2_ctrl_activate(channel->mpeg_video_hevc_tier, + codec == V4L2_PIX_FMT_HEVC); + v4l2_ctrl_activate(channel->mpeg_video_hevc_i_frame_qp, + codec == V4L2_PIX_FMT_HEVC); + v4l2_ctrl_activate(channel->mpeg_video_hevc_max_qp, + codec == V4L2_PIX_FMT_HEVC); + v4l2_ctrl_activate(channel->mpeg_video_hevc_min_qp, + codec == V4L2_PIX_FMT_HEVC); + v4l2_ctrl_activate(channel->mpeg_video_hevc_p_frame_qp, + codec == V4L2_PIX_FMT_HEVC); + v4l2_ctrl_activate(channel->mpeg_video_hevc_b_frame_qp, + codec == V4L2_PIX_FMT_HEVC); + + if (codec == V4L2_PIX_FMT_H264) + channel->log2_max_frame_num = LOG2_MAX_FRAME_NUM; + channel->temporal_mvp_enable = true; + channel->dbf_ovr_en = (codec == V4L2_PIX_FMT_H264); + channel->enable_deblocking_filter_override = (codec == V4L2_PIX_FMT_HEVC); + channel->enable_reordering = (codec == V4L2_PIX_FMT_HEVC); + channel->enable_loop_filter_across_tiles = true; + channel->enable_loop_filter_across_slices = true; + + if (codec == V4L2_PIX_FMT_H264) { + channel->b_hrz_me_range = 8; + channel->b_vrt_me_range = 8; + channel->p_hrz_me_range = 16; + channel->p_vrt_me_range = 16; + channel->max_cu_size = ilog2(16); + channel->min_cu_size = ilog2(8); + channel->max_tu_size = ilog2(4); + channel->min_tu_size = ilog2(4); + } else { + channel->b_hrz_me_range = 16; + channel->b_vrt_me_range = 16; + channel->p_hrz_me_range = 32; + channel->p_vrt_me_range = 32; + channel->max_cu_size = ilog2(32); + channel->min_cu_size = ilog2(8); + channel->max_tu_size = ilog2(32); + channel->min_tu_size = ilog2(4); + } + channel->max_transfo_depth_intra = 1; + channel->max_transfo_depth_inter = 1; +} + +static void allegro_set_default_params(struct allegro_channel *channel) +{ + channel->width = ALLEGRO_WIDTH_DEFAULT; + channel->height = ALLEGRO_HEIGHT_DEFAULT; + channel->stride = round_up(channel->width, 32); + channel->framerate = ALLEGRO_FRAMERATE_DEFAULT; + + channel->colorspace = V4L2_COLORSPACE_REC709; + channel->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + channel->quantization = V4L2_QUANTIZATION_DEFAULT; + channel->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + channel->pixelformat = V4L2_PIX_FMT_NV12; + channel->sizeimage_raw = channel->stride * channel->height * 3 / 2; + + channel->codec = V4L2_PIX_FMT_H264; +} + +static int allegro_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct allegro_channel *channel = vb2_get_drv_priv(vq); + struct allegro_dev *dev = channel->dev; + + v4l2_dbg(2, debug, &dev->v4l2_dev, + "%s: queue setup[%s]: nplanes = %d\n", + V4L2_TYPE_IS_OUTPUT(vq->type) ? "output" : "capture", + *nplanes == 0 ? "REQBUFS" : "CREATE_BUFS", *nplanes); + + if (*nplanes != 0) { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + if (sizes[0] < channel->sizeimage_raw) + return -EINVAL; + } else { + if (sizes[0] < channel->sizeimage_encoded) + return -EINVAL; + } + } else { + *nplanes = 1; + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + sizes[0] = channel->sizeimage_raw; + else + sizes[0] = channel->sizeimage_encoded; + } + + return 0; +} + +static int allegro_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct allegro_channel *channel = vb2_get_drv_priv(vb->vb2_queue); + struct allegro_dev *dev = channel->dev; + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) { + v4l2_err(&dev->v4l2_dev, + "channel %d: unsupported field\n", + channel->mcu_channel_id); + return -EINVAL; + } + } + + return 0; +} + +static void allegro_buf_queue(struct vb2_buffer *vb) +{ + struct allegro_channel *channel = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *q = vb->vb2_queue; + + if (V4L2_TYPE_IS_CAPTURE(q->type) && + vb2_is_streaming(q) && + v4l2_m2m_dst_buf_is_last(channel->fh.m2m_ctx)) { + unsigned int i; + + for (i = 0; i < vb->num_planes; i++) + vb2_set_plane_payload(vb, i, 0); + + vbuf->field = V4L2_FIELD_NONE; + vbuf->sequence = channel->csequence++; + + v4l2_m2m_last_buffer_done(channel->fh.m2m_ctx, vbuf); + allegro_channel_eos_event(channel); + return; + } + + v4l2_m2m_buf_queue(channel->fh.m2m_ctx, vbuf); +} + +static int allegro_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct allegro_channel *channel = vb2_get_drv_priv(q); + struct allegro_dev *dev = channel->dev; + + v4l2_dbg(2, debug, &dev->v4l2_dev, + "%s: start streaming\n", + V4L2_TYPE_IS_OUTPUT(q->type) ? "output" : "capture"); + + v4l2_m2m_update_start_streaming_state(channel->fh.m2m_ctx, q); + + if (V4L2_TYPE_IS_OUTPUT(q->type)) + channel->osequence = 0; + else + channel->csequence = 0; + + return 0; +} + +static void allegro_stop_streaming(struct vb2_queue *q) +{ + struct allegro_channel *channel = vb2_get_drv_priv(q); + struct allegro_dev *dev = channel->dev; + struct vb2_v4l2_buffer *buffer; + struct allegro_m2m_buffer *shadow, *tmp; + + v4l2_dbg(2, debug, &dev->v4l2_dev, + "%s: stop streaming\n", + V4L2_TYPE_IS_OUTPUT(q->type) ? "output" : "capture"); + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + mutex_lock(&channel->shadow_list_lock); + list_for_each_entry_safe(shadow, tmp, + &channel->source_shadow_list, head) { + list_del(&shadow->head); + v4l2_m2m_buf_done(&shadow->buf.vb, VB2_BUF_STATE_ERROR); + } + mutex_unlock(&channel->shadow_list_lock); + + while ((buffer = v4l2_m2m_src_buf_remove(channel->fh.m2m_ctx))) + v4l2_m2m_buf_done(buffer, VB2_BUF_STATE_ERROR); + } else { + mutex_lock(&channel->shadow_list_lock); + list_for_each_entry_safe(shadow, tmp, + &channel->stream_shadow_list, head) { + list_del(&shadow->head); + v4l2_m2m_buf_done(&shadow->buf.vb, VB2_BUF_STATE_ERROR); + } + mutex_unlock(&channel->shadow_list_lock); + + allegro_destroy_channel(channel); + while ((buffer = v4l2_m2m_dst_buf_remove(channel->fh.m2m_ctx))) + v4l2_m2m_buf_done(buffer, VB2_BUF_STATE_ERROR); + } + + v4l2_m2m_update_stop_streaming_state(channel->fh.m2m_ctx, q); + + if (V4L2_TYPE_IS_OUTPUT(q->type) && + v4l2_m2m_has_stopped(channel->fh.m2m_ctx)) + allegro_channel_eos_event(channel); +} + +static const struct vb2_ops allegro_queue_ops = { + .queue_setup = allegro_queue_setup, + .buf_prepare = allegro_buf_prepare, + .buf_queue = allegro_buf_queue, + .start_streaming = allegro_start_streaming, + .stop_streaming = allegro_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int allegro_queue_init(void *priv, + struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + int err; + struct allegro_channel *channel = priv; + + src_vq->dev = &channel->dev->plat_dev->dev; + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_DMABUF | VB2_MMAP; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->drv_priv = channel; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->ops = &allegro_queue_ops; + src_vq->buf_struct_size = sizeof(struct allegro_m2m_buffer); + src_vq->lock = &channel->dev->lock; + err = vb2_queue_init(src_vq); + if (err) + return err; + + dst_vq->dev = &channel->dev->plat_dev->dev; + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_DMABUF | VB2_MMAP; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->drv_priv = channel; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->ops = &allegro_queue_ops; + dst_vq->buf_struct_size = sizeof(struct allegro_m2m_buffer); + dst_vq->lock = &channel->dev->lock; + err = vb2_queue_init(dst_vq); + if (err) + return err; + + return 0; +} + +static int allegro_clamp_qp(struct allegro_channel *channel, + struct v4l2_ctrl *ctrl) +{ + struct v4l2_ctrl *next_ctrl; + + if (ctrl->id == V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP) + next_ctrl = channel->mpeg_video_h264_p_frame_qp; + else if (ctrl->id == V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP) + next_ctrl = channel->mpeg_video_h264_b_frame_qp; + else + return 0; + + /* Modify range automatically updates the value */ + __v4l2_ctrl_modify_range(next_ctrl, ctrl->val, 51, 1, ctrl->val); + + return allegro_clamp_qp(channel, next_ctrl); +} + +static int allegro_clamp_bitrate(struct allegro_channel *channel, + struct v4l2_ctrl *ctrl) +{ + struct v4l2_ctrl *ctrl_bitrate = channel->mpeg_video_bitrate; + struct v4l2_ctrl *ctrl_bitrate_peak = channel->mpeg_video_bitrate_peak; + + if (ctrl->val == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR && + ctrl_bitrate_peak->val < ctrl_bitrate->val) + ctrl_bitrate_peak->val = ctrl_bitrate->val; + + return 0; +} + +static int allegro_try_ctrl(struct v4l2_ctrl *ctrl) +{ + struct allegro_channel *channel = container_of(ctrl->handler, + struct allegro_channel, + ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + allegro_clamp_bitrate(channel, ctrl); + break; + case V4L2_CID_USER_ALLEGRO_ENCODER_BUFFER: + if (!channel->dev->has_encoder_buffer) + ctrl->val = 0; + break; + } + + return 0; +} + +static int allegro_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct allegro_channel *channel = container_of(ctrl->handler, + struct allegro_channel, + ctrl_handler); + struct allegro_dev *dev = channel->dev; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "s_ctrl: %s = %d\n", v4l2_ctrl_get_name(ctrl->id), ctrl->val); + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + channel->frame_rc_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + channel->bitrate = channel->mpeg_video_bitrate->val; + channel->bitrate_peak = channel->mpeg_video_bitrate_peak->val; + v4l2_ctrl_activate(channel->mpeg_video_bitrate_peak, + ctrl->val == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + allegro_clamp_qp(channel, ctrl); + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops allegro_ctrl_ops = { + .try_ctrl = allegro_try_ctrl, + .s_ctrl = allegro_s_ctrl, +}; + +static const struct v4l2_ctrl_config allegro_encoder_buffer_ctrl_config = { + .id = V4L2_CID_USER_ALLEGRO_ENCODER_BUFFER, + .name = "Encoder Buffer Enable", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +static int allegro_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct allegro_dev *dev = video_get_drvdata(vdev); + struct allegro_channel *channel = NULL; + struct v4l2_ctrl_handler *handler; + u64 mask; + int ret; + unsigned int bitrate_max; + unsigned int bitrate_def; + unsigned int cpb_size_max; + unsigned int cpb_size_def; + + channel = kzalloc(sizeof(*channel), GFP_KERNEL); + if (!channel) + return -ENOMEM; + + v4l2_fh_init(&channel->fh, vdev); + + init_completion(&channel->completion); + INIT_LIST_HEAD(&channel->source_shadow_list); + INIT_LIST_HEAD(&channel->stream_shadow_list); + mutex_init(&channel->shadow_list_lock); + + channel->dev = dev; + + allegro_set_default_params(channel); + + handler = &channel->ctrl_handler; + v4l2_ctrl_handler_init(handler, 0); + channel->mpeg_video_h264_profile = v4l2_ctrl_new_std_menu(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, 0x0, + V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE); + mask = 1 << V4L2_MPEG_VIDEO_H264_LEVEL_1B; + channel->mpeg_video_h264_level = v4l2_ctrl_new_std_menu(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, mask, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1); + channel->mpeg_video_h264_i_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, + 0, 51, 1, 30); + channel->mpeg_video_h264_max_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MAX_QP, + 0, 51, 1, 51); + channel->mpeg_video_h264_min_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, + 0, 51, 1, 0); + channel->mpeg_video_h264_p_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, + 0, 51, 1, 30); + channel->mpeg_video_h264_b_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP, + 0, 51, 1, 30); + + channel->mpeg_video_hevc_profile = + v4l2_ctrl_new_std_menu(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_PROFILE, + V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, 0x0, + V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN); + channel->mpeg_video_hevc_level = + v4l2_ctrl_new_std_menu(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_LEVEL, + V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1, 0x0, + V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1); + channel->mpeg_video_hevc_tier = + v4l2_ctrl_new_std_menu(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_TIER, + V4L2_MPEG_VIDEO_HEVC_TIER_HIGH, 0x0, + V4L2_MPEG_VIDEO_HEVC_TIER_MAIN); + channel->mpeg_video_hevc_i_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP, + 0, 51, 1, 30); + channel->mpeg_video_hevc_max_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP, + 0, 51, 1, 51); + channel->mpeg_video_hevc_min_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP, + 0, 51, 1, 0); + channel->mpeg_video_hevc_p_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP, + 0, 51, 1, 30); + channel->mpeg_video_hevc_b_frame_qp = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP, + 0, 51, 1, 30); + + channel->mpeg_video_frame_rc_enable = + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, + false, 0x1, + true, false); + channel->mpeg_video_bitrate_mode = v4l2_ctrl_new_std_menu(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, 0, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR); + + if (channel->codec == V4L2_PIX_FMT_H264) { + bitrate_max = h264_maximum_bitrate(V4L2_MPEG_VIDEO_H264_LEVEL_5_1); + bitrate_def = h264_maximum_bitrate(V4L2_MPEG_VIDEO_H264_LEVEL_5_1); + cpb_size_max = h264_maximum_cpb_size(V4L2_MPEG_VIDEO_H264_LEVEL_5_1); + cpb_size_def = h264_maximum_cpb_size(V4L2_MPEG_VIDEO_H264_LEVEL_5_1); + } else { + bitrate_max = hevc_maximum_bitrate(V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1); + bitrate_def = hevc_maximum_bitrate(V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1); + cpb_size_max = hevc_maximum_cpb_size(V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1); + cpb_size_def = hevc_maximum_cpb_size(V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1); + } + channel->mpeg_video_bitrate = v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, + 0, bitrate_max, 1, bitrate_def); + channel->mpeg_video_bitrate_peak = v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + 0, bitrate_max, 1, bitrate_def); + channel->mpeg_video_cpb_size = v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE, + 0, cpb_size_max, 1, cpb_size_def); + channel->mpeg_video_gop_size = v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 0, ALLEGRO_GOP_SIZE_MAX, + 1, ALLEGRO_GOP_SIZE_DEFAULT); + channel->encoder_buffer = v4l2_ctrl_new_custom(handler, + &allegro_encoder_buffer_ctrl_config, NULL); + v4l2_ctrl_new_std(handler, + &allegro_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, + 1, 32, + 1, 1); + if (handler->error != 0) { + ret = handler->error; + goto error; + } + + channel->fh.ctrl_handler = handler; + + v4l2_ctrl_cluster(3, &channel->mpeg_video_bitrate_mode); + + v4l2_ctrl_handler_setup(handler); + + channel->mcu_channel_id = -1; + channel->user_id = -1; + + INIT_LIST_HEAD(&channel->buffers_reference); + INIT_LIST_HEAD(&channel->buffers_intermediate); + + channel->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, channel, + allegro_queue_init); + + if (IS_ERR(channel->fh.m2m_ctx)) { + ret = PTR_ERR(channel->fh.m2m_ctx); + goto error; + } + + list_add(&channel->list, &dev->channels); + file->private_data = &channel->fh; + v4l2_fh_add(&channel->fh); + + allegro_channel_adjust(channel); + + return 0; + +error: + v4l2_ctrl_handler_free(handler); + kfree(channel); + return ret; +} + +static int allegro_release(struct file *file) +{ + struct allegro_channel *channel = fh_to_channel(file->private_data); + + v4l2_m2m_ctx_release(channel->fh.m2m_ctx); + + list_del(&channel->list); + + v4l2_ctrl_handler_free(&channel->ctrl_handler); + + v4l2_fh_del(&channel->fh); + v4l2_fh_exit(&channel->fh); + + kfree(channel); + + return 0; +} + +static int allegro_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "Allegro DVT Video Encoder", sizeof(cap->card)); + + return 0; +} + +static int allegro_enum_fmt_vid(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + switch (f->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (f->index >= 1) + return -EINVAL; + f->pixelformat = V4L2_PIX_FMT_NV12; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (f->index >= 2) + return -EINVAL; + if (f->index == 0) + f->pixelformat = V4L2_PIX_FMT_H264; + if (f->index == 1) + f->pixelformat = V4L2_PIX_FMT_HEVC; + break; + default: + return -EINVAL; + } + return 0; +} + +static int allegro_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct allegro_channel *channel = fh_to_channel(fh); + + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.width = channel->width; + f->fmt.pix.height = channel->height; + + f->fmt.pix.colorspace = channel->colorspace; + f->fmt.pix.ycbcr_enc = channel->ycbcr_enc; + f->fmt.pix.quantization = channel->quantization; + f->fmt.pix.xfer_func = channel->xfer_func; + + f->fmt.pix.pixelformat = channel->codec; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = channel->sizeimage_encoded; + + return 0; +} + +static int allegro_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + f->fmt.pix.field = V4L2_FIELD_NONE; + + f->fmt.pix.width = clamp_t(__u32, f->fmt.pix.width, + ALLEGRO_WIDTH_MIN, ALLEGRO_WIDTH_MAX); + f->fmt.pix.height = clamp_t(__u32, f->fmt.pix.height, + ALLEGRO_HEIGHT_MIN, ALLEGRO_HEIGHT_MAX); + + if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_HEVC && + f->fmt.pix.pixelformat != V4L2_PIX_FMT_H264) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_H264; + + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = + estimate_stream_size(f->fmt.pix.width, f->fmt.pix.height); + + return 0; +} + +static int allegro_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct allegro_channel *channel = fh_to_channel(fh); + struct vb2_queue *vq; + int err; + + err = allegro_try_fmt_vid_cap(file, fh, f); + if (err) + return err; + + vq = v4l2_m2m_get_vq(channel->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + if (vb2_is_busy(vq)) + return -EBUSY; + + channel->codec = f->fmt.pix.pixelformat; + + allegro_channel_adjust(channel); + + return 0; +} + +static int allegro_g_fmt_vid_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct allegro_channel *channel = fh_to_channel(fh); + + f->fmt.pix.field = V4L2_FIELD_NONE; + + f->fmt.pix.width = channel->width; + f->fmt.pix.height = channel->height; + + f->fmt.pix.colorspace = channel->colorspace; + f->fmt.pix.ycbcr_enc = channel->ycbcr_enc; + f->fmt.pix.quantization = channel->quantization; + f->fmt.pix.xfer_func = channel->xfer_func; + + f->fmt.pix.pixelformat = channel->pixelformat; + f->fmt.pix.bytesperline = channel->stride; + f->fmt.pix.sizeimage = channel->sizeimage_raw; + + return 0; +} + +static int allegro_try_fmt_vid_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + f->fmt.pix.field = V4L2_FIELD_NONE; + + /* + * The firmware of the Allegro codec handles the padding internally + * and expects the visual frame size when configuring a channel. + * Therefore, unlike other encoder drivers, this driver does not round + * up the width and height to macroblock alignment and does not + * implement the selection api. + */ + f->fmt.pix.width = clamp_t(__u32, f->fmt.pix.width, + ALLEGRO_WIDTH_MIN, ALLEGRO_WIDTH_MAX); + f->fmt.pix.height = clamp_t(__u32, f->fmt.pix.height, + ALLEGRO_HEIGHT_MIN, ALLEGRO_HEIGHT_MAX); + + f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 32); + f->fmt.pix.sizeimage = + f->fmt.pix.bytesperline * f->fmt.pix.height * 3 / 2; + + return 0; +} + +static int allegro_s_fmt_vid_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct allegro_channel *channel = fh_to_channel(fh); + int err; + + err = allegro_try_fmt_vid_out(file, fh, f); + if (err) + return err; + + channel->width = f->fmt.pix.width; + channel->height = f->fmt.pix.height; + channel->stride = f->fmt.pix.bytesperline; + channel->sizeimage_raw = f->fmt.pix.sizeimage; + + channel->colorspace = f->fmt.pix.colorspace; + channel->ycbcr_enc = f->fmt.pix.ycbcr_enc; + channel->quantization = f->fmt.pix.quantization; + channel->xfer_func = f->fmt.pix.xfer_func; + + allegro_channel_adjust(channel); + + return 0; +} + +static int allegro_channel_cmd_stop(struct allegro_channel *channel) +{ + if (v4l2_m2m_has_stopped(channel->fh.m2m_ctx)) + allegro_channel_eos_event(channel); + + return 0; +} + +static int allegro_channel_cmd_start(struct allegro_channel *channel) +{ + if (v4l2_m2m_has_stopped(channel->fh.m2m_ctx)) + vb2_clear_last_buffer_dequeued(&channel->fh.m2m_ctx->cap_q_ctx.q); + + return 0; +} + +static int allegro_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *cmd) +{ + struct allegro_channel *channel = fh_to_channel(fh); + int err; + + err = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, cmd); + if (err) + return err; + + err = v4l2_m2m_ioctl_encoder_cmd(file, fh, cmd); + if (err) + return err; + + if (cmd->cmd == V4L2_ENC_CMD_STOP) + err = allegro_channel_cmd_stop(channel); + + if (cmd->cmd == V4L2_ENC_CMD_START) + err = allegro_channel_cmd_start(channel); + + return err; +} + +static int allegro_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + switch (fsize->pixel_format) { + case V4L2_PIX_FMT_HEVC: + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_NV12: + break; + default: + return -EINVAL; + } + + if (fsize->index) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = ALLEGRO_WIDTH_MIN; + fsize->stepwise.max_width = ALLEGRO_WIDTH_MAX; + fsize->stepwise.step_width = 1; + fsize->stepwise.min_height = ALLEGRO_HEIGHT_MIN; + fsize->stepwise.max_height = ALLEGRO_HEIGHT_MAX; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int allegro_ioctl_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct v4l2_fh *fh = file->private_data; + struct allegro_channel *channel = fh_to_channel(fh); + int err; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + err = allegro_create_channel(channel); + if (err) + return err; + } + + return v4l2_m2m_streamon(file, fh->m2m_ctx, type); +} + +static int allegro_g_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct allegro_channel *channel = fh_to_channel(fh); + struct v4l2_fract *timeperframe; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + timeperframe = &a->parm.output.timeperframe; + timeperframe->numerator = channel->framerate.denominator; + timeperframe->denominator = channel->framerate.numerator; + + return 0; +} + +static int allegro_s_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct allegro_channel *channel = fh_to_channel(fh); + struct v4l2_fract *timeperframe; + int div; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + timeperframe = &a->parm.output.timeperframe; + + if (timeperframe->numerator == 0 || timeperframe->denominator == 0) + return allegro_g_parm(file, fh, a); + + div = gcd(timeperframe->denominator, timeperframe->numerator); + channel->framerate.numerator = timeperframe->denominator / div; + channel->framerate.denominator = timeperframe->numerator / div; + + return 0; +} + +static int allegro_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static const struct v4l2_ioctl_ops allegro_ioctl_ops = { + .vidioc_querycap = allegro_querycap, + .vidioc_enum_fmt_vid_cap = allegro_enum_fmt_vid, + .vidioc_enum_fmt_vid_out = allegro_enum_fmt_vid, + .vidioc_g_fmt_vid_cap = allegro_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = allegro_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = allegro_s_fmt_vid_cap, + .vidioc_g_fmt_vid_out = allegro_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = allegro_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = allegro_s_fmt_vid_out, + + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + + .vidioc_streamon = allegro_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, + .vidioc_encoder_cmd = allegro_encoder_cmd, + .vidioc_enum_framesizes = allegro_enum_framesizes, + + .vidioc_g_parm = allegro_g_parm, + .vidioc_s_parm = allegro_s_parm, + + .vidioc_subscribe_event = allegro_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations allegro_fops = { + .owner = THIS_MODULE, + .open = allegro_open, + .release = allegro_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int allegro_register_device(struct allegro_dev *dev) +{ + struct video_device *video_dev = &dev->video_dev; + + strscpy(video_dev->name, "allegro", sizeof(video_dev->name)); + video_dev->fops = &allegro_fops; + video_dev->ioctl_ops = &allegro_ioctl_ops; + video_dev->release = video_device_release_empty; + video_dev->lock = &dev->lock; + video_dev->v4l2_dev = &dev->v4l2_dev; + video_dev->vfl_dir = VFL_DIR_M2M; + video_dev->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; + video_set_drvdata(video_dev, dev); + + return video_register_device(video_dev, VFL_TYPE_VIDEO, 0); +} + +static void allegro_device_run(void *priv) +{ + struct allegro_channel *channel = priv; + struct allegro_dev *dev = channel->dev; + struct vb2_v4l2_buffer *src_buf; + struct vb2_v4l2_buffer *dst_buf; + dma_addr_t src_y; + dma_addr_t src_uv; + dma_addr_t dst_addr; + unsigned long dst_size; + u64 src_handle; + u64 dst_handle; + + dst_buf = v4l2_m2m_dst_buf_remove(channel->fh.m2m_ctx); + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + dst_size = vb2_plane_size(&dst_buf->vb2_buf, 0); + dst_handle = allegro_put_buffer(channel, &channel->stream_shadow_list, + dst_buf); + allegro_mcu_send_put_stream_buffer(dev, channel, dst_addr, dst_size, + dst_handle); + + src_buf = v4l2_m2m_src_buf_remove(channel->fh.m2m_ctx); + src_buf->sequence = channel->osequence++; + src_y = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + src_uv = src_y + (channel->stride * channel->height); + src_handle = allegro_put_buffer(channel, &channel->source_shadow_list, + src_buf); + allegro_mcu_send_encode_frame(dev, channel, src_y, src_uv, src_handle); + + v4l2_m2m_job_finish(dev->m2m_dev, channel->fh.m2m_ctx); +} + +static const struct v4l2_m2m_ops allegro_m2m_ops = { + .device_run = allegro_device_run, +}; + +static int allegro_mcu_hw_init(struct allegro_dev *dev, + const struct fw_info *info) +{ + int err; + + dev->mbox_command = allegro_mbox_init(dev, info->mailbox_cmd, + info->mailbox_size); + dev->mbox_status = allegro_mbox_init(dev, info->mailbox_status, + info->mailbox_size); + if (IS_ERR(dev->mbox_command) || IS_ERR(dev->mbox_status)) { + v4l2_err(&dev->v4l2_dev, + "failed to initialize mailboxes\n"); + return -EIO; + } + + err = allegro_encoder_buffer_init(dev, &dev->encoder_buffer); + dev->has_encoder_buffer = (err == 0); + if (!dev->has_encoder_buffer) + v4l2_info(&dev->v4l2_dev, "encoder buffer not available\n"); + + allegro_mcu_enable_interrupts(dev); + + /* The mcu sends INIT after reset. */ + allegro_mcu_start(dev); + err = allegro_mcu_wait_for_init_timeout(dev, 5000); + if (err < 0) { + v4l2_err(&dev->v4l2_dev, + "mcu did not send INIT after reset\n"); + err = -EIO; + goto err_disable_interrupts; + } + + err = allegro_alloc_buffer(dev, &dev->suballocator, + info->suballocator_size); + if (err) { + v4l2_err(&dev->v4l2_dev, + "failed to allocate %zu bytes for suballocator\n", + info->suballocator_size); + goto err_reset_mcu; + } + + allegro_mcu_send_init(dev, dev->suballocator.paddr, + dev->suballocator.size); + err = allegro_mcu_wait_for_init_timeout(dev, 5000); + if (err < 0) { + v4l2_err(&dev->v4l2_dev, + "mcu failed to configure sub-allocator\n"); + err = -EIO; + goto err_free_suballocator; + } + + return 0; + +err_free_suballocator: + allegro_free_buffer(dev, &dev->suballocator); +err_reset_mcu: + allegro_mcu_reset(dev); +err_disable_interrupts: + allegro_mcu_disable_interrupts(dev); + + return err; +} + +static int allegro_mcu_hw_deinit(struct allegro_dev *dev) +{ + int err; + + err = allegro_mcu_reset(dev); + if (err) + v4l2_warn(&dev->v4l2_dev, + "mcu failed to enter sleep state\n"); + + err = allegro_mcu_disable_interrupts(dev); + if (err) + v4l2_warn(&dev->v4l2_dev, + "failed to disable interrupts\n"); + + allegro_free_buffer(dev, &dev->suballocator); + + return 0; +} + +static void allegro_fw_callback(const struct firmware *fw, void *context) +{ + struct allegro_dev *dev = context; + const char *fw_codec_name = "al5e.fw"; + const struct firmware *fw_codec; + int err; + + if (!fw) + return; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "requesting codec firmware '%s'\n", fw_codec_name); + err = request_firmware(&fw_codec, fw_codec_name, &dev->plat_dev->dev); + if (err) + goto err_release_firmware; + + dev->fw_info = allegro_get_firmware_info(dev, fw, fw_codec); + if (!dev->fw_info) { + v4l2_err(&dev->v4l2_dev, "firmware is not supported\n"); + goto err_release_firmware_codec; + } + + v4l2_info(&dev->v4l2_dev, + "using mcu firmware version '%s'\n", dev->fw_info->version); + + pm_runtime_enable(&dev->plat_dev->dev); + err = pm_runtime_resume_and_get(&dev->plat_dev->dev); + if (err) + goto err_release_firmware_codec; + + /* Ensure that the mcu is sleeping at the reset vector */ + err = allegro_mcu_reset(dev); + if (err) { + v4l2_err(&dev->v4l2_dev, "failed to reset mcu\n"); + goto err_suspend; + } + + allegro_copy_firmware(dev, fw->data, fw->size); + allegro_copy_fw_codec(dev, fw_codec->data, fw_codec->size); + + err = allegro_mcu_hw_init(dev, dev->fw_info); + if (err) { + v4l2_err(&dev->v4l2_dev, "failed to initialize mcu\n"); + goto err_free_fw_codec; + } + + dev->m2m_dev = v4l2_m2m_init(&allegro_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "failed to init mem2mem device\n"); + goto err_mcu_hw_deinit; + } + + err = allegro_register_device(dev); + if (err) { + v4l2_err(&dev->v4l2_dev, "failed to register video device\n"); + goto err_m2m_release; + } + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "allegro codec registered as /dev/video%d\n", + dev->video_dev.num); + + dev->initialized = true; + + release_firmware(fw_codec); + release_firmware(fw); + + return; + +err_m2m_release: + v4l2_m2m_release(dev->m2m_dev); + dev->m2m_dev = NULL; +err_mcu_hw_deinit: + allegro_mcu_hw_deinit(dev); +err_free_fw_codec: + allegro_free_fw_codec(dev); +err_suspend: + pm_runtime_put(&dev->plat_dev->dev); + pm_runtime_disable(&dev->plat_dev->dev); +err_release_firmware_codec: + release_firmware(fw_codec); +err_release_firmware: + release_firmware(fw); +} + +static int allegro_firmware_request_nowait(struct allegro_dev *dev) +{ + const char *fw = "al5e_b.fw"; + + v4l2_dbg(1, debug, &dev->v4l2_dev, + "requesting firmware '%s'\n", fw); + return request_firmware_nowait(THIS_MODULE, true, fw, + &dev->plat_dev->dev, GFP_KERNEL, dev, + allegro_fw_callback); +} + +static int allegro_probe(struct platform_device *pdev) +{ + struct allegro_dev *dev; + struct resource *res, *sram_res; + int ret; + int irq; + void __iomem *regs, *sram_regs; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + dev->plat_dev = pdev; + init_completion(&dev->init_complete); + INIT_LIST_HEAD(&dev->channels); + + mutex_init(&dev->lock); + + dev->initialized = false; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "regs"); + if (!res) { + dev_err(&pdev->dev, + "regs resource missing from device tree\n"); + return -EINVAL; + } + regs = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!regs) { + dev_err(&pdev->dev, "failed to map registers\n"); + return -ENOMEM; + } + dev->regmap = devm_regmap_init_mmio(&pdev->dev, regs, + &allegro_regmap_config); + if (IS_ERR(dev->regmap)) { + dev_err(&pdev->dev, "failed to init regmap\n"); + return PTR_ERR(dev->regmap); + } + + sram_res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram"); + if (!sram_res) { + dev_err(&pdev->dev, + "sram resource missing from device tree\n"); + return -EINVAL; + } + sram_regs = devm_ioremap(&pdev->dev, + sram_res->start, + resource_size(sram_res)); + if (!sram_regs) { + dev_err(&pdev->dev, "failed to map sram\n"); + return -ENOMEM; + } + dev->sram = devm_regmap_init_mmio(&pdev->dev, sram_regs, + &allegro_sram_config); + if (IS_ERR(dev->sram)) { + dev_err(&pdev->dev, "failed to init sram\n"); + return PTR_ERR(dev->sram); + } + + dev->settings = syscon_regmap_lookup_by_compatible("xlnx,vcu-settings"); + if (IS_ERR(dev->settings)) + dev_warn(&pdev->dev, "failed to open settings\n"); + + dev->clk_core = devm_clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(dev->clk_core)) + return PTR_ERR(dev->clk_core); + + dev->clk_mcu = devm_clk_get(&pdev->dev, "mcu_clk"); + if (IS_ERR(dev->clk_mcu)) + return PTR_ERR(dev->clk_mcu); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + ret = devm_request_threaded_irq(&pdev->dev, irq, + allegro_hardirq, + allegro_irq_thread, + IRQF_SHARED, dev_name(&pdev->dev), dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq: %d\n", ret); + return ret; + } + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + return ret; + + platform_set_drvdata(pdev, dev); + + ret = allegro_firmware_request_nowait(dev); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, + "failed to request firmware: %d\n", ret); + return ret; + } + + return 0; +} + +static int allegro_remove(struct platform_device *pdev) +{ + struct allegro_dev *dev = platform_get_drvdata(pdev); + + if (dev->initialized) { + video_unregister_device(&dev->video_dev); + if (dev->m2m_dev) + v4l2_m2m_release(dev->m2m_dev); + allegro_mcu_hw_deinit(dev); + allegro_free_fw_codec(dev); + } + + pm_runtime_put(&dev->plat_dev->dev); + pm_runtime_disable(&dev->plat_dev->dev); + + v4l2_device_unregister(&dev->v4l2_dev); + + return 0; +} + +static int allegro_runtime_resume(struct device *device) +{ + struct allegro_dev *dev = dev_get_drvdata(device); + struct regmap *settings = dev->settings; + unsigned int clk_mcu; + unsigned int clk_core; + int err; + + if (!settings) + return -EINVAL; + +#define MHZ_TO_HZ(freq) ((freq) * 1000 * 1000) + + err = regmap_read(settings, VCU_CORE_CLK, &clk_core); + if (err < 0) + return err; + err = clk_set_rate(dev->clk_core, MHZ_TO_HZ(clk_core)); + if (err < 0) + return err; + err = clk_prepare_enable(dev->clk_core); + if (err) + return err; + + err = regmap_read(settings, VCU_MCU_CLK, &clk_mcu); + if (err < 0) + goto disable_clk_core; + err = clk_set_rate(dev->clk_mcu, MHZ_TO_HZ(clk_mcu)); + if (err < 0) + goto disable_clk_core; + err = clk_prepare_enable(dev->clk_mcu); + if (err) + goto disable_clk_core; + +#undef MHZ_TO_HZ + + return 0; + +disable_clk_core: + clk_disable_unprepare(dev->clk_core); + + return err; +} + +static int allegro_runtime_suspend(struct device *device) +{ + struct allegro_dev *dev = dev_get_drvdata(device); + + clk_disable_unprepare(dev->clk_mcu); + clk_disable_unprepare(dev->clk_core); + + return 0; +} + +static const struct of_device_id allegro_dt_ids[] = { + { .compatible = "allegro,al5e-1.1" }, + { /* sentinel */ } +}; + +MODULE_DEVICE_TABLE(of, allegro_dt_ids); + +static const struct dev_pm_ops allegro_pm_ops = { + .runtime_resume = allegro_runtime_resume, + .runtime_suspend = allegro_runtime_suspend, +}; + +static struct platform_driver allegro_driver = { + .probe = allegro_probe, + .remove = allegro_remove, + .driver = { + .name = "allegro", + .of_match_table = of_match_ptr(allegro_dt_ids), + .pm = &allegro_pm_ops, + }, +}; + +module_platform_driver(allegro_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Tretter "); +MODULE_DESCRIPTION("Allegro DVT encoder driver"); diff --git a/drivers/media/platform/allegro-dvt/allegro-mail.c b/drivers/media/platform/allegro-dvt/allegro-mail.c new file mode 100644 index 000000000..16effad10 --- /dev/null +++ b/drivers/media/platform/allegro-dvt/allegro-mail.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter + * + * Helper functions for handling messages that are send via mailbox to the + * Allegro VCU firmware. + */ + +#include +#include +#include +#include +#include + +#include "allegro-mail.h" + +const char *msg_type_name(enum mcu_msg_type type) +{ + static char buf[9]; + + switch (type) { + case MCU_MSG_TYPE_INIT: + return "INIT"; + case MCU_MSG_TYPE_CREATE_CHANNEL: + return "CREATE_CHANNEL"; + case MCU_MSG_TYPE_DESTROY_CHANNEL: + return "DESTROY_CHANNEL"; + case MCU_MSG_TYPE_ENCODE_FRAME: + return "ENCODE_FRAME"; + case MCU_MSG_TYPE_PUT_STREAM_BUFFER: + return "PUT_STREAM_BUFFER"; + case MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE: + return "PUSH_BUFFER_INTERMEDIATE"; + case MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE: + return "PUSH_BUFFER_REFERENCE"; + default: + snprintf(buf, sizeof(buf), "(0x%04x)", type); + return buf; + } +} +EXPORT_SYMBOL(msg_type_name); + +static ssize_t +allegro_enc_init(u32 *dst, struct mcu_msg_init_request *msg) +{ + unsigned int i = 0; + enum mcu_msg_version version = msg->header.version; + + dst[i++] = msg->reserved0; + dst[i++] = msg->suballoc_dma; + dst[i++] = msg->suballoc_size; + dst[i++] = msg->encoder_buffer_size; + dst[i++] = msg->encoder_buffer_color_depth; + dst[i++] = msg->num_cores; + if (version >= MCU_MSG_VERSION_2019_2) { + dst[i++] = msg->clk_rate; + dst[i++] = 0; + } + + return i * sizeof(*dst); +} + +static inline u32 settings_get_mcu_codec(struct create_channel_param *param) +{ + enum mcu_msg_version version = param->version; + u32 pixelformat = param->codec; + + if (version < MCU_MSG_VERSION_2019_2) { + switch (pixelformat) { + case V4L2_PIX_FMT_HEVC: + return 2; + case V4L2_PIX_FMT_H264: + default: + return 1; + } + } else { + switch (pixelformat) { + case V4L2_PIX_FMT_HEVC: + return 1; + case V4L2_PIX_FMT_H264: + default: + return 0; + } + } +} + +ssize_t +allegro_encode_config_blob(u32 *dst, struct create_channel_param *param) +{ + enum mcu_msg_version version = param->version; + unsigned int i = 0; + unsigned int j = 0; + u32 val; + unsigned int codec = settings_get_mcu_codec(param); + + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = param->layer_id; + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->height) | + FIELD_PREP(GENMASK(15, 0), param->width); + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = param->videomode; + dst[i++] = param->format; + if (version < MCU_MSG_VERSION_2019_2) + dst[i++] = param->colorspace; + dst[i++] = param->src_mode; + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = param->src_bit_depth; + dst[i++] = FIELD_PREP(GENMASK(31, 24), codec) | + FIELD_PREP(GENMASK(23, 8), param->constraint_set_flags) | + FIELD_PREP(GENMASK(7, 0), param->profile); + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->tier) | + FIELD_PREP(GENMASK(15, 0), param->level); + + val = 0; + val |= param->temporal_mvp_enable ? BIT(20) : 0; + val |= FIELD_PREP(GENMASK(7, 4), param->log2_max_frame_num); + if (version >= MCU_MSG_VERSION_2019_2) + val |= FIELD_PREP(GENMASK(3, 0), param->log2_max_poc - 1); + else + val |= FIELD_PREP(GENMASK(3, 0), param->log2_max_poc); + dst[i++] = val; + + val = 0; + val |= param->enable_reordering ? BIT(0) : 0; + val |= param->dbf_ovr_en ? BIT(2) : 0; + val |= param->override_lf ? BIT(12) : 0; + dst[i++] = val; + + if (version >= MCU_MSG_VERSION_2019_2) { + val = 0; + val |= param->custom_lda ? BIT(2) : 0; + val |= param->rdo_cost_mode ? BIT(20) : 0; + dst[i++] = val; + + val = 0; + val |= param->lf ? BIT(2) : 0; + val |= param->lf_x_tile ? BIT(3) : 0; + val |= param->lf_x_slice ? BIT(4) : 0; + dst[i++] = val; + } else { + val = 0; + dst[i++] = val; + } + + dst[i++] = FIELD_PREP(GENMASK(15, 8), param->beta_offset) | + FIELD_PREP(GENMASK(7, 0), param->tc_offset); + dst[i++] = param->unknown11; + dst[i++] = param->unknown12; + dst[i++] = param->num_slices; + dst[i++] = param->encoder_buffer_offset; + dst[i++] = param->encoder_buffer_enabled; + + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->clip_vrt_range) | + FIELD_PREP(GENMASK(15, 0), param->clip_hrz_range); + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->me_range[1]) | + FIELD_PREP(GENMASK(15, 0), param->me_range[0]); + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->me_range[3]) | + FIELD_PREP(GENMASK(15, 0), param->me_range[2]); + dst[i++] = FIELD_PREP(GENMASK(31, 24), param->min_tu_size) | + FIELD_PREP(GENMASK(23, 16), param->max_tu_size) | + FIELD_PREP(GENMASK(15, 8), param->min_cu_size) | + FIELD_PREP(GENMASK(8, 0), param->max_cu_size); + dst[i++] = FIELD_PREP(GENMASK(15, 8), param->max_transfo_depth_intra) | + FIELD_PREP(GENMASK(7, 0), param->max_transfo_depth_inter); + dst[i++] = param->entropy_mode; + dst[i++] = param->wp_mode; + + dst[i++] = param->rate_control_mode; + dst[i++] = param->initial_rem_delay; + dst[i++] = param->cpb_size; + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->clk_ratio) | + FIELD_PREP(GENMASK(15, 0), param->framerate); + dst[i++] = param->target_bitrate; + dst[i++] = param->max_bitrate; + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->min_qp) | + FIELD_PREP(GENMASK(15, 0), param->initial_qp); + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->ip_delta) | + FIELD_PREP(GENMASK(15, 0), param->max_qp); + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->golden_ref) | + FIELD_PREP(GENMASK(15, 0), param->pb_delta); + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->golden_ref_frequency) | + FIELD_PREP(GENMASK(15, 0), param->golden_delta); + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = param->rate_control_option; + else + dst[i++] = 0; + + if (version >= MCU_MSG_VERSION_2019_2) { + dst[i++] = param->num_pixel; + dst[i++] = FIELD_PREP(GENMASK(31, 16), param->max_pixel_value) | + FIELD_PREP(GENMASK(15, 0), param->max_psnr); + for (j = 0; j < 3; j++) + dst[i++] = param->maxpicturesize[j]; + } + + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = param->gop_ctrl_mode; + else + dst[i++] = 0; + + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = FIELD_PREP(GENMASK(31, 24), param->freq_golden_ref) | + FIELD_PREP(GENMASK(23, 16), param->num_b) | + FIELD_PREP(GENMASK(15, 0), param->gop_length); + dst[i++] = param->freq_idr; + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = param->enable_lt; + dst[i++] = param->freq_lt; + dst[i++] = param->gdr_mode; + if (version < MCU_MSG_VERSION_2019_2) + dst[i++] = FIELD_PREP(GENMASK(31, 24), param->freq_golden_ref) | + FIELD_PREP(GENMASK(23, 16), param->num_b) | + FIELD_PREP(GENMASK(15, 0), param->gop_length); + + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = param->tmpdqp; + + dst[i++] = param->subframe_latency; + dst[i++] = param->lda_control_mode; + if (version < MCU_MSG_VERSION_2019_2) + dst[i++] = param->unknown41; + + if (version >= MCU_MSG_VERSION_2019_2) { + for (j = 0; j < 6; j++) + dst[i++] = param->lda_factors[j]; + dst[i++] = param->max_num_merge_cand; + } + + return i * sizeof(*dst); +} + +static ssize_t +allegro_enc_create_channel(u32 *dst, struct mcu_msg_create_channel *msg) +{ + enum mcu_msg_version version = msg->header.version; + unsigned int i = 0; + + dst[i++] = msg->user_id; + + if (version >= MCU_MSG_VERSION_2019_2) { + dst[i++] = msg->blob_mcu_addr; + } else { + memcpy(&dst[i], msg->blob, msg->blob_size); + i += msg->blob_size / sizeof(*dst); + } + + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = msg->ep1_addr; + + return i * sizeof(*dst); +} + +ssize_t allegro_decode_config_blob(struct create_channel_param *param, + struct mcu_msg_create_channel_response *msg, + u32 *src) +{ + enum mcu_msg_version version = msg->header.version; + + if (version >= MCU_MSG_VERSION_2019_2) { + param->num_ref_idx_l0 = FIELD_GET(GENMASK(7, 4), src[9]); + param->num_ref_idx_l1 = FIELD_GET(GENMASK(11, 8), src[9]); + } else { + param->num_ref_idx_l0 = msg->num_ref_idx_l0; + param->num_ref_idx_l1 = msg->num_ref_idx_l1; + } + + return 0; +} + +static ssize_t +allegro_enc_destroy_channel(u32 *dst, struct mcu_msg_destroy_channel *msg) +{ + unsigned int i = 0; + + dst[i++] = msg->channel_id; + + return i * sizeof(*dst); +} + +static ssize_t +allegro_enc_push_buffers(u32 *dst, struct mcu_msg_push_buffers_internal *msg) +{ + unsigned int i = 0; + struct mcu_msg_push_buffers_internal_buffer *buffer; + unsigned int num_buffers = msg->num_buffers; + unsigned int j; + + dst[i++] = msg->channel_id; + + for (j = 0; j < num_buffers; j++) { + buffer = &msg->buffer[j]; + dst[i++] = buffer->dma_addr; + dst[i++] = buffer->mcu_addr; + dst[i++] = buffer->size; + } + + return i * sizeof(*dst); +} + +static ssize_t +allegro_enc_put_stream_buffer(u32 *dst, + struct mcu_msg_put_stream_buffer *msg) +{ + unsigned int i = 0; + + dst[i++] = msg->channel_id; + dst[i++] = msg->dma_addr; + dst[i++] = msg->mcu_addr; + dst[i++] = msg->size; + dst[i++] = msg->offset; + dst[i++] = lower_32_bits(msg->dst_handle); + dst[i++] = upper_32_bits(msg->dst_handle); + + return i * sizeof(*dst); +} + +static ssize_t +allegro_enc_encode_frame(u32 *dst, struct mcu_msg_encode_frame *msg) +{ + enum mcu_msg_version version = msg->header.version; + unsigned int i = 0; + + dst[i++] = msg->channel_id; + + dst[i++] = msg->reserved; + dst[i++] = msg->encoding_options; + dst[i++] = FIELD_PREP(GENMASK(31, 16), msg->padding) | + FIELD_PREP(GENMASK(15, 0), msg->pps_qp); + + if (version >= MCU_MSG_VERSION_2019_2) { + dst[i++] = 0; + dst[i++] = 0; + dst[i++] = 0; + dst[i++] = 0; + } + + dst[i++] = lower_32_bits(msg->user_param); + dst[i++] = upper_32_bits(msg->user_param); + dst[i++] = lower_32_bits(msg->src_handle); + dst[i++] = upper_32_bits(msg->src_handle); + dst[i++] = msg->request_options; + dst[i++] = msg->src_y; + dst[i++] = msg->src_uv; + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = msg->is_10_bit; + dst[i++] = msg->stride; + if (version >= MCU_MSG_VERSION_2019_2) + dst[i++] = msg->format; + dst[i++] = msg->ep2; + dst[i++] = lower_32_bits(msg->ep2_v); + dst[i++] = upper_32_bits(msg->ep2_v); + + return i * sizeof(*dst); +} + +static ssize_t +allegro_dec_init(struct mcu_msg_init_response *msg, u32 *src) +{ + unsigned int i = 0; + + msg->reserved0 = src[i++]; + + return i * sizeof(*src); +} + +static ssize_t +allegro_dec_create_channel(struct mcu_msg_create_channel_response *msg, + u32 *src) +{ + enum mcu_msg_version version = msg->header.version; + unsigned int i = 0; + + msg->channel_id = src[i++]; + msg->user_id = src[i++]; + /* + * Version >= MCU_MSG_VERSION_2019_2 is handled in + * allegro_decode_config_blob(). + */ + if (version < MCU_MSG_VERSION_2019_2) { + msg->options = src[i++]; + msg->num_core = src[i++]; + msg->num_ref_idx_l0 = FIELD_GET(GENMASK(7, 4), src[i]); + msg->num_ref_idx_l1 = FIELD_GET(GENMASK(11, 8), src[i++]); + } + msg->int_buffers_count = src[i++]; + msg->int_buffers_size = src[i++]; + msg->rec_buffers_count = src[i++]; + msg->rec_buffers_size = src[i++]; + msg->reserved = src[i++]; + msg->error_code = src[i++]; + + return i * sizeof(*src); +} + +static ssize_t +allegro_dec_destroy_channel(struct mcu_msg_destroy_channel_response *msg, + u32 *src) +{ + unsigned int i = 0; + + msg->channel_id = src[i++]; + + return i * sizeof(*src); +} + +static ssize_t +allegro_dec_encode_frame(struct mcu_msg_encode_frame_response *msg, u32 *src) +{ + enum mcu_msg_version version = msg->header.version; + unsigned int i = 0; + unsigned int j; + + msg->channel_id = src[i++]; + + msg->dst_handle = src[i++]; + msg->dst_handle |= (((u64)src[i++]) << 32); + msg->user_param = src[i++]; + msg->user_param |= (((u64)src[i++]) << 32); + msg->src_handle = src[i++]; + msg->src_handle |= (((u64)src[i++]) << 32); + msg->skip = FIELD_GET(GENMASK(31, 16), src[i]); + msg->is_ref = FIELD_GET(GENMASK(15, 0), src[i++]); + msg->initial_removal_delay = src[i++]; + msg->dpb_output_delay = src[i++]; + msg->size = src[i++]; + msg->frame_tag_size = src[i++]; + msg->stuffing = src[i++]; + msg->filler = src[i++]; + msg->num_row = FIELD_GET(GENMASK(31, 16), src[i]); + msg->num_column = FIELD_GET(GENMASK(15, 0), src[i++]); + msg->num_ref_idx_l1 = FIELD_GET(GENMASK(31, 24), src[i]); + msg->num_ref_idx_l0 = FIELD_GET(GENMASK(23, 16), src[i]); + msg->qp = FIELD_GET(GENMASK(15, 0), src[i++]); + msg->partition_table_offset = src[i++]; + msg->partition_table_size = src[i++]; + msg->sum_complex = src[i++]; + for (j = 0; j < 4; j++) + msg->tile_width[j] = src[i++]; + for (j = 0; j < 22; j++) + msg->tile_height[j] = src[i++]; + msg->error_code = src[i++]; + msg->slice_type = src[i++]; + msg->pic_struct = src[i++]; + msg->reserved = FIELD_GET(GENMASK(31, 24), src[i]); + msg->is_last_slice = FIELD_GET(GENMASK(23, 16), src[i]); + msg->is_first_slice = FIELD_GET(GENMASK(15, 8), src[i]); + msg->is_idr = FIELD_GET(GENMASK(7, 0), src[i++]); + + msg->reserved1 = FIELD_GET(GENMASK(31, 16), src[i]); + msg->pps_qp = FIELD_GET(GENMASK(15, 0), src[i++]); + + msg->reserved2 = src[i++]; + if (version >= MCU_MSG_VERSION_2019_2) { + msg->reserved3 = src[i++]; + msg->reserved4 = src[i++]; + msg->reserved5 = src[i++]; + msg->reserved6 = src[i++]; + } + + return i * sizeof(*src); +} + +/** + * allegro_encode_mail() - Encode allegro messages to firmware format + * @dst: Pointer to the memory that will be filled with data + * @msg: The allegro message that will be encoded + */ +ssize_t allegro_encode_mail(u32 *dst, void *msg) +{ + const struct mcu_msg_header *header = msg; + ssize_t size; + + if (!msg || !dst) + return -EINVAL; + + switch (header->type) { + case MCU_MSG_TYPE_INIT: + size = allegro_enc_init(&dst[1], msg); + break; + case MCU_MSG_TYPE_CREATE_CHANNEL: + size = allegro_enc_create_channel(&dst[1], msg); + break; + case MCU_MSG_TYPE_DESTROY_CHANNEL: + size = allegro_enc_destroy_channel(&dst[1], msg); + break; + case MCU_MSG_TYPE_ENCODE_FRAME: + size = allegro_enc_encode_frame(&dst[1], msg); + break; + case MCU_MSG_TYPE_PUT_STREAM_BUFFER: + size = allegro_enc_put_stream_buffer(&dst[1], msg); + break; + case MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE: + case MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE: + size = allegro_enc_push_buffers(&dst[1], msg); + break; + default: + return -EINVAL; + } + + /* + * The encoded messages might have different length depending on + * the firmware version or certain fields. Therefore, we have to + * set the body length after encoding the message. + */ + dst[0] = FIELD_PREP(GENMASK(31, 16), header->type) | + FIELD_PREP(GENMASK(15, 0), size); + + return size + sizeof(*dst); +} + +/** + * allegro_decode_mail() - Parse allegro messages from the firmware. + * @msg: The mcu_msg_response that will be filled with parsed values. + * @src: Pointer to the memory that will be parsed + * + * The message format in the mailbox depends on the firmware. Parse the + * different formats into a uniform message format that can be used without + * taking care of the firmware version. + */ +int allegro_decode_mail(void *msg, u32 *src) +{ + struct mcu_msg_header *header; + + if (!src || !msg) + return -EINVAL; + + header = msg; + header->type = FIELD_GET(GENMASK(31, 16), src[0]); + + src++; + switch (header->type) { + case MCU_MSG_TYPE_INIT: + allegro_dec_init(msg, src); + break; + case MCU_MSG_TYPE_CREATE_CHANNEL: + allegro_dec_create_channel(msg, src); + break; + case MCU_MSG_TYPE_DESTROY_CHANNEL: + allegro_dec_destroy_channel(msg, src); + break; + case MCU_MSG_TYPE_ENCODE_FRAME: + allegro_dec_encode_frame(msg, src); + break; + default: + return -EINVAL; + } + + return 0; +} diff --git a/drivers/media/platform/allegro-dvt/allegro-mail.h b/drivers/media/platform/allegro-dvt/allegro-mail.h new file mode 100644 index 000000000..a5686058d --- /dev/null +++ b/drivers/media/platform/allegro-dvt/allegro-mail.h @@ -0,0 +1,297 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter + * + * Allegro VCU firmware mailbox mail definitions + */ + +#ifndef ALLEGRO_MAIL_H +#define ALLEGRO_MAIL_H + +#include + +enum mcu_msg_type { + MCU_MSG_TYPE_INIT = 0x0000, + MCU_MSG_TYPE_CREATE_CHANNEL = 0x0005, + MCU_MSG_TYPE_DESTROY_CHANNEL = 0x0006, + MCU_MSG_TYPE_ENCODE_FRAME = 0x0007, + MCU_MSG_TYPE_PUT_STREAM_BUFFER = 0x0012, + MCU_MSG_TYPE_PUSH_BUFFER_INTERMEDIATE = 0x000e, + MCU_MSG_TYPE_PUSH_BUFFER_REFERENCE = 0x000f, +}; + +enum mcu_msg_version { + MCU_MSG_VERSION_2018_2, + MCU_MSG_VERSION_2019_2, +}; + +const char *msg_type_name(enum mcu_msg_type type); + +struct mcu_msg_header { + enum mcu_msg_type type; + enum mcu_msg_version version; +}; + +struct mcu_msg_init_request { + struct mcu_msg_header header; + u32 reserved0; /* maybe a unused channel id */ + u32 suballoc_dma; + u32 suballoc_size; + s32 encoder_buffer_size; + s32 encoder_buffer_color_depth; + s32 num_cores; + s32 clk_rate; +}; + +struct mcu_msg_init_response { + struct mcu_msg_header header; + u32 reserved0; +}; + +struct create_channel_param { + enum mcu_msg_version version; + u32 layer_id; + u16 width; + u16 height; + u32 videomode; + u32 format; + u32 colorspace; + u32 src_mode; + u32 src_bit_depth; + u8 profile; + u16 constraint_set_flags; + u32 codec; + u16 level; + u16 tier; + u32 log2_max_poc; + u32 log2_max_frame_num; + u32 temporal_mvp_enable; + u32 enable_reordering; + u32 dbf_ovr_en; + u32 override_lf; + u32 num_ref_idx_l0; + u32 num_ref_idx_l1; + u32 custom_lda; + u32 rdo_cost_mode; + u32 lf; + u32 lf_x_tile; + u32 lf_x_slice; + s8 beta_offset; + s8 tc_offset; + u16 reserved10; + u32 unknown11; + u32 unknown12; + u16 num_slices; + u32 encoder_buffer_offset; + u32 encoder_buffer_enabled; + u16 clip_hrz_range; + u16 clip_vrt_range; + u16 me_range[4]; + u8 max_cu_size; + u8 min_cu_size; + u8 max_tu_size; + u8 min_tu_size; + u8 max_transfo_depth_inter; + u8 max_transfo_depth_intra; + u16 reserved20; + u32 entropy_mode; + u32 wp_mode; + + /* rate control param */ + u32 rate_control_mode; + u32 initial_rem_delay; + u32 cpb_size; + u16 framerate; + u16 clk_ratio; + u32 target_bitrate; + u32 max_bitrate; + u16 initial_qp; + u16 min_qp; + u16 max_qp; + s16 ip_delta; + s16 pb_delta; + u16 golden_ref; + u16 golden_delta; + u16 golden_ref_frequency; + u32 rate_control_option; + u32 num_pixel; + u16 max_psnr; + u16 max_pixel_value; + u32 maxpicturesize[3]; + + /* gop param */ + u32 gop_ctrl_mode; + u32 freq_idr; + u32 freq_lt; + u32 gdr_mode; + u16 gop_length; + u8 num_b; + u8 freq_golden_ref; + u32 enable_lt; + u32 tmpdqp; + + u32 subframe_latency; + u32 lda_control_mode; + u32 unknown41; + + u32 lda_factors[6]; + + u32 max_num_merge_cand; +}; + +struct mcu_msg_create_channel { + struct mcu_msg_header header; + u32 user_id; + u32 *blob; + size_t blob_size; + u32 blob_mcu_addr; + u32 ep1_addr; +}; + +struct mcu_msg_create_channel_response { + struct mcu_msg_header header; + u32 channel_id; + u32 user_id; + u32 options; + u32 num_core; + u32 num_ref_idx_l0; + u32 num_ref_idx_l1; + u32 int_buffers_count; + u32 int_buffers_size; + u32 rec_buffers_count; + u32 rec_buffers_size; + u32 reserved; + u32 error_code; +}; + +struct mcu_msg_destroy_channel { + struct mcu_msg_header header; + u32 channel_id; +}; + +struct mcu_msg_destroy_channel_response { + struct mcu_msg_header header; + u32 channel_id; +}; + +struct mcu_msg_push_buffers_internal_buffer { + u32 dma_addr; + u32 mcu_addr; + u32 size; +}; + +struct mcu_msg_push_buffers_internal { + struct mcu_msg_header header; + u32 channel_id; + size_t num_buffers; + struct mcu_msg_push_buffers_internal_buffer buffer[]; +}; + +struct mcu_msg_put_stream_buffer { + struct mcu_msg_header header; + u32 channel_id; + u32 dma_addr; + u32 mcu_addr; + u32 size; + u32 offset; + u64 dst_handle; +}; + +struct mcu_msg_encode_frame { + struct mcu_msg_header header; + u32 channel_id; + u32 reserved; + + u32 encoding_options; +#define AL_OPT_USE_QP_TABLE BIT(0) +#define AL_OPT_FORCE_LOAD BIT(1) +#define AL_OPT_USE_L2 BIT(2) +#define AL_OPT_DISABLE_INTRA BIT(3) +#define AL_OPT_DEPENDENT_SLICES BIT(4) + + s16 pps_qp; + u16 padding; + u64 user_param; + u64 src_handle; + + u32 request_options; +#define AL_OPT_SCENE_CHANGE BIT(0) +#define AL_OPT_RESTART_GOP BIT(1) +#define AL_OPT_USE_LONG_TERM BIT(2) +#define AL_OPT_UPDATE_PARAMS BIT(3) + + /* u32 scene_change_delay (optional) */ + /* rate control param (optional) */ + /* gop param (optional) */ + /* dynamic resolution params (optional) */ + u32 src_y; + u32 src_uv; + u32 is_10_bit; + u32 stride; + u32 format; + u32 ep2; + u64 ep2_v; +}; + +struct mcu_msg_encode_frame_response { + struct mcu_msg_header header; + u32 channel_id; + u64 dst_handle; /* see mcu_msg_put_stream_buffer */ + u64 user_param; /* see mcu_msg_encode_frame */ + u64 src_handle; /* see mcu_msg_encode_frame */ + u16 skip; + u16 is_ref; + u32 initial_removal_delay; + u32 dpb_output_delay; + u32 size; + u32 frame_tag_size; + s32 stuffing; + s32 filler; + u16 num_column; + u16 num_row; + u16 qp; + u8 num_ref_idx_l0; + u8 num_ref_idx_l1; + u32 partition_table_offset; + s32 partition_table_size; + u32 sum_complex; + s32 tile_width[4]; + s32 tile_height[22]; + u32 error_code; + + u32 slice_type; +#define AL_ENC_SLICE_TYPE_B 0 +#define AL_ENC_SLICE_TYPE_P 1 +#define AL_ENC_SLICE_TYPE_I 2 + + u32 pic_struct; + u8 is_idr; + u8 is_first_slice; + u8 is_last_slice; + u8 reserved; + u16 pps_qp; + u16 reserved1; + u32 reserved2; + u32 reserved3; + u32 reserved4; + u32 reserved5; + u32 reserved6; +}; + +union mcu_msg_response { + struct mcu_msg_header header; + struct mcu_msg_init_response init; + struct mcu_msg_create_channel_response create_channel; + struct mcu_msg_destroy_channel_response destroy_channel; + struct mcu_msg_encode_frame_response encode_frame; +}; + +ssize_t allegro_encode_config_blob(u32 *dst, struct create_channel_param *param); +ssize_t allegro_decode_config_blob(struct create_channel_param *param, + struct mcu_msg_create_channel_response *msg, + u32 *src); + +int allegro_decode_mail(void *msg, u32 *src); +ssize_t allegro_encode_mail(u32 *dst, void *msg); + +#endif diff --git a/drivers/media/platform/allegro-dvt/nal-h264.c b/drivers/media/platform/allegro-dvt/nal-h264.c new file mode 100644 index 000000000..326637663 --- /dev/null +++ b/drivers/media/platform/allegro-dvt/nal-h264.c @@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter + * + * Convert NAL units between raw byte sequence payloads (RBSP) and C structs + * + * The conversion is defined in "ITU-T Rec. H.264 (04/2017) Advanced video + * coding for generic audiovisual services". Decoder drivers may use the + * parser to parse RBSP from encoded streams and configure the hardware, if + * the hardware is not able to parse RBSP itself. Encoder drivers may use the + * generator to generate the RBSP for SPS/PPS nal units and add them to the + * encoded stream if the hardware does not generate the units. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "nal-h264.h" +#include "nal-rbsp.h" + +/* + * See Rec. ITU-T H.264 (04/2017) Table 7-1 - NAL unit type codes, syntax + * element categories, and NAL unit type classes + */ +enum nal_unit_type { + SEQUENCE_PARAMETER_SET = 7, + PICTURE_PARAMETER_SET = 8, + FILLER_DATA = 12, +}; + +static void nal_h264_write_start_code_prefix(struct rbsp *rbsp) +{ + u8 *p = rbsp->data + DIV_ROUND_UP(rbsp->pos, 8); + int i = 4; + + if (DIV_ROUND_UP(rbsp->pos, 8) + i > rbsp->size) { + rbsp->error = -EINVAL; + return; + } + + p[0] = 0x00; + p[1] = 0x00; + p[2] = 0x00; + p[3] = 0x01; + + rbsp->pos += i * 8; +} + +static void nal_h264_read_start_code_prefix(struct rbsp *rbsp) +{ + u8 *p = rbsp->data + DIV_ROUND_UP(rbsp->pos, 8); + int i = 4; + + if (DIV_ROUND_UP(rbsp->pos, 8) + i > rbsp->size) { + rbsp->error = -EINVAL; + return; + } + + if (p[0] != 0x00 || p[1] != 0x00 || p[2] != 0x00 || p[3] != 0x01) { + rbsp->error = -EINVAL; + return; + } + + rbsp->pos += i * 8; +} + +static void nal_h264_write_filler_data(struct rbsp *rbsp) +{ + u8 *p = rbsp->data + DIV_ROUND_UP(rbsp->pos, 8); + int i; + + /* Keep 1 byte extra for terminating the NAL unit */ + i = rbsp->size - DIV_ROUND_UP(rbsp->pos, 8) - 1; + memset(p, 0xff, i); + rbsp->pos += i * 8; +} + +static void nal_h264_read_filler_data(struct rbsp *rbsp) +{ + u8 *p = rbsp->data + DIV_ROUND_UP(rbsp->pos, 8); + + while (*p == 0xff) { + if (DIV_ROUND_UP(rbsp->pos, 8) > rbsp->size) { + rbsp->error = -EINVAL; + return; + } + + p++; + rbsp->pos += 8; + } +} + +static void nal_h264_rbsp_hrd_parameters(struct rbsp *rbsp, + struct nal_h264_hrd_parameters *hrd) +{ + unsigned int i; + + if (!hrd) { + rbsp->error = -EINVAL; + return; + } + + rbsp_uev(rbsp, &hrd->cpb_cnt_minus1); + rbsp_bits(rbsp, 4, &hrd->bit_rate_scale); + rbsp_bits(rbsp, 4, &hrd->cpb_size_scale); + + for (i = 0; i <= hrd->cpb_cnt_minus1; i++) { + rbsp_uev(rbsp, &hrd->bit_rate_value_minus1[i]); + rbsp_uev(rbsp, &hrd->cpb_size_value_minus1[i]); + rbsp_bit(rbsp, &hrd->cbr_flag[i]); + } + + rbsp_bits(rbsp, 5, &hrd->initial_cpb_removal_delay_length_minus1); + rbsp_bits(rbsp, 5, &hrd->cpb_removal_delay_length_minus1); + rbsp_bits(rbsp, 5, &hrd->dpb_output_delay_length_minus1); + rbsp_bits(rbsp, 5, &hrd->time_offset_length); +} + +static void nal_h264_rbsp_vui_parameters(struct rbsp *rbsp, + struct nal_h264_vui_parameters *vui) +{ + if (!vui) { + rbsp->error = -EINVAL; + return; + } + + rbsp_bit(rbsp, &vui->aspect_ratio_info_present_flag); + if (vui->aspect_ratio_info_present_flag) { + rbsp_bits(rbsp, 8, &vui->aspect_ratio_idc); + if (vui->aspect_ratio_idc == 255) { + rbsp_bits(rbsp, 16, &vui->sar_width); + rbsp_bits(rbsp, 16, &vui->sar_height); + } + } + + rbsp_bit(rbsp, &vui->overscan_info_present_flag); + if (vui->overscan_info_present_flag) + rbsp_bit(rbsp, &vui->overscan_appropriate_flag); + + rbsp_bit(rbsp, &vui->video_signal_type_present_flag); + if (vui->video_signal_type_present_flag) { + rbsp_bits(rbsp, 3, &vui->video_format); + rbsp_bit(rbsp, &vui->video_full_range_flag); + + rbsp_bit(rbsp, &vui->colour_description_present_flag); + if (vui->colour_description_present_flag) { + rbsp_bits(rbsp, 8, &vui->colour_primaries); + rbsp_bits(rbsp, 8, &vui->transfer_characteristics); + rbsp_bits(rbsp, 8, &vui->matrix_coefficients); + } + } + + rbsp_bit(rbsp, &vui->chroma_loc_info_present_flag); + if (vui->chroma_loc_info_present_flag) { + rbsp_uev(rbsp, &vui->chroma_sample_loc_type_top_field); + rbsp_uev(rbsp, &vui->chroma_sample_loc_type_bottom_field); + } + + rbsp_bit(rbsp, &vui->timing_info_present_flag); + if (vui->timing_info_present_flag) { + rbsp_bits(rbsp, 32, &vui->num_units_in_tick); + rbsp_bits(rbsp, 32, &vui->time_scale); + rbsp_bit(rbsp, &vui->fixed_frame_rate_flag); + } + + rbsp_bit(rbsp, &vui->nal_hrd_parameters_present_flag); + if (vui->nal_hrd_parameters_present_flag) + nal_h264_rbsp_hrd_parameters(rbsp, &vui->nal_hrd_parameters); + + rbsp_bit(rbsp, &vui->vcl_hrd_parameters_present_flag); + if (vui->vcl_hrd_parameters_present_flag) + nal_h264_rbsp_hrd_parameters(rbsp, &vui->vcl_hrd_parameters); + + if (vui->nal_hrd_parameters_present_flag || + vui->vcl_hrd_parameters_present_flag) + rbsp_bit(rbsp, &vui->low_delay_hrd_flag); + + rbsp_bit(rbsp, &vui->pic_struct_present_flag); + + rbsp_bit(rbsp, &vui->bitstream_restriction_flag); + if (vui->bitstream_restriction_flag) { + rbsp_bit(rbsp, &vui->motion_vectors_over_pic_boundaries_flag); + rbsp_uev(rbsp, &vui->max_bytes_per_pic_denom); + rbsp_uev(rbsp, &vui->max_bits_per_mb_denom); + rbsp_uev(rbsp, &vui->log2_max_mv_length_horizontal); + rbsp_uev(rbsp, &vui->log21_max_mv_length_vertical); + rbsp_uev(rbsp, &vui->max_num_reorder_frames); + rbsp_uev(rbsp, &vui->max_dec_frame_buffering); + } +} + +static void nal_h264_rbsp_sps(struct rbsp *rbsp, struct nal_h264_sps *sps) +{ + unsigned int i; + + if (!sps) { + rbsp->error = -EINVAL; + return; + } + + rbsp_bits(rbsp, 8, &sps->profile_idc); + rbsp_bit(rbsp, &sps->constraint_set0_flag); + rbsp_bit(rbsp, &sps->constraint_set1_flag); + rbsp_bit(rbsp, &sps->constraint_set2_flag); + rbsp_bit(rbsp, &sps->constraint_set3_flag); + rbsp_bit(rbsp, &sps->constraint_set4_flag); + rbsp_bit(rbsp, &sps->constraint_set5_flag); + rbsp_bits(rbsp, 2, &sps->reserved_zero_2bits); + rbsp_bits(rbsp, 8, &sps->level_idc); + + rbsp_uev(rbsp, &sps->seq_parameter_set_id); + + if (sps->profile_idc == 100 || sps->profile_idc == 110 || + sps->profile_idc == 122 || sps->profile_idc == 244 || + sps->profile_idc == 44 || sps->profile_idc == 83 || + sps->profile_idc == 86 || sps->profile_idc == 118 || + sps->profile_idc == 128 || sps->profile_idc == 138 || + sps->profile_idc == 139 || sps->profile_idc == 134 || + sps->profile_idc == 135) { + rbsp_uev(rbsp, &sps->chroma_format_idc); + + if (sps->chroma_format_idc == 3) + rbsp_bit(rbsp, &sps->separate_colour_plane_flag); + rbsp_uev(rbsp, &sps->bit_depth_luma_minus8); + rbsp_uev(rbsp, &sps->bit_depth_chroma_minus8); + rbsp_bit(rbsp, &sps->qpprime_y_zero_transform_bypass_flag); + rbsp_bit(rbsp, &sps->seq_scaling_matrix_present_flag); + if (sps->seq_scaling_matrix_present_flag) + rbsp->error = -EINVAL; + } + + rbsp_uev(rbsp, &sps->log2_max_frame_num_minus4); + + rbsp_uev(rbsp, &sps->pic_order_cnt_type); + switch (sps->pic_order_cnt_type) { + case 0: + rbsp_uev(rbsp, &sps->log2_max_pic_order_cnt_lsb_minus4); + break; + case 1: + rbsp_bit(rbsp, &sps->delta_pic_order_always_zero_flag); + rbsp_sev(rbsp, &sps->offset_for_non_ref_pic); + rbsp_sev(rbsp, &sps->offset_for_top_to_bottom_field); + + rbsp_uev(rbsp, &sps->num_ref_frames_in_pic_order_cnt_cycle); + for (i = 0; i < sps->num_ref_frames_in_pic_order_cnt_cycle; i++) + rbsp_sev(rbsp, &sps->offset_for_ref_frame[i]); + break; + default: + rbsp->error = -EINVAL; + break; + } + + rbsp_uev(rbsp, &sps->max_num_ref_frames); + rbsp_bit(rbsp, &sps->gaps_in_frame_num_value_allowed_flag); + rbsp_uev(rbsp, &sps->pic_width_in_mbs_minus1); + rbsp_uev(rbsp, &sps->pic_height_in_map_units_minus1); + + rbsp_bit(rbsp, &sps->frame_mbs_only_flag); + if (!sps->frame_mbs_only_flag) + rbsp_bit(rbsp, &sps->mb_adaptive_frame_field_flag); + + rbsp_bit(rbsp, &sps->direct_8x8_inference_flag); + + rbsp_bit(rbsp, &sps->frame_cropping_flag); + if (sps->frame_cropping_flag) { + rbsp_uev(rbsp, &sps->crop_left); + rbsp_uev(rbsp, &sps->crop_right); + rbsp_uev(rbsp, &sps->crop_top); + rbsp_uev(rbsp, &sps->crop_bottom); + } + + rbsp_bit(rbsp, &sps->vui_parameters_present_flag); + if (sps->vui_parameters_present_flag) + nal_h264_rbsp_vui_parameters(rbsp, &sps->vui); +} + +static void nal_h264_rbsp_pps(struct rbsp *rbsp, struct nal_h264_pps *pps) +{ + int i; + + rbsp_uev(rbsp, &pps->pic_parameter_set_id); + rbsp_uev(rbsp, &pps->seq_parameter_set_id); + rbsp_bit(rbsp, &pps->entropy_coding_mode_flag); + rbsp_bit(rbsp, &pps->bottom_field_pic_order_in_frame_present_flag); + rbsp_uev(rbsp, &pps->num_slice_groups_minus1); + if (pps->num_slice_groups_minus1 > 0) { + rbsp_uev(rbsp, &pps->slice_group_map_type); + switch (pps->slice_group_map_type) { + case 0: + for (i = 0; i < pps->num_slice_groups_minus1; i++) + rbsp_uev(rbsp, &pps->run_length_minus1[i]); + break; + case 2: + for (i = 0; i < pps->num_slice_groups_minus1; i++) { + rbsp_uev(rbsp, &pps->top_left[i]); + rbsp_uev(rbsp, &pps->bottom_right[i]); + } + break; + case 3: case 4: case 5: + rbsp_bit(rbsp, &pps->slice_group_change_direction_flag); + rbsp_uev(rbsp, &pps->slice_group_change_rate_minus1); + break; + case 6: + rbsp_uev(rbsp, &pps->pic_size_in_map_units_minus1); + for (i = 0; i < pps->pic_size_in_map_units_minus1; i++) + rbsp_bits(rbsp, + order_base_2(pps->num_slice_groups_minus1 + 1), + &pps->slice_group_id[i]); + break; + default: + break; + } + } + rbsp_uev(rbsp, &pps->num_ref_idx_l0_default_active_minus1); + rbsp_uev(rbsp, &pps->num_ref_idx_l1_default_active_minus1); + rbsp_bit(rbsp, &pps->weighted_pred_flag); + rbsp_bits(rbsp, 2, &pps->weighted_bipred_idc); + rbsp_sev(rbsp, &pps->pic_init_qp_minus26); + rbsp_sev(rbsp, &pps->pic_init_qs_minus26); + rbsp_sev(rbsp, &pps->chroma_qp_index_offset); + rbsp_bit(rbsp, &pps->deblocking_filter_control_present_flag); + rbsp_bit(rbsp, &pps->constrained_intra_pred_flag); + rbsp_bit(rbsp, &pps->redundant_pic_cnt_present_flag); + if (/* more_rbsp_data() */ false) { + rbsp_bit(rbsp, &pps->transform_8x8_mode_flag); + rbsp_bit(rbsp, &pps->pic_scaling_matrix_present_flag); + if (pps->pic_scaling_matrix_present_flag) + rbsp->error = -EINVAL; + rbsp_sev(rbsp, &pps->second_chroma_qp_index_offset); + } +} + +/** + * nal_h264_write_sps() - Write SPS NAL unit into RBSP format + * @dev: device pointer + * @dest: the buffer that is filled with RBSP data + * @n: maximum size of @dest in bytes + * @sps: &struct nal_h264_sps to convert to RBSP + * + * Convert @sps to RBSP data and write it into @dest. + * + * The size of the SPS NAL unit is not known in advance and this function will + * fail, if @dest does not hold sufficient space for the SPS NAL unit. + * + * Return: number of bytes written to @dest or negative error code + */ +ssize_t nal_h264_write_sps(const struct device *dev, + void *dest, size_t n, struct nal_h264_sps *sps) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit = 0; + unsigned int nal_ref_idc = 0; + unsigned int nal_unit_type = SEQUENCE_PARAMETER_SET; + + if (!dest) + return -EINVAL; + + rbsp_init(&rbsp, dest, n, &write); + + nal_h264_write_start_code_prefix(&rbsp); + + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 2, &nal_ref_idc); + rbsp_bits(&rbsp, 5, &nal_unit_type); + + nal_h264_rbsp_sps(&rbsp, sps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_h264_write_sps); + +/** + * nal_h264_read_sps() - Read SPS NAL unit from RBSP format + * @dev: device pointer + * @sps: the &struct nal_h264_sps to fill from the RBSP data + * @src: the buffer that contains the RBSP data + * @n: size of @src in bytes + * + * Read RBSP data from @src and use it to fill @sps. + * + * Return: number of bytes read from @src or negative error code + */ +ssize_t nal_h264_read_sps(const struct device *dev, + struct nal_h264_sps *sps, void *src, size_t n) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit; + unsigned int nal_ref_idc; + unsigned int nal_unit_type; + + if (!src) + return -EINVAL; + + rbsp_init(&rbsp, src, n, &read); + + nal_h264_read_start_code_prefix(&rbsp); + + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 2, &nal_ref_idc); + rbsp_bits(&rbsp, 5, &nal_unit_type); + + if (rbsp.error || + forbidden_zero_bit != 0 || + nal_ref_idc != 0 || + nal_unit_type != SEQUENCE_PARAMETER_SET) + return -EINVAL; + + nal_h264_rbsp_sps(&rbsp, sps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_h264_read_sps); + +/** + * nal_h264_write_pps() - Write PPS NAL unit into RBSP format + * @dev: device pointer + * @dest: the buffer that is filled with RBSP data + * @n: maximum size of @dest in bytes + * @pps: &struct nal_h264_pps to convert to RBSP + * + * Convert @pps to RBSP data and write it into @dest. + * + * The size of the PPS NAL unit is not known in advance and this function will + * fail, if @dest does not hold sufficient space for the PPS NAL unit. + * + * Return: number of bytes written to @dest or negative error code + */ +ssize_t nal_h264_write_pps(const struct device *dev, + void *dest, size_t n, struct nal_h264_pps *pps) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit = 0; + unsigned int nal_ref_idc = 0; + unsigned int nal_unit_type = PICTURE_PARAMETER_SET; + + if (!dest) + return -EINVAL; + + rbsp_init(&rbsp, dest, n, &write); + + nal_h264_write_start_code_prefix(&rbsp); + + /* NAL unit header */ + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 2, &nal_ref_idc); + rbsp_bits(&rbsp, 5, &nal_unit_type); + + nal_h264_rbsp_pps(&rbsp, pps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_h264_write_pps); + +/** + * nal_h264_read_pps() - Read PPS NAL unit from RBSP format + * @dev: device pointer + * @pps: the &struct nal_h264_pps to fill from the RBSP data + * @src: the buffer that contains the RBSP data + * @n: size of @src in bytes + * + * Read RBSP data from @src and use it to fill @pps. + * + * Return: number of bytes read from @src or negative error code + */ +ssize_t nal_h264_read_pps(const struct device *dev, + struct nal_h264_pps *pps, void *src, size_t n) +{ + struct rbsp rbsp; + + if (!src) + return -EINVAL; + + rbsp_init(&rbsp, src, n, &read); + + nal_h264_read_start_code_prefix(&rbsp); + + /* NAL unit header */ + rbsp.pos += 8; + + nal_h264_rbsp_pps(&rbsp, pps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_h264_read_pps); + +/** + * nal_h264_write_filler() - Write filler data RBSP + * @dev: device pointer + * @dest: buffer to fill with filler data + * @n: size of the buffer to fill with filler data + * + * Write a filler data RBSP to @dest with a size of @n bytes and return the + * number of written filler data bytes. + * + * Use this function to generate dummy data in an RBSP data stream that can be + * safely ignored by h264 decoders. + * + * The RBSP format of the filler data is specified in Rec. ITU-T H.264 + * (04/2017) 7.3.2.7 Filler data RBSP syntax. + * + * Return: number of filler data bytes (including marker) or negative error + */ +ssize_t nal_h264_write_filler(const struct device *dev, void *dest, size_t n) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit = 0; + unsigned int nal_ref_idc = 0; + unsigned int nal_unit_type = FILLER_DATA; + + if (!dest) + return -EINVAL; + + rbsp_init(&rbsp, dest, n, &write); + + nal_h264_write_start_code_prefix(&rbsp); + + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 2, &nal_ref_idc); + rbsp_bits(&rbsp, 5, &nal_unit_type); + + nal_h264_write_filler_data(&rbsp); + + rbsp_trailing_bits(&rbsp); + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_h264_write_filler); + +/** + * nal_h264_read_filler() - Read filler data RBSP + * @dev: device pointer + * @src: buffer with RBSP data that is read + * @n: maximum size of src that shall be read + * + * Read a filler data RBSP from @src up to a maximum size of @n bytes and + * return the size of the filler data in bytes including the marker. + * + * This function is used to parse filler data and skip the respective bytes in + * the RBSP data. + * + * The RBSP format of the filler data is specified in Rec. ITU-T H.264 + * (04/2017) 7.3.2.7 Filler data RBSP syntax. + * + * Return: number of filler data bytes (including marker) or negative error + */ +ssize_t nal_h264_read_filler(const struct device *dev, void *src, size_t n) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit; + unsigned int nal_ref_idc; + unsigned int nal_unit_type; + + if (!src) + return -EINVAL; + + rbsp_init(&rbsp, src, n, &read); + + nal_h264_read_start_code_prefix(&rbsp); + + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 2, &nal_ref_idc); + rbsp_bits(&rbsp, 5, &nal_unit_type); + + if (rbsp.error) + return rbsp.error; + if (forbidden_zero_bit != 0 || + nal_ref_idc != 0 || + nal_unit_type != FILLER_DATA) + return -EINVAL; + + nal_h264_read_filler_data(&rbsp); + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_h264_read_filler); diff --git a/drivers/media/platform/allegro-dvt/nal-h264.h b/drivers/media/platform/allegro-dvt/nal-h264.h new file mode 100644 index 000000000..34db07cda --- /dev/null +++ b/drivers/media/platform/allegro-dvt/nal-h264.h @@ -0,0 +1,404 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter + * + * Convert NAL units between raw byte sequence payloads (RBSP) and C structs. + */ + +#ifndef __NAL_H264_H__ +#define __NAL_H264_H__ + +#include +#include +#include +#include +#include + +/* + * struct nal_h264_hrd_parameters - HRD parameters + * + * C struct representation of the sequence parameter set NAL unit as defined by + * Rec. ITU-T H.264 (04/2017) E.1.2 HRD parameters syntax. + */ +struct nal_h264_hrd_parameters { + unsigned int cpb_cnt_minus1; + unsigned int bit_rate_scale; + unsigned int cpb_size_scale; + struct { + int bit_rate_value_minus1[16]; + int cpb_size_value_minus1[16]; + unsigned int cbr_flag[16]; + }; + unsigned int initial_cpb_removal_delay_length_minus1; + unsigned int cpb_removal_delay_length_minus1; + unsigned int dpb_output_delay_length_minus1; + unsigned int time_offset_length; +}; + +/* + * struct nal_h264_vui_parameters - VUI parameters + * + * C struct representation of the VUI parameters as defined by Rec. ITU-T + * H.264 (04/2017) E.1.1 VUI parameters syntax. + */ +struct nal_h264_vui_parameters { + unsigned int aspect_ratio_info_present_flag; + struct { + unsigned int aspect_ratio_idc; + unsigned int sar_width; + unsigned int sar_height; + }; + unsigned int overscan_info_present_flag; + unsigned int overscan_appropriate_flag; + unsigned int video_signal_type_present_flag; + struct { + unsigned int video_format; + unsigned int video_full_range_flag; + unsigned int colour_description_present_flag; + struct { + unsigned int colour_primaries; + unsigned int transfer_characteristics; + unsigned int matrix_coefficients; + }; + }; + unsigned int chroma_loc_info_present_flag; + struct { + unsigned int chroma_sample_loc_type_top_field; + unsigned int chroma_sample_loc_type_bottom_field; + }; + unsigned int timing_info_present_flag; + struct { + unsigned int num_units_in_tick; + unsigned int time_scale; + unsigned int fixed_frame_rate_flag; + }; + unsigned int nal_hrd_parameters_present_flag; + struct nal_h264_hrd_parameters nal_hrd_parameters; + unsigned int vcl_hrd_parameters_present_flag; + struct nal_h264_hrd_parameters vcl_hrd_parameters; + unsigned int low_delay_hrd_flag; + unsigned int pic_struct_present_flag; + unsigned int bitstream_restriction_flag; + struct { + unsigned int motion_vectors_over_pic_boundaries_flag; + unsigned int max_bytes_per_pic_denom; + unsigned int max_bits_per_mb_denom; + unsigned int log2_max_mv_length_horizontal; + unsigned int log21_max_mv_length_vertical; + unsigned int max_num_reorder_frames; + unsigned int max_dec_frame_buffering; + }; +}; + +/* + * struct nal_h264_sps - Sequence parameter set + * + * C struct representation of the sequence parameter set NAL unit as defined by + * Rec. ITU-T H.264 (04/2017) 7.3.2.1.1 Sequence parameter set data syntax. + */ +struct nal_h264_sps { + unsigned int profile_idc; + unsigned int constraint_set0_flag; + unsigned int constraint_set1_flag; + unsigned int constraint_set2_flag; + unsigned int constraint_set3_flag; + unsigned int constraint_set4_flag; + unsigned int constraint_set5_flag; + unsigned int reserved_zero_2bits; + unsigned int level_idc; + unsigned int seq_parameter_set_id; + struct { + unsigned int chroma_format_idc; + unsigned int separate_colour_plane_flag; + unsigned int bit_depth_luma_minus8; + unsigned int bit_depth_chroma_minus8; + unsigned int qpprime_y_zero_transform_bypass_flag; + unsigned int seq_scaling_matrix_present_flag; + }; + unsigned int log2_max_frame_num_minus4; + unsigned int pic_order_cnt_type; + union { + unsigned int log2_max_pic_order_cnt_lsb_minus4; + struct { + unsigned int delta_pic_order_always_zero_flag; + int offset_for_non_ref_pic; + int offset_for_top_to_bottom_field; + unsigned int num_ref_frames_in_pic_order_cnt_cycle; + int offset_for_ref_frame[255]; + }; + }; + unsigned int max_num_ref_frames; + unsigned int gaps_in_frame_num_value_allowed_flag; + unsigned int pic_width_in_mbs_minus1; + unsigned int pic_height_in_map_units_minus1; + unsigned int frame_mbs_only_flag; + unsigned int mb_adaptive_frame_field_flag; + unsigned int direct_8x8_inference_flag; + unsigned int frame_cropping_flag; + struct { + unsigned int crop_left; + unsigned int crop_right; + unsigned int crop_top; + unsigned int crop_bottom; + }; + unsigned int vui_parameters_present_flag; + struct nal_h264_vui_parameters vui; +}; + +/* + * struct nal_h264_pps - Picture parameter set + * + * C struct representation of the picture parameter set NAL unit as defined by + * Rec. ITU-T H.264 (04/2017) 7.3.2.2 Picture parameter set RBSP syntax. + */ +struct nal_h264_pps { + unsigned int pic_parameter_set_id; + unsigned int seq_parameter_set_id; + unsigned int entropy_coding_mode_flag; + unsigned int bottom_field_pic_order_in_frame_present_flag; + unsigned int num_slice_groups_minus1; + unsigned int slice_group_map_type; + union { + unsigned int run_length_minus1[8]; + struct { + unsigned int top_left[8]; + unsigned int bottom_right[8]; + }; + struct { + unsigned int slice_group_change_direction_flag; + unsigned int slice_group_change_rate_minus1; + }; + struct { + unsigned int pic_size_in_map_units_minus1; + unsigned int slice_group_id[8]; + }; + }; + unsigned int num_ref_idx_l0_default_active_minus1; + unsigned int num_ref_idx_l1_default_active_minus1; + unsigned int weighted_pred_flag; + unsigned int weighted_bipred_idc; + int pic_init_qp_minus26; + int pic_init_qs_minus26; + int chroma_qp_index_offset; + unsigned int deblocking_filter_control_present_flag; + unsigned int constrained_intra_pred_flag; + unsigned int redundant_pic_cnt_present_flag; + struct { + unsigned int transform_8x8_mode_flag; + unsigned int pic_scaling_matrix_present_flag; + int second_chroma_qp_index_offset; + }; +}; + +/** + * nal_h264_profile() - Get profile_idc for v4l2 h264 profile + * @profile: the profile as &enum v4l2_mpeg_video_h264_profile + * + * Convert the &enum v4l2_mpeg_video_h264_profile to profile_idc as specified + * in Rec. ITU-T H.264 (04/2017) A.2. + * + * Return: the profile_idc for the passed level + */ +static inline int nal_h264_profile(enum v4l2_mpeg_video_h264_profile profile) +{ + switch (profile) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + return 66; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + return 77; + case V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED: + return 88; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + return 100; + default: + return -EINVAL; + } +} + +/** + * nal_h264_level() - Get level_idc for v4l2 h264 level + * @level: the level as &enum v4l2_mpeg_video_h264_level + * + * Convert the &enum v4l2_mpeg_video_h264_level to level_idc as specified in + * Rec. ITU-T H.264 (04/2017) A.3.2. + * + * Return: the level_idc for the passed level + */ +static inline int nal_h264_level(enum v4l2_mpeg_video_h264_level level) +{ + switch (level) { + case V4L2_MPEG_VIDEO_H264_LEVEL_1_0: + return 10; + case V4L2_MPEG_VIDEO_H264_LEVEL_1B: + return 9; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_1: + return 11; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_2: + return 12; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_3: + return 13; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_0: + return 20; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_1: + return 21; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_2: + return 22; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_0: + return 30; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: + return 31; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: + return 32; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + return 40; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + return 41; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + return 42; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_0: + return 50; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_1: + return 51; + default: + return -EINVAL; + } +} + +/** + * nal_h264_full_range() - Get video_full_range_flag for v4l2 quantization + * @quantization: the quantization type as &enum v4l2_quantization + * + * Convert the &enum v4l2_quantization to video_full_range_flag as specified in + * Rec. ITU-T H.264 (04/2017) E.2.1. + * + * Return: the video_full_range_flag value for the passed quantization + */ +static inline int nal_h264_full_range(enum v4l2_quantization quantization) +{ + switch (quantization) { + case V4L2_QUANTIZATION_FULL_RANGE: + return 1; + case V4L2_QUANTIZATION_LIM_RANGE: + return 0; + default: + break; + } + + return 0; +} + +/** + * nal_h264_color_primaries() - Get color_primaries for v4l2 colorspace + * @colorspace: the color space as &enum v4l2_colorspace + * + * Convert the &enum v4l2_colorspace to color_primaries as specified in + * Rec. ITU-T H.264 (04/2017) E.2.1. + * + * Return: the color_primaries value for the passed colorspace + */ +static inline int nal_h264_color_primaries(enum v4l2_colorspace colorspace) +{ + switch (colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + return 6; + case V4L2_COLORSPACE_SMPTE240M: + return 7; + case V4L2_COLORSPACE_REC709: + return 1; + case V4L2_COLORSPACE_470_SYSTEM_M: + return 4; + case V4L2_COLORSPACE_JPEG: + case V4L2_COLORSPACE_SRGB: + case V4L2_COLORSPACE_470_SYSTEM_BG: + return 5; + case V4L2_COLORSPACE_BT2020: + return 9; + case V4L2_COLORSPACE_DEFAULT: + case V4L2_COLORSPACE_OPRGB: + case V4L2_COLORSPACE_RAW: + case V4L2_COLORSPACE_DCI_P3: + default: + return 2; + } +} + +/** + * nal_h264_transfer_characteristics() - Get transfer_characteristics for v4l2 xfer_func + * @colorspace: the color space as &enum v4l2_colorspace + * @xfer_func: the transfer function as &enum v4l2_xfer_func + * + * Convert the &enum v4l2_xfer_func to transfer_characteristics as specified in + * Rec. ITU-T H.264 (04/2017) E.2.1. + * + * Return: the transfer_characteristics value for the passed transfer function + */ +static inline int nal_h264_transfer_characteristics(enum v4l2_colorspace colorspace, + enum v4l2_xfer_func xfer_func) +{ + if (xfer_func == V4L2_XFER_FUNC_DEFAULT) + xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(colorspace); + + switch (xfer_func) { + case V4L2_XFER_FUNC_709: + return 6; + case V4L2_XFER_FUNC_SMPTE2084: + return 16; + case V4L2_XFER_FUNC_SRGB: + case V4L2_XFER_FUNC_OPRGB: + case V4L2_XFER_FUNC_NONE: + case V4L2_XFER_FUNC_DCI_P3: + case V4L2_XFER_FUNC_SMPTE240M: + default: + return 2; + } +} + +/** + * nal_h264_matrix_coeffs() - Get matrix_coefficients for v4l2 v4l2_ycbcr_encoding + * @colorspace: the color space as &enum v4l2_colorspace + * @ycbcr_encoding: the ycbcr encoding as &enum v4l2_ycbcr_encoding + * + * Convert the &enum v4l2_ycbcr_encoding to matrix_coefficients as specified in + * Rec. ITU-T H.264 (04/2017) E.2.1. + * + * Return: the matrix_coefficients value for the passed encoding + */ +static inline int nal_h264_matrix_coeffs(enum v4l2_colorspace colorspace, + enum v4l2_ycbcr_encoding ycbcr_encoding) +{ + if (ycbcr_encoding == V4L2_YCBCR_ENC_DEFAULT) + ycbcr_encoding = V4L2_MAP_YCBCR_ENC_DEFAULT(colorspace); + + switch (ycbcr_encoding) { + case V4L2_YCBCR_ENC_601: + case V4L2_YCBCR_ENC_XV601: + return 5; + case V4L2_YCBCR_ENC_709: + case V4L2_YCBCR_ENC_XV709: + return 1; + case V4L2_YCBCR_ENC_BT2020: + return 9; + case V4L2_YCBCR_ENC_BT2020_CONST_LUM: + return 10; + case V4L2_YCBCR_ENC_SMPTE240M: + default: + return 2; + } +} + +ssize_t nal_h264_write_sps(const struct device *dev, + void *dest, size_t n, struct nal_h264_sps *sps); +ssize_t nal_h264_read_sps(const struct device *dev, + struct nal_h264_sps *sps, void *src, size_t n); +void nal_h264_print_sps(const struct device *dev, struct nal_h264_sps *sps); + +ssize_t nal_h264_write_pps(const struct device *dev, + void *dest, size_t n, struct nal_h264_pps *pps); +ssize_t nal_h264_read_pps(const struct device *dev, + struct nal_h264_pps *pps, void *src, size_t n); +void nal_h264_print_pps(const struct device *dev, struct nal_h264_pps *pps); + +ssize_t nal_h264_write_filler(const struct device *dev, void *dest, size_t n); +ssize_t nal_h264_read_filler(const struct device *dev, void *src, size_t n); + +#endif /* __NAL_H264_H__ */ diff --git a/drivers/media/platform/allegro-dvt/nal-hevc.c b/drivers/media/platform/allegro-dvt/nal-hevc.c new file mode 100644 index 000000000..9cdf2756e --- /dev/null +++ b/drivers/media/platform/allegro-dvt/nal-hevc.c @@ -0,0 +1,884 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2020 Pengutronix, Michael Tretter + * + * Convert NAL units between raw byte sequence payloads (RBSP) and C structs. + * + * The conversion is defined in "ITU-T Rec. H.265 (02/2018) high efficiency + * video coding". Decoder drivers may use the parser to parse RBSP from + * encoded streams and configure the hardware, if the hardware is not able to + * parse RBSP itself. Encoder drivers may use the generator to generate the + * RBSP for VPS/SPS/PPS nal units and add them to the encoded stream if the + * hardware does not generate the units. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "nal-hevc.h" +#include "nal-rbsp.h" + +/* + * See Rec. ITU-T H.265 (02/2018) Table 7-1 - NAL unit type codes and NAL unit + * type classes + */ +enum nal_unit_type { + VPS_NUT = 32, + SPS_NUT = 33, + PPS_NUT = 34, + FD_NUT = 38, +}; + +static void nal_hevc_write_start_code_prefix(struct rbsp *rbsp) +{ + u8 *p = rbsp->data + DIV_ROUND_UP(rbsp->pos, 8); + int i = 4; + + if (DIV_ROUND_UP(rbsp->pos, 8) + i > rbsp->size) { + rbsp->error = -EINVAL; + return; + } + + p[0] = 0x00; + p[1] = 0x00; + p[2] = 0x00; + p[3] = 0x01; + + rbsp->pos += i * 8; +} + +static void nal_hevc_read_start_code_prefix(struct rbsp *rbsp) +{ + u8 *p = rbsp->data + DIV_ROUND_UP(rbsp->pos, 8); + int i = 4; + + if (DIV_ROUND_UP(rbsp->pos, 8) + i > rbsp->size) { + rbsp->error = -EINVAL; + return; + } + + if (p[0] != 0x00 || p[1] != 0x00 || p[2] != 0x00 || p[3] != 0x01) { + rbsp->error = -EINVAL; + return; + } + + rbsp->pos += i * 8; +} + +static void nal_hevc_write_filler_data(struct rbsp *rbsp) +{ + u8 *p = rbsp->data + DIV_ROUND_UP(rbsp->pos, 8); + int i; + + /* Keep 1 byte extra for terminating the NAL unit */ + i = rbsp->size - DIV_ROUND_UP(rbsp->pos, 8) - 1; + memset(p, 0xff, i); + rbsp->pos += i * 8; +} + +static void nal_hevc_read_filler_data(struct rbsp *rbsp) +{ + u8 *p = rbsp->data + DIV_ROUND_UP(rbsp->pos, 8); + + while (*p == 0xff) { + if (DIV_ROUND_UP(rbsp->pos, 8) > rbsp->size) { + rbsp->error = -EINVAL; + return; + } + + p++; + rbsp->pos += 8; + } +} + +static void nal_hevc_rbsp_profile_tier_level(struct rbsp *rbsp, + struct nal_hevc_profile_tier_level *ptl) +{ + unsigned int i; + unsigned int max_num_sub_layers_minus_1 = 0; + + rbsp_bits(rbsp, 2, &ptl->general_profile_space); + rbsp_bit(rbsp, &ptl->general_tier_flag); + rbsp_bits(rbsp, 5, &ptl->general_profile_idc); + for (i = 0; i < 32; i++) + rbsp_bit(rbsp, &ptl->general_profile_compatibility_flag[i]); + rbsp_bit(rbsp, &ptl->general_progressive_source_flag); + rbsp_bit(rbsp, &ptl->general_interlaced_source_flag); + rbsp_bit(rbsp, &ptl->general_non_packed_constraint_flag); + rbsp_bit(rbsp, &ptl->general_frame_only_constraint_flag); + if (ptl->general_profile_idc == 4 || + ptl->general_profile_compatibility_flag[4] || + ptl->general_profile_idc == 5 || + ptl->general_profile_compatibility_flag[5] || + ptl->general_profile_idc == 6 || + ptl->general_profile_compatibility_flag[6] || + ptl->general_profile_idc == 7 || + ptl->general_profile_compatibility_flag[7] || + ptl->general_profile_idc == 8 || + ptl->general_profile_compatibility_flag[8] || + ptl->general_profile_idc == 9 || + ptl->general_profile_compatibility_flag[9] || + ptl->general_profile_idc == 10 || + ptl->general_profile_compatibility_flag[10]) { + rbsp_bit(rbsp, &ptl->general_max_12bit_constraint_flag); + rbsp_bit(rbsp, &ptl->general_max_10bit_constraint_flag); + rbsp_bit(rbsp, &ptl->general_max_8bit_constraint_flag); + rbsp_bit(rbsp, &ptl->general_max_422chroma_constraint_flag); + rbsp_bit(rbsp, &ptl->general_max_420chroma_constraint_flag); + rbsp_bit(rbsp, &ptl->general_max_monochrome_constraint_flag); + rbsp_bit(rbsp, &ptl->general_intra_constraint_flag); + rbsp_bit(rbsp, &ptl->general_one_picture_only_constraint_flag); + rbsp_bit(rbsp, &ptl->general_lower_bit_rate_constraint_flag); + if (ptl->general_profile_idc == 5 || + ptl->general_profile_compatibility_flag[5] || + ptl->general_profile_idc == 9 || + ptl->general_profile_compatibility_flag[9] || + ptl->general_profile_idc == 10 || + ptl->general_profile_compatibility_flag[10]) { + rbsp_bit(rbsp, &ptl->general_max_14bit_constraint_flag); + rbsp_bits(rbsp, 32, &ptl->general_reserved_zero_33bits); + rbsp_bits(rbsp, 33 - 32, &ptl->general_reserved_zero_33bits); + } else { + rbsp_bits(rbsp, 32, &ptl->general_reserved_zero_34bits); + rbsp_bits(rbsp, 34 - 2, &ptl->general_reserved_zero_34bits); + } + } else if (ptl->general_profile_idc == 2 || + ptl->general_profile_compatibility_flag[2]) { + rbsp_bits(rbsp, 7, &ptl->general_reserved_zero_7bits); + rbsp_bit(rbsp, &ptl->general_one_picture_only_constraint_flag); + rbsp_bits(rbsp, 32, &ptl->general_reserved_zero_35bits); + rbsp_bits(rbsp, 35 - 32, &ptl->general_reserved_zero_35bits); + } else { + rbsp_bits(rbsp, 32, &ptl->general_reserved_zero_43bits); + rbsp_bits(rbsp, 43 - 32, &ptl->general_reserved_zero_43bits); + } + if ((ptl->general_profile_idc >= 1 && ptl->general_profile_idc <= 5) || + ptl->general_profile_idc == 9 || + ptl->general_profile_compatibility_flag[1] || + ptl->general_profile_compatibility_flag[2] || + ptl->general_profile_compatibility_flag[3] || + ptl->general_profile_compatibility_flag[4] || + ptl->general_profile_compatibility_flag[5] || + ptl->general_profile_compatibility_flag[9]) + rbsp_bit(rbsp, &ptl->general_inbld_flag); + else + rbsp_bit(rbsp, &ptl->general_reserved_zero_bit); + rbsp_bits(rbsp, 8, &ptl->general_level_idc); + if (max_num_sub_layers_minus_1 > 0) + rbsp_unsupported(rbsp); +} + +static void nal_hevc_rbsp_vps(struct rbsp *rbsp, struct nal_hevc_vps *vps) +{ + unsigned int i, j; + unsigned int reserved_0xffff_16bits = 0xffff; + + rbsp_bits(rbsp, 4, &vps->video_parameter_set_id); + rbsp_bit(rbsp, &vps->base_layer_internal_flag); + rbsp_bit(rbsp, &vps->base_layer_available_flag); + rbsp_bits(rbsp, 6, &vps->max_layers_minus1); + rbsp_bits(rbsp, 3, &vps->max_sub_layers_minus1); + rbsp_bits(rbsp, 1, &vps->temporal_id_nesting_flag); + rbsp_bits(rbsp, 16, &reserved_0xffff_16bits); + nal_hevc_rbsp_profile_tier_level(rbsp, &vps->profile_tier_level); + rbsp_bit(rbsp, &vps->sub_layer_ordering_info_present_flag); + for (i = vps->sub_layer_ordering_info_present_flag ? 0 : vps->max_sub_layers_minus1; + i <= vps->max_sub_layers_minus1; i++) { + rbsp_uev(rbsp, &vps->max_dec_pic_buffering_minus1[i]); + rbsp_uev(rbsp, &vps->max_num_reorder_pics[i]); + rbsp_uev(rbsp, &vps->max_latency_increase_plus1[i]); + } + rbsp_bits(rbsp, 6, &vps->max_layer_id); + rbsp_uev(rbsp, &vps->num_layer_sets_minus1); + for (i = 0; i <= vps->num_layer_sets_minus1; i++) + for (j = 0; j <= vps->max_layer_id; j++) + rbsp_bit(rbsp, &vps->layer_id_included_flag[i][j]); + rbsp_bit(rbsp, &vps->timing_info_present_flag); + if (vps->timing_info_present_flag) + rbsp_unsupported(rbsp); + rbsp_bit(rbsp, &vps->extension_flag); + if (vps->extension_flag) + rbsp_unsupported(rbsp); +} + +static void nal_hevc_rbsp_sub_layer_hrd_parameters(struct rbsp *rbsp, + struct nal_hevc_sub_layer_hrd_parameters *hrd) +{ + unsigned int i; + unsigned int cpb_cnt = 1; + + for (i = 0; i < cpb_cnt; i++) { + rbsp_uev(rbsp, &hrd->bit_rate_value_minus1[i]); + rbsp_uev(rbsp, &hrd->cpb_size_value_minus1[i]); + rbsp_bit(rbsp, &hrd->cbr_flag[i]); + } +} + +static void nal_hevc_rbsp_hrd_parameters(struct rbsp *rbsp, + struct nal_hevc_hrd_parameters *hrd) +{ + unsigned int i; + unsigned int max_num_sub_layers_minus_1 = 0; + + rbsp_bit(rbsp, &hrd->nal_hrd_parameters_present_flag); + rbsp_bit(rbsp, &hrd->vcl_hrd_parameters_present_flag); + if (hrd->nal_hrd_parameters_present_flag || hrd->vcl_hrd_parameters_present_flag) { + rbsp_bit(rbsp, &hrd->sub_pic_hrd_params_present_flag); + if (hrd->sub_pic_hrd_params_present_flag) { + rbsp_bits(rbsp, 8, &hrd->tick_divisor_minus2); + rbsp_bits(rbsp, 5, &hrd->du_cpb_removal_delay_increment_length_minus1); + rbsp_bit(rbsp, &hrd->sub_pic_cpb_params_in_pic_timing_sei_flag); + rbsp_bits(rbsp, 5, &hrd->dpb_output_delay_du_length_minus1); + } + rbsp_bits(rbsp, 4, &hrd->bit_rate_scale); + rbsp_bits(rbsp, 4, &hrd->cpb_size_scale); + if (hrd->sub_pic_hrd_params_present_flag) + rbsp_bits(rbsp, 4, &hrd->cpb_size_du_scale); + rbsp_bits(rbsp, 5, &hrd->initial_cpb_removal_delay_length_minus1); + rbsp_bits(rbsp, 5, &hrd->au_cpb_removal_delay_length_minus1); + rbsp_bits(rbsp, 5, &hrd->dpb_output_delay_length_minus1); + } + for (i = 0; i <= max_num_sub_layers_minus_1; i++) { + rbsp_bit(rbsp, &hrd->fixed_pic_rate_general_flag[i]); + if (!hrd->fixed_pic_rate_general_flag[i]) + rbsp_bit(rbsp, &hrd->fixed_pic_rate_within_cvs_flag[i]); + if (hrd->fixed_pic_rate_within_cvs_flag[i]) + rbsp_uev(rbsp, &hrd->elemental_duration_in_tc_minus1[i]); + else + rbsp_bit(rbsp, &hrd->low_delay_hrd_flag[i]); + if (!hrd->low_delay_hrd_flag[i]) + rbsp_uev(rbsp, &hrd->cpb_cnt_minus1[i]); + if (hrd->nal_hrd_parameters_present_flag) + nal_hevc_rbsp_sub_layer_hrd_parameters(rbsp, &hrd->vcl_hrd[i]); + if (hrd->vcl_hrd_parameters_present_flag) + nal_hevc_rbsp_sub_layer_hrd_parameters(rbsp, &hrd->vcl_hrd[i]); + } +} + +static void nal_hevc_rbsp_vui_parameters(struct rbsp *rbsp, + struct nal_hevc_vui_parameters *vui) +{ + if (!vui) { + rbsp->error = -EINVAL; + return; + } + + rbsp_bit(rbsp, &vui->aspect_ratio_info_present_flag); + if (vui->aspect_ratio_info_present_flag) { + rbsp_bits(rbsp, 8, &vui->aspect_ratio_idc); + if (vui->aspect_ratio_idc == 255) { + rbsp_bits(rbsp, 16, &vui->sar_width); + rbsp_bits(rbsp, 16, &vui->sar_height); + } + } + + rbsp_bit(rbsp, &vui->overscan_info_present_flag); + if (vui->overscan_info_present_flag) + rbsp_bit(rbsp, &vui->overscan_appropriate_flag); + + rbsp_bit(rbsp, &vui->video_signal_type_present_flag); + if (vui->video_signal_type_present_flag) { + rbsp_bits(rbsp, 3, &vui->video_format); + rbsp_bit(rbsp, &vui->video_full_range_flag); + + rbsp_bit(rbsp, &vui->colour_description_present_flag); + if (vui->colour_description_present_flag) { + rbsp_bits(rbsp, 8, &vui->colour_primaries); + rbsp_bits(rbsp, 8, &vui->transfer_characteristics); + rbsp_bits(rbsp, 8, &vui->matrix_coeffs); + } + } + + rbsp_bit(rbsp, &vui->chroma_loc_info_present_flag); + if (vui->chroma_loc_info_present_flag) { + rbsp_uev(rbsp, &vui->chroma_sample_loc_type_top_field); + rbsp_uev(rbsp, &vui->chroma_sample_loc_type_bottom_field); + } + + rbsp_bit(rbsp, &vui->neutral_chroma_indication_flag); + rbsp_bit(rbsp, &vui->field_seq_flag); + rbsp_bit(rbsp, &vui->frame_field_info_present_flag); + rbsp_bit(rbsp, &vui->default_display_window_flag); + if (vui->default_display_window_flag) { + rbsp_uev(rbsp, &vui->def_disp_win_left_offset); + rbsp_uev(rbsp, &vui->def_disp_win_right_offset); + rbsp_uev(rbsp, &vui->def_disp_win_top_offset); + rbsp_uev(rbsp, &vui->def_disp_win_bottom_offset); + } + + rbsp_bit(rbsp, &vui->vui_timing_info_present_flag); + if (vui->vui_timing_info_present_flag) { + rbsp_bits(rbsp, 32, &vui->vui_num_units_in_tick); + rbsp_bits(rbsp, 32, &vui->vui_time_scale); + rbsp_bit(rbsp, &vui->vui_poc_proportional_to_timing_flag); + if (vui->vui_poc_proportional_to_timing_flag) + rbsp_uev(rbsp, &vui->vui_num_ticks_poc_diff_one_minus1); + rbsp_bit(rbsp, &vui->vui_hrd_parameters_present_flag); + if (vui->vui_hrd_parameters_present_flag) + nal_hevc_rbsp_hrd_parameters(rbsp, &vui->nal_hrd_parameters); + } + + rbsp_bit(rbsp, &vui->bitstream_restriction_flag); + if (vui->bitstream_restriction_flag) { + rbsp_bit(rbsp, &vui->tiles_fixed_structure_flag); + rbsp_bit(rbsp, &vui->motion_vectors_over_pic_boundaries_flag); + rbsp_bit(rbsp, &vui->restricted_ref_pic_lists_flag); + rbsp_uev(rbsp, &vui->min_spatial_segmentation_idc); + rbsp_uev(rbsp, &vui->max_bytes_per_pic_denom); + rbsp_uev(rbsp, &vui->max_bits_per_min_cu_denom); + rbsp_uev(rbsp, &vui->log2_max_mv_length_horizontal); + rbsp_uev(rbsp, &vui->log2_max_mv_length_vertical); + } +} + +static void nal_hevc_rbsp_sps(struct rbsp *rbsp, struct nal_hevc_sps *sps) +{ + unsigned int i; + + rbsp_bits(rbsp, 4, &sps->video_parameter_set_id); + rbsp_bits(rbsp, 3, &sps->max_sub_layers_minus1); + rbsp_bit(rbsp, &sps->temporal_id_nesting_flag); + nal_hevc_rbsp_profile_tier_level(rbsp, &sps->profile_tier_level); + rbsp_uev(rbsp, &sps->seq_parameter_set_id); + + rbsp_uev(rbsp, &sps->chroma_format_idc); + if (sps->chroma_format_idc == 3) + rbsp_bit(rbsp, &sps->separate_colour_plane_flag); + rbsp_uev(rbsp, &sps->pic_width_in_luma_samples); + rbsp_uev(rbsp, &sps->pic_height_in_luma_samples); + rbsp_bit(rbsp, &sps->conformance_window_flag); + if (sps->conformance_window_flag) { + rbsp_uev(rbsp, &sps->conf_win_left_offset); + rbsp_uev(rbsp, &sps->conf_win_right_offset); + rbsp_uev(rbsp, &sps->conf_win_top_offset); + rbsp_uev(rbsp, &sps->conf_win_bottom_offset); + } + rbsp_uev(rbsp, &sps->bit_depth_luma_minus8); + rbsp_uev(rbsp, &sps->bit_depth_chroma_minus8); + + rbsp_uev(rbsp, &sps->log2_max_pic_order_cnt_lsb_minus4); + + rbsp_bit(rbsp, &sps->sub_layer_ordering_info_present_flag); + for (i = (sps->sub_layer_ordering_info_present_flag ? 0 : sps->max_sub_layers_minus1); + i <= sps->max_sub_layers_minus1; i++) { + rbsp_uev(rbsp, &sps->max_dec_pic_buffering_minus1[i]); + rbsp_uev(rbsp, &sps->max_num_reorder_pics[i]); + rbsp_uev(rbsp, &sps->max_latency_increase_plus1[i]); + } + rbsp_uev(rbsp, &sps->log2_min_luma_coding_block_size_minus3); + rbsp_uev(rbsp, &sps->log2_diff_max_min_luma_coding_block_size); + rbsp_uev(rbsp, &sps->log2_min_luma_transform_block_size_minus2); + rbsp_uev(rbsp, &sps->log2_diff_max_min_luma_transform_block_size); + rbsp_uev(rbsp, &sps->max_transform_hierarchy_depth_inter); + rbsp_uev(rbsp, &sps->max_transform_hierarchy_depth_intra); + + rbsp_bit(rbsp, &sps->scaling_list_enabled_flag); + if (sps->scaling_list_enabled_flag) + rbsp_unsupported(rbsp); + + rbsp_bit(rbsp, &sps->amp_enabled_flag); + rbsp_bit(rbsp, &sps->sample_adaptive_offset_enabled_flag); + rbsp_bit(rbsp, &sps->pcm_enabled_flag); + if (sps->pcm_enabled_flag) { + rbsp_bits(rbsp, 4, &sps->pcm_sample_bit_depth_luma_minus1); + rbsp_bits(rbsp, 4, &sps->pcm_sample_bit_depth_chroma_minus1); + rbsp_uev(rbsp, &sps->log2_min_pcm_luma_coding_block_size_minus3); + rbsp_uev(rbsp, &sps->log2_diff_max_min_pcm_luma_coding_block_size); + rbsp_bit(rbsp, &sps->pcm_loop_filter_disabled_flag); + } + + rbsp_uev(rbsp, &sps->num_short_term_ref_pic_sets); + if (sps->num_short_term_ref_pic_sets > 0) + rbsp_unsupported(rbsp); + + rbsp_bit(rbsp, &sps->long_term_ref_pics_present_flag); + if (sps->long_term_ref_pics_present_flag) + rbsp_unsupported(rbsp); + + rbsp_bit(rbsp, &sps->sps_temporal_mvp_enabled_flag); + rbsp_bit(rbsp, &sps->strong_intra_smoothing_enabled_flag); + rbsp_bit(rbsp, &sps->vui_parameters_present_flag); + if (sps->vui_parameters_present_flag) + nal_hevc_rbsp_vui_parameters(rbsp, &sps->vui); + + rbsp_bit(rbsp, &sps->extension_present_flag); + if (sps->extension_present_flag) { + rbsp_bit(rbsp, &sps->sps_range_extension_flag); + rbsp_bit(rbsp, &sps->sps_multilayer_extension_flag); + rbsp_bit(rbsp, &sps->sps_3d_extension_flag); + rbsp_bit(rbsp, &sps->sps_scc_extension_flag); + rbsp_bits(rbsp, 5, &sps->sps_extension_4bits); + } + if (sps->sps_range_extension_flag) + rbsp_unsupported(rbsp); + if (sps->sps_multilayer_extension_flag) + rbsp_unsupported(rbsp); + if (sps->sps_3d_extension_flag) + rbsp_unsupported(rbsp); + if (sps->sps_scc_extension_flag) + rbsp_unsupported(rbsp); + if (sps->sps_extension_4bits) + rbsp_unsupported(rbsp); +} + +static void nal_hevc_rbsp_pps(struct rbsp *rbsp, struct nal_hevc_pps *pps) +{ + unsigned int i; + + rbsp_uev(rbsp, &pps->pps_pic_parameter_set_id); + rbsp_uev(rbsp, &pps->pps_seq_parameter_set_id); + rbsp_bit(rbsp, &pps->dependent_slice_segments_enabled_flag); + rbsp_bit(rbsp, &pps->output_flag_present_flag); + rbsp_bits(rbsp, 3, &pps->num_extra_slice_header_bits); + rbsp_bit(rbsp, &pps->sign_data_hiding_enabled_flag); + rbsp_bit(rbsp, &pps->cabac_init_present_flag); + rbsp_uev(rbsp, &pps->num_ref_idx_l0_default_active_minus1); + rbsp_uev(rbsp, &pps->num_ref_idx_l1_default_active_minus1); + rbsp_sev(rbsp, &pps->init_qp_minus26); + rbsp_bit(rbsp, &pps->constrained_intra_pred_flag); + rbsp_bit(rbsp, &pps->transform_skip_enabled_flag); + rbsp_bit(rbsp, &pps->cu_qp_delta_enabled_flag); + if (pps->cu_qp_delta_enabled_flag) + rbsp_uev(rbsp, &pps->diff_cu_qp_delta_depth); + rbsp_sev(rbsp, &pps->pps_cb_qp_offset); + rbsp_sev(rbsp, &pps->pps_cr_qp_offset); + rbsp_bit(rbsp, &pps->pps_slice_chroma_qp_offsets_present_flag); + rbsp_bit(rbsp, &pps->weighted_pred_flag); + rbsp_bit(rbsp, &pps->weighted_bipred_flag); + rbsp_bit(rbsp, &pps->transquant_bypass_enabled_flag); + rbsp_bit(rbsp, &pps->tiles_enabled_flag); + rbsp_bit(rbsp, &pps->entropy_coding_sync_enabled_flag); + if (pps->tiles_enabled_flag) { + rbsp_uev(rbsp, &pps->num_tile_columns_minus1); + rbsp_uev(rbsp, &pps->num_tile_rows_minus1); + rbsp_bit(rbsp, &pps->uniform_spacing_flag); + if (!pps->uniform_spacing_flag) { + for (i = 0; i < pps->num_tile_columns_minus1; i++) + rbsp_uev(rbsp, &pps->column_width_minus1[i]); + for (i = 0; i < pps->num_tile_rows_minus1; i++) + rbsp_uev(rbsp, &pps->row_height_minus1[i]); + } + rbsp_bit(rbsp, &pps->loop_filter_across_tiles_enabled_flag); + } + rbsp_bit(rbsp, &pps->pps_loop_filter_across_slices_enabled_flag); + rbsp_bit(rbsp, &pps->deblocking_filter_control_present_flag); + if (pps->deblocking_filter_control_present_flag) { + rbsp_bit(rbsp, &pps->deblocking_filter_override_enabled_flag); + rbsp_bit(rbsp, &pps->pps_deblocking_filter_disabled_flag); + if (!pps->pps_deblocking_filter_disabled_flag) { + rbsp_sev(rbsp, &pps->pps_beta_offset_div2); + rbsp_sev(rbsp, &pps->pps_tc_offset_div2); + } + } + rbsp_bit(rbsp, &pps->pps_scaling_list_data_present_flag); + if (pps->pps_scaling_list_data_present_flag) + rbsp_unsupported(rbsp); + rbsp_bit(rbsp, &pps->lists_modification_present_flag); + rbsp_uev(rbsp, &pps->log2_parallel_merge_level_minus2); + rbsp_bit(rbsp, &pps->slice_segment_header_extension_present_flag); + rbsp_bit(rbsp, &pps->pps_extension_present_flag); + if (pps->pps_extension_present_flag) { + rbsp_bit(rbsp, &pps->pps_range_extension_flag); + rbsp_bit(rbsp, &pps->pps_multilayer_extension_flag); + rbsp_bit(rbsp, &pps->pps_3d_extension_flag); + rbsp_bit(rbsp, &pps->pps_scc_extension_flag); + rbsp_bits(rbsp, 4, &pps->pps_extension_4bits); + } + if (pps->pps_range_extension_flag) + rbsp_unsupported(rbsp); + if (pps->pps_multilayer_extension_flag) + rbsp_unsupported(rbsp); + if (pps->pps_3d_extension_flag) + rbsp_unsupported(rbsp); + if (pps->pps_scc_extension_flag) + rbsp_unsupported(rbsp); + if (pps->pps_extension_4bits) + rbsp_unsupported(rbsp); +} + +/** + * nal_hevc_write_vps() - Write PPS NAL unit into RBSP format + * @dev: device pointer + * @dest: the buffer that is filled with RBSP data + * @n: maximum size of @dest in bytes + * @vps: &struct nal_hevc_vps to convert to RBSP + * + * Convert @vps to RBSP data and write it into @dest. + * + * The size of the VPS NAL unit is not known in advance and this function will + * fail, if @dest does not hold sufficient space for the VPS NAL unit. + * + * Return: number of bytes written to @dest or negative error code + */ +ssize_t nal_hevc_write_vps(const struct device *dev, + void *dest, size_t n, struct nal_hevc_vps *vps) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit = 0; + unsigned int nal_unit_type = VPS_NUT; + unsigned int nuh_layer_id = 0; + unsigned int nuh_temporal_id_plus1 = 1; + + if (!dest) + return -EINVAL; + + rbsp_init(&rbsp, dest, n, &write); + + nal_hevc_write_start_code_prefix(&rbsp); + + /* NAL unit header */ + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 6, &nal_unit_type); + rbsp_bits(&rbsp, 6, &nuh_layer_id); + rbsp_bits(&rbsp, 3, &nuh_temporal_id_plus1); + + nal_hevc_rbsp_vps(&rbsp, vps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_hevc_write_vps); + +/** + * nal_hevc_read_vps() - Read VPS NAL unit from RBSP format + * @dev: device pointer + * @vps: the &struct nal_hevc_vps to fill from the RBSP data + * @src: the buffer that contains the RBSP data + * @n: size of @src in bytes + * + * Read RBSP data from @src and use it to fill @vps. + * + * Return: number of bytes read from @src or negative error code + */ +ssize_t nal_hevc_read_vps(const struct device *dev, + struct nal_hevc_vps *vps, void *src, size_t n) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit; + unsigned int nal_unit_type; + unsigned int nuh_layer_id; + unsigned int nuh_temporal_id_plus1; + + if (!src) + return -EINVAL; + + rbsp_init(&rbsp, src, n, &read); + + nal_hevc_read_start_code_prefix(&rbsp); + + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 6, &nal_unit_type); + rbsp_bits(&rbsp, 6, &nuh_layer_id); + rbsp_bits(&rbsp, 3, &nuh_temporal_id_plus1); + + if (rbsp.error || + forbidden_zero_bit != 0 || + nal_unit_type != VPS_NUT) + return -EINVAL; + + nal_hevc_rbsp_vps(&rbsp, vps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_hevc_read_vps); + +/** + * nal_hevc_write_sps() - Write SPS NAL unit into RBSP format + * @dev: device pointer + * @dest: the buffer that is filled with RBSP data + * @n: maximum size of @dest in bytes + * @sps: &struct nal_hevc_sps to convert to RBSP + * + * Convert @sps to RBSP data and write it into @dest. + * + * The size of the SPS NAL unit is not known in advance and this function will + * fail, if @dest does not hold sufficient space for the SPS NAL unit. + * + * Return: number of bytes written to @dest or negative error code + */ +ssize_t nal_hevc_write_sps(const struct device *dev, + void *dest, size_t n, struct nal_hevc_sps *sps) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit = 0; + unsigned int nal_unit_type = SPS_NUT; + unsigned int nuh_layer_id = 0; + unsigned int nuh_temporal_id_plus1 = 1; + + if (!dest) + return -EINVAL; + + rbsp_init(&rbsp, dest, n, &write); + + nal_hevc_write_start_code_prefix(&rbsp); + + /* NAL unit header */ + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 6, &nal_unit_type); + rbsp_bits(&rbsp, 6, &nuh_layer_id); + rbsp_bits(&rbsp, 3, &nuh_temporal_id_plus1); + + nal_hevc_rbsp_sps(&rbsp, sps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_hevc_write_sps); + +/** + * nal_hevc_read_sps() - Read SPS NAL unit from RBSP format + * @dev: device pointer + * @sps: the &struct nal_hevc_sps to fill from the RBSP data + * @src: the buffer that contains the RBSP data + * @n: size of @src in bytes + * + * Read RBSP data from @src and use it to fill @sps. + * + * Return: number of bytes read from @src or negative error code + */ +ssize_t nal_hevc_read_sps(const struct device *dev, + struct nal_hevc_sps *sps, void *src, size_t n) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit; + unsigned int nal_unit_type; + unsigned int nuh_layer_id; + unsigned int nuh_temporal_id_plus1; + + if (!src) + return -EINVAL; + + rbsp_init(&rbsp, src, n, &read); + + nal_hevc_read_start_code_prefix(&rbsp); + + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 6, &nal_unit_type); + rbsp_bits(&rbsp, 6, &nuh_layer_id); + rbsp_bits(&rbsp, 3, &nuh_temporal_id_plus1); + + if (rbsp.error || + forbidden_zero_bit != 0 || + nal_unit_type != SPS_NUT) + return -EINVAL; + + nal_hevc_rbsp_sps(&rbsp, sps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_hevc_read_sps); + +/** + * nal_hevc_write_pps() - Write PPS NAL unit into RBSP format + * @dev: device pointer + * @dest: the buffer that is filled with RBSP data + * @n: maximum size of @dest in bytes + * @pps: &struct nal_hevc_pps to convert to RBSP + * + * Convert @pps to RBSP data and write it into @dest. + * + * The size of the PPS NAL unit is not known in advance and this function will + * fail, if @dest does not hold sufficient space for the PPS NAL unit. + * + * Return: number of bytes written to @dest or negative error code + */ +ssize_t nal_hevc_write_pps(const struct device *dev, + void *dest, size_t n, struct nal_hevc_pps *pps) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit = 0; + unsigned int nal_unit_type = PPS_NUT; + unsigned int nuh_layer_id = 0; + unsigned int nuh_temporal_id_plus1 = 1; + + if (!dest) + return -EINVAL; + + rbsp_init(&rbsp, dest, n, &write); + + nal_hevc_write_start_code_prefix(&rbsp); + + /* NAL unit header */ + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 6, &nal_unit_type); + rbsp_bits(&rbsp, 6, &nuh_layer_id); + rbsp_bits(&rbsp, 3, &nuh_temporal_id_plus1); + + nal_hevc_rbsp_pps(&rbsp, pps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_hevc_write_pps); + +/** + * nal_hevc_read_pps() - Read PPS NAL unit from RBSP format + * @dev: device pointer + * @pps: the &struct nal_hevc_pps to fill from the RBSP data + * @src: the buffer that contains the RBSP data + * @n: size of @src in bytes + * + * Read RBSP data from @src and use it to fill @pps. + * + * Return: number of bytes read from @src or negative error code + */ +ssize_t nal_hevc_read_pps(const struct device *dev, + struct nal_hevc_pps *pps, void *src, size_t n) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit; + unsigned int nal_unit_type; + unsigned int nuh_layer_id; + unsigned int nuh_temporal_id_plus1; + + if (!src) + return -EINVAL; + + rbsp_init(&rbsp, src, n, &read); + + nal_hevc_read_start_code_prefix(&rbsp); + + /* NAL unit header */ + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 6, &nal_unit_type); + rbsp_bits(&rbsp, 6, &nuh_layer_id); + rbsp_bits(&rbsp, 3, &nuh_temporal_id_plus1); + + nal_hevc_rbsp_pps(&rbsp, pps); + + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_hevc_read_pps); + +/** + * nal_hevc_write_filler() - Write filler data RBSP + * @dev: device pointer + * @dest: buffer to fill with filler data + * @n: size of the buffer to fill with filler data + * + * Write a filler data RBSP to @dest with a size of @n bytes and return the + * number of written filler data bytes. + * + * Use this function to generate dummy data in an RBSP data stream that can be + * safely ignored by hevc decoders. + * + * The RBSP format of the filler data is specified in Rec. ITU-T H.265 + * (02/2018) 7.3.2.8 Filler data RBSP syntax. + * + * Return: number of filler data bytes (including marker) or negative error + */ +ssize_t nal_hevc_write_filler(const struct device *dev, void *dest, size_t n) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit = 0; + unsigned int nal_unit_type = FD_NUT; + unsigned int nuh_layer_id = 0; + unsigned int nuh_temporal_id_plus1 = 1; + + if (!dest) + return -EINVAL; + + rbsp_init(&rbsp, dest, n, &write); + + nal_hevc_write_start_code_prefix(&rbsp); + + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 6, &nal_unit_type); + rbsp_bits(&rbsp, 6, &nuh_layer_id); + rbsp_bits(&rbsp, 3, &nuh_temporal_id_plus1); + + nal_hevc_write_filler_data(&rbsp); + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_hevc_write_filler); + +/** + * nal_hevc_read_filler() - Read filler data RBSP + * @dev: device pointer + * @src: buffer with RBSP data that is read + * @n: maximum size of src that shall be read + * + * Read a filler data RBSP from @src up to a maximum size of @n bytes and + * return the size of the filler data in bytes including the marker. + * + * This function is used to parse filler data and skip the respective bytes in + * the RBSP data. + * + * The RBSP format of the filler data is specified in Rec. ITU-T H.265 + * (02/2018) 7.3.2.8 Filler data RBSP syntax. + * + * Return: number of filler data bytes (including marker) or negative error + */ +ssize_t nal_hevc_read_filler(const struct device *dev, void *src, size_t n) +{ + struct rbsp rbsp; + unsigned int forbidden_zero_bit; + unsigned int nal_unit_type; + unsigned int nuh_layer_id; + unsigned int nuh_temporal_id_plus1; + + if (!src) + return -EINVAL; + + rbsp_init(&rbsp, src, n, &read); + + nal_hevc_read_start_code_prefix(&rbsp); + + rbsp_bit(&rbsp, &forbidden_zero_bit); + rbsp_bits(&rbsp, 6, &nal_unit_type); + rbsp_bits(&rbsp, 6, &nuh_layer_id); + rbsp_bits(&rbsp, 3, &nuh_temporal_id_plus1); + + if (rbsp.error) + return rbsp.error; + if (forbidden_zero_bit != 0 || + nal_unit_type != FD_NUT) + return -EINVAL; + + nal_hevc_read_filler_data(&rbsp); + rbsp_trailing_bits(&rbsp); + + if (rbsp.error) + return rbsp.error; + + return DIV_ROUND_UP(rbsp.pos, 8); +} +EXPORT_SYMBOL_GPL(nal_hevc_read_filler); diff --git a/drivers/media/platform/allegro-dvt/nal-hevc.h b/drivers/media/platform/allegro-dvt/nal-hevc.h new file mode 100644 index 000000000..eb46f12aa --- /dev/null +++ b/drivers/media/platform/allegro-dvt/nal-hevc.h @@ -0,0 +1,519 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019 Pengutronix, Michael Tretter + * + * Convert NAL units between raw byte sequence payloads (RBSP) and C structs. + */ + +#ifndef __NAL_HEVC_H__ +#define __NAL_HEVC_H__ + +#include +#include +#include +#include +#include + +struct nal_hevc_profile_tier_level { + unsigned int general_profile_space; + unsigned int general_tier_flag; + unsigned int general_profile_idc; + unsigned int general_profile_compatibility_flag[32]; + unsigned int general_progressive_source_flag; + unsigned int general_interlaced_source_flag; + unsigned int general_non_packed_constraint_flag; + unsigned int general_frame_only_constraint_flag; + union { + struct { + unsigned int general_max_12bit_constraint_flag; + unsigned int general_max_10bit_constraint_flag; + unsigned int general_max_8bit_constraint_flag; + unsigned int general_max_422chroma_constraint_flag; + unsigned int general_max_420chroma_constraint_flag; + unsigned int general_max_monochrome_constraint_flag; + unsigned int general_intra_constraint_flag; + unsigned int general_one_picture_only_constraint_flag; + unsigned int general_lower_bit_rate_constraint_flag; + union { + struct { + unsigned int general_max_14bit_constraint_flag; + unsigned int general_reserved_zero_33bits; + }; + unsigned int general_reserved_zero_34bits; + }; + }; + struct { + unsigned int general_reserved_zero_7bits; + /* unsigned int general_one_picture_only_constraint_flag; */ + unsigned int general_reserved_zero_35bits; + }; + unsigned int general_reserved_zero_43bits; + }; + union { + unsigned int general_inbld_flag; + unsigned int general_reserved_zero_bit; + }; + unsigned int general_level_idc; +}; + +/* + * struct nal_hevc_vps - Video parameter set + * + * C struct representation of the video parameter set NAL unit as defined by + * Rec. ITU-T H.265 (02/2018) 7.3.2.1 Video parameter set RBSP syntax + */ +struct nal_hevc_vps { + unsigned int video_parameter_set_id; + unsigned int base_layer_internal_flag; + unsigned int base_layer_available_flag; + unsigned int max_layers_minus1; + unsigned int max_sub_layers_minus1; + unsigned int temporal_id_nesting_flag; + struct nal_hevc_profile_tier_level profile_tier_level; + unsigned int sub_layer_ordering_info_present_flag; + struct { + unsigned int max_dec_pic_buffering_minus1[7]; + unsigned int max_num_reorder_pics[7]; + unsigned int max_latency_increase_plus1[7]; + }; + unsigned int max_layer_id; + unsigned int num_layer_sets_minus1; + unsigned int layer_id_included_flag[1024][64]; + unsigned int timing_info_present_flag; + struct { + unsigned int num_units_in_tick; + unsigned int time_scale; + unsigned int poc_proportional_to_timing_flag; + unsigned int num_ticks_poc_diff_one_minus1; + unsigned int num_hrd_parameters; + struct { + unsigned int hrd_layer_set_idx[0]; + unsigned int cprms_present_flag[0]; + }; + /* hrd_parameters( cprms_present_flag[ i ], max_sub_layers_minus1 ) */ + }; + unsigned int extension_flag; + unsigned int extension_data_flag; +}; + +struct nal_hevc_sub_layer_hrd_parameters { + unsigned int bit_rate_value_minus1[1]; + unsigned int cpb_size_value_minus1[1]; + unsigned int cbr_flag[1]; +}; + +struct nal_hevc_hrd_parameters { + unsigned int nal_hrd_parameters_present_flag; + unsigned int vcl_hrd_parameters_present_flag; + struct { + unsigned int sub_pic_hrd_params_present_flag; + struct { + unsigned int tick_divisor_minus2; + unsigned int du_cpb_removal_delay_increment_length_minus1; + unsigned int sub_pic_cpb_params_in_pic_timing_sei_flag; + unsigned int dpb_output_delay_du_length_minus1; + }; + unsigned int bit_rate_scale; + unsigned int cpb_size_scale; + unsigned int cpb_size_du_scale; + unsigned int initial_cpb_removal_delay_length_minus1; + unsigned int au_cpb_removal_delay_length_minus1; + unsigned int dpb_output_delay_length_minus1; + }; + struct { + unsigned int fixed_pic_rate_general_flag[1]; + unsigned int fixed_pic_rate_within_cvs_flag[1]; + unsigned int elemental_duration_in_tc_minus1[1]; + unsigned int low_delay_hrd_flag[1]; + unsigned int cpb_cnt_minus1[1]; + struct nal_hevc_sub_layer_hrd_parameters nal_hrd[1]; + struct nal_hevc_sub_layer_hrd_parameters vcl_hrd[1]; + }; +}; + +/* + * struct nal_hevc_vui_parameters - VUI parameters + * + * C struct representation of the VUI parameters as defined by Rec. ITU-T + * H.265 (02/2018) E.2.1 VUI parameters syntax. + */ +struct nal_hevc_vui_parameters { + unsigned int aspect_ratio_info_present_flag; + struct { + unsigned int aspect_ratio_idc; + unsigned int sar_width; + unsigned int sar_height; + }; + unsigned int overscan_info_present_flag; + unsigned int overscan_appropriate_flag; + unsigned int video_signal_type_present_flag; + struct { + unsigned int video_format; + unsigned int video_full_range_flag; + unsigned int colour_description_present_flag; + struct { + unsigned int colour_primaries; + unsigned int transfer_characteristics; + unsigned int matrix_coeffs; + }; + }; + unsigned int chroma_loc_info_present_flag; + struct { + unsigned int chroma_sample_loc_type_top_field; + unsigned int chroma_sample_loc_type_bottom_field; + }; + unsigned int neutral_chroma_indication_flag; + unsigned int field_seq_flag; + unsigned int frame_field_info_present_flag; + unsigned int default_display_window_flag; + struct { + unsigned int def_disp_win_left_offset; + unsigned int def_disp_win_right_offset; + unsigned int def_disp_win_top_offset; + unsigned int def_disp_win_bottom_offset; + }; + unsigned int vui_timing_info_present_flag; + struct { + unsigned int vui_num_units_in_tick; + unsigned int vui_time_scale; + unsigned int vui_poc_proportional_to_timing_flag; + unsigned int vui_num_ticks_poc_diff_one_minus1; + unsigned int vui_hrd_parameters_present_flag; + struct nal_hevc_hrd_parameters nal_hrd_parameters; + }; + unsigned int bitstream_restriction_flag; + struct { + unsigned int tiles_fixed_structure_flag; + unsigned int motion_vectors_over_pic_boundaries_flag; + unsigned int restricted_ref_pic_lists_flag; + unsigned int min_spatial_segmentation_idc; + unsigned int max_bytes_per_pic_denom; + unsigned int max_bits_per_min_cu_denom; + unsigned int log2_max_mv_length_horizontal; + unsigned int log2_max_mv_length_vertical; + }; +}; + +/* + * struct nal_hevc_sps - Sequence parameter set + * + * C struct representation of the video parameter set NAL unit as defined by + * Rec. ITU-T H.265 (02/2018) 7.3.2.2 Sequence parameter set RBSP syntax + */ +struct nal_hevc_sps { + unsigned int video_parameter_set_id; + unsigned int max_sub_layers_minus1; + unsigned int temporal_id_nesting_flag; + struct nal_hevc_profile_tier_level profile_tier_level; + unsigned int seq_parameter_set_id; + unsigned int chroma_format_idc; + unsigned int separate_colour_plane_flag; + unsigned int pic_width_in_luma_samples; + unsigned int pic_height_in_luma_samples; + unsigned int conformance_window_flag; + struct { + unsigned int conf_win_left_offset; + unsigned int conf_win_right_offset; + unsigned int conf_win_top_offset; + unsigned int conf_win_bottom_offset; + }; + + unsigned int bit_depth_luma_minus8; + unsigned int bit_depth_chroma_minus8; + unsigned int log2_max_pic_order_cnt_lsb_minus4; + unsigned int sub_layer_ordering_info_present_flag; + struct { + unsigned int max_dec_pic_buffering_minus1[7]; + unsigned int max_num_reorder_pics[7]; + unsigned int max_latency_increase_plus1[7]; + }; + unsigned int log2_min_luma_coding_block_size_minus3; + unsigned int log2_diff_max_min_luma_coding_block_size; + unsigned int log2_min_luma_transform_block_size_minus2; + unsigned int log2_diff_max_min_luma_transform_block_size; + unsigned int max_transform_hierarchy_depth_inter; + unsigned int max_transform_hierarchy_depth_intra; + + unsigned int scaling_list_enabled_flag; + unsigned int scaling_list_data_present_flag; + unsigned int amp_enabled_flag; + unsigned int sample_adaptive_offset_enabled_flag; + unsigned int pcm_enabled_flag; + struct { + unsigned int pcm_sample_bit_depth_luma_minus1; + unsigned int pcm_sample_bit_depth_chroma_minus1; + unsigned int log2_min_pcm_luma_coding_block_size_minus3; + unsigned int log2_diff_max_min_pcm_luma_coding_block_size; + unsigned int pcm_loop_filter_disabled_flag; + }; + + unsigned int num_short_term_ref_pic_sets; + unsigned int long_term_ref_pics_present_flag; + unsigned int sps_temporal_mvp_enabled_flag; + unsigned int strong_intra_smoothing_enabled_flag; + unsigned int vui_parameters_present_flag; + struct nal_hevc_vui_parameters vui; + unsigned int extension_present_flag; + struct { + unsigned int sps_range_extension_flag; + unsigned int sps_multilayer_extension_flag; + unsigned int sps_3d_extension_flag; + unsigned int sps_scc_extension_flag; + unsigned int sps_extension_4bits; + }; +}; + +struct nal_hevc_pps { + unsigned int pps_pic_parameter_set_id; + unsigned int pps_seq_parameter_set_id; + unsigned int dependent_slice_segments_enabled_flag; + unsigned int output_flag_present_flag; + unsigned int num_extra_slice_header_bits; + unsigned int sign_data_hiding_enabled_flag; + unsigned int cabac_init_present_flag; + unsigned int num_ref_idx_l0_default_active_minus1; + unsigned int num_ref_idx_l1_default_active_minus1; + int init_qp_minus26; + unsigned int constrained_intra_pred_flag; + unsigned int transform_skip_enabled_flag; + unsigned int cu_qp_delta_enabled_flag; + unsigned int diff_cu_qp_delta_depth; + int pps_cb_qp_offset; + int pps_cr_qp_offset; + unsigned int pps_slice_chroma_qp_offsets_present_flag; + unsigned int weighted_pred_flag; + unsigned int weighted_bipred_flag; + unsigned int transquant_bypass_enabled_flag; + unsigned int tiles_enabled_flag; + unsigned int entropy_coding_sync_enabled_flag; + struct { + unsigned int num_tile_columns_minus1; + unsigned int num_tile_rows_minus1; + unsigned int uniform_spacing_flag; + struct { + unsigned int column_width_minus1[1]; + unsigned int row_height_minus1[1]; + }; + unsigned int loop_filter_across_tiles_enabled_flag; + }; + unsigned int pps_loop_filter_across_slices_enabled_flag; + unsigned int deblocking_filter_control_present_flag; + struct { + unsigned int deblocking_filter_override_enabled_flag; + unsigned int pps_deblocking_filter_disabled_flag; + struct { + int pps_beta_offset_div2; + int pps_tc_offset_div2; + }; + }; + unsigned int pps_scaling_list_data_present_flag; + unsigned int lists_modification_present_flag; + unsigned int log2_parallel_merge_level_minus2; + unsigned int slice_segment_header_extension_present_flag; + unsigned int pps_extension_present_flag; + struct { + unsigned int pps_range_extension_flag; + unsigned int pps_multilayer_extension_flag; + unsigned int pps_3d_extension_flag; + unsigned int pps_scc_extension_flag; + unsigned int pps_extension_4bits; + }; +}; + +/** + * nal_hevc_profile() - Get profile_idc for v4l2 hevc profile + * @profile: the profile as &enum v4l2_mpeg_video_hevc_profile + * + * Convert the &enum v4l2_mpeg_video_hevc_profile to profile_idc as specified + * in Rec. ITU-T H.265 (02/2018) A.3. + * + * Return: the profile_idc for the passed level + */ +static inline int nal_hevc_profile(enum v4l2_mpeg_video_hevc_profile profile) +{ + switch (profile) { + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN: + return 1; + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10: + return 2; + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE: + return 3; + default: + return -EINVAL; + } +} + +/** + * nal_hevc_tier() - Get tier_flag for v4l2 hevc tier + * @tier: the tier as &enum v4l2_mpeg_video_hevc_tier + * + * Convert the &enum v4l2_mpeg_video_hevc_tier to tier_flag as specified + * in Rec. ITU-T H.265 (02/2018) A.4.1. + * + * Return: the tier_flag for the passed tier + */ +static inline int nal_hevc_tier(enum v4l2_mpeg_video_hevc_tier tier) +{ + switch (tier) { + case V4L2_MPEG_VIDEO_HEVC_TIER_MAIN: + return 0; + case V4L2_MPEG_VIDEO_HEVC_TIER_HIGH: + return 1; + default: + return -EINVAL; + } +} + +/** + * nal_hevc_level() - Get level_idc for v4l2 hevc level + * @level: the level as &enum v4l2_mpeg_video_hevc_level + * + * Convert the &enum v4l2_mpeg_video_hevc_level to level_idc as specified in + * Rec. ITU-T H.265 (02/2018) A.4.1. + * + * Return: the level_idc for the passed level + */ +static inline int nal_hevc_level(enum v4l2_mpeg_video_hevc_level level) +{ + /* + * T-Rec-H.265 p. 280: general_level_idc and sub_layer_level_idc[ i ] + * shall be set equal to a value of 30 times the level number + * specified in Table A.6. + */ + int factor = 30 / 10; + + switch (level) { + case V4L2_MPEG_VIDEO_HEVC_LEVEL_1: + return factor * 10; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_2: + return factor * 20; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1: + return factor * 21; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_3: + return factor * 30; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1: + return factor * 31; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_4: + return factor * 40; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1: + return factor * 41; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5: + return factor * 50; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1: + return factor * 51; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_5_2: + return factor * 52; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_6: + return factor * 60; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1: + return factor * 61; + case V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2: + return factor * 62; + default: + return -EINVAL; + } +} + +static inline int nal_hevc_full_range(enum v4l2_quantization quantization) +{ + switch (quantization) { + case V4L2_QUANTIZATION_FULL_RANGE: + return 1; + case V4L2_QUANTIZATION_LIM_RANGE: + return 0; + default: + break; + } + + return 0; +} + +static inline int nal_hevc_color_primaries(enum v4l2_colorspace colorspace) +{ + switch (colorspace) { + case V4L2_COLORSPACE_SMPTE170M: + return 6; + case V4L2_COLORSPACE_SMPTE240M: + return 7; + case V4L2_COLORSPACE_REC709: + return 1; + case V4L2_COLORSPACE_470_SYSTEM_M: + return 4; + case V4L2_COLORSPACE_JPEG: + case V4L2_COLORSPACE_SRGB: + case V4L2_COLORSPACE_470_SYSTEM_BG: + return 5; + case V4L2_COLORSPACE_BT2020: + return 9; + case V4L2_COLORSPACE_DEFAULT: + case V4L2_COLORSPACE_OPRGB: + case V4L2_COLORSPACE_RAW: + case V4L2_COLORSPACE_DCI_P3: + default: + return 2; + } +} + +static inline int nal_hevc_transfer_characteristics(enum v4l2_colorspace colorspace, + enum v4l2_xfer_func xfer_func) +{ + if (xfer_func == V4L2_XFER_FUNC_DEFAULT) + xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(colorspace); + + switch (xfer_func) { + case V4L2_XFER_FUNC_709: + return 6; + case V4L2_XFER_FUNC_SMPTE2084: + return 16; + case V4L2_XFER_FUNC_SRGB: + case V4L2_XFER_FUNC_OPRGB: + case V4L2_XFER_FUNC_NONE: + case V4L2_XFER_FUNC_DCI_P3: + case V4L2_XFER_FUNC_SMPTE240M: + default: + return 2; + } +} + +static inline int nal_hevc_matrix_coeffs(enum v4l2_colorspace colorspace, + enum v4l2_ycbcr_encoding ycbcr_encoding) +{ + if (ycbcr_encoding == V4L2_YCBCR_ENC_DEFAULT) + ycbcr_encoding = V4L2_MAP_YCBCR_ENC_DEFAULT(colorspace); + + switch (ycbcr_encoding) { + case V4L2_YCBCR_ENC_601: + case V4L2_YCBCR_ENC_XV601: + return 5; + case V4L2_YCBCR_ENC_709: + case V4L2_YCBCR_ENC_XV709: + return 1; + case V4L2_YCBCR_ENC_BT2020: + return 9; + case V4L2_YCBCR_ENC_BT2020_CONST_LUM: + return 10; + case V4L2_YCBCR_ENC_SMPTE240M: + default: + return 2; + } +} + +ssize_t nal_hevc_write_vps(const struct device *dev, + void *dest, size_t n, struct nal_hevc_vps *vps); +ssize_t nal_hevc_read_vps(const struct device *dev, + struct nal_hevc_vps *vps, void *src, size_t n); + +ssize_t nal_hevc_write_sps(const struct device *dev, + void *dest, size_t n, struct nal_hevc_sps *sps); +ssize_t nal_hevc_read_sps(const struct device *dev, + struct nal_hevc_sps *sps, void *src, size_t n); + +ssize_t nal_hevc_write_pps(const struct device *dev, + void *dest, size_t n, struct nal_hevc_pps *pps); +ssize_t nal_hevc_read_pps(const struct device *dev, + struct nal_hevc_pps *pps, void *src, size_t n); + +ssize_t nal_hevc_write_filler(const struct device *dev, void *dest, size_t n); +ssize_t nal_hevc_read_filler(const struct device *dev, void *src, size_t n); + +#endif /* __NAL_HEVC_H__ */ diff --git a/drivers/media/platform/allegro-dvt/nal-rbsp.c b/drivers/media/platform/allegro-dvt/nal-rbsp.c new file mode 100644 index 000000000..d911322d0 --- /dev/null +++ b/drivers/media/platform/allegro-dvt/nal-rbsp.c @@ -0,0 +1,310 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019-2020 Pengutronix, Michael Tretter + * + * Helper functions to generate a raw byte sequence payload from values. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "nal-rbsp.h" + +void rbsp_init(struct rbsp *rbsp, void *addr, size_t size, + struct nal_rbsp_ops *ops) +{ + if (!rbsp) + return; + + rbsp->data = addr; + rbsp->size = size; + rbsp->pos = 0; + rbsp->ops = ops; + rbsp->error = 0; +} + +void rbsp_unsupported(struct rbsp *rbsp) +{ + rbsp->error = -EINVAL; +} + +static int rbsp_read_bits(struct rbsp *rbsp, int n, unsigned int *value); +static int rbsp_write_bits(struct rbsp *rbsp, int n, unsigned int value); + +/* + * When reading or writing, the emulation_prevention_three_byte is detected + * only when the 2 one bits need to be inserted. Therefore, we are not + * actually adding the 0x3 byte, but the 2 one bits and the six 0 bits of the + * next byte. + */ +#define EMULATION_PREVENTION_THREE_BYTE (0x3 << 6) + +static int add_emulation_prevention_three_byte(struct rbsp *rbsp) +{ + rbsp->num_consecutive_zeros = 0; + rbsp_write_bits(rbsp, 8, EMULATION_PREVENTION_THREE_BYTE); + + return 0; +} + +static int discard_emulation_prevention_three_byte(struct rbsp *rbsp) +{ + unsigned int tmp = 0; + + rbsp->num_consecutive_zeros = 0; + rbsp_read_bits(rbsp, 8, &tmp); + if (tmp != EMULATION_PREVENTION_THREE_BYTE) + return -EINVAL; + + return 0; +} + +static inline int rbsp_read_bit(struct rbsp *rbsp) +{ + int shift; + int ofs; + int bit; + int err; + + if (rbsp->num_consecutive_zeros == 22) { + err = discard_emulation_prevention_three_byte(rbsp); + if (err) + return err; + } + + shift = 7 - (rbsp->pos % 8); + ofs = rbsp->pos / 8; + if (ofs >= rbsp->size) + return -EINVAL; + + bit = (rbsp->data[ofs] >> shift) & 1; + + rbsp->pos++; + + if (bit == 1 || + (rbsp->num_consecutive_zeros < 7 && (rbsp->pos % 8 == 0))) + rbsp->num_consecutive_zeros = 0; + else + rbsp->num_consecutive_zeros++; + + return bit; +} + +static inline int rbsp_write_bit(struct rbsp *rbsp, bool value) +{ + int shift; + int ofs; + + if (rbsp->num_consecutive_zeros == 22) + add_emulation_prevention_three_byte(rbsp); + + shift = 7 - (rbsp->pos % 8); + ofs = rbsp->pos / 8; + if (ofs >= rbsp->size) + return -EINVAL; + + rbsp->data[ofs] &= ~(1 << shift); + rbsp->data[ofs] |= value << shift; + + rbsp->pos++; + + if (value || + (rbsp->num_consecutive_zeros < 7 && (rbsp->pos % 8 == 0))) { + rbsp->num_consecutive_zeros = 0; + } else { + rbsp->num_consecutive_zeros++; + } + + return 0; +} + +static inline int rbsp_read_bits(struct rbsp *rbsp, int n, unsigned int *value) +{ + int i; + int bit; + unsigned int tmp = 0; + + if (n > 8 * sizeof(*value)) + return -EINVAL; + + for (i = n; i > 0; i--) { + bit = rbsp_read_bit(rbsp); + if (bit < 0) + return bit; + tmp |= bit << (i - 1); + } + + if (value) + *value = tmp; + + return 0; +} + +static int rbsp_write_bits(struct rbsp *rbsp, int n, unsigned int value) +{ + int ret; + + if (n > 8 * sizeof(value)) + return -EINVAL; + + while (n--) { + ret = rbsp_write_bit(rbsp, (value >> n) & 1); + if (ret) + return ret; + } + + return 0; +} + +static int rbsp_read_uev(struct rbsp *rbsp, unsigned int *value) +{ + int leading_zero_bits = 0; + unsigned int tmp = 0; + int ret; + + while ((ret = rbsp_read_bit(rbsp)) == 0) + leading_zero_bits++; + if (ret < 0) + return ret; + + if (leading_zero_bits > 0) { + ret = rbsp_read_bits(rbsp, leading_zero_bits, &tmp); + if (ret) + return ret; + } + + if (value) + *value = (1 << leading_zero_bits) - 1 + tmp; + + return 0; +} + +static int rbsp_write_uev(struct rbsp *rbsp, unsigned int *value) +{ + int ret; + int leading_zero_bits; + + if (!value) + return -EINVAL; + + leading_zero_bits = ilog2(*value + 1); + + ret = rbsp_write_bits(rbsp, leading_zero_bits, 0); + if (ret) + return ret; + + return rbsp_write_bits(rbsp, leading_zero_bits + 1, *value + 1); +} + +static int rbsp_read_sev(struct rbsp *rbsp, int *value) +{ + int ret; + unsigned int tmp; + + ret = rbsp_read_uev(rbsp, &tmp); + if (ret) + return ret; + + if (value) { + if (tmp & 1) + *value = (tmp + 1) / 2; + else + *value = -(tmp / 2); + } + + return 0; +} + +static int rbsp_write_sev(struct rbsp *rbsp, int *value) +{ + unsigned int tmp; + + if (!value) + return -EINVAL; + + if (*value > 0) + tmp = (2 * (*value)) | 1; + else + tmp = -2 * (*value); + + return rbsp_write_uev(rbsp, &tmp); +} + +static int __rbsp_write_bit(struct rbsp *rbsp, int *value) +{ + return rbsp_write_bit(rbsp, *value); +} + +static int __rbsp_write_bits(struct rbsp *rbsp, int n, unsigned int *value) +{ + return rbsp_write_bits(rbsp, n, *value); +} + +struct nal_rbsp_ops write = { + .rbsp_bit = __rbsp_write_bit, + .rbsp_bits = __rbsp_write_bits, + .rbsp_uev = rbsp_write_uev, + .rbsp_sev = rbsp_write_sev, +}; + +static int __rbsp_read_bit(struct rbsp *rbsp, int *value) +{ + int tmp = rbsp_read_bit(rbsp); + + if (tmp < 0) + return tmp; + *value = tmp; + + return 0; +} + +struct nal_rbsp_ops read = { + .rbsp_bit = __rbsp_read_bit, + .rbsp_bits = rbsp_read_bits, + .rbsp_uev = rbsp_read_uev, + .rbsp_sev = rbsp_read_sev, +}; + +void rbsp_bit(struct rbsp *rbsp, int *value) +{ + if (rbsp->error) + return; + rbsp->error = rbsp->ops->rbsp_bit(rbsp, value); +} + +void rbsp_bits(struct rbsp *rbsp, int n, int *value) +{ + if (rbsp->error) + return; + rbsp->error = rbsp->ops->rbsp_bits(rbsp, n, value); +} + +void rbsp_uev(struct rbsp *rbsp, unsigned int *value) +{ + if (rbsp->error) + return; + rbsp->error = rbsp->ops->rbsp_uev(rbsp, value); +} + +void rbsp_sev(struct rbsp *rbsp, int *value) +{ + if (rbsp->error) + return; + rbsp->error = rbsp->ops->rbsp_sev(rbsp, value); +} + +void rbsp_trailing_bits(struct rbsp *rbsp) +{ + unsigned int rbsp_stop_one_bit = 1; + unsigned int rbsp_alignment_zero_bit = 0; + + rbsp_bit(rbsp, &rbsp_stop_one_bit); + rbsp_bits(rbsp, round_up(rbsp->pos, 8) - rbsp->pos, + &rbsp_alignment_zero_bit); +} diff --git a/drivers/media/platform/allegro-dvt/nal-rbsp.h b/drivers/media/platform/allegro-dvt/nal-rbsp.h new file mode 100644 index 000000000..c72f49fed --- /dev/null +++ b/drivers/media/platform/allegro-dvt/nal-rbsp.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2019-2020 Pengutronix, Michael Tretter + */ + +#ifndef __NAL_RBSP_H__ +#define __NAL_RBSP_H__ + +#include +#include + +struct rbsp; + +struct nal_rbsp_ops { + int (*rbsp_bit)(struct rbsp *rbsp, int *val); + int (*rbsp_bits)(struct rbsp *rbsp, int n, unsigned int *val); + int (*rbsp_uev)(struct rbsp *rbsp, unsigned int *val); + int (*rbsp_sev)(struct rbsp *rbsp, int *val); +}; + +/** + * struct rbsp - State object for handling a raw byte sequence payload + * @data: pointer to the data of the rbsp + * @size: maximum size of the data of the rbsp + * @pos: current bit position inside the rbsp + * @num_consecutive_zeros: number of zeros before @pos + * @ops: per datatype functions for interacting with the rbsp + * @error: an error occurred while handling the rbsp + * + * This struct is passed around the various parsing functions and tracks the + * current position within the raw byte sequence payload. + * + * The @ops field allows to separate the operation, i.e., reading/writing a + * value from/to that rbsp, from the structure of the NAL unit. This allows to + * have a single function for iterating the NAL unit, while @ops has function + * pointers for handling each type in the rbsp. + */ +struct rbsp { + u8 *data; + size_t size; + unsigned int pos; + unsigned int num_consecutive_zeros; + struct nal_rbsp_ops *ops; + int error; +}; + +extern struct nal_rbsp_ops write; +extern struct nal_rbsp_ops read; + +void rbsp_init(struct rbsp *rbsp, void *addr, size_t size, + struct nal_rbsp_ops *ops); +void rbsp_unsupported(struct rbsp *rbsp); + +void rbsp_bit(struct rbsp *rbsp, int *value); +void rbsp_bits(struct rbsp *rbsp, int n, int *value); +void rbsp_uev(struct rbsp *rbsp, unsigned int *value); +void rbsp_sev(struct rbsp *rbsp, int *value); + +void rbsp_trailing_bits(struct rbsp *rbsp); + +#endif /* __NAL_RBSP_H__ */ diff --git a/drivers/media/platform/amlogic/Kconfig b/drivers/media/platform/amlogic/Kconfig new file mode 100644 index 000000000..501495740 --- /dev/null +++ b/drivers/media/platform/amlogic/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Amlogic media platform drivers" + +source "drivers/media/platform/amlogic/meson-ge2d/Kconfig" diff --git a/drivers/media/platform/amlogic/Makefile b/drivers/media/platform/amlogic/Makefile new file mode 100644 index 000000000..d3cdb8fa4 --- /dev/null +++ b/drivers/media/platform/amlogic/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += meson-ge2d/ diff --git a/drivers/media/platform/amlogic/meson-ge2d/Kconfig b/drivers/media/platform/amlogic/meson-ge2d/Kconfig new file mode 100644 index 000000000..312c4169e --- /dev/null +++ b/drivers/media/platform/amlogic/meson-ge2d/Kconfig @@ -0,0 +1,14 @@ +config VIDEO_MESON_GE2D + tristate "Amlogic 2D Graphic Acceleration Unit" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_MESON || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a v4l2 driver for Amlogic GE2D 2D graphics accelerator. + GE2D is a standalone 2D graphic acceleration unit, with color converter, + image scaling, BitBLT & alpha blending operations. + + To compile this driver as a module choose m here. + diff --git a/drivers/media/platform/amlogic/meson-ge2d/Makefile b/drivers/media/platform/amlogic/meson-ge2d/Makefile new file mode 100644 index 000000000..450586df2 --- /dev/null +++ b/drivers/media/platform/amlogic/meson-ge2d/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-$(CONFIG_VIDEO_MESON_GE2D) += ge2d.o diff --git a/drivers/media/platform/amlogic/meson-ge2d/ge2d-regs.h b/drivers/media/platform/amlogic/meson-ge2d/ge2d-regs.h new file mode 100644 index 000000000..2a76dd4c0 --- /dev/null +++ b/drivers/media/platform/amlogic/meson-ge2d/ge2d-regs.h @@ -0,0 +1,360 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2020 BayLibre, SAS + * Author: Neil Armstrong + * Copyright (C) 2017 Amlogic, Inc. All rights reserved. + */ + +#ifndef __GE2D_REGS__ +#define __GE2D_REGS__ + +/* Registers starts at (GE2D_REG(0x8a0 * 4) */ +#define GE2D_REG(x) ((0x8a0 + (x)) * 4) + +#define GE2D_GEN_CTRL0 GE2D_REG(0x00) + +#define GE2D_DST_BYTEMASK_ONLY BIT(31) +#define GE2D_DST_BITMASK_EN BIT(30) +#define GE2D_SRC2_KEY_EN BIT(29) +#define GE2D_SRC2_KEY_MODE BIT(28) +#define GE2D_SRC1_KEY_EN BIT(27) +#define GE2D_SRC1_KEY_MODE BIT(26) +#define GE2D_DST1_8B_MODE_SEL GENMASK(25, 24) +#define GE2D_DST_CLIP_MODE BIT(23) +#define GE2D_SRC2_8B_MODE_SEL GENMASK(16, 15) +#define GE2D_SRC2_FILL_MODE BIT(14) +#define GE2D_SRC2_PIC_STRUCT GENMASK(13, 12) +#define GE2D_SRC2_X_YC_RATIO BIT(11) +#define GE2D_SRC1_8B_MODE_SEL GENMASK(6, 5) +#define GE2D_SRC1_FILL_MODE BIT(4) +#define GE2D_SRC1_LUT_EN BIT(3) +#define GE2D_SRC1_PIC_STRUCT GENMASK(2, 1) + +#define GE2D_GEN_CTRL1 GE2D_REG(0x01) + +#define GE2D_SOFT_RST BIT(31) +#define GE2D_DST_WRITE_RESP_CNT_RST BIT(30) +#define GE2D_DST_WRITE_RESP_CNT_ADD_DIS BIT(29) +#define GE2D_COLOR_CONVERSION_MODE1 BIT(26) +#define GE2D_INTERRUPT_CTRL GENMASK(25, 24) +#define GE2D_SRC2_BURST_SIZE_CTRL GENMASK(23, 22) +#define GE2D_SRC1_BURST_SIZE_CTRL GENMASK(21, 16) +#define GE2D_DST1_PIC_STRUCT GENMASK(15, 14) +#define GE2D_SRC_RD_CTRL GENMASK(13, 12) +#define GE2D_DST2_URGENT_EN BIT(11) +#define GE2D_SRC1_URGENT_EN BIT(10) +#define GE2D_SRC2_URGENT_EN BIT(9) +#define GE2D_DST1_URGENT_EN BIT(8) +#define GE2D_SRC1_GB_ALPHA GENMASK(7, 0) + +#define GE2D_GEN_CTRL2 GE2D_REG(0x02) + +#define GE2D_ALPHA_CONVERSION_MODE0 BIT(31) +#define GE2D_COLOR_CONVERSION_MODE0 BIT(30) +#define GE2D_SRC1_GB_ALPHA_EN BIT(29) +#define GE2D_DST1_COLOR_ROUND_MODE BIT(28) +#define GE2D_SRC2_COLOR_EXPAND_MODE BIT(27) +#define GE2D_SRC2_ALPHA_EXPAND_MODE BIT(26) +#define GE2D_SRC1_COLOR_EXPAND_MODE BIT(25) +#define GE2D_SRC1_ALPHA_EXPAND_MODE BIT(24) +#define GE2D_DST_LITTLE_ENDIAN BIT(23) +#define GE2D_DST1_COLOR_MAP GENMASK(22, 19) +#define GE2D_ALU_MULT_MODE BIT(18) +#define GE2D_DST1_FORMAT GENMASK(17, 16) +#define GE2D_SRC2_LITTLE_ENDIAN BIT(15) +#define GE2D_SRC2_COLOR_MAP GENMASK(14, 11) +#define GE2D_ALPHA_CONVERSION_MODE1 BIT(10) +#define GE2D_SRC2_FORMAT GENMASK(9, 8) +#define GE2D_SRC1_LITTLE_ENDIAN BIT(7) +#define GE2D_SRC1_COLOR_MAP GENMASK(6, 3) +#define GE2D_SRC1_DEEPCOLOR BIT(2) +#define GE2D_SRC1_FORMAT GENMASK(1, 0) + +#define GE2D_FORMAT_8BIT 0 +#define GE2D_FORMAT_16BIT 1 +#define GE2D_FORMAT_24BIT 2 +#define GE2D_FORMAT_32BIT 3 + +/* 16 bit */ +#define GE2D_COLOR_MAP_YUV422 0 +#define GE2D_COLOR_MAP_RGB655 1 +#define GE2D_COLOR_MAP_YUV655 1 +#define GE2D_COLOR_MAP_RGB844 2 +#define GE2D_COLOR_MAP_YUV844 2 +#define GE2D_COLOR_MAP_RGBA6442 3 +#define GE2D_COLOR_MAP_YUVA6442 3 +#define GE2D_COLOR_MAP_RGBA4444 4 +#define GE2D_COLOR_MAP_YUVA4444 4 +#define GE2D_COLOR_MAP_RGB565 5 +#define GE2D_COLOR_MAP_YUV565 5 +#define GE2D_COLOR_MAP_ARGB4444 6 +#define GE2D_COLOR_MAP_AYUV4444 6 +#define GE2D_COLOR_MAP_ARGB1555 7 +#define GE2D_COLOR_MAP_AYUV1555 7 +#define GE2D_COLOR_MAP_RGBA4642 8 +#define GE2D_COLOR_MAP_YUVA4642 8 + +/* 24 bit */ +#define GE2D_COLOR_MAP_RGB888 0 +#define GE2D_COLOR_MAP_YUV444 0 +#define GE2D_COLOR_MAP_RGBA5658 1 +#define GE2D_COLOR_MAP_YUVA5658 1 +#define GE2D_COLOR_MAP_ARGB8565 2 +#define GE2D_COLOR_MAP_AYUV8565 2 +#define GE2D_COLOR_MAP_RGBA6666 3 +#define GE2D_COLOR_MAP_YUVA6666 3 +#define GE2D_COLOR_MAP_ARGB6666 4 +#define GE2D_COLOR_MAP_AYUV6666 4 +#define GE2D_COLOR_MAP_BGR888 5 +#define GE2D_COLOR_MAP_VUY888 5 + +/* 32 bit */ +#define GE2D_COLOR_MAP_RGBA8888 0 +#define GE2D_COLOR_MAP_YUVA8888 0 +#define GE2D_COLOR_MAP_ARGB8888 1 +#define GE2D_COLOR_MAP_AYUV8888 1 +#define GE2D_COLOR_MAP_ABGR8888 2 +#define GE2D_COLOR_MAP_AVUY8888 2 +#define GE2D_COLOR_MAP_BGRA8888 3 +#define GE2D_COLOR_MAP_VUYA8888 3 + +#define GE2D_CMD_CTRL GE2D_REG(0x03) + +#define GE2D_SRC2_FILL_COLOR_EN BIT(9) +#define GE2D_SRC1_FILL_COLOR_EN BIT(8) +#define GE2D_DST_XY_SWAP BIT(7) +#define GE2D_DST_X_REV BIT(6) +#define GE2D_DST_Y_REV BIT(5) +#define GE2D_SRC2_X_REV BIT(4) +#define GE2D_SRC2_Y_REV BIT(3) +#define GE2D_SRC1_X_REV BIT(2) +#define GE2D_SRC1_Y_REV BIT(1) +#define GE2D_CBUS_CMD_WR BIT(0) + +#define GE2D_STATUS0 GE2D_REG(0x04) + +#define GE2D_DST_WRITE_RSP_CNT GENMASK(28, 17) +#define GE2D_DP_STATUS GENMASK(16, 7) +#define GE2D_R1CMD_RDY BIT(6) +#define GE2D_R2CMD_RDY BIT(5) +#define GE2D_PDPCMD_VALID BIT(4) +#define GE2D_DPCMD_RDY BIT(3) +#define GE2D_BUF_CMD_VALID BIT(2) +#define GE2D_CURR_CMD_VALID BIT(1) +#define GE2D_GE2D_BUSY BIT(0) + +#define GE2D_STATUS1 GE2D_REG(0x05) + +#define GE2D_WR_DST1_STATUS GENMASK(29, 16) +#define GE2D_RD_SRC2_FIFO_EMPTY BIT(15) +#define GE2D_RD_SRC2_FIFO_OVERFLOW BIT(14) +#define GE2D_RD_SRC2_STATE_Y GENMASK(13, 12) +#define GE2D_RD_SRC2_WIN_ERR BIT(11) +#define GE2D_RD_SRC2_CMD_BUSY BIT(10) +#define GE2D_RD_SRC1_FIFO_EMPTY BIT(9) +#define GE2D_RD_SRC1_FIFO_OVERFLOW BIT(8) +#define GE2D_RD_SRC1_STATE_CR GENMASK(7, 6) +#define GE2D_RD_SRC1_STATE_CB GENMASK(5, 4) +#define GE2D_RD_SRC1_STATE_Y GENMASK(3, 2) +#define GE2D_RD_SRC1_WIN_ERR BIT(1) +#define GE2D_RD_SRC1_CMD_BUSY BIT(0) + +#define GE2D_SRC1_DEF_COLOR GE2D_REG(0x06) + +#define GE2D_COLOR_R_Y GENMASK(31, 24) +#define GE2D_COLOR_B_CB GENMASK(23, 16) +#define GE2D_COLOR_B_CR GENMASK(15, 8) +#define GE2D_COLOR_ALPHA GENMASK(7, 0) + +#define GE2D_SRC1_CLIPX_START_END GE2D_REG(0x07) + +#define GE2D_START_EXTRA BIT(31) /* For GE2D_SRC1_CLIPX/Y_START_END */ +#define GE2D_START_EXTRA0 BIT(30) /* For GE2D_SRC1_X/Y_START_END */ +#define GE2D_START GENMASK(28, 16) +#define GE2D_END_EXTRA BIT(15) /* For GE2D_SRC1_CLIPX/Y_START_END */ +#define GE2D_END_EXTRA0 BIT(14) /* For GE2D_SRC1_X/Y_START_END */ +#define GE2D_END GENMASK(12, 0) + +#define GE2D_SRC1_CLIPY_START_END GE2D_REG(0x08) +#define GE2D_SRC1_CANVAS GE2D_REG(0x09) + +#define GE2D_SRC1_CANVAS_ADDR GENMASK(31, 24) + +#define GE2D_SRC1_X_START_END GE2D_REG(0x0a) +#define GE2D_SRC1_Y_START_END GE2D_REG(0x0b) +#define GE2D_SRC1_LUT_ADDR GE2D_REG(0x0c) + +#define GE2D_LUT_READ BIT(8) +#define GE2D_LUT_ADDR GENMASK(7, 0) + +#define GE2D_SRC1_LUT_DAT GE2D_REG(0x0d) +#define GE2D_SRC1_FMT_CTRL GE2D_REG(0x0e) +#define GE2D_SRC2_DEF_COLOR GE2D_REG(0x0f) +#define GE2D_SRC2_CLIPX_START_END GE2D_REG(0x10) +#define GE2D_SRC2_CLIPY_START_END GE2D_REG(0x11) +#define GE2D_SRC2_X_START_END GE2D_REG(0x12) +#define GE2D_SRC2_Y_START_END GE2D_REG(0x13) +#define GE2D_DST_CLIPX_START_END GE2D_REG(0x14) +#define GE2D_DST_CLIPY_START_END GE2D_REG(0x15) +#define GE2D_DST_X_START_END GE2D_REG(0x16) +#define GE2D_DST_Y_START_END GE2D_REG(0x17) +#define GE2D_SRC2_DST_CANVAS GE2D_REG(0x18) + +#define GE2D_DST2_CANVAS_ADDR GENMASK(23, 16) +#define GE2D_SRC2_CANVAS_ADDR GENMASK(15, 8) +#define GE2D_DST1_CANVAS_ADDR GENMASK(7, 0) + +#define GE2D_VSC_START_PHASE_STEP GE2D_REG(0x19) +#define GE2D_VSC_PHASE_SLOPE GE2D_REG(0x1a) +#define GE2D_VSC_INI_CTRL GE2D_REG(0x1b) +#define GE2D_HSC_START_PHASE_STEP GE2D_REG(0x1c) +#define GE2D_HSC_PHASE_SLOPE GE2D_REG(0x1d) +#define GE2D_HSC_INI_CTRL GE2D_REG(0x1e) +#define GE2D_HSC_ADV_CTRL GE2D_REG(0x1f) +#define GE2D_SC_MISC_CTRL GE2D_REG(0x20) +#define GE2D_VSC_NRND_POINT GE2D_REG(0x21) +#define GE2D_VSC_NRND_PHASE GE2D_REG(0x22) +#define GE2D_HSC_NRND_POINT GE2D_REG(0x23) +#define GE2D_HSC_NRND_PHASE GE2D_REG(0x24) +#define GE2D_MATRIX_PRE_OFFSET GE2D_REG(0x25) +#define GE2D_MATRIX_COEF00_01 GE2D_REG(0x26) +#define GE2D_MATRIX_COEF02_10 GE2D_REG(0x27) +#define GE2D_MATRIX_COEF11_12 GE2D_REG(0x28) +#define GE2D_MATRIX_COEF20_21 GE2D_REG(0x29) +#define GE2D_MATRIX_COEF22_CTRL GE2D_REG(0x2a) +#define GE2D_MATRIX_OFFSET GE2D_REG(0x2b) +#define GE2D_ALU_OP_CTRL GE2D_REG(0x2c) + +#define GE2D_SRC1_COLOR_MULT_ALPHA_SEL GENMASK(26, 25) +#define GE2D_SRC2_COLOR_MULT_ALPHA_SEL BIT(24) +#define GE2D_ALU_BLEND_MODE GENMASK(22, 20) + +#define OPERATION_ADD 0 /* Cd = Cs*Fs+Cd*Fd */ +#define OPERATION_SUB 1 /* Cd = Cs*Fs-Cd*Fd */ +#define OPERATION_REVERSE_SUB 2 /* Cd = Cd*Fd-Cs*Fs */ +#define OPERATION_MIN 3 /* Cd = Min(Cd*Fd,Cs*Fs) */ +#define OPERATION_MAX 4 /* Cd = Max(Cd*Fd,Cs*Fs) */ +#define OPERATION_LOGIC 5 + +#define GE2D_ALU_SRC_COLOR_BLEND_FACTOR GENMASK(19, 16) +#define GE2D_ALU_DST_COLOR_BLEND_FACTOR GENMASK(15, 12) + +#define COLOR_FACTOR_ZERO 0 +#define COLOR_FACTOR_ONE 1 +#define COLOR_FACTOR_SRC_COLOR 2 +#define COLOR_FACTOR_ONE_MINUS_SRC_COLOR 3 +#define COLOR_FACTOR_DST_COLOR 4 +#define COLOR_FACTOR_ONE_MINUS_DST_COLOR 5 +#define COLOR_FACTOR_SRC_ALPHA 6 +#define COLOR_FACTOR_ONE_MINUS_SRC_ALPHA 7 +#define COLOR_FACTOR_DST_ALPHA 8 +#define COLOR_FACTOR_ONE_MINUS_DST_ALPHA 9 +#define COLOR_FACTOR_CONST_COLOR 10 +#define COLOR_FACTOR_ONE_MINUS_CONST_COLOR 11 +#define COLOR_FACTOR_CONST_ALPHA 12 +#define COLOR_FACTOR_ONE_MINUS_CONST_ALPHA 13 +#define COLOR_FACTOR_SRC_ALPHA_SATURATE 14 + +#define GE2D_ALU_OPERATION_LOGIC GENMASK(15, 12) + +#define LOGIC_OPERATION_CLEAR 0 +#define LOGIC_OPERATION_COPY 1 +#define LOGIC_OPERATION_NOOP 2 +#define LOGIC_OPERATION_SET 3 +#define LOGIC_OPERATION_COPY_INVERT 4 +#define LOGIC_OPERATION_INVERT 5 +#define LOGIC_OPERATION_AND_REVERSE 6 +#define LOGIC_OPERATION_OR_REVERSE 7 +#define LOGIC_OPERATION_AND 8 +#define LOGIC_OPERATION_OR 9 +#define LOGIC_OPERATION_NAND 10 +#define LOGIC_OPERATION_NOR 11 +#define LOGIC_OPERATION_XOR 12 +#define LOGIC_OPERATION_EQUIV 13 +#define LOGIC_OPERATION_AND_INVERT 14 +#define LOGIC_OPERATION_OR_INVERT 15 + +#define GE2D_ALU_ALPHA_BLEND_MODE GENMASK(10, 8) +#define GE2D_ALU_SRC_ALPHA_BLEND_FACTOR GENMASK(7, 4) +#define GE2D_ALU_DST_ALPHA_BLEND_FACTOR GENMASK(3, 0) + +#define ALPHA_FACTOR_ZERO 0 +#define ALPHA_FACTOR_ONE 1 +#define ALPHA_FACTOR_SRC_ALPHA 2 +#define ALPHA_FACTOR_ONE_MINUS_SRC_ALPHA 3 +#define ALPHA_FACTOR_DST_ALPHA 4 +#define ALPHA_FACTOR_ONE_MINUS_DST_ALPHA 5 +#define ALPHA_FACTOR_CONST_ALPHA 6 +#define ALPHA_FACTOR_ONE_MINUS_CONST_ALPHA 7 + +#define GE2D_ALU_ALPHA_OPERATION_LOGIC GENMASK(3, 0) + +#define GE2D_ALU_COLOR_OP(__op, __src_factor, __dst_factor) \ + (FIELD_PREP(GE2D_ALU_BLEND_MODE, __op) | \ + FIELD_PREP(GE2D_ALU_SRC_COLOR_BLEND_FACTOR, __src_factor) | \ + FIELD_PREP(GE2D_ALU_DST_COLOR_BLEND_FACTOR, __dst_factor)) + +#define GE2D_ALU_DO_COLOR_OPERATION_LOGIC(__op, __src_factor) \ + GE2D_ALU_COLOR_OP(OPERATION_LOGIC, __src_factor, __op) + +#define GE2D_ALU_ALPHA_OP(__op, __src_factor, __dst_factor) \ + (FIELD_PREP(GE2D_ALU_ALPHA_BLEND_MODE, __op) | \ + FIELD_PREP(GE2D_ALU_SRC_ALPHA_BLEND_FACTOR, __src_factor) | \ + FIELD_PREP(GE2D_ALU_DST_ALPHA_BLEND_FACTOR, __dst_factor)) + +#define GE2D_ALU_DO_ALPHA_OPERATION_LOGIC(__op, __src_factor) \ + GE2D_ALU_ALPHA_OP(OPERATION_LOGIC, __src_factor, __op) + +#define GE2D_ALU_CONST_COLOR GE2D_REG(0x2d) +#define GE2D_SRC1_KEY GE2D_REG(0x2e) +#define GE2D_SRC1_KEY_MASK GE2D_REG(0x2f) +#define GE2D_SRC2_KEY GE2D_REG(0x30) +#define GE2D_SRC2_KEY_MASK GE2D_REG(0x31) +#define GE2D_DST_BITMASK GE2D_REG(0x32) +#define GE2D_DP_ONOFF_CTRL GE2D_REG(0x33) +#define GE2D_SCALE_COEF_IDX GE2D_REG(0x34) +#define GE2D_SCALE_COEF GE2D_REG(0x35) +#define GE2D_SRC_OUTSIDE_ALPHA GE2D_REG(0x36) +#define GE2D_ANTIFLICK_CTRL0 GE2D_REG(0x38) +#define GE2D_ANTIFLICK_CTRL1 GE2D_REG(0x39) +#define GE2D_ANTIFLICK_COLOR_FILT0 GE2D_REG(0x3a) +#define GE2D_ANTIFLICK_COLOR_FILT1 GE2D_REG(0x3b) +#define GE2D_ANTIFLICK_COLOR_FILT2 GE2D_REG(0x3c) +#define GE2D_ANTIFLICK_COLOR_FILT3 GE2D_REG(0x3d) +#define GE2D_ANTIFLICK_ALPHA_FILT0 GE2D_REG(0x3e) +#define GE2D_ANTIFLICK_ALPHA_FILT1 GE2D_REG(0x3f) +#define GE2D_ANTIFLICK_ALPHA_FILT2 GE2D_REG(0x40) +#define GE2D_ANTIFLICK_ALPHA_FILT3 GE2D_REG(0x41) +#define GE2D_SRC1_RANGE_MAP_Y_CTRL GE2D_REG(0x43) +#define GE2D_SRC1_RANGE_MAP_CB_CTRL GE2D_REG(0x44) +#define GE2D_SRC1_RANGE_MAP_CR_CTRL GE2D_REG(0x45) +#define GE2D_ARB_BURST_NUM GE2D_REG(0x46) +#define GE2D_TID_TOKEN GE2D_REG(0x47) +#define GE2D_GEN_CTRL3 GE2D_REG(0x48) + +#define GE2D_DST2_BYTEMASK_VAL GENMASK(31, 28) +#define GE2D_DST2_PIC_STRUCT GENMASK(27, 26) +#define GE2D_DST2_8B_MODE_SEL GENMASK(25, 24) +#define GE2D_DST2_COLOR_MAP GENMASK(22, 19) +#define GE2D_DST2_FORMAT GENMASK(17, 16) +#define GE2D_DST2_COLOR_ROUND_MODE BIT(14) +#define GE2D_DST2_X_DISCARD_MODE GENMASK(13, 12) +#define GE2D_DST2_Y_DISCARD_MODE GENMASK(11, 10) +#define GE2D_DST2_ENABLE BIT(8) +#define GE2D_DST1_X_DISCARD_MODE GENMASK(5, 4) +#define GE2D_DST1_Y_DISCARD_MODE GENMASK(3, 2) +#define GE2D_DST1_ENABLE BIT(0) + +#define GE2D_STATUS2 GE2D_REG(0x49) +#define GE2D_GEN_CTRL4 GE2D_REG(0x4a) +#define GE2D_DST1_BADDR_CTRL GE2D_REG(0x51) +#define GE2D_DST1_STRIDE_CTRL GE2D_REG(0x52) + +#define GE2D_STRIDE_SIZE GENMASK(19, 0) + +#define GE2D_SRC1_BADDR_CTRL GE2D_REG(0x53) +#define GE2D_SRC1_STRIDE_CTRL GE2D_REG(0x54) +#define GE2D_SRC2_BADDR_CTRL GE2D_REG(0x55) +#define GE2D_SRC2_STRIDE_CTRL GE2D_REG(0x56) + +#endif /* __GE2D_REGS__ */ diff --git a/drivers/media/platform/amlogic/meson-ge2d/ge2d.c b/drivers/media/platform/amlogic/meson-ge2d/ge2d.c new file mode 100644 index 000000000..142d421a8 --- /dev/null +++ b/drivers/media/platform/amlogic/meson-ge2d/ge2d.c @@ -0,0 +1,1061 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 BayLibre, SAS + * Author: Neil Armstrong + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ge2d-regs.h" + +#define GE2D_NAME "meson-ge2d" + +#define DEFAULT_WIDTH 128 +#define DEFAULT_HEIGHT 128 +#define DEFAULT_STRIDE 512 + +#define MAX_WIDTH 8191 +#define MAX_HEIGHT 8191 + +/* + * Missing features: + * - Scaling + * - Simple 1/2 vertical scaling + * - YUV input support + * - Source global alpha + * - Colorspace conversion + */ + +struct ge2d_fmt { + u32 fourcc; + bool alpha; + bool le; + unsigned int depth; + unsigned int hw_fmt; + unsigned int hw_map; +}; + +struct ge2d_frame { + struct vb2_v4l2_buffer *buf; + + /* Image Format */ + struct v4l2_pix_format pix_fmt; + + /* Crop */ + struct v4l2_rect crop; + + /* Image format */ + const struct ge2d_fmt *fmt; +}; + +struct ge2d_ctx { + struct v4l2_fh fh; + struct meson_ge2d *ge2d; + struct ge2d_frame in; + struct ge2d_frame out; + struct v4l2_ctrl_handler ctrl_handler; + + unsigned long sequence_out, sequence_cap; + + /* Control values */ + u32 hflip; + u32 vflip; + u32 xy_swap; +}; + +struct meson_ge2d { + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct video_device *vfd; + + struct device *dev; + struct regmap *map; + struct clk *clk; + + /* vb2 queue lock */ + struct mutex mutex; + + struct ge2d_ctx *curr; +}; + +#define FMT(_fourcc, _alpha, _depth, _map) \ +{ \ + .fourcc = _fourcc, \ + .alpha = (_alpha), \ + .depth = (_depth), \ + .hw_fmt = GE2D_FORMAT_ ## _depth ## BIT, \ + .hw_map = GE2D_COLOR_MAP_ ## _map, \ +} + +/* TOFIX Handle the YUV input formats */ +static const struct ge2d_fmt formats[] = { + /* FOURCC Alpha HW FMT HW MAP */ + FMT(V4L2_PIX_FMT_XRGB32, false, 32, BGRA8888), + FMT(V4L2_PIX_FMT_RGB32, true, 32, BGRA8888), + FMT(V4L2_PIX_FMT_ARGB32, true, 32, BGRA8888), + FMT(V4L2_PIX_FMT_RGBX32, false, 32, ABGR8888), + FMT(V4L2_PIX_FMT_RGBA32, true, 32, ABGR8888), + FMT(V4L2_PIX_FMT_BGRX32, false, 32, RGBA8888), + FMT(V4L2_PIX_FMT_BGRA32, true, 32, RGBA8888), + FMT(V4L2_PIX_FMT_BGR32, true, 32, ARGB8888), + FMT(V4L2_PIX_FMT_ABGR32, true, 32, ARGB8888), + FMT(V4L2_PIX_FMT_XBGR32, false, 32, ARGB8888), + + FMT(V4L2_PIX_FMT_RGB24, false, 24, BGR888), + FMT(V4L2_PIX_FMT_BGR24, false, 24, RGB888), + + FMT(V4L2_PIX_FMT_XRGB555X, false, 16, ARGB1555), + FMT(V4L2_PIX_FMT_ARGB555X, true, 16, ARGB1555), + FMT(V4L2_PIX_FMT_RGB565, false, 16, RGB565), + FMT(V4L2_PIX_FMT_RGBX444, false, 16, RGBA4444), + FMT(V4L2_PIX_FMT_RGBA444, true, 16, RGBA4444), + FMT(V4L2_PIX_FMT_XRGB444, false, 16, ARGB4444), + FMT(V4L2_PIX_FMT_ARGB444, true, 16, ARGB4444), +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static const struct ge2d_fmt *find_fmt(struct v4l2_format *f) +{ + unsigned int i; + + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].fourcc == f->fmt.pix.pixelformat) + return &formats[i]; + } + + /* + * TRY_FMT/S_FMT should never return an error when the requested format + * is not supported. Drivers should always return a valid format, + * preferably a format that is as widely supported by applications as + * possible. + */ + return &formats[0]; +} + +static struct ge2d_frame *get_frame(struct ge2d_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &ctx->in; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &ctx->out; + default: + /* This should never happen, warn and return OUTPUT frame */ + dev_warn(ctx->ge2d->dev, "%s: invalid buffer type\n", __func__); + return &ctx->in; + } +} + +static void ge2d_hw_start(struct meson_ge2d *ge2d) +{ + struct ge2d_ctx *ctx = ge2d->curr; + u32 reg; + + /* Reset */ + regmap_update_bits(ge2d->map, GE2D_GEN_CTRL1, + GE2D_SOFT_RST, GE2D_SOFT_RST); + regmap_update_bits(ge2d->map, GE2D_GEN_CTRL1, + GE2D_SOFT_RST, 0); + + usleep_range(100, 200); + + /* Implement CANVAS for non-AXG */ + regmap_write(ge2d->map, GE2D_SRC1_BADDR_CTRL, + (vb2_dma_contig_plane_dma_addr(&ctx->in.buf->vb2_buf, 0) + 7) >> 3); + regmap_write(ge2d->map, GE2D_SRC1_STRIDE_CTRL, + (ctx->in.pix_fmt.bytesperline + 7) >> 3); + regmap_write(ge2d->map, GE2D_SRC2_BADDR_CTRL, + (vb2_dma_contig_plane_dma_addr(&ctx->out.buf->vb2_buf, 0) + 7) >> 3); + regmap_write(ge2d->map, GE2D_SRC2_STRIDE_CTRL, + (ctx->out.pix_fmt.bytesperline + 7) >> 3); + regmap_write(ge2d->map, GE2D_DST1_BADDR_CTRL, + (vb2_dma_contig_plane_dma_addr(&ctx->out.buf->vb2_buf, 0) + 7) >> 3); + regmap_write(ge2d->map, GE2D_DST1_STRIDE_CTRL, + (ctx->out.pix_fmt.bytesperline + 7) >> 3); + + regmap_write(ge2d->map, GE2D_GEN_CTRL0, 0); + regmap_write(ge2d->map, GE2D_GEN_CTRL1, + FIELD_PREP(GE2D_INTERRUPT_CTRL, 2) | + FIELD_PREP(GE2D_SRC2_BURST_SIZE_CTRL, 3) | + FIELD_PREP(GE2D_SRC1_BURST_SIZE_CTRL, 0x3f)); + + regmap_write(ge2d->map, GE2D_GEN_CTRL2, + GE2D_SRC1_LITTLE_ENDIAN | + GE2D_SRC2_LITTLE_ENDIAN | + GE2D_DST_LITTLE_ENDIAN | + FIELD_PREP(GE2D_DST1_COLOR_MAP, ctx->out.fmt->hw_map) | + FIELD_PREP(GE2D_DST1_FORMAT, ctx->out.fmt->hw_fmt) | + FIELD_PREP(GE2D_SRC2_COLOR_MAP, ctx->out.fmt->hw_map) | + FIELD_PREP(GE2D_SRC2_FORMAT, ctx->out.fmt->hw_fmt) | + FIELD_PREP(GE2D_SRC1_COLOR_MAP, ctx->in.fmt->hw_map) | + FIELD_PREP(GE2D_SRC1_FORMAT, ctx->in.fmt->hw_fmt)); + regmap_write(ge2d->map, GE2D_GEN_CTRL3, + GE2D_DST1_ENABLE); + + regmap_write(ge2d->map, GE2D_SRC1_CLIPY_START_END, + FIELD_PREP(GE2D_START, ctx->in.crop.top) | + FIELD_PREP(GE2D_END, ctx->in.crop.top + ctx->in.crop.height - 1)); + regmap_write(ge2d->map, GE2D_SRC1_CLIPX_START_END, + FIELD_PREP(GE2D_START, ctx->in.crop.left) | + FIELD_PREP(GE2D_END, ctx->in.crop.left + ctx->in.crop.width - 1)); + regmap_write(ge2d->map, GE2D_SRC2_CLIPY_START_END, + FIELD_PREP(GE2D_START, ctx->out.crop.top) | + FIELD_PREP(GE2D_END, ctx->out.crop.top + ctx->out.crop.height - 1)); + regmap_write(ge2d->map, GE2D_SRC2_CLIPX_START_END, + FIELD_PREP(GE2D_START, ctx->out.crop.left) | + FIELD_PREP(GE2D_END, ctx->out.crop.left + ctx->out.crop.width - 1)); + regmap_write(ge2d->map, GE2D_DST_CLIPY_START_END, + FIELD_PREP(GE2D_START, ctx->out.crop.top) | + FIELD_PREP(GE2D_END, ctx->out.crop.top + ctx->out.crop.height - 1)); + regmap_write(ge2d->map, GE2D_DST_CLIPX_START_END, + FIELD_PREP(GE2D_START, ctx->out.crop.left) | + FIELD_PREP(GE2D_END, ctx->out.crop.left + ctx->out.crop.width - 1)); + + regmap_write(ge2d->map, GE2D_SRC1_Y_START_END, + FIELD_PREP(GE2D_END, ctx->in.pix_fmt.height - 1)); + regmap_write(ge2d->map, GE2D_SRC1_X_START_END, + FIELD_PREP(GE2D_END, ctx->in.pix_fmt.width - 1)); + regmap_write(ge2d->map, GE2D_SRC2_Y_START_END, + FIELD_PREP(GE2D_END, ctx->out.pix_fmt.height - 1)); + regmap_write(ge2d->map, GE2D_SRC2_X_START_END, + FIELD_PREP(GE2D_END, ctx->out.pix_fmt.width - 1)); + regmap_write(ge2d->map, GE2D_DST_Y_START_END, + FIELD_PREP(GE2D_END, ctx->out.pix_fmt.height - 1)); + regmap_write(ge2d->map, GE2D_DST_X_START_END, + FIELD_PREP(GE2D_END, ctx->out.pix_fmt.width - 1)); + + /* Color, no blend, use source color */ + reg = GE2D_ALU_DO_COLOR_OPERATION_LOGIC(LOGIC_OPERATION_COPY, + COLOR_FACTOR_SRC_COLOR); + + if (ctx->in.fmt->alpha && ctx->out.fmt->alpha) + /* Take source alpha */ + reg |= GE2D_ALU_DO_ALPHA_OPERATION_LOGIC(LOGIC_OPERATION_COPY, + COLOR_FACTOR_SRC_ALPHA); + else if (!ctx->out.fmt->alpha) + /* Set alpha to 0 */ + reg |= GE2D_ALU_DO_ALPHA_OPERATION_LOGIC(LOGIC_OPERATION_SET, + COLOR_FACTOR_ZERO); + else + /* Keep original alpha */ + reg |= GE2D_ALU_DO_ALPHA_OPERATION_LOGIC(LOGIC_OPERATION_COPY, + COLOR_FACTOR_DST_ALPHA); + + regmap_write(ge2d->map, GE2D_ALU_OP_CTRL, reg); + + /* Start */ + regmap_write(ge2d->map, GE2D_CMD_CTRL, + (ctx->xy_swap ? GE2D_DST_XY_SWAP : 0) | + (ctx->hflip ? GE2D_SRC1_Y_REV : 0) | + (ctx->vflip ? GE2D_SRC1_X_REV : 0) | + GE2D_CBUS_CMD_WR); +} + +static void device_run(void *priv) +{ + struct ge2d_ctx *ctx = priv; + struct meson_ge2d *ge2d = ctx->ge2d; + + ge2d->curr = ctx; + + ctx->in.buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + ctx->out.buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + ge2d_hw_start(ge2d); +} + +static irqreturn_t ge2d_isr(int irq, void *priv) +{ + struct meson_ge2d *ge2d = priv; + u32 intr; + + regmap_read(ge2d->map, GE2D_STATUS0, &intr); + + if (!(intr & GE2D_GE2D_BUSY)) { + struct vb2_v4l2_buffer *src, *dst; + struct ge2d_ctx *ctx = ge2d->curr; + + ge2d->curr = NULL; + + src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + src->sequence = ctx->sequence_out++; + dst->sequence = ctx->sequence_cap++; + + dst->timecode = src->timecode; + dst->vb2_buf.timestamp = src->vb2_buf.timestamp; + dst->flags = src->flags; + + v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE); + v4l2_m2m_job_finish(ge2d->m2m_dev, ctx->fh.m2m_ctx); + } + + return IRQ_HANDLED; +} + +static const struct v4l2_m2m_ops ge2d_m2m_ops = { + .device_run = device_run, +}; + +static int ge2d_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct ge2d_ctx *ctx = vb2_get_drv_priv(vq); + struct ge2d_frame *f = get_frame(ctx, vq->type); + + if (*nplanes) + return sizes[0] < f->pix_fmt.sizeimage ? -EINVAL : 0; + + sizes[0] = f->pix_fmt.sizeimage; + *nplanes = 1; + + return 0; +} + +static int ge2d_buf_prepare(struct vb2_buffer *vb) +{ + struct ge2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct ge2d_frame *f = get_frame(ctx, vb->vb2_queue->type); + + vbuf->field = V4L2_FIELD_NONE; + + vb2_set_plane_payload(vb, 0, f->pix_fmt.sizeimage); + + return 0; +} + +static void ge2d_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct ge2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int ge2d_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct ge2d_ctx *ctx = vb2_get_drv_priv(vq); + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + ctx->sequence_out = 0; + else + ctx->sequence_cap = 0; + + return 0; +} + +static void ge2d_stop_streaming(struct vb2_queue *vq) +{ + struct ge2d_ctx *ctx = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) + break; + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops ge2d_qops = { + .queue_setup = ge2d_queue_setup, + .buf_prepare = ge2d_buf_prepare, + .buf_queue = ge2d_buf_queue, + .start_streaming = ge2d_start_streaming, + .stop_streaming = ge2d_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int +queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) +{ + struct ge2d_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->ops = &ge2d_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->ge2d->mutex; + src_vq->dev = ctx->ge2d->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->ops = &ge2d_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->ge2d->mutex; + dst_vq->dev = ctx->ge2d->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +static int +vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) +{ + strscpy(cap->driver, GE2D_NAME, sizeof(cap->driver)); + strscpy(cap->card, GE2D_NAME, sizeof(cap->card)); + strscpy(cap->bus_info, "platform:" GE2D_NAME, sizeof(cap->bus_info)); + + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *priv, struct v4l2_fmtdesc *f) +{ + const struct ge2d_fmt *fmt; + + if (f->index >= NUM_FORMATS) + return -EINVAL; + + fmt = &formats[f->index]; + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int vidioc_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct ge2d_ctx *ctx = priv; + struct ge2d_frame *f; + bool use_frame = false; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + f = get_frame(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + break; + case V4L2_SEL_TGT_COMPOSE: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + use_frame = true; + break; + case V4L2_SEL_TGT_CROP: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + use_frame = true; + break; + default: + return -EINVAL; + } + + if (use_frame) { + s->r = f->crop; + } else { + s->r.left = 0; + s->r.top = 0; + s->r.width = f->pix_fmt.width; + s->r.height = f->pix_fmt.height; + } + + return 0; +} + +static int vidioc_s_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct ge2d_ctx *ctx = priv; + struct meson_ge2d *ge2d = ctx->ge2d; + struct ge2d_frame *f; + int ret = 0; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + f = get_frame(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + /* + * COMPOSE target is only valid for capture buffer type, return + * error for output buffer type + */ + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + break; + case V4L2_SEL_TGT_CROP: + /* + * CROP target is only valid for output buffer type, return + * error for capture buffer type + */ + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + break; + /* + * bound and default crop/compose targets are invalid targets to + * try/set + */ + default: + return -EINVAL; + } + + if (s->r.top < 0 || s->r.left < 0) { + v4l2_err(&ge2d->v4l2_dev, + "doesn't support negative values for top & left.\n"); + return -EINVAL; + } + + if (s->r.left + s->r.width > f->pix_fmt.width || + s->r.top + s->r.height > f->pix_fmt.height) { + v4l2_err(&ge2d->v4l2_dev, "unsupported rectangle value.\n"); + return -EINVAL; + } + + f->crop = s->r; + + return ret; +} + +static void vidioc_setup_cap_fmt(struct ge2d_ctx *ctx, struct v4l2_pix_format *f) +{ + struct ge2d_frame *frm_out = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + *f = frm_out->pix_fmt; + + if (ctx->xy_swap) { + f->width = frm_out->pix_fmt.height; + f->height = frm_out->pix_fmt.width; + } +} + +static int vidioc_try_fmt_cap(struct file *file, void *priv, struct v4l2_format *f) +{ + const struct ge2d_fmt *fmt = find_fmt(f); + struct ge2d_ctx *ctx = priv; + struct v4l2_pix_format fmt_cap; + + vidioc_setup_cap_fmt(ctx, &fmt_cap); + + fmt_cap.pixelformat = fmt->fourcc; + + fmt_cap.bytesperline = max(f->fmt.pix.bytesperline, + ALIGN((fmt_cap.width * fmt->depth) >> 3, 8)); + + fmt_cap.sizeimage = max(f->fmt.pix.sizeimage, + fmt_cap.height * fmt_cap.bytesperline); + + f->fmt.pix = fmt_cap; + + return 0; +} + +static int vidioc_s_fmt_cap(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ge2d_ctx *ctx = priv; + struct meson_ge2d *ge2d = ctx->ge2d; + struct vb2_queue *vq; + struct ge2d_frame *frm; + int ret = 0; + + /* Adjust all values accordingly to the hardware capabilities + * and chosen format. + */ + ret = vidioc_try_fmt_cap(file, priv, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) { + v4l2_err(&ge2d->v4l2_dev, "queue (%d) bust\n", f->type); + return -EBUSY; + } + + frm = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + frm->pix_fmt = f->fmt.pix; + frm->fmt = find_fmt(f); + f->fmt.pix.pixelformat = frm->fmt->fourcc; + + /* Reset crop settings */ + frm->crop.left = 0; + frm->crop.top = 0; + frm->crop.width = frm->pix_fmt.width; + frm->crop.height = frm->pix_fmt.height; + + return 0; +} + +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ge2d_ctx *ctx = priv; + struct vb2_queue *vq; + struct ge2d_frame *frm; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + frm = get_frame(ctx, f->type); + + f->fmt.pix = frm->pix_fmt; + f->fmt.pix.pixelformat = frm->fmt->fourcc; + + return 0; +} + +static int vidioc_try_fmt_out(struct file *file, void *priv, struct v4l2_format *f) +{ + const struct ge2d_fmt *fmt = find_fmt(f); + + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = fmt->fourcc; + + if (f->fmt.pix.width > MAX_WIDTH) + f->fmt.pix.width = MAX_WIDTH; + if (f->fmt.pix.height > MAX_HEIGHT) + f->fmt.pix.height = MAX_HEIGHT; + + f->fmt.pix.bytesperline = max(f->fmt.pix.bytesperline, + ALIGN((f->fmt.pix.width * fmt->depth) >> 3, 8)); + + f->fmt.pix.sizeimage = max(f->fmt.pix.sizeimage, + f->fmt.pix.height * f->fmt.pix.bytesperline); + + return 0; +} + +static int vidioc_s_fmt_out(struct file *file, void *priv, struct v4l2_format *f) +{ + struct ge2d_ctx *ctx = priv; + struct meson_ge2d *ge2d = ctx->ge2d; + struct vb2_queue *vq; + struct ge2d_frame *frm, *frm_cap; + int ret = 0; + + /* Adjust all values accordingly to the hardware capabilities + * and chosen format. + */ + ret = vidioc_try_fmt_out(file, priv, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) { + v4l2_err(&ge2d->v4l2_dev, "queue (%d) bust\n", f->type); + return -EBUSY; + } + + frm = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + frm_cap = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + frm->pix_fmt = f->fmt.pix; + frm->fmt = find_fmt(f); + f->fmt.pix.pixelformat = frm->fmt->fourcc; + + /* Reset crop settings */ + frm->crop.left = 0; + frm->crop.top = 0; + frm->crop.width = frm->pix_fmt.width; + frm->crop.height = frm->pix_fmt.height; + + /* Propagate settings to capture */ + vidioc_setup_cap_fmt(ctx, &frm_cap->pix_fmt); + + return 0; +} + +static const struct v4l2_ioctl_ops ge2d_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt, + .vidioc_g_fmt_vid_out = vidioc_g_fmt, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_g_selection = vidioc_g_selection, + .vidioc_s_selection = vidioc_s_selection, +}; + +static int ge2d_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct ge2d_ctx *ctx = container_of(ctrl->handler, struct ge2d_ctx, + ctrl_handler); + struct v4l2_pix_format fmt; + struct vb2_queue *vq; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->hflip = ctrl->val; + break; + case V4L2_CID_VFLIP: + ctx->vflip = ctrl->val; + break; + case V4L2_CID_ROTATE: + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (vb2_is_busy(vq)) + return -EBUSY; + + if (ctrl->val == 90) { + ctx->hflip = 0; + ctx->vflip = 1; + ctx->xy_swap = 1; + } else if (ctrl->val == 180) { + ctx->hflip = 1; + ctx->vflip = 1; + ctx->xy_swap = 0; + } else if (ctrl->val == 270) { + ctx->hflip = 1; + ctx->vflip = 0; + ctx->xy_swap = 1; + } else { + ctx->hflip = 0; + ctx->vflip = 0; + ctx->xy_swap = 0; + } + + vidioc_setup_cap_fmt(ctx, &fmt); + + /* + * If the rotation parameter changes the OUTPUT frames + * parameters, take them in account + */ + ctx->out.pix_fmt = fmt; + + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops ge2d_ctrl_ops = { + .s_ctrl = ge2d_s_ctrl, +}; + +static int ge2d_setup_ctrls(struct ge2d_ctx *ctx) +{ + struct meson_ge2d *ge2d = ctx->ge2d; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 4); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &ge2d_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &ge2d_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &ge2d_ctrl_ops, + V4L2_CID_ROTATE, 0, 270, 90, 0); + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + + v4l2_err(&ge2d->v4l2_dev, "%s failed\n", __func__); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; + } + + return 0; +} + +static const struct ge2d_frame def_frame = { + .pix_fmt = { + .width = DEFAULT_WIDTH, + .height = DEFAULT_HEIGHT, + .bytesperline = DEFAULT_STRIDE, + .sizeimage = DEFAULT_STRIDE * DEFAULT_HEIGHT, + .field = V4L2_FIELD_NONE, + }, + .crop.width = DEFAULT_WIDTH, + .crop.height = DEFAULT_HEIGHT, + .fmt = &formats[0], +}; + +static int ge2d_open(struct file *file) +{ + struct meson_ge2d *ge2d = video_drvdata(file); + struct ge2d_ctx *ctx = NULL; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + ctx->ge2d = ge2d; + + /* Set default formats */ + ctx->in = def_frame; + ctx->out = def_frame; + + if (mutex_lock_interruptible(&ge2d->mutex)) { + kfree(ctx); + return -ERESTARTSYS; + } + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(ge2d->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + mutex_unlock(&ge2d->mutex); + kfree(ctx); + return ret; + } + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ge2d_setup_ctrls(ctx); + + /* Write the default values to the ctx struct */ + v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + mutex_unlock(&ge2d->mutex); + + return 0; +} + +static int ge2d_release(struct file *file) +{ + struct ge2d_ctx *ctx = + container_of(file->private_data, struct ge2d_ctx, fh); + struct meson_ge2d *ge2d = ctx->ge2d; + + mutex_lock(&ge2d->mutex); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + + mutex_unlock(&ge2d->mutex); + + return 0; +} + +static const struct v4l2_file_operations ge2d_fops = { + .owner = THIS_MODULE, + .open = ge2d_open, + .release = ge2d_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device ge2d_videodev = { + .name = "meson-ge2d", + .fops = &ge2d_fops, + .ioctl_ops = &ge2d_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, +}; + +static const struct regmap_config meson_ge2d_regmap_conf = { + .reg_bits = 8, + .val_bits = 32, + .reg_stride = 4, + .max_register = GE2D_SRC2_STRIDE_CTRL, +}; + +static int ge2d_probe(struct platform_device *pdev) +{ + struct reset_control *rst; + struct video_device *vfd; + struct meson_ge2d *ge2d; + void __iomem *regs; + int ret = 0; + int irq; + + if (!pdev->dev.of_node) + return -ENODEV; + + ge2d = devm_kzalloc(&pdev->dev, sizeof(*ge2d), GFP_KERNEL); + if (!ge2d) + return -ENOMEM; + + ge2d->dev = &pdev->dev; + mutex_init(&ge2d->mutex); + + regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + ge2d->map = devm_regmap_init_mmio(ge2d->dev, regs, + &meson_ge2d_regmap_conf); + if (IS_ERR(ge2d->map)) + return PTR_ERR(ge2d->map); + + irq = platform_get_irq(pdev, 0); + ret = devm_request_irq(ge2d->dev, irq, ge2d_isr, 0, + dev_name(ge2d->dev), ge2d); + if (ret < 0) { + dev_err(ge2d->dev, "failed to request irq\n"); + return ret; + } + + rst = devm_reset_control_get(ge2d->dev, NULL); + if (IS_ERR(rst)) { + dev_err(ge2d->dev, "failed to get core reset controller\n"); + return PTR_ERR(rst); + } + + ge2d->clk = devm_clk_get(ge2d->dev, NULL); + if (IS_ERR(ge2d->clk)) { + dev_err(ge2d->dev, "failed to get clock\n"); + return PTR_ERR(ge2d->clk); + } + + reset_control_assert(rst); + udelay(1); + reset_control_deassert(rst); + + ret = clk_prepare_enable(ge2d->clk); + if (ret) { + dev_err(ge2d->dev, "Cannot enable ge2d sclk: %d\n", ret); + return ret; + } + + ret = v4l2_device_register(&pdev->dev, &ge2d->v4l2_dev); + if (ret) + goto disable_clks; + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&ge2d->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_v4l2_dev; + } + + *vfd = ge2d_videodev; + vfd->lock = &ge2d->mutex; + vfd->v4l2_dev = &ge2d->v4l2_dev; + + video_set_drvdata(vfd, ge2d); + ge2d->vfd = vfd; + + platform_set_drvdata(pdev, ge2d); + ge2d->m2m_dev = v4l2_m2m_init(&ge2d_m2m_ops); + if (IS_ERR(ge2d->m2m_dev)) { + v4l2_err(&ge2d->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(ge2d->m2m_dev); + goto rel_vdev; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&ge2d->v4l2_dev, "Failed to register video device\n"); + goto rel_m2m; + } + + v4l2_info(&ge2d->v4l2_dev, "Registered %s as /dev/%s\n", + vfd->name, video_device_node_name(vfd)); + + return 0; + +rel_m2m: + v4l2_m2m_release(ge2d->m2m_dev); +rel_vdev: + video_device_release(ge2d->vfd); +unreg_v4l2_dev: + v4l2_device_unregister(&ge2d->v4l2_dev); +disable_clks: + clk_disable_unprepare(ge2d->clk); + + return ret; +} + +static int ge2d_remove(struct platform_device *pdev) +{ + struct meson_ge2d *ge2d = platform_get_drvdata(pdev); + + video_unregister_device(ge2d->vfd); + v4l2_m2m_release(ge2d->m2m_dev); + v4l2_device_unregister(&ge2d->v4l2_dev); + clk_disable_unprepare(ge2d->clk); + + return 0; +} + +static const struct of_device_id meson_ge2d_match[] = { + { + .compatible = "amlogic,axg-ge2d", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, meson_ge2d_match); + +static struct platform_driver ge2d_drv = { + .probe = ge2d_probe, + .remove = ge2d_remove, + .driver = { + .name = "meson-ge2d", + .of_match_table = meson_ge2d_match, + }, +}; + +module_platform_driver(ge2d_drv); + +MODULE_AUTHOR("Neil Armstrong "); +MODULE_DESCRIPTION("Amlogic 2D Graphic Acceleration Unit"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/amphion/Kconfig b/drivers/media/platform/amphion/Kconfig new file mode 100644 index 000000000..4a363e07c --- /dev/null +++ b/drivers/media/platform/amphion/Kconfig @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Amphion drivers" + +config VIDEO_AMPHION_VPU + tristate "Amphion VPU (Video Processing Unit) Codec IP" + depends on V4L_MEM2MEM_DRIVERS + depends on ARCH_MXC || COMPILE_TEST + depends on MEDIA_SUPPORT + depends on VIDEO_DEV && MAILBOX + select MEDIA_CONTROLLER + select V4L2_MEM2MEM_DEV + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC + help + Amphion VPU Codec IP contains two parts: Windsor and Malone. + Windsor is encoder that supports H.264, and Malone is decoder + that supports H.264, HEVC, and other video formats. + This is a V4L2 driver for NXP MXC 8Q video accelerator hardware. + It accelerates encoding and decoding operations on + various NXP SoCs. + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/amphion/Makefile b/drivers/media/platform/amphion/Makefile new file mode 100644 index 000000000..807173128 --- /dev/null +++ b/drivers/media/platform/amphion/Makefile @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for NXP VPU driver + +amphion-vpu-objs += vpu_drv.o \ + vpu_core.o \ + vpu_mbox.o \ + vpu_v4l2.o \ + vpu_helpers.o \ + vpu_cmds.o \ + vpu_msgs.o \ + vpu_rpc.o \ + vpu_imx8q.o \ + vpu_windsor.o \ + vpu_malone.o \ + vpu_color.o \ + vdec.o \ + venc.o \ + vpu_dbg.o + +obj-$(CONFIG_VIDEO_AMPHION_VPU) += amphion-vpu.o diff --git a/drivers/media/platform/amphion/vdec.c b/drivers/media/platform/amphion/vdec.c new file mode 100644 index 000000000..dc35a87e6 --- /dev/null +++ b/drivers/media/platform/amphion/vdec.c @@ -0,0 +1,1700 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_defs.h" +#include "vpu_core.h" +#include "vpu_helpers.h" +#include "vpu_v4l2.h" +#include "vpu_cmds.h" +#include "vpu_rpc.h" + +#define VDEC_MIN_BUFFER_CAP 8 +#define VDEC_MIN_BUFFER_OUT 8 + +struct vdec_fs_info { + char name[8]; + u32 type; + u32 max_count; + u32 req_count; + u32 count; + u32 index; + u32 size; + struct vpu_buffer buffer[32]; + u32 tag; +}; + +struct vdec_t { + u32 seq_hdr_found; + struct vpu_buffer udata; + struct vpu_decode_params params; + struct vpu_dec_codec_info codec_info; + enum vpu_codec_state state; + + struct vpu_vb2_buffer *slots[VB2_MAX_FRAME]; + u32 req_frame_count; + struct vdec_fs_info mbi; + struct vdec_fs_info dcp; + u32 seq_tag; + + bool reset_codec; + bool fixed_fmt; + u32 decoded_frame_count; + u32 display_frame_count; + u32 sequence; + u32 eos_received; + bool is_source_changed; + u32 source_change; + u32 drain; + bool aborting; +}; + +static const struct vpu_format vdec_formats[] = { + { + .pixfmt = V4L2_PIX_FMT_NV12M_8L128, + .num_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, + { + .pixfmt = V4L2_PIX_FMT_NV12M_10BE_8L128, + .num_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, + { + .pixfmt = V4L2_PIX_FMT_H264, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + { + .pixfmt = V4L2_PIX_FMT_H264_MVC, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + { + .pixfmt = V4L2_PIX_FMT_HEVC, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + { + .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_G, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + { + .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_L, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + { + .pixfmt = V4L2_PIX_FMT_MPEG2, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + { + .pixfmt = V4L2_PIX_FMT_MPEG4, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + { + .pixfmt = V4L2_PIX_FMT_XVID, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + { + .pixfmt = V4L2_PIX_FMT_VP8, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + { + .pixfmt = V4L2_PIX_FMT_H263, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION + }, + {0, 0, 0, 0}, +}; + +static int vdec_op_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vpu_inst *inst = ctrl_to_inst(ctrl); + struct vdec_t *vdec = inst->priv; + int ret = 0; + + vpu_inst_lock(inst); + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE: + vdec->params.display_delay_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY: + vdec->params.display_delay = ctrl->val; + break; + default: + ret = -EINVAL; + break; + } + vpu_inst_unlock(inst); + + return ret; +} + +static const struct v4l2_ctrl_ops vdec_ctrl_ops = { + .s_ctrl = vdec_op_s_ctrl, + .g_volatile_ctrl = vpu_helper_g_volatile_ctrl, +}; + +static int vdec_ctrl_init(struct vpu_inst *inst) +{ + struct v4l2_ctrl *ctrl; + int ret; + + ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, 20); + if (ret) + return ret; + + v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY, + 0, 0, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE, + 0, 1, 1, 0); + + ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 2); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, 1, 32, 1, 2); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + if (inst->ctrl_handler.error) { + ret = inst->ctrl_handler.error; + v4l2_ctrl_handler_free(&inst->ctrl_handler); + return ret; + } + + ret = v4l2_ctrl_handler_setup(&inst->ctrl_handler); + if (ret) { + dev_err(inst->dev, "[%d] setup ctrls fail, ret = %d\n", inst->id, ret); + v4l2_ctrl_handler_free(&inst->ctrl_handler); + return ret; + } + + return 0; +} + +static void vdec_handle_resolution_change(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + struct vb2_queue *q; + + if (!inst->fh.m2m_ctx) + return; + + if (inst->state != VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE) + return; + if (!vdec->source_change) + return; + + q = v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx); + if (!list_empty(&q->done_list)) + return; + + vdec->source_change--; + vpu_notify_source_change(inst); + vpu_set_last_buffer_dequeued(inst, false); +} + +static int vdec_update_state(struct vpu_inst *inst, enum vpu_codec_state state, u32 force) +{ + struct vdec_t *vdec = inst->priv; + enum vpu_codec_state pre_state = inst->state; + + if (state == VPU_CODEC_STATE_SEEK) { + if (inst->state == VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE) + vdec->state = inst->state; + else + vdec->state = VPU_CODEC_STATE_ACTIVE; + } + if (inst->state != VPU_CODEC_STATE_SEEK || force) + inst->state = state; + else if (state == VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE) + vdec->state = VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE; + + if (inst->state != pre_state) + vpu_trace(inst->dev, "[%d] %s -> %s\n", inst->id, + vpu_codec_state_name(pre_state), vpu_codec_state_name(inst->state)); + + if (inst->state == VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE) + vdec_handle_resolution_change(inst); + + return 0; +} + +static void vdec_set_last_buffer_dequeued(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + if (inst->state == VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE) + return; + + if (vdec->eos_received) { + if (!vpu_set_last_buffer_dequeued(inst, true)) { + vdec->eos_received--; + vdec_update_state(inst, VPU_CODEC_STATE_DRAIN, 0); + } + } +} + +static int vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + strscpy(cap->driver, "amphion-vpu", sizeof(cap->driver)); + strscpy(cap->card, "amphion vpu decoder", sizeof(cap->card)); + strscpy(cap->bus_info, "platform: amphion-vpu", sizeof(cap->bus_info)); + + return 0; +} + +static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct vpu_inst *inst = to_inst(file); + struct vdec_t *vdec = inst->priv; + const struct vpu_format *fmt; + int ret = -EINVAL; + + vpu_inst_lock(inst); + if (!V4L2_TYPE_IS_OUTPUT(f->type) && vdec->fixed_fmt) { + if (f->index == 0) { + f->pixelformat = inst->cap_format.pixfmt; + f->flags = inst->cap_format.flags; + ret = 0; + } + } else { + fmt = vpu_helper_enum_format(inst, f->type, f->index); + memset(f->reserved, 0, sizeof(f->reserved)); + if (!fmt) + goto exit; + + f->pixelformat = fmt->pixfmt; + f->flags = fmt->flags; + ret = 0; + } + +exit: + vpu_inst_unlock(inst); + return ret; +} + +static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct vpu_inst *inst = to_inst(file); + struct vdec_t *vdec = inst->priv; + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct vpu_format *cur_fmt; + int i; + + vpu_inst_lock(inst); + cur_fmt = vpu_get_format(inst, f->type); + + pixmp->pixelformat = cur_fmt->pixfmt; + pixmp->num_planes = cur_fmt->num_planes; + pixmp->width = cur_fmt->width; + pixmp->height = cur_fmt->height; + pixmp->field = cur_fmt->field; + pixmp->flags = cur_fmt->flags; + for (i = 0; i < pixmp->num_planes; i++) { + pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; + pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + } + + f->fmt.pix_mp.colorspace = vdec->codec_info.color_primaries; + f->fmt.pix_mp.xfer_func = vdec->codec_info.transfer_chars; + f->fmt.pix_mp.ycbcr_enc = vdec->codec_info.matrix_coeffs; + f->fmt.pix_mp.quantization = vdec->codec_info.full_range; + vpu_inst_unlock(inst); + + return 0; +} + +static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct vpu_inst *inst = to_inst(file); + struct vdec_t *vdec = inst->priv; + + vpu_try_fmt_common(inst, f); + + vpu_inst_lock(inst); + if (vdec->fixed_fmt) { + f->fmt.pix_mp.colorspace = vdec->codec_info.color_primaries; + f->fmt.pix_mp.xfer_func = vdec->codec_info.transfer_chars; + f->fmt.pix_mp.ycbcr_enc = vdec->codec_info.matrix_coeffs; + f->fmt.pix_mp.quantization = vdec->codec_info.full_range; + } else { + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_DEFAULT; + f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_DEFAULT; + f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_DEFAULT; + } + vpu_inst_unlock(inst); + + return 0; +} + +static int vdec_s_fmt_common(struct vpu_inst *inst, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + const struct vpu_format *fmt; + struct vpu_format *cur_fmt; + struct vb2_queue *q; + struct vdec_t *vdec = inst->priv; + int i; + + if (!inst->fh.m2m_ctx) + return -EINVAL; + + q = v4l2_m2m_get_vq(inst->fh.m2m_ctx, f->type); + if (!q) + return -EINVAL; + if (vb2_is_busy(q)) + return -EBUSY; + + fmt = vpu_try_fmt_common(inst, f); + if (!fmt) + return -EINVAL; + + cur_fmt = vpu_get_format(inst, f->type); + if (V4L2_TYPE_IS_OUTPUT(f->type) && inst->state != VPU_CODEC_STATE_DEINIT) { + if (cur_fmt->pixfmt != fmt->pixfmt) { + vdec->reset_codec = true; + vdec->fixed_fmt = false; + } + } + cur_fmt->pixfmt = fmt->pixfmt; + if (V4L2_TYPE_IS_OUTPUT(f->type) || !vdec->fixed_fmt) { + cur_fmt->num_planes = fmt->num_planes; + cur_fmt->flags = fmt->flags; + cur_fmt->width = pixmp->width; + cur_fmt->height = pixmp->height; + for (i = 0; i < fmt->num_planes; i++) { + cur_fmt->sizeimage[i] = pixmp->plane_fmt[i].sizeimage; + cur_fmt->bytesperline[i] = pixmp->plane_fmt[i].bytesperline; + } + if (pixmp->field != V4L2_FIELD_ANY) + cur_fmt->field = pixmp->field; + } else { + pixmp->num_planes = cur_fmt->num_planes; + pixmp->width = cur_fmt->width; + pixmp->height = cur_fmt->height; + for (i = 0; i < pixmp->num_planes; i++) { + pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; + pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + } + pixmp->field = cur_fmt->field; + } + + if (!vdec->fixed_fmt) { + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + vdec->params.codec_format = cur_fmt->pixfmt; + vdec->codec_info.color_primaries = f->fmt.pix_mp.colorspace; + vdec->codec_info.transfer_chars = f->fmt.pix_mp.xfer_func; + vdec->codec_info.matrix_coeffs = f->fmt.pix_mp.ycbcr_enc; + vdec->codec_info.full_range = f->fmt.pix_mp.quantization; + } else { + vdec->params.output_format = cur_fmt->pixfmt; + inst->crop.left = 0; + inst->crop.top = 0; + inst->crop.width = cur_fmt->width; + inst->crop.height = cur_fmt->height; + } + } + + vpu_trace(inst->dev, "[%d] %c%c%c%c %dx%d\n", inst->id, + f->fmt.pix_mp.pixelformat, + f->fmt.pix_mp.pixelformat >> 8, + f->fmt.pix_mp.pixelformat >> 16, + f->fmt.pix_mp.pixelformat >> 24, + f->fmt.pix_mp.width, + f->fmt.pix_mp.height); + + return 0; +} + +static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct vpu_inst *inst = to_inst(file); + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct vdec_t *vdec = inst->priv; + int ret = 0; + + vpu_inst_lock(inst); + ret = vdec_s_fmt_common(inst, f); + if (ret) + goto exit; + + if (V4L2_TYPE_IS_OUTPUT(f->type) && !vdec->fixed_fmt) { + struct v4l2_format fc; + + memset(&fc, 0, sizeof(fc)); + fc.type = inst->cap_format.type; + fc.fmt.pix_mp.pixelformat = inst->cap_format.pixfmt; + fc.fmt.pix_mp.width = pixmp->width; + fc.fmt.pix_mp.height = pixmp->height; + vdec_s_fmt_common(inst, &fc); + } + + f->fmt.pix_mp.colorspace = vdec->codec_info.color_primaries; + f->fmt.pix_mp.xfer_func = vdec->codec_info.transfer_chars; + f->fmt.pix_mp.ycbcr_enc = vdec->codec_info.matrix_coeffs; + f->fmt.pix_mp.quantization = vdec->codec_info.full_range; + +exit: + vpu_inst_unlock(inst); + return ret; +} + +static int vdec_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct vpu_inst *inst = to_inst(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_PADDED: + s->r = inst->crop; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = inst->cap_format.width; + s->r.height = inst->cap_format.height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vdec_drain(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + if (!inst->fh.m2m_ctx) + return 0; + + if (!vdec->drain) + return 0; + + if (!vpu_is_source_empty(inst)) + return 0; + + if (!vdec->params.frame_count) { + vpu_set_last_buffer_dequeued(inst, true); + return 0; + } + + vpu_iface_add_scode(inst, SCODE_PADDING_EOS); + vdec->params.end_flag = 1; + vpu_iface_set_decode_params(inst, &vdec->params, 1); + vdec->drain = 0; + vpu_trace(inst->dev, "[%d] frame_count = %d\n", inst->id, vdec->params.frame_count); + + return 0; +} + +static int vdec_cmd_start(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + switch (inst->state) { + case VPU_CODEC_STATE_STARTED: + case VPU_CODEC_STATE_DRAIN: + case VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE: + vdec_update_state(inst, VPU_CODEC_STATE_ACTIVE, 0); + break; + default: + break; + } + vpu_process_capture_buffer(inst); + if (vdec->eos_received) + vdec_set_last_buffer_dequeued(inst); + return 0; +} + +static int vdec_cmd_stop(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + vpu_trace(inst->dev, "[%d]\n", inst->id); + + if (inst->state == VPU_CODEC_STATE_DEINIT) { + vpu_set_last_buffer_dequeued(inst, true); + } else { + vdec->drain = 1; + vdec_drain(inst); + } + + return 0; +} + +static int vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) +{ + struct vpu_inst *inst = to_inst(file); + int ret; + + ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, cmd); + if (ret) + return ret; + + vpu_inst_lock(inst); + switch (cmd->cmd) { + case V4L2_DEC_CMD_START: + vdec_cmd_start(inst); + break; + case V4L2_DEC_CMD_STOP: + vdec_cmd_stop(inst); + break; + default: + break; + } + vpu_inst_unlock(inst); + + return 0; +} + +static int vdec_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ioctl_ops vdec_ioctl_ops = { + .vidioc_querycap = vdec_querycap, + .vidioc_enum_fmt_vid_cap = vdec_enum_fmt, + .vidioc_enum_fmt_vid_out = vdec_enum_fmt, + .vidioc_g_fmt_vid_cap_mplane = vdec_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vdec_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = vdec_try_fmt, + .vidioc_try_fmt_vid_out_mplane = vdec_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = vdec_s_fmt, + .vidioc_s_fmt_vid_out_mplane = vdec_s_fmt, + .vidioc_g_selection = vdec_g_selection, + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, + .vidioc_decoder_cmd = vdec_decoder_cmd, + .vidioc_subscribe_event = vdec_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + +static bool vdec_check_ready(struct vpu_inst *inst, unsigned int type) +{ + struct vdec_t *vdec = inst->priv; + + if (V4L2_TYPE_IS_OUTPUT(type)) + return true; + + if (vdec->req_frame_count) + return true; + + return false; +} + +static struct vb2_v4l2_buffer *vdec_get_src_buffer(struct vpu_inst *inst, u32 count) +{ + if (count > 1) + vpu_skip_frame(inst, count - 1); + + return vpu_next_src_buf(inst); +} + +static int vdec_frame_decoded(struct vpu_inst *inst, void *arg) +{ + struct vdec_t *vdec = inst->priv; + struct vpu_dec_pic_info *info = arg; + struct vpu_vb2_buffer *vpu_buf; + struct vb2_v4l2_buffer *vbuf; + struct vb2_v4l2_buffer *src_buf; + int ret = 0; + + if (!info || info->id >= ARRAY_SIZE(vdec->slots)) + return -EINVAL; + + vpu_inst_lock(inst); + vpu_buf = vdec->slots[info->id]; + if (!vpu_buf) { + dev_err(inst->dev, "[%d] decoded invalid frame[%d]\n", inst->id, info->id); + ret = -EINVAL; + goto exit; + } + vbuf = &vpu_buf->m2m_buf.vb; + src_buf = vdec_get_src_buffer(inst, info->consumed_count); + if (src_buf) { + v4l2_m2m_buf_copy_metadata(src_buf, vbuf, true); + if (info->consumed_count) { + v4l2_m2m_src_buf_remove(inst->fh.m2m_ctx); + vpu_set_buffer_state(src_buf, VPU_BUF_STATE_IDLE); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + } else { + vpu_set_buffer_state(src_buf, VPU_BUF_STATE_DECODED); + } + } + if (vpu_get_buffer_state(vbuf) == VPU_BUF_STATE_DECODED) + dev_info(inst->dev, "[%d] buf[%d] has been decoded\n", inst->id, info->id); + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_DECODED); + vdec->decoded_frame_count++; +exit: + vpu_inst_unlock(inst); + + return ret; +} + +static struct vpu_vb2_buffer *vdec_find_buffer(struct vpu_inst *inst, u32 luma) +{ + struct vdec_t *vdec = inst->priv; + int i; + + for (i = 0; i < ARRAY_SIZE(vdec->slots); i++) { + if (!vdec->slots[i]) + continue; + if (luma == vdec->slots[i]->luma) + return vdec->slots[i]; + } + + return NULL; +} + +static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) +{ + struct vdec_t *vdec = inst->priv; + struct vpu_vb2_buffer *vpu_buf; + struct vb2_v4l2_buffer *vbuf; + u32 sequence; + + if (!frame) + return; + + vpu_inst_lock(inst); + sequence = vdec->sequence++; + vpu_buf = vdec_find_buffer(inst, frame->luma); + vpu_inst_unlock(inst); + if (!vpu_buf) { + dev_err(inst->dev, "[%d] can't find buffer, id = %d, addr = 0x%x\n", + inst->id, frame->id, frame->luma); + return; + } + if (frame->skipped) { + dev_dbg(inst->dev, "[%d] frame skip\n", inst->id); + return; + } + + vbuf = &vpu_buf->m2m_buf.vb; + if (vbuf->vb2_buf.index != frame->id) + dev_err(inst->dev, "[%d] buffer id(%d, %d) dismatch\n", + inst->id, vbuf->vb2_buf.index, frame->id); + + if (vpu_get_buffer_state(vbuf) != VPU_BUF_STATE_DECODED) + dev_err(inst->dev, "[%d] buffer(%d) ready without decoded\n", inst->id, frame->id); + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_READY); + vb2_set_plane_payload(&vbuf->vb2_buf, 0, inst->cap_format.sizeimage[0]); + vb2_set_plane_payload(&vbuf->vb2_buf, 1, inst->cap_format.sizeimage[1]); + vbuf->field = inst->cap_format.field; + vbuf->sequence = sequence; + dev_dbg(inst->dev, "[%d][OUTPUT TS]%32lld\n", inst->id, vbuf->vb2_buf.timestamp); + + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); + vpu_inst_lock(inst); + vdec->display_frame_count++; + vpu_inst_unlock(inst); + dev_dbg(inst->dev, "[%d] decoded : %d, display : %d, sequence : %d\n", + inst->id, vdec->decoded_frame_count, vdec->display_frame_count, vdec->sequence); +} + +static void vdec_stop_done(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + vpu_inst_lock(inst); + vdec_update_state(inst, VPU_CODEC_STATE_DEINIT, 0); + vdec->seq_hdr_found = 0; + vdec->req_frame_count = 0; + vdec->reset_codec = false; + vdec->fixed_fmt = false; + vdec->params.end_flag = 0; + vdec->drain = 0; + vdec->params.frame_count = 0; + vdec->decoded_frame_count = 0; + vdec->display_frame_count = 0; + vdec->sequence = 0; + vdec->eos_received = 0; + vdec->is_source_changed = false; + vdec->source_change = 0; + inst->total_input_count = 0; + vpu_inst_unlock(inst); +} + +static bool vdec_check_source_change(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + const struct vpu_format *fmt; + int i; + + if (!inst->fh.m2m_ctx) + return false; + + if (vdec->reset_codec) + return false; + + if (!vb2_is_streaming(v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx))) + return true; + fmt = vpu_helper_find_format(inst, inst->cap_format.type, vdec->codec_info.pixfmt); + if (inst->cap_format.pixfmt != vdec->codec_info.pixfmt) + return true; + if (inst->cap_format.width != vdec->codec_info.decoded_width) + return true; + if (inst->cap_format.height != vdec->codec_info.decoded_height) + return true; + if (vpu_get_num_buffers(inst, inst->cap_format.type) < inst->min_buffer_cap) + return true; + if (inst->crop.left != vdec->codec_info.offset_x) + return true; + if (inst->crop.top != vdec->codec_info.offset_y) + return true; + if (inst->crop.width != vdec->codec_info.width) + return true; + if (inst->crop.height != vdec->codec_info.height) + return true; + if (fmt && inst->cap_format.num_planes != fmt->num_planes) + return true; + for (i = 0; i < inst->cap_format.num_planes; i++) { + if (inst->cap_format.bytesperline[i] != vdec->codec_info.bytesperline[i]) + return true; + if (inst->cap_format.sizeimage[i] != vdec->codec_info.sizeimage[i]) + return true; + } + + return false; +} + +static void vdec_init_fmt(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + const struct vpu_format *fmt; + int i; + + fmt = vpu_helper_find_format(inst, inst->cap_format.type, vdec->codec_info.pixfmt); + inst->out_format.width = vdec->codec_info.width; + inst->out_format.height = vdec->codec_info.height; + inst->cap_format.width = vdec->codec_info.decoded_width; + inst->cap_format.height = vdec->codec_info.decoded_height; + inst->cap_format.pixfmt = vdec->codec_info.pixfmt; + if (fmt) { + inst->cap_format.num_planes = fmt->num_planes; + inst->cap_format.flags = fmt->flags; + } + for (i = 0; i < inst->cap_format.num_planes; i++) { + inst->cap_format.bytesperline[i] = vdec->codec_info.bytesperline[i]; + inst->cap_format.sizeimage[i] = vdec->codec_info.sizeimage[i]; + } + if (vdec->codec_info.progressive) + inst->cap_format.field = V4L2_FIELD_NONE; + else + inst->cap_format.field = V4L2_FIELD_SEQ_TB; +} + +static void vdec_init_crop(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + inst->crop.left = vdec->codec_info.offset_x; + inst->crop.top = vdec->codec_info.offset_y; + inst->crop.width = vdec->codec_info.width; + inst->crop.height = vdec->codec_info.height; +} + +static void vdec_init_mbi(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + vdec->mbi.size = vdec->codec_info.mbi_size; + vdec->mbi.max_count = ARRAY_SIZE(vdec->mbi.buffer); + scnprintf(vdec->mbi.name, sizeof(vdec->mbi.name), "mbi"); + vdec->mbi.type = MEM_RES_MBI; + vdec->mbi.tag = vdec->seq_tag; +} + +static void vdec_init_dcp(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + vdec->dcp.size = vdec->codec_info.dcp_size; + vdec->dcp.max_count = ARRAY_SIZE(vdec->dcp.buffer); + scnprintf(vdec->dcp.name, sizeof(vdec->dcp.name), "dcp"); + vdec->dcp.type = MEM_RES_DCP; + vdec->dcp.tag = vdec->seq_tag; +} + +static void vdec_request_one_fs(struct vdec_fs_info *fs) +{ + fs->req_count++; + if (fs->req_count > fs->max_count) + fs->req_count = fs->max_count; +} + +static int vdec_alloc_fs_buffer(struct vpu_inst *inst, struct vdec_fs_info *fs) +{ + struct vpu_buffer *buffer; + + if (!fs->size) + return -EINVAL; + + if (fs->count >= fs->req_count) + return -EINVAL; + + buffer = &fs->buffer[fs->count]; + if (buffer->virt && buffer->length >= fs->size) + return 0; + + vpu_free_dma(buffer); + buffer->length = fs->size; + return vpu_alloc_dma(inst->core, buffer); +} + +static void vdec_alloc_fs(struct vpu_inst *inst, struct vdec_fs_info *fs) +{ + int ret; + + while (fs->count < fs->req_count) { + ret = vdec_alloc_fs_buffer(inst, fs); + if (ret) + break; + fs->count++; + } +} + +static void vdec_clear_fs(struct vdec_fs_info *fs) +{ + u32 i; + + if (!fs) + return; + + for (i = 0; i < ARRAY_SIZE(fs->buffer); i++) + vpu_free_dma(&fs->buffer[i]); + memset(fs, 0, sizeof(*fs)); +} + +static int vdec_response_fs(struct vpu_inst *inst, struct vdec_fs_info *fs) +{ + struct vpu_fs_info info; + int ret; + + if (fs->index >= fs->count) + return 0; + + memset(&info, 0, sizeof(info)); + info.id = fs->index; + info.type = fs->type; + info.tag = fs->tag; + info.luma_addr = fs->buffer[fs->index].phys; + info.luma_size = fs->buffer[fs->index].length; + ret = vpu_session_alloc_fs(inst, &info); + if (ret) + return ret; + + fs->index++; + return 0; +} + +static int vdec_response_frame_abnormal(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + struct vpu_fs_info info; + int ret; + + if (!vdec->req_frame_count) + return 0; + + memset(&info, 0, sizeof(info)); + info.type = MEM_RES_FRAME; + info.tag = vdec->seq_tag + 0xf0; + ret = vpu_session_alloc_fs(inst, &info); + if (ret) + return ret; + vdec->req_frame_count--; + + return 0; +} + +static int vdec_response_frame(struct vpu_inst *inst, struct vb2_v4l2_buffer *vbuf) +{ + struct vdec_t *vdec = inst->priv; + struct vpu_vb2_buffer *vpu_buf; + struct vpu_fs_info info; + int ret; + + if (inst->state != VPU_CODEC_STATE_ACTIVE) + return -EINVAL; + + if (vdec->aborting) + return -EINVAL; + + if (!vdec->req_frame_count) + return -EINVAL; + + if (!vbuf) + return -EINVAL; + + if (vdec->slots[vbuf->vb2_buf.index]) { + dev_err(inst->dev, "[%d] repeat alloc fs %d\n", + inst->id, vbuf->vb2_buf.index); + return -EINVAL; + } + + dev_dbg(inst->dev, "[%d] state = %s, alloc fs %d, tag = 0x%x\n", + inst->id, vpu_codec_state_name(inst->state), vbuf->vb2_buf.index, vdec->seq_tag); + vpu_buf = to_vpu_vb2_buffer(vbuf); + + memset(&info, 0, sizeof(info)); + info.id = vbuf->vb2_buf.index; + info.type = MEM_RES_FRAME; + info.tag = vdec->seq_tag; + info.luma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 0); + info.luma_size = inst->cap_format.sizeimage[0]; + info.chroma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 1); + info.chromau_size = inst->cap_format.sizeimage[1]; + info.bytesperline = inst->cap_format.bytesperline[0]; + ret = vpu_session_alloc_fs(inst, &info); + if (ret) + return ret; + + vpu_buf->tag = info.tag; + vpu_buf->luma = info.luma_addr; + vpu_buf->chroma_u = info.chromau_size; + vpu_buf->chroma_v = 0; + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_INUSE); + vdec->slots[info.id] = vpu_buf; + vdec->req_frame_count--; + + return 0; +} + +static void vdec_response_fs_request(struct vpu_inst *inst, bool force) +{ + struct vdec_t *vdec = inst->priv; + int i; + int ret; + + if (force) { + for (i = vdec->req_frame_count; i > 0; i--) + vdec_response_frame_abnormal(inst); + return; + } + + for (i = vdec->req_frame_count; i > 0; i--) { + ret = vpu_process_capture_buffer(inst); + if (ret) + break; + if (vdec->eos_received) + break; + } + + for (i = vdec->mbi.index; i < vdec->mbi.count; i++) { + if (vdec_response_fs(inst, &vdec->mbi)) + break; + if (vdec->eos_received) + break; + } + for (i = vdec->dcp.index; i < vdec->dcp.count; i++) { + if (vdec_response_fs(inst, &vdec->dcp)) + break; + if (vdec->eos_received) + break; + } +} + +static void vdec_response_fs_release(struct vpu_inst *inst, u32 id, u32 tag) +{ + struct vpu_fs_info info; + + memset(&info, 0, sizeof(info)); + info.id = id; + info.tag = tag; + vpu_session_release_fs(inst, &info); +} + +static void vdec_recycle_buffer(struct vpu_inst *inst, struct vb2_v4l2_buffer *vbuf) +{ + if (!inst->fh.m2m_ctx) + return; + if (vbuf->vb2_buf.state != VB2_BUF_STATE_ACTIVE) + return; + if (vpu_find_buf_by_idx(inst, vbuf->vb2_buf.type, vbuf->vb2_buf.index)) + return; + v4l2_m2m_buf_queue(inst->fh.m2m_ctx, vbuf); +} + +static void vdec_clear_slots(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + struct vpu_vb2_buffer *vpu_buf; + struct vb2_v4l2_buffer *vbuf; + int i; + + for (i = 0; i < ARRAY_SIZE(vdec->slots); i++) { + if (!vdec->slots[i]) + continue; + + vpu_buf = vdec->slots[i]; + vbuf = &vpu_buf->m2m_buf.vb; + + vpu_trace(inst->dev, "clear slot %d\n", i); + vdec_response_fs_release(inst, i, vpu_buf->tag); + vdec_recycle_buffer(inst, vbuf); + vdec->slots[i]->state = VPU_BUF_STATE_IDLE; + vdec->slots[i] = NULL; + } +} + +static void vdec_event_seq_hdr(struct vpu_inst *inst, struct vpu_dec_codec_info *hdr) +{ + struct vdec_t *vdec = inst->priv; + + vpu_inst_lock(inst); + memcpy(&vdec->codec_info, hdr, sizeof(vdec->codec_info)); + + vpu_trace(inst->dev, "[%d] %d x %d, crop : (%d, %d) %d x %d, %d, %d\n", + inst->id, + vdec->codec_info.decoded_width, + vdec->codec_info.decoded_height, + vdec->codec_info.offset_x, + vdec->codec_info.offset_y, + vdec->codec_info.width, + vdec->codec_info.height, + hdr->num_ref_frms, + hdr->num_dpb_frms); + inst->min_buffer_cap = hdr->num_ref_frms + hdr->num_dpb_frms; + vdec->is_source_changed = vdec_check_source_change(inst); + vdec_init_fmt(inst); + vdec_init_crop(inst); + vdec_init_mbi(inst); + vdec_init_dcp(inst); + if (!vdec->seq_hdr_found) { + vdec->seq_tag = vdec->codec_info.tag; + if (vdec->is_source_changed) { + vdec_update_state(inst, VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE, 0); + vdec->source_change++; + vdec_handle_resolution_change(inst); + vdec->is_source_changed = false; + } + } + if (vdec->seq_tag != vdec->codec_info.tag) { + vdec_response_fs_request(inst, true); + vpu_trace(inst->dev, "[%d] seq tag change: %d -> %d\n", + inst->id, vdec->seq_tag, vdec->codec_info.tag); + } + vdec->seq_hdr_found++; + vdec->fixed_fmt = true; + vpu_inst_unlock(inst); +} + +static void vdec_event_resolution_change(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + vpu_trace(inst->dev, "[%d]\n", inst->id); + vpu_inst_lock(inst); + vdec->seq_tag = vdec->codec_info.tag; + vdec_clear_fs(&vdec->mbi); + vdec_clear_fs(&vdec->dcp); + vdec_clear_slots(inst); + vdec_init_mbi(inst); + vdec_init_dcp(inst); + if (vdec->is_source_changed) { + vdec_update_state(inst, VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE, 0); + vdec->source_change++; + vdec_handle_resolution_change(inst); + vdec->is_source_changed = false; + } + vpu_inst_unlock(inst); +} + +static void vdec_event_req_fs(struct vpu_inst *inst, struct vpu_fs_info *fs) +{ + struct vdec_t *vdec = inst->priv; + + if (!fs) + return; + + vpu_inst_lock(inst); + + switch (fs->type) { + case MEM_RES_FRAME: + vdec->req_frame_count++; + break; + case MEM_RES_MBI: + vdec_request_one_fs(&vdec->mbi); + break; + case MEM_RES_DCP: + vdec_request_one_fs(&vdec->dcp); + break; + default: + break; + } + + vdec_alloc_fs(inst, &vdec->mbi); + vdec_alloc_fs(inst, &vdec->dcp); + + vdec_response_fs_request(inst, false); + + vpu_inst_unlock(inst); +} + +static void vdec_evnet_rel_fs(struct vpu_inst *inst, struct vpu_fs_info *fs) +{ + struct vdec_t *vdec = inst->priv; + struct vpu_vb2_buffer *vpu_buf; + struct vb2_v4l2_buffer *vbuf; + + if (!fs || fs->id >= ARRAY_SIZE(vdec->slots)) + return; + if (fs->type != MEM_RES_FRAME) + return; + + if (fs->id >= vpu_get_num_buffers(inst, inst->cap_format.type)) { + dev_err(inst->dev, "[%d] invalid fs(%d) to release\n", inst->id, fs->id); + return; + } + + vpu_inst_lock(inst); + vpu_buf = vdec->slots[fs->id]; + vdec->slots[fs->id] = NULL; + + if (!vpu_buf) { + dev_dbg(inst->dev, "[%d] fs[%d] has bee released\n", inst->id, fs->id); + goto exit; + } + + vbuf = &vpu_buf->m2m_buf.vb; + if (vpu_get_buffer_state(vbuf) == VPU_BUF_STATE_DECODED) { + dev_dbg(inst->dev, "[%d] frame skip\n", inst->id); + vdec->sequence++; + } + + vdec_response_fs_release(inst, fs->id, vpu_buf->tag); + if (vpu_get_buffer_state(vbuf) != VPU_BUF_STATE_READY) + vdec_recycle_buffer(inst, vbuf); + + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_IDLE); + vpu_process_capture_buffer(inst); + +exit: + vpu_inst_unlock(inst); +} + +static void vdec_event_eos(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + vpu_trace(inst->dev, "[%d] input : %d, decoded : %d, display : %d, sequence : %d\n", + inst->id, + vdec->params.frame_count, + vdec->decoded_frame_count, + vdec->display_frame_count, + vdec->sequence); + vpu_inst_lock(inst); + vdec->eos_received++; + vdec->fixed_fmt = false; + inst->min_buffer_cap = VDEC_MIN_BUFFER_CAP; + vdec_set_last_buffer_dequeued(inst); + vpu_inst_unlock(inst); +} + +static void vdec_event_notify(struct vpu_inst *inst, u32 event, void *data) +{ + switch (event) { + case VPU_MSG_ID_SEQ_HDR_FOUND: + vdec_event_seq_hdr(inst, data); + break; + case VPU_MSG_ID_RES_CHANGE: + vdec_event_resolution_change(inst); + break; + case VPU_MSG_ID_FRAME_REQ: + vdec_event_req_fs(inst, data); + break; + case VPU_MSG_ID_FRAME_RELEASE: + vdec_evnet_rel_fs(inst, data); + break; + case VPU_MSG_ID_PIC_EOS: + vdec_event_eos(inst); + break; + default: + break; + } +} + +static int vdec_process_output(struct vpu_inst *inst, struct vb2_buffer *vb) +{ + struct vdec_t *vdec = inst->priv; + struct vb2_v4l2_buffer *vbuf; + struct vpu_rpc_buffer_desc desc; + u32 free_space; + int ret; + + vbuf = to_vb2_v4l2_buffer(vb); + dev_dbg(inst->dev, "[%d] dec output [%d] %d : %ld\n", + inst->id, vbuf->sequence, vb->index, vb2_get_plane_payload(vb, 0)); + + if (inst->state == VPU_CODEC_STATE_DEINIT) + return -EINVAL; + if (vdec->reset_codec) + return -EINVAL; + + if (inst->state == VPU_CODEC_STATE_STARTED) + vdec_update_state(inst, VPU_CODEC_STATE_ACTIVE, 0); + + ret = vpu_iface_get_stream_buffer_desc(inst, &desc); + if (ret) + return ret; + + free_space = vpu_helper_get_free_space(inst); + if (free_space < vb2_get_plane_payload(vb, 0) + 0x40000) + return -ENOMEM; + + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_INUSE); + ret = vpu_iface_input_frame(inst, vb); + if (ret < 0) + return -ENOMEM; + + dev_dbg(inst->dev, "[%d][INPUT TS]%32lld\n", inst->id, vb->timestamp); + vdec->params.frame_count++; + + if (vdec->drain) + vdec_drain(inst); + + return 0; +} + +static int vdec_process_capture(struct vpu_inst *inst, struct vb2_buffer *vb) +{ + struct vdec_t *vdec = inst->priv; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + int ret; + + if (inst->state == VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE) + return -EINVAL; + if (vdec->reset_codec) + return -EINVAL; + + ret = vdec_response_frame(inst, vbuf); + if (ret) + return ret; + v4l2_m2m_dst_buf_remove_by_buf(inst->fh.m2m_ctx, vbuf); + return 0; +} + +static void vdec_on_queue_empty(struct vpu_inst *inst, u32 type) +{ + struct vdec_t *vdec = inst->priv; + + if (V4L2_TYPE_IS_OUTPUT(type)) + return; + + vdec_handle_resolution_change(inst); + if (vdec->eos_received) + vdec_set_last_buffer_dequeued(inst); +} + +static void vdec_abort(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + struct vpu_rpc_buffer_desc desc; + int ret; + + vpu_trace(inst->dev, "[%d] state = %s\n", inst->id, vpu_codec_state_name(inst->state)); + + vdec->aborting = true; + vpu_iface_add_scode(inst, SCODE_PADDING_ABORT); + vdec->params.end_flag = 1; + vpu_iface_set_decode_params(inst, &vdec->params, 1); + + vpu_session_abort(inst); + + ret = vpu_iface_get_stream_buffer_desc(inst, &desc); + if (!ret) + vpu_iface_update_stream_buffer(inst, desc.rptr, 1); + + vpu_session_rst_buf(inst); + vpu_trace(inst->dev, "[%d] input : %d, decoded : %d, display : %d, sequence : %d\n", + inst->id, + vdec->params.frame_count, + vdec->decoded_frame_count, + vdec->display_frame_count, + vdec->sequence); + if (!vdec->seq_hdr_found) + vdec->reset_codec = true; + vdec->params.end_flag = 0; + vdec->drain = 0; + vdec->params.frame_count = 0; + vdec->decoded_frame_count = 0; + vdec->display_frame_count = 0; + vdec->sequence = 0; + vdec->aborting = false; + inst->extra_size = 0; +} + +static void vdec_stop(struct vpu_inst *inst, bool free) +{ + struct vdec_t *vdec = inst->priv; + + vdec_clear_slots(inst); + if (inst->state != VPU_CODEC_STATE_DEINIT) + vpu_session_stop(inst); + vdec_clear_fs(&vdec->mbi); + vdec_clear_fs(&vdec->dcp); + if (free) { + vpu_free_dma(&vdec->udata); + vpu_free_dma(&inst->stream_buffer); + } + vdec_update_state(inst, VPU_CODEC_STATE_DEINIT, 1); + vdec->reset_codec = false; +} + +static void vdec_release(struct vpu_inst *inst) +{ + if (inst->id != VPU_INST_NULL_ID) + vpu_trace(inst->dev, "[%d]\n", inst->id); + vdec_stop(inst, true); +} + +static void vdec_cleanup(struct vpu_inst *inst) +{ + struct vdec_t *vdec; + + if (!inst) + return; + + vdec = inst->priv; + vfree(vdec); + inst->priv = NULL; + vfree(inst); +} + +static void vdec_init_params(struct vdec_t *vdec) +{ + vdec->params.frame_count = 0; + vdec->params.end_flag = 0; +} + +static int vdec_start(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + int stream_buffer_size; + int ret; + + if (inst->state != VPU_CODEC_STATE_DEINIT) + return 0; + + vpu_trace(inst->dev, "[%d]\n", inst->id); + if (!vdec->udata.virt) { + vdec->udata.length = 0x1000; + ret = vpu_alloc_dma(inst->core, &vdec->udata); + if (ret) { + dev_err(inst->dev, "[%d] alloc udata fail\n", inst->id); + goto error; + } + } + + if (!inst->stream_buffer.virt) { + stream_buffer_size = vpu_iface_get_stream_buffer_size(inst->core); + if (stream_buffer_size > 0) { + inst->stream_buffer.length = stream_buffer_size; + ret = vpu_alloc_dma(inst->core, &inst->stream_buffer); + if (ret) { + dev_err(inst->dev, "[%d] alloc stream buffer fail\n", inst->id); + goto error; + } + inst->use_stream_buffer = true; + } + } + + if (inst->use_stream_buffer) + vpu_iface_config_stream_buffer(inst, &inst->stream_buffer); + vpu_iface_init_instance(inst); + vdec->params.udata.base = vdec->udata.phys; + vdec->params.udata.size = vdec->udata.length; + ret = vpu_iface_set_decode_params(inst, &vdec->params, 0); + if (ret) { + dev_err(inst->dev, "[%d] set decode params fail\n", inst->id); + goto error; + } + + vdec_init_params(vdec); + ret = vpu_session_start(inst); + if (ret) { + dev_err(inst->dev, "[%d] start fail\n", inst->id); + goto error; + } + + vdec_update_state(inst, VPU_CODEC_STATE_STARTED, 0); + + return 0; +error: + vpu_free_dma(&vdec->udata); + vpu_free_dma(&inst->stream_buffer); + return ret; +} + +static int vdec_start_session(struct vpu_inst *inst, u32 type) +{ + struct vdec_t *vdec = inst->priv; + int ret = 0; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + if (vdec->reset_codec) + vdec_stop(inst, false); + if (inst->state == VPU_CODEC_STATE_DEINIT) { + ret = vdec_start(inst); + if (ret) + return ret; + } + } + + if (V4L2_TYPE_IS_OUTPUT(type)) { + vdec_update_state(inst, vdec->state, 1); + vdec->eos_received = 0; + vpu_process_output_buffer(inst); + } else { + vdec_cmd_start(inst); + } + if (inst->state == VPU_CODEC_STATE_ACTIVE) + vdec_response_fs_request(inst, false); + + return ret; +} + +static int vdec_stop_session(struct vpu_inst *inst, u32 type) +{ + struct vdec_t *vdec = inst->priv; + + if (inst->state == VPU_CODEC_STATE_DEINIT) + return 0; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + vdec_update_state(inst, VPU_CODEC_STATE_SEEK, 0); + vdec->drain = 0; + } else { + if (inst->state != VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE) { + vdec_abort(inst); + vdec->eos_received = 0; + } + vdec_clear_slots(inst); + } + + return 0; +} + +static int vdec_get_debug_info(struct vpu_inst *inst, char *str, u32 size, u32 i) +{ + struct vdec_t *vdec = inst->priv; + int num = -1; + + switch (i) { + case 0: + num = scnprintf(str, size, + "req_frame_count = %d\ninterlaced = %d\n", + vdec->req_frame_count, + vdec->codec_info.progressive ? 0 : 1); + break; + case 1: + num = scnprintf(str, size, + "mbi: size = 0x%x request = %d, alloc = %d, response = %d\n", + vdec->mbi.size, + vdec->mbi.req_count, + vdec->mbi.count, + vdec->mbi.index); + break; + case 2: + num = scnprintf(str, size, + "dcp: size = 0x%x request = %d, alloc = %d, response = %d\n", + vdec->dcp.size, + vdec->dcp.req_count, + vdec->dcp.count, + vdec->dcp.index); + break; + case 3: + num = scnprintf(str, size, "input_frame_count = %d\n", vdec->params.frame_count); + break; + case 4: + num = scnprintf(str, size, "decoded_frame_count = %d\n", vdec->decoded_frame_count); + break; + case 5: + num = scnprintf(str, size, "display_frame_count = %d\n", vdec->display_frame_count); + break; + case 6: + num = scnprintf(str, size, "sequence = %d\n", vdec->sequence); + break; + case 7: + num = scnprintf(str, size, "drain = %d, eos = %d, source_change = %d\n", + vdec->drain, vdec->eos_received, vdec->source_change); + break; + case 8: + num = scnprintf(str, size, "fps = %d/%d\n", + vdec->codec_info.frame_rate.numerator, + vdec->codec_info.frame_rate.denominator); + break; + case 9: + num = scnprintf(str, size, "colorspace: %d, %d, %d, %d (%d)\n", + vdec->codec_info.color_primaries, + vdec->codec_info.transfer_chars, + vdec->codec_info.matrix_coeffs, + vdec->codec_info.full_range, + vdec->codec_info.vui_present); + break; + default: + break; + } + + return num; +} + +static struct vpu_inst_ops vdec_inst_ops = { + .ctrl_init = vdec_ctrl_init, + .check_ready = vdec_check_ready, + .buf_done = vdec_buf_done, + .get_one_frame = vdec_frame_decoded, + .stop_done = vdec_stop_done, + .event_notify = vdec_event_notify, + .release = vdec_release, + .cleanup = vdec_cleanup, + .start = vdec_start_session, + .stop = vdec_stop_session, + .process_output = vdec_process_output, + .process_capture = vdec_process_capture, + .on_queue_empty = vdec_on_queue_empty, + .get_debug_info = vdec_get_debug_info, + .wait_prepare = vpu_inst_unlock, + .wait_finish = vpu_inst_lock, +}; + +static void vdec_init(struct file *file) +{ + struct vpu_inst *inst = to_inst(file); + struct v4l2_format f; + + memset(&f, 0, sizeof(f)); + f.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + f.fmt.pix_mp.width = 1280; + f.fmt.pix_mp.height = 720; + f.fmt.pix_mp.field = V4L2_FIELD_NONE; + vdec_s_fmt(file, &inst->fh, &f); + + memset(&f, 0, sizeof(f)); + f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M_8L128; + f.fmt.pix_mp.width = 1280; + f.fmt.pix_mp.height = 720; + f.fmt.pix_mp.field = V4L2_FIELD_NONE; + vdec_s_fmt(file, &inst->fh, &f); +} + +static int vdec_open(struct file *file) +{ + struct vpu_inst *inst; + struct vdec_t *vdec; + int ret; + + inst = vzalloc(sizeof(*inst)); + if (!inst) + return -ENOMEM; + + vdec = vzalloc(sizeof(*vdec)); + if (!vdec) { + vfree(inst); + return -ENOMEM; + } + + inst->ops = &vdec_inst_ops; + inst->formats = vdec_formats; + inst->type = VPU_CORE_TYPE_DEC; + inst->priv = vdec; + + ret = vpu_v4l2_open(file, inst); + if (ret) + return ret; + + vdec->fixed_fmt = false; + vdec->state = VPU_CODEC_STATE_ACTIVE; + inst->min_buffer_cap = VDEC_MIN_BUFFER_CAP; + inst->min_buffer_out = VDEC_MIN_BUFFER_OUT; + vdec_init(file); + + return 0; +} + +static const struct v4l2_file_operations vdec_fops = { + .owner = THIS_MODULE, + .open = vdec_open, + .release = vpu_v4l2_close, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +}; + +const struct v4l2_ioctl_ops *vdec_get_ioctl_ops(void) +{ + return &vdec_ioctl_ops; +} + +const struct v4l2_file_operations *vdec_get_fops(void) +{ + return &vdec_fops; +} diff --git a/drivers/media/platform/amphion/venc.c b/drivers/media/platform/amphion/venc.c new file mode 100644 index 000000000..1df2b35c1 --- /dev/null +++ b/drivers/media/platform/amphion/venc.c @@ -0,0 +1,1356 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_defs.h" +#include "vpu_core.h" +#include "vpu_helpers.h" +#include "vpu_v4l2.h" +#include "vpu_cmds.h" +#include "vpu_rpc.h" + +#define VENC_OUTPUT_ENABLE BIT(0) +#define VENC_CAPTURE_ENABLE BIT(1) +#define VENC_ENABLE_MASK (VENC_OUTPUT_ENABLE | VENC_CAPTURE_ENABLE) +#define VENC_MAX_BUF_CNT 8 +#define VENC_MIN_BUFFER_OUT 6 +#define VENC_MIN_BUFFER_CAP 6 + +struct venc_t { + struct vpu_encode_params params; + u32 request_key_frame; + u32 input_ready; + u32 cpb_size; + bool bitrate_change; + + struct vpu_buffer enc[VENC_MAX_BUF_CNT]; + struct vpu_buffer ref[VENC_MAX_BUF_CNT]; + struct vpu_buffer act[VENC_MAX_BUF_CNT]; + struct list_head frames; + u32 frame_count; + u32 encode_count; + u32 ready_count; + u32 enable; + u32 stopped; + + u32 skipped_count; + u32 skipped_bytes; + + wait_queue_head_t wq; +}; + +struct venc_frame_t { + struct list_head list; + struct vpu_enc_pic_info info; + u32 bytesused; + s64 timestamp; +}; + +static const struct vpu_format venc_formats[] = { + { + .pixfmt = V4L2_PIX_FMT_NV12M, + .num_planes = 2, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + { + .pixfmt = V4L2_PIX_FMT_H264, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, + {0, 0, 0, 0}, +}; + +static int venc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + strscpy(cap->driver, "amphion-vpu", sizeof(cap->driver)); + strscpy(cap->card, "amphion vpu encoder", sizeof(cap->card)); + strscpy(cap->bus_info, "platform: amphion-vpu", sizeof(cap->bus_info)); + + return 0; +} + +static int venc_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct vpu_inst *inst = to_inst(file); + const struct vpu_format *fmt; + + memset(f->reserved, 0, sizeof(f->reserved)); + fmt = vpu_helper_enum_format(inst, f->type, f->index); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixfmt; + f->flags = fmt->flags; + + return 0; +} + +static int venc_enum_framesizes(struct file *file, void *fh, struct v4l2_frmsizeenum *fsize) +{ + struct vpu_inst *inst = to_inst(file); + const struct vpu_core_resources *res; + + if (!fsize || fsize->index) + return -EINVAL; + + if (!vpu_helper_find_format(inst, 0, fsize->pixel_format)) + return -EINVAL; + + res = vpu_get_resource(inst); + if (!res) + return -EINVAL; + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.max_width = res->max_width; + fsize->stepwise.max_height = res->max_height; + fsize->stepwise.min_width = res->min_width; + fsize->stepwise.min_height = res->min_height; + fsize->stepwise.step_width = res->step_width; + fsize->stepwise.step_height = res->step_height; + + return 0; +} + +static int venc_enum_frameintervals(struct file *file, void *fh, struct v4l2_frmivalenum *fival) +{ + struct vpu_inst *inst = to_inst(file); + const struct vpu_core_resources *res; + + if (!fival || fival->index) + return -EINVAL; + + if (!vpu_helper_find_format(inst, 0, fival->pixel_format)) + return -EINVAL; + + if (!fival->width || !fival->height) + return -EINVAL; + + res = vpu_get_resource(inst); + if (!res) + return -EINVAL; + if (fival->width < res->min_width || fival->width > res->max_width || + fival->height < res->min_height || fival->height > res->max_height) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + fival->stepwise.min.numerator = 1; + fival->stepwise.min.denominator = USHRT_MAX; + fival->stepwise.max.numerator = USHRT_MAX; + fival->stepwise.max.denominator = 1; + fival->stepwise.step.numerator = 1; + fival->stepwise.step.denominator = 1; + + return 0; +} + +static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct vpu_inst *inst = to_inst(file); + struct venc_t *venc = inst->priv; + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct vpu_format *cur_fmt; + int i; + + cur_fmt = vpu_get_format(inst, f->type); + + pixmp->pixelformat = cur_fmt->pixfmt; + pixmp->num_planes = cur_fmt->num_planes; + pixmp->width = cur_fmt->width; + pixmp->height = cur_fmt->height; + pixmp->field = cur_fmt->field; + pixmp->flags = cur_fmt->flags; + for (i = 0; i < pixmp->num_planes; i++) { + pixmp->plane_fmt[i].bytesperline = cur_fmt->bytesperline[i]; + pixmp->plane_fmt[i].sizeimage = cur_fmt->sizeimage[i]; + } + + f->fmt.pix_mp.colorspace = venc->params.color.primaries; + f->fmt.pix_mp.xfer_func = venc->params.color.transfer; + f->fmt.pix_mp.ycbcr_enc = venc->params.color.matrix; + f->fmt.pix_mp.quantization = venc->params.color.full_range; + + return 0; +} + +static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct vpu_inst *inst = to_inst(file); + + vpu_try_fmt_common(inst, f); + + return 0; +} + +static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct vpu_inst *inst = to_inst(file); + const struct vpu_format *fmt; + struct vpu_format *cur_fmt; + struct vb2_queue *q; + struct venc_t *venc = inst->priv; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + int i; + + q = v4l2_m2m_get_vq(inst->fh.m2m_ctx, f->type); + if (!q) + return -EINVAL; + if (vb2_is_busy(q)) + return -EBUSY; + + fmt = vpu_try_fmt_common(inst, f); + if (!fmt) + return -EINVAL; + + cur_fmt = vpu_get_format(inst, f->type); + + cur_fmt->pixfmt = fmt->pixfmt; + cur_fmt->num_planes = fmt->num_planes; + cur_fmt->flags = fmt->flags; + cur_fmt->width = pix_mp->width; + cur_fmt->height = pix_mp->height; + for (i = 0; i < fmt->num_planes; i++) { + cur_fmt->sizeimage[i] = pix_mp->plane_fmt[i].sizeimage; + cur_fmt->bytesperline[i] = pix_mp->plane_fmt[i].bytesperline; + } + + if (pix_mp->field != V4L2_FIELD_ANY) + cur_fmt->field = pix_mp->field; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + venc->params.input_format = cur_fmt->pixfmt; + venc->params.src_stride = cur_fmt->bytesperline[0]; + venc->params.src_width = cur_fmt->width; + venc->params.src_height = cur_fmt->height; + venc->params.crop.left = 0; + venc->params.crop.top = 0; + venc->params.crop.width = cur_fmt->width; + venc->params.crop.height = cur_fmt->height; + } else { + venc->params.codec_format = cur_fmt->pixfmt; + venc->params.out_width = cur_fmt->width; + venc->params.out_height = cur_fmt->height; + } + + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + if (!vpu_color_check_primaries(pix_mp->colorspace)) { + venc->params.color.primaries = pix_mp->colorspace; + vpu_color_get_default(venc->params.color.primaries, + &venc->params.color.transfer, + &venc->params.color.matrix, + &venc->params.color.full_range); + } + if (!vpu_color_check_transfers(pix_mp->xfer_func)) + venc->params.color.transfer = pix_mp->xfer_func; + if (!vpu_color_check_matrix(pix_mp->ycbcr_enc)) + venc->params.color.matrix = pix_mp->ycbcr_enc; + if (!vpu_color_check_full_range(pix_mp->quantization)) + venc->params.color.full_range = pix_mp->quantization; + } + + pix_mp->colorspace = venc->params.color.primaries; + pix_mp->xfer_func = venc->params.color.transfer; + pix_mp->ycbcr_enc = venc->params.color.matrix; + pix_mp->quantization = venc->params.color.full_range; + + return 0; +} + +static int venc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *parm) +{ + struct vpu_inst *inst = to_inst(file); + struct venc_t *venc = inst->priv; + struct v4l2_fract *timeperframe; + + if (!parm) + return -EINVAL; + + if (!V4L2_TYPE_IS_OUTPUT(parm->type)) + return -EINVAL; + + if (!vpu_helper_check_type(inst, parm->type)) + return -EINVAL; + + timeperframe = &parm->parm.capture.timeperframe; + parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + parm->parm.capture.readbuffers = 0; + timeperframe->numerator = venc->params.frame_rate.numerator; + timeperframe->denominator = venc->params.frame_rate.denominator; + + return 0; +} + +static int venc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *parm) +{ + struct vpu_inst *inst = to_inst(file); + struct venc_t *venc = inst->priv; + struct v4l2_fract *timeperframe; + unsigned long n, d; + + if (!parm) + return -EINVAL; + + if (!V4L2_TYPE_IS_OUTPUT(parm->type)) + return -EINVAL; + + if (!vpu_helper_check_type(inst, parm->type)) + return -EINVAL; + + timeperframe = &parm->parm.capture.timeperframe; + if (!timeperframe->numerator) + timeperframe->numerator = venc->params.frame_rate.numerator; + if (!timeperframe->denominator) + timeperframe->denominator = venc->params.frame_rate.denominator; + + venc->params.frame_rate.numerator = timeperframe->numerator; + venc->params.frame_rate.denominator = timeperframe->denominator; + + rational_best_approximation(venc->params.frame_rate.numerator, + venc->params.frame_rate.denominator, + venc->params.frame_rate.numerator, + venc->params.frame_rate.denominator, + &n, &d); + venc->params.frame_rate.numerator = n; + venc->params.frame_rate.denominator = d; + + parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + memset(parm->parm.capture.reserved, 0, sizeof(parm->parm.capture.reserved)); + + return 0; +} + +static int venc_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct vpu_inst *inst = to_inst(file); + struct venc_t *venc = inst->priv; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = inst->out_format.width; + s->r.height = inst->out_format.height; + break; + case V4L2_SEL_TGT_CROP: + s->r = venc->params.crop; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int venc_valid_crop(struct venc_t *venc, const struct vpu_core_resources *res) +{ + struct v4l2_rect *rect = NULL; + u32 min_width; + u32 min_height; + u32 src_width; + u32 src_height; + + rect = &venc->params.crop; + min_width = res->min_width; + min_height = res->min_height; + src_width = venc->params.src_width; + src_height = venc->params.src_height; + + if (rect->width == 0 || rect->height == 0) + return -EINVAL; + if (rect->left > src_width - min_width || rect->top > src_height - min_height) + return -EINVAL; + + rect->width = min(rect->width, src_width - rect->left); + rect->width = max_t(u32, rect->width, min_width); + + rect->height = min(rect->height, src_height - rect->top); + rect->height = max_t(u32, rect->height, min_height); + + return 0; +} + +static int venc_s_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct vpu_inst *inst = to_inst(file); + const struct vpu_core_resources *res; + struct venc_t *venc = inst->priv; + + res = vpu_get_resource(inst); + if (!res) + return -EINVAL; + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + if (s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + venc->params.crop.left = ALIGN(s->r.left, res->step_width); + venc->params.crop.top = ALIGN(s->r.top, res->step_height); + venc->params.crop.width = ALIGN(s->r.width, res->step_width); + venc->params.crop.height = ALIGN(s->r.height, res->step_height); + if (venc_valid_crop(venc, res)) { + venc->params.crop.left = 0; + venc->params.crop.top = 0; + venc->params.crop.width = venc->params.src_width; + venc->params.crop.height = venc->params.src_height; + } + + inst->crop = venc->params.crop; + + return 0; +} + +static int venc_drain(struct vpu_inst *inst) +{ + struct venc_t *venc = inst->priv; + int ret; + + if (!inst->fh.m2m_ctx) + return 0; + + if (inst->state != VPU_CODEC_STATE_DRAIN) + return 0; + + if (!vpu_is_source_empty(inst)) + return 0; + + if (!venc->input_ready) + return 0; + + venc->input_ready = false; + vpu_trace(inst->dev, "[%d]\n", inst->id); + ret = vpu_session_stop(inst); + if (ret) + return ret; + inst->state = VPU_CODEC_STATE_STOP; + wake_up_all(&venc->wq); + + return 0; +} + +static int venc_request_eos(struct vpu_inst *inst) +{ + inst->state = VPU_CODEC_STATE_DRAIN; + venc_drain(inst); + + return 0; +} + +static int venc_encoder_cmd(struct file *file, void *fh, struct v4l2_encoder_cmd *cmd) +{ + struct vpu_inst *inst = to_inst(file); + int ret; + + ret = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, cmd); + if (ret) + return ret; + + vpu_inst_lock(inst); + if (cmd->cmd == V4L2_ENC_CMD_STOP) { + if (inst->state == VPU_CODEC_STATE_DEINIT) + vpu_set_last_buffer_dequeued(inst, true); + else + venc_request_eos(inst); + } + vpu_inst_unlock(inst); + + return 0; +} + +static int venc_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_ioctl_ops venc_ioctl_ops = { + .vidioc_querycap = venc_querycap, + .vidioc_enum_fmt_vid_cap = venc_enum_fmt, + .vidioc_enum_fmt_vid_out = venc_enum_fmt, + .vidioc_enum_framesizes = venc_enum_framesizes, + .vidioc_enum_frameintervals = venc_enum_frameintervals, + .vidioc_g_fmt_vid_cap_mplane = venc_g_fmt, + .vidioc_g_fmt_vid_out_mplane = venc_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = venc_try_fmt, + .vidioc_try_fmt_vid_out_mplane = venc_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = venc_s_fmt, + .vidioc_s_fmt_vid_out_mplane = venc_s_fmt, + .vidioc_g_parm = venc_g_parm, + .vidioc_s_parm = venc_s_parm, + .vidioc_g_selection = venc_g_selection, + .vidioc_s_selection = venc_s_selection, + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, + .vidioc_encoder_cmd = venc_encoder_cmd, + .vidioc_subscribe_event = venc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + +static int venc_op_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vpu_inst *inst = ctrl_to_inst(ctrl); + struct venc_t *venc = inst->priv; + int ret = 0; + + vpu_inst_lock(inst); + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + venc->params.profile = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + venc->params.level = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + venc->params.rc_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + venc->params.rc_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + if (ctrl->val != venc->params.bitrate) + venc->bitrate_change = true; + venc->params.bitrate = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: + venc->params.bitrate_max = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + venc->params.gop_length = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + venc->params.bframes = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + venc->params.i_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + venc->params.p_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + venc->params.b_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: + venc->request_key_frame = 1; + break; + case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE: + venc->cpb_size = ctrl->val * 1024; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE: + venc->params.sar.enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC: + venc->params.sar.idc = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH: + venc->params.sar.width = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT: + venc->params.sar.height = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + break; + default: + ret = -EINVAL; + break; + } + vpu_inst_unlock(inst); + + return ret; +} + +static const struct v4l2_ctrl_ops venc_ctrl_ops = { + .s_ctrl = venc_op_s_ctrl, + .g_volatile_ctrl = vpu_helper_g_volatile_ctrl, +}; + +static int venc_ctrl_init(struct vpu_inst *inst) +{ + struct v4l2_ctrl *ctrl; + int ret; + + ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, 20); + if (ret) + return ret; + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + 0x0, + V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, 0, 1, 1, 1); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, + ~((1 << V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) | + (1 << V4L2_MPEG_VIDEO_BITRATE_MODE_CBR)), + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, + BITRATE_MIN, + BITRATE_MAX, + BITRATE_STEP, + BITRATE_DEFAULT); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, + BITRATE_MIN, BITRATE_MAX, + BITRATE_STEP, + BITRATE_DEFAULT_PEAK); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 1, 8000, 1, 30); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_B_FRAMES, 0, 4, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, 1, 51, 1, 26); + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, 1, 51, 1, 28); + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP, 1, 51, 1, 30); + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, 0, 0, 0, 0); + ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 2); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, 1, 32, 1, 2); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE, 64, 10240, 1, 1024); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE, 0, 1, 1, 1); + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC, + V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED, + 0x0, + V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_1x1); + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH, + 0, USHRT_MAX, 1, 1); + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT, + 0, USHRT_MAX, 1, 1); + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEADER_MODE, + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + ~(1 << V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME), + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME); + + if (inst->ctrl_handler.error) { + ret = inst->ctrl_handler.error; + v4l2_ctrl_handler_free(&inst->ctrl_handler); + return ret; + } + + ret = v4l2_ctrl_handler_setup(&inst->ctrl_handler); + if (ret) { + dev_err(inst->dev, "[%d] setup ctrls fail, ret = %d\n", inst->id, ret); + v4l2_ctrl_handler_free(&inst->ctrl_handler); + return ret; + } + + return 0; +} + +static bool venc_check_ready(struct vpu_inst *inst, unsigned int type) +{ + struct venc_t *venc = inst->priv; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + if (vpu_helper_get_free_space(inst) < venc->cpb_size) + return false; + return venc->input_ready; + } + + if (list_empty(&venc->frames)) + return false; + return true; +} + +static u32 venc_get_enable_mask(u32 type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return VENC_OUTPUT_ENABLE; + else + return VENC_CAPTURE_ENABLE; +} + +static void venc_set_enable(struct venc_t *venc, u32 type, int enable) +{ + u32 mask = venc_get_enable_mask(type); + + if (enable) + venc->enable |= mask; + else + venc->enable &= ~mask; +} + +static u32 venc_get_enable(struct venc_t *venc, u32 type) +{ + return venc->enable & venc_get_enable_mask(type); +} + +static void venc_input_done(struct vpu_inst *inst) +{ + struct venc_t *venc = inst->priv; + + vpu_inst_lock(inst); + venc->input_ready = true; + vpu_process_output_buffer(inst); + if (inst->state == VPU_CODEC_STATE_DRAIN) + venc_drain(inst); + vpu_inst_unlock(inst); +} + +/* + * It's hardware limitation, that there may be several bytes + * redundant data at the beginning of frame. + * For android platform, the redundant data may cause cts test fail + * So driver will strip them + */ +static int venc_precheck_encoded_frame(struct vpu_inst *inst, struct venc_frame_t *frame) +{ + struct venc_t *venc; + int skipped; + + if (!frame || !frame->bytesused) + return -EINVAL; + + venc = inst->priv; + skipped = vpu_helper_find_startcode(&inst->stream_buffer, + inst->cap_format.pixfmt, + frame->info.wptr - inst->stream_buffer.phys, + frame->bytesused); + if (skipped > 0) { + frame->bytesused -= skipped; + frame->info.wptr = vpu_helper_step_walk(&inst->stream_buffer, + frame->info.wptr, skipped); + venc->skipped_bytes += skipped; + venc->skipped_count++; + } + + return 0; +} + +static int venc_get_one_encoded_frame(struct vpu_inst *inst, + struct venc_frame_t *frame, + struct vb2_v4l2_buffer *vbuf) +{ + struct venc_t *venc = inst->priv; + struct vb2_v4l2_buffer *src_buf; + + if (!vbuf) + return -EAGAIN; + + src_buf = vpu_find_buf_by_sequence(inst, inst->out_format.type, frame->info.frame_id); + if (src_buf) { + v4l2_m2m_buf_copy_metadata(src_buf, vbuf, true); + vpu_set_buffer_state(src_buf, VPU_BUF_STATE_IDLE); + v4l2_m2m_src_buf_remove_by_buf(inst->fh.m2m_ctx, src_buf); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + } else { + vbuf->vb2_buf.timestamp = frame->info.timestamp; + } + if (!venc_get_enable(inst->priv, vbuf->vb2_buf.type)) { + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + return 0; + } + if (frame->bytesused > vbuf->vb2_buf.planes[0].length) { + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + return -ENOMEM; + } + + venc_precheck_encoded_frame(inst, frame); + + if (frame->bytesused) { + u32 rptr = frame->info.wptr; + void *dst = vb2_plane_vaddr(&vbuf->vb2_buf, 0); + + vpu_helper_copy_from_stream_buffer(&inst->stream_buffer, + &rptr, frame->bytesused, dst); + vpu_iface_update_stream_buffer(inst, rptr, 0); + } + vb2_set_plane_payload(&vbuf->vb2_buf, 0, frame->bytesused); + vbuf->sequence = frame->info.frame_id; + vbuf->field = inst->cap_format.field; + vbuf->flags |= frame->info.pic_type; + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_IDLE); + dev_dbg(inst->dev, "[%d][OUTPUT TS]%32lld\n", inst->id, vbuf->vb2_buf.timestamp); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); + venc->ready_count++; + + if (vbuf->flags & V4L2_BUF_FLAG_KEYFRAME) + dev_dbg(inst->dev, "[%d][%d]key frame\n", inst->id, frame->info.frame_id); + + return 0; +} + +static int venc_get_encoded_frames(struct vpu_inst *inst) +{ + struct venc_t *venc; + struct venc_frame_t *frame; + struct venc_frame_t *tmp; + + if (!inst->fh.m2m_ctx) + return 0; + venc = inst->priv; + list_for_each_entry_safe(frame, tmp, &venc->frames, list) { + if (venc_get_one_encoded_frame(inst, frame, + v4l2_m2m_dst_buf_remove(inst->fh.m2m_ctx))) + break; + list_del_init(&frame->list); + vfree(frame); + } + + return 0; +} + +static int venc_frame_encoded(struct vpu_inst *inst, void *arg) +{ + struct vpu_enc_pic_info *info = arg; + struct venc_frame_t *frame; + struct venc_t *venc; + int ret = 0; + + if (!info) + return -EINVAL; + venc = inst->priv; + frame = vzalloc(sizeof(*frame)); + if (!frame) + return -ENOMEM; + + memcpy(&frame->info, info, sizeof(frame->info)); + frame->bytesused = info->frame_size; + + vpu_inst_lock(inst); + list_add_tail(&frame->list, &venc->frames); + venc->encode_count++; + venc_get_encoded_frames(inst); + vpu_inst_unlock(inst); + + return ret; +} + +static void venc_set_last_buffer_dequeued(struct vpu_inst *inst) +{ + struct venc_t *venc = inst->priv; + + if (venc->stopped && list_empty(&venc->frames)) + vpu_set_last_buffer_dequeued(inst, true); +} + +static void venc_stop_done(struct vpu_inst *inst) +{ + struct venc_t *venc = inst->priv; + + vpu_inst_lock(inst); + venc->stopped = true; + venc_set_last_buffer_dequeued(inst); + vpu_inst_unlock(inst); + + wake_up_all(&venc->wq); +} + +static void venc_event_notify(struct vpu_inst *inst, u32 event, void *data) +{ +} + +static void venc_release(struct vpu_inst *inst) +{ +} + +static void venc_cleanup(struct vpu_inst *inst) +{ + struct venc_t *venc; + + if (!inst) + return; + + venc = inst->priv; + vfree(venc); + inst->priv = NULL; + vfree(inst); +} + +static int venc_start_session(struct vpu_inst *inst, u32 type) +{ + struct venc_t *venc = inst->priv; + int stream_buffer_size; + int ret; + + venc_set_enable(venc, type, 1); + if ((venc->enable & VENC_ENABLE_MASK) != VENC_ENABLE_MASK) + return 0; + + vpu_iface_init_instance(inst); + stream_buffer_size = vpu_iface_get_stream_buffer_size(inst->core); + if (stream_buffer_size > 0) { + inst->stream_buffer.length = max_t(u32, stream_buffer_size, venc->cpb_size * 3); + ret = vpu_alloc_dma(inst->core, &inst->stream_buffer); + if (ret) + goto error; + + inst->use_stream_buffer = true; + vpu_iface_config_stream_buffer(inst, &inst->stream_buffer); + } + + ret = vpu_iface_set_encode_params(inst, &venc->params, 0); + if (ret) + goto error; + ret = vpu_session_configure_codec(inst); + if (ret) + goto error; + + inst->state = VPU_CODEC_STATE_CONFIGURED; + /*vpu_iface_config_memory_resource*/ + + /*config enc expert mode parameter*/ + ret = vpu_iface_set_encode_params(inst, &venc->params, 1); + if (ret) + goto error; + + ret = vpu_session_start(inst); + if (ret) + goto error; + inst->state = VPU_CODEC_STATE_STARTED; + + venc->bitrate_change = false; + venc->input_ready = true; + venc->frame_count = 0; + venc->encode_count = 0; + venc->ready_count = 0; + venc->stopped = false; + vpu_process_output_buffer(inst); + if (venc->frame_count == 0) + dev_err(inst->dev, "[%d] there is no input when starting\n", inst->id); + + return 0; +error: + venc_set_enable(venc, type, 0); + inst->state = VPU_CODEC_STATE_DEINIT; + + vpu_free_dma(&inst->stream_buffer); + return ret; +} + +static void venc_cleanup_mem_resource(struct vpu_inst *inst) +{ + struct venc_t *venc; + u32 i; + + venc = inst->priv; + + for (i = 0; i < ARRAY_SIZE(venc->enc); i++) + vpu_free_dma(&venc->enc[i]); + for (i = 0; i < ARRAY_SIZE(venc->ref); i++) + vpu_free_dma(&venc->ref[i]); +} + +static void venc_request_mem_resource(struct vpu_inst *inst, + u32 enc_frame_size, + u32 enc_frame_num, + u32 ref_frame_size, + u32 ref_frame_num, + u32 act_frame_size, + u32 act_frame_num) +{ + struct venc_t *venc; + u32 i; + int ret; + + venc = inst->priv; + if (enc_frame_num > ARRAY_SIZE(venc->enc)) { + dev_err(inst->dev, "[%d] enc num(%d) is out of range\n", inst->id, enc_frame_num); + return; + } + if (ref_frame_num > ARRAY_SIZE(venc->ref)) { + dev_err(inst->dev, "[%d] ref num(%d) is out of range\n", inst->id, ref_frame_num); + return; + } + if (act_frame_num > ARRAY_SIZE(venc->act)) { + dev_err(inst->dev, "[%d] act num(%d) is out of range\n", inst->id, act_frame_num); + return; + } + + for (i = 0; i < enc_frame_num; i++) { + venc->enc[i].length = enc_frame_size; + ret = vpu_alloc_dma(inst->core, &venc->enc[i]); + if (ret) { + venc_cleanup_mem_resource(inst); + return; + } + } + for (i = 0; i < ref_frame_num; i++) { + venc->ref[i].length = ref_frame_size; + ret = vpu_alloc_dma(inst->core, &venc->ref[i]); + if (ret) { + venc_cleanup_mem_resource(inst); + return; + } + } + if (act_frame_num != 1 || act_frame_size > inst->act.length) { + venc_cleanup_mem_resource(inst); + return; + } + venc->act[0].length = act_frame_size; + venc->act[0].phys = inst->act.phys; + venc->act[0].virt = inst->act.virt; + + for (i = 0; i < enc_frame_num; i++) + vpu_iface_config_memory_resource(inst, MEM_RES_ENC, i, &venc->enc[i]); + for (i = 0; i < ref_frame_num; i++) + vpu_iface_config_memory_resource(inst, MEM_RES_REF, i, &venc->ref[i]); + for (i = 0; i < act_frame_num; i++) + vpu_iface_config_memory_resource(inst, MEM_RES_ACT, i, &venc->act[i]); +} + +static void venc_cleanup_frames(struct venc_t *venc) +{ + struct venc_frame_t *frame; + struct venc_frame_t *tmp; + + list_for_each_entry_safe(frame, tmp, &venc->frames, list) { + list_del_init(&frame->list); + vfree(frame); + } +} + +static int venc_stop_session(struct vpu_inst *inst, u32 type) +{ + struct venc_t *venc = inst->priv; + + venc_set_enable(venc, type, 0); + if (venc->enable & VENC_ENABLE_MASK) + return 0; + + if (inst->state == VPU_CODEC_STATE_DEINIT) + return 0; + + if (inst->state != VPU_CODEC_STATE_STOP) + venc_request_eos(inst); + + call_void_vop(inst, wait_prepare); + if (!wait_event_timeout(venc->wq, venc->stopped, VPU_TIMEOUT)) { + set_bit(inst->id, &inst->core->hang_mask); + vpu_session_debug(inst); + } + call_void_vop(inst, wait_finish); + + inst->state = VPU_CODEC_STATE_DEINIT; + venc_cleanup_frames(inst->priv); + vpu_free_dma(&inst->stream_buffer); + venc_cleanup_mem_resource(inst); + + return 0; +} + +static int venc_process_output(struct vpu_inst *inst, struct vb2_buffer *vb) +{ + struct venc_t *venc = inst->priv; + struct vb2_v4l2_buffer *vbuf; + u32 flags; + + if (inst->state == VPU_CODEC_STATE_DEINIT) + return -EINVAL; + + vbuf = to_vb2_v4l2_buffer(vb); + if (inst->state == VPU_CODEC_STATE_STARTED) + inst->state = VPU_CODEC_STATE_ACTIVE; + + flags = vbuf->flags; + if (venc->request_key_frame) { + vbuf->flags |= V4L2_BUF_FLAG_KEYFRAME; + venc->request_key_frame = 0; + } + if (venc->bitrate_change) { + vpu_session_update_parameters(inst, &venc->params); + venc->bitrate_change = false; + } + dev_dbg(inst->dev, "[%d][INPUT TS]%32lld\n", inst->id, vb->timestamp); + vpu_iface_input_frame(inst, vb); + vbuf->flags = flags; + venc->input_ready = false; + venc->frame_count++; + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_INUSE); + + return 0; +} + +static int venc_process_capture(struct vpu_inst *inst, struct vb2_buffer *vb) +{ + struct venc_t *venc; + struct venc_frame_t *frame = NULL; + struct vb2_v4l2_buffer *vbuf; + int ret; + + venc = inst->priv; + if (list_empty(&venc->frames)) + return -EINVAL; + + frame = list_first_entry(&venc->frames, struct venc_frame_t, list); + vbuf = to_vb2_v4l2_buffer(vb); + v4l2_m2m_dst_buf_remove_by_buf(inst->fh.m2m_ctx, vbuf); + ret = venc_get_one_encoded_frame(inst, frame, vbuf); + if (ret) + return ret; + + list_del_init(&frame->list); + vfree(frame); + return 0; +} + +static void venc_on_queue_empty(struct vpu_inst *inst, u32 type) +{ + struct venc_t *venc = inst->priv; + + if (V4L2_TYPE_IS_OUTPUT(type)) + return; + + if (venc->stopped) + venc_set_last_buffer_dequeued(inst); +} + +static int venc_get_debug_info(struct vpu_inst *inst, char *str, u32 size, u32 i) +{ + struct venc_t *venc = inst->priv; + int num = -1; + + switch (i) { + case 0: + num = scnprintf(str, size, "profile = %d\n", venc->params.profile); + break; + case 1: + num = scnprintf(str, size, "level = %d\n", venc->params.level); + break; + case 2: + num = scnprintf(str, size, "fps = %d/%d\n", + venc->params.frame_rate.numerator, + venc->params.frame_rate.denominator); + break; + case 3: + num = scnprintf(str, size, "%d x %d -> %d x %d\n", + venc->params.src_width, + venc->params.src_height, + venc->params.out_width, + venc->params.out_height); + break; + case 4: + num = scnprintf(str, size, "(%d, %d) %d x %d\n", + venc->params.crop.left, + venc->params.crop.top, + venc->params.crop.width, + venc->params.crop.height); + break; + case 5: + num = scnprintf(str, size, + "enable = 0x%x, input = %d, encode = %d, ready = %d, stopped = %d\n", + venc->enable, + venc->frame_count, venc->encode_count, + venc->ready_count, + venc->stopped); + break; + case 6: + num = scnprintf(str, size, "gop = %d\n", venc->params.gop_length); + break; + case 7: + num = scnprintf(str, size, "bframes = %d\n", venc->params.bframes); + break; + case 8: + num = scnprintf(str, size, "rc: %s, mode = %d, bitrate = %d(%d), qp = %d\n", + venc->params.rc_enable ? "enable" : "disable", + venc->params.rc_mode, + venc->params.bitrate, + venc->params.bitrate_max, + venc->params.i_frame_qp); + break; + case 9: + num = scnprintf(str, size, "sar: enable = %d, idc = %d, %d x %d\n", + venc->params.sar.enable, + venc->params.sar.idc, + venc->params.sar.width, + venc->params.sar.height); + + break; + case 10: + num = scnprintf(str, size, + "colorspace: primaries = %d, transfer = %d, matrix = %d, full_range = %d\n", + venc->params.color.primaries, + venc->params.color.transfer, + venc->params.color.matrix, + venc->params.color.full_range); + break; + case 11: + num = scnprintf(str, size, "skipped: count = %d, bytes = %d\n", + venc->skipped_count, venc->skipped_bytes); + break; + default: + break; + } + + return num; +} + +static struct vpu_inst_ops venc_inst_ops = { + .ctrl_init = venc_ctrl_init, + .check_ready = venc_check_ready, + .input_done = venc_input_done, + .get_one_frame = venc_frame_encoded, + .stop_done = venc_stop_done, + .event_notify = venc_event_notify, + .release = venc_release, + .cleanup = venc_cleanup, + .start = venc_start_session, + .mem_request = venc_request_mem_resource, + .stop = venc_stop_session, + .process_output = venc_process_output, + .process_capture = venc_process_capture, + .on_queue_empty = venc_on_queue_empty, + .get_debug_info = venc_get_debug_info, + .wait_prepare = vpu_inst_unlock, + .wait_finish = vpu_inst_lock, +}; + +static void venc_init(struct file *file) +{ + struct vpu_inst *inst = to_inst(file); + struct venc_t *venc; + struct v4l2_format f; + struct v4l2_streamparm parm; + + venc = inst->priv; + venc->params.qp_min = 1; + venc->params.qp_max = 51; + venc->params.qp_min_i = 1; + venc->params.qp_max_i = 51; + venc->params.bitrate_min = BITRATE_MIN; + + memset(&f, 0, sizeof(f)); + f.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; + f.fmt.pix_mp.width = 1280; + f.fmt.pix_mp.height = 720; + f.fmt.pix_mp.field = V4L2_FIELD_NONE; + f.fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; + venc_s_fmt(file, &inst->fh, &f); + + memset(&f, 0, sizeof(f)); + f.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + f.fmt.pix_mp.width = 1280; + f.fmt.pix_mp.height = 720; + f.fmt.pix_mp.field = V4L2_FIELD_NONE; + venc_s_fmt(file, &inst->fh, &f); + + memset(&parm, 0, sizeof(parm)); + parm.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + parm.parm.capture.timeperframe.numerator = 1; + parm.parm.capture.timeperframe.denominator = 30; + venc_s_parm(file, &inst->fh, &parm); +} + +static int venc_open(struct file *file) +{ + struct vpu_inst *inst; + struct venc_t *venc; + int ret; + + inst = vzalloc(sizeof(*inst)); + if (!inst) + return -ENOMEM; + + venc = vzalloc(sizeof(*venc)); + if (!venc) { + vfree(inst); + return -ENOMEM; + } + + inst->ops = &venc_inst_ops; + inst->formats = venc_formats; + inst->type = VPU_CORE_TYPE_ENC; + inst->priv = venc; + INIT_LIST_HEAD(&venc->frames); + init_waitqueue_head(&venc->wq); + + ret = vpu_v4l2_open(file, inst); + if (ret) + return ret; + + inst->min_buffer_out = VENC_MIN_BUFFER_OUT; + inst->min_buffer_cap = VENC_MIN_BUFFER_CAP; + venc_init(file); + + return 0; +} + +static const struct v4l2_file_operations venc_fops = { + .owner = THIS_MODULE, + .open = venc_open, + .release = vpu_v4l2_close, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +}; + +const struct v4l2_ioctl_ops *venc_get_ioctl_ops(void) +{ + return &venc_ioctl_ops; +} + +const struct v4l2_file_operations *venc_get_fops(void) +{ + return &venc_fops; +} diff --git a/drivers/media/platform/amphion/vpu.h b/drivers/media/platform/amphion/vpu.h new file mode 100644 index 000000000..deb2288d4 --- /dev/null +++ b/drivers/media/platform/amphion/vpu.h @@ -0,0 +1,366 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_H +#define _AMPHION_VPU_H + +#include +#include +#include +#include +#include +#include + +#define VPU_TIMEOUT_WAKEUP msecs_to_jiffies(200) +#define VPU_TIMEOUT msecs_to_jiffies(1000) +#define VPU_INST_NULL_ID (-1L) +#define VPU_MSG_BUFFER_SIZE (8192) + +enum imx_plat_type { + IMX8QXP = 0, + IMX8QM = 1, + IMX8DM, + IMX8DX, + PLAT_TYPE_RESERVED +}; + +enum vpu_core_type { + VPU_CORE_TYPE_ENC = 0, + VPU_CORE_TYPE_DEC = 0x10, +}; + +struct vpu_dev; +struct vpu_resources { + enum imx_plat_type plat_type; + u32 mreg_base; + int (*setup)(struct vpu_dev *vpu); + int (*setup_encoder)(struct vpu_dev *vpu); + int (*setup_decoder)(struct vpu_dev *vpu); + int (*reset)(struct vpu_dev *vpu); +}; + +struct vpu_buffer { + void *virt; + dma_addr_t phys; + u32 length; + u32 bytesused; + struct device *dev; +}; + +struct vpu_func { + struct video_device *vfd; + struct v4l2_m2m_dev *m2m_dev; + enum vpu_core_type type; + int function; +}; + +struct vpu_dev { + void __iomem *base; + struct platform_device *pdev; + struct device *dev; + struct mutex lock; /* protect vpu device */ + const struct vpu_resources *res; + struct list_head cores; + + struct v4l2_device v4l2_dev; + struct vpu_func encoder; + struct vpu_func decoder; + struct media_device mdev; + + struct delayed_work watchdog_work; + void (*get_vpu)(struct vpu_dev *vpu); + void (*put_vpu)(struct vpu_dev *vpu); + void (*get_enc)(struct vpu_dev *vpu); + void (*put_enc)(struct vpu_dev *vpu); + void (*get_dec)(struct vpu_dev *vpu); + void (*put_dec)(struct vpu_dev *vpu); + atomic_t ref_vpu; + atomic_t ref_enc; + atomic_t ref_dec; + + struct dentry *debugfs; +}; + +struct vpu_format { + u32 pixfmt; + unsigned int num_planes; + u32 type; + u32 flags; + u32 width; + u32 height; + u32 sizeimage[VIDEO_MAX_PLANES]; + u32 bytesperline[VIDEO_MAX_PLANES]; + u32 field; +}; + +struct vpu_core_resources { + enum vpu_core_type type; + const char *fwname; + u32 stride; + u32 max_width; + u32 min_width; + u32 step_width; + u32 max_height; + u32 min_height; + u32 step_height; + u32 rpc_size; + u32 fwlog_size; + u32 act_size; +}; + +struct vpu_mbox { + char name[20]; + struct mbox_client cl; + struct mbox_chan *ch; + bool block; +}; + +enum vpu_core_state { + VPU_CORE_DEINIT = 0, + VPU_CORE_ACTIVE, + VPU_CORE_HANG +}; + +struct vpu_core { + void __iomem *base; + struct platform_device *pdev; + struct device *dev; + struct device *parent; + struct device *pd; + struct device_link *pd_link; + struct mutex lock; /* protect vpu core */ + struct mutex cmd_lock; /* Lock vpu command */ + struct list_head list; + enum vpu_core_type type; + int id; + const struct vpu_core_resources *res; + unsigned long instance_mask; + u32 supported_instance_count; + unsigned long hang_mask; + u32 request_count; + struct list_head instances; + enum vpu_core_state state; + u32 fw_version; + + struct vpu_buffer fw; + struct vpu_buffer rpc; + struct vpu_buffer log; + struct vpu_buffer act; + + struct vpu_mbox tx_type; + struct vpu_mbox tx_data; + struct vpu_mbox rx; + unsigned long cmd_seq; + + wait_queue_head_t ack_wq; + struct completion cmp; + struct workqueue_struct *workqueue; + struct work_struct msg_work; + struct delayed_work msg_delayed_work; + struct kfifo msg_fifo; + void *msg_buffer; + unsigned int msg_buffer_size; + + struct vpu_dev *vpu; + void *iface; + + struct dentry *debugfs; + struct dentry *debugfs_fwlog; +}; + +enum vpu_codec_state { + VPU_CODEC_STATE_DEINIT = 1, + VPU_CODEC_STATE_CONFIGURED, + VPU_CODEC_STATE_START, + VPU_CODEC_STATE_STARTED, + VPU_CODEC_STATE_ACTIVE, + VPU_CODEC_STATE_SEEK, + VPU_CODEC_STATE_STOP, + VPU_CODEC_STATE_DRAIN, + VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE, +}; + +struct vpu_frame_info { + u32 type; + u32 id; + u32 sequence; + u32 luma; + u32 chroma_u; + u32 chroma_v; + u32 data_offset; + u32 flags; + u32 skipped; + s64 timestamp; +}; + +struct vpu_inst; +struct vpu_inst_ops { + int (*ctrl_init)(struct vpu_inst *inst); + int (*start)(struct vpu_inst *inst, u32 type); + int (*stop)(struct vpu_inst *inst, u32 type); + int (*abort)(struct vpu_inst *inst); + bool (*check_ready)(struct vpu_inst *inst, unsigned int type); + void (*buf_done)(struct vpu_inst *inst, struct vpu_frame_info *frame); + void (*event_notify)(struct vpu_inst *inst, u32 event, void *data); + void (*release)(struct vpu_inst *inst); + void (*cleanup)(struct vpu_inst *inst); + void (*mem_request)(struct vpu_inst *inst, + u32 enc_frame_size, + u32 enc_frame_num, + u32 ref_frame_size, + u32 ref_frame_num, + u32 act_frame_size, + u32 act_frame_num); + void (*input_done)(struct vpu_inst *inst); + void (*stop_done)(struct vpu_inst *inst); + int (*process_output)(struct vpu_inst *inst, struct vb2_buffer *vb); + int (*process_capture)(struct vpu_inst *inst, struct vb2_buffer *vb); + int (*get_one_frame)(struct vpu_inst *inst, void *info); + void (*on_queue_empty)(struct vpu_inst *inst, u32 type); + int (*get_debug_info)(struct vpu_inst *inst, char *str, u32 size, u32 i); + void (*wait_prepare)(struct vpu_inst *inst); + void (*wait_finish)(struct vpu_inst *inst); +}; + +struct vpu_inst { + struct list_head list; + struct mutex lock; /* v4l2 and videobuf2 lock */ + struct vpu_dev *vpu; + struct vpu_core *core; + struct device *dev; + int id; + + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; + atomic_t ref_count; + int (*release)(struct vpu_inst *inst); + + enum vpu_codec_state state; + enum vpu_core_type type; + + struct workqueue_struct *workqueue; + struct work_struct msg_work; + struct kfifo msg_fifo; + u8 msg_buffer[VPU_MSG_BUFFER_SIZE]; + + struct vpu_buffer stream_buffer; + bool use_stream_buffer; + struct vpu_buffer act; + + struct list_head cmd_q; + void *pending; + + struct vpu_inst_ops *ops; + const struct vpu_format *formats; + struct vpu_format out_format; + struct vpu_format cap_format; + u32 min_buffer_cap; + u32 min_buffer_out; + u32 total_input_count; + + struct v4l2_rect crop; + u32 colorspace; + u8 ycbcr_enc; + u8 quantization; + u8 xfer_func; + u32 sequence; + u32 extra_size; + + u32 flows[16]; + u32 flow_idx; + + pid_t pid; + pid_t tgid; + struct dentry *debugfs; + + void *priv; +}; + +#define call_vop(inst, op, args...) \ + ((inst)->ops->op ? (inst)->ops->op(inst, ##args) : 0) \ + +#define call_void_vop(inst, op, args...) \ + do { \ + if ((inst)->ops->op) \ + (inst)->ops->op(inst, ##args); \ + } while (0) + +enum { + VPU_BUF_STATE_IDLE = 0, + VPU_BUF_STATE_INUSE, + VPU_BUF_STATE_DECODED, + VPU_BUF_STATE_READY, + VPU_BUF_STATE_SKIP, + VPU_BUF_STATE_ERROR +}; + +struct vpu_vb2_buffer { + struct v4l2_m2m_buffer m2m_buf; + dma_addr_t luma; + dma_addr_t chroma_u; + dma_addr_t chroma_v; + unsigned int state; + u32 tag; +}; + +void vpu_writel(struct vpu_dev *vpu, u32 reg, u32 val); +u32 vpu_readl(struct vpu_dev *vpu, u32 reg); + +static inline struct vpu_vb2_buffer *to_vpu_vb2_buffer(struct vb2_v4l2_buffer *vbuf) +{ + struct v4l2_m2m_buffer *m2m_buf = container_of(vbuf, struct v4l2_m2m_buffer, vb); + + return container_of(m2m_buf, struct vpu_vb2_buffer, m2m_buf); +} + +static inline const char *vpu_core_type_desc(enum vpu_core_type type) +{ + return type == VPU_CORE_TYPE_ENC ? "encoder" : "decoder"; +} + +static inline struct vpu_inst *to_inst(struct file *filp) +{ + return container_of(filp->private_data, struct vpu_inst, fh); +} + +#define ctrl_to_inst(ctrl) \ + container_of((ctrl)->handler, struct vpu_inst, ctrl_handler) + +const struct v4l2_ioctl_ops *venc_get_ioctl_ops(void); +const struct v4l2_file_operations *venc_get_fops(void); +const struct v4l2_ioctl_ops *vdec_get_ioctl_ops(void); +const struct v4l2_file_operations *vdec_get_fops(void); + +int vpu_add_func(struct vpu_dev *vpu, struct vpu_func *func); +void vpu_remove_func(struct vpu_func *func); + +struct vpu_inst *vpu_inst_get(struct vpu_inst *inst); +void vpu_inst_put(struct vpu_inst *inst); +struct vpu_core *vpu_request_core(struct vpu_dev *vpu, enum vpu_core_type type); +void vpu_release_core(struct vpu_core *core); +int vpu_inst_register(struct vpu_inst *inst); +int vpu_inst_unregister(struct vpu_inst *inst); +const struct vpu_core_resources *vpu_get_resource(struct vpu_inst *inst); + +int vpu_inst_create_dbgfs_file(struct vpu_inst *inst); +int vpu_inst_remove_dbgfs_file(struct vpu_inst *inst); +int vpu_core_create_dbgfs_file(struct vpu_core *core); +int vpu_core_remove_dbgfs_file(struct vpu_core *core); +void vpu_inst_record_flow(struct vpu_inst *inst, u32 flow); + +int vpu_core_driver_init(void); +void vpu_core_driver_exit(void); + +const char *vpu_id_name(u32 id); +const char *vpu_codec_state_name(enum vpu_codec_state state); + +extern bool debug; +#define vpu_trace(dev, fmt, arg...) \ + do { \ + if (debug) \ + dev_info(dev, "%s: " fmt, __func__, ## arg); \ + } while (0) + +#endif diff --git a/drivers/media/platform/amphion/vpu_cmds.c b/drivers/media/platform/amphion/vpu_cmds.c new file mode 100644 index 000000000..235b71398 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_cmds.c @@ -0,0 +1,465 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_defs.h" +#include "vpu_cmds.h" +#include "vpu_rpc.h" +#include "vpu_mbox.h" + +struct vpu_cmd_request { + u32 request; + u32 response; + u32 handled; +}; + +struct vpu_cmd_t { + struct list_head list; + u32 id; + struct vpu_cmd_request *request; + struct vpu_rpc_event *pkt; + unsigned long key; +}; + +static struct vpu_cmd_request vpu_cmd_requests[] = { + { + .request = VPU_CMD_ID_CONFIGURE_CODEC, + .response = VPU_MSG_ID_MEM_REQUEST, + .handled = 1, + }, + { + .request = VPU_CMD_ID_START, + .response = VPU_MSG_ID_START_DONE, + .handled = 0, + }, + { + .request = VPU_CMD_ID_STOP, + .response = VPU_MSG_ID_STOP_DONE, + .handled = 0, + }, + { + .request = VPU_CMD_ID_ABORT, + .response = VPU_MSG_ID_ABORT_DONE, + .handled = 0, + }, + { + .request = VPU_CMD_ID_RST_BUF, + .response = VPU_MSG_ID_BUF_RST, + .handled = 1, + }, +}; + +static int vpu_cmd_send(struct vpu_core *core, struct vpu_rpc_event *pkt) +{ + int ret = 0; + + ret = vpu_iface_send_cmd(core, pkt); + if (ret) + return ret; + + /*write cmd data to cmd buffer before trigger a cmd interrupt*/ + mb(); + vpu_mbox_send_type(core, COMMAND); + + return ret; +} + +static struct vpu_cmd_t *vpu_alloc_cmd(struct vpu_inst *inst, u32 id, void *data) +{ + struct vpu_cmd_t *cmd; + int i; + int ret; + + cmd = vzalloc(sizeof(*cmd)); + if (!cmd) + return NULL; + + cmd->pkt = vzalloc(sizeof(*cmd->pkt)); + if (!cmd->pkt) { + vfree(cmd); + return NULL; + } + + cmd->id = id; + ret = vpu_iface_pack_cmd(inst->core, cmd->pkt, inst->id, id, data); + if (ret) { + dev_err(inst->dev, "iface pack cmd %s fail\n", vpu_id_name(id)); + vfree(cmd->pkt); + vfree(cmd); + return NULL; + } + for (i = 0; i < ARRAY_SIZE(vpu_cmd_requests); i++) { + if (vpu_cmd_requests[i].request == id) { + cmd->request = &vpu_cmd_requests[i]; + break; + } + } + + return cmd; +} + +static void vpu_free_cmd(struct vpu_cmd_t *cmd) +{ + if (!cmd) + return; + vfree(cmd->pkt); + vfree(cmd); +} + +static int vpu_session_process_cmd(struct vpu_inst *inst, struct vpu_cmd_t *cmd) +{ + int ret; + + dev_dbg(inst->dev, "[%d]send cmd %s\n", inst->id, vpu_id_name(cmd->id)); + vpu_iface_pre_send_cmd(inst); + ret = vpu_cmd_send(inst->core, cmd->pkt); + if (!ret) { + vpu_iface_post_send_cmd(inst); + vpu_inst_record_flow(inst, cmd->id); + } else { + dev_err(inst->dev, "[%d] iface send cmd %s fail\n", inst->id, vpu_id_name(cmd->id)); + } + + return ret; +} + +static void vpu_process_cmd_request(struct vpu_inst *inst) +{ + struct vpu_cmd_t *cmd; + struct vpu_cmd_t *tmp; + + if (!inst || inst->pending) + return; + + list_for_each_entry_safe(cmd, tmp, &inst->cmd_q, list) { + list_del_init(&cmd->list); + if (vpu_session_process_cmd(inst, cmd)) + dev_err(inst->dev, "[%d] process cmd %s fail\n", + inst->id, vpu_id_name(cmd->id)); + if (cmd->request) { + inst->pending = (void *)cmd; + break; + } + vpu_free_cmd(cmd); + } +} + +static int vpu_request_cmd(struct vpu_inst *inst, u32 id, void *data, + unsigned long *key, int *sync) +{ + struct vpu_core *core; + struct vpu_cmd_t *cmd; + + if (!inst || !inst->core) + return -EINVAL; + + core = inst->core; + cmd = vpu_alloc_cmd(inst, id, data); + if (!cmd) + return -ENOMEM; + + mutex_lock(&core->cmd_lock); + cmd->key = core->cmd_seq++; + if (key) + *key = cmd->key; + if (sync) + *sync = cmd->request ? true : false; + list_add_tail(&cmd->list, &inst->cmd_q); + vpu_process_cmd_request(inst); + mutex_unlock(&core->cmd_lock); + + return 0; +} + +static void vpu_clear_pending(struct vpu_inst *inst) +{ + if (!inst || !inst->pending) + return; + + vpu_free_cmd(inst->pending); + wake_up_all(&inst->core->ack_wq); + inst->pending = NULL; +} + +static bool vpu_check_response(struct vpu_cmd_t *cmd, u32 response, u32 handled) +{ + struct vpu_cmd_request *request; + + if (!cmd || !cmd->request) + return false; + + request = cmd->request; + if (request->response != response) + return false; + if (request->handled != handled) + return false; + + return true; +} + +int vpu_response_cmd(struct vpu_inst *inst, u32 response, u32 handled) +{ + struct vpu_core *core; + + if (!inst || !inst->core) + return -EINVAL; + + core = inst->core; + mutex_lock(&core->cmd_lock); + if (vpu_check_response(inst->pending, response, handled)) + vpu_clear_pending(inst); + + vpu_process_cmd_request(inst); + mutex_unlock(&core->cmd_lock); + + return 0; +} + +void vpu_clear_request(struct vpu_inst *inst) +{ + struct vpu_cmd_t *cmd; + struct vpu_cmd_t *tmp; + + mutex_lock(&inst->core->cmd_lock); + if (inst->pending) + vpu_clear_pending(inst); + + list_for_each_entry_safe(cmd, tmp, &inst->cmd_q, list) { + list_del_init(&cmd->list); + vpu_free_cmd(cmd); + } + mutex_unlock(&inst->core->cmd_lock); +} + +static bool check_is_responsed(struct vpu_inst *inst, unsigned long key) +{ + struct vpu_core *core = inst->core; + struct vpu_cmd_t *cmd; + bool flag = true; + + mutex_lock(&core->cmd_lock); + cmd = inst->pending; + if (cmd && key == cmd->key) { + flag = false; + goto exit; + } + list_for_each_entry(cmd, &inst->cmd_q, list) { + if (key == cmd->key) { + flag = false; + break; + } + } +exit: + mutex_unlock(&core->cmd_lock); + + return flag; +} + +static int sync_session_response(struct vpu_inst *inst, unsigned long key, long timeout, int try) +{ + struct vpu_core *core; + + if (!inst || !inst->core) + return -EINVAL; + + core = inst->core; + + call_void_vop(inst, wait_prepare); + wait_event_timeout(core->ack_wq, check_is_responsed(inst, key), timeout); + call_void_vop(inst, wait_finish); + + if (!check_is_responsed(inst, key)) { + if (try) + return -EINVAL; + dev_err(inst->dev, "[%d] sync session timeout\n", inst->id); + set_bit(inst->id, &core->hang_mask); + mutex_lock(&inst->core->cmd_lock); + vpu_clear_pending(inst); + mutex_unlock(&inst->core->cmd_lock); + return -EINVAL; + } + + return 0; +} + +static void vpu_core_keep_active(struct vpu_core *core) +{ + struct vpu_rpc_event pkt; + + memset(&pkt, 0, sizeof(pkt)); + vpu_iface_pack_cmd(core, &pkt, 0, VPU_CMD_ID_NOOP, NULL); + + dev_dbg(core->dev, "try to wake up\n"); + mutex_lock(&core->cmd_lock); + if (vpu_cmd_send(core, &pkt)) + dev_err(core->dev, "fail to keep active\n"); + mutex_unlock(&core->cmd_lock); +} + +static int vpu_session_send_cmd(struct vpu_inst *inst, u32 id, void *data) +{ + unsigned long key; + int sync = false; + int ret; + + if (inst->id < 0) + return -EINVAL; + + ret = vpu_request_cmd(inst, id, data, &key, &sync); + if (ret) + goto exit; + + /* workaround for a firmware issue, + * firmware should be waked up by start or configure command, + * but there is a very small change that firmware failed to wakeup. + * in such case, try to wakeup firmware again by sending a noop command + */ + if (sync && (id == VPU_CMD_ID_CONFIGURE_CODEC || id == VPU_CMD_ID_START)) { + if (sync_session_response(inst, key, VPU_TIMEOUT_WAKEUP, 1)) + vpu_core_keep_active(inst->core); + else + goto exit; + } + + if (sync) + ret = sync_session_response(inst, key, VPU_TIMEOUT, 0); + +exit: + if (ret) + dev_err(inst->dev, "[%d] send cmd %s fail\n", inst->id, vpu_id_name(id)); + + return ret; +} + +int vpu_session_configure_codec(struct vpu_inst *inst) +{ + return vpu_session_send_cmd(inst, VPU_CMD_ID_CONFIGURE_CODEC, NULL); +} + +int vpu_session_start(struct vpu_inst *inst) +{ + vpu_trace(inst->dev, "[%d]\n", inst->id); + + return vpu_session_send_cmd(inst, VPU_CMD_ID_START, NULL); +} + +int vpu_session_stop(struct vpu_inst *inst) +{ + int ret; + + vpu_trace(inst->dev, "[%d]\n", inst->id); + + ret = vpu_session_send_cmd(inst, VPU_CMD_ID_STOP, NULL); + /* workaround for a firmware bug, + * if the next command is too close after stop cmd, + * the firmware may enter wfi wrongly. + */ + usleep_range(3000, 5000); + return ret; +} + +int vpu_session_encode_frame(struct vpu_inst *inst, s64 timestamp) +{ + return vpu_session_send_cmd(inst, VPU_CMD_ID_FRAME_ENCODE, ×tamp); +} + +int vpu_session_alloc_fs(struct vpu_inst *inst, struct vpu_fs_info *fs) +{ + return vpu_session_send_cmd(inst, VPU_CMD_ID_FS_ALLOC, fs); +} + +int vpu_session_release_fs(struct vpu_inst *inst, struct vpu_fs_info *fs) +{ + return vpu_session_send_cmd(inst, VPU_CMD_ID_FS_RELEASE, fs); +} + +int vpu_session_abort(struct vpu_inst *inst) +{ + return vpu_session_send_cmd(inst, VPU_CMD_ID_ABORT, NULL); +} + +int vpu_session_rst_buf(struct vpu_inst *inst) +{ + return vpu_session_send_cmd(inst, VPU_CMD_ID_RST_BUF, NULL); +} + +int vpu_session_fill_timestamp(struct vpu_inst *inst, struct vpu_ts_info *info) +{ + return vpu_session_send_cmd(inst, VPU_CMD_ID_TIMESTAMP, info); +} + +int vpu_session_update_parameters(struct vpu_inst *inst, void *arg) +{ + if (inst->type & VPU_CORE_TYPE_DEC) + vpu_iface_set_decode_params(inst, arg, 1); + else + vpu_iface_set_encode_params(inst, arg, 1); + + return vpu_session_send_cmd(inst, VPU_CMD_ID_UPDATE_PARAMETER, arg); +} + +int vpu_session_debug(struct vpu_inst *inst) +{ + return vpu_session_send_cmd(inst, VPU_CMD_ID_DEBUG, NULL); +} + +int vpu_core_snapshot(struct vpu_core *core) +{ + struct vpu_inst *inst; + int ret; + + if (!core || list_empty(&core->instances)) + return 0; + + inst = list_first_entry(&core->instances, struct vpu_inst, list); + + reinit_completion(&core->cmp); + ret = vpu_session_send_cmd(inst, VPU_CMD_ID_SNAPSHOT, NULL); + if (ret) + return ret; + ret = wait_for_completion_timeout(&core->cmp, VPU_TIMEOUT); + if (!ret) { + dev_err(core->dev, "snapshot timeout\n"); + return -EINVAL; + } + + return 0; +} + +int vpu_core_sw_reset(struct vpu_core *core) +{ + struct vpu_rpc_event pkt; + int ret; + + memset(&pkt, 0, sizeof(pkt)); + vpu_iface_pack_cmd(core, &pkt, 0, VPU_CMD_ID_FIRM_RESET, NULL); + + reinit_completion(&core->cmp); + mutex_lock(&core->cmd_lock); + ret = vpu_cmd_send(core, &pkt); + mutex_unlock(&core->cmd_lock); + if (ret) + return ret; + ret = wait_for_completion_timeout(&core->cmp, VPU_TIMEOUT); + if (!ret) { + dev_err(core->dev, "sw reset timeout\n"); + return -EINVAL; + } + + return 0; +} diff --git a/drivers/media/platform/amphion/vpu_cmds.h b/drivers/media/platform/amphion/vpu_cmds.h new file mode 100644 index 000000000..bc538d277 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_cmds.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_CMDS_H +#define _AMPHION_VPU_CMDS_H + +int vpu_session_configure_codec(struct vpu_inst *inst); +int vpu_session_start(struct vpu_inst *inst); +int vpu_session_stop(struct vpu_inst *inst); +int vpu_session_abort(struct vpu_inst *inst); +int vpu_session_rst_buf(struct vpu_inst *inst); +int vpu_session_encode_frame(struct vpu_inst *inst, s64 timestamp); +int vpu_session_alloc_fs(struct vpu_inst *inst, struct vpu_fs_info *fs); +int vpu_session_release_fs(struct vpu_inst *inst, struct vpu_fs_info *fs); +int vpu_session_fill_timestamp(struct vpu_inst *inst, struct vpu_ts_info *info); +int vpu_session_update_parameters(struct vpu_inst *inst, void *arg); +int vpu_core_snapshot(struct vpu_core *core); +int vpu_core_sw_reset(struct vpu_core *core); +int vpu_response_cmd(struct vpu_inst *inst, u32 response, u32 handled); +void vpu_clear_request(struct vpu_inst *inst); +int vpu_session_debug(struct vpu_inst *inst); + +#endif diff --git a/drivers/media/platform/amphion/vpu_codec.h b/drivers/media/platform/amphion/vpu_codec.h new file mode 100644 index 000000000..bac6d0d94 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_codec.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_CODEC_H +#define _AMPHION_VPU_CODEC_H + +struct vpu_encode_params { + u32 input_format; + u32 codec_format; + u32 profile; + u32 tier; + u32 level; + struct v4l2_fract frame_rate; + u32 src_stride; + u32 src_width; + u32 src_height; + struct v4l2_rect crop; + u32 out_width; + u32 out_height; + + u32 gop_length; + u32 bframes; + + u32 rc_enable; + u32 rc_mode; + u32 bitrate; + u32 bitrate_min; + u32 bitrate_max; + + u32 i_frame_qp; + u32 p_frame_qp; + u32 b_frame_qp; + u32 qp_min; + u32 qp_max; + u32 qp_min_i; + u32 qp_max_i; + + struct { + u32 enable; + u32 idc; + u32 width; + u32 height; + } sar; + + struct { + u32 primaries; + u32 transfer; + u32 matrix; + u32 full_range; + } color; +}; + +struct vpu_decode_params { + u32 codec_format; + u32 output_format; + u32 display_delay_enable; + u32 display_delay; + u32 b_non_frame; + u32 frame_count; + u32 end_flag; + struct { + u32 base; + u32 size; + } udata; +}; + +#endif diff --git a/drivers/media/platform/amphion/vpu_color.c b/drivers/media/platform/amphion/vpu_color.c new file mode 100644 index 000000000..4ae435cbc --- /dev/null +++ b/drivers/media/platform/amphion/vpu_color.c @@ -0,0 +1,183 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_helpers.h" + +static const u8 colorprimaries[] = { + V4L2_COLORSPACE_LAST, + V4L2_COLORSPACE_REC709, /*Rec. ITU-R BT.709-6*/ + 0, + 0, + V4L2_COLORSPACE_470_SYSTEM_M, /*Rec. ITU-R BT.470-6 System M*/ + V4L2_COLORSPACE_470_SYSTEM_BG, /*Rec. ITU-R BT.470-6 System B, G*/ + V4L2_COLORSPACE_SMPTE170M, /*SMPTE170M*/ + V4L2_COLORSPACE_SMPTE240M, /*SMPTE240M*/ + 0, /*Generic film*/ + V4L2_COLORSPACE_BT2020, /*Rec. ITU-R BT.2020-2*/ + 0, /*SMPTE ST 428-1*/ +}; + +static const u8 colortransfers[] = { + V4L2_XFER_FUNC_LAST, + V4L2_XFER_FUNC_709, /*Rec. ITU-R BT.709-6*/ + 0, + 0, + 0, /*Rec. ITU-R BT.470-6 System M*/ + 0, /*Rec. ITU-R BT.470-6 System B, G*/ + V4L2_XFER_FUNC_709, /*SMPTE170M*/ + V4L2_XFER_FUNC_SMPTE240M, /*SMPTE240M*/ + V4L2_XFER_FUNC_NONE, /*Linear transfer characteristics*/ + 0, + 0, + 0, /*IEC 61966-2-4*/ + 0, /*Rec. ITU-R BT.1361-0 extended colour gamut*/ + V4L2_XFER_FUNC_SRGB, /*IEC 61966-2-1 sRGB or sYCC*/ + V4L2_XFER_FUNC_709, /*Rec. ITU-R BT.2020-2 (10 bit system)*/ + V4L2_XFER_FUNC_709, /*Rec. ITU-R BT.2020-2 (12 bit system)*/ + V4L2_XFER_FUNC_SMPTE2084, /*SMPTE ST 2084*/ + 0, /*SMPTE ST 428-1*/ + 0 /*Rec. ITU-R BT.2100-0 hybrid log-gamma (HLG)*/ +}; + +static const u8 colormatrixcoefs[] = { + V4L2_YCBCR_ENC_LAST, + V4L2_YCBCR_ENC_709, /*Rec. ITU-R BT.709-6*/ + 0, + 0, + 0, /*Title 47 Code of Federal Regulations*/ + V4L2_YCBCR_ENC_601, /*Rec. ITU-R BT.601-7 625*/ + V4L2_YCBCR_ENC_601, /*Rec. ITU-R BT.601-7 525*/ + V4L2_YCBCR_ENC_SMPTE240M, /*SMPTE240M*/ + 0, + V4L2_YCBCR_ENC_BT2020, /*Rec. ITU-R BT.2020-2*/ + V4L2_YCBCR_ENC_BT2020_CONST_LUM /*Rec. ITU-R BT.2020-2 constant*/ +}; + +u32 vpu_color_cvrt_primaries_v2i(u32 primaries) +{ + return vpu_helper_find_in_array_u8(colorprimaries, ARRAY_SIZE(colorprimaries), primaries); +} + +u32 vpu_color_cvrt_primaries_i2v(u32 primaries) +{ + return primaries < ARRAY_SIZE(colorprimaries) ? colorprimaries[primaries] : 0; +} + +u32 vpu_color_cvrt_transfers_v2i(u32 transfers) +{ + return vpu_helper_find_in_array_u8(colortransfers, ARRAY_SIZE(colortransfers), transfers); +} + +u32 vpu_color_cvrt_transfers_i2v(u32 transfers) +{ + return transfers < ARRAY_SIZE(colortransfers) ? colortransfers[transfers] : 0; +} + +u32 vpu_color_cvrt_matrix_v2i(u32 matrix) +{ + return vpu_helper_find_in_array_u8(colormatrixcoefs, ARRAY_SIZE(colormatrixcoefs), matrix); +} + +u32 vpu_color_cvrt_matrix_i2v(u32 matrix) +{ + return matrix < ARRAY_SIZE(colormatrixcoefs) ? colormatrixcoefs[matrix] : 0; +} + +u32 vpu_color_cvrt_full_range_v2i(u32 full_range) +{ + return (full_range == V4L2_QUANTIZATION_FULL_RANGE); +} + +u32 vpu_color_cvrt_full_range_i2v(u32 full_range) +{ + if (full_range) + return V4L2_QUANTIZATION_FULL_RANGE; + + return V4L2_QUANTIZATION_LIM_RANGE; +} + +int vpu_color_check_primaries(u32 primaries) +{ + return vpu_color_cvrt_primaries_v2i(primaries) ? 0 : -EINVAL; +} + +int vpu_color_check_transfers(u32 transfers) +{ + return vpu_color_cvrt_transfers_v2i(transfers) ? 0 : -EINVAL; +} + +int vpu_color_check_matrix(u32 matrix) +{ + return vpu_color_cvrt_matrix_v2i(matrix) ? 0 : -EINVAL; +} + +int vpu_color_check_full_range(u32 full_range) +{ + int ret = -EINVAL; + + switch (full_range) { + case V4L2_QUANTIZATION_FULL_RANGE: + case V4L2_QUANTIZATION_LIM_RANGE: + ret = 0; + break; + default: + break; + } + + return ret; +} + +int vpu_color_get_default(u32 primaries, u32 *ptransfers, u32 *pmatrix, u32 *pfull_range) +{ + u32 transfers; + u32 matrix; + u32 full_range; + + switch (primaries) { + case V4L2_COLORSPACE_REC709: + transfers = V4L2_XFER_FUNC_709; + matrix = V4L2_YCBCR_ENC_709; + break; + case V4L2_COLORSPACE_470_SYSTEM_M: + case V4L2_COLORSPACE_470_SYSTEM_BG: + case V4L2_COLORSPACE_SMPTE170M: + transfers = V4L2_XFER_FUNC_709; + matrix = V4L2_YCBCR_ENC_601; + break; + case V4L2_COLORSPACE_SMPTE240M: + transfers = V4L2_XFER_FUNC_SMPTE240M; + matrix = V4L2_YCBCR_ENC_SMPTE240M; + break; + case V4L2_COLORSPACE_BT2020: + transfers = V4L2_XFER_FUNC_709; + matrix = V4L2_YCBCR_ENC_BT2020; + break; + default: + transfers = V4L2_XFER_FUNC_DEFAULT; + matrix = V4L2_YCBCR_ENC_DEFAULT; + break; + } + full_range = V4L2_QUANTIZATION_LIM_RANGE; + + if (ptransfers) + *ptransfers = transfers; + if (pmatrix) + *pmatrix = matrix; + if (pfull_range) + *pfull_range = full_range; + + return 0; +} diff --git a/drivers/media/platform/amphion/vpu_core.c b/drivers/media/platform/amphion/vpu_core.c new file mode 100644 index 000000000..9add73b9b --- /dev/null +++ b/drivers/media/platform/amphion/vpu_core.c @@ -0,0 +1,885 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_defs.h" +#include "vpu_core.h" +#include "vpu_mbox.h" +#include "vpu_msgs.h" +#include "vpu_rpc.h" +#include "vpu_cmds.h" + +void csr_writel(struct vpu_core *core, u32 reg, u32 val) +{ + writel(val, core->base + reg); +} + +u32 csr_readl(struct vpu_core *core, u32 reg) +{ + return readl(core->base + reg); +} + +static int vpu_core_load_firmware(struct vpu_core *core) +{ + const struct firmware *pfw = NULL; + int ret = 0; + + if (!core->fw.virt) { + dev_err(core->dev, "firmware buffer is not ready\n"); + return -EINVAL; + } + + ret = request_firmware(&pfw, core->res->fwname, core->dev); + dev_dbg(core->dev, "request_firmware %s : %d\n", core->res->fwname, ret); + if (ret) { + dev_err(core->dev, "request firmware %s failed, ret = %d\n", + core->res->fwname, ret); + return ret; + } + + if (core->fw.length < pfw->size) { + dev_err(core->dev, "firmware buffer size want %zu, but %d\n", + pfw->size, core->fw.length); + ret = -EINVAL; + goto exit; + } + + memset(core->fw.virt, 0, core->fw.length); + memcpy(core->fw.virt, pfw->data, pfw->size); + core->fw.bytesused = pfw->size; + ret = vpu_iface_on_firmware_loaded(core); +exit: + release_firmware(pfw); + pfw = NULL; + + return ret; +} + +static int vpu_core_boot_done(struct vpu_core *core) +{ + u32 fw_version; + + fw_version = vpu_iface_get_version(core); + dev_info(core->dev, "%s firmware version : %d.%d.%d\n", + vpu_core_type_desc(core->type), + (fw_version >> 16) & 0xff, + (fw_version >> 8) & 0xff, + fw_version & 0xff); + core->supported_instance_count = vpu_iface_get_max_instance_count(core); + if (core->res->act_size) { + u32 count = core->act.length / core->res->act_size; + + core->supported_instance_count = min(core->supported_instance_count, count); + } + if (core->supported_instance_count >= BITS_PER_TYPE(core->instance_mask)) + core->supported_instance_count = BITS_PER_TYPE(core->instance_mask); + core->fw_version = fw_version; + vpu_core_set_state(core, VPU_CORE_ACTIVE); + + return 0; +} + +static int vpu_core_wait_boot_done(struct vpu_core *core) +{ + int ret; + + ret = wait_for_completion_timeout(&core->cmp, VPU_TIMEOUT); + if (!ret) { + dev_err(core->dev, "boot timeout\n"); + return -EINVAL; + } + return vpu_core_boot_done(core); +} + +static int vpu_core_boot(struct vpu_core *core, bool load) +{ + int ret; + + reinit_completion(&core->cmp); + if (load) { + ret = vpu_core_load_firmware(core); + if (ret) + return ret; + } + + vpu_iface_boot_core(core); + return vpu_core_wait_boot_done(core); +} + +static int vpu_core_shutdown(struct vpu_core *core) +{ + return vpu_iface_shutdown_core(core); +} + +static int vpu_core_restore(struct vpu_core *core) +{ + int ret; + + ret = vpu_core_sw_reset(core); + if (ret) + return ret; + + vpu_core_boot_done(core); + return vpu_iface_restore_core(core); +} + +static int __vpu_alloc_dma(struct device *dev, struct vpu_buffer *buf) +{ + gfp_t gfp = GFP_KERNEL | GFP_DMA32; + + if (!buf->length) + return 0; + + buf->virt = dma_alloc_coherent(dev, buf->length, &buf->phys, gfp); + if (!buf->virt) + return -ENOMEM; + + buf->dev = dev; + + return 0; +} + +void vpu_free_dma(struct vpu_buffer *buf) +{ + if (!buf->virt || !buf->dev) + return; + + dma_free_coherent(buf->dev, buf->length, buf->virt, buf->phys); + buf->virt = NULL; + buf->phys = 0; + buf->length = 0; + buf->bytesused = 0; + buf->dev = NULL; +} + +int vpu_alloc_dma(struct vpu_core *core, struct vpu_buffer *buf) +{ + return __vpu_alloc_dma(core->dev, buf); +} + +void vpu_core_set_state(struct vpu_core *core, enum vpu_core_state state) +{ + if (state != core->state) + vpu_trace(core->dev, "vpu core state change from %d to %d\n", core->state, state); + core->state = state; + if (core->state == VPU_CORE_DEINIT) + core->hang_mask = 0; +} + +static void vpu_core_update_state(struct vpu_core *core) +{ + if (!vpu_iface_get_power_state(core)) { + if (core->request_count) + vpu_core_set_state(core, VPU_CORE_HANG); + else + vpu_core_set_state(core, VPU_CORE_DEINIT); + + } else if (core->state == VPU_CORE_ACTIVE && core->hang_mask) { + vpu_core_set_state(core, VPU_CORE_HANG); + } +} + +static struct vpu_core *vpu_core_find_proper_by_type(struct vpu_dev *vpu, u32 type) +{ + struct vpu_core *core = NULL; + int request_count = INT_MAX; + struct vpu_core *c; + + list_for_each_entry(c, &vpu->cores, list) { + dev_dbg(c->dev, "instance_mask = 0x%lx, state = %d\n", c->instance_mask, c->state); + if (c->type != type) + continue; + mutex_lock(&c->lock); + vpu_core_update_state(c); + mutex_unlock(&c->lock); + if (c->state == VPU_CORE_DEINIT) { + core = c; + break; + } + if (c->state != VPU_CORE_ACTIVE) + continue; + if (c->request_count < request_count) { + request_count = c->request_count; + core = c; + } + if (!request_count) + break; + } + + return core; +} + +static bool vpu_core_is_exist(struct vpu_dev *vpu, struct vpu_core *core) +{ + struct vpu_core *c; + + list_for_each_entry(c, &vpu->cores, list) { + if (c == core) + return true; + } + + return false; +} + +static void vpu_core_get_vpu(struct vpu_core *core) +{ + core->vpu->get_vpu(core->vpu); + if (core->type == VPU_CORE_TYPE_ENC) + core->vpu->get_enc(core->vpu); + if (core->type == VPU_CORE_TYPE_DEC) + core->vpu->get_dec(core->vpu); +} + +static int vpu_core_register(struct device *dev, struct vpu_core *core) +{ + struct vpu_dev *vpu = dev_get_drvdata(dev); + int ret = 0; + + dev_dbg(core->dev, "register core %s\n", vpu_core_type_desc(core->type)); + if (vpu_core_is_exist(vpu, core)) + return 0; + + core->workqueue = alloc_workqueue("vpu", WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!core->workqueue) { + dev_err(core->dev, "fail to alloc workqueue\n"); + return -ENOMEM; + } + INIT_WORK(&core->msg_work, vpu_msg_run_work); + INIT_DELAYED_WORK(&core->msg_delayed_work, vpu_msg_delayed_work); + core->msg_buffer_size = roundup_pow_of_two(VPU_MSG_BUFFER_SIZE); + core->msg_buffer = vzalloc(core->msg_buffer_size); + if (!core->msg_buffer) { + dev_err(core->dev, "failed allocate buffer for fifo\n"); + ret = -ENOMEM; + goto error; + } + ret = kfifo_init(&core->msg_fifo, core->msg_buffer, core->msg_buffer_size); + if (ret) { + dev_err(core->dev, "failed init kfifo\n"); + goto error; + } + + list_add_tail(&core->list, &vpu->cores); + vpu_core_get_vpu(core); + + return 0; +error: + if (core->msg_buffer) { + vfree(core->msg_buffer); + core->msg_buffer = NULL; + } + if (core->workqueue) { + destroy_workqueue(core->workqueue); + core->workqueue = NULL; + } + return ret; +} + +static void vpu_core_put_vpu(struct vpu_core *core) +{ + if (core->type == VPU_CORE_TYPE_ENC) + core->vpu->put_enc(core->vpu); + if (core->type == VPU_CORE_TYPE_DEC) + core->vpu->put_dec(core->vpu); + core->vpu->put_vpu(core->vpu); +} + +static int vpu_core_unregister(struct device *dev, struct vpu_core *core) +{ + list_del_init(&core->list); + + vpu_core_put_vpu(core); + core->vpu = NULL; + vfree(core->msg_buffer); + core->msg_buffer = NULL; + + if (core->workqueue) { + cancel_work_sync(&core->msg_work); + cancel_delayed_work_sync(&core->msg_delayed_work); + destroy_workqueue(core->workqueue); + core->workqueue = NULL; + } + + return 0; +} + +static int vpu_core_acquire_instance(struct vpu_core *core) +{ + int id; + + id = ffz(core->instance_mask); + if (id >= core->supported_instance_count) + return -EINVAL; + + set_bit(id, &core->instance_mask); + + return id; +} + +static void vpu_core_release_instance(struct vpu_core *core, int id) +{ + if (id < 0 || id >= core->supported_instance_count) + return; + + clear_bit(id, &core->instance_mask); +} + +struct vpu_inst *vpu_inst_get(struct vpu_inst *inst) +{ + if (!inst) + return NULL; + + atomic_inc(&inst->ref_count); + + return inst; +} + +void vpu_inst_put(struct vpu_inst *inst) +{ + if (!inst) + return; + if (atomic_dec_and_test(&inst->ref_count)) { + if (inst->release) + inst->release(inst); + } +} + +struct vpu_core *vpu_request_core(struct vpu_dev *vpu, enum vpu_core_type type) +{ + struct vpu_core *core = NULL; + int ret; + + mutex_lock(&vpu->lock); + + core = vpu_core_find_proper_by_type(vpu, type); + if (!core) + goto exit; + + mutex_lock(&core->lock); + pm_runtime_resume_and_get(core->dev); + + if (core->state == VPU_CORE_DEINIT) { + if (vpu_iface_get_power_state(core)) + ret = vpu_core_restore(core); + else + ret = vpu_core_boot(core, true); + if (ret) { + pm_runtime_put_sync(core->dev); + mutex_unlock(&core->lock); + core = NULL; + goto exit; + } + } + + core->request_count++; + + mutex_unlock(&core->lock); +exit: + mutex_unlock(&vpu->lock); + + return core; +} + +void vpu_release_core(struct vpu_core *core) +{ + if (!core) + return; + + mutex_lock(&core->lock); + pm_runtime_put_sync(core->dev); + if (core->request_count) + core->request_count--; + mutex_unlock(&core->lock); +} + +int vpu_inst_register(struct vpu_inst *inst) +{ + struct vpu_dev *vpu; + struct vpu_core *core; + int ret = 0; + + vpu = inst->vpu; + core = inst->core; + if (!core) { + core = vpu_request_core(vpu, inst->type); + if (!core) { + dev_err(vpu->dev, "there is no vpu core for %s\n", + vpu_core_type_desc(inst->type)); + return -EINVAL; + } + inst->core = core; + inst->dev = get_device(core->dev); + } + + mutex_lock(&core->lock); + if (core->state != VPU_CORE_ACTIVE) { + dev_err(core->dev, "vpu core is not active, state = %d\n", core->state); + ret = -EINVAL; + goto exit; + } + + if (inst->id >= 0 && inst->id < core->supported_instance_count) + goto exit; + + ret = vpu_core_acquire_instance(core); + if (ret < 0) + goto exit; + + vpu_trace(inst->dev, "[%d] %p\n", ret, inst); + inst->id = ret; + list_add_tail(&inst->list, &core->instances); + ret = 0; + if (core->res->act_size) { + inst->act.phys = core->act.phys + core->res->act_size * inst->id; + inst->act.virt = core->act.virt + core->res->act_size * inst->id; + inst->act.length = core->res->act_size; + } + vpu_inst_create_dbgfs_file(inst); +exit: + mutex_unlock(&core->lock); + + if (ret) + dev_err(core->dev, "register instance fail\n"); + return ret; +} + +int vpu_inst_unregister(struct vpu_inst *inst) +{ + struct vpu_core *core; + + if (!inst->core) + return 0; + + core = inst->core; + vpu_clear_request(inst); + mutex_lock(&core->lock); + if (inst->id >= 0 && inst->id < core->supported_instance_count) { + vpu_inst_remove_dbgfs_file(inst); + list_del_init(&inst->list); + vpu_core_release_instance(core, inst->id); + inst->id = VPU_INST_NULL_ID; + } + vpu_core_update_state(core); + if (core->state == VPU_CORE_HANG && !core->instance_mask) { + int err; + + dev_info(core->dev, "reset hang core\n"); + mutex_unlock(&core->lock); + err = vpu_core_sw_reset(core); + mutex_lock(&core->lock); + if (!err) { + vpu_core_set_state(core, VPU_CORE_ACTIVE); + core->hang_mask = 0; + } + } + mutex_unlock(&core->lock); + + return 0; +} + +struct vpu_inst *vpu_core_find_instance(struct vpu_core *core, u32 index) +{ + struct vpu_inst *inst = NULL; + struct vpu_inst *tmp; + + mutex_lock(&core->lock); + if (index >= core->supported_instance_count || !test_bit(index, &core->instance_mask)) + goto exit; + list_for_each_entry(tmp, &core->instances, list) { + if (tmp->id == index) { + inst = vpu_inst_get(tmp); + break; + } + } +exit: + mutex_unlock(&core->lock); + + return inst; +} + +const struct vpu_core_resources *vpu_get_resource(struct vpu_inst *inst) +{ + struct vpu_dev *vpu; + struct vpu_core *core = NULL; + const struct vpu_core_resources *res = NULL; + + if (!inst || !inst->vpu) + return NULL; + + if (inst->core && inst->core->res) + return inst->core->res; + + vpu = inst->vpu; + mutex_lock(&vpu->lock); + list_for_each_entry(core, &vpu->cores, list) { + if (core->type == inst->type) { + res = core->res; + break; + } + } + mutex_unlock(&vpu->lock); + + return res; +} + +static int vpu_core_parse_dt(struct vpu_core *core, struct device_node *np) +{ + struct device_node *node; + struct resource res; + int ret; + + if (of_count_phandle_with_args(np, "memory-region", NULL) < 2) { + dev_err(core->dev, "need 2 memory-region for boot and rpc\n"); + return -ENODEV; + } + + node = of_parse_phandle(np, "memory-region", 0); + if (!node) { + dev_err(core->dev, "boot-region of_parse_phandle error\n"); + return -ENODEV; + } + if (of_address_to_resource(node, 0, &res)) { + dev_err(core->dev, "boot-region of_address_to_resource error\n"); + of_node_put(node); + return -EINVAL; + } + core->fw.phys = res.start; + core->fw.length = resource_size(&res); + + of_node_put(node); + + node = of_parse_phandle(np, "memory-region", 1); + if (!node) { + dev_err(core->dev, "rpc-region of_parse_phandle error\n"); + return -ENODEV; + } + if (of_address_to_resource(node, 0, &res)) { + dev_err(core->dev, "rpc-region of_address_to_resource error\n"); + of_node_put(node); + return -EINVAL; + } + core->rpc.phys = res.start; + core->rpc.length = resource_size(&res); + + if (core->rpc.length < core->res->rpc_size + core->res->fwlog_size) { + dev_err(core->dev, "the rpc-region <%pad, 0x%x> is not enough\n", + &core->rpc.phys, core->rpc.length); + of_node_put(node); + return -EINVAL; + } + + core->fw.virt = memremap(core->fw.phys, core->fw.length, MEMREMAP_WC); + core->rpc.virt = memremap(core->rpc.phys, core->rpc.length, MEMREMAP_WC); + memset(core->rpc.virt, 0, core->rpc.length); + + ret = vpu_iface_check_memory_region(core, core->rpc.phys, core->rpc.length); + if (ret != VPU_CORE_MEMORY_UNCACHED) { + dev_err(core->dev, "rpc region<%pad, 0x%x> isn't uncached\n", + &core->rpc.phys, core->rpc.length); + of_node_put(node); + return -EINVAL; + } + + core->log.phys = core->rpc.phys + core->res->rpc_size; + core->log.virt = core->rpc.virt + core->res->rpc_size; + core->log.length = core->res->fwlog_size; + core->act.phys = core->log.phys + core->log.length; + core->act.virt = core->log.virt + core->log.length; + core->act.length = core->rpc.length - core->res->rpc_size - core->log.length; + core->rpc.length = core->res->rpc_size; + + of_node_put(node); + + return 0; +} + +static int vpu_core_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vpu_core *core; + struct vpu_dev *vpu = dev_get_drvdata(dev->parent); + struct vpu_shared_addr *iface; + u32 iface_data_size; + int ret; + + dev_dbg(dev, "probe\n"); + if (!vpu) + return -EINVAL; + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + core->pdev = pdev; + core->dev = dev; + platform_set_drvdata(pdev, core); + core->vpu = vpu; + INIT_LIST_HEAD(&core->instances); + mutex_init(&core->lock); + mutex_init(&core->cmd_lock); + init_completion(&core->cmp); + init_waitqueue_head(&core->ack_wq); + vpu_core_set_state(core, VPU_CORE_DEINIT); + + core->res = of_device_get_match_data(dev); + if (!core->res) + return -ENODEV; + + core->type = core->res->type; + core->id = of_alias_get_id(dev->of_node, "vpu_core"); + if (core->id < 0) { + dev_err(dev, "can't get vpu core id\n"); + return core->id; + } + dev_info(core->dev, "[%d] = %s\n", core->id, vpu_core_type_desc(core->type)); + ret = vpu_core_parse_dt(core, dev->of_node); + if (ret) + return ret; + + core->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(core->base)) + return PTR_ERR(core->base); + + if (!vpu_iface_check_codec(core)) { + dev_err(core->dev, "is not supported\n"); + return -EINVAL; + } + + ret = vpu_mbox_init(core); + if (ret) + return ret; + + iface = devm_kzalloc(dev, sizeof(*iface), GFP_KERNEL); + if (!iface) + return -ENOMEM; + + iface_data_size = vpu_iface_get_data_size(core); + if (iface_data_size) { + iface->priv = devm_kzalloc(dev, iface_data_size, GFP_KERNEL); + if (!iface->priv) + return -ENOMEM; + } + + ret = vpu_iface_init(core, iface, &core->rpc, core->fw.phys); + if (ret) { + dev_err(core->dev, "init iface fail, ret = %d\n", ret); + return ret; + } + + vpu_iface_config_system(core, vpu->res->mreg_base, vpu->base); + vpu_iface_set_log_buf(core, &core->log); + + pm_runtime_enable(dev); + ret = pm_runtime_resume_and_get(dev); + if (ret) { + pm_runtime_put_noidle(dev); + pm_runtime_set_suspended(dev); + goto err_runtime_disable; + } + + ret = vpu_core_register(dev->parent, core); + if (ret) + goto err_core_register; + core->parent = dev->parent; + + pm_runtime_put_sync(dev); + vpu_core_create_dbgfs_file(core); + + return 0; + +err_core_register: + pm_runtime_put_sync(dev); +err_runtime_disable: + pm_runtime_disable(dev); + + return ret; +} + +static int vpu_core_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vpu_core *core = platform_get_drvdata(pdev); + int ret; + + vpu_core_remove_dbgfs_file(core); + ret = pm_runtime_resume_and_get(dev); + WARN_ON(ret < 0); + + vpu_core_shutdown(core); + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + vpu_core_unregister(core->parent, core); + memunmap(core->fw.virt); + memunmap(core->rpc.virt); + mutex_destroy(&core->lock); + mutex_destroy(&core->cmd_lock); + + return 0; +} + +static int __maybe_unused vpu_core_runtime_resume(struct device *dev) +{ + struct vpu_core *core = dev_get_drvdata(dev); + + return vpu_mbox_request(core); +} + +static int __maybe_unused vpu_core_runtime_suspend(struct device *dev) +{ + struct vpu_core *core = dev_get_drvdata(dev); + + vpu_mbox_free(core); + return 0; +} + +static void vpu_core_cancel_work(struct vpu_core *core) +{ + struct vpu_inst *inst = NULL; + + cancel_work_sync(&core->msg_work); + cancel_delayed_work_sync(&core->msg_delayed_work); + + mutex_lock(&core->lock); + list_for_each_entry(inst, &core->instances, list) + cancel_work_sync(&inst->msg_work); + mutex_unlock(&core->lock); +} + +static void vpu_core_resume_work(struct vpu_core *core) +{ + struct vpu_inst *inst = NULL; + unsigned long delay = msecs_to_jiffies(10); + + queue_work(core->workqueue, &core->msg_work); + queue_delayed_work(core->workqueue, &core->msg_delayed_work, delay); + + mutex_lock(&core->lock); + list_for_each_entry(inst, &core->instances, list) + queue_work(inst->workqueue, &inst->msg_work); + mutex_unlock(&core->lock); +} + +static int __maybe_unused vpu_core_resume(struct device *dev) +{ + struct vpu_core *core = dev_get_drvdata(dev); + int ret = 0; + + mutex_lock(&core->lock); + pm_runtime_resume_and_get(dev); + vpu_core_get_vpu(core); + + if (core->request_count) { + if (!vpu_iface_get_power_state(core)) + ret = vpu_core_boot(core, false); + else + ret = vpu_core_sw_reset(core); + if (ret) { + dev_err(core->dev, "resume fail\n"); + vpu_core_set_state(core, VPU_CORE_HANG); + } + } + vpu_core_update_state(core); + pm_runtime_put_sync(dev); + mutex_unlock(&core->lock); + + vpu_core_resume_work(core); + return ret; +} + +static int __maybe_unused vpu_core_suspend(struct device *dev) +{ + struct vpu_core *core = dev_get_drvdata(dev); + int ret = 0; + + mutex_lock(&core->lock); + if (core->request_count) + ret = vpu_core_snapshot(core); + mutex_unlock(&core->lock); + if (ret) + return ret; + + vpu_core_cancel_work(core); + + mutex_lock(&core->lock); + vpu_core_put_vpu(core); + mutex_unlock(&core->lock); + return ret; +} + +static const struct dev_pm_ops vpu_core_pm_ops = { + SET_RUNTIME_PM_OPS(vpu_core_runtime_suspend, vpu_core_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(vpu_core_suspend, vpu_core_resume) +}; + +static struct vpu_core_resources imx8q_enc = { + .type = VPU_CORE_TYPE_ENC, + .fwname = "amphion/vpu/vpu_fw_imx8_enc.bin", + .stride = 16, + .max_width = 1920, + .max_height = 1920, + .min_width = 64, + .min_height = 48, + .step_width = 2, + .step_height = 2, + .rpc_size = 0x80000, + .fwlog_size = 0x80000, + .act_size = 0xc0000, +}; + +static struct vpu_core_resources imx8q_dec = { + .type = VPU_CORE_TYPE_DEC, + .fwname = "amphion/vpu/vpu_fw_imx8_dec.bin", + .stride = 256, + .max_width = 8188, + .max_height = 8188, + .min_width = 16, + .min_height = 16, + .step_width = 1, + .step_height = 1, + .rpc_size = 0x80000, + .fwlog_size = 0x80000, +}; + +static const struct of_device_id vpu_core_dt_match[] = { + { .compatible = "nxp,imx8q-vpu-encoder", .data = &imx8q_enc }, + { .compatible = "nxp,imx8q-vpu-decoder", .data = &imx8q_dec }, + {} +}; +MODULE_DEVICE_TABLE(of, vpu_core_dt_match); + +static struct platform_driver amphion_vpu_core_driver = { + .probe = vpu_core_probe, + .remove = vpu_core_remove, + .driver = { + .name = "amphion-vpu-core", + .of_match_table = vpu_core_dt_match, + .pm = &vpu_core_pm_ops, + }, +}; + +int __init vpu_core_driver_init(void) +{ + return platform_driver_register(&hion_vpu_core_driver); +} + +void __exit vpu_core_driver_exit(void) +{ + platform_driver_unregister(&hion_vpu_core_driver); +} diff --git a/drivers/media/platform/amphion/vpu_core.h b/drivers/media/platform/amphion/vpu_core.h new file mode 100644 index 000000000..65b562642 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_core.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_CORE_H +#define _AMPHION_VPU_CORE_H + +void csr_writel(struct vpu_core *core, u32 reg, u32 val); +u32 csr_readl(struct vpu_core *core, u32 reg); +int vpu_alloc_dma(struct vpu_core *core, struct vpu_buffer *buf); +void vpu_free_dma(struct vpu_buffer *buf); +struct vpu_inst *vpu_core_find_instance(struct vpu_core *core, u32 index); +void vpu_core_set_state(struct vpu_core *core, enum vpu_core_state state); + +#endif diff --git a/drivers/media/platform/amphion/vpu_dbg.c b/drivers/media/platform/amphion/vpu_dbg.c new file mode 100644 index 000000000..f105da82d --- /dev/null +++ b/drivers/media/platform/amphion/vpu_dbg.c @@ -0,0 +1,494 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_defs.h" +#include "vpu_core.h" +#include "vpu_helpers.h" +#include "vpu_cmds.h" +#include "vpu_rpc.h" +#include "vpu_v4l2.h" + +struct print_buf_desc { + u32 start_h_phy; + u32 start_h_vir; + u32 start_m; + u32 bytes; + u32 read; + u32 write; + char buffer[]; +}; + +static char *vb2_stat_name[] = { + [VB2_BUF_STATE_DEQUEUED] = "dequeued", + [VB2_BUF_STATE_IN_REQUEST] = "in_request", + [VB2_BUF_STATE_PREPARING] = "preparing", + [VB2_BUF_STATE_QUEUED] = "queued", + [VB2_BUF_STATE_ACTIVE] = "active", + [VB2_BUF_STATE_DONE] = "done", + [VB2_BUF_STATE_ERROR] = "error", +}; + +static char *vpu_stat_name[] = { + [VPU_BUF_STATE_IDLE] = "idle", + [VPU_BUF_STATE_INUSE] = "inuse", + [VPU_BUF_STATE_DECODED] = "decoded", + [VPU_BUF_STATE_READY] = "ready", + [VPU_BUF_STATE_SKIP] = "skip", + [VPU_BUF_STATE_ERROR] = "error", +}; + +static inline const char *to_vpu_stat_name(int state) +{ + if (state <= VPU_BUF_STATE_ERROR) + return vpu_stat_name[state]; + return "unknown"; +} + +static int vpu_dbg_instance(struct seq_file *s, void *data) +{ + struct vpu_inst *inst = s->private; + char str[128]; + int num; + struct vb2_queue *vq; + int i; + + if (!inst->fh.m2m_ctx) + return 0; + num = scnprintf(str, sizeof(str), "[%s]\n", vpu_core_type_desc(inst->type)); + if (seq_write(s, str, num)) + return 0; + + num = scnprintf(str, sizeof(str), "tgig = %d,pid = %d\n", inst->tgid, inst->pid); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), "state = %s\n", vpu_codec_state_name(inst->state)); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), + "min_buffer_out = %d, min_buffer_cap = %d\n", + inst->min_buffer_out, inst->min_buffer_cap); + if (seq_write(s, str, num)) + return 0; + + vq = v4l2_m2m_get_src_vq(inst->fh.m2m_ctx); + num = scnprintf(str, sizeof(str), + "output (%2d, %2d): fmt = %c%c%c%c %d x %d, %d;", + vb2_is_streaming(vq), + vq->num_buffers, + inst->out_format.pixfmt, + inst->out_format.pixfmt >> 8, + inst->out_format.pixfmt >> 16, + inst->out_format.pixfmt >> 24, + inst->out_format.width, + inst->out_format.height, + vq->last_buffer_dequeued); + if (seq_write(s, str, num)) + return 0; + for (i = 0; i < inst->out_format.num_planes; i++) { + num = scnprintf(str, sizeof(str), " %d(%d)", + inst->out_format.sizeimage[i], + inst->out_format.bytesperline[i]); + if (seq_write(s, str, num)) + return 0; + } + if (seq_write(s, "\n", 1)) + return 0; + + vq = v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx); + num = scnprintf(str, sizeof(str), + "capture(%2d, %2d): fmt = %c%c%c%c %d x %d, %d;", + vb2_is_streaming(vq), + vq->num_buffers, + inst->cap_format.pixfmt, + inst->cap_format.pixfmt >> 8, + inst->cap_format.pixfmt >> 16, + inst->cap_format.pixfmt >> 24, + inst->cap_format.width, + inst->cap_format.height, + vq->last_buffer_dequeued); + if (seq_write(s, str, num)) + return 0; + for (i = 0; i < inst->cap_format.num_planes; i++) { + num = scnprintf(str, sizeof(str), " %d(%d)", + inst->cap_format.sizeimage[i], + inst->cap_format.bytesperline[i]); + if (seq_write(s, str, num)) + return 0; + } + if (seq_write(s, "\n", 1)) + return 0; + num = scnprintf(str, sizeof(str), "crop: (%d, %d) %d x %d\n", + inst->crop.left, + inst->crop.top, + inst->crop.width, + inst->crop.height); + if (seq_write(s, str, num)) + return 0; + + vq = v4l2_m2m_get_src_vq(inst->fh.m2m_ctx); + for (i = 0; i < vq->num_buffers; i++) { + struct vb2_buffer *vb = vq->bufs[i]; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + if (vb->state == VB2_BUF_STATE_DEQUEUED) + continue; + num = scnprintf(str, sizeof(str), + "output [%2d] state = %10s, %8s\n", + i, vb2_stat_name[vb->state], + to_vpu_stat_name(vpu_get_buffer_state(vbuf))); + if (seq_write(s, str, num)) + return 0; + } + + vq = v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx); + for (i = 0; i < vq->num_buffers; i++) { + struct vb2_buffer *vb = vq->bufs[i]; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + if (vb->state == VB2_BUF_STATE_DEQUEUED) + continue; + num = scnprintf(str, sizeof(str), + "capture[%2d] state = %10s, %8s\n", + i, vb2_stat_name[vb->state], + to_vpu_stat_name(vpu_get_buffer_state(vbuf))); + if (seq_write(s, str, num)) + return 0; + } + + num = scnprintf(str, sizeof(str), "sequence = %d\n", inst->sequence); + if (seq_write(s, str, num)) + return 0; + + if (inst->use_stream_buffer) { + num = scnprintf(str, sizeof(str), "stream_buffer = %d / %d, <%pad, 0x%x>\n", + vpu_helper_get_used_space(inst), + inst->stream_buffer.length, + &inst->stream_buffer.phys, + inst->stream_buffer.length); + if (seq_write(s, str, num)) + return 0; + } + num = scnprintf(str, sizeof(str), "kfifo len = 0x%x\n", kfifo_len(&inst->msg_fifo)); + if (seq_write(s, str, num)) + return 0; + + num = scnprintf(str, sizeof(str), "flow :\n"); + if (seq_write(s, str, num)) + return 0; + + mutex_lock(&inst->core->cmd_lock); + for (i = 0; i < ARRAY_SIZE(inst->flows); i++) { + u32 idx = (inst->flow_idx + i) % (ARRAY_SIZE(inst->flows)); + + if (!inst->flows[idx]) + continue; + num = scnprintf(str, sizeof(str), "\t[%s] %s\n", + inst->flows[idx] >= VPU_MSG_ID_NOOP ? "M" : "C", + vpu_id_name(inst->flows[idx])); + if (seq_write(s, str, num)) { + mutex_unlock(&inst->core->cmd_lock); + return 0; + } + } + mutex_unlock(&inst->core->cmd_lock); + + i = 0; + while (true) { + num = call_vop(inst, get_debug_info, str, sizeof(str), i++); + if (num <= 0) + break; + if (seq_write(s, str, num)) + return 0; + } + + return 0; +} + +static int vpu_dbg_core(struct seq_file *s, void *data) +{ + struct vpu_core *core = s->private; + struct vpu_shared_addr *iface = core->iface; + char str[128]; + int num; + + num = scnprintf(str, sizeof(str), "[%s]\n", vpu_core_type_desc(core->type)); + if (seq_write(s, str, num)) + return 0; + + num = scnprintf(str, sizeof(str), "boot_region = <%pad, 0x%x>\n", + &core->fw.phys, core->fw.length); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), "rpc_region = <%pad, 0x%x> used = 0x%x\n", + &core->rpc.phys, core->rpc.length, core->rpc.bytesused); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), "fwlog_region = <%pad, 0x%x>\n", + &core->log.phys, core->log.length); + if (seq_write(s, str, num)) + return 0; + + num = scnprintf(str, sizeof(str), "power %s\n", + vpu_iface_get_power_state(core) ? "on" : "off"); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), "state = %d\n", core->state); + if (seq_write(s, str, num)) + return 0; + if (core->state == VPU_CORE_DEINIT) + return 0; + num = scnprintf(str, sizeof(str), "fw version = %d.%d.%d\n", + (core->fw_version >> 16) & 0xff, + (core->fw_version >> 8) & 0xff, + core->fw_version & 0xff); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), "instances = %d/%d (0x%02lx), %d\n", + hweight32(core->instance_mask), + core->supported_instance_count, + core->instance_mask, + core->request_count); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), "kfifo len = 0x%x\n", kfifo_len(&core->msg_fifo)); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), + "cmd_buf:[0x%x, 0x%x], wptr = 0x%x, rptr = 0x%x\n", + iface->cmd_desc->start, + iface->cmd_desc->end, + iface->cmd_desc->wptr, + iface->cmd_desc->rptr); + if (seq_write(s, str, num)) + return 0; + num = scnprintf(str, sizeof(str), + "msg_buf:[0x%x, 0x%x], wptr = 0x%x, rptr = 0x%x\n", + iface->msg_desc->start, + iface->msg_desc->end, + iface->msg_desc->wptr, + iface->msg_desc->rptr); + if (seq_write(s, str, num)) + return 0; + + return 0; +} + +static int vpu_dbg_fwlog(struct seq_file *s, void *data) +{ + struct vpu_core *core = s->private; + struct print_buf_desc *print_buf; + int length; + u32 rptr; + u32 wptr; + int ret = 0; + + if (!core->log.virt || core->state == VPU_CORE_DEINIT) + return 0; + + print_buf = core->log.virt; + rptr = print_buf->read; + wptr = print_buf->write; + + if (rptr == wptr) + return 0; + else if (rptr < wptr) + length = wptr - rptr; + else + length = print_buf->bytes + wptr - rptr; + + if (s->count + length >= s->size) { + s->count = s->size; + return 0; + } + + if (rptr + length >= print_buf->bytes) { + int num = print_buf->bytes - rptr; + + if (seq_write(s, print_buf->buffer + rptr, num)) + ret = -1; + length -= num; + rptr = 0; + } + + if (length) { + if (seq_write(s, print_buf->buffer + rptr, length)) + ret = -1; + rptr += length; + } + if (!ret) + print_buf->read = rptr; + + return 0; +} + +static int vpu_dbg_inst_open(struct inode *inode, struct file *filp) +{ + return single_open(filp, vpu_dbg_instance, inode->i_private); +} + +static ssize_t vpu_dbg_inst_write(struct file *file, + const char __user *user_buf, size_t size, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct vpu_inst *inst = s->private; + + vpu_session_debug(inst); + + return size; +} + +static ssize_t vpu_dbg_core_write(struct file *file, + const char __user *user_buf, size_t size, loff_t *ppos) +{ + struct seq_file *s = file->private_data; + struct vpu_core *core = s->private; + + pm_runtime_resume_and_get(core->dev); + mutex_lock(&core->lock); + if (vpu_iface_get_power_state(core) && !core->request_count) { + dev_info(core->dev, "reset\n"); + if (!vpu_core_sw_reset(core)) { + vpu_core_set_state(core, VPU_CORE_ACTIVE); + core->hang_mask = 0; + } + } + mutex_unlock(&core->lock); + pm_runtime_put_sync(core->dev); + + return size; +} + +static int vpu_dbg_core_open(struct inode *inode, struct file *filp) +{ + return single_open(filp, vpu_dbg_core, inode->i_private); +} + +static int vpu_dbg_fwlog_open(struct inode *inode, struct file *filp) +{ + return single_open(filp, vpu_dbg_fwlog, inode->i_private); +} + +static const struct file_operations vpu_dbg_inst_fops = { + .owner = THIS_MODULE, + .open = vpu_dbg_inst_open, + .release = single_release, + .read = seq_read, + .write = vpu_dbg_inst_write, +}; + +static const struct file_operations vpu_dbg_core_fops = { + .owner = THIS_MODULE, + .open = vpu_dbg_core_open, + .release = single_release, + .read = seq_read, + .write = vpu_dbg_core_write, +}; + +static const struct file_operations vpu_dbg_fwlog_fops = { + .owner = THIS_MODULE, + .open = vpu_dbg_fwlog_open, + .release = single_release, + .read = seq_read, +}; + +int vpu_inst_create_dbgfs_file(struct vpu_inst *inst) +{ + struct vpu_dev *vpu; + char name[64]; + + if (!inst || !inst->core || !inst->core->vpu) + return -EINVAL; + + vpu = inst->core->vpu; + if (!vpu->debugfs) + return -EINVAL; + + if (inst->debugfs) + return 0; + + scnprintf(name, sizeof(name), "instance.%d.%d", inst->core->id, inst->id); + inst->debugfs = debugfs_create_file((const char *)name, + VERIFY_OCTAL_PERMISSIONS(0644), + vpu->debugfs, + inst, + &vpu_dbg_inst_fops); + + return 0; +} + +int vpu_inst_remove_dbgfs_file(struct vpu_inst *inst) +{ + if (!inst) + return 0; + + debugfs_remove(inst->debugfs); + inst->debugfs = NULL; + + return 0; +} + +int vpu_core_create_dbgfs_file(struct vpu_core *core) +{ + struct vpu_dev *vpu; + char name[64]; + + if (!core || !core->vpu) + return -EINVAL; + + vpu = core->vpu; + if (!vpu->debugfs) + return -EINVAL; + + if (!core->debugfs) { + scnprintf(name, sizeof(name), "core.%d", core->id); + core->debugfs = debugfs_create_file((const char *)name, + VERIFY_OCTAL_PERMISSIONS(0644), + vpu->debugfs, + core, + &vpu_dbg_core_fops); + } + if (!core->debugfs_fwlog) { + scnprintf(name, sizeof(name), "fwlog.%d", core->id); + core->debugfs_fwlog = debugfs_create_file((const char *)name, + VERIFY_OCTAL_PERMISSIONS(0444), + vpu->debugfs, + core, + &vpu_dbg_fwlog_fops); + } + + return 0; +} + +int vpu_core_remove_dbgfs_file(struct vpu_core *core) +{ + if (!core) + return 0; + debugfs_remove(core->debugfs); + core->debugfs = NULL; + debugfs_remove(core->debugfs_fwlog); + core->debugfs_fwlog = NULL; + + return 0; +} + +void vpu_inst_record_flow(struct vpu_inst *inst, u32 flow) +{ + if (!inst) + return; + + inst->flows[inst->flow_idx] = flow; + inst->flow_idx = (inst->flow_idx + 1) % (ARRAY_SIZE(inst->flows)); +} diff --git a/drivers/media/platform/amphion/vpu_defs.h b/drivers/media/platform/amphion/vpu_defs.h new file mode 100644 index 000000000..732085266 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_defs.h @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_DEFS_H +#define _AMPHION_VPU_DEFS_H + +enum MSG_TYPE { + INIT_DONE = 1, + PRC_BUF_OFFSET, + BOOT_ADDRESS, + COMMAND, + EVENT, +}; + +enum { + VPU_IRQ_CODE_BOOT_DONE = 0x55, + VPU_IRQ_CODE_SNAPSHOT_DONE = 0xa5, + VPU_IRQ_CODE_SYNC = 0xaa, +}; + +enum { + VPU_CMD_ID_NOOP = 0x0, + VPU_CMD_ID_CONFIGURE_CODEC, + VPU_CMD_ID_START, + VPU_CMD_ID_STOP, + VPU_CMD_ID_ABORT, + VPU_CMD_ID_RST_BUF, + VPU_CMD_ID_SNAPSHOT, + VPU_CMD_ID_FIRM_RESET, + VPU_CMD_ID_UPDATE_PARAMETER, + VPU_CMD_ID_FRAME_ENCODE, + VPU_CMD_ID_SKIP, + VPU_CMD_ID_PARSE_NEXT_SEQ, + VPU_CMD_ID_PARSE_NEXT_I, + VPU_CMD_ID_PARSE_NEXT_IP, + VPU_CMD_ID_PARSE_NEXT_ANY, + VPU_CMD_ID_DEC_PIC, + VPU_CMD_ID_FS_ALLOC, + VPU_CMD_ID_FS_RELEASE, + VPU_CMD_ID_TIMESTAMP, + VPU_CMD_ID_DEBUG +}; + +enum { + VPU_MSG_ID_NOOP = 0x100, + VPU_MSG_ID_RESET_DONE, + VPU_MSG_ID_START_DONE, + VPU_MSG_ID_STOP_DONE, + VPU_MSG_ID_ABORT_DONE, + VPU_MSG_ID_BUF_RST, + VPU_MSG_ID_MEM_REQUEST, + VPU_MSG_ID_PARAM_UPD_DONE, + VPU_MSG_ID_FRAME_INPUT_DONE, + VPU_MSG_ID_ENC_DONE, + VPU_MSG_ID_DEC_DONE, + VPU_MSG_ID_FRAME_REQ, + VPU_MSG_ID_FRAME_RELEASE, + VPU_MSG_ID_SEQ_HDR_FOUND, + VPU_MSG_ID_RES_CHANGE, + VPU_MSG_ID_PIC_HDR_FOUND, + VPU_MSG_ID_PIC_DECODED, + VPU_MSG_ID_PIC_EOS, + VPU_MSG_ID_FIFO_LOW, + VPU_MSG_ID_FIFO_HIGH, + VPU_MSG_ID_FIFO_EMPTY, + VPU_MSG_ID_FIFO_FULL, + VPU_MSG_ID_BS_ERROR, + VPU_MSG_ID_UNSUPPORTED, + VPU_MSG_ID_TIMESTAMP_INFO, + VPU_MSG_ID_FIRMWARE_XCPT, + VPU_MSG_ID_PIC_SKIPPED, + VPU_MSG_ID_DBG_MSG, +}; + +enum VPU_ENC_MEMORY_RESOURSE { + MEM_RES_ENC, + MEM_RES_REF, + MEM_RES_ACT +}; + +enum VPU_DEC_MEMORY_RESOURCE { + MEM_RES_FRAME, + MEM_RES_MBI, + MEM_RES_DCP +}; + +enum VPU_SCODE_TYPE { + SCODE_PADDING_EOS = 1, + SCODE_PADDING_BUFFLUSH = 2, + SCODE_PADDING_ABORT = 3, + SCODE_SEQUENCE = 0x31, + SCODE_PICTURE = 0x32, + SCODE_SLICE = 0x33 +}; + +struct vpu_pkt_mem_req_data { + u32 enc_frame_size; + u32 enc_frame_num; + u32 ref_frame_size; + u32 ref_frame_num; + u32 act_buf_size; + u32 act_buf_num; +}; + +struct vpu_enc_pic_info { + u32 frame_id; + u32 pic_type; + u32 skipped_frame; + u32 error_flag; + u32 psnr; + u32 frame_size; + u32 wptr; + u32 crc; + s64 timestamp; +}; + +struct vpu_dec_codec_info { + u32 pixfmt; + u32 num_ref_frms; + u32 num_dpb_frms; + u32 num_dfe_area; + u32 color_primaries; + u32 transfer_chars; + u32 matrix_coeffs; + u32 full_range; + u32 vui_present; + u32 progressive; + u32 width; + u32 height; + u32 decoded_width; + u32 decoded_height; + struct v4l2_fract frame_rate; + u32 dsp_asp_ratio; + u32 level_idc; + u32 bit_depth_luma; + u32 bit_depth_chroma; + u32 chroma_fmt; + u32 mvc_num_views; + u32 offset_x; + u32 offset_y; + u32 tag; + u32 sizeimage[VIDEO_MAX_PLANES]; + u32 bytesperline[VIDEO_MAX_PLANES]; + u32 mbi_size; + u32 dcp_size; + u32 stride; +}; + +struct vpu_dec_pic_info { + u32 id; + u32 luma; + u32 start; + u32 end; + u32 pic_size; + u32 stride; + u32 skipped; + s64 timestamp; + u32 consumed_count; +}; + +struct vpu_fs_info { + u32 id; + u32 type; + u32 tag; + u32 luma_addr; + u32 luma_size; + u32 chroma_addr; + u32 chromau_size; + u32 chromav_addr; + u32 chromav_size; + u32 bytesperline; + u32 not_displayed; +}; + +struct vpu_ts_info { + s64 timestamp; + u32 size; +}; + +#define BITRATE_STEP (1024) +#define BITRATE_MIN (16 * BITRATE_STEP) +#define BITRATE_MAX (240 * 1024 * BITRATE_STEP) +#define BITRATE_DEFAULT (2 * 1024 * BITRATE_STEP) +#define BITRATE_DEFAULT_PEAK (BITRATE_DEFAULT * 2) + +#endif diff --git a/drivers/media/platform/amphion/vpu_drv.c b/drivers/media/platform/amphion/vpu_drv.c new file mode 100644 index 000000000..f01ce49d2 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_drv.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_imx8q.h" + +bool debug; +module_param(debug, bool, 0644); + +void vpu_writel(struct vpu_dev *vpu, u32 reg, u32 val) +{ + writel(val, vpu->base + reg); +} + +u32 vpu_readl(struct vpu_dev *vpu, u32 reg) +{ + return readl(vpu->base + reg); +} + +static void vpu_dev_get(struct vpu_dev *vpu) +{ + if (atomic_inc_return(&vpu->ref_vpu) == 1 && vpu->res->setup) + vpu->res->setup(vpu); +} + +static void vpu_dev_put(struct vpu_dev *vpu) +{ + atomic_dec(&vpu->ref_vpu); +} + +static void vpu_enc_get(struct vpu_dev *vpu) +{ + if (atomic_inc_return(&vpu->ref_enc) == 1 && vpu->res->setup_encoder) + vpu->res->setup_encoder(vpu); +} + +static void vpu_enc_put(struct vpu_dev *vpu) +{ + atomic_dec(&vpu->ref_enc); +} + +static void vpu_dec_get(struct vpu_dev *vpu) +{ + if (atomic_inc_return(&vpu->ref_dec) == 1 && vpu->res->setup_decoder) + vpu->res->setup_decoder(vpu); +} + +static void vpu_dec_put(struct vpu_dev *vpu) +{ + atomic_dec(&vpu->ref_dec); +} + +static int vpu_init_media_device(struct vpu_dev *vpu) +{ + vpu->mdev.dev = vpu->dev; + strscpy(vpu->mdev.model, "amphion-vpu", sizeof(vpu->mdev.model)); + strscpy(vpu->mdev.bus_info, "platform: amphion-vpu", sizeof(vpu->mdev.bus_info)); + media_device_init(&vpu->mdev); + vpu->v4l2_dev.mdev = &vpu->mdev; + + return 0; +} + +static int vpu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct vpu_dev *vpu; + int ret; + + dev_dbg(dev, "probe\n"); + vpu = devm_kzalloc(dev, sizeof(*vpu), GFP_KERNEL); + if (!vpu) + return -ENOMEM; + + vpu->pdev = pdev; + vpu->dev = dev; + mutex_init(&vpu->lock); + INIT_LIST_HEAD(&vpu->cores); + platform_set_drvdata(pdev, vpu); + atomic_set(&vpu->ref_vpu, 0); + atomic_set(&vpu->ref_enc, 0); + atomic_set(&vpu->ref_dec, 0); + vpu->get_vpu = vpu_dev_get; + vpu->put_vpu = vpu_dev_put; + vpu->get_enc = vpu_enc_get; + vpu->put_enc = vpu_enc_put; + vpu->get_dec = vpu_dec_get; + vpu->put_dec = vpu_dec_put; + + vpu->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(vpu->base)) + return PTR_ERR(vpu->base); + + vpu->res = of_device_get_match_data(dev); + if (!vpu->res) + return -ENODEV; + + pm_runtime_enable(dev); + + ret = v4l2_device_register(dev, &vpu->v4l2_dev); + if (ret) + goto err_vpu_deinit; + + vpu_init_media_device(vpu); + vpu->encoder.type = VPU_CORE_TYPE_ENC; + vpu->encoder.function = MEDIA_ENT_F_PROC_VIDEO_ENCODER; + vpu->decoder.type = VPU_CORE_TYPE_DEC; + vpu->decoder.function = MEDIA_ENT_F_PROC_VIDEO_DECODER; + ret = vpu_add_func(vpu, &vpu->decoder); + if (ret) + goto err_add_decoder; + ret = vpu_add_func(vpu, &vpu->encoder); + if (ret) + goto err_add_encoder; + ret = media_device_register(&vpu->mdev); + if (ret) + goto err_vpu_media; + vpu->debugfs = debugfs_create_dir("amphion_vpu", NULL); + + of_platform_populate(dev->of_node, NULL, NULL, dev); + + return 0; + +err_vpu_media: + vpu_remove_func(&vpu->encoder); +err_add_encoder: + vpu_remove_func(&vpu->decoder); +err_add_decoder: + media_device_cleanup(&vpu->mdev); + v4l2_device_unregister(&vpu->v4l2_dev); +err_vpu_deinit: + pm_runtime_set_suspended(dev); + pm_runtime_disable(dev); + + return ret; +} + +static int vpu_remove(struct platform_device *pdev) +{ + struct vpu_dev *vpu = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + debugfs_remove_recursive(vpu->debugfs); + vpu->debugfs = NULL; + + pm_runtime_disable(dev); + + media_device_unregister(&vpu->mdev); + vpu_remove_func(&vpu->decoder); + vpu_remove_func(&vpu->encoder); + media_device_cleanup(&vpu->mdev); + v4l2_device_unregister(&vpu->v4l2_dev); + mutex_destroy(&vpu->lock); + + return 0; +} + +static int __maybe_unused vpu_runtime_resume(struct device *dev) +{ + return 0; +} + +static int __maybe_unused vpu_runtime_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused vpu_resume(struct device *dev) +{ + return 0; +} + +static int __maybe_unused vpu_suspend(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops vpu_pm_ops = { + SET_RUNTIME_PM_OPS(vpu_runtime_suspend, vpu_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(vpu_suspend, vpu_resume) +}; + +static struct vpu_resources imx8qxp_res = { + .plat_type = IMX8QXP, + .mreg_base = 0x40000000, + .setup = vpu_imx8q_setup, + .setup_encoder = vpu_imx8q_setup_enc, + .setup_decoder = vpu_imx8q_setup_dec, + .reset = vpu_imx8q_reset +}; + +static struct vpu_resources imx8qm_res = { + .plat_type = IMX8QM, + .mreg_base = 0x40000000, + .setup = vpu_imx8q_setup, + .setup_encoder = vpu_imx8q_setup_enc, + .setup_decoder = vpu_imx8q_setup_dec, + .reset = vpu_imx8q_reset +}; + +static const struct of_device_id vpu_dt_match[] = { + { .compatible = "nxp,imx8qxp-vpu", .data = &imx8qxp_res }, + { .compatible = "nxp,imx8qm-vpu", .data = &imx8qm_res }, + {} +}; +MODULE_DEVICE_TABLE(of, vpu_dt_match); + +static struct platform_driver amphion_vpu_driver = { + .probe = vpu_probe, + .remove = vpu_remove, + .driver = { + .name = "amphion-vpu", + .of_match_table = vpu_dt_match, + .pm = &vpu_pm_ops, + }, +}; + +static int __init vpu_driver_init(void) +{ + int ret; + + ret = platform_driver_register(&hion_vpu_driver); + if (ret) + return ret; + + ret = vpu_core_driver_init(); + if (ret) + platform_driver_unregister(&hion_vpu_driver); + + return ret; +} + +static void __exit vpu_driver_exit(void) +{ + vpu_core_driver_exit(); + platform_driver_unregister(&hion_vpu_driver); +} +module_init(vpu_driver_init); +module_exit(vpu_driver_exit); + +MODULE_AUTHOR("Freescale Semiconductor, Inc."); +MODULE_DESCRIPTION("Linux VPU driver for Freescale i.MX8Q"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/amphion/vpu_helpers.c b/drivers/media/platform/amphion/vpu_helpers.c new file mode 100644 index 000000000..66fdb0bae --- /dev/null +++ b/drivers/media/platform/amphion/vpu_helpers.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_defs.h" +#include "vpu_core.h" +#include "vpu_rpc.h" +#include "vpu_helpers.h" + +int vpu_helper_find_in_array_u8(const u8 *array, u32 size, u32 x) +{ + int i; + + for (i = 0; i < size; i++) { + if (array[i] == x) + return i; + } + + return 0; +} + +bool vpu_helper_check_type(struct vpu_inst *inst, u32 type) +{ + const struct vpu_format *pfmt; + + for (pfmt = inst->formats; pfmt->pixfmt; pfmt++) { + if (!vpu_iface_check_format(inst, pfmt->pixfmt)) + continue; + if (pfmt->type == type) + return true; + } + + return false; +} + +const struct vpu_format *vpu_helper_find_format(struct vpu_inst *inst, u32 type, u32 pixelfmt) +{ + const struct vpu_format *pfmt; + + if (!inst || !inst->formats) + return NULL; + + if (!vpu_iface_check_format(inst, pixelfmt)) + return NULL; + + for (pfmt = inst->formats; pfmt->pixfmt; pfmt++) { + if (pfmt->pixfmt == pixelfmt && (!type || type == pfmt->type)) + return pfmt; + } + + return NULL; +} + +const struct vpu_format *vpu_helper_enum_format(struct vpu_inst *inst, u32 type, int index) +{ + const struct vpu_format *pfmt; + int i = 0; + + if (!inst || !inst->formats) + return NULL; + + for (pfmt = inst->formats; pfmt->pixfmt; pfmt++) { + if (!vpu_iface_check_format(inst, pfmt->pixfmt)) + continue; + + if (pfmt->type == type) { + if (index == i) + return pfmt; + i++; + } + } + + return NULL; +} + +u32 vpu_helper_valid_frame_width(struct vpu_inst *inst, u32 width) +{ + const struct vpu_core_resources *res; + + if (!inst) + return width; + + res = vpu_get_resource(inst); + if (!res) + return width; + if (res->max_width) + width = clamp(width, res->min_width, res->max_width); + if (res->step_width) + width = ALIGN(width, res->step_width); + + return width; +} + +u32 vpu_helper_valid_frame_height(struct vpu_inst *inst, u32 height) +{ + const struct vpu_core_resources *res; + + if (!inst) + return height; + + res = vpu_get_resource(inst); + if (!res) + return height; + if (res->max_height) + height = clamp(height, res->min_height, res->max_height); + if (res->step_height) + height = ALIGN(height, res->step_height); + + return height; +} + +static u32 get_nv12_plane_size(u32 width, u32 height, int plane_no, + u32 stride, u32 interlaced, u32 *pbl) +{ + u32 bytesperline; + u32 size = 0; + + bytesperline = ALIGN(width, stride); + if (pbl) + bytesperline = max(bytesperline, *pbl); + height = ALIGN(height, 2); + if (plane_no == 0) + size = bytesperline * height; + else if (plane_no == 1) + size = bytesperline * height >> 1; + if (pbl) + *pbl = bytesperline; + + return size; +} + +static u32 get_tiled_8l128_plane_size(u32 fmt, u32 width, u32 height, int plane_no, + u32 stride, u32 interlaced, u32 *pbl) +{ + u32 ws = 3; + u32 hs = 7; + u32 bitdepth = 8; + u32 bytesperline; + u32 size = 0; + + if (interlaced) + hs++; + if (fmt == V4L2_PIX_FMT_NV12M_10BE_8L128) + bitdepth = 10; + bytesperline = DIV_ROUND_UP(width * bitdepth, BITS_PER_BYTE); + bytesperline = ALIGN(bytesperline, 1 << ws); + bytesperline = ALIGN(bytesperline, stride); + if (pbl) + bytesperline = max(bytesperline, *pbl); + height = ALIGN(height, 1 << hs); + if (plane_no == 0) + size = bytesperline * height; + else if (plane_no == 1) + size = (bytesperline * ALIGN(height, 1 << (hs + 1))) >> 1; + if (pbl) + *pbl = bytesperline; + + return size; +} + +static u32 get_default_plane_size(u32 width, u32 height, int plane_no, + u32 stride, u32 interlaced, u32 *pbl) +{ + u32 bytesperline; + u32 size = 0; + + bytesperline = ALIGN(width, stride); + if (pbl) + bytesperline = max(bytesperline, *pbl); + if (plane_no == 0) + size = bytesperline * height; + if (pbl) + *pbl = bytesperline; + + return size; +} + +u32 vpu_helper_get_plane_size(u32 fmt, u32 w, u32 h, int plane_no, + u32 stride, u32 interlaced, u32 *pbl) +{ + switch (fmt) { + case V4L2_PIX_FMT_NV12M: + return get_nv12_plane_size(w, h, plane_no, stride, interlaced, pbl); + case V4L2_PIX_FMT_NV12M_8L128: + case V4L2_PIX_FMT_NV12M_10BE_8L128: + return get_tiled_8l128_plane_size(fmt, w, h, plane_no, stride, interlaced, pbl); + default: + return get_default_plane_size(w, h, plane_no, stride, interlaced, pbl); + } +} + +int vpu_helper_copy_from_stream_buffer(struct vpu_buffer *stream_buffer, + u32 *rptr, u32 size, void *dst) +{ + u32 offset; + u32 start; + u32 end; + void *virt; + + if (!stream_buffer || !rptr || !dst) + return -EINVAL; + + if (!size) + return 0; + + offset = *rptr; + start = stream_buffer->phys; + end = start + stream_buffer->length; + virt = stream_buffer->virt; + + if (offset < start || offset > end) + return -EINVAL; + + if (offset + size <= end) { + memcpy(dst, virt + (offset - start), size); + } else { + memcpy(dst, virt + (offset - start), end - offset); + memcpy(dst + end - offset, virt, size + offset - end); + } + + *rptr = vpu_helper_step_walk(stream_buffer, offset, size); + + return 0; +} + +int vpu_helper_copy_to_stream_buffer(struct vpu_buffer *stream_buffer, + u32 *wptr, u32 size, void *src) +{ + u32 offset; + u32 start; + u32 end; + void *virt; + + if (!stream_buffer || !wptr || !src) + return -EINVAL; + + if (!size) + return 0; + + offset = *wptr; + start = stream_buffer->phys; + end = start + stream_buffer->length; + virt = stream_buffer->virt; + if (offset < start || offset > end) + return -EINVAL; + + if (offset + size <= end) { + memcpy(virt + (offset - start), src, size); + } else { + memcpy(virt + (offset - start), src, end - offset); + memcpy(virt, src + end - offset, size + offset - end); + } + + *wptr = vpu_helper_step_walk(stream_buffer, offset, size); + + return 0; +} + +int vpu_helper_memset_stream_buffer(struct vpu_buffer *stream_buffer, + u32 *wptr, u8 val, u32 size) +{ + u32 offset; + u32 start; + u32 end; + void *virt; + + if (!stream_buffer || !wptr) + return -EINVAL; + + if (!size) + return 0; + + offset = *wptr; + start = stream_buffer->phys; + end = start + stream_buffer->length; + virt = stream_buffer->virt; + if (offset < start || offset > end) + return -EINVAL; + + if (offset + size <= end) { + memset(virt + (offset - start), val, size); + } else { + memset(virt + (offset - start), val, end - offset); + memset(virt, val, size + offset - end); + } + + offset += size; + if (offset >= end) + offset -= stream_buffer->length; + + *wptr = offset; + + return 0; +} + +u32 vpu_helper_get_free_space(struct vpu_inst *inst) +{ + struct vpu_rpc_buffer_desc desc; + + if (vpu_iface_get_stream_buffer_desc(inst, &desc)) + return 0; + + if (desc.rptr > desc.wptr) + return desc.rptr - desc.wptr; + else if (desc.rptr < desc.wptr) + return (desc.end - desc.start + desc.rptr - desc.wptr); + else + return desc.end - desc.start; +} + +u32 vpu_helper_get_used_space(struct vpu_inst *inst) +{ + struct vpu_rpc_buffer_desc desc; + + if (vpu_iface_get_stream_buffer_desc(inst, &desc)) + return 0; + + if (desc.wptr > desc.rptr) + return desc.wptr - desc.rptr; + else if (desc.wptr < desc.rptr) + return (desc.end - desc.start + desc.wptr - desc.rptr); + else + return 0; +} + +int vpu_helper_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vpu_inst *inst = ctrl_to_inst(ctrl); + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + ctrl->val = inst->min_buffer_cap; + break; + case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT: + ctrl->val = inst->min_buffer_out; + break; + default: + return -EINVAL; + } + + return 0; +} + +int vpu_helper_find_startcode(struct vpu_buffer *stream_buffer, + u32 pixelformat, u32 offset, u32 bytesused) +{ + u32 start_code; + int start_code_size; + u32 val = 0; + int i; + int ret = -EINVAL; + + if (!stream_buffer || !stream_buffer->virt) + return -EINVAL; + + switch (pixelformat) { + case V4L2_PIX_FMT_H264: + start_code_size = 4; + start_code = 0x00000001; + break; + default: + return 0; + } + + for (i = 0; i < bytesused; i++) { + val = (val << 8) | vpu_helper_read_byte(stream_buffer, offset + i); + if (i < start_code_size - 1) + continue; + if (val == start_code) { + ret = i + 1 - start_code_size; + break; + } + } + + return ret; +} + +int vpu_find_dst_by_src(struct vpu_pair *pairs, u32 cnt, u32 src) +{ + u32 i; + + if (!pairs || !cnt) + return -EINVAL; + + for (i = 0; i < cnt; i++) { + if (pairs[i].src == src) + return pairs[i].dst; + } + + return -EINVAL; +} + +int vpu_find_src_by_dst(struct vpu_pair *pairs, u32 cnt, u32 dst) +{ + u32 i; + + if (!pairs || !cnt) + return -EINVAL; + + for (i = 0; i < cnt; i++) { + if (pairs[i].dst == dst) + return pairs[i].src; + } + + return -EINVAL; +} + +const char *vpu_id_name(u32 id) +{ + switch (id) { + case VPU_CMD_ID_NOOP: return "noop"; + case VPU_CMD_ID_CONFIGURE_CODEC: return "configure codec"; + case VPU_CMD_ID_START: return "start"; + case VPU_CMD_ID_STOP: return "stop"; + case VPU_CMD_ID_ABORT: return "abort"; + case VPU_CMD_ID_RST_BUF: return "reset buf"; + case VPU_CMD_ID_SNAPSHOT: return "snapshot"; + case VPU_CMD_ID_FIRM_RESET: return "reset firmware"; + case VPU_CMD_ID_UPDATE_PARAMETER: return "update parameter"; + case VPU_CMD_ID_FRAME_ENCODE: return "encode frame"; + case VPU_CMD_ID_SKIP: return "skip"; + case VPU_CMD_ID_FS_ALLOC: return "alloc fb"; + case VPU_CMD_ID_FS_RELEASE: return "release fb"; + case VPU_CMD_ID_TIMESTAMP: return "timestamp"; + case VPU_CMD_ID_DEBUG: return "debug"; + case VPU_MSG_ID_RESET_DONE: return "reset done"; + case VPU_MSG_ID_START_DONE: return "start done"; + case VPU_MSG_ID_STOP_DONE: return "stop done"; + case VPU_MSG_ID_ABORT_DONE: return "abort done"; + case VPU_MSG_ID_BUF_RST: return "buf reset done"; + case VPU_MSG_ID_MEM_REQUEST: return "mem request"; + case VPU_MSG_ID_PARAM_UPD_DONE: return "param upd done"; + case VPU_MSG_ID_FRAME_INPUT_DONE: return "frame input done"; + case VPU_MSG_ID_ENC_DONE: return "encode done"; + case VPU_MSG_ID_DEC_DONE: return "frame display"; + case VPU_MSG_ID_FRAME_REQ: return "fb request"; + case VPU_MSG_ID_FRAME_RELEASE: return "fb release"; + case VPU_MSG_ID_SEQ_HDR_FOUND: return "seq hdr found"; + case VPU_MSG_ID_RES_CHANGE: return "resolution change"; + case VPU_MSG_ID_PIC_HDR_FOUND: return "pic hdr found"; + case VPU_MSG_ID_PIC_DECODED: return "picture decoded"; + case VPU_MSG_ID_PIC_EOS: return "eos"; + case VPU_MSG_ID_FIFO_LOW: return "fifo low"; + case VPU_MSG_ID_BS_ERROR: return "bs error"; + case VPU_MSG_ID_UNSUPPORTED: return "unsupported"; + case VPU_MSG_ID_FIRMWARE_XCPT: return "exception"; + case VPU_MSG_ID_PIC_SKIPPED: return "skipped"; + case VPU_MSG_ID_DBG_MSG: return "debug msg"; + } + return ""; +} + +const char *vpu_codec_state_name(enum vpu_codec_state state) +{ + switch (state) { + case VPU_CODEC_STATE_DEINIT: return "initialization"; + case VPU_CODEC_STATE_CONFIGURED: return "configured"; + case VPU_CODEC_STATE_START: return "start"; + case VPU_CODEC_STATE_STARTED: return "started"; + case VPU_CODEC_STATE_ACTIVE: return "active"; + case VPU_CODEC_STATE_SEEK: return "seek"; + case VPU_CODEC_STATE_STOP: return "stop"; + case VPU_CODEC_STATE_DRAIN: return "drain"; + case VPU_CODEC_STATE_DYAMIC_RESOLUTION_CHANGE: return "resolution change"; + } + return ""; +} diff --git a/drivers/media/platform/amphion/vpu_helpers.h b/drivers/media/platform/amphion/vpu_helpers.h new file mode 100644 index 000000000..bc2835095 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_helpers.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_HELPERS_H +#define _AMPHION_VPU_HELPERS_H + +struct vpu_pair { + u32 src; + u32 dst; +}; + +int vpu_helper_find_in_array_u8(const u8 *array, u32 size, u32 x); +bool vpu_helper_check_type(struct vpu_inst *inst, u32 type); +const struct vpu_format *vpu_helper_find_format(struct vpu_inst *inst, u32 type, u32 pixelfmt); +const struct vpu_format *vpu_helper_enum_format(struct vpu_inst *inst, u32 type, int index); +u32 vpu_helper_valid_frame_width(struct vpu_inst *inst, u32 width); +u32 vpu_helper_valid_frame_height(struct vpu_inst *inst, u32 height); +u32 vpu_helper_get_plane_size(u32 fmt, u32 width, u32 height, int plane_no, + u32 stride, u32 interlaced, u32 *pbl); +int vpu_helper_copy_from_stream_buffer(struct vpu_buffer *stream_buffer, + u32 *rptr, u32 size, void *dst); +int vpu_helper_copy_to_stream_buffer(struct vpu_buffer *stream_buffer, + u32 *wptr, u32 size, void *src); +int vpu_helper_memset_stream_buffer(struct vpu_buffer *stream_buffer, + u32 *wptr, u8 val, u32 size); +u32 vpu_helper_get_free_space(struct vpu_inst *inst); +u32 vpu_helper_get_used_space(struct vpu_inst *inst); +int vpu_helper_g_volatile_ctrl(struct v4l2_ctrl *ctrl); +void vpu_helper_get_kmp_next(const u8 *pattern, int *next, int size); +int vpu_helper_kmp_search(u8 *s, int s_len, const u8 *p, int p_len, int *next); +int vpu_helper_kmp_search_in_stream_buffer(struct vpu_buffer *stream_buffer, + u32 offset, int bytesused, + const u8 *p, int p_len, int *next); +int vpu_helper_find_startcode(struct vpu_buffer *stream_buffer, + u32 pixelformat, u32 offset, u32 bytesused); + +static inline u32 vpu_helper_step_walk(struct vpu_buffer *stream_buffer, u32 pos, u32 step) +{ + pos += step; + if (pos > stream_buffer->phys + stream_buffer->length) + pos -= stream_buffer->length; + + return pos; +} + +static inline u8 vpu_helper_read_byte(struct vpu_buffer *stream_buffer, u32 pos) +{ + u8 *pdata = (u8 *)stream_buffer->virt; + + return pdata[pos % stream_buffer->length]; +} + +int vpu_color_check_primaries(u32 primaries); +int vpu_color_check_transfers(u32 transfers); +int vpu_color_check_matrix(u32 matrix); +int vpu_color_check_full_range(u32 full_range); +u32 vpu_color_cvrt_primaries_v2i(u32 primaries); +u32 vpu_color_cvrt_primaries_i2v(u32 primaries); +u32 vpu_color_cvrt_transfers_v2i(u32 transfers); +u32 vpu_color_cvrt_transfers_i2v(u32 transfers); +u32 vpu_color_cvrt_matrix_v2i(u32 matrix); +u32 vpu_color_cvrt_matrix_i2v(u32 matrix); +u32 vpu_color_cvrt_full_range_v2i(u32 full_range); +u32 vpu_color_cvrt_full_range_i2v(u32 full_range); +int vpu_color_get_default(u32 primaries, u32 *ptransfers, u32 *pmatrix, u32 *pfull_range); + +int vpu_find_dst_by_src(struct vpu_pair *pairs, u32 cnt, u32 src); +int vpu_find_src_by_dst(struct vpu_pair *pairs, u32 cnt, u32 dst); +#endif diff --git a/drivers/media/platform/amphion/vpu_imx8q.c b/drivers/media/platform/amphion/vpu_imx8q.c new file mode 100644 index 000000000..f14c2b831 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_imx8q.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_core.h" +#include "vpu_imx8q.h" +#include "vpu_rpc.h" + +#define IMX8Q_CSR_CM0Px_ADDR_OFFSET 0x00000000 +#define IMX8Q_CSR_CM0Px_CPUWAIT 0x00000004 + +#ifdef CONFIG_IMX_SCU +#include +#include + +#define VPU_DISABLE_BITS 0x7 +#define VPU_IMX_DECODER_FUSE_OFFSET 14 +#define VPU_ENCODER_MASK 0x1 +#define VPU_DECODER_MASK 0x3UL +#define VPU_DECODER_H264_MASK 0x2UL +#define VPU_DECODER_HEVC_MASK 0x1UL + +static u32 imx8q_fuse; + +struct vpu_sc_msg_misc { + struct imx_sc_rpc_msg hdr; + u32 word; +} __packed; +#endif + +int vpu_imx8q_setup_dec(struct vpu_dev *vpu) +{ + const off_t offset = DEC_MFD_XREG_SLV_BASE + MFD_BLK_CTRL; + + vpu_writel(vpu, offset + MFD_BLK_CTRL_MFD_SYS_CLOCK_ENABLE_SET, 0x1f); + vpu_writel(vpu, offset + MFD_BLK_CTRL_MFD_SYS_RESET_SET, 0xffffffff); + + return 0; +} + +int vpu_imx8q_setup_enc(struct vpu_dev *vpu) +{ + return 0; +} + +int vpu_imx8q_setup(struct vpu_dev *vpu) +{ + const off_t offset = SCB_XREG_SLV_BASE + SCB_SCB_BLK_CTRL; + + vpu_readl(vpu, offset + 0x108); + + vpu_writel(vpu, offset + SCB_BLK_CTRL_SCB_CLK_ENABLE_SET, 0x1); + vpu_writel(vpu, offset + 0x190, 0xffffffff); + vpu_writel(vpu, offset + SCB_BLK_CTRL_XMEM_RESET_SET, 0xffffffff); + vpu_writel(vpu, offset + SCB_BLK_CTRL_SCB_CLK_ENABLE_SET, 0xE); + vpu_writel(vpu, offset + SCB_BLK_CTRL_CACHE_RESET_SET, 0x7); + vpu_writel(vpu, XMEM_CONTROL, 0x102); + + vpu_readl(vpu, offset + 0x108); + + return 0; +} + +static int vpu_imx8q_reset_enc(struct vpu_dev *vpu) +{ + return 0; +} + +static int vpu_imx8q_reset_dec(struct vpu_dev *vpu) +{ + const off_t offset = DEC_MFD_XREG_SLV_BASE + MFD_BLK_CTRL; + + vpu_writel(vpu, offset + MFD_BLK_CTRL_MFD_SYS_RESET_CLR, 0xffffffff); + + return 0; +} + +int vpu_imx8q_reset(struct vpu_dev *vpu) +{ + const off_t offset = SCB_XREG_SLV_BASE + SCB_SCB_BLK_CTRL; + + vpu_writel(vpu, offset + SCB_BLK_CTRL_CACHE_RESET_CLR, 0x7); + vpu_imx8q_reset_enc(vpu); + vpu_imx8q_reset_dec(vpu); + + return 0; +} + +int vpu_imx8q_set_system_cfg_common(struct vpu_rpc_system_config *config, u32 regs, u32 core_id) +{ + if (!config) + return -EINVAL; + + switch (core_id) { + case 0: + config->malone_base_addr[0] = regs + DEC_MFD_XREG_SLV_BASE; + config->num_malones = 1; + config->num_windsors = 0; + break; + case 1: + config->windsor_base_addr[0] = regs + ENC_MFD_XREG_SLV_0_BASE; + config->num_windsors = 1; + config->num_malones = 0; + break; + case 2: + config->windsor_base_addr[0] = regs + ENC_MFD_XREG_SLV_1_BASE; + config->num_windsors = 1; + config->num_malones = 0; + break; + default: + return -EINVAL; + } + if (config->num_windsors) { + config->windsor_irq_pin[0x0][0x0] = WINDSOR_PAL_IRQ_PIN_L; + config->windsor_irq_pin[0x0][0x1] = WINDSOR_PAL_IRQ_PIN_H; + } + + config->malone_base_addr[0x1] = 0x0; + config->hif_offset[0x0] = MFD_HIF; + config->hif_offset[0x1] = 0x0; + + config->dpv_base_addr = 0x0; + config->dpv_irq_pin = 0x0; + config->pixif_base_addr = regs + DEC_MFD_XREG_SLV_BASE + MFD_PIX_IF; + config->cache_base_addr[0] = regs + MC_CACHE_0_BASE; + config->cache_base_addr[1] = regs + MC_CACHE_1_BASE; + + return 0; +} + +int vpu_imx8q_boot_core(struct vpu_core *core) +{ + csr_writel(core, IMX8Q_CSR_CM0Px_ADDR_OFFSET, core->fw.phys); + csr_writel(core, IMX8Q_CSR_CM0Px_CPUWAIT, 0); + return 0; +} + +int vpu_imx8q_get_power_state(struct vpu_core *core) +{ + if (csr_readl(core, IMX8Q_CSR_CM0Px_CPUWAIT) == 1) + return 0; + return 1; +} + +int vpu_imx8q_on_firmware_loaded(struct vpu_core *core) +{ + u8 *p; + + p = core->fw.virt; + p[16] = core->vpu->res->plat_type; + p[17] = core->id; + p[18] = 1; + + return 0; +} + +int vpu_imx8q_check_memory_region(dma_addr_t base, dma_addr_t addr, u32 size) +{ + const struct vpu_rpc_region_t imx8q_regions[] = { + {0x00000000, 0x08000000, VPU_CORE_MEMORY_CACHED}, + {0x08000000, 0x10000000, VPU_CORE_MEMORY_UNCACHED}, + {0x10000000, 0x20000000, VPU_CORE_MEMORY_CACHED}, + {0x20000000, 0x40000000, VPU_CORE_MEMORY_UNCACHED} + }; + int i; + + if (addr < base) + return VPU_CORE_MEMORY_INVALID; + + addr -= base; + for (i = 0; i < ARRAY_SIZE(imx8q_regions); i++) { + const struct vpu_rpc_region_t *region = &imx8q_regions[i]; + + if (addr >= region->start && addr + size < region->end) + return region->type; + } + + return VPU_CORE_MEMORY_INVALID; +} + +#ifdef CONFIG_IMX_SCU +static u32 vpu_imx8q_get_fuse(void) +{ + static u32 fuse_got; + struct imx_sc_ipc *ipc; + struct vpu_sc_msg_misc msg; + struct imx_sc_rpc_msg *hdr = &msg.hdr; + int ret; + + if (fuse_got) + return imx8q_fuse; + + ret = imx_scu_get_handle(&ipc); + if (ret) { + pr_err("error: get sct handle fail: %d\n", ret); + return 0; + } + + hdr->ver = IMX_SC_RPC_VERSION; + hdr->svc = IMX_SC_RPC_SVC_MISC; + hdr->func = IMX_SC_MISC_FUNC_OTP_FUSE_READ; + hdr->size = 2; + + msg.word = VPU_DISABLE_BITS; + + ret = imx_scu_call_rpc(ipc, &msg, true); + if (ret) + return 0; + + imx8q_fuse = msg.word; + fuse_got = 1; + return imx8q_fuse; +} + +bool vpu_imx8q_check_codec(enum vpu_core_type type) +{ + u32 fuse = vpu_imx8q_get_fuse(); + + if (type == VPU_CORE_TYPE_ENC) { + if (fuse & VPU_ENCODER_MASK) + return false; + } else if (type == VPU_CORE_TYPE_DEC) { + fuse >>= VPU_IMX_DECODER_FUSE_OFFSET; + fuse &= VPU_DECODER_MASK; + + if (fuse == VPU_DECODER_MASK) + return false; + } + return true; +} + +bool vpu_imx8q_check_fmt(enum vpu_core_type type, u32 pixelfmt) +{ + u32 fuse = vpu_imx8q_get_fuse(); + + if (type == VPU_CORE_TYPE_DEC) { + fuse >>= VPU_IMX_DECODER_FUSE_OFFSET; + fuse &= VPU_DECODER_MASK; + + if (fuse == VPU_DECODER_HEVC_MASK && pixelfmt == V4L2_PIX_FMT_HEVC) + return false; + if (fuse == VPU_DECODER_H264_MASK && pixelfmt == V4L2_PIX_FMT_H264) + return false; + if (fuse == VPU_DECODER_MASK) + return false; + } + + return true; +} +#else +bool vpu_imx8q_check_codec(enum vpu_core_type type) +{ + return true; +} + +bool vpu_imx8q_check_fmt(enum vpu_core_type type, u32 pixelfmt) +{ + return true; +} +#endif diff --git a/drivers/media/platform/amphion/vpu_imx8q.h b/drivers/media/platform/amphion/vpu_imx8q.h new file mode 100644 index 000000000..9deffd7dd --- /dev/null +++ b/drivers/media/platform/amphion/vpu_imx8q.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_IMX8Q_H +#define _AMPHION_VPU_IMX8Q_H + +#define SCB_XREG_SLV_BASE 0x00000000 +#define SCB_SCB_BLK_CTRL 0x00070000 +#define SCB_BLK_CTRL_XMEM_RESET_SET 0x00000090 +#define SCB_BLK_CTRL_CACHE_RESET_SET 0x000000A0 +#define SCB_BLK_CTRL_CACHE_RESET_CLR 0x000000A4 +#define SCB_BLK_CTRL_SCB_CLK_ENABLE_SET 0x00000100 + +#define XMEM_CONTROL 0x00041000 + +#define MC_CACHE_0_BASE 0x00060000 +#define MC_CACHE_1_BASE 0x00068000 + +#define DEC_MFD_XREG_SLV_BASE 0x00180000 +#define ENC_MFD_XREG_SLV_0_BASE 0x00800000 +#define ENC_MFD_XREG_SLV_1_BASE 0x00A00000 + +#define MFD_HIF 0x0001C000 +#define MFD_HIF_MSD_REG_INTERRUPT_STATUS 0x00000018 +#define MFD_SIF 0x0001D000 +#define MFD_SIF_CTRL_STATUS 0x000000F0 +#define MFD_SIF_INTR_STATUS 0x000000F4 +#define MFD_MCX 0x00020800 +#define MFD_MCX_OFF 0x00000020 +#define MFD_PIX_IF 0x00020000 + +#define MFD_BLK_CTRL 0x00030000 +#define MFD_BLK_CTRL_MFD_SYS_RESET_SET 0x00000000 +#define MFD_BLK_CTRL_MFD_SYS_RESET_CLR 0x00000004 +#define MFD_BLK_CTRL_MFD_SYS_CLOCK_ENABLE_SET 0x00000100 +#define MFD_BLK_CTRL_MFD_SYS_CLOCK_ENABLE_CLR 0x00000104 + +#define VID_API_NUM_STREAMS 8 +#define VID_API_MAX_BUF_PER_STR 3 +#define VID_API_MAX_NUM_MVC_VIEWS 4 +#define MEDIAIP_MAX_NUM_MALONES 2 +#define MEDIAIP_MAX_NUM_MALONE_IRQ_PINS 2 +#define MEDIAIP_MAX_NUM_WINDSORS 1 +#define MEDIAIP_MAX_NUM_WINDSOR_IRQ_PINS 2 +#define MEDIAIP_MAX_NUM_CMD_IRQ_PINS 2 +#define MEDIAIP_MAX_NUM_MSG_IRQ_PINS 1 +#define MEDIAIP_MAX_NUM_TIMER_IRQ_PINS 4 +#define MEDIAIP_MAX_NUM_TIMER_IRQ_SLOTS 4 + +#define WINDSOR_PAL_IRQ_PIN_L 0x4 +#define WINDSOR_PAL_IRQ_PIN_H 0x5 + +struct vpu_rpc_system_config { + u32 cfg_cookie; + + u32 num_malones; + u32 malone_base_addr[MEDIAIP_MAX_NUM_MALONES]; + u32 hif_offset[MEDIAIP_MAX_NUM_MALONES]; + u32 malone_irq_pin[MEDIAIP_MAX_NUM_MALONES][MEDIAIP_MAX_NUM_MALONE_IRQ_PINS]; + u32 malone_irq_target[MEDIAIP_MAX_NUM_MALONES][MEDIAIP_MAX_NUM_MALONE_IRQ_PINS]; + + u32 num_windsors; + u32 windsor_base_addr[MEDIAIP_MAX_NUM_WINDSORS]; + u32 windsor_irq_pin[MEDIAIP_MAX_NUM_WINDSORS][MEDIAIP_MAX_NUM_WINDSOR_IRQ_PINS]; + u32 windsor_irq_target[MEDIAIP_MAX_NUM_WINDSORS][MEDIAIP_MAX_NUM_WINDSOR_IRQ_PINS]; + + u32 cmd_irq_pin[MEDIAIP_MAX_NUM_CMD_IRQ_PINS]; + u32 cmd_irq_target[MEDIAIP_MAX_NUM_CMD_IRQ_PINS]; + + u32 msg_irq_pin[MEDIAIP_MAX_NUM_MSG_IRQ_PINS]; + u32 msg_irq_target[MEDIAIP_MAX_NUM_MSG_IRQ_PINS]; + + u32 sys_clk_freq; + u32 num_timers; + u32 timer_base_addr; + u32 timer_irq_pin[MEDIAIP_MAX_NUM_TIMER_IRQ_PINS]; + u32 timer_irq_target[MEDIAIP_MAX_NUM_TIMER_IRQ_PINS]; + u32 timer_slots[MEDIAIP_MAX_NUM_TIMER_IRQ_SLOTS]; + + u32 gic_base_addr; + u32 uart_base_addr; + + u32 dpv_base_addr; + u32 dpv_irq_pin; + u32 dpv_irq_target; + + u32 pixif_base_addr; + + u32 pal_trace_level; + u32 pal_trace_destination; + + u32 pal_trace_level1; + u32 pal_trace_destination1; + + u32 heap_base; + u32 heap_size; + + u32 cache_base_addr[2]; +}; + +int vpu_imx8q_setup_dec(struct vpu_dev *vpu); +int vpu_imx8q_setup_enc(struct vpu_dev *vpu); +int vpu_imx8q_setup(struct vpu_dev *vpu); +int vpu_imx8q_reset(struct vpu_dev *vpu); +int vpu_imx8q_set_system_cfg_common(struct vpu_rpc_system_config *config, u32 regs, u32 core_id); +int vpu_imx8q_boot_core(struct vpu_core *core); +int vpu_imx8q_get_power_state(struct vpu_core *core); +int vpu_imx8q_on_firmware_loaded(struct vpu_core *core); +int vpu_imx8q_check_memory_region(dma_addr_t base, dma_addr_t addr, u32 size); +bool vpu_imx8q_check_codec(enum vpu_core_type type); +bool vpu_imx8q_check_fmt(enum vpu_core_type type, u32 pixelfmt); + +#endif diff --git a/drivers/media/platform/amphion/vpu_malone.c b/drivers/media/platform/amphion/vpu_malone.c new file mode 100644 index 000000000..6b37453ee --- /dev/null +++ b/drivers/media/platform/amphion/vpu_malone.c @@ -0,0 +1,1682 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_rpc.h" +#include "vpu_defs.h" +#include "vpu_helpers.h" +#include "vpu_v4l2.h" +#include "vpu_cmds.h" +#include "vpu_imx8q.h" +#include "vpu_malone.h" + +#define CMD_SIZE 25600 +#define MSG_SIZE 25600 +#define CODEC_SIZE 0x1000 +#define JPEG_SIZE 0x1000 +#define SEQ_SIZE 0x1000 +#define GOP_SIZE 0x1000 +#define PIC_SIZE 0x1000 +#define QMETER_SIZE 0x1000 +#define DBGLOG_SIZE 0x10000 +#define DEBUG_SIZE 0x80000 +#define ENG_SIZE 0x1000 +#define MALONE_SKIPPED_FRAME_ID 0x555 + +#define MALONE_ALIGN_MBI 0x800 +#define MALONE_DCP_CHUNK_BIT 16 +#define MALONE_DCP_SIZE_MAX 0x3000000 +#define MALONE_DCP_SIZE_MIN 0x100000 +#define MALONE_DCP_FIXED_MB_ALLOC 250 + +#define CONFIG_SET(val, cfg, pos, mask) \ + (*(cfg) |= (((val) << (pos)) & (mask))) +//x means source data , y means destination data +#define STREAM_CONFIG_FORMAT_SET(x, y) CONFIG_SET(x, y, 0, 0x0000000F) +#define STREAM_CONFIG_STRBUFIDX_SET(x, y) CONFIG_SET(x, y, 8, 0x00000300) +#define STREAM_CONFIG_NOSEQ_SET(x, y) CONFIG_SET(x, y, 10, 0x00000400) +#define STREAM_CONFIG_DEBLOCK_SET(x, y) CONFIG_SET(x, y, 11, 0x00000800) +#define STREAM_CONFIG_DERING_SET(x, y) CONFIG_SET(x, y, 12, 0x00001000) +#define STREAM_CONFIG_IBWAIT_SET(x, y) CONFIG_SET(x, y, 13, 0x00002000) +#define STREAM_CONFIG_FBC_SET(x, y) CONFIG_SET(x, y, 14, 0x00004000) +#define STREAM_CONFIG_PLAY_MODE_SET(x, y) CONFIG_SET(x, y, 16, 0x00030000) +#define STREAM_CONFIG_ENABLE_DCP_SET(x, y) CONFIG_SET(x, y, 20, 0x00100000) +#define STREAM_CONFIG_NUM_STR_BUF_SET(x, y) CONFIG_SET(x, y, 21, 0x00600000) +#define STREAM_CONFIG_MALONE_USAGE_SET(x, y) CONFIG_SET(x, y, 23, 0x01800000) +#define STREAM_CONFIG_MULTI_VID_SET(x, y) CONFIG_SET(x, y, 25, 0x02000000) +#define STREAM_CONFIG_OBFUSC_EN_SET(x, y) CONFIG_SET(x, y, 26, 0x04000000) +#define STREAM_CONFIG_RC4_EN_SET(x, y) CONFIG_SET(x, y, 27, 0x08000000) +#define STREAM_CONFIG_MCX_SET(x, y) CONFIG_SET(x, y, 28, 0x10000000) +#define STREAM_CONFIG_PES_SET(x, y) CONFIG_SET(x, y, 29, 0x20000000) +#define STREAM_CONFIG_NUM_DBE_SET(x, y) CONFIG_SET(x, y, 30, 0x40000000) +#define STREAM_CONFIG_FS_CTRL_MODE_SET(x, y) CONFIG_SET(x, y, 31, 0x80000000) + +enum vpu_malone_stream_input_mode { + INVALID_MODE = 0, + FRAME_LVL, + NON_FRAME_LVL +}; + +enum vpu_malone_format { + MALONE_FMT_NULL = 0x0, + MALONE_FMT_AVC = 0x1, + MALONE_FMT_MP2 = 0x2, + MALONE_FMT_VC1 = 0x3, + MALONE_FMT_AVS = 0x4, + MALONE_FMT_ASP = 0x5, + MALONE_FMT_JPG = 0x6, + MALONE_FMT_RV = 0x7, + MALONE_FMT_VP6 = 0x8, + MALONE_FMT_SPK = 0x9, + MALONE_FMT_VP8 = 0xA, + MALONE_FMT_HEVC = 0xB, + MALONE_FMT_LAST = MALONE_FMT_HEVC +}; + +enum { + VID_API_CMD_NULL = 0x00, + VID_API_CMD_PARSE_NEXT_SEQ = 0x01, + VID_API_CMD_PARSE_NEXT_I = 0x02, + VID_API_CMD_PARSE_NEXT_IP = 0x03, + VID_API_CMD_PARSE_NEXT_ANY = 0x04, + VID_API_CMD_DEC_PIC = 0x05, + VID_API_CMD_UPDATE_ES_WR_PTR = 0x06, + VID_API_CMD_UPDATE_ES_RD_PTR = 0x07, + VID_API_CMD_UPDATE_UDATA = 0x08, + VID_API_CMD_GET_FSINFO = 0x09, + VID_API_CMD_SKIP_PIC = 0x0a, + VID_API_CMD_DEC_CHUNK = 0x0b, + VID_API_CMD_START = 0x10, + VID_API_CMD_STOP = 0x11, + VID_API_CMD_ABORT = 0x12, + VID_API_CMD_RST_BUF = 0x13, + VID_API_CMD_FS_RELEASE = 0x15, + VID_API_CMD_MEM_REGION_ATTACH = 0x16, + VID_API_CMD_MEM_REGION_DETACH = 0x17, + VID_API_CMD_MVC_VIEW_SELECT = 0x18, + VID_API_CMD_FS_ALLOC = 0x19, + VID_API_CMD_DBG_GET_STATUS = 0x1C, + VID_API_CMD_DBG_START_LOG = 0x1D, + VID_API_CMD_DBG_STOP_LOG = 0x1E, + VID_API_CMD_DBG_DUMP_LOG = 0x1F, + VID_API_CMD_YUV_READY = 0x20, + VID_API_CMD_TS = 0x21, + + VID_API_CMD_FIRM_RESET = 0x40, + + VID_API_CMD_SNAPSHOT = 0xAA, + VID_API_CMD_ROLL_SNAPSHOT = 0xAB, + VID_API_CMD_LOCK_SCHEDULER = 0xAC, + VID_API_CMD_UNLOCK_SCHEDULER = 0xAD, + VID_API_CMD_CQ_FIFO_DUMP = 0xAE, + VID_API_CMD_DBG_FIFO_DUMP = 0xAF, + VID_API_CMD_SVC_ILP = 0xBB, + VID_API_CMD_FW_STATUS = 0xF0, + VID_API_CMD_INVALID = 0xFF +}; + +enum { + VID_API_EVENT_NULL = 0x00, + VID_API_EVENT_RESET_DONE = 0x01, + VID_API_EVENT_SEQ_HDR_FOUND = 0x02, + VID_API_EVENT_PIC_HDR_FOUND = 0x03, + VID_API_EVENT_PIC_DECODED = 0x04, + VID_API_EVENT_FIFO_LOW = 0x05, + VID_API_EVENT_FIFO_HIGH = 0x06, + VID_API_EVENT_FIFO_EMPTY = 0x07, + VID_API_EVENT_FIFO_FULL = 0x08, + VID_API_EVENT_BS_ERROR = 0x09, + VID_API_EVENT_UDATA_FIFO_UPTD = 0x0A, + VID_API_EVENT_RES_CHANGE = 0x0B, + VID_API_EVENT_FIFO_OVF = 0x0C, + VID_API_EVENT_CHUNK_DECODED = 0x0D, + VID_API_EVENT_REQ_FRAME_BUFF = 0x10, + VID_API_EVENT_FRAME_BUFF_RDY = 0x11, + VID_API_EVENT_REL_FRAME_BUFF = 0x12, + VID_API_EVENT_STR_BUF_RST = 0x13, + VID_API_EVENT_RET_PING = 0x14, + VID_API_EVENT_QMETER = 0x15, + VID_API_EVENT_STR_FMT_CHANGE = 0x16, + VID_API_EVENT_FIRMWARE_XCPT = 0x17, + VID_API_EVENT_START_DONE = 0x18, + VID_API_EVENT_STOPPED = 0x19, + VID_API_EVENT_ABORT_DONE = 0x1A, + VID_API_EVENT_FINISHED = 0x1B, + VID_API_EVENT_DBG_STAT_UPDATE = 0x1C, + VID_API_EVENT_DBG_LOG_STARTED = 0x1D, + VID_API_EVENT_DBG_LOG_STOPPED = 0x1E, + VID_API_EVENT_DBG_LOG_UPDATED = 0x1F, + VID_API_EVENT_DBG_MSG_DEC = 0x20, + VID_API_EVENT_DEC_SC_ERR = 0x21, + VID_API_EVENT_CQ_FIFO_DUMP = 0x22, + VID_API_EVENT_DBG_FIFO_DUMP = 0x23, + VID_API_EVENT_DEC_CHECK_RES = 0x24, + VID_API_EVENT_DEC_CFG_INFO = 0x25, + VID_API_EVENT_UNSUPPORTED_STREAM = 0x26, + VID_API_EVENT_PIC_SKIPPED = 0x27, + VID_API_EVENT_STR_SUSPENDED = 0x30, + VID_API_EVENT_SNAPSHOT_DONE = 0x40, + VID_API_EVENT_FW_STATUS = 0xF0, + VID_API_EVENT_INVALID = 0xFF +}; + +struct vpu_malone_buffer_desc { + struct vpu_rpc_buffer_desc buffer; + u32 low; + u32 high; +}; + +struct vpu_malone_str_buffer { + u32 wptr; + u32 rptr; + u32 start; + u32 end; + u32 lwm; +}; + +struct vpu_malone_picth_info { + u32 frame_pitch; +}; + +struct vpu_malone_table_desc { + u32 array_base; + u32 size; +}; + +struct vpu_malone_dbglog_desc { + u32 addr; + u32 size; + u32 level; + u32 reserved; +}; + +struct vpu_malone_frame_buffer { + u32 addr; + u32 size; +}; + +struct vpu_malone_udata { + u32 base; + u32 total_size; + u32 slot_size; +}; + +struct vpu_malone_buffer_info { + u32 stream_input_mode; + u32 stream_pic_input_count; + u32 stream_pic_parsed_count; + u32 stream_buffer_threshold; + u32 stream_pic_end_flag; +}; + +struct vpu_malone_encrypt_info { + u32 rec4key[8]; + u32 obfusc; +}; + +struct malone_iface { + u32 exec_base_addr; + u32 exec_area_size; + struct vpu_malone_buffer_desc cmd_buffer_desc; + struct vpu_malone_buffer_desc msg_buffer_desc; + u32 cmd_int_enable[VID_API_NUM_STREAMS]; + struct vpu_malone_picth_info stream_pitch_info[VID_API_NUM_STREAMS]; + u32 stream_config[VID_API_NUM_STREAMS]; + struct vpu_malone_table_desc codec_param_tab_desc; + struct vpu_malone_table_desc jpeg_param_tab_desc; + u32 stream_buffer_desc[VID_API_NUM_STREAMS][VID_API_MAX_BUF_PER_STR]; + struct vpu_malone_table_desc seq_info_tab_desc; + struct vpu_malone_table_desc pic_info_tab_desc; + struct vpu_malone_table_desc gop_info_tab_desc; + struct vpu_malone_table_desc qmeter_info_tab_desc; + u32 stream_error[VID_API_NUM_STREAMS]; + u32 fw_version; + u32 fw_offset; + u32 max_streams; + struct vpu_malone_dbglog_desc dbglog_desc; + struct vpu_rpc_buffer_desc api_cmd_buffer_desc[VID_API_NUM_STREAMS]; + struct vpu_malone_udata udata_buffer[VID_API_NUM_STREAMS]; + struct vpu_malone_buffer_desc debug_buffer_desc; + struct vpu_malone_buffer_desc eng_access_buff_desc[VID_API_NUM_STREAMS]; + u32 encrypt_info[VID_API_NUM_STREAMS]; + struct vpu_rpc_system_config system_cfg; + u32 api_version; + struct vpu_malone_buffer_info stream_buff_info[VID_API_NUM_STREAMS]; +}; + +struct malone_jpg_params { + u32 rotation_angle; + u32 horiz_scale_factor; + u32 vert_scale_factor; + u32 rotation_mode; + u32 rgb_mode; + u32 chunk_mode; /* 0 ~ 1 */ + u32 last_chunk; /* 0 ~ 1 */ + u32 chunk_rows; /* 0 ~ 255 */ + u32 num_bytes; + u32 jpg_crop_x; + u32 jpg_crop_y; + u32 jpg_crop_width; + u32 jpg_crop_height; + u32 jpg_mjpeg_mode; + u32 jpg_mjpeg_interlaced; +}; + +struct malone_codec_params { + u32 disp_imm; + u32 fourcc; + u32 codec_version; + u32 frame_rate; + u32 dbglog_enable; + u32 bsdma_lwm; + u32 bbd_coring; + u32 bbd_s_thr_row; + u32 bbd_p_thr_row; + u32 bbd_s_thr_logo_row; + u32 bbd_p_thr_logo_row; + u32 bbd_s_thr_col; + u32 bbd_p_thr_col; + u32 bbd_chr_thr_row; + u32 bbd_chr_thr_col; + u32 bbd_uv_mid_level; + u32 bbd_excl_win_mb_left; + u32 bbd_excl_win_mb_right; +}; + +struct malone_padding_scode { + u32 scode_type; + u32 pixelformat; + u32 data[2]; +}; + +struct malone_fmt_mapping { + u32 pixelformat; + enum vpu_malone_format malone_format; + u32 is_disabled; +}; + +struct malone_scode_t { + struct vpu_inst *inst; + struct vb2_buffer *vb; + u32 wptr; + u32 need_data; +}; + +struct malone_scode_handler { + u32 pixelformat; + int (*insert_scode_seq)(struct malone_scode_t *scode); + int (*insert_scode_pic)(struct malone_scode_t *scode); +}; + +struct vpu_dec_ctrl { + struct malone_codec_params *codec_param; + struct malone_jpg_params *jpg; + void *seq_mem; + void *pic_mem; + void *gop_mem; + void *qmeter_mem; + void *dbglog_mem; + struct vpu_malone_str_buffer __iomem *str_buf[VID_API_NUM_STREAMS]; + u32 buf_addr[VID_API_NUM_STREAMS]; +}; + +u32 vpu_malone_get_data_size(void) +{ + return sizeof(struct vpu_dec_ctrl); +} + +void vpu_malone_init_rpc(struct vpu_shared_addr *shared, + struct vpu_buffer *rpc, dma_addr_t boot_addr) +{ + struct malone_iface *iface; + struct vpu_dec_ctrl *hc; + unsigned long base_phy_addr; + unsigned long phy_addr; + unsigned long offset; + unsigned int i; + + if (rpc->phys < boot_addr) + return; + + iface = rpc->virt; + base_phy_addr = rpc->phys - boot_addr; + hc = shared->priv; + + shared->iface = iface; + shared->boot_addr = boot_addr; + + iface->exec_base_addr = base_phy_addr; + iface->exec_area_size = rpc->length; + + offset = sizeof(struct malone_iface); + phy_addr = base_phy_addr + offset; + + shared->cmd_desc = &iface->cmd_buffer_desc.buffer; + shared->cmd_mem_vir = rpc->virt + offset; + iface->cmd_buffer_desc.buffer.start = + iface->cmd_buffer_desc.buffer.rptr = + iface->cmd_buffer_desc.buffer.wptr = phy_addr; + iface->cmd_buffer_desc.buffer.end = iface->cmd_buffer_desc.buffer.start + CMD_SIZE; + offset += CMD_SIZE; + phy_addr = base_phy_addr + offset; + + shared->msg_desc = &iface->msg_buffer_desc.buffer; + shared->msg_mem_vir = rpc->virt + offset; + iface->msg_buffer_desc.buffer.start = + iface->msg_buffer_desc.buffer.wptr = + iface->msg_buffer_desc.buffer.rptr = phy_addr; + iface->msg_buffer_desc.buffer.end = iface->msg_buffer_desc.buffer.start + MSG_SIZE; + offset += MSG_SIZE; + phy_addr = base_phy_addr + offset; + + iface->codec_param_tab_desc.array_base = phy_addr; + hc->codec_param = rpc->virt + offset; + offset += CODEC_SIZE; + phy_addr = base_phy_addr + offset; + + iface->jpeg_param_tab_desc.array_base = phy_addr; + hc->jpg = rpc->virt + offset; + offset += JPEG_SIZE; + phy_addr = base_phy_addr + offset; + + iface->seq_info_tab_desc.array_base = phy_addr; + hc->seq_mem = rpc->virt + offset; + offset += SEQ_SIZE; + phy_addr = base_phy_addr + offset; + + iface->pic_info_tab_desc.array_base = phy_addr; + hc->pic_mem = rpc->virt + offset; + offset += PIC_SIZE; + phy_addr = base_phy_addr + offset; + + iface->gop_info_tab_desc.array_base = phy_addr; + hc->gop_mem = rpc->virt + offset; + offset += GOP_SIZE; + phy_addr = base_phy_addr + offset; + + iface->qmeter_info_tab_desc.array_base = phy_addr; + hc->qmeter_mem = rpc->virt + offset; + offset += QMETER_SIZE; + phy_addr = base_phy_addr + offset; + + iface->dbglog_desc.addr = phy_addr; + iface->dbglog_desc.size = DBGLOG_SIZE; + hc->dbglog_mem = rpc->virt + offset; + offset += DBGLOG_SIZE; + phy_addr = base_phy_addr + offset; + + for (i = 0; i < VID_API_NUM_STREAMS; i++) { + iface->eng_access_buff_desc[i].buffer.start = + iface->eng_access_buff_desc[i].buffer.wptr = + iface->eng_access_buff_desc[i].buffer.rptr = phy_addr; + iface->eng_access_buff_desc[i].buffer.end = + iface->eng_access_buff_desc[i].buffer.start + ENG_SIZE; + offset += ENG_SIZE; + phy_addr = base_phy_addr + offset; + } + + for (i = 0; i < VID_API_NUM_STREAMS; i++) { + iface->encrypt_info[i] = phy_addr; + offset += sizeof(struct vpu_malone_encrypt_info); + phy_addr = base_phy_addr + offset; + } + + rpc->bytesused = offset; +} + +void vpu_malone_set_log_buf(struct vpu_shared_addr *shared, + struct vpu_buffer *log) +{ + struct malone_iface *iface = shared->iface; + + iface->debug_buffer_desc.buffer.start = + iface->debug_buffer_desc.buffer.wptr = + iface->debug_buffer_desc.buffer.rptr = log->phys - shared->boot_addr; + iface->debug_buffer_desc.buffer.end = iface->debug_buffer_desc.buffer.start + log->length; +} + +static u32 get_str_buffer_offset(u32 instance) +{ + return DEC_MFD_XREG_SLV_BASE + MFD_MCX + MFD_MCX_OFF * instance; +} + +void vpu_malone_set_system_cfg(struct vpu_shared_addr *shared, + u32 regs_base, void __iomem *regs, u32 core_id) +{ + struct malone_iface *iface = shared->iface; + struct vpu_rpc_system_config *config = &iface->system_cfg; + struct vpu_dec_ctrl *hc = shared->priv; + int i; + + vpu_imx8q_set_system_cfg_common(config, regs_base, core_id); + for (i = 0; i < VID_API_NUM_STREAMS; i++) { + u32 offset = get_str_buffer_offset(i); + + hc->buf_addr[i] = regs_base + offset; + hc->str_buf[i] = regs + offset; + } +} + +u32 vpu_malone_get_version(struct vpu_shared_addr *shared) +{ + struct malone_iface *iface = shared->iface; + + return iface->fw_version; +} + +int vpu_malone_get_stream_buffer_size(struct vpu_shared_addr *shared) +{ + return 0xc00000; +} + +int vpu_malone_config_stream_buffer(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_buffer *buf) +{ + struct malone_iface *iface = shared->iface; + struct vpu_dec_ctrl *hc = shared->priv; + struct vpu_malone_str_buffer __iomem *str_buf = hc->str_buf[instance]; + + writel(buf->phys, &str_buf->start); + writel(buf->phys, &str_buf->rptr); + writel(buf->phys, &str_buf->wptr); + writel(buf->phys + buf->length, &str_buf->end); + writel(0x1, &str_buf->lwm); + + iface->stream_buffer_desc[instance][0] = hc->buf_addr[instance]; + + return 0; +} + +int vpu_malone_get_stream_buffer_desc(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_rpc_buffer_desc *desc) +{ + struct vpu_dec_ctrl *hc = shared->priv; + struct vpu_malone_str_buffer __iomem *str_buf = hc->str_buf[instance]; + + if (desc) { + desc->wptr = readl(&str_buf->wptr); + desc->rptr = readl(&str_buf->rptr); + desc->start = readl(&str_buf->start); + desc->end = readl(&str_buf->end); + } + + return 0; +} + +static void vpu_malone_update_wptr(struct vpu_malone_str_buffer __iomem *str_buf, u32 wptr) +{ + /*update wptr after data is written*/ + mb(); + writel(wptr, &str_buf->wptr); +} + +static void vpu_malone_update_rptr(struct vpu_malone_str_buffer __iomem *str_buf, u32 rptr) +{ + /*update rptr after data is read*/ + mb(); + writel(rptr, &str_buf->rptr); +} + +int vpu_malone_update_stream_buffer(struct vpu_shared_addr *shared, + u32 instance, u32 ptr, bool write) +{ + struct vpu_dec_ctrl *hc = shared->priv; + struct vpu_malone_str_buffer __iomem *str_buf = hc->str_buf[instance]; + + if (write) + vpu_malone_update_wptr(str_buf, ptr); + else + vpu_malone_update_rptr(str_buf, ptr); + + return 0; +} + +static struct malone_fmt_mapping fmt_mappings[] = { + {V4L2_PIX_FMT_H264, MALONE_FMT_AVC}, + {V4L2_PIX_FMT_H264_MVC, MALONE_FMT_AVC}, + {V4L2_PIX_FMT_HEVC, MALONE_FMT_HEVC}, + {V4L2_PIX_FMT_VC1_ANNEX_G, MALONE_FMT_VC1}, + {V4L2_PIX_FMT_VC1_ANNEX_L, MALONE_FMT_VC1}, + {V4L2_PIX_FMT_MPEG2, MALONE_FMT_MP2}, + {V4L2_PIX_FMT_MPEG4, MALONE_FMT_ASP}, + {V4L2_PIX_FMT_XVID, MALONE_FMT_ASP}, + {V4L2_PIX_FMT_H263, MALONE_FMT_ASP}, + {V4L2_PIX_FMT_JPEG, MALONE_FMT_JPG}, + {V4L2_PIX_FMT_VP8, MALONE_FMT_VP8}, +}; + +static enum vpu_malone_format vpu_malone_format_remap(u32 pixelformat) +{ + u32 i; + + for (i = 0; i < ARRAY_SIZE(fmt_mappings); i++) { + if (fmt_mappings[i].is_disabled) + continue; + if (pixelformat == fmt_mappings[i].pixelformat) + return fmt_mappings[i].malone_format; + } + + return MALONE_FMT_NULL; +} + +bool vpu_malone_check_fmt(enum vpu_core_type type, u32 pixelfmt) +{ + if (!vpu_imx8q_check_fmt(type, pixelfmt)) + return false; + + if (pixelfmt == V4L2_PIX_FMT_NV12M_8L128 || pixelfmt == V4L2_PIX_FMT_NV12M_10BE_8L128) + return true; + if (vpu_malone_format_remap(pixelfmt) == MALONE_FMT_NULL) + return false; + + return true; +} + +static void vpu_malone_set_stream_cfg(struct vpu_shared_addr *shared, + u32 instance, + enum vpu_malone_format malone_format) +{ + struct malone_iface *iface = shared->iface; + u32 *curr_str_cfg = &iface->stream_config[instance]; + + *curr_str_cfg = 0; + STREAM_CONFIG_FORMAT_SET(malone_format, curr_str_cfg); + STREAM_CONFIG_STRBUFIDX_SET(0, curr_str_cfg); + STREAM_CONFIG_NOSEQ_SET(0, curr_str_cfg); + STREAM_CONFIG_DEBLOCK_SET(0, curr_str_cfg); + STREAM_CONFIG_DERING_SET(0, curr_str_cfg); + STREAM_CONFIG_PLAY_MODE_SET(0x3, curr_str_cfg); + STREAM_CONFIG_FS_CTRL_MODE_SET(0x1, curr_str_cfg); + STREAM_CONFIG_ENABLE_DCP_SET(1, curr_str_cfg); + STREAM_CONFIG_NUM_STR_BUF_SET(1, curr_str_cfg); + STREAM_CONFIG_MALONE_USAGE_SET(1, curr_str_cfg); + STREAM_CONFIG_MULTI_VID_SET(0, curr_str_cfg); + STREAM_CONFIG_OBFUSC_EN_SET(0, curr_str_cfg); + STREAM_CONFIG_RC4_EN_SET(0, curr_str_cfg); + STREAM_CONFIG_MCX_SET(1, curr_str_cfg); + STREAM_CONFIG_PES_SET(0, curr_str_cfg); + STREAM_CONFIG_NUM_DBE_SET(1, curr_str_cfg); +} + +static int vpu_malone_set_params(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_decode_params *params) +{ + struct malone_iface *iface = shared->iface; + struct vpu_dec_ctrl *hc = shared->priv; + enum vpu_malone_format malone_format; + + malone_format = vpu_malone_format_remap(params->codec_format); + if (WARN_ON(malone_format == MALONE_FMT_NULL)) + return -EINVAL; + iface->udata_buffer[instance].base = params->udata.base; + iface->udata_buffer[instance].slot_size = params->udata.size; + + vpu_malone_set_stream_cfg(shared, instance, malone_format); + + if (malone_format == MALONE_FMT_JPG) { + //1:JPGD_MJPEG_MODE_A; 2:JPGD_MJPEG_MODE_B + hc->jpg[instance].jpg_mjpeg_mode = 1; + //0: JPGD_MJPEG_PROGRESSIVE + hc->jpg[instance].jpg_mjpeg_interlaced = 0; + } + + hc->codec_param[instance].disp_imm = params->display_delay_enable ? 1 : 0; + if (malone_format != MALONE_FMT_AVC) + hc->codec_param[instance].disp_imm = 0; + hc->codec_param[instance].dbglog_enable = 0; + iface->dbglog_desc.level = 0; + + if (params->b_non_frame) + iface->stream_buff_info[instance].stream_input_mode = NON_FRAME_LVL; + else + iface->stream_buff_info[instance].stream_input_mode = FRAME_LVL; + iface->stream_buff_info[instance].stream_buffer_threshold = 0; + iface->stream_buff_info[instance].stream_pic_input_count = 0; + + return 0; +} + +static bool vpu_malone_is_non_frame_mode(struct vpu_shared_addr *shared, u32 instance) +{ + struct malone_iface *iface = shared->iface; + + if (iface->stream_buff_info[instance].stream_input_mode == NON_FRAME_LVL) + return true; + + return false; +} + +static int vpu_malone_update_params(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_decode_params *params) +{ + struct malone_iface *iface = shared->iface; + + if (params->end_flag) + iface->stream_buff_info[instance].stream_pic_end_flag = params->end_flag; + params->end_flag = 0; + + return 0; +} + +int vpu_malone_set_decode_params(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_decode_params *params, + u32 update) +{ + if (!params) + return -EINVAL; + + if (!update) + return vpu_malone_set_params(shared, instance, params); + else + return vpu_malone_update_params(shared, instance, params); +} + +static struct vpu_pair malone_cmds[] = { + {VPU_CMD_ID_NOOP, VID_API_CMD_NULL}, + {VPU_CMD_ID_START, VID_API_CMD_START}, + {VPU_CMD_ID_STOP, VID_API_CMD_STOP}, + {VPU_CMD_ID_ABORT, VID_API_CMD_ABORT}, + {VPU_CMD_ID_RST_BUF, VID_API_CMD_RST_BUF}, + {VPU_CMD_ID_SNAPSHOT, VID_API_CMD_SNAPSHOT}, + {VPU_CMD_ID_FIRM_RESET, VID_API_CMD_FIRM_RESET}, + {VPU_CMD_ID_FS_ALLOC, VID_API_CMD_FS_ALLOC}, + {VPU_CMD_ID_FS_RELEASE, VID_API_CMD_FS_RELEASE}, + {VPU_CMD_ID_TIMESTAMP, VID_API_CMD_TS}, + {VPU_CMD_ID_DEBUG, VID_API_CMD_FW_STATUS}, +}; + +static struct vpu_pair malone_msgs[] = { + {VPU_MSG_ID_RESET_DONE, VID_API_EVENT_RESET_DONE}, + {VPU_MSG_ID_START_DONE, VID_API_EVENT_START_DONE}, + {VPU_MSG_ID_STOP_DONE, VID_API_EVENT_STOPPED}, + {VPU_MSG_ID_ABORT_DONE, VID_API_EVENT_ABORT_DONE}, + {VPU_MSG_ID_BUF_RST, VID_API_EVENT_STR_BUF_RST}, + {VPU_MSG_ID_PIC_EOS, VID_API_EVENT_FINISHED}, + {VPU_MSG_ID_SEQ_HDR_FOUND, VID_API_EVENT_SEQ_HDR_FOUND}, + {VPU_MSG_ID_RES_CHANGE, VID_API_EVENT_RES_CHANGE}, + {VPU_MSG_ID_PIC_HDR_FOUND, VID_API_EVENT_PIC_HDR_FOUND}, + {VPU_MSG_ID_PIC_DECODED, VID_API_EVENT_PIC_DECODED}, + {VPU_MSG_ID_DEC_DONE, VID_API_EVENT_FRAME_BUFF_RDY}, + {VPU_MSG_ID_FRAME_REQ, VID_API_EVENT_REQ_FRAME_BUFF}, + {VPU_MSG_ID_FRAME_RELEASE, VID_API_EVENT_REL_FRAME_BUFF}, + {VPU_MSG_ID_FIFO_LOW, VID_API_EVENT_FIFO_LOW}, + {VPU_MSG_ID_BS_ERROR, VID_API_EVENT_BS_ERROR}, + {VPU_MSG_ID_UNSUPPORTED, VID_API_EVENT_UNSUPPORTED_STREAM}, + {VPU_MSG_ID_FIRMWARE_XCPT, VID_API_EVENT_FIRMWARE_XCPT}, + {VPU_MSG_ID_PIC_SKIPPED, VID_API_EVENT_PIC_SKIPPED}, + {VPU_MSG_ID_DBG_MSG, VID_API_EVENT_DBG_MSG_DEC}, +}; + +static void vpu_malone_pack_fs_alloc(struct vpu_rpc_event *pkt, + struct vpu_fs_info *fs) +{ + const u32 fs_type[] = { + [MEM_RES_FRAME] = 0, + [MEM_RES_MBI] = 1, + [MEM_RES_DCP] = 2, + }; + + pkt->hdr.num = 7; + pkt->data[0] = fs->id | (fs->tag << 24); + pkt->data[1] = fs->luma_addr; + if (fs->type == MEM_RES_FRAME) { + /* + * if luma_addr equal to chroma_addr, + * means luma(plane[0]) and chromau(plane[1]) used the + * same fd -- usage of NXP codec2. Need to manually + * offset chroma addr. + */ + if (fs->luma_addr == fs->chroma_addr) + fs->chroma_addr = fs->luma_addr + fs->luma_size; + pkt->data[2] = fs->luma_addr + fs->luma_size / 2; + pkt->data[3] = fs->chroma_addr; + pkt->data[4] = fs->chroma_addr + fs->chromau_size / 2; + pkt->data[5] = fs->bytesperline; + } else { + pkt->data[2] = fs->luma_size; + pkt->data[3] = 0; + pkt->data[4] = 0; + pkt->data[5] = 0; + } + pkt->data[6] = fs_type[fs->type]; +} + +static void vpu_malone_pack_fs_release(struct vpu_rpc_event *pkt, + struct vpu_fs_info *fs) +{ + pkt->hdr.num = 1; + pkt->data[0] = fs->id | (fs->tag << 24); +} + +static void vpu_malone_pack_timestamp(struct vpu_rpc_event *pkt, + struct vpu_ts_info *info) +{ + struct timespec64 ts = ns_to_timespec64(info->timestamp); + + pkt->hdr.num = 3; + + pkt->data[0] = ts.tv_sec; + pkt->data[1] = ts.tv_nsec; + pkt->data[2] = info->size; +} + +int vpu_malone_pack_cmd(struct vpu_rpc_event *pkt, u32 index, u32 id, void *data) +{ + int ret; + + ret = vpu_find_dst_by_src(malone_cmds, ARRAY_SIZE(malone_cmds), id); + if (ret < 0) + return ret; + + pkt->hdr.id = ret; + pkt->hdr.num = 0; + pkt->hdr.index = index; + + switch (id) { + case VPU_CMD_ID_FS_ALLOC: + vpu_malone_pack_fs_alloc(pkt, data); + break; + case VPU_CMD_ID_FS_RELEASE: + vpu_malone_pack_fs_release(pkt, data); + break; + case VPU_CMD_ID_TIMESTAMP: + vpu_malone_pack_timestamp(pkt, data); + break; + } + + pkt->hdr.index = index; + return 0; +} + +int vpu_malone_convert_msg_id(u32 id) +{ + return vpu_find_src_by_dst(malone_msgs, ARRAY_SIZE(malone_msgs), id); +} + +static void vpu_malone_fill_planes(struct vpu_dec_codec_info *info) +{ + u32 interlaced = info->progressive ? 0 : 1; + + info->bytesperline[0] = 0; + info->sizeimage[0] = vpu_helper_get_plane_size(info->pixfmt, + info->decoded_width, + info->decoded_height, + 0, + info->stride, + interlaced, + &info->bytesperline[0]); + info->bytesperline[1] = 0; + info->sizeimage[1] = vpu_helper_get_plane_size(info->pixfmt, + info->decoded_width, + info->decoded_height, + 1, + info->stride, + interlaced, + &info->bytesperline[1]); +} + +static void vpu_malone_init_seq_hdr(struct vpu_dec_codec_info *info) +{ + u32 chunks = info->num_dfe_area >> MALONE_DCP_CHUNK_BIT; + + vpu_malone_fill_planes(info); + + info->mbi_size = (info->sizeimage[0] + info->sizeimage[1]) >> 2; + info->mbi_size = ALIGN(info->mbi_size, MALONE_ALIGN_MBI); + + info->dcp_size = MALONE_DCP_SIZE_MAX; + if (chunks) { + u32 mb_num; + u32 mb_w; + u32 mb_h; + + mb_w = DIV_ROUND_UP(info->decoded_width, 16); + mb_h = DIV_ROUND_UP(info->decoded_height, 16); + mb_num = mb_w * mb_h; + info->dcp_size = mb_num * MALONE_DCP_FIXED_MB_ALLOC * chunks; + info->dcp_size = clamp_t(u32, info->dcp_size, + MALONE_DCP_SIZE_MIN, MALONE_DCP_SIZE_MAX); + } +} + +static void vpu_malone_unpack_seq_hdr(struct vpu_rpc_event *pkt, + struct vpu_dec_codec_info *info) +{ + info->num_ref_frms = pkt->data[0]; + info->num_dpb_frms = pkt->data[1]; + info->num_dfe_area = pkt->data[2]; + info->progressive = pkt->data[3]; + info->width = pkt->data[5]; + info->height = pkt->data[4]; + info->decoded_width = pkt->data[12]; + info->decoded_height = pkt->data[11]; + info->frame_rate.numerator = 1000; + info->frame_rate.denominator = pkt->data[8]; + info->dsp_asp_ratio = pkt->data[9]; + info->level_idc = pkt->data[10]; + info->bit_depth_luma = pkt->data[13]; + info->bit_depth_chroma = pkt->data[14]; + info->chroma_fmt = pkt->data[15]; + info->color_primaries = vpu_color_cvrt_primaries_i2v(pkt->data[16]); + info->transfer_chars = vpu_color_cvrt_transfers_i2v(pkt->data[17]); + info->matrix_coeffs = vpu_color_cvrt_matrix_i2v(pkt->data[18]); + info->full_range = vpu_color_cvrt_full_range_i2v(pkt->data[19]); + info->vui_present = pkt->data[20]; + info->mvc_num_views = pkt->data[21]; + info->offset_x = pkt->data[23]; + info->offset_y = pkt->data[25]; + info->tag = pkt->data[27]; + if (info->bit_depth_luma > 8) + info->pixfmt = V4L2_PIX_FMT_NV12M_10BE_8L128; + else + info->pixfmt = V4L2_PIX_FMT_NV12M_8L128; + if (info->frame_rate.numerator && info->frame_rate.denominator) { + unsigned long n, d; + + rational_best_approximation(info->frame_rate.numerator, + info->frame_rate.denominator, + info->frame_rate.numerator, + info->frame_rate.denominator, + &n, &d); + info->frame_rate.numerator = n; + info->frame_rate.denominator = d; + } + vpu_malone_init_seq_hdr(info); +} + +static void vpu_malone_unpack_pic_info(struct vpu_rpc_event *pkt, + struct vpu_dec_pic_info *info) +{ + info->id = pkt->data[7]; + info->luma = pkt->data[0]; + info->start = pkt->data[10]; + info->end = pkt->data[12]; + info->pic_size = pkt->data[11]; + info->stride = pkt->data[5]; + info->consumed_count = pkt->data[13]; + if (info->id == MALONE_SKIPPED_FRAME_ID) + info->skipped = 1; + else + info->skipped = 0; +} + +static void vpu_malone_unpack_req_frame(struct vpu_rpc_event *pkt, + struct vpu_fs_info *info) +{ + info->type = pkt->data[1]; +} + +static void vpu_malone_unpack_rel_frame(struct vpu_rpc_event *pkt, + struct vpu_fs_info *info) +{ + info->id = pkt->data[0]; + info->type = pkt->data[1]; + info->not_displayed = pkt->data[2]; +} + +static void vpu_malone_unpack_buff_rdy(struct vpu_rpc_event *pkt, + struct vpu_dec_pic_info *info) +{ + struct timespec64 ts = { pkt->data[9], pkt->data[10] }; + + info->id = pkt->data[0]; + info->luma = pkt->data[1]; + info->stride = pkt->data[3]; + if (info->id == MALONE_SKIPPED_FRAME_ID) + info->skipped = 1; + else + info->skipped = 0; + + info->timestamp = timespec64_to_ns(&ts); +} + +int vpu_malone_unpack_msg_data(struct vpu_rpc_event *pkt, void *data) +{ + if (!pkt || !data) + return -EINVAL; + + switch (pkt->hdr.id) { + case VID_API_EVENT_SEQ_HDR_FOUND: + vpu_malone_unpack_seq_hdr(pkt, data); + break; + case VID_API_EVENT_PIC_DECODED: + vpu_malone_unpack_pic_info(pkt, data); + break; + case VID_API_EVENT_REQ_FRAME_BUFF: + vpu_malone_unpack_req_frame(pkt, data); + break; + case VID_API_EVENT_REL_FRAME_BUFF: + vpu_malone_unpack_rel_frame(pkt, data); + break; + case VID_API_EVENT_FRAME_BUFF_RDY: + vpu_malone_unpack_buff_rdy(pkt, data); + break; + } + + return 0; +} + +static const struct malone_padding_scode padding_scodes[] = { + {SCODE_PADDING_EOS, V4L2_PIX_FMT_H264, {0x0B010000, 0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_H264_MVC, {0x0B010000, 0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_HEVC, {0x4A010000, 0x20}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_VC1_ANNEX_G, {0x0a010000, 0x0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_VC1_ANNEX_L, {0x0a010000, 0x0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_MPEG2, {0xCC010000, 0x0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_MPEG4, {0xb1010000, 0x0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_XVID, {0xb1010000, 0x0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_H263, {0xb1010000, 0x0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_VP8, {0x34010000, 0x0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_JPEG, {0xefff0000, 0x0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_H264, {0x0B010000, 0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_H264_MVC, {0x0B010000, 0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_HEVC, {0x4A010000, 0x20}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_VC1_ANNEX_G, {0x0a010000, 0x0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_VC1_ANNEX_L, {0x0a010000, 0x0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_MPEG2, {0xb7010000, 0x0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_MPEG4, {0xb1010000, 0x0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_XVID, {0xb1010000, 0x0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_H263, {0xb1010000, 0x0}}, + {SCODE_PADDING_ABORT, V4L2_PIX_FMT_VP8, {0x34010000, 0x0}}, + {SCODE_PADDING_EOS, V4L2_PIX_FMT_JPEG, {0x0, 0x0}}, + {SCODE_PADDING_BUFFLUSH, V4L2_PIX_FMT_H264, {0x15010000, 0x0}}, + {SCODE_PADDING_BUFFLUSH, V4L2_PIX_FMT_H264_MVC, {0x15010000, 0x0}}, +}; + +static const struct malone_padding_scode padding_scode_dft = {0x0, 0x0}; + +static const struct malone_padding_scode *get_padding_scode(u32 type, u32 fmt) +{ + const struct malone_padding_scode *s; + int i; + + for (i = 0; i < ARRAY_SIZE(padding_scodes); i++) { + s = &padding_scodes[i]; + + if (s->scode_type == type && s->pixelformat == fmt) + return s; + } + + if (type != SCODE_PADDING_BUFFLUSH) + return &padding_scode_dft; + + return NULL; +} + +static int vpu_malone_add_padding_scode(struct vpu_buffer *stream_buffer, + struct vpu_malone_str_buffer __iomem *str_buf, + u32 pixelformat, u32 scode_type) +{ + u32 wptr; + int size; + int total_size = 0; + const struct malone_padding_scode *ps; + const u32 padding_size = 4096; + int ret; + + ps = get_padding_scode(scode_type, pixelformat); + if (!ps) + return -EINVAL; + + wptr = readl(&str_buf->wptr); + if (wptr < stream_buffer->phys || wptr > stream_buffer->phys + stream_buffer->length) + return -EINVAL; + if (wptr == stream_buffer->phys + stream_buffer->length) + wptr = stream_buffer->phys; + size = ALIGN(wptr, 4) - wptr; + if (size) + vpu_helper_memset_stream_buffer(stream_buffer, &wptr, 0, size); + total_size += size; + + size = sizeof(ps->data); + ret = vpu_helper_copy_to_stream_buffer(stream_buffer, &wptr, size, (void *)ps->data); + if (ret < 0) + return -EINVAL; + total_size += size; + + size = padding_size - sizeof(ps->data); + vpu_helper_memset_stream_buffer(stream_buffer, &wptr, 0, size); + total_size += size; + + vpu_malone_update_wptr(str_buf, wptr); + return total_size; +} + +int vpu_malone_add_scode(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_buffer *stream_buffer, + u32 pixelformat, + u32 scode_type) +{ + struct vpu_dec_ctrl *hc = shared->priv; + struct vpu_malone_str_buffer __iomem *str_buf = hc->str_buf[instance]; + int ret = -EINVAL; + + switch (scode_type) { + case SCODE_PADDING_EOS: + case SCODE_PADDING_ABORT: + case SCODE_PADDING_BUFFLUSH: + ret = vpu_malone_add_padding_scode(stream_buffer, str_buf, pixelformat, scode_type); + break; + default: + break; + } + + return ret; +} + +#define MALONE_PAYLOAD_HEADER_SIZE 16 +#define MALONE_CODEC_VERSION_ID 0x1 +#define MALONE_CODEC_ID_VC1_SIMPLE 0x10 +#define MALONE_CODEC_ID_VC1_MAIN 0x11 +#define MALONE_CODEC_ID_ARV8 0x28 +#define MALONE_CODEC_ID_ARV9 0x29 +#define MALONE_CODEC_ID_VP6 0x36 +#define MALONE_CODEC_ID_VP8 0x36 +#define MALONE_CODEC_ID_DIVX3 0x38 +#define MALONE_CODEC_ID_SPK 0x39 + +#define MALONE_VP8_IVF_SEQ_HEADER_LEN 32 +#define MALONE_VP8_IVF_FRAME_HEADER_LEN 8 + +#define MALONE_VC1_RCV_CODEC_V1_VERSION 0x85 +#define MALONE_VC1_RCV_CODEC_V2_VERSION 0xC5 +#define MALONE_VC1_RCV_NUM_FRAMES 0xFF +#define MALONE_VC1_RCV_SEQ_EXT_DATA_SIZE 4 +#define MALONE_VC1_RCV_SEQ_HEADER_LEN 20 +#define MALONE_VC1_RCV_PIC_HEADER_LEN 4 +#define MALONE_VC1_NAL_HEADER_LEN 4 +#define MALONE_VC1_CONTAIN_NAL(data) (((data) & 0x00FFFFFF) == 0x00010000) + +static void set_payload_hdr(u8 *dst, u32 scd_type, u32 codec_id, + u32 buffer_size, u32 width, u32 height) +{ + unsigned int payload_size; + /* payload_size = buffer_size + itself_size(16) - start_code(4) */ + payload_size = buffer_size + 12; + + dst[0] = 0x00; + dst[1] = 0x00; + dst[2] = 0x01; + dst[3] = scd_type; + + /* length */ + dst[4] = ((payload_size >> 16) & 0xff); + dst[5] = ((payload_size >> 8) & 0xff); + dst[6] = 0x4e; + dst[7] = ((payload_size >> 0) & 0xff); + + /* Codec ID and Version */ + dst[8] = codec_id; + dst[9] = MALONE_CODEC_VERSION_ID; + + /* width */ + dst[10] = ((width >> 8) & 0xff); + dst[11] = ((width >> 0) & 0xff); + dst[12] = 0x58; + + /* height */ + dst[13] = ((height >> 8) & 0xff); + dst[14] = ((height >> 0) & 0xff); + dst[15] = 0x50; +} + +static void set_vp8_ivf_seqhdr(u8 *dst, u32 width, u32 height) +{ + /* 0-3byte signature "DKIF" */ + dst[0] = 0x44; + dst[1] = 0x4b; + dst[2] = 0x49; + dst[3] = 0x46; + /* 4-5byte version: should be 0*/ + dst[4] = 0x00; + dst[5] = 0x00; + /* 6-7 length of Header */ + dst[6] = MALONE_VP8_IVF_SEQ_HEADER_LEN; + dst[7] = MALONE_VP8_IVF_SEQ_HEADER_LEN >> 8; + /* 8-11 VP8 fourcc */ + dst[8] = 0x56; + dst[9] = 0x50; + dst[10] = 0x38; + dst[11] = 0x30; + /* 12-13 width in pixels */ + dst[12] = width; + dst[13] = width >> 8; + /* 14-15 height in pixels */ + dst[14] = height; + dst[15] = height >> 8; + /* 16-19 frame rate */ + dst[16] = 0xe8; + dst[17] = 0x03; + dst[18] = 0x00; + dst[19] = 0x00; + /* 20-23 time scale */ + dst[20] = 0x01; + dst[21] = 0x00; + dst[22] = 0x00; + dst[23] = 0x00; + /* 24-27 number frames */ + dst[24] = 0xdf; + dst[25] = 0xf9; + dst[26] = 0x09; + dst[27] = 0x00; + /* 28-31 reserved */ +} + +static void set_vp8_ivf_pichdr(u8 *dst, u32 frame_size) +{ + /* + * firmware just parse 64-bit timestamp(8 bytes). + * As not transfer timestamp to firmware, use default value(ZERO). + * No need to do anything here + */ +} + +static void set_vc1_rcv_seqhdr(u8 *dst, u8 *src, u32 width, u32 height) +{ + u32 frames = MALONE_VC1_RCV_NUM_FRAMES; + u32 ext_data_size = MALONE_VC1_RCV_SEQ_EXT_DATA_SIZE; + + /* 0-2 Number of frames, used default value 0xFF */ + dst[0] = frames; + dst[1] = frames >> 8; + dst[2] = frames >> 16; + + /* 3 RCV version, used V1 */ + dst[3] = MALONE_VC1_RCV_CODEC_V1_VERSION; + + /* 4-7 extension data size */ + dst[4] = ext_data_size; + dst[5] = ext_data_size >> 8; + dst[6] = ext_data_size >> 16; + dst[7] = ext_data_size >> 24; + /* 8-11 extension data */ + dst[8] = src[0]; + dst[9] = src[1]; + dst[10] = src[2]; + dst[11] = src[3]; + + /* height */ + dst[12] = height; + dst[13] = (height >> 8) & 0xff; + dst[14] = (height >> 16) & 0xff; + dst[15] = (height >> 24) & 0xff; + /* width */ + dst[16] = width; + dst[17] = (width >> 8) & 0xff; + dst[18] = (width >> 16) & 0xff; + dst[19] = (width >> 24) & 0xff; +} + +static void set_vc1_rcv_pichdr(u8 *dst, u32 buffer_size) +{ + dst[0] = buffer_size; + dst[1] = buffer_size >> 8; + dst[2] = buffer_size >> 16; + dst[3] = buffer_size >> 24; +} + +static void create_vc1_nal_pichdr(u8 *dst) +{ + /* need insert nal header: special ID */ + dst[0] = 0x0; + dst[1] = 0x0; + dst[2] = 0x01; + dst[3] = 0x0D; +} + +static int vpu_malone_insert_scode_seq(struct malone_scode_t *scode, u32 codec_id, u32 ext_size) +{ + u8 hdr[MALONE_PAYLOAD_HEADER_SIZE]; + int ret; + + set_payload_hdr(hdr, + SCODE_SEQUENCE, + codec_id, + ext_size, + scode->inst->out_format.width, + scode->inst->out_format.height); + ret = vpu_helper_copy_to_stream_buffer(&scode->inst->stream_buffer, + &scode->wptr, + sizeof(hdr), + hdr); + if (ret < 0) + return ret; + return sizeof(hdr); +} + +static int vpu_malone_insert_scode_pic(struct malone_scode_t *scode, u32 codec_id, u32 ext_size) +{ + u8 hdr[MALONE_PAYLOAD_HEADER_SIZE]; + int ret; + + set_payload_hdr(hdr, + SCODE_PICTURE, + codec_id, + ext_size + vb2_get_plane_payload(scode->vb, 0), + scode->inst->out_format.width, + scode->inst->out_format.height); + ret = vpu_helper_copy_to_stream_buffer(&scode->inst->stream_buffer, + &scode->wptr, + sizeof(hdr), + hdr); + if (ret < 0) + return ret; + return sizeof(hdr); +} + +static int vpu_malone_insert_scode_vc1_g_seq(struct malone_scode_t *scode) +{ + if (!scode->inst->total_input_count) + return 0; + if (vpu_vb_is_codecconfig(to_vb2_v4l2_buffer(scode->vb))) + scode->need_data = 0; + return 0; +} + +static int vpu_malone_insert_scode_vc1_g_pic(struct malone_scode_t *scode) +{ + struct vb2_v4l2_buffer *vbuf; + u8 nal_hdr[MALONE_VC1_NAL_HEADER_LEN]; + u32 *data = NULL; + int ret; + + vbuf = to_vb2_v4l2_buffer(scode->vb); + data = vb2_plane_vaddr(scode->vb, 0); + + if (scode->inst->total_input_count == 0 || vpu_vb_is_codecconfig(vbuf)) + return 0; + if (MALONE_VC1_CONTAIN_NAL(*data)) + return 0; + + create_vc1_nal_pichdr(nal_hdr); + ret = vpu_helper_copy_to_stream_buffer(&scode->inst->stream_buffer, + &scode->wptr, + sizeof(nal_hdr), + nal_hdr); + if (ret < 0) + return ret; + return sizeof(nal_hdr); +} + +static int vpu_malone_insert_scode_vc1_l_seq(struct malone_scode_t *scode) +{ + int ret; + int size = 0; + u8 rcv_seqhdr[MALONE_VC1_RCV_SEQ_HEADER_LEN]; + + if (vpu_vb_is_codecconfig(to_vb2_v4l2_buffer(scode->vb))) + scode->need_data = 0; + if (scode->inst->total_input_count) + return 0; + scode->need_data = 0; + + ret = vpu_malone_insert_scode_seq(scode, MALONE_CODEC_ID_VC1_SIMPLE, sizeof(rcv_seqhdr)); + if (ret < 0) + return ret; + size = ret; + + set_vc1_rcv_seqhdr(rcv_seqhdr, + vb2_plane_vaddr(scode->vb, 0), + scode->inst->out_format.width, + scode->inst->out_format.height); + ret = vpu_helper_copy_to_stream_buffer(&scode->inst->stream_buffer, + &scode->wptr, + sizeof(rcv_seqhdr), + rcv_seqhdr); + + if (ret < 0) + return ret; + size += sizeof(rcv_seqhdr); + return size; +} + +static int vpu_malone_insert_scode_vc1_l_pic(struct malone_scode_t *scode) +{ + int ret; + int size = 0; + u8 rcv_pichdr[MALONE_VC1_RCV_PIC_HEADER_LEN]; + + ret = vpu_malone_insert_scode_pic(scode, MALONE_CODEC_ID_VC1_SIMPLE, + sizeof(rcv_pichdr)); + if (ret < 0) + return ret; + size = ret; + + set_vc1_rcv_pichdr(rcv_pichdr, vb2_get_plane_payload(scode->vb, 0)); + ret = vpu_helper_copy_to_stream_buffer(&scode->inst->stream_buffer, + &scode->wptr, + sizeof(rcv_pichdr), + rcv_pichdr); + if (ret < 0) + return ret; + size += sizeof(rcv_pichdr); + return size; +} + +static int vpu_malone_insert_scode_vp8_seq(struct malone_scode_t *scode) +{ + int ret; + int size = 0; + u8 ivf_hdr[MALONE_VP8_IVF_SEQ_HEADER_LEN]; + + ret = vpu_malone_insert_scode_seq(scode, MALONE_CODEC_ID_VP8, sizeof(ivf_hdr)); + if (ret < 0) + return ret; + size = ret; + + set_vp8_ivf_seqhdr(ivf_hdr, + scode->inst->out_format.width, + scode->inst->out_format.height); + ret = vpu_helper_copy_to_stream_buffer(&scode->inst->stream_buffer, + &scode->wptr, + sizeof(ivf_hdr), + ivf_hdr); + if (ret < 0) + return ret; + size += sizeof(ivf_hdr); + + return size; +} + +static int vpu_malone_insert_scode_vp8_pic(struct malone_scode_t *scode) +{ + int ret; + int size = 0; + u8 ivf_hdr[MALONE_VP8_IVF_FRAME_HEADER_LEN] = {0}; + + ret = vpu_malone_insert_scode_pic(scode, MALONE_CODEC_ID_VP8, sizeof(ivf_hdr)); + if (ret < 0) + return ret; + size = ret; + + set_vp8_ivf_pichdr(ivf_hdr, vb2_get_plane_payload(scode->vb, 0)); + ret = vpu_helper_copy_to_stream_buffer(&scode->inst->stream_buffer, + &scode->wptr, + sizeof(ivf_hdr), + ivf_hdr); + if (ret < 0) + return ret; + size += sizeof(ivf_hdr); + + return size; +} + +static const struct malone_scode_handler scode_handlers[] = { + { + /* fix me, need to swap return operation after gstreamer swap */ + .pixelformat = V4L2_PIX_FMT_VC1_ANNEX_L, + .insert_scode_seq = vpu_malone_insert_scode_vc1_l_seq, + .insert_scode_pic = vpu_malone_insert_scode_vc1_l_pic, + }, + { + .pixelformat = V4L2_PIX_FMT_VC1_ANNEX_G, + .insert_scode_seq = vpu_malone_insert_scode_vc1_g_seq, + .insert_scode_pic = vpu_malone_insert_scode_vc1_g_pic, + }, + { + .pixelformat = V4L2_PIX_FMT_VP8, + .insert_scode_seq = vpu_malone_insert_scode_vp8_seq, + .insert_scode_pic = vpu_malone_insert_scode_vp8_pic, + }, +}; + +static const struct malone_scode_handler *get_scode_handler(u32 pixelformat) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(scode_handlers); i++) { + if (scode_handlers[i].pixelformat == pixelformat) + return &scode_handlers[i]; + } + + return NULL; +} + +static int vpu_malone_insert_scode(struct malone_scode_t *scode, u32 type) +{ + const struct malone_scode_handler *handler; + int ret = 0; + + if (!scode || !scode->inst || !scode->vb) + return 0; + + scode->need_data = 1; + handler = get_scode_handler(scode->inst->out_format.pixfmt); + if (!handler) + return 0; + + switch (type) { + case SCODE_SEQUENCE: + if (handler->insert_scode_seq) + ret = handler->insert_scode_seq(scode); + break; + case SCODE_PICTURE: + if (handler->insert_scode_pic) + ret = handler->insert_scode_pic(scode); + break; + default: + break; + } + + return ret; +} + +static int vpu_malone_input_frame_data(struct vpu_malone_str_buffer __iomem *str_buf, + struct vpu_inst *inst, struct vb2_buffer *vb, + u32 disp_imm) +{ + struct malone_scode_t scode; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + u32 wptr = readl(&str_buf->wptr); + int size = 0; + int ret = 0; + + /*add scode: SCODE_SEQUENCE, SCODE_PICTURE, SCODE_SLICE*/ + scode.inst = inst; + scode.vb = vb; + scode.wptr = wptr; + scode.need_data = 1; + if (vbuf->sequence == 0 || vpu_vb_is_codecconfig(vbuf)) + ret = vpu_malone_insert_scode(&scode, SCODE_SEQUENCE); + + if (ret < 0) + return -ENOMEM; + size += ret; + wptr = scode.wptr; + if (!scode.need_data) { + vpu_malone_update_wptr(str_buf, wptr); + return size; + } + + ret = vpu_malone_insert_scode(&scode, SCODE_PICTURE); + if (ret < 0) + return -ENOMEM; + size += ret; + wptr = scode.wptr; + + ret = vpu_helper_copy_to_stream_buffer(&inst->stream_buffer, + &wptr, + vb2_get_plane_payload(vb, 0), + vb2_plane_vaddr(vb, 0)); + if (ret < 0) + return -ENOMEM; + size += vb2_get_plane_payload(vb, 0); + + vpu_malone_update_wptr(str_buf, wptr); + + if (disp_imm && !vpu_vb_is_codecconfig(vbuf)) { + ret = vpu_malone_add_scode(inst->core->iface, + inst->id, + &inst->stream_buffer, + inst->out_format.pixfmt, + SCODE_PADDING_BUFFLUSH); + if (ret < 0) + return ret; + size += ret; + } + + return size; +} + +static int vpu_malone_input_stream_data(struct vpu_malone_str_buffer __iomem *str_buf, + struct vpu_inst *inst, struct vb2_buffer *vb) +{ + u32 wptr = readl(&str_buf->wptr); + int ret = 0; + + ret = vpu_helper_copy_to_stream_buffer(&inst->stream_buffer, + &wptr, + vb2_get_plane_payload(vb, 0), + vb2_plane_vaddr(vb, 0)); + if (ret < 0) + return -ENOMEM; + + vpu_malone_update_wptr(str_buf, wptr); + + return ret; +} + +static int vpu_malone_input_ts(struct vpu_inst *inst, s64 timestamp, u32 size) +{ + struct vpu_ts_info info; + + memset(&info, 0, sizeof(info)); + info.timestamp = timestamp; + info.size = size; + + return vpu_session_fill_timestamp(inst, &info); +} + +int vpu_malone_input_frame(struct vpu_shared_addr *shared, + struct vpu_inst *inst, struct vb2_buffer *vb) +{ + struct vpu_dec_ctrl *hc = shared->priv; + struct vb2_v4l2_buffer *vbuf; + struct vpu_malone_str_buffer __iomem *str_buf = hc->str_buf[inst->id]; + u32 disp_imm = hc->codec_param[inst->id].disp_imm; + u32 size; + int ret; + + if (vpu_malone_is_non_frame_mode(shared, inst->id)) + ret = vpu_malone_input_stream_data(str_buf, inst, vb); + else + ret = vpu_malone_input_frame_data(str_buf, inst, vb, disp_imm); + if (ret < 0) + return ret; + size = ret; + + /* + * if buffer only contain codec data, and the timestamp is invalid, + * don't put the invalid timestamp to resync + * merge the data to next frame + */ + vbuf = to_vb2_v4l2_buffer(vb); + if (vpu_vb_is_codecconfig(vbuf)) { + inst->extra_size += size; + return 0; + } + if (inst->extra_size) { + size += inst->extra_size; + inst->extra_size = 0; + } + + ret = vpu_malone_input_ts(inst, vb->timestamp, size); + if (ret) + return ret; + + return 0; +} + +static bool vpu_malone_check_ready(struct vpu_shared_addr *shared, u32 instance) +{ + struct malone_iface *iface = shared->iface; + struct vpu_rpc_buffer_desc *desc = &iface->api_cmd_buffer_desc[instance]; + u32 size = desc->end - desc->start; + u32 rptr = desc->rptr; + u32 wptr = desc->wptr; + u32 used; + + if (!size) + return true; + + used = (wptr + size - rptr) % size; + if (used < (size / 2)) + return true; + + return false; +} + +bool vpu_malone_is_ready(struct vpu_shared_addr *shared, u32 instance) +{ + u32 cnt = 0; + + while (!vpu_malone_check_ready(shared, instance)) { + if (cnt > 30) + return false; + mdelay(1); + cnt++; + } + return true; +} + +int vpu_malone_pre_cmd(struct vpu_shared_addr *shared, u32 instance) +{ + if (!vpu_malone_is_ready(shared, instance)) + return -EINVAL; + + return 0; +} + +int vpu_malone_post_cmd(struct vpu_shared_addr *shared, u32 instance) +{ + struct malone_iface *iface = shared->iface; + struct vpu_rpc_buffer_desc *desc = &iface->api_cmd_buffer_desc[instance]; + + desc->wptr++; + if (desc->wptr == desc->end) + desc->wptr = desc->start; + + return 0; +} + +int vpu_malone_init_instance(struct vpu_shared_addr *shared, u32 instance) +{ + struct malone_iface *iface = shared->iface; + struct vpu_rpc_buffer_desc *desc = &iface->api_cmd_buffer_desc[instance]; + + desc->wptr = desc->rptr; + if (desc->wptr == desc->end) + desc->wptr = desc->start; + + return 0; +} + +u32 vpu_malone_get_max_instance_count(struct vpu_shared_addr *shared) +{ + struct malone_iface *iface = shared->iface; + + return iface->max_streams; +} diff --git a/drivers/media/platform/amphion/vpu_malone.h b/drivers/media/platform/amphion/vpu_malone.h new file mode 100644 index 000000000..02a9d9530 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_malone.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_MALONE_H +#define _AMPHION_VPU_MALONE_H + +u32 vpu_malone_get_data_size(void); +void vpu_malone_init_rpc(struct vpu_shared_addr *shared, + struct vpu_buffer *rpc, dma_addr_t boot_addr); +void vpu_malone_set_log_buf(struct vpu_shared_addr *shared, + struct vpu_buffer *log); +void vpu_malone_set_system_cfg(struct vpu_shared_addr *shared, + u32 regs_base, void __iomem *regs, u32 core_id); +u32 vpu_malone_get_version(struct vpu_shared_addr *shared); +int vpu_malone_get_stream_buffer_size(struct vpu_shared_addr *shared); +int vpu_malone_config_stream_buffer(struct vpu_shared_addr *shared, + u32 instance, struct vpu_buffer *buf); +int vpu_malone_get_stream_buffer_desc(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_rpc_buffer_desc *desc); +int vpu_malone_update_stream_buffer(struct vpu_shared_addr *shared, + u32 instance, u32 ptr, bool write); +int vpu_malone_set_decode_params(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_decode_params *params, u32 update); +int vpu_malone_pack_cmd(struct vpu_rpc_event *pkt, u32 index, u32 id, void *data); +int vpu_malone_convert_msg_id(u32 msg_id); +int vpu_malone_unpack_msg_data(struct vpu_rpc_event *pkt, void *data); +int vpu_malone_add_scode(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_buffer *stream_buffer, + u32 pixelformat, + u32 scode_type); +int vpu_malone_input_frame(struct vpu_shared_addr *shared, + struct vpu_inst *inst, struct vb2_buffer *vb); +bool vpu_malone_is_ready(struct vpu_shared_addr *shared, u32 instance); +int vpu_malone_pre_cmd(struct vpu_shared_addr *shared, u32 instance); +int vpu_malone_post_cmd(struct vpu_shared_addr *shared, u32 instance); +int vpu_malone_init_instance(struct vpu_shared_addr *shared, u32 instance); +u32 vpu_malone_get_max_instance_count(struct vpu_shared_addr *shared); +bool vpu_malone_check_fmt(enum vpu_core_type type, u32 pixelfmt); + +#endif diff --git a/drivers/media/platform/amphion/vpu_mbox.c b/drivers/media/platform/amphion/vpu_mbox.c new file mode 100644 index 000000000..b6d5b4844 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_mbox.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_mbox.h" +#include "vpu_msgs.h" + +static void vpu_mbox_rx_callback(struct mbox_client *cl, void *msg) +{ + struct vpu_mbox *rx = container_of(cl, struct vpu_mbox, cl); + struct vpu_core *core = container_of(rx, struct vpu_core, rx); + + vpu_isr(core, *(u32 *)msg); +} + +static int vpu_mbox_request_channel(struct device *dev, struct vpu_mbox *mbox) +{ + struct mbox_chan *ch; + struct mbox_client *cl; + + if (!dev || !mbox) + return -EINVAL; + if (mbox->ch) + return 0; + + cl = &mbox->cl; + cl->dev = dev; + if (mbox->block) { + cl->tx_block = true; + cl->tx_tout = 1000; + } else { + cl->tx_block = false; + } + cl->knows_txdone = false; + cl->rx_callback = vpu_mbox_rx_callback; + + ch = mbox_request_channel_byname(cl, mbox->name); + if (IS_ERR(ch)) + return dev_err_probe(dev, PTR_ERR(ch), + "Failed to request mbox chan %s\n", + mbox->name); + + mbox->ch = ch; + return 0; +} + +int vpu_mbox_init(struct vpu_core *core) +{ + scnprintf(core->tx_type.name, sizeof(core->tx_type.name) - 1, "tx0"); + core->tx_type.block = true; + + scnprintf(core->tx_data.name, sizeof(core->tx_data.name) - 1, "tx1"); + core->tx_data.block = false; + + scnprintf(core->rx.name, sizeof(core->rx.name) - 1, "rx"); + core->rx.block = true; + + return 0; +} + +int vpu_mbox_request(struct vpu_core *core) +{ + int ret; + + ret = vpu_mbox_request_channel(core->dev, &core->tx_type); + if (ret) + goto error; + ret = vpu_mbox_request_channel(core->dev, &core->tx_data); + if (ret) + goto error; + ret = vpu_mbox_request_channel(core->dev, &core->rx); + if (ret) + goto error; + + dev_dbg(core->dev, "%s request mbox\n", vpu_core_type_desc(core->type)); + return 0; +error: + vpu_mbox_free(core); + return ret; +} + +void vpu_mbox_free(struct vpu_core *core) +{ + mbox_free_channel(core->tx_type.ch); + mbox_free_channel(core->tx_data.ch); + mbox_free_channel(core->rx.ch); + core->tx_type.ch = NULL; + core->tx_data.ch = NULL; + core->rx.ch = NULL; + dev_dbg(core->dev, "%s free mbox\n", vpu_core_type_desc(core->type)); +} + +void vpu_mbox_send_type(struct vpu_core *core, u32 type) +{ + mbox_send_message(core->tx_type.ch, &type); +} + +void vpu_mbox_send_msg(struct vpu_core *core, u32 type, u32 data) +{ + mbox_send_message(core->tx_data.ch, &data); + mbox_send_message(core->tx_type.ch, &type); +} + +void vpu_mbox_enable_rx(struct vpu_dev *dev) +{ +} diff --git a/drivers/media/platform/amphion/vpu_mbox.h b/drivers/media/platform/amphion/vpu_mbox.h new file mode 100644 index 000000000..79cfd874e --- /dev/null +++ b/drivers/media/platform/amphion/vpu_mbox.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_MBOX_H +#define _AMPHION_VPU_MBOX_H + +int vpu_mbox_init(struct vpu_core *core); +int vpu_mbox_request(struct vpu_core *core); +void vpu_mbox_free(struct vpu_core *core); +void vpu_mbox_send_msg(struct vpu_core *core, u32 type, u32 data); +void vpu_mbox_send_type(struct vpu_core *core, u32 type); +void vpu_mbox_enable_rx(struct vpu_dev *dev); + +#endif diff --git a/drivers/media/platform/amphion/vpu_msgs.c b/drivers/media/platform/amphion/vpu_msgs.c new file mode 100644 index 000000000..b74a407a1 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_msgs.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_core.h" +#include "vpu_rpc.h" +#include "vpu_mbox.h" +#include "vpu_defs.h" +#include "vpu_cmds.h" +#include "vpu_msgs.h" +#include "vpu_v4l2.h" + +#define VPU_PKT_HEADER_LENGTH 3 + +struct vpu_msg_handler { + u32 id; + void (*done)(struct vpu_inst *inst, struct vpu_rpc_event *pkt); + u32 is_str; +}; + +static void vpu_session_handle_start_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + vpu_trace(inst->dev, "[%d]\n", inst->id); +} + +static void vpu_session_handle_mem_request(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + struct vpu_pkt_mem_req_data req_data = { 0 }; + + vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&req_data); + vpu_trace(inst->dev, "[%d] %d:%d %d:%d %d:%d\n", + inst->id, + req_data.enc_frame_size, + req_data.enc_frame_num, + req_data.ref_frame_size, + req_data.ref_frame_num, + req_data.act_buf_size, + req_data.act_buf_num); + vpu_inst_lock(inst); + call_void_vop(inst, mem_request, + req_data.enc_frame_size, + req_data.enc_frame_num, + req_data.ref_frame_size, + req_data.ref_frame_num, + req_data.act_buf_size, + req_data.act_buf_num); + vpu_inst_unlock(inst); +} + +static void vpu_session_handle_stop_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + vpu_trace(inst->dev, "[%d]\n", inst->id); + + call_void_vop(inst, stop_done); +} + +static void vpu_session_handle_seq_hdr(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + struct vpu_dec_codec_info info; + const struct vpu_core_resources *res; + + memset(&info, 0, sizeof(info)); + res = vpu_get_resource(inst); + info.stride = res ? res->stride : 1; + vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info); + call_void_vop(inst, event_notify, VPU_MSG_ID_SEQ_HDR_FOUND, &info); +} + +static void vpu_session_handle_resolution_change(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + call_void_vop(inst, event_notify, VPU_MSG_ID_RES_CHANGE, NULL); +} + +static void vpu_session_handle_enc_frame_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + struct vpu_enc_pic_info info = { 0 }; + + vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info); + dev_dbg(inst->dev, "[%d] frame id = %d, wptr = 0x%x, size = %d\n", + inst->id, info.frame_id, info.wptr, info.frame_size); + call_void_vop(inst, get_one_frame, &info); +} + +static void vpu_session_handle_frame_request(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + struct vpu_fs_info fs = { 0 }; + + vpu_iface_unpack_msg_data(inst->core, pkt, &fs); + call_void_vop(inst, event_notify, VPU_MSG_ID_FRAME_REQ, &fs); +} + +static void vpu_session_handle_frame_release(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + if (inst->core->type == VPU_CORE_TYPE_ENC) { + struct vpu_frame_info info; + + memset(&info, 0, sizeof(info)); + vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info.sequence); + dev_dbg(inst->dev, "[%d] %d\n", inst->id, info.sequence); + info.type = inst->out_format.type; + call_void_vop(inst, buf_done, &info); + } else if (inst->core->type == VPU_CORE_TYPE_DEC) { + struct vpu_fs_info fs = { 0 }; + + vpu_iface_unpack_msg_data(inst->core, pkt, &fs); + call_void_vop(inst, event_notify, VPU_MSG_ID_FRAME_RELEASE, &fs); + } +} + +static void vpu_session_handle_input_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + dev_dbg(inst->dev, "[%d]\n", inst->id); + call_void_vop(inst, input_done); +} + +static void vpu_session_handle_pic_decoded(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + struct vpu_dec_pic_info info = { 0 }; + + vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info); + call_void_vop(inst, get_one_frame, &info); +} + +static void vpu_session_handle_pic_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + struct vpu_dec_pic_info info = { 0 }; + struct vpu_frame_info frame; + + memset(&frame, 0, sizeof(frame)); + vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info); + if (inst->core->type == VPU_CORE_TYPE_DEC) + frame.type = inst->cap_format.type; + frame.id = info.id; + frame.luma = info.luma; + frame.skipped = info.skipped; + frame.timestamp = info.timestamp; + + call_void_vop(inst, buf_done, &frame); +} + +static void vpu_session_handle_eos(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + call_void_vop(inst, event_notify, VPU_MSG_ID_PIC_EOS, NULL); +} + +static void vpu_session_handle_error(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + char *str = (char *)pkt->data; + + if (*str) + dev_err(inst->dev, "instance %d firmware error : %s\n", inst->id, str); + else + dev_err(inst->dev, "instance %d is unsupported stream\n", inst->id); + call_void_vop(inst, event_notify, VPU_MSG_ID_UNSUPPORTED, NULL); + vpu_v4l2_set_error(inst); +} + +static void vpu_session_handle_firmware_xcpt(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + char *str = (char *)pkt->data; + + dev_err(inst->dev, "%s firmware xcpt: %s\n", + vpu_core_type_desc(inst->core->type), str); + call_void_vop(inst, event_notify, VPU_MSG_ID_FIRMWARE_XCPT, NULL); + set_bit(inst->id, &inst->core->hang_mask); + vpu_v4l2_set_error(inst); +} + +static void vpu_session_handle_pic_skipped(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + vpu_inst_lock(inst); + vpu_skip_frame(inst, 1); + vpu_inst_unlock(inst); +} + +static void vpu_session_handle_dbg_msg(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + char *str = (char *)pkt->data; + + if (*str) + dev_info(inst->dev, "instance %d firmware dbg msg : %s\n", inst->id, str); +} + +static void vpu_terminate_string_msg(struct vpu_rpc_event *pkt) +{ + if (pkt->hdr.num == ARRAY_SIZE(pkt->data)) + pkt->hdr.num--; + pkt->data[pkt->hdr.num] = 0; +} + +static struct vpu_msg_handler handlers[] = { + {VPU_MSG_ID_START_DONE, vpu_session_handle_start_done}, + {VPU_MSG_ID_STOP_DONE, vpu_session_handle_stop_done}, + {VPU_MSG_ID_MEM_REQUEST, vpu_session_handle_mem_request}, + {VPU_MSG_ID_SEQ_HDR_FOUND, vpu_session_handle_seq_hdr}, + {VPU_MSG_ID_RES_CHANGE, vpu_session_handle_resolution_change}, + {VPU_MSG_ID_FRAME_INPUT_DONE, vpu_session_handle_input_done}, + {VPU_MSG_ID_FRAME_REQ, vpu_session_handle_frame_request}, + {VPU_MSG_ID_FRAME_RELEASE, vpu_session_handle_frame_release}, + {VPU_MSG_ID_ENC_DONE, vpu_session_handle_enc_frame_done}, + {VPU_MSG_ID_PIC_DECODED, vpu_session_handle_pic_decoded}, + {VPU_MSG_ID_DEC_DONE, vpu_session_handle_pic_done}, + {VPU_MSG_ID_PIC_EOS, vpu_session_handle_eos}, + {VPU_MSG_ID_UNSUPPORTED, vpu_session_handle_error, true}, + {VPU_MSG_ID_FIRMWARE_XCPT, vpu_session_handle_firmware_xcpt, true}, + {VPU_MSG_ID_PIC_SKIPPED, vpu_session_handle_pic_skipped}, + {VPU_MSG_ID_DBG_MSG, vpu_session_handle_dbg_msg, true}, +}; + +static int vpu_session_handle_msg(struct vpu_inst *inst, struct vpu_rpc_event *msg) +{ + int ret; + u32 msg_id; + struct vpu_msg_handler *handler = NULL; + unsigned int i; + + ret = vpu_iface_convert_msg_id(inst->core, msg->hdr.id); + if (ret < 0) + return -EINVAL; + + msg_id = ret; + dev_dbg(inst->dev, "[%d] receive event(%s)\n", inst->id, vpu_id_name(msg_id)); + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + if (handlers[i].id == msg_id) { + handler = &handlers[i]; + break; + } + } + + if (handler) { + if (handler->is_str) + vpu_terminate_string_msg(msg); + if (handler->done) + handler->done(inst, msg); + } + + vpu_response_cmd(inst, msg_id, 1); + + return 0; +} + +static bool vpu_inst_receive_msg(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + unsigned long bytes = sizeof(struct vpu_rpc_event_header); + u32 ret; + + memset(pkt, 0, sizeof(*pkt)); + if (kfifo_len(&inst->msg_fifo) < bytes) + return false; + + ret = kfifo_out(&inst->msg_fifo, pkt, bytes); + if (ret != bytes) + return false; + + if (pkt->hdr.num > 0) { + bytes = pkt->hdr.num * sizeof(u32); + ret = kfifo_out(&inst->msg_fifo, pkt->data, bytes); + if (ret != bytes) + return false; + } + + return true; +} + +void vpu_inst_run_work(struct work_struct *work) +{ + struct vpu_inst *inst = container_of(work, struct vpu_inst, msg_work); + struct vpu_rpc_event pkt; + + while (vpu_inst_receive_msg(inst, &pkt)) + vpu_session_handle_msg(inst, &pkt); +} + +static void vpu_inst_handle_msg(struct vpu_inst *inst, struct vpu_rpc_event *pkt) +{ + unsigned long bytes; + u32 id = pkt->hdr.id; + int ret; + + if (!inst->workqueue) + return; + + bytes = sizeof(pkt->hdr) + pkt->hdr.num * sizeof(u32); + ret = kfifo_in(&inst->msg_fifo, pkt, bytes); + if (ret != bytes) + dev_err(inst->dev, "[%d:%d]overflow: %d\n", inst->core->id, inst->id, id); + queue_work(inst->workqueue, &inst->msg_work); +} + +static int vpu_handle_msg(struct vpu_core *core) +{ + struct vpu_rpc_event pkt; + struct vpu_inst *inst; + int ret; + + memset(&pkt, 0, sizeof(pkt)); + while (!vpu_iface_receive_msg(core, &pkt)) { + dev_dbg(core->dev, "event index = %d, id = %d, num = %d\n", + pkt.hdr.index, pkt.hdr.id, pkt.hdr.num); + + ret = vpu_iface_convert_msg_id(core, pkt.hdr.id); + if (ret < 0) + continue; + + inst = vpu_core_find_instance(core, pkt.hdr.index); + if (inst) { + vpu_response_cmd(inst, ret, 0); + mutex_lock(&core->cmd_lock); + vpu_inst_record_flow(inst, ret); + mutex_unlock(&core->cmd_lock); + + vpu_inst_handle_msg(inst, &pkt); + vpu_inst_put(inst); + } + memset(&pkt, 0, sizeof(pkt)); + } + + return 0; +} + +static int vpu_isr_thread(struct vpu_core *core, u32 irq_code) +{ + dev_dbg(core->dev, "irq code = 0x%x\n", irq_code); + switch (irq_code) { + case VPU_IRQ_CODE_SYNC: + vpu_mbox_send_msg(core, PRC_BUF_OFFSET, core->rpc.phys - core->fw.phys); + vpu_mbox_send_msg(core, BOOT_ADDRESS, core->fw.phys); + vpu_mbox_send_msg(core, INIT_DONE, 2); + break; + case VPU_IRQ_CODE_BOOT_DONE: + break; + case VPU_IRQ_CODE_SNAPSHOT_DONE: + break; + default: + vpu_handle_msg(core); + break; + } + + return 0; +} + +static void vpu_core_run_msg_work(struct vpu_core *core) +{ + const unsigned int SIZE = sizeof(u32); + + while (kfifo_len(&core->msg_fifo) >= SIZE) { + u32 data = 0; + + if (kfifo_out(&core->msg_fifo, &data, SIZE) == SIZE) + vpu_isr_thread(core, data); + } +} + +void vpu_msg_run_work(struct work_struct *work) +{ + struct vpu_core *core = container_of(work, struct vpu_core, msg_work); + unsigned long delay = msecs_to_jiffies(10); + + vpu_core_run_msg_work(core); + queue_delayed_work(core->workqueue, &core->msg_delayed_work, delay); +} + +void vpu_msg_delayed_work(struct work_struct *work) +{ + struct vpu_core *core; + struct delayed_work *dwork; + unsigned long bytes = sizeof(u32); + u32 i; + + if (!work) + return; + + dwork = to_delayed_work(work); + core = container_of(dwork, struct vpu_core, msg_delayed_work); + if (kfifo_len(&core->msg_fifo) >= bytes) + vpu_core_run_msg_work(core); + + bytes = sizeof(struct vpu_rpc_event_header); + for (i = 0; i < core->supported_instance_count; i++) { + struct vpu_inst *inst = vpu_core_find_instance(core, i); + + if (!inst) + continue; + + if (inst->workqueue && kfifo_len(&inst->msg_fifo) >= bytes) + queue_work(inst->workqueue, &inst->msg_work); + + vpu_inst_put(inst); + } +} + +int vpu_isr(struct vpu_core *core, u32 irq) +{ + switch (irq) { + case VPU_IRQ_CODE_SYNC: + break; + case VPU_IRQ_CODE_BOOT_DONE: + complete(&core->cmp); + break; + case VPU_IRQ_CODE_SNAPSHOT_DONE: + complete(&core->cmp); + break; + default: + break; + } + + if (kfifo_in(&core->msg_fifo, &irq, sizeof(irq)) != sizeof(irq)) + dev_err(core->dev, "[%d]overflow: %d\n", core->id, irq); + queue_work(core->workqueue, &core->msg_work); + + return 0; +} diff --git a/drivers/media/platform/amphion/vpu_msgs.h b/drivers/media/platform/amphion/vpu_msgs.h new file mode 100644 index 000000000..c466b4f62 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_msgs.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_MSGS_H +#define _AMPHION_VPU_MSGS_H + +int vpu_isr(struct vpu_core *core, u32 irq); +void vpu_inst_run_work(struct work_struct *work); +void vpu_msg_run_work(struct work_struct *work); +void vpu_msg_delayed_work(struct work_struct *work); + +#endif diff --git a/drivers/media/platform/amphion/vpu_rpc.c b/drivers/media/platform/amphion/vpu_rpc.c new file mode 100644 index 000000000..676f7da04 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_rpc.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_rpc.h" +#include "vpu_imx8q.h" +#include "vpu_windsor.h" +#include "vpu_malone.h" + +int vpu_iface_check_memory_region(struct vpu_core *core, dma_addr_t addr, u32 size) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->check_memory_region) + return VPU_CORE_MEMORY_INVALID; + + return ops->check_memory_region(core->fw.phys, addr, size); +} + +static u32 vpu_rpc_check_buffer_space(struct vpu_rpc_buffer_desc *desc, bool write) +{ + u32 ptr1; + u32 ptr2; + u32 size; + + size = desc->end - desc->start; + if (write) { + ptr1 = desc->wptr; + ptr2 = desc->rptr; + } else { + ptr1 = desc->rptr; + ptr2 = desc->wptr; + } + + if (ptr1 == ptr2) { + if (!write) + return 0; + else + return size; + } + + return (ptr2 + size - ptr1) % size; +} + +static int vpu_rpc_send_cmd_buf(struct vpu_shared_addr *shared, struct vpu_rpc_event *cmd) +{ + struct vpu_rpc_buffer_desc *desc; + u32 space = 0; + u32 *data; + u32 wptr; + u32 i; + + if (cmd->hdr.num > 0xff || cmd->hdr.num >= ARRAY_SIZE(cmd->data)) + return -EINVAL; + desc = shared->cmd_desc; + space = vpu_rpc_check_buffer_space(desc, true); + if (space < (((cmd->hdr.num + 1) << 2) + 16)) + return -EINVAL; + wptr = desc->wptr; + data = (u32 *)(shared->cmd_mem_vir + desc->wptr - desc->start); + *data = 0; + *data |= ((cmd->hdr.index & 0xff) << 24); + *data |= ((cmd->hdr.num & 0xff) << 16); + *data |= (cmd->hdr.id & 0x3fff); + wptr += 4; + data++; + if (wptr >= desc->end) { + wptr = desc->start; + data = shared->cmd_mem_vir; + } + + for (i = 0; i < cmd->hdr.num; i++) { + *data = cmd->data[i]; + wptr += 4; + data++; + if (wptr >= desc->end) { + wptr = desc->start; + data = shared->cmd_mem_vir; + } + } + + /*update wptr after data is written*/ + mb(); + desc->wptr = wptr; + + return 0; +} + +static bool vpu_rpc_check_msg(struct vpu_shared_addr *shared) +{ + struct vpu_rpc_buffer_desc *desc; + u32 space = 0; + u32 msgword; + u32 msgnum; + + desc = shared->msg_desc; + space = vpu_rpc_check_buffer_space(desc, 0); + space = (space >> 2); + + if (space) { + msgword = *(u32 *)(shared->msg_mem_vir + desc->rptr - desc->start); + msgnum = (msgword & 0xff0000) >> 16; + if (msgnum <= space) + return true; + } + + return false; +} + +static int vpu_rpc_receive_msg_buf(struct vpu_shared_addr *shared, struct vpu_rpc_event *msg) +{ + struct vpu_rpc_buffer_desc *desc; + u32 *data; + u32 msgword; + u32 rptr; + u32 i; + + if (!vpu_rpc_check_msg(shared)) + return -EINVAL; + + desc = shared->msg_desc; + data = (u32 *)(shared->msg_mem_vir + desc->rptr - desc->start); + rptr = desc->rptr; + msgword = *data; + data++; + rptr += 4; + if (rptr >= desc->end) { + rptr = desc->start; + data = shared->msg_mem_vir; + } + + msg->hdr.index = (msgword >> 24) & 0xff; + msg->hdr.num = (msgword >> 16) & 0xff; + msg->hdr.id = msgword & 0x3fff; + + if (msg->hdr.num > ARRAY_SIZE(msg->data)) + return -EINVAL; + + for (i = 0; i < msg->hdr.num; i++) { + msg->data[i] = *data; + data++; + rptr += 4; + if (rptr >= desc->end) { + rptr = desc->start; + data = shared->msg_mem_vir; + } + } + + /*update rptr after data is read*/ + mb(); + desc->rptr = rptr; + + return 0; +} + +static struct vpu_iface_ops imx8q_rpc_ops[] = { + [VPU_CORE_TYPE_ENC] = { + .check_codec = vpu_imx8q_check_codec, + .check_fmt = vpu_imx8q_check_fmt, + .boot_core = vpu_imx8q_boot_core, + .get_power_state = vpu_imx8q_get_power_state, + .on_firmware_loaded = vpu_imx8q_on_firmware_loaded, + .get_data_size = vpu_windsor_get_data_size, + .check_memory_region = vpu_imx8q_check_memory_region, + .init_rpc = vpu_windsor_init_rpc, + .set_log_buf = vpu_windsor_set_log_buf, + .set_system_cfg = vpu_windsor_set_system_cfg, + .get_version = vpu_windsor_get_version, + .send_cmd_buf = vpu_rpc_send_cmd_buf, + .receive_msg_buf = vpu_rpc_receive_msg_buf, + .pack_cmd = vpu_windsor_pack_cmd, + .convert_msg_id = vpu_windsor_convert_msg_id, + .unpack_msg_data = vpu_windsor_unpack_msg_data, + .config_memory_resource = vpu_windsor_config_memory_resource, + .get_stream_buffer_size = vpu_windsor_get_stream_buffer_size, + .config_stream_buffer = vpu_windsor_config_stream_buffer, + .get_stream_buffer_desc = vpu_windsor_get_stream_buffer_desc, + .update_stream_buffer = vpu_windsor_update_stream_buffer, + .set_encode_params = vpu_windsor_set_encode_params, + .input_frame = vpu_windsor_input_frame, + .get_max_instance_count = vpu_windsor_get_max_instance_count, + }, + [VPU_CORE_TYPE_DEC] = { + .check_codec = vpu_imx8q_check_codec, + .check_fmt = vpu_malone_check_fmt, + .boot_core = vpu_imx8q_boot_core, + .get_power_state = vpu_imx8q_get_power_state, + .on_firmware_loaded = vpu_imx8q_on_firmware_loaded, + .get_data_size = vpu_malone_get_data_size, + .check_memory_region = vpu_imx8q_check_memory_region, + .init_rpc = vpu_malone_init_rpc, + .set_log_buf = vpu_malone_set_log_buf, + .set_system_cfg = vpu_malone_set_system_cfg, + .get_version = vpu_malone_get_version, + .send_cmd_buf = vpu_rpc_send_cmd_buf, + .receive_msg_buf = vpu_rpc_receive_msg_buf, + .get_stream_buffer_size = vpu_malone_get_stream_buffer_size, + .config_stream_buffer = vpu_malone_config_stream_buffer, + .set_decode_params = vpu_malone_set_decode_params, + .pack_cmd = vpu_malone_pack_cmd, + .convert_msg_id = vpu_malone_convert_msg_id, + .unpack_msg_data = vpu_malone_unpack_msg_data, + .get_stream_buffer_desc = vpu_malone_get_stream_buffer_desc, + .update_stream_buffer = vpu_malone_update_stream_buffer, + .add_scode = vpu_malone_add_scode, + .input_frame = vpu_malone_input_frame, + .pre_send_cmd = vpu_malone_pre_cmd, + .post_send_cmd = vpu_malone_post_cmd, + .init_instance = vpu_malone_init_instance, + .get_max_instance_count = vpu_malone_get_max_instance_count, + }, +}; + +static struct vpu_iface_ops *vpu_get_iface(struct vpu_dev *vpu, enum vpu_core_type type) +{ + struct vpu_iface_ops *rpc_ops = NULL; + u32 size = 0; + + switch (vpu->res->plat_type) { + case IMX8QXP: + case IMX8QM: + rpc_ops = imx8q_rpc_ops; + size = ARRAY_SIZE(imx8q_rpc_ops); + break; + default: + return NULL; + } + + if (type >= size) + return NULL; + + return &rpc_ops[type]; +} + +struct vpu_iface_ops *vpu_core_get_iface(struct vpu_core *core) +{ + return vpu_get_iface(core->vpu, core->type); +} + +struct vpu_iface_ops *vpu_inst_get_iface(struct vpu_inst *inst) +{ + if (inst->core) + return vpu_core_get_iface(inst->core); + + return vpu_get_iface(inst->vpu, inst->type); +} diff --git a/drivers/media/platform/amphion/vpu_rpc.h b/drivers/media/platform/amphion/vpu_rpc.h new file mode 100644 index 000000000..7eb6f01e6 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_rpc.h @@ -0,0 +1,466 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_RPC_H +#define _AMPHION_VPU_RPC_H + +#include +#include "vpu_codec.h" + +struct vpu_rpc_buffer_desc { + u32 wptr; + u32 rptr; + u32 start; + u32 end; +}; + +struct vpu_shared_addr { + void *iface; + struct vpu_rpc_buffer_desc *cmd_desc; + void *cmd_mem_vir; + struct vpu_rpc_buffer_desc *msg_desc; + void *msg_mem_vir; + + unsigned long boot_addr; + struct vpu_core *core; + void *priv; +}; + +struct vpu_rpc_event_header { + u32 index; + u32 id; + u32 num; +}; + +struct vpu_rpc_event { + struct vpu_rpc_event_header hdr; + u32 data[128]; +}; + +struct vpu_iface_ops { + bool (*check_codec)(enum vpu_core_type type); + bool (*check_fmt)(enum vpu_core_type type, u32 pixelfmt); + u32 (*get_data_size)(void); + int (*check_memory_region)(dma_addr_t base, dma_addr_t addr, u32 size); + int (*boot_core)(struct vpu_core *core); + int (*shutdown_core)(struct vpu_core *core); + int (*restore_core)(struct vpu_core *core); + int (*get_power_state)(struct vpu_core *core); + int (*on_firmware_loaded)(struct vpu_core *core); + void (*init_rpc)(struct vpu_shared_addr *shared, + struct vpu_buffer *rpc, dma_addr_t boot_addr); + void (*set_log_buf)(struct vpu_shared_addr *shared, + struct vpu_buffer *log); + void (*set_system_cfg)(struct vpu_shared_addr *shared, + u32 regs_base, void __iomem *regs, u32 index); + void (*set_stream_cfg)(struct vpu_shared_addr *shared, u32 index); + u32 (*get_version)(struct vpu_shared_addr *shared); + u32 (*get_max_instance_count)(struct vpu_shared_addr *shared); + int (*get_stream_buffer_size)(struct vpu_shared_addr *shared); + int (*send_cmd_buf)(struct vpu_shared_addr *shared, + struct vpu_rpc_event *cmd); + int (*receive_msg_buf)(struct vpu_shared_addr *shared, + struct vpu_rpc_event *msg); + int (*pack_cmd)(struct vpu_rpc_event *pkt, u32 index, u32 id, void *data); + int (*convert_msg_id)(u32 msg_id); + int (*unpack_msg_data)(struct vpu_rpc_event *pkt, void *data); + int (*input_frame)(struct vpu_shared_addr *shared, + struct vpu_inst *inst, struct vb2_buffer *vb); + int (*config_memory_resource)(struct vpu_shared_addr *shared, + u32 instance, + u32 type, + u32 index, + struct vpu_buffer *buf); + int (*config_stream_buffer)(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_buffer *buf); + int (*update_stream_buffer)(struct vpu_shared_addr *shared, + u32 instance, u32 ptr, bool write); + int (*get_stream_buffer_desc)(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_rpc_buffer_desc *desc); + int (*set_encode_params)(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_encode_params *params, + u32 update); + int (*set_decode_params)(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_decode_params *params, + u32 update); + int (*add_scode)(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_buffer *stream_buffer, + u32 pixelformat, + u32 scode_type); + int (*pre_send_cmd)(struct vpu_shared_addr *shared, u32 instance); + int (*post_send_cmd)(struct vpu_shared_addr *shared, u32 instance); + int (*init_instance)(struct vpu_shared_addr *shared, u32 instance); +}; + +enum { + VPU_CORE_MEMORY_INVALID = 0, + VPU_CORE_MEMORY_CACHED, + VPU_CORE_MEMORY_UNCACHED +}; + +struct vpu_rpc_region_t { + dma_addr_t start; + dma_addr_t end; + dma_addr_t type; +}; + +struct vpu_iface_ops *vpu_core_get_iface(struct vpu_core *core); +struct vpu_iface_ops *vpu_inst_get_iface(struct vpu_inst *inst); +int vpu_iface_check_memory_region(struct vpu_core *core, dma_addr_t addr, u32 size); + +static inline bool vpu_iface_check_codec(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (ops && ops->check_codec) + return ops->check_codec(core->type); + + return true; +} + +static inline bool vpu_iface_check_format(struct vpu_inst *inst, u32 pixelfmt) +{ + struct vpu_iface_ops *ops = vpu_inst_get_iface(inst); + + if (ops && ops->check_fmt) + return ops->check_fmt(inst->type, pixelfmt); + + return true; +} + +static inline int vpu_iface_boot_core(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (ops && ops->boot_core) + return ops->boot_core(core); + return 0; +} + +static inline int vpu_iface_get_power_state(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (ops && ops->get_power_state) + return ops->get_power_state(core); + return 1; +} + +static inline int vpu_iface_shutdown_core(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (ops && ops->shutdown_core) + return ops->shutdown_core(core); + return 0; +} + +static inline int vpu_iface_restore_core(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (ops && ops->restore_core) + return ops->restore_core(core); + return 0; +} + +static inline int vpu_iface_on_firmware_loaded(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (ops && ops->on_firmware_loaded) + return ops->on_firmware_loaded(core); + + return 0; +} + +static inline u32 vpu_iface_get_data_size(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->get_data_size) + return 0; + + return ops->get_data_size(); +} + +static inline int vpu_iface_init(struct vpu_core *core, + struct vpu_shared_addr *shared, + struct vpu_buffer *rpc, + dma_addr_t boot_addr) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->init_rpc) + return -EINVAL; + + ops->init_rpc(shared, rpc, boot_addr); + core->iface = shared; + shared->core = core; + if (rpc->bytesused > rpc->length) + return -ENOSPC; + return 0; +} + +static inline int vpu_iface_set_log_buf(struct vpu_core *core, + struct vpu_buffer *log) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops) + return -EINVAL; + + if (ops->set_log_buf) + ops->set_log_buf(core->iface, log); + + return 0; +} + +static inline int vpu_iface_config_system(struct vpu_core *core, u32 regs_base, void __iomem *regs) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops) + return -EINVAL; + if (ops->set_system_cfg) + ops->set_system_cfg(core->iface, regs_base, regs, core->id); + + return 0; +} + +static inline int vpu_iface_get_stream_buffer_size(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->get_stream_buffer_size) + return 0; + + return ops->get_stream_buffer_size(core->iface); +} + +static inline int vpu_iface_config_stream(struct vpu_inst *inst) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (!ops || inst->id < 0) + return -EINVAL; + if (ops->set_stream_cfg) + ops->set_stream_cfg(inst->core->iface, inst->id); + return 0; +} + +static inline int vpu_iface_send_cmd(struct vpu_core *core, struct vpu_rpc_event *cmd) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->send_cmd_buf) + return -EINVAL; + + return ops->send_cmd_buf(core->iface, cmd); +} + +static inline int vpu_iface_receive_msg(struct vpu_core *core, struct vpu_rpc_event *msg) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->receive_msg_buf) + return -EINVAL; + + return ops->receive_msg_buf(core->iface, msg); +} + +static inline int vpu_iface_pack_cmd(struct vpu_core *core, + struct vpu_rpc_event *pkt, + u32 index, u32 id, void *data) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->pack_cmd) + return -EINVAL; + return ops->pack_cmd(pkt, index, id, data); +} + +static inline int vpu_iface_convert_msg_id(struct vpu_core *core, u32 msg_id) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->convert_msg_id) + return -EINVAL; + + return ops->convert_msg_id(msg_id); +} + +static inline int vpu_iface_unpack_msg_data(struct vpu_core *core, + struct vpu_rpc_event *pkt, void *data) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->unpack_msg_data) + return -EINVAL; + + return ops->unpack_msg_data(pkt, data); +} + +static inline int vpu_iface_input_frame(struct vpu_inst *inst, + struct vb2_buffer *vb) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + int ret; + + if (!ops || !ops->input_frame) + return -EINVAL; + + ret = ops->input_frame(inst->core->iface, inst, vb); + if (ret < 0) + return ret; + inst->total_input_count++; + return ret; +} + +static inline int vpu_iface_config_memory_resource(struct vpu_inst *inst, + u32 type, + u32 index, + struct vpu_buffer *buf) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (!ops || !ops->config_memory_resource || inst->id < 0) + return -EINVAL; + + return ops->config_memory_resource(inst->core->iface, + inst->id, + type, index, buf); +} + +static inline int vpu_iface_config_stream_buffer(struct vpu_inst *inst, + struct vpu_buffer *buf) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (!ops || !ops->config_stream_buffer || inst->id < 0) + return -EINVAL; + + if ((buf->phys % 4) || (buf->length % 4)) + return -EINVAL; + if (buf->phys + buf->length > (u64)UINT_MAX) + return -EINVAL; + + return ops->config_stream_buffer(inst->core->iface, inst->id, buf); +} + +static inline int vpu_iface_update_stream_buffer(struct vpu_inst *inst, + u32 ptr, bool write) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (!ops || !ops->update_stream_buffer || inst->id < 0) + return -EINVAL; + + return ops->update_stream_buffer(inst->core->iface, inst->id, ptr, write); +} + +static inline int vpu_iface_get_stream_buffer_desc(struct vpu_inst *inst, + struct vpu_rpc_buffer_desc *desc) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (!ops || !ops->get_stream_buffer_desc || inst->id < 0) + return -EINVAL; + + if (!desc) + return 0; + + return ops->get_stream_buffer_desc(inst->core->iface, inst->id, desc); +} + +static inline u32 vpu_iface_get_version(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->get_version) + return 0; + + return ops->get_version(core->iface); +} + +static inline u32 vpu_iface_get_max_instance_count(struct vpu_core *core) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(core); + + if (!ops || !ops->get_max_instance_count) + return 0; + + return ops->get_max_instance_count(core->iface); +} + +static inline int vpu_iface_set_encode_params(struct vpu_inst *inst, + struct vpu_encode_params *params, u32 update) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (!ops || !ops->set_encode_params || inst->id < 0) + return -EINVAL; + + return ops->set_encode_params(inst->core->iface, inst->id, params, update); +} + +static inline int vpu_iface_set_decode_params(struct vpu_inst *inst, + struct vpu_decode_params *params, u32 update) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (!ops || !ops->set_decode_params || inst->id < 0) + return -EINVAL; + + return ops->set_decode_params(inst->core->iface, inst->id, params, update); +} + +static inline int vpu_iface_add_scode(struct vpu_inst *inst, u32 scode_type) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (!ops || !ops->add_scode || inst->id < 0) + return -EINVAL; + + return ops->add_scode(inst->core->iface, inst->id, + &inst->stream_buffer, + inst->out_format.pixfmt, + scode_type); +} + +static inline int vpu_iface_pre_send_cmd(struct vpu_inst *inst) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (ops && ops->pre_send_cmd && inst->id >= 0) + return ops->pre_send_cmd(inst->core->iface, inst->id); + return 0; +} + +static inline int vpu_iface_post_send_cmd(struct vpu_inst *inst) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (ops && ops->post_send_cmd && inst->id >= 0) + return ops->post_send_cmd(inst->core->iface, inst->id); + return 0; +} + +static inline int vpu_iface_init_instance(struct vpu_inst *inst) +{ + struct vpu_iface_ops *ops = vpu_core_get_iface(inst->core); + + if (ops && ops->init_instance && inst->id >= 0) + return ops->init_instance(inst->core->iface, inst->id); + + return 0; +} + +#endif diff --git a/drivers/media/platform/amphion/vpu_v4l2.c b/drivers/media/platform/amphion/vpu_v4l2.c new file mode 100644 index 000000000..e5c8e1a75 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_v4l2.c @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_core.h" +#include "vpu_v4l2.h" +#include "vpu_msgs.h" +#include "vpu_helpers.h" + +void vpu_inst_lock(struct vpu_inst *inst) +{ + mutex_lock(&inst->lock); +} + +void vpu_inst_unlock(struct vpu_inst *inst) +{ + mutex_unlock(&inst->lock); +} + +dma_addr_t vpu_get_vb_phy_addr(struct vb2_buffer *vb, u32 plane_no) +{ + if (plane_no >= vb->num_planes) + return 0; + return vb2_dma_contig_plane_dma_addr(vb, plane_no) + + vb->planes[plane_no].data_offset; +} + +unsigned int vpu_get_vb_length(struct vb2_buffer *vb, u32 plane_no) +{ + if (plane_no >= vb->num_planes) + return 0; + return vb2_plane_size(vb, plane_no) - vb->planes[plane_no].data_offset; +} + +void vpu_set_buffer_state(struct vb2_v4l2_buffer *vbuf, unsigned int state) +{ + struct vpu_vb2_buffer *vpu_buf = to_vpu_vb2_buffer(vbuf); + + vpu_buf->state = state; +} + +unsigned int vpu_get_buffer_state(struct vb2_v4l2_buffer *vbuf) +{ + struct vpu_vb2_buffer *vpu_buf = to_vpu_vb2_buffer(vbuf); + + return vpu_buf->state; +} + +void vpu_v4l2_set_error(struct vpu_inst *inst) +{ + vpu_inst_lock(inst); + dev_err(inst->dev, "some error occurs in codec\n"); + if (inst->fh.m2m_ctx) { + vb2_queue_error(v4l2_m2m_get_src_vq(inst->fh.m2m_ctx)); + vb2_queue_error(v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx)); + } + vpu_inst_unlock(inst); +} + +int vpu_notify_eos(struct vpu_inst *inst) +{ + static const struct v4l2_event ev = { + .id = 0, + .type = V4L2_EVENT_EOS + }; + + vpu_trace(inst->dev, "[%d]\n", inst->id); + v4l2_event_queue_fh(&inst->fh, &ev); + + return 0; +} + +int vpu_notify_source_change(struct vpu_inst *inst) +{ + static const struct v4l2_event ev = { + .id = 0, + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION + }; + + vpu_trace(inst->dev, "[%d]\n", inst->id); + v4l2_event_queue_fh(&inst->fh, &ev); + return 0; +} + +int vpu_set_last_buffer_dequeued(struct vpu_inst *inst, bool eos) +{ + struct vb2_queue *q; + + if (!inst || !inst->fh.m2m_ctx) + return -EINVAL; + + q = v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx); + if (!list_empty(&q->done_list)) + return -EINVAL; + + if (q->last_buffer_dequeued) + return 0; + vpu_trace(inst->dev, "last buffer dequeued\n"); + q->last_buffer_dequeued = true; + wake_up(&q->done_wq); + if (eos) + vpu_notify_eos(inst); + return 0; +} + +bool vpu_is_source_empty(struct vpu_inst *inst) +{ + struct v4l2_m2m_buffer *buf = NULL; + + if (!inst->fh.m2m_ctx) + return true; + v4l2_m2m_for_each_src_buf(inst->fh.m2m_ctx, buf) { + if (vpu_get_buffer_state(&buf->vb) == VPU_BUF_STATE_IDLE) + return false; + } + return true; +} + +const struct vpu_format *vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + u32 type = f->type; + u32 stride = 1; + u32 bytesperline; + u32 sizeimage; + const struct vpu_format *fmt; + const struct vpu_core_resources *res; + int i; + + fmt = vpu_helper_find_format(inst, type, pixmp->pixelformat); + if (!fmt) { + fmt = vpu_helper_enum_format(inst, type, 0); + if (!fmt) + return NULL; + pixmp->pixelformat = fmt->pixfmt; + } + + res = vpu_get_resource(inst); + if (res) + stride = res->stride; + if (pixmp->width) + pixmp->width = vpu_helper_valid_frame_width(inst, pixmp->width); + if (pixmp->height) + pixmp->height = vpu_helper_valid_frame_height(inst, pixmp->height); + pixmp->flags = fmt->flags; + pixmp->num_planes = fmt->num_planes; + if (pixmp->field == V4L2_FIELD_ANY) + pixmp->field = V4L2_FIELD_NONE; + for (i = 0; i < pixmp->num_planes; i++) { + bytesperline = max_t(s32, pixmp->plane_fmt[i].bytesperline, 0); + sizeimage = vpu_helper_get_plane_size(pixmp->pixelformat, + pixmp->width, + pixmp->height, + i, + stride, + pixmp->field > V4L2_FIELD_NONE ? 1 : 0, + &bytesperline); + sizeimage = max_t(s32, pixmp->plane_fmt[i].sizeimage, sizeimage); + pixmp->plane_fmt[i].bytesperline = bytesperline; + pixmp->plane_fmt[i].sizeimage = sizeimage; + } + + return fmt; +} + +static bool vpu_check_ready(struct vpu_inst *inst, u32 type) +{ + if (!inst) + return false; + if (inst->state == VPU_CODEC_STATE_DEINIT || inst->id < 0) + return false; + if (!inst->ops->check_ready) + return true; + return call_vop(inst, check_ready, type); +} + +int vpu_process_output_buffer(struct vpu_inst *inst) +{ + struct v4l2_m2m_buffer *buf = NULL; + struct vb2_v4l2_buffer *vbuf = NULL; + + if (!inst || !inst->fh.m2m_ctx) + return -EINVAL; + + if (!vpu_check_ready(inst, inst->out_format.type)) + return -EINVAL; + + v4l2_m2m_for_each_src_buf(inst->fh.m2m_ctx, buf) { + vbuf = &buf->vb; + if (vpu_get_buffer_state(vbuf) == VPU_BUF_STATE_IDLE) + break; + vbuf = NULL; + } + + if (!vbuf) + return -EINVAL; + + dev_dbg(inst->dev, "[%d]frame id = %d / %d\n", + inst->id, vbuf->sequence, inst->sequence); + return call_vop(inst, process_output, &vbuf->vb2_buf); +} + +int vpu_process_capture_buffer(struct vpu_inst *inst) +{ + struct v4l2_m2m_buffer *buf = NULL; + struct vb2_v4l2_buffer *vbuf = NULL; + + if (!inst || !inst->fh.m2m_ctx) + return -EINVAL; + + if (!vpu_check_ready(inst, inst->cap_format.type)) + return -EINVAL; + + v4l2_m2m_for_each_dst_buf(inst->fh.m2m_ctx, buf) { + vbuf = &buf->vb; + if (vpu_get_buffer_state(vbuf) == VPU_BUF_STATE_IDLE) + break; + vbuf = NULL; + } + if (!vbuf) + return -EINVAL; + + return call_vop(inst, process_capture, &vbuf->vb2_buf); +} + +struct vb2_v4l2_buffer *vpu_next_src_buf(struct vpu_inst *inst) +{ + struct vb2_v4l2_buffer *src_buf = NULL; + + if (!inst->fh.m2m_ctx) + return NULL; + + src_buf = v4l2_m2m_next_src_buf(inst->fh.m2m_ctx); + if (!src_buf || vpu_get_buffer_state(src_buf) == VPU_BUF_STATE_IDLE) + return NULL; + + while (vpu_vb_is_codecconfig(src_buf)) { + v4l2_m2m_src_buf_remove(inst->fh.m2m_ctx); + vpu_set_buffer_state(src_buf, VPU_BUF_STATE_IDLE); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + + src_buf = v4l2_m2m_next_src_buf(inst->fh.m2m_ctx); + if (!src_buf || vpu_get_buffer_state(src_buf) == VPU_BUF_STATE_IDLE) + return NULL; + } + + return src_buf; +} + +void vpu_skip_frame(struct vpu_inst *inst, int count) +{ + struct vb2_v4l2_buffer *src_buf; + enum vb2_buffer_state state; + int i = 0; + + if (count <= 0 || !inst->fh.m2m_ctx) + return; + + while (i < count) { + src_buf = v4l2_m2m_src_buf_remove(inst->fh.m2m_ctx); + if (!src_buf || vpu_get_buffer_state(src_buf) == VPU_BUF_STATE_IDLE) + return; + if (vpu_get_buffer_state(src_buf) == VPU_BUF_STATE_DECODED) + state = VB2_BUF_STATE_DONE; + else + state = VB2_BUF_STATE_ERROR; + i++; + vpu_set_buffer_state(src_buf, VPU_BUF_STATE_IDLE); + v4l2_m2m_buf_done(src_buf, state); + } +} + +struct vb2_v4l2_buffer *vpu_find_buf_by_sequence(struct vpu_inst *inst, u32 type, u32 sequence) +{ + struct v4l2_m2m_buffer *buf = NULL; + struct vb2_v4l2_buffer *vbuf = NULL; + + if (!inst || !inst->fh.m2m_ctx) + return NULL; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + v4l2_m2m_for_each_src_buf(inst->fh.m2m_ctx, buf) { + vbuf = &buf->vb; + if (vbuf->sequence == sequence) + break; + vbuf = NULL; + } + } else { + v4l2_m2m_for_each_dst_buf(inst->fh.m2m_ctx, buf) { + vbuf = &buf->vb; + if (vbuf->sequence == sequence) + break; + vbuf = NULL; + } + } + + return vbuf; +} + +struct vb2_v4l2_buffer *vpu_find_buf_by_idx(struct vpu_inst *inst, u32 type, u32 idx) +{ + struct v4l2_m2m_buffer *buf = NULL; + struct vb2_v4l2_buffer *vbuf = NULL; + + if (!inst || !inst->fh.m2m_ctx) + return NULL; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + v4l2_m2m_for_each_src_buf(inst->fh.m2m_ctx, buf) { + vbuf = &buf->vb; + if (vbuf->vb2_buf.index == idx) + break; + vbuf = NULL; + } + } else { + v4l2_m2m_for_each_dst_buf(inst->fh.m2m_ctx, buf) { + vbuf = &buf->vb; + if (vbuf->vb2_buf.index == idx) + break; + vbuf = NULL; + } + } + + return vbuf; +} + +int vpu_get_num_buffers(struct vpu_inst *inst, u32 type) +{ + struct vb2_queue *q; + + if (!inst || !inst->fh.m2m_ctx) + return -EINVAL; + + if (V4L2_TYPE_IS_OUTPUT(type)) + q = v4l2_m2m_get_src_vq(inst->fh.m2m_ctx); + else + q = v4l2_m2m_get_dst_vq(inst->fh.m2m_ctx); + + return q->num_buffers; +} + +static void vpu_m2m_device_run(void *priv) +{ +} + +static void vpu_m2m_job_abort(void *priv) +{ + struct vpu_inst *inst = priv; + struct v4l2_m2m_ctx *m2m_ctx = inst->fh.m2m_ctx; + + v4l2_m2m_job_finish(m2m_ctx->m2m_dev, m2m_ctx); +} + +static const struct v4l2_m2m_ops vpu_m2m_ops = { + .device_run = vpu_m2m_device_run, + .job_abort = vpu_m2m_job_abort +}; + +static int vpu_vb2_queue_setup(struct vb2_queue *vq, + unsigned int *buf_count, + unsigned int *plane_count, + unsigned int psize[], + struct device *allocators[]) +{ + struct vpu_inst *inst = vb2_get_drv_priv(vq); + struct vpu_format *cur_fmt; + int i; + + cur_fmt = vpu_get_format(inst, vq->type); + + if (*plane_count) { + if (*plane_count != cur_fmt->num_planes) + return -EINVAL; + for (i = 0; i < cur_fmt->num_planes; i++) { + if (psize[i] < cur_fmt->sizeimage[i]) + return -EINVAL; + } + return 0; + } + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + *buf_count = max_t(unsigned int, *buf_count, inst->min_buffer_out); + else + *buf_count = max_t(unsigned int, *buf_count, inst->min_buffer_cap); + *plane_count = cur_fmt->num_planes; + for (i = 0; i < cur_fmt->num_planes; i++) + psize[i] = cur_fmt->sizeimage[i]; + + if (V4L2_TYPE_IS_OUTPUT(vq->type) && inst->state == VPU_CODEC_STATE_SEEK) { + vpu_trace(inst->dev, "reinit when VIDIOC_REQBUFS(OUTPUT, 0)\n"); + call_void_vop(inst, release); + } + + return 0; +} + +static int vpu_vb2_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_IDLE); + return 0; +} + +static int vpu_vb2_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int vpu_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct vpu_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vpu_format *cur_fmt; + u32 i; + + cur_fmt = vpu_get_format(inst, vb->type); + for (i = 0; i < cur_fmt->num_planes; i++) { + if (vpu_get_vb_length(vb, i) < cur_fmt->sizeimage[i]) { + dev_dbg(inst->dev, "[%d] %s buf[%d] is invalid\n", + inst->id, vpu_type_name(vb->type), vb->index); + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_ERROR); + } + } + + return 0; +} + +static void vpu_vb2_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vpu_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_queue *q = vb->vb2_queue; + + if (vbuf->flags & V4L2_BUF_FLAG_LAST) + vpu_notify_eos(inst); + + if (list_empty(&q->done_list)) + call_void_vop(inst, on_queue_empty, q->type); +} + +void vpu_vb2_buffers_return(struct vpu_inst *inst, unsigned int type, enum vb2_buffer_state state) +{ + struct vb2_v4l2_buffer *buf; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + while ((buf = v4l2_m2m_src_buf_remove(inst->fh.m2m_ctx))) { + vpu_set_buffer_state(buf, VPU_BUF_STATE_IDLE); + v4l2_m2m_buf_done(buf, state); + } + } else { + while ((buf = v4l2_m2m_dst_buf_remove(inst->fh.m2m_ctx))) { + vpu_set_buffer_state(buf, VPU_BUF_STATE_IDLE); + v4l2_m2m_buf_done(buf, state); + } + } +} + +static int vpu_vb2_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct vpu_inst *inst = vb2_get_drv_priv(q); + struct vpu_format *fmt = vpu_get_format(inst, q->type); + int ret; + + vpu_inst_unlock(inst); + ret = vpu_inst_register(inst); + vpu_inst_lock(inst); + if (ret) { + vpu_vb2_buffers_return(inst, q->type, VB2_BUF_STATE_QUEUED); + return ret; + } + + vpu_trace(inst->dev, "[%d] %s %c%c%c%c %dx%d %u(%u) %u(%u) %u(%u) %d\n", + inst->id, vpu_type_name(q->type), + fmt->pixfmt, + fmt->pixfmt >> 8, + fmt->pixfmt >> 16, + fmt->pixfmt >> 24, + fmt->width, fmt->height, + fmt->sizeimage[0], fmt->bytesperline[0], + fmt->sizeimage[1], fmt->bytesperline[1], + fmt->sizeimage[2], fmt->bytesperline[2], + q->num_buffers); + vb2_clear_last_buffer_dequeued(q); + ret = call_vop(inst, start, q->type); + if (ret) + vpu_vb2_buffers_return(inst, q->type, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void vpu_vb2_stop_streaming(struct vb2_queue *q) +{ + struct vpu_inst *inst = vb2_get_drv_priv(q); + + vpu_trace(inst->dev, "[%d] %s\n", inst->id, vpu_type_name(q->type)); + + call_void_vop(inst, stop, q->type); + vpu_vb2_buffers_return(inst, q->type, VB2_BUF_STATE_ERROR); + if (V4L2_TYPE_IS_OUTPUT(q->type)) + inst->sequence = 0; +} + +static void vpu_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vpu_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + + if (V4L2_TYPE_IS_OUTPUT(vb->type)) + vbuf->sequence = inst->sequence++; + + v4l2_m2m_buf_queue(inst->fh.m2m_ctx, vbuf); + vpu_process_output_buffer(inst); + vpu_process_capture_buffer(inst); +} + +static const struct vb2_ops vpu_vb2_ops = { + .queue_setup = vpu_vb2_queue_setup, + .buf_init = vpu_vb2_buf_init, + .buf_out_validate = vpu_vb2_buf_out_validate, + .buf_prepare = vpu_vb2_buf_prepare, + .buf_finish = vpu_vb2_buf_finish, + .start_streaming = vpu_vb2_start_streaming, + .stop_streaming = vpu_vb2_stop_streaming, + .buf_queue = vpu_vb2_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int vpu_m2m_queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) +{ + struct vpu_inst *inst = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + inst->out_format.type = src_vq->type; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->ops = &vpu_vb2_ops; + src_vq->mem_ops = &vb2_dma_contig_memops; + if (inst->type == VPU_CORE_TYPE_DEC && inst->use_stream_buffer) + src_vq->mem_ops = &vb2_vmalloc_memops; + src_vq->drv_priv = inst; + src_vq->buf_struct_size = sizeof(struct vpu_vb2_buffer); + src_vq->min_buffers_needed = 1; + src_vq->dev = inst->vpu->dev; + src_vq->lock = &inst->lock; + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + inst->cap_format.type = dst_vq->type; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->ops = &vpu_vb2_ops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + if (inst->type == VPU_CORE_TYPE_ENC && inst->use_stream_buffer) + dst_vq->mem_ops = &vb2_vmalloc_memops; + dst_vq->drv_priv = inst; + dst_vq->buf_struct_size = sizeof(struct vpu_vb2_buffer); + dst_vq->min_buffers_needed = 1; + dst_vq->dev = inst->vpu->dev; + dst_vq->lock = &inst->lock; + ret = vb2_queue_init(dst_vq); + if (ret) { + vb2_queue_release(src_vq); + return ret; + } + + return 0; +} + +static int vpu_v4l2_release(struct vpu_inst *inst) +{ + vpu_trace(inst->vpu->dev, "%p\n", inst); + + vpu_release_core(inst->core); + put_device(inst->dev); + + if (inst->workqueue) { + cancel_work_sync(&inst->msg_work); + destroy_workqueue(inst->workqueue); + inst->workqueue = NULL; + } + + v4l2_ctrl_handler_free(&inst->ctrl_handler); + mutex_destroy(&inst->lock); + v4l2_fh_del(&inst->fh); + v4l2_fh_exit(&inst->fh); + + call_void_vop(inst, cleanup); + + return 0; +} + +int vpu_v4l2_open(struct file *file, struct vpu_inst *inst) +{ + struct vpu_dev *vpu = video_drvdata(file); + struct vpu_func *func; + int ret = 0; + + if (!inst || !inst->ops) + return -EINVAL; + + if (inst->type == VPU_CORE_TYPE_ENC) + func = &vpu->encoder; + else + func = &vpu->decoder; + + atomic_set(&inst->ref_count, 0); + vpu_inst_get(inst); + inst->vpu = vpu; + inst->core = vpu_request_core(vpu, inst->type); + if (inst->core) + inst->dev = get_device(inst->core->dev); + mutex_init(&inst->lock); + INIT_LIST_HEAD(&inst->cmd_q); + inst->id = VPU_INST_NULL_ID; + inst->release = vpu_v4l2_release; + inst->pid = current->pid; + inst->tgid = current->tgid; + inst->min_buffer_cap = 2; + inst->min_buffer_out = 2; + v4l2_fh_init(&inst->fh, func->vfd); + v4l2_fh_add(&inst->fh); + + ret = call_vop(inst, ctrl_init); + if (ret) + goto error; + + inst->fh.m2m_ctx = v4l2_m2m_ctx_init(func->m2m_dev, inst, vpu_m2m_queue_init); + if (IS_ERR(inst->fh.m2m_ctx)) { + dev_err(vpu->dev, "v4l2_m2m_ctx_init fail\n"); + ret = PTR_ERR(inst->fh.m2m_ctx); + goto error; + } + + inst->fh.ctrl_handler = &inst->ctrl_handler; + file->private_data = &inst->fh; + inst->state = VPU_CODEC_STATE_DEINIT; + inst->workqueue = alloc_workqueue("vpu_inst", WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (inst->workqueue) { + INIT_WORK(&inst->msg_work, vpu_inst_run_work); + ret = kfifo_init(&inst->msg_fifo, + inst->msg_buffer, + rounddown_pow_of_two(sizeof(inst->msg_buffer))); + if (ret) { + destroy_workqueue(inst->workqueue); + inst->workqueue = NULL; + } + } + vpu_trace(vpu->dev, "tgid = %d, pid = %d, type = %s, inst = %p\n", + inst->tgid, inst->pid, vpu_core_type_desc(inst->type), inst); + + return 0; +error: + vpu_inst_put(inst); + return ret; +} + +int vpu_v4l2_close(struct file *file) +{ + struct vpu_dev *vpu = video_drvdata(file); + struct vpu_inst *inst = to_inst(file); + + vpu_trace(vpu->dev, "tgid = %d, pid = %d, inst = %p\n", inst->tgid, inst->pid, inst); + + vpu_inst_lock(inst); + if (inst->fh.m2m_ctx) { + v4l2_m2m_ctx_release(inst->fh.m2m_ctx); + inst->fh.m2m_ctx = NULL; + } + call_void_vop(inst, release); + vpu_inst_unlock(inst); + + vpu_inst_unregister(inst); + vpu_inst_put(inst); + + return 0; +} + +int vpu_add_func(struct vpu_dev *vpu, struct vpu_func *func) +{ + struct video_device *vfd; + int ret; + + if (!vpu || !func) + return -EINVAL; + + if (func->vfd) + return 0; + + func->m2m_dev = v4l2_m2m_init(&vpu_m2m_ops); + if (IS_ERR(func->m2m_dev)) { + dev_err(vpu->dev, "v4l2_m2m_init fail\n"); + func->vfd = NULL; + return PTR_ERR(func->m2m_dev); + } + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_m2m_release(func->m2m_dev); + dev_err(vpu->dev, "alloc vpu decoder video device fail\n"); + return -ENOMEM; + } + vfd->release = video_device_release; + vfd->vfl_dir = VFL_DIR_M2M; + vfd->v4l2_dev = &vpu->v4l2_dev; + vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + if (func->type == VPU_CORE_TYPE_ENC) { + strscpy(vfd->name, "amphion-vpu-encoder", sizeof(vfd->name)); + vfd->fops = venc_get_fops(); + vfd->ioctl_ops = venc_get_ioctl_ops(); + } else { + strscpy(vfd->name, "amphion-vpu-decoder", sizeof(vfd->name)); + vfd->fops = vdec_get_fops(); + vfd->ioctl_ops = vdec_get_ioctl_ops(); + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) { + video_device_release(vfd); + v4l2_m2m_release(func->m2m_dev); + return ret; + } + video_set_drvdata(vfd, vpu); + func->vfd = vfd; + + ret = v4l2_m2m_register_media_controller(func->m2m_dev, func->vfd, func->function); + if (ret) { + v4l2_m2m_release(func->m2m_dev); + func->m2m_dev = NULL; + video_unregister_device(func->vfd); + func->vfd = NULL; + return ret; + } + + return 0; +} + +void vpu_remove_func(struct vpu_func *func) +{ + if (!func) + return; + + if (func->m2m_dev) { + v4l2_m2m_unregister_media_controller(func->m2m_dev); + v4l2_m2m_release(func->m2m_dev); + func->m2m_dev = NULL; + } + if (func->vfd) { + video_unregister_device(func->vfd); + func->vfd = NULL; + } +} diff --git a/drivers/media/platform/amphion/vpu_v4l2.h b/drivers/media/platform/amphion/vpu_v4l2.h new file mode 100644 index 000000000..000af24a0 --- /dev/null +++ b/drivers/media/platform/amphion/vpu_v4l2.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_V4L2_H +#define _AMPHION_VPU_V4L2_H + +#include + +void vpu_inst_lock(struct vpu_inst *inst); +void vpu_inst_unlock(struct vpu_inst *inst); +void vpu_set_buffer_state(struct vb2_v4l2_buffer *vbuf, unsigned int state); +unsigned int vpu_get_buffer_state(struct vb2_v4l2_buffer *vbuf); + +int vpu_v4l2_open(struct file *file, struct vpu_inst *inst); +int vpu_v4l2_close(struct file *file); + +const struct vpu_format *vpu_try_fmt_common(struct vpu_inst *inst, struct v4l2_format *f); +int vpu_process_output_buffer(struct vpu_inst *inst); +int vpu_process_capture_buffer(struct vpu_inst *inst); +struct vb2_v4l2_buffer *vpu_next_src_buf(struct vpu_inst *inst); +void vpu_skip_frame(struct vpu_inst *inst, int count); +struct vb2_v4l2_buffer *vpu_find_buf_by_sequence(struct vpu_inst *inst, u32 type, u32 sequence); +struct vb2_v4l2_buffer *vpu_find_buf_by_idx(struct vpu_inst *inst, u32 type, u32 idx); +void vpu_v4l2_set_error(struct vpu_inst *inst); +int vpu_notify_eos(struct vpu_inst *inst); +int vpu_notify_source_change(struct vpu_inst *inst); +int vpu_set_last_buffer_dequeued(struct vpu_inst *inst, bool eos); +void vpu_vb2_buffers_return(struct vpu_inst *inst, unsigned int type, enum vb2_buffer_state state); +int vpu_get_num_buffers(struct vpu_inst *inst, u32 type); +bool vpu_is_source_empty(struct vpu_inst *inst); + +dma_addr_t vpu_get_vb_phy_addr(struct vb2_buffer *vb, u32 plane_no); +unsigned int vpu_get_vb_length(struct vb2_buffer *vb, u32 plane_no); +static inline struct vpu_format *vpu_get_format(struct vpu_inst *inst, u32 type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &inst->out_format; + else + return &inst->cap_format; +} + +static inline char *vpu_type_name(u32 type) +{ + return V4L2_TYPE_IS_OUTPUT(type) ? "output" : "capture"; +} + +static inline int vpu_vb_is_codecconfig(struct vb2_v4l2_buffer *vbuf) +{ +#ifdef V4L2_BUF_FLAG_CODECCONFIG + return (vbuf->flags & V4L2_BUF_FLAG_CODECCONFIG) ? 1 : 0; +#else + return 0; +#endif +} + +#endif diff --git a/drivers/media/platform/amphion/vpu_windsor.c b/drivers/media/platform/amphion/vpu_windsor.c new file mode 100644 index 000000000..b93c8cfde --- /dev/null +++ b/drivers/media/platform/amphion/vpu_windsor.c @@ -0,0 +1,1174 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright 2020-2021 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpu.h" +#include "vpu_rpc.h" +#include "vpu_defs.h" +#include "vpu_helpers.h" +#include "vpu_cmds.h" +#include "vpu_v4l2.h" +#include "vpu_imx8q.h" +#include "vpu_windsor.h" + +#define CMD_SIZE 2560 +#define MSG_SIZE 25600 +#define WINDSOR_USER_DATA_WORDS 16 +#define WINDSOR_MAX_SRC_FRAMES 0x6 +#define WINDSOR_MAX_REF_FRAMES 0x3 +#define WINDSOR_BITRATE_UNIT 1024 +#define WINDSOR_H264_EXTENDED_SAR 255 + +enum { + GTB_ENC_CMD_NOOP = 0x0, + GTB_ENC_CMD_STREAM_START, + GTB_ENC_CMD_FRAME_ENCODE, + GTB_ENC_CMD_FRAME_SKIP, + GTB_ENC_CMD_STREAM_STOP, + GTB_ENC_CMD_PARAMETER_UPD, + GTB_ENC_CMD_TERMINATE, + GTB_ENC_CMD_SNAPSHOT, + GTB_ENC_CMD_ROLL_SNAPSHOT, + GTB_ENC_CMD_LOCK_SCHEDULER, + GTB_ENC_CMD_UNLOCK_SCHEDULER, + GTB_ENC_CMD_CONFIGURE_CODEC, + GTB_ENC_CMD_DEAD_MARK, + GTB_ENC_CMD_FIRM_RESET, + GTB_ENC_CMD_FW_STATUS, + GTB_ENC_CMD_RESERVED +}; + +enum { + VID_API_EVENT_UNDEFINED = 0x0, + VID_API_ENC_EVENT_RESET_DONE = 0x1, + VID_API_ENC_EVENT_START_DONE, + VID_API_ENC_EVENT_STOP_DONE, + VID_API_ENC_EVENT_TERMINATE_DONE, + VID_API_ENC_EVENT_FRAME_INPUT_DONE, + VID_API_ENC_EVENT_FRAME_DONE, + VID_API_ENC_EVENT_FRAME_RELEASE, + VID_API_ENC_EVENT_PARA_UPD_DONE, + VID_API_ENC_EVENT_MEM_REQUEST, + VID_API_ENC_EVENT_FIRMWARE_XCPT, + VID_API_ENC_EVENT_RESERVED +}; + +enum { + MEDIAIP_ENC_PIC_TYPE_B_FRAME = 0, + MEDIAIP_ENC_PIC_TYPE_P_FRAME, + MEDIAIP_ENC_PIC_TYPE_I_FRAME, + MEDIAIP_ENC_PIC_TYPE_IDR_FRAME, + MEDIAIP_ENC_PIC_TYPE_BI_FRAME +}; + +struct windsor_iface { + u32 exec_base_addr; + u32 exec_area_size; + struct vpu_rpc_buffer_desc cmd_buffer_desc; + struct vpu_rpc_buffer_desc msg_buffer_desc; + u32 cmd_int_enable[VID_API_NUM_STREAMS]; + u32 fw_version; + u32 mvd_fw_offset; + u32 max_streams; + u32 ctrl_iface[VID_API_NUM_STREAMS]; + struct vpu_rpc_system_config system_config; + u32 api_version; + struct vpu_rpc_buffer_desc log_buffer_desc; +}; + +struct windsor_ctrl_iface { + u32 enc_yuv_buffer_desc; + u32 enc_stream_buffer_desc; + u32 enc_expert_mode_param; + u32 enc_param; + u32 enc_mem_pool; + u32 enc_encoding_status; + u32 enc_dsa_status; +}; + +struct vpu_enc_yuv_desc { + u32 frame_id; + u32 luma_base; + u32 chroma_base; + u32 param_idx; + u32 key_frame; +}; + +struct vpu_enc_calib_params { + u32 use_ame; + + u32 cme_mvx_max; + u32 cme_mvy_max; + u32 ame_prefresh_y0; + u32 ame_prefresh_y1; + u32 fme_min_sad; + u32 cme_min_sad; + + u32 fme_pred_int_weight; + u32 fme_pred_hp_weight; + u32 fme_pred_qp_weight; + u32 fme_cost_weight; + u32 fme_act_thold; + u32 fme_sad_thold; + u32 fme_zero_sad_thold; + + u32 fme_lrg_mvx_lmt; + u32 fme_lrg_mvy_lmt; + u32 fme_force_mode; + u32 fme_force4mvcost; + u32 fme_force2mvcost; + + u32 h264_inter_thrd; + + u32 i16x16_mode_cost; + u32 i4x4_mode_lambda; + u32 i8x8_mode_lambda; + + u32 inter_mod_mult; + u32 inter_sel_mult; + u32 inter_bid_cost; + u32 inter_bwd_cost; + u32 inter_4mv_cost; + s32 one_mv_i16_cost; + s32 one_mv_i4x4_cost; + s32 one_mv_i8x8_cost; + s32 two_mv_i16_cost; + s32 two_mv_i4x4_cost; + s32 two_mv_i8x8_cost; + s32 four_mv_i16_cost; + s32 four_mv_i4x4_cost; + s32 four_mv_i8x8_cost; + + u32 intra_pred_enab; + u32 intra_chr_pred; + u32 intra16_pred; + u32 intra4x4_pred; + u32 intra8x8_pred; + + u32 cb_base; + u32 cb_size; + u32 cb_head_room; + + u32 mem_page_width; + u32 mem_page_height; + u32 mem_total_size; + u32 mem_chunk_phys_addr; + u32 mem_chunk_virt_addr; + u32 mem_chunk_size; + u32 mem_y_stride; + u32 mem_uv_stride; + + u32 split_wr_enab; + u32 split_wr_req_size; + u32 split_rd_enab; + u32 split_rd_req_size; +}; + +struct vpu_enc_config_params { + u32 param_change; + u32 start_frame; + u32 end_frame; + u32 userdata_enable; + u32 userdata_id[4]; + u32 userdata_message[WINDSOR_USER_DATA_WORDS]; + u32 userdata_length; + u32 h264_profile_idc; + u32 h264_level_idc; + u32 h264_au_delimiter; + u32 h264_seq_end_code; + u32 h264_recovery_points; + u32 h264_vui_parameters; + u32 h264_aspect_ratio_present; + u32 h264_aspect_ratio_sar_width; + u32 h264_aspect_ratio_sar_height; + u32 h264_overscan_present; + u32 h264_video_type_present; + u32 h264_video_format; + u32 h264_video_full_range; + u32 h264_video_colour_descriptor; + u32 h264_video_colour_primaries; + u32 h264_video_transfer_char; + u32 h264_video_matrix_coeff; + u32 h264_chroma_loc_info_present; + u32 h264_chroma_loc_type_top; + u32 h264_chroma_loc_type_bot; + u32 h264_timing_info_present; + u32 h264_buffering_period_present; + u32 h264_low_delay_hrd_flag; + u32 aspect_ratio; + u32 test_mode; // Automated firmware test mode + u32 dsa_test_mode; // Automated test mode for the DSA. + u32 fme_test_mode; // Automated test mode for the fme + u32 cbr_row_mode; //0: FW mode; 1: HW mode + u32 windsor_mode; //0: normal mode; 1: intra only mode; 2: intra+0MV mode + u32 encode_mode; // H264, VC1, MPEG2, DIVX + u32 frame_width; // display width + u32 frame_height; // display height + u32 enc_frame_width; // encoding width, should be 16-pix align + u32 enc_frame_height; // encoding height, should be 16-pix aligned + u32 frame_rate_num; + u32 frame_rate_den; + u32 vi_field_source; + u32 vi_frame_width; + u32 vi_frame_height; + u32 crop_frame_width; + u32 crop_frame_height; + u32 crop_x_start_posn; + u32 crop_y_start_posn; + u32 mode422; + u32 mode_yuy2; + u32 dsa_luma_en; + u32 dsa_chroma_en; + u32 dsa_ext_hfilt_en; + u32 dsa_di_en; + u32 dsa_di_top_ref; + u32 dsa_vertf_disable; + u32 dsa_disable_pwb; + u32 dsa_hor_phase; + u32 dsa_ver_phase; + u32 dsa_iac_enable; + u32 iac_sc_threshold; + u32 iac_vm_threshold; + u32 iac_skip_mode; + u32 iac_grp_width; + u32 iac_grp_height; + u32 rate_control_mode; + u32 rate_control_resolution; + u32 buffer_size; + u32 buffer_level_init; + u32 buffer_I_bit_budget; + u32 top_field_first; + u32 intra_lum_qoffset; + u32 intra_chr_qoffset; + u32 inter_lum_qoffset; + u32 inter_chr_qoffset; + u32 use_def_scaling_mtx; + u32 inter_8x8_enab; + u32 inter_4x4_enab; + u32 fme_enable_qpel; + u32 fme_enable_hpel; + u32 fme_nozeromv; + u32 fme_predmv_en; + u32 fme_pred_2mv4mv; + u32 fme_smallsadthresh; + u32 ame_en_lmvc; + u32 ame_x_mult; + u32 cme_enable_4mv; + u32 cme_enable_1mv; + u32 hme_enable_16x8mv; + u32 hme_enable_8x16mv; + u32 cme_mv_weight; + u32 cme_mv_cost; + u32 ame_mult_mv; + u32 ame_shift_mv; + u32 hme_forceto1mv_en; + u32 hme_2mv_cost; + u32 hme_pred_mode; + u32 hme_sc_rnge; + u32 hme_sw_rnge; + u32 output_format; + u32 timestamp_enab; + u32 initial_pts_enab; + u32 initial_pts; +}; + +struct vpu_enc_static_params { + u32 param_change; + u32 gop_length; + u32 rate_control_bitrate; + u32 rate_control_bitrate_min; + u32 rate_control_bitrate_max; + u32 rate_control_content_models; + u32 rate_control_iframe_maxsize; + u32 rate_control_qp_init; + u32 rate_control_islice_qp; + u32 rate_control_pslice_qp; + u32 rate_control_bslice_qp; + u32 adaptive_quantization; + u32 aq_variance; + u32 cost_optimization; + u32 fdlp_mode; + u32 enable_isegbframes; + u32 enable_adaptive_keyratio; + u32 keyratio_imin; + u32 keyratio_imax; + u32 keyratio_pmin; + u32 keyratio_pmax; + u32 keyratio_bmin; + u32 keyratio_bmax; + s32 keyratio_istep; + s32 keyratio_pstep; + s32 keyratio_bstep; + u32 enable_paff; + u32 enable_b_frame_ref; + u32 enable_adaptive_gop; + u32 enable_closed_gop; + u32 open_gop_refresh_freq; + u32 enable_adaptive_sc; + u32 enable_fade_detection; + s32 fade_detection_threshold; + u32 enable_repeat_b; + u32 enable_low_delay_b; +}; + +struct vpu_enc_dynamic_params { + u32 param_change; + u32 rows_per_slice; + u32 mbaff_enable; + u32 dbf_enable; + u32 field_source; + u32 gop_b_length; + u32 mb_group_size; + u32 cbr_rows_per_group; + u32 skip_enable; + u32 pts_bits_0_to_31; + u32 pts_bit_32; + u32 rm_expsv_cff; + u32 const_ipred; + s32 chr_qp_offset; + u32 intra_mb_qp_offset; + u32 h264_cabac_init_method; + u32 h264_cabac_init_idc; + u32 h264_cabac_enable; + s32 alpha_c0_offset_div2; + s32 beta_offset_div2; + u32 intra_prefresh_y0; + u32 intra_prefresh_y1; + u32 dbg_dump_rec_src; +}; + +struct vpu_enc_expert_mode_param { + struct vpu_enc_calib_params calib_param; + struct vpu_enc_config_params config_param; + struct vpu_enc_static_params static_param; + struct vpu_enc_dynamic_params dynamic_param; +}; + +enum MEDIAIP_ENC_FMT { + MEDIAIP_ENC_FMT_H264 = 0, + MEDIAIP_ENC_FMT_VC1, + MEDIAIP_ENC_FMT_MPEG2, + MEDIAIP_ENC_FMT_MPEG4SP, + MEDIAIP_ENC_FMT_H263, + MEDIAIP_ENC_FMT_MPEG1, + MEDIAIP_ENC_FMT_SHORT_HEADER, + MEDIAIP_ENC_FMT_NULL +}; + +enum MEDIAIP_ENC_PROFILE { + MEDIAIP_ENC_PROF_MPEG2_SP = 0, + MEDIAIP_ENC_PROF_MPEG2_MP, + MEDIAIP_ENC_PROF_MPEG2_HP, + MEDIAIP_ENC_PROF_H264_BP, + MEDIAIP_ENC_PROF_H264_MP, + MEDIAIP_ENC_PROF_H264_HP, + MEDIAIP_ENC_PROF_MPEG4_SP, + MEDIAIP_ENC_PROF_MPEG4_ASP, + MEDIAIP_ENC_PROF_VC1_SP, + MEDIAIP_ENC_PROF_VC1_MP, + MEDIAIP_ENC_PROF_VC1_AP +}; + +enum MEDIAIP_ENC_BITRATE_MODE { + MEDIAIP_ENC_BITRATE_MODE_VBR = 0x00000001, + MEDIAIP_ENC_BITRATE_MODE_CBR = 0x00000002, + MEDIAIP_ENC_BITRATE_MODE_CONSTANT_QP = 0x00000004 +}; + +struct vpu_enc_memory_resource { + u32 phys; + u32 virt; + u32 size; +}; + +struct vpu_enc_param { + enum MEDIAIP_ENC_FMT codec_mode; + enum MEDIAIP_ENC_PROFILE profile; + u32 level; + + struct vpu_enc_memory_resource enc_mem_desc; + + u32 frame_rate; + u32 src_stride; + u32 src_width; + u32 src_height; + u32 src_offset_x; + u32 src_offset_y; + u32 src_crop_width; + u32 src_crop_height; + u32 out_width; + u32 out_height; + u32 iframe_interval; + u32 bframes; + u32 low_latency_mode; + + enum MEDIAIP_ENC_BITRATE_MODE bitrate_mode; + u32 target_bitrate; + u32 max_bitrate; + u32 min_bitrate; + u32 init_slice_qp; +}; + +struct vpu_enc_mem_pool { + struct vpu_enc_memory_resource enc_frames[WINDSOR_MAX_SRC_FRAMES]; + struct vpu_enc_memory_resource ref_frames[WINDSOR_MAX_REF_FRAMES]; + struct vpu_enc_memory_resource act_frame; +}; + +struct vpu_enc_encoding_status { + u32 frame_id; + u32 error_flag; //Error type + u32 mb_y; + u32 mb_x; + u32 reserved[12]; + +}; + +struct vpu_enc_dsa_status { + u32 frame_id; + u32 dsa_cyle; + u32 mb_y; + u32 mb_x; + u32 reserved[4]; +}; + +struct vpu_enc_ctrl { + struct vpu_enc_yuv_desc *yuv_desc; + struct vpu_rpc_buffer_desc *stream_desc; + struct vpu_enc_expert_mode_param *expert; + struct vpu_enc_param *param; + struct vpu_enc_mem_pool *pool; + struct vpu_enc_encoding_status *status; + struct vpu_enc_dsa_status *dsa; +}; + +struct vpu_enc_host_ctrls { + struct vpu_enc_ctrl ctrls[VID_API_NUM_STREAMS]; +}; + +struct windsor_pic_info { + u32 frame_id; + u32 pic_encod_done; + u32 pic_type; + u32 skipped_frame; + u32 error_flag; + u32 psnr; + u32 flush_done; + u32 mb_y; + u32 mb_x; + u32 frame_size; + u32 frame_enc_ttl_cycles; + u32 frame_enc_ttl_frm_cycles; + u32 frame_enc_ttl_slc_cycles; + u32 frame_enc_ttl_enc_cycles; + u32 frame_enc_ttl_hme_cycles; + u32 frame_enc_ttl_dsa_cycles; + u32 frame_enc_fw_cycles; + u32 frame_crc; + u32 num_interrupts_1; + u32 num_interrupts_2; + u32 poc; + u32 ref_info; + u32 pic_num; + u32 pic_activity; + u32 scene_change; + u32 mb_stats; + u32 enc_cache_count0; + u32 enc_cache_count1; + u32 mtl_wr_strb_cnt; + u32 mtl_rd_strb_cnt; + u32 str_buff_wptr; + u32 diagnosticEvents; + u32 proc_iacc_tot_rd_cnt; + u32 proc_dacc_tot_rd_cnt; + u32 proc_dacc_tot_wr_cnt; + u32 proc_dacc_reg_rd_cnt; + u32 proc_dacc_reg_wr_cnt; + u32 proc_dacc_rng_rd_cnt; + u32 proc_dacc_rng_wr_cnt; + s32 tv_s; + u32 tv_ns; +}; + +u32 vpu_windsor_get_data_size(void) +{ + return sizeof(struct vpu_enc_host_ctrls); +} + +static struct vpu_enc_yuv_desc *get_yuv_desc(struct vpu_shared_addr *shared, + u32 instance) +{ + struct vpu_enc_host_ctrls *hcs = shared->priv; + + return hcs->ctrls[instance].yuv_desc; +} + +static struct vpu_enc_mem_pool *get_mem_pool(struct vpu_shared_addr *shared, + u32 instance) +{ + struct vpu_enc_host_ctrls *hcs = shared->priv; + + return hcs->ctrls[instance].pool; +} + +static struct vpu_rpc_buffer_desc *get_stream_buf_desc(struct vpu_shared_addr *shared, + u32 instance) +{ + struct vpu_enc_host_ctrls *hcs = shared->priv; + + return hcs->ctrls[instance].stream_desc; +} + +static struct vpu_enc_expert_mode_param *get_expert_param(struct vpu_shared_addr *shared, + u32 instance) +{ + struct vpu_enc_host_ctrls *hcs = shared->priv; + + return hcs->ctrls[instance].expert; +} + +static struct vpu_enc_param *get_enc_param(struct vpu_shared_addr *shared, u32 instance) +{ + struct vpu_enc_host_ctrls *hcs = shared->priv; + + return hcs->ctrls[instance].param; +} + +static u32 get_ptr(u32 ptr) +{ + return (ptr | 0x80000000); +} + +void vpu_windsor_init_rpc(struct vpu_shared_addr *shared, + struct vpu_buffer *rpc, dma_addr_t boot_addr) +{ + unsigned long base_phy_addr; + unsigned long phy_addr; + unsigned long offset; + struct windsor_iface *iface; + struct windsor_ctrl_iface *ctrl; + struct vpu_enc_host_ctrls *hcs; + unsigned int i; + + if (rpc->phys < boot_addr) + return; + + base_phy_addr = rpc->phys - boot_addr; + iface = rpc->virt; + shared->iface = iface; + shared->boot_addr = boot_addr; + hcs = shared->priv; + + iface->exec_base_addr = base_phy_addr; + iface->exec_area_size = rpc->length; + + offset = sizeof(struct windsor_iface); + phy_addr = base_phy_addr + offset; + shared->cmd_desc = &iface->cmd_buffer_desc; + shared->cmd_mem_vir = rpc->virt + offset; + iface->cmd_buffer_desc.start = + iface->cmd_buffer_desc.rptr = + iface->cmd_buffer_desc.wptr = phy_addr; + iface->cmd_buffer_desc.end = iface->cmd_buffer_desc.start + CMD_SIZE; + + offset += CMD_SIZE; + phy_addr = base_phy_addr + offset; + shared->msg_desc = &iface->msg_buffer_desc; + shared->msg_mem_vir = rpc->virt + offset; + iface->msg_buffer_desc.start = + iface->msg_buffer_desc.wptr = + iface->msg_buffer_desc.rptr = phy_addr; + iface->msg_buffer_desc.end = iface->msg_buffer_desc.start + MSG_SIZE; + + offset += MSG_SIZE; + for (i = 0; i < ARRAY_SIZE(iface->ctrl_iface); i++) { + iface->ctrl_iface[i] = base_phy_addr + offset; + offset += sizeof(struct windsor_ctrl_iface); + } + for (i = 0; i < ARRAY_SIZE(iface->ctrl_iface); i++) { + ctrl = rpc->virt + (iface->ctrl_iface[i] - base_phy_addr); + + ctrl->enc_yuv_buffer_desc = base_phy_addr + offset; + hcs->ctrls[i].yuv_desc = rpc->virt + offset; + offset += sizeof(struct vpu_enc_yuv_desc); + + ctrl->enc_stream_buffer_desc = base_phy_addr + offset; + hcs->ctrls[i].stream_desc = rpc->virt + offset; + offset += sizeof(struct vpu_rpc_buffer_desc); + + ctrl->enc_expert_mode_param = base_phy_addr + offset; + hcs->ctrls[i].expert = rpc->virt + offset; + offset += sizeof(struct vpu_enc_expert_mode_param); + + ctrl->enc_param = base_phy_addr + offset; + hcs->ctrls[i].param = rpc->virt + offset; + offset += sizeof(struct vpu_enc_param); + + ctrl->enc_mem_pool = base_phy_addr + offset; + hcs->ctrls[i].pool = rpc->virt + offset; + offset += sizeof(struct vpu_enc_mem_pool); + + ctrl->enc_encoding_status = base_phy_addr + offset; + hcs->ctrls[i].status = rpc->virt + offset; + offset += sizeof(struct vpu_enc_encoding_status); + + ctrl->enc_dsa_status = base_phy_addr + offset; + hcs->ctrls[i].dsa = rpc->virt + offset; + offset += sizeof(struct vpu_enc_dsa_status); + } + + rpc->bytesused = offset; +} + +void vpu_windsor_set_log_buf(struct vpu_shared_addr *shared, struct vpu_buffer *log) +{ + struct windsor_iface *iface = shared->iface; + + iface->log_buffer_desc.start = + iface->log_buffer_desc.wptr = + iface->log_buffer_desc.rptr = log->phys - shared->boot_addr; + iface->log_buffer_desc.end = iface->log_buffer_desc.start + log->length; +} + +void vpu_windsor_set_system_cfg(struct vpu_shared_addr *shared, + u32 regs_base, void __iomem *regs, u32 core_id) +{ + struct windsor_iface *iface = shared->iface; + struct vpu_rpc_system_config *config = &iface->system_config; + + vpu_imx8q_set_system_cfg_common(config, regs_base, core_id); +} + +int vpu_windsor_get_stream_buffer_size(struct vpu_shared_addr *shared) +{ + return 0x300000; +} + +static struct vpu_pair windsor_cmds[] = { + {VPU_CMD_ID_NOOP, GTB_ENC_CMD_NOOP}, + {VPU_CMD_ID_CONFIGURE_CODEC, GTB_ENC_CMD_CONFIGURE_CODEC}, + {VPU_CMD_ID_START, GTB_ENC_CMD_STREAM_START}, + {VPU_CMD_ID_STOP, GTB_ENC_CMD_STREAM_STOP}, + {VPU_CMD_ID_FRAME_ENCODE, GTB_ENC_CMD_FRAME_ENCODE}, + {VPU_CMD_ID_SNAPSHOT, GTB_ENC_CMD_SNAPSHOT}, + {VPU_CMD_ID_FIRM_RESET, GTB_ENC_CMD_FIRM_RESET}, + {VPU_CMD_ID_UPDATE_PARAMETER, GTB_ENC_CMD_PARAMETER_UPD}, + {VPU_CMD_ID_DEBUG, GTB_ENC_CMD_FW_STATUS} +}; + +static struct vpu_pair windsor_msgs[] = { + {VPU_MSG_ID_RESET_DONE, VID_API_ENC_EVENT_RESET_DONE}, + {VPU_MSG_ID_START_DONE, VID_API_ENC_EVENT_START_DONE}, + {VPU_MSG_ID_STOP_DONE, VID_API_ENC_EVENT_STOP_DONE}, + {VPU_MSG_ID_FRAME_INPUT_DONE, VID_API_ENC_EVENT_FRAME_INPUT_DONE}, + {VPU_MSG_ID_ENC_DONE, VID_API_ENC_EVENT_FRAME_DONE}, + {VPU_MSG_ID_FRAME_RELEASE, VID_API_ENC_EVENT_FRAME_RELEASE}, + {VPU_MSG_ID_MEM_REQUEST, VID_API_ENC_EVENT_MEM_REQUEST}, + {VPU_MSG_ID_PARAM_UPD_DONE, VID_API_ENC_EVENT_PARA_UPD_DONE}, + {VPU_MSG_ID_FIRMWARE_XCPT, VID_API_ENC_EVENT_FIRMWARE_XCPT}, +}; + +int vpu_windsor_pack_cmd(struct vpu_rpc_event *pkt, u32 index, u32 id, void *data) +{ + int ret; + + ret = vpu_find_dst_by_src(windsor_cmds, ARRAY_SIZE(windsor_cmds), id); + if (ret < 0) + return ret; + pkt->hdr.id = ret; + pkt->hdr.num = 0; + pkt->hdr.index = index; + if (id == VPU_CMD_ID_FRAME_ENCODE) { + s64 timestamp = *(s64 *)data; + struct timespec64 ts = ns_to_timespec64(timestamp); + + pkt->hdr.num = 2; + pkt->data[0] = ts.tv_sec; + pkt->data[1] = ts.tv_nsec; + } + + return 0; +} + +int vpu_windsor_convert_msg_id(u32 id) +{ + return vpu_find_src_by_dst(windsor_msgs, ARRAY_SIZE(windsor_msgs), id); +} + +static void vpu_windsor_unpack_pic_info(struct vpu_rpc_event *pkt, void *data) +{ + struct vpu_enc_pic_info *info = data; + struct windsor_pic_info *windsor = (struct windsor_pic_info *)pkt->data; + struct timespec64 ts = { windsor->tv_s, windsor->tv_ns }; + + info->frame_id = windsor->frame_id; + switch (windsor->pic_type) { + case MEDIAIP_ENC_PIC_TYPE_I_FRAME: + case MEDIAIP_ENC_PIC_TYPE_IDR_FRAME: + info->pic_type = V4L2_BUF_FLAG_KEYFRAME; + break; + case MEDIAIP_ENC_PIC_TYPE_P_FRAME: + info->pic_type = V4L2_BUF_FLAG_PFRAME; + break; + case MEDIAIP_ENC_PIC_TYPE_B_FRAME: + info->pic_type = V4L2_BUF_FLAG_BFRAME; + break; + default: + break; + } + info->skipped_frame = windsor->skipped_frame; + info->error_flag = windsor->error_flag; + info->psnr = windsor->psnr; + info->frame_size = windsor->frame_size; + info->wptr = get_ptr(windsor->str_buff_wptr); + info->crc = windsor->frame_crc; + info->timestamp = timespec64_to_ns(&ts); +} + +static void vpu_windsor_unpack_mem_req(struct vpu_rpc_event *pkt, void *data) +{ + struct vpu_pkt_mem_req_data *req_data = data; + + req_data->enc_frame_size = pkt->data[0]; + req_data->enc_frame_num = pkt->data[1]; + req_data->ref_frame_size = pkt->data[2]; + req_data->ref_frame_num = pkt->data[3]; + req_data->act_buf_size = pkt->data[4]; + req_data->act_buf_num = 1; +} + +int vpu_windsor_unpack_msg_data(struct vpu_rpc_event *pkt, void *data) +{ + if (!pkt || !data) + return -EINVAL; + + switch (pkt->hdr.id) { + case VID_API_ENC_EVENT_FRAME_DONE: + vpu_windsor_unpack_pic_info(pkt, data); + break; + case VID_API_ENC_EVENT_MEM_REQUEST: + vpu_windsor_unpack_mem_req(pkt, data); + break; + case VID_API_ENC_EVENT_FRAME_RELEASE: + *(u32 *)data = pkt->data[0]; + break; + default: + break; + } + + return 0; +} + +static int vpu_windsor_fill_yuv_frame(struct vpu_shared_addr *shared, + u32 instance, + struct vb2_buffer *vb) +{ + struct vpu_enc_yuv_desc *desc; + struct vb2_v4l2_buffer *vbuf; + + if (instance >= VID_API_NUM_STREAMS) + return -EINVAL; + + desc = get_yuv_desc(shared, instance); + + vbuf = to_vb2_v4l2_buffer(vb); + desc->frame_id = vbuf->sequence; + if (vbuf->flags & V4L2_BUF_FLAG_KEYFRAME) + desc->key_frame = 1; + else + desc->key_frame = 0; + desc->luma_base = vpu_get_vb_phy_addr(vb, 0); + desc->chroma_base = vpu_get_vb_phy_addr(vb, 1); + + return 0; +} + +int vpu_windsor_input_frame(struct vpu_shared_addr *shared, + struct vpu_inst *inst, struct vb2_buffer *vb) +{ + vpu_windsor_fill_yuv_frame(shared, inst->id, vb); + return vpu_session_encode_frame(inst, vb->timestamp); +} + +int vpu_windsor_config_memory_resource(struct vpu_shared_addr *shared, + u32 instance, + u32 type, + u32 index, + struct vpu_buffer *buf) +{ + struct vpu_enc_mem_pool *pool; + struct vpu_enc_memory_resource *res; + + if (instance >= VID_API_NUM_STREAMS) + return -EINVAL; + + pool = get_mem_pool(shared, instance); + + switch (type) { + case MEM_RES_ENC: + if (index >= ARRAY_SIZE(pool->enc_frames)) + return -EINVAL; + res = &pool->enc_frames[index]; + break; + case MEM_RES_REF: + if (index >= ARRAY_SIZE(pool->ref_frames)) + return -EINVAL; + res = &pool->ref_frames[index]; + break; + case MEM_RES_ACT: + if (index) + return -EINVAL; + res = &pool->act_frame; + break; + default: + return -EINVAL; + } + + res->phys = buf->phys; + res->virt = buf->phys - shared->boot_addr; + res->size = buf->length; + + return 0; +} + +int vpu_windsor_config_stream_buffer(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_buffer *buf) +{ + struct vpu_rpc_buffer_desc *desc; + struct vpu_enc_expert_mode_param *expert; + + desc = get_stream_buf_desc(shared, instance); + expert = get_expert_param(shared, instance); + + desc->start = buf->phys; + desc->wptr = buf->phys; + desc->rptr = buf->phys; + desc->end = buf->phys + buf->length; + + expert->calib_param.mem_chunk_phys_addr = 0; + expert->calib_param.mem_chunk_virt_addr = 0; + expert->calib_param.mem_chunk_size = 0; + expert->calib_param.cb_base = buf->phys; + expert->calib_param.cb_size = buf->length; + + return 0; +} + +int vpu_windsor_update_stream_buffer(struct vpu_shared_addr *shared, + u32 instance, u32 ptr, bool write) +{ + struct vpu_rpc_buffer_desc *desc; + + desc = get_stream_buf_desc(shared, instance); + + /*update wptr/rptr after data is written or read*/ + mb(); + if (write) + desc->wptr = ptr; + else + desc->rptr = ptr; + + return 0; +} + +int vpu_windsor_get_stream_buffer_desc(struct vpu_shared_addr *shared, + u32 instance, struct vpu_rpc_buffer_desc *desc) +{ + struct vpu_rpc_buffer_desc *rpc_desc; + + rpc_desc = get_stream_buf_desc(shared, instance); + if (desc) { + desc->wptr = get_ptr(rpc_desc->wptr); + desc->rptr = get_ptr(rpc_desc->rptr); + desc->start = get_ptr(rpc_desc->start); + desc->end = get_ptr(rpc_desc->end); + } + + return 0; +} + +u32 vpu_windsor_get_version(struct vpu_shared_addr *shared) +{ + struct windsor_iface *iface = shared->iface; + + return iface->fw_version; +} + +static int vpu_windsor_set_frame_rate(struct vpu_enc_expert_mode_param *expert, + struct vpu_encode_params *params) +{ + expert->config_param.frame_rate_num = params->frame_rate.numerator; + expert->config_param.frame_rate_den = params->frame_rate.denominator; + + return 0; +} + +static int vpu_windsor_set_format(struct vpu_enc_param *param, u32 pixelformat) +{ + switch (pixelformat) { + case V4L2_PIX_FMT_H264: + param->codec_mode = MEDIAIP_ENC_FMT_H264; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vpu_windsor_set_profile(struct vpu_enc_param *param, u32 profile) +{ + switch (profile) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + param->profile = MEDIAIP_ENC_PROF_H264_BP; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + param->profile = MEDIAIP_ENC_PROF_H264_MP; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + param->profile = MEDIAIP_ENC_PROF_H264_HP; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const u32 h264_level[] = { + [V4L2_MPEG_VIDEO_H264_LEVEL_1_0] = 10, + [V4L2_MPEG_VIDEO_H264_LEVEL_1B] = 14, + [V4L2_MPEG_VIDEO_H264_LEVEL_1_1] = 11, + [V4L2_MPEG_VIDEO_H264_LEVEL_1_2] = 12, + [V4L2_MPEG_VIDEO_H264_LEVEL_1_3] = 13, + [V4L2_MPEG_VIDEO_H264_LEVEL_2_0] = 20, + [V4L2_MPEG_VIDEO_H264_LEVEL_2_1] = 21, + [V4L2_MPEG_VIDEO_H264_LEVEL_2_2] = 22, + [V4L2_MPEG_VIDEO_H264_LEVEL_3_0] = 30, + [V4L2_MPEG_VIDEO_H264_LEVEL_3_1] = 31, + [V4L2_MPEG_VIDEO_H264_LEVEL_3_2] = 32, + [V4L2_MPEG_VIDEO_H264_LEVEL_4_0] = 40, + [V4L2_MPEG_VIDEO_H264_LEVEL_4_1] = 41, + [V4L2_MPEG_VIDEO_H264_LEVEL_4_2] = 42, + [V4L2_MPEG_VIDEO_H264_LEVEL_5_0] = 50, + [V4L2_MPEG_VIDEO_H264_LEVEL_5_1] = 51 +}; + +static int vpu_windsor_set_level(struct vpu_enc_param *param, u32 level) +{ + if (level >= ARRAY_SIZE(h264_level)) + return -EINVAL; + + param->level = h264_level[level]; + + return 0; +} + +static int vpu_windsor_set_size(struct vpu_enc_param *windsor, + struct vpu_encode_params *params) +{ + windsor->src_stride = params->src_stride; + windsor->src_width = params->src_width; + windsor->src_height = params->src_height; + windsor->src_offset_x = params->crop.left; + windsor->src_offset_y = params->crop.top; + windsor->src_crop_width = params->crop.width; + windsor->src_crop_height = params->crop.height; + windsor->out_width = params->out_width; + windsor->out_height = params->out_height; + + return 0; +} + +static int vpu_windsor_set_gop(struct vpu_enc_param *param, u32 gop) +{ + param->iframe_interval = gop; + + return 0; +} + +static int vpu_windsor_set_bframes(struct vpu_enc_param *param, u32 bframes) +{ + if (bframes) { + param->low_latency_mode = 0; + param->bframes = bframes; + } else { + param->low_latency_mode = 1; + param->bframes = 0; + } + + return 0; +} + +static int vpu_windsor_set_bitrate_mode(struct vpu_enc_param *param, u32 rc_enable, u32 mode) +{ + if (!rc_enable) + param->bitrate_mode = MEDIAIP_ENC_BITRATE_MODE_CONSTANT_QP; + else if (mode == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) + param->bitrate_mode = MEDIAIP_ENC_BITRATE_MODE_VBR; + else + param->bitrate_mode = MEDIAIP_ENC_BITRATE_MODE_CBR; + + return 0; +} + +static u32 vpu_windsor_bitrate(u32 bitrate) +{ + return DIV_ROUND_CLOSEST(bitrate, WINDSOR_BITRATE_UNIT); +} + +static int vpu_windsor_set_bitrate(struct vpu_enc_param *windsor, + struct vpu_encode_params *params) +{ + windsor->target_bitrate = vpu_windsor_bitrate(params->bitrate); + windsor->min_bitrate = vpu_windsor_bitrate(params->bitrate_min); + windsor->max_bitrate = vpu_windsor_bitrate(params->bitrate_max); + + return 0; +} + +static int vpu_windsor_set_qp(struct vpu_enc_expert_mode_param *expert, + struct vpu_encode_params *params) +{ + expert->static_param.rate_control_islice_qp = params->i_frame_qp; + expert->static_param.rate_control_pslice_qp = params->p_frame_qp; + expert->static_param.rate_control_bslice_qp = params->b_frame_qp; + + return 0; +} + +static int vpu_windsor_set_sar(struct vpu_enc_expert_mode_param *expert, + struct vpu_encode_params *params) +{ + expert->config_param.h264_aspect_ratio_present = params->sar.enable; + if (params->sar.idc == V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED) + expert->config_param.aspect_ratio = WINDSOR_H264_EXTENDED_SAR; + else + expert->config_param.aspect_ratio = params->sar.idc; + expert->config_param.h264_aspect_ratio_sar_width = params->sar.width; + expert->config_param.h264_aspect_ratio_sar_height = params->sar.height; + + return 0; +} + +static int vpu_windsor_set_color(struct vpu_enc_expert_mode_param *expert, + struct vpu_encode_params *params) +{ + expert->config_param.h264_video_type_present = 1; + expert->config_param.h264_video_format = 5; + expert->config_param.h264_video_colour_descriptor = 1; + expert->config_param.h264_video_colour_primaries = + vpu_color_cvrt_primaries_v2i(params->color.primaries); + expert->config_param.h264_video_transfer_char = + vpu_color_cvrt_transfers_v2i(params->color.transfer); + expert->config_param.h264_video_matrix_coeff = + vpu_color_cvrt_matrix_v2i(params->color.matrix); + expert->config_param.h264_video_full_range = + vpu_color_cvrt_full_range_v2i(params->color.full_range); + return 0; +} + +static int vpu_windsor_update_bitrate(struct vpu_shared_addr *shared, + u32 instance, struct vpu_encode_params *params) +{ + struct vpu_enc_param *windsor; + struct vpu_enc_expert_mode_param *expert; + + windsor = get_enc_param(shared, instance); + expert = get_expert_param(shared, instance); + + if (windsor->bitrate_mode != MEDIAIP_ENC_BITRATE_MODE_CBR) + return 0; + if (!params->rc_enable) + return 0; + if (vpu_windsor_bitrate(params->bitrate) == windsor->target_bitrate) + return 0; + + vpu_windsor_set_bitrate(windsor, params); + expert->static_param.rate_control_bitrate = windsor->target_bitrate; + expert->static_param.rate_control_bitrate_min = windsor->min_bitrate; + expert->static_param.rate_control_bitrate_max = windsor->max_bitrate; + + return 0; +} + +static int vpu_windsor_set_params(struct vpu_shared_addr *shared, + u32 instance, struct vpu_encode_params *params) +{ + struct vpu_enc_param *windsor; + int ret; + + windsor = get_enc_param(shared, instance); + + if (params->input_format != V4L2_PIX_FMT_NV12 && + params->input_format != V4L2_PIX_FMT_NV12M) + return -EINVAL; + + ret = vpu_windsor_set_format(windsor, params->codec_format); + if (ret) + return ret; + vpu_windsor_set_profile(windsor, params->profile); + vpu_windsor_set_level(windsor, params->level); + vpu_windsor_set_size(windsor, params); + vpu_windsor_set_gop(windsor, params->gop_length); + vpu_windsor_set_bframes(windsor, params->bframes); + vpu_windsor_set_bitrate_mode(windsor, params->rc_enable, params->rc_mode); + vpu_windsor_set_bitrate(windsor, params); + windsor->init_slice_qp = params->i_frame_qp; + + if (!params->frame_rate.numerator) + return -EINVAL; + windsor->frame_rate = params->frame_rate.denominator / params->frame_rate.numerator; + + return 0; +} + +static int vpu_windsor_update_params(struct vpu_shared_addr *shared, + u32 instance, struct vpu_encode_params *params) +{ + struct vpu_enc_expert_mode_param *expert; + + expert = get_expert_param(shared, instance); + + vpu_windsor_set_frame_rate(expert, params); + vpu_windsor_set_qp(expert, params); + vpu_windsor_set_sar(expert, params); + vpu_windsor_set_color(expert, params); + vpu_windsor_update_bitrate(shared, instance, params); + /*expert->config_param.iac_sc_threshold = 0;*/ + + return 0; +} + +int vpu_windsor_set_encode_params(struct vpu_shared_addr *shared, + u32 instance, struct vpu_encode_params *params, u32 update) +{ + if (!params) + return -EINVAL; + + if (!update) + return vpu_windsor_set_params(shared, instance, params); + else + return vpu_windsor_update_params(shared, instance, params); +} + +u32 vpu_windsor_get_max_instance_count(struct vpu_shared_addr *shared) +{ + struct windsor_iface *iface = shared->iface; + + return iface->max_streams; +} diff --git a/drivers/media/platform/amphion/vpu_windsor.h b/drivers/media/platform/amphion/vpu_windsor.h new file mode 100644 index 000000000..3fbb6556d --- /dev/null +++ b/drivers/media/platform/amphion/vpu_windsor.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright 2020-2021 NXP + */ + +#ifndef _AMPHION_VPU_WINDSOR_H +#define _AMPHION_VPU_WINDSOR_H + +u32 vpu_windsor_get_data_size(void); +void vpu_windsor_init_rpc(struct vpu_shared_addr *shared, + struct vpu_buffer *rpc, dma_addr_t boot_addr); +void vpu_windsor_set_log_buf(struct vpu_shared_addr *shared, struct vpu_buffer *log); +void vpu_windsor_set_system_cfg(struct vpu_shared_addr *shared, + u32 regs_base, void __iomem *regs, u32 core_id); +int vpu_windsor_get_stream_buffer_size(struct vpu_shared_addr *shared); +int vpu_windsor_pack_cmd(struct vpu_rpc_event *pkt, u32 index, u32 id, void *data); +int vpu_windsor_convert_msg_id(u32 msg_id); +int vpu_windsor_unpack_msg_data(struct vpu_rpc_event *pkt, void *data); +int vpu_windsor_config_memory_resource(struct vpu_shared_addr *shared, + u32 instance, u32 type, u32 index, + struct vpu_buffer *buf); +int vpu_windsor_config_stream_buffer(struct vpu_shared_addr *shared, + u32 instance, struct vpu_buffer *buf); +int vpu_windsor_update_stream_buffer(struct vpu_shared_addr *shared, + u32 instance, u32 ptr, bool write); +int vpu_windsor_get_stream_buffer_desc(struct vpu_shared_addr *shared, + u32 instance, struct vpu_rpc_buffer_desc *desc); +u32 vpu_windsor_get_version(struct vpu_shared_addr *shared); +int vpu_windsor_set_encode_params(struct vpu_shared_addr *shared, + u32 instance, + struct vpu_encode_params *params, + u32 update); +int vpu_windsor_input_frame(struct vpu_shared_addr *shared, + struct vpu_inst *inst, struct vb2_buffer *vb); +u32 vpu_windsor_get_max_instance_count(struct vpu_shared_addr *shared); + +#endif diff --git a/drivers/media/platform/aspeed/Kconfig b/drivers/media/platform/aspeed/Kconfig new file mode 100644 index 000000000..c871eda33 --- /dev/null +++ b/drivers/media/platform/aspeed/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Aspeed media platform drivers" + +config VIDEO_ASPEED + tristate "Aspeed AST2400 and AST2500 Video Engine driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + help + Support for the Aspeed Video Engine (VE) embedded in the Aspeed + AST2400 and AST2500 SOCs. The VE can capture and compress video data + from digital or analog sources. diff --git a/drivers/media/platform/aspeed/Makefile b/drivers/media/platform/aspeed/Makefile new file mode 100644 index 000000000..1979af63d --- /dev/null +++ b/drivers/media/platform/aspeed/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_ASPEED) += aspeed-video.o diff --git a/drivers/media/platform/aspeed/aspeed-video.c b/drivers/media/platform/aspeed/aspeed-video.c new file mode 100644 index 000000000..20f795ccc --- /dev/null +++ b/drivers/media/platform/aspeed/aspeed-video.c @@ -0,0 +1,2051 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +// Copyright 2020 IBM Corp. +// Copyright (c) 2019-2020 Intel Corporation + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ASPEED_VIDEO_V4L2_MIN_BUF_REQ 3 + +#define DEVICE_NAME "aspeed-video" + +#define ASPEED_VIDEO_JPEG_NUM_QUALITIES 12 +#define ASPEED_VIDEO_JPEG_HEADER_SIZE 10 +#define ASPEED_VIDEO_JPEG_QUANT_SIZE 116 +#define ASPEED_VIDEO_JPEG_DCT_SIZE 34 + +#define MAX_FRAME_RATE 60 +#define MAX_HEIGHT 1200 +#define MAX_WIDTH 1920 +#define MIN_HEIGHT 480 +#define MIN_WIDTH 640 + +#define NUM_POLARITY_CHECKS 10 +#define INVALID_RESOLUTION_RETRIES 2 +#define INVALID_RESOLUTION_DELAY msecs_to_jiffies(250) +#define RESOLUTION_CHANGE_DELAY msecs_to_jiffies(500) +#define MODE_DETECT_TIMEOUT msecs_to_jiffies(500) +#define STOP_TIMEOUT msecs_to_jiffies(1000) +#define DIRECT_FETCH_THRESHOLD 0x0c0000 /* 1024 * 768 */ + +#define VE_MAX_SRC_BUFFER_SIZE 0x8ca000 /* 1920 * 1200, 32bpp */ +#define VE_JPEG_HEADER_SIZE 0x006000 /* 512 * 12 * 4 */ + +#define VE_PROTECTION_KEY 0x000 +#define VE_PROTECTION_KEY_UNLOCK 0x1a038aa8 + +#define VE_SEQ_CTRL 0x004 +#define VE_SEQ_CTRL_TRIG_MODE_DET BIT(0) +#define VE_SEQ_CTRL_TRIG_CAPTURE BIT(1) +#define VE_SEQ_CTRL_FORCE_IDLE BIT(2) +#define VE_SEQ_CTRL_MULT_FRAME BIT(3) +#define VE_SEQ_CTRL_TRIG_COMP BIT(4) +#define VE_SEQ_CTRL_AUTO_COMP BIT(5) +#define VE_SEQ_CTRL_EN_WATCHDOG BIT(7) +#define VE_SEQ_CTRL_YUV420 BIT(10) +#define VE_SEQ_CTRL_COMP_FMT GENMASK(11, 10) +#define VE_SEQ_CTRL_HALT BIT(12) +#define VE_SEQ_CTRL_EN_WATCHDOG_COMP BIT(14) +#define VE_SEQ_CTRL_TRIG_JPG BIT(15) +#define VE_SEQ_CTRL_CAP_BUSY BIT(16) +#define VE_SEQ_CTRL_COMP_BUSY BIT(18) + +#define AST2500_VE_SEQ_CTRL_JPEG_MODE BIT(13) +#define AST2400_VE_SEQ_CTRL_JPEG_MODE BIT(8) + +#define VE_CTRL 0x008 +#define VE_CTRL_HSYNC_POL BIT(0) +#define VE_CTRL_VSYNC_POL BIT(1) +#define VE_CTRL_SOURCE BIT(2) +#define VE_CTRL_INT_DE BIT(4) +#define VE_CTRL_DIRECT_FETCH BIT(5) +#define VE_CTRL_CAPTURE_FMT GENMASK(7, 6) +#define VE_CTRL_AUTO_OR_CURSOR BIT(8) +#define VE_CTRL_CLK_INVERSE BIT(11) +#define VE_CTRL_CLK_DELAY GENMASK(11, 9) +#define VE_CTRL_INTERLACE BIT(14) +#define VE_CTRL_HSYNC_POL_CTRL BIT(15) +#define VE_CTRL_FRC GENMASK(23, 16) + +#define VE_TGS_0 0x00c +#define VE_TGS_1 0x010 +#define VE_TGS_FIRST GENMASK(28, 16) +#define VE_TGS_LAST GENMASK(12, 0) + +#define VE_SCALING_FACTOR 0x014 +#define VE_SCALING_FILTER0 0x018 +#define VE_SCALING_FILTER1 0x01c +#define VE_SCALING_FILTER2 0x020 +#define VE_SCALING_FILTER3 0x024 + +#define VE_CAP_WINDOW 0x030 +#define VE_COMP_WINDOW 0x034 +#define VE_COMP_PROC_OFFSET 0x038 +#define VE_COMP_OFFSET 0x03c +#define VE_JPEG_ADDR 0x040 +#define VE_SRC0_ADDR 0x044 +#define VE_SRC_SCANLINE_OFFSET 0x048 +#define VE_SRC1_ADDR 0x04c +#define VE_COMP_ADDR 0x054 + +#define VE_STREAM_BUF_SIZE 0x058 +#define VE_STREAM_BUF_SIZE_N_PACKETS GENMASK(5, 3) +#define VE_STREAM_BUF_SIZE_P_SIZE GENMASK(2, 0) + +#define VE_COMP_CTRL 0x060 +#define VE_COMP_CTRL_VQ_DCT_ONLY BIT(0) +#define VE_COMP_CTRL_VQ_4COLOR BIT(1) +#define VE_COMP_CTRL_QUANTIZE BIT(2) +#define VE_COMP_CTRL_EN_BQ BIT(4) +#define VE_COMP_CTRL_EN_CRYPTO BIT(5) +#define VE_COMP_CTRL_DCT_CHR GENMASK(10, 6) +#define VE_COMP_CTRL_DCT_LUM GENMASK(15, 11) +#define VE_COMP_CTRL_EN_HQ BIT(16) +#define VE_COMP_CTRL_RSVD BIT(19) +#define VE_COMP_CTRL_ENCODE GENMASK(21, 20) +#define VE_COMP_CTRL_HQ_DCT_CHR GENMASK(26, 22) +#define VE_COMP_CTRL_HQ_DCT_LUM GENMASK(31, 27) + +#define AST2400_VE_COMP_SIZE_READ_BACK 0x078 +#define AST2600_VE_COMP_SIZE_READ_BACK 0x084 + +#define VE_SRC_LR_EDGE_DET 0x090 +#define VE_SRC_LR_EDGE_DET_LEFT GENMASK(11, 0) +#define VE_SRC_LR_EDGE_DET_NO_V BIT(12) +#define VE_SRC_LR_EDGE_DET_NO_H BIT(13) +#define VE_SRC_LR_EDGE_DET_NO_DISP BIT(14) +#define VE_SRC_LR_EDGE_DET_NO_CLK BIT(15) +#define VE_SRC_LR_EDGE_DET_RT GENMASK(27, 16) +#define VE_SRC_LR_EDGE_DET_INTERLACE BIT(31) + +#define VE_SRC_TB_EDGE_DET 0x094 +#define VE_SRC_TB_EDGE_DET_TOP GENMASK(12, 0) +#define VE_SRC_TB_EDGE_DET_BOT GENMASK(28, 16) + +#define VE_MODE_DETECT_STATUS 0x098 +#define VE_MODE_DETECT_H_PERIOD GENMASK(11, 0) +#define VE_MODE_DETECT_EXTSRC_ADC BIT(12) +#define VE_MODE_DETECT_H_STABLE BIT(13) +#define VE_MODE_DETECT_V_STABLE BIT(14) +#define VE_MODE_DETECT_V_LINES GENMASK(27, 16) +#define VE_MODE_DETECT_STATUS_VSYNC BIT(28) +#define VE_MODE_DETECT_STATUS_HSYNC BIT(29) +#define VE_MODE_DETECT_VSYNC_RDY BIT(30) +#define VE_MODE_DETECT_HSYNC_RDY BIT(31) + +#define VE_SYNC_STATUS 0x09c +#define VE_SYNC_STATUS_HSYNC GENMASK(11, 0) +#define VE_SYNC_STATUS_VSYNC GENMASK(27, 16) + +#define VE_H_TOTAL_PIXELS 0x0A0 + +#define VE_INTERRUPT_CTRL 0x304 +#define VE_INTERRUPT_STATUS 0x308 +#define VE_INTERRUPT_MODE_DETECT_WD BIT(0) +#define VE_INTERRUPT_CAPTURE_COMPLETE BIT(1) +#define VE_INTERRUPT_COMP_READY BIT(2) +#define VE_INTERRUPT_COMP_COMPLETE BIT(3) +#define VE_INTERRUPT_MODE_DETECT BIT(4) +#define VE_INTERRUPT_FRAME_COMPLETE BIT(5) +#define VE_INTERRUPT_DECODE_ERR BIT(6) +#define VE_INTERRUPT_HALT_READY BIT(8) +#define VE_INTERRUPT_HANG_WD BIT(9) +#define VE_INTERRUPT_STREAM_DESC BIT(10) +#define VE_INTERRUPT_VSYNC_DESC BIT(11) + +#define VE_MODE_DETECT 0x30c +#define VE_MODE_DT_HOR_TOLER GENMASK(31, 28) +#define VE_MODE_DT_VER_TOLER GENMASK(27, 24) +#define VE_MODE_DT_HOR_STABLE GENMASK(23, 20) +#define VE_MODE_DT_VER_STABLE GENMASK(19, 16) +#define VE_MODE_DT_EDG_THROD GENMASK(15, 8) + +#define VE_MEM_RESTRICT_START 0x310 +#define VE_MEM_RESTRICT_END 0x314 + +/* + * VIDEO_MODE_DETECT_DONE: a flag raised if signal lock + * VIDEO_RES_CHANGE: a flag raised if res_change work on-going + * VIDEO_RES_DETECT: a flag raised if res. detection on-going + * VIDEO_STREAMING: a flag raised if user requires stream-on + * VIDEO_FRAME_INPRG: a flag raised if hw working on a frame + * VIDEO_STOPPED: a flag raised if device release + * VIDEO_CLOCKS_ON: a flag raised if clk is on + */ +enum { + VIDEO_MODE_DETECT_DONE, + VIDEO_RES_CHANGE, + VIDEO_RES_DETECT, + VIDEO_STREAMING, + VIDEO_FRAME_INPRG, + VIDEO_STOPPED, + VIDEO_CLOCKS_ON, +}; + +// for VE_CTRL_CAPTURE_FMT +enum aspeed_video_capture_format { + VIDEO_CAP_FMT_YUV_STUDIO_SWING = 0, + VIDEO_CAP_FMT_YUV_FULL_SWING, + VIDEO_CAP_FMT_RGB, + VIDEO_CAP_FMT_GRAY, + VIDEO_CAP_FMT_MAX +}; + +struct aspeed_video_addr { + unsigned int size; + dma_addr_t dma; + void *virt; +}; + +struct aspeed_video_buffer { + struct vb2_v4l2_buffer vb; + struct list_head link; +}; + +struct aspeed_video_perf { + ktime_t last_sample; + u32 totaltime; + u32 duration; + u32 duration_min; + u32 duration_max; +}; + +#define to_aspeed_video_buffer(x) \ + container_of((x), struct aspeed_video_buffer, vb) + +/* + * struct aspeed_video - driver data + * + * res_work: holds the delayed_work for res-detection if unlock + * buffers: holds the list of buffer queued from user + * flags: holds the state of video + * sequence: holds the last number of frame completed + * max_compressed_size: holds max compressed stream's size + * srcs: holds the buffer information for srcs + * jpeg: holds the buffer information for jpeg header + * yuv420: a flag raised if JPEG subsampling is 420 + * frame_rate: holds the frame_rate + * jpeg_quality: holds jpeq's quality (0~11) + * frame_bottom: end position of video data in vertical direction + * frame_left: start position of video data in horizontal direction + * frame_right: end position of video data in horizontal direction + * frame_top: start position of video data in vertical direction + * perf: holds the statistics primary for debugfs + */ +struct aspeed_video { + void __iomem *base; + struct clk *eclk; + struct clk *vclk; + + struct device *dev; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_device v4l2_dev; + struct v4l2_pix_format pix_fmt; + struct v4l2_bt_timings active_timings; + struct v4l2_bt_timings detected_timings; + u32 v4l2_input_status; + struct vb2_queue queue; + struct video_device vdev; + struct mutex video_lock; /* v4l2 and videobuf2 lock */ + + u32 jpeg_mode; + u32 comp_size_read; + + wait_queue_head_t wait; + spinlock_t lock; /* buffer list lock */ + struct delayed_work res_work; + struct list_head buffers; + unsigned long flags; + unsigned int sequence; + + unsigned int max_compressed_size; + struct aspeed_video_addr srcs[2]; + struct aspeed_video_addr jpeg; + + bool yuv420; + unsigned int frame_rate; + unsigned int jpeg_quality; + + unsigned int frame_bottom; + unsigned int frame_left; + unsigned int frame_right; + unsigned int frame_top; + + struct aspeed_video_perf perf; +}; + +#define to_aspeed_video(x) container_of((x), struct aspeed_video, v4l2_dev) + +struct aspeed_video_config { + u32 jpeg_mode; + u32 comp_size_read; +}; + +static const struct aspeed_video_config ast2400_config = { + .jpeg_mode = AST2400_VE_SEQ_CTRL_JPEG_MODE, + .comp_size_read = AST2400_VE_COMP_SIZE_READ_BACK, +}; + +static const struct aspeed_video_config ast2500_config = { + .jpeg_mode = AST2500_VE_SEQ_CTRL_JPEG_MODE, + .comp_size_read = AST2400_VE_COMP_SIZE_READ_BACK, +}; + +static const struct aspeed_video_config ast2600_config = { + .jpeg_mode = AST2500_VE_SEQ_CTRL_JPEG_MODE, + .comp_size_read = AST2600_VE_COMP_SIZE_READ_BACK, +}; + +static const u32 aspeed_video_jpeg_header[ASPEED_VIDEO_JPEG_HEADER_SIZE] = { + 0xe0ffd8ff, 0x464a1000, 0x01004649, 0x60000101, 0x00006000, 0x0f00feff, + 0x00002d05, 0x00000000, 0x00000000, 0x00dbff00 +}; + +static const u32 aspeed_video_jpeg_quant[ASPEED_VIDEO_JPEG_QUANT_SIZE] = { + 0x081100c0, 0x00000000, 0x00110103, 0x03011102, 0xc4ff0111, 0x00001f00, + 0x01010501, 0x01010101, 0x00000000, 0x00000000, 0x04030201, 0x08070605, + 0xff0b0a09, 0x10b500c4, 0x03010200, 0x03040203, 0x04040505, 0x7d010000, + 0x00030201, 0x12051104, 0x06413121, 0x07615113, 0x32147122, 0x08a19181, + 0xc1b14223, 0xf0d15215, 0x72623324, 0x160a0982, 0x1a191817, 0x28272625, + 0x35342a29, 0x39383736, 0x4544433a, 0x49484746, 0x5554534a, 0x59585756, + 0x6564635a, 0x69686766, 0x7574736a, 0x79787776, 0x8584837a, 0x89888786, + 0x9493928a, 0x98979695, 0xa3a29a99, 0xa7a6a5a4, 0xb2aaa9a8, 0xb6b5b4b3, + 0xbab9b8b7, 0xc5c4c3c2, 0xc9c8c7c6, 0xd4d3d2ca, 0xd8d7d6d5, 0xe2e1dad9, + 0xe6e5e4e3, 0xeae9e8e7, 0xf4f3f2f1, 0xf8f7f6f5, 0xc4fffaf9, 0x00011f00, + 0x01010103, 0x01010101, 0x00000101, 0x00000000, 0x04030201, 0x08070605, + 0xff0b0a09, 0x11b500c4, 0x02010200, 0x04030404, 0x04040507, 0x77020100, + 0x03020100, 0x21050411, 0x41120631, 0x71610751, 0x81322213, 0x91421408, + 0x09c1b1a1, 0xf0523323, 0xd1726215, 0x3424160a, 0x17f125e1, 0x261a1918, + 0x2a292827, 0x38373635, 0x44433a39, 0x48474645, 0x54534a49, 0x58575655, + 0x64635a59, 0x68676665, 0x74736a69, 0x78777675, 0x83827a79, 0x87868584, + 0x928a8988, 0x96959493, 0x9a999897, 0xa5a4a3a2, 0xa9a8a7a6, 0xb4b3b2aa, + 0xb8b7b6b5, 0xc3c2bab9, 0xc7c6c5c4, 0xd2cac9c8, 0xd6d5d4d3, 0xdad9d8d7, + 0xe5e4e3e2, 0xe9e8e7e6, 0xf4f3f2ea, 0xf8f7f6f5, 0xdafffaf9, 0x01030c00, + 0x03110200, 0x003f0011 +}; + +static const u32 aspeed_video_jpeg_dct[ASPEED_VIDEO_JPEG_NUM_QUALITIES] + [ASPEED_VIDEO_JPEG_DCT_SIZE] = { + { 0x0d140043, 0x0c0f110f, 0x11101114, 0x17141516, 0x1e20321e, + 0x3d1e1b1b, 0x32242e2b, 0x4b4c3f48, 0x44463f47, 0x61735a50, + 0x566c5550, 0x88644644, 0x7a766c65, 0x4d808280, 0x8c978d60, + 0x7e73967d, 0xdbff7b80, 0x1f014300, 0x272d2121, 0x3030582d, + 0x697bb958, 0xb8b9b97b, 0xb9b8a6a6, 0xb9b9b9b9, 0xb9b9b9b9, + 0xb9b9b9b9, 0xb9b9b9b9, 0xb9b9b9b9, 0xb9b9b9b9, 0xb9b9b9b9, + 0xb9b9b9b9, 0xb9b9b9b9, 0xb9b9b9b9, 0xffb9b9b9 }, + { 0x0c110043, 0x0a0d0f0d, 0x0f0e0f11, 0x14111213, 0x1a1c2b1a, + 0x351a1818, 0x2b1f2826, 0x4142373f, 0x3c3d373e, 0x55644e46, + 0x4b5f4a46, 0x77573d3c, 0x6b675f58, 0x43707170, 0x7a847b54, + 0x6e64836d, 0xdbff6c70, 0x1b014300, 0x22271d1d, 0x2a2a4c27, + 0x5b6ba04c, 0xa0a0a06b, 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, + 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, + 0xa0a0a0a0, 0xa0a0a0a0, 0xa0a0a0a0, 0xffa0a0a0 }, + { 0x090e0043, 0x090a0c0a, 0x0c0b0c0e, 0x110e0f10, 0x15172415, + 0x2c151313, 0x241a211f, 0x36372e34, 0x31322e33, 0x4653413a, + 0x3e4e3d3a, 0x62483231, 0x58564e49, 0x385d5e5d, 0x656d6645, + 0x5b536c5a, 0xdbff595d, 0x16014300, 0x1c201818, 0x22223f20, + 0x4b58853f, 0x85858558, 0x85858585, 0x85858585, 0x85858585, + 0x85858585, 0x85858585, 0x85858585, 0x85858585, 0x85858585, + 0x85858585, 0x85858585, 0x85858585, 0xff858585 }, + { 0x070b0043, 0x07080a08, 0x0a090a0b, 0x0d0b0c0c, 0x11121c11, + 0x23110f0f, 0x1c141a19, 0x2b2b2429, 0x27282428, 0x3842332e, + 0x313e302e, 0x4e392827, 0x46443e3a, 0x2c4a4a4a, 0x50565137, + 0x48425647, 0xdbff474a, 0x12014300, 0x161a1313, 0x1c1c331a, + 0x3d486c33, 0x6c6c6c48, 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, + 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, + 0x6c6c6c6c, 0x6c6c6c6c, 0x6c6c6c6c, 0xff6c6c6c }, + { 0x06090043, 0x05060706, 0x07070709, 0x0a09090a, 0x0d0e160d, + 0x1b0d0c0c, 0x16101413, 0x21221c20, 0x1e1f1c20, 0x2b332824, + 0x26302624, 0x3d2d1f1e, 0x3735302d, 0x22393a39, 0x3f443f2b, + 0x38334338, 0xdbff3739, 0x0d014300, 0x11130e0e, 0x15152613, + 0x2d355026, 0x50505035, 0x50505050, 0x50505050, 0x50505050, + 0x50505050, 0x50505050, 0x50505050, 0x50505050, 0x50505050, + 0x50505050, 0x50505050, 0x50505050, 0xff505050 }, + { 0x04060043, 0x03040504, 0x05040506, 0x07060606, 0x09090f09, + 0x12090808, 0x0f0a0d0d, 0x16161315, 0x14151315, 0x1d221b18, + 0x19201918, 0x281e1514, 0x2423201e, 0x17262726, 0x2a2d2a1c, + 0x25222d25, 0xdbff2526, 0x09014300, 0x0b0d0a0a, 0x0e0e1a0d, + 0x1f25371a, 0x37373725, 0x37373737, 0x37373737, 0x37373737, + 0x37373737, 0x37373737, 0x37373737, 0x37373737, 0x37373737, + 0x37373737, 0x37373737, 0x37373737, 0xff373737 }, + { 0x02030043, 0x01020202, 0x02020203, 0x03030303, 0x04040704, + 0x09040404, 0x07050606, 0x0b0b090a, 0x0a0a090a, 0x0e110d0c, + 0x0c100c0c, 0x140f0a0a, 0x1211100f, 0x0b131313, 0x1516150e, + 0x12111612, 0xdbff1213, 0x04014300, 0x05060505, 0x07070d06, + 0x0f121b0d, 0x1b1b1b12, 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, + 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, + 0x1b1b1b1b, 0x1b1b1b1b, 0x1b1b1b1b, 0xff1b1b1b }, + { 0x01020043, 0x01010101, 0x01010102, 0x02020202, 0x03030503, + 0x06030202, 0x05030404, 0x07070607, 0x06070607, 0x090b0908, + 0x080a0808, 0x0d0a0706, 0x0c0b0a0a, 0x070c0d0c, 0x0e0f0e09, + 0x0c0b0f0c, 0xdbff0c0c, 0x03014300, 0x03040303, 0x04040804, + 0x0a0c1208, 0x1212120c, 0x12121212, 0x12121212, 0x12121212, + 0x12121212, 0x12121212, 0x12121212, 0x12121212, 0x12121212, + 0x12121212, 0x12121212, 0x12121212, 0xff121212 }, + { 0x01020043, 0x01010101, 0x01010102, 0x02020202, 0x03030503, + 0x06030202, 0x05030404, 0x07070607, 0x06070607, 0x090b0908, + 0x080a0808, 0x0d0a0706, 0x0c0b0a0a, 0x070c0d0c, 0x0e0f0e09, + 0x0c0b0f0c, 0xdbff0c0c, 0x02014300, 0x03030202, 0x04040703, + 0x080a0f07, 0x0f0f0f0a, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, + 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, + 0x0f0f0f0f, 0x0f0f0f0f, 0x0f0f0f0f, 0xff0f0f0f }, + { 0x01010043, 0x01010101, 0x01010101, 0x01010101, 0x02020302, + 0x04020202, 0x03020303, 0x05050405, 0x05050405, 0x07080606, + 0x06080606, 0x0a070505, 0x09080807, 0x05090909, 0x0a0b0a07, + 0x09080b09, 0xdbff0909, 0x02014300, 0x02030202, 0x03030503, + 0x07080c05, 0x0c0c0c08, 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, + 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, + 0x0c0c0c0c, 0x0c0c0c0c, 0x0c0c0c0c, 0xff0c0c0c }, + { 0x01010043, 0x01010101, 0x01010101, 0x01010101, 0x01010201, + 0x03010101, 0x02010202, 0x03030303, 0x03030303, 0x04050404, + 0x04050404, 0x06050303, 0x06050505, 0x03060606, 0x07070704, + 0x06050706, 0xdbff0606, 0x01014300, 0x01020101, 0x02020402, + 0x05060904, 0x09090906, 0x09090909, 0x09090909, 0x09090909, + 0x09090909, 0x09090909, 0x09090909, 0x09090909, 0x09090909, + 0x09090909, 0x09090909, 0x09090909, 0xff090909 }, + { 0x01010043, 0x01010101, 0x01010101, 0x01010101, 0x01010101, + 0x01010101, 0x01010101, 0x01010101, 0x01010101, 0x02020202, + 0x02020202, 0x03020101, 0x03020202, 0x01030303, 0x03030302, + 0x03020303, 0xdbff0403, 0x01014300, 0x01010101, 0x01010201, + 0x03040602, 0x06060604, 0x06060606, 0x06060606, 0x06060606, + 0x06060606, 0x06060606, 0x06060606, 0x06060606, 0x06060606, + 0x06060606, 0x06060606, 0x06060606, 0xff060606 } +}; + +static const struct v4l2_dv_timings_cap aspeed_video_timings_cap = { + .type = V4L2_DV_BT_656_1120, + .bt = { + .min_width = MIN_WIDTH, + .max_width = MAX_WIDTH, + .min_height = MIN_HEIGHT, + .max_height = MAX_HEIGHT, + .min_pixelclock = 6574080, /* 640 x 480 x 24Hz */ + .max_pixelclock = 138240000, /* 1920 x 1200 x 60Hz */ + .standards = V4L2_DV_BT_STD_CEA861 | V4L2_DV_BT_STD_DMT | + V4L2_DV_BT_STD_CVT | V4L2_DV_BT_STD_GTF, + .capabilities = V4L2_DV_BT_CAP_PROGRESSIVE | + V4L2_DV_BT_CAP_REDUCED_BLANKING | + V4L2_DV_BT_CAP_CUSTOM, + }, +}; + +static unsigned int debug; + +static void aspeed_video_init_jpeg_table(u32 *table, bool yuv420) +{ + int i; + unsigned int base; + + for (i = 0; i < ASPEED_VIDEO_JPEG_NUM_QUALITIES; i++) { + base = 256 * i; /* AST HW requires this header spacing */ + memcpy(&table[base], aspeed_video_jpeg_header, + sizeof(aspeed_video_jpeg_header)); + + base += ASPEED_VIDEO_JPEG_HEADER_SIZE; + memcpy(&table[base], aspeed_video_jpeg_dct[i], + sizeof(aspeed_video_jpeg_dct[i])); + + base += ASPEED_VIDEO_JPEG_DCT_SIZE; + memcpy(&table[base], aspeed_video_jpeg_quant, + sizeof(aspeed_video_jpeg_quant)); + + if (yuv420) + table[base + 2] = 0x00220103; + } +} + +// just update jpeg dct table per 420/444 +static void aspeed_video_update_jpeg_table(u32 *table, bool yuv420) +{ + int i; + unsigned int base; + + for (i = 0; i < ASPEED_VIDEO_JPEG_NUM_QUALITIES; i++) { + base = 256 * i; /* AST HW requires this header spacing */ + base += ASPEED_VIDEO_JPEG_HEADER_SIZE + + ASPEED_VIDEO_JPEG_DCT_SIZE; + + table[base + 2] = (yuv420) ? 0x00220103 : 0x00110103; + } +} + +static void aspeed_video_update(struct aspeed_video *video, u32 reg, u32 clear, + u32 bits) +{ + u32 t = readl(video->base + reg); + u32 before = t; + + t &= ~clear; + t |= bits; + writel(t, video->base + reg); + v4l2_dbg(3, debug, &video->v4l2_dev, "update %03x[%08x -> %08x]\n", + reg, before, readl(video->base + reg)); +} + +static u32 aspeed_video_read(struct aspeed_video *video, u32 reg) +{ + u32 t = readl(video->base + reg); + + v4l2_dbg(3, debug, &video->v4l2_dev, "read %03x[%08x]\n", reg, t); + return t; +} + +static void aspeed_video_write(struct aspeed_video *video, u32 reg, u32 val) +{ + writel(val, video->base + reg); + v4l2_dbg(3, debug, &video->v4l2_dev, "write %03x[%08x]\n", reg, + readl(video->base + reg)); +} + +static void update_perf(struct aspeed_video_perf *p) +{ + struct aspeed_video *v = container_of(p, struct aspeed_video, + perf); + + p->duration = + ktime_to_ms(ktime_sub(ktime_get(), p->last_sample)); + p->totaltime += p->duration; + + p->duration_max = max(p->duration, p->duration_max); + p->duration_min = min(p->duration, p->duration_min); + v4l2_dbg(2, debug, &v->v4l2_dev, "time consumed: %d ms\n", + p->duration); +} + +static int aspeed_video_start_frame(struct aspeed_video *video) +{ + dma_addr_t addr; + unsigned long flags; + struct aspeed_video_buffer *buf; + u32 seq_ctrl = aspeed_video_read(video, VE_SEQ_CTRL); + + if (video->v4l2_input_status) { + v4l2_warn(&video->v4l2_dev, "No signal; don't start frame\n"); + return 0; + } + + if (!(seq_ctrl & VE_SEQ_CTRL_COMP_BUSY) || + !(seq_ctrl & VE_SEQ_CTRL_CAP_BUSY)) { + v4l2_warn(&video->v4l2_dev, "Engine busy; don't start frame\n"); + return -EBUSY; + } + + spin_lock_irqsave(&video->lock, flags); + buf = list_first_entry_or_null(&video->buffers, + struct aspeed_video_buffer, link); + if (!buf) { + spin_unlock_irqrestore(&video->lock, flags); + v4l2_warn(&video->v4l2_dev, "No buffers; don't start frame\n"); + return -EPROTO; + } + + set_bit(VIDEO_FRAME_INPRG, &video->flags); + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + spin_unlock_irqrestore(&video->lock, flags); + + aspeed_video_write(video, VE_COMP_PROC_OFFSET, 0); + aspeed_video_write(video, VE_COMP_OFFSET, 0); + aspeed_video_write(video, VE_COMP_ADDR, addr); + + aspeed_video_update(video, VE_INTERRUPT_CTRL, 0, + VE_INTERRUPT_COMP_COMPLETE); + + video->perf.last_sample = ktime_get(); + + aspeed_video_update(video, VE_SEQ_CTRL, 0, + VE_SEQ_CTRL_TRIG_CAPTURE | VE_SEQ_CTRL_TRIG_COMP); + + return 0; +} + +static void aspeed_video_enable_mode_detect(struct aspeed_video *video) +{ + /* Enable mode detect interrupts */ + aspeed_video_update(video, VE_INTERRUPT_CTRL, 0, + VE_INTERRUPT_MODE_DETECT); + + /* Disable mode detect in order to re-trigger */ + aspeed_video_update(video, VE_SEQ_CTRL, + VE_SEQ_CTRL_TRIG_MODE_DET, 0); + + /* Trigger mode detect */ + aspeed_video_update(video, VE_SEQ_CTRL, 0, VE_SEQ_CTRL_TRIG_MODE_DET); +} + +static void aspeed_video_off(struct aspeed_video *video) +{ + if (!test_bit(VIDEO_CLOCKS_ON, &video->flags)) + return; + + /* Disable interrupts */ + aspeed_video_write(video, VE_INTERRUPT_CTRL, 0); + aspeed_video_write(video, VE_INTERRUPT_STATUS, 0xffffffff); + + /* Turn off the relevant clocks */ + clk_disable(video->eclk); + clk_disable(video->vclk); + + clear_bit(VIDEO_CLOCKS_ON, &video->flags); +} + +static void aspeed_video_on(struct aspeed_video *video) +{ + if (test_bit(VIDEO_CLOCKS_ON, &video->flags)) + return; + + /* Turn on the relevant clocks */ + clk_enable(video->vclk); + clk_enable(video->eclk); + + set_bit(VIDEO_CLOCKS_ON, &video->flags); +} + +static void aspeed_video_bufs_done(struct aspeed_video *video, + enum vb2_buffer_state state) +{ + unsigned long flags; + struct aspeed_video_buffer *buf; + + spin_lock_irqsave(&video->lock, flags); + list_for_each_entry(buf, &video->buffers, link) + vb2_buffer_done(&buf->vb.vb2_buf, state); + INIT_LIST_HEAD(&video->buffers); + spin_unlock_irqrestore(&video->lock, flags); +} + +static void aspeed_video_irq_res_change(struct aspeed_video *video, ulong delay) +{ + v4l2_dbg(1, debug, &video->v4l2_dev, "Resolution changed; resetting\n"); + + set_bit(VIDEO_RES_CHANGE, &video->flags); + clear_bit(VIDEO_FRAME_INPRG, &video->flags); + + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + + aspeed_video_off(video); + aspeed_video_bufs_done(video, VB2_BUF_STATE_ERROR); + + schedule_delayed_work(&video->res_work, delay); +} + +static irqreturn_t aspeed_video_irq(int irq, void *arg) +{ + struct aspeed_video *video = arg; + u32 sts = aspeed_video_read(video, VE_INTERRUPT_STATUS); + + /* + * Hardware sometimes asserts interrupts that we haven't actually + * enabled; ignore them if so. + */ + sts &= aspeed_video_read(video, VE_INTERRUPT_CTRL); + + v4l2_dbg(2, debug, &video->v4l2_dev, "irq sts=%#x %s%s%s%s\n", sts, + sts & VE_INTERRUPT_MODE_DETECT_WD ? ", unlock" : "", + sts & VE_INTERRUPT_MODE_DETECT ? ", lock" : "", + sts & VE_INTERRUPT_CAPTURE_COMPLETE ? ", capture-done" : "", + sts & VE_INTERRUPT_COMP_COMPLETE ? ", comp-done" : ""); + + /* + * Resolution changed or signal was lost; reset the engine and + * re-initialize + */ + if (sts & VE_INTERRUPT_MODE_DETECT_WD) { + aspeed_video_irq_res_change(video, 0); + return IRQ_HANDLED; + } + + if (sts & VE_INTERRUPT_MODE_DETECT) { + if (test_bit(VIDEO_RES_DETECT, &video->flags)) { + aspeed_video_update(video, VE_INTERRUPT_CTRL, + VE_INTERRUPT_MODE_DETECT, 0); + aspeed_video_write(video, VE_INTERRUPT_STATUS, + VE_INTERRUPT_MODE_DETECT); + sts &= ~VE_INTERRUPT_MODE_DETECT; + set_bit(VIDEO_MODE_DETECT_DONE, &video->flags); + wake_up_interruptible_all(&video->wait); + } else { + /* + * Signal acquired while NOT doing resolution + * detection; reset the engine and re-initialize + */ + aspeed_video_irq_res_change(video, + RESOLUTION_CHANGE_DELAY); + return IRQ_HANDLED; + } + } + + if (sts & VE_INTERRUPT_COMP_COMPLETE) { + struct aspeed_video_buffer *buf; + u32 frame_size = aspeed_video_read(video, + video->comp_size_read); + + update_perf(&video->perf); + + spin_lock(&video->lock); + clear_bit(VIDEO_FRAME_INPRG, &video->flags); + buf = list_first_entry_or_null(&video->buffers, + struct aspeed_video_buffer, + link); + if (buf) { + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, frame_size); + + if (!list_is_last(&buf->link, &video->buffers)) { + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.sequence = video->sequence++; + buf->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(&buf->vb.vb2_buf, + VB2_BUF_STATE_DONE); + list_del(&buf->link); + } + } + spin_unlock(&video->lock); + + aspeed_video_update(video, VE_SEQ_CTRL, + VE_SEQ_CTRL_TRIG_CAPTURE | + VE_SEQ_CTRL_FORCE_IDLE | + VE_SEQ_CTRL_TRIG_COMP, 0); + aspeed_video_update(video, VE_INTERRUPT_CTRL, + VE_INTERRUPT_COMP_COMPLETE, 0); + aspeed_video_write(video, VE_INTERRUPT_STATUS, + VE_INTERRUPT_COMP_COMPLETE); + sts &= ~VE_INTERRUPT_COMP_COMPLETE; + if (test_bit(VIDEO_STREAMING, &video->flags) && buf) + aspeed_video_start_frame(video); + } + + return sts ? IRQ_NONE : IRQ_HANDLED; +} + +static void aspeed_video_check_and_set_polarity(struct aspeed_video *video) +{ + int i; + int hsync_counter = 0; + int vsync_counter = 0; + u32 sts, ctrl; + + for (i = 0; i < NUM_POLARITY_CHECKS; ++i) { + sts = aspeed_video_read(video, VE_MODE_DETECT_STATUS); + if (sts & VE_MODE_DETECT_STATUS_VSYNC) + vsync_counter--; + else + vsync_counter++; + + if (sts & VE_MODE_DETECT_STATUS_HSYNC) + hsync_counter--; + else + hsync_counter++; + } + + ctrl = aspeed_video_read(video, VE_CTRL); + + if (hsync_counter < 0) { + ctrl |= VE_CTRL_HSYNC_POL; + video->detected_timings.polarities &= + ~V4L2_DV_HSYNC_POS_POL; + } else { + ctrl &= ~VE_CTRL_HSYNC_POL; + video->detected_timings.polarities |= + V4L2_DV_HSYNC_POS_POL; + } + + if (vsync_counter < 0) { + ctrl |= VE_CTRL_VSYNC_POL; + video->detected_timings.polarities &= + ~V4L2_DV_VSYNC_POS_POL; + } else { + ctrl &= ~VE_CTRL_VSYNC_POL; + video->detected_timings.polarities |= + V4L2_DV_VSYNC_POS_POL; + } + + aspeed_video_write(video, VE_CTRL, ctrl); +} + +static bool aspeed_video_alloc_buf(struct aspeed_video *video, + struct aspeed_video_addr *addr, + unsigned int size) +{ + addr->virt = dma_alloc_coherent(video->dev, size, &addr->dma, + GFP_KERNEL); + if (!addr->virt) + return false; + + addr->size = size; + return true; +} + +static void aspeed_video_free_buf(struct aspeed_video *video, + struct aspeed_video_addr *addr) +{ + dma_free_coherent(video->dev, addr->size, addr->virt, addr->dma); + addr->size = 0; + addr->dma = 0ULL; + addr->virt = NULL; +} + +/* + * Get the minimum HW-supported compression buffer size for the frame size. + * Assume worst-case JPEG compression size is 1/8 raw size. This should be + * plenty even for maximum quality; any worse and the engine will simply return + * incomplete JPEGs. + */ +static void aspeed_video_calc_compressed_size(struct aspeed_video *video, + unsigned int frame_size) +{ + int i, j; + u32 compression_buffer_size_reg = 0; + unsigned int size; + const unsigned int num_compression_packets = 4; + const unsigned int compression_packet_size = 1024; + const unsigned int max_compressed_size = frame_size / 2; /* 4bpp / 8 */ + + video->max_compressed_size = UINT_MAX; + + for (i = 0; i < 6; ++i) { + for (j = 0; j < 8; ++j) { + size = (num_compression_packets << i) * + (compression_packet_size << j); + if (size < max_compressed_size) + continue; + + if (size < video->max_compressed_size) { + compression_buffer_size_reg = (i << 3) | j; + video->max_compressed_size = size; + } + } + } + + aspeed_video_write(video, VE_STREAM_BUF_SIZE, + compression_buffer_size_reg); + + v4l2_dbg(1, debug, &video->v4l2_dev, "Max compressed size: %#x\n", + video->max_compressed_size); +} + +/* + * Update v4l2_bt_timings per current status. + * frame_top/frame_bottom/frame_left/frame_right need to be ready. + * + * The following registers start counting from sync's rising edge: + * 1. VR090: frame edge's left and right + * 2. VR094: frame edge's top and bottom + * 3. VR09C: counting from sync's rising edge to falling edge + * + * [Vertical timing] + * +--+ +-------------------+ +--+ + * | | | v i d e o | | | + * +--+ +-----+ +-----+ +---+ + * vsync+--+ + * frame_top+--------+ + * frame_bottom+----------------------------+ + * + * +-------------------+ + * | v i d e o | + * +--+ +-----+ +-----+ +---+ + * | | | | + * +--+ +--+ + * vsync+-------------------------------+ + * frame_top+-----+ + * frame_bottom+-------------------------+ + * + * [Horizontal timing] + * +--+ +-------------------+ +--+ + * | | | v i d e o | | | + * +--+ +-----+ +-----+ +---+ + * hsync+--+ + * frame_left+--------+ + * frame_right+----------------------------+ + * + * +-------------------+ + * | v i d e o | + * +--+ +-----+ +-----+ +---+ + * | | | | + * +--+ +--+ + * hsync+-------------------------------+ + * frame_left+-----+ + * frame_right+-------------------------+ + * + * @v: the struct of aspeed_video + * @det: v4l2_bt_timings to be updated. + */ +static void aspeed_video_get_timings(struct aspeed_video *v, + struct v4l2_bt_timings *det) +{ + u32 mds, sync, htotal, vtotal, vsync, hsync; + + mds = aspeed_video_read(v, VE_MODE_DETECT_STATUS); + sync = aspeed_video_read(v, VE_SYNC_STATUS); + htotal = aspeed_video_read(v, VE_H_TOTAL_PIXELS); + vtotal = FIELD_GET(VE_MODE_DETECT_V_LINES, mds); + vsync = FIELD_GET(VE_SYNC_STATUS_VSYNC, sync); + hsync = FIELD_GET(VE_SYNC_STATUS_HSYNC, sync); + + /* + * This is a workaround for polarity detection. + * Because ast-soc counts sync from sync's rising edge, the reg value + * of sync would be larger than video's active area if negative. + */ + if (vsync > det->height) + det->polarities &= ~V4L2_DV_VSYNC_POS_POL; + else + det->polarities |= V4L2_DV_VSYNC_POS_POL; + if (hsync > det->width) + det->polarities &= ~V4L2_DV_HSYNC_POS_POL; + else + det->polarities |= V4L2_DV_HSYNC_POS_POL; + + if (det->polarities & V4L2_DV_VSYNC_POS_POL) { + det->vbackporch = v->frame_top - vsync; + det->vfrontporch = vtotal - v->frame_bottom; + det->vsync = vsync; + } else { + det->vbackporch = v->frame_top; + det->vfrontporch = vsync - v->frame_bottom; + det->vsync = vtotal - vsync; + } + + if (det->polarities & V4L2_DV_HSYNC_POS_POL) { + det->hbackporch = v->frame_left - hsync; + det->hfrontporch = htotal - v->frame_right; + det->hsync = hsync; + } else { + det->hbackporch = v->frame_left; + det->hfrontporch = hsync - v->frame_right; + det->hsync = htotal - hsync; + } +} + +#define res_check(v) test_and_clear_bit(VIDEO_MODE_DETECT_DONE, &(v)->flags) + +static void aspeed_video_get_resolution(struct aspeed_video *video) +{ + bool invalid_resolution = true; + int rc; + int tries = 0; + u32 mds; + u32 src_lr_edge; + u32 src_tb_edge; + struct v4l2_bt_timings *det = &video->detected_timings; + + det->width = MIN_WIDTH; + det->height = MIN_HEIGHT; + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + memset(&video->perf, 0, sizeof(video->perf)); + + do { + if (tries) { + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(INVALID_RESOLUTION_DELAY)) + return; + } + + set_bit(VIDEO_RES_DETECT, &video->flags); + aspeed_video_update(video, VE_CTRL, + VE_CTRL_VSYNC_POL | VE_CTRL_HSYNC_POL, 0); + aspeed_video_enable_mode_detect(video); + + rc = wait_event_interruptible_timeout(video->wait, + res_check(video), + MODE_DETECT_TIMEOUT); + if (!rc) { + v4l2_warn(&video->v4l2_dev, "Timed out; first mode detect\n"); + clear_bit(VIDEO_RES_DETECT, &video->flags); + return; + } + + mds = aspeed_video_read(video, VE_MODE_DETECT_STATUS); + // try detection again if current signal isn't stable + if (!(mds & VE_MODE_DETECT_H_STABLE) || + !(mds & VE_MODE_DETECT_V_STABLE) || + (mds & VE_MODE_DETECT_EXTSRC_ADC)) + continue; + + aspeed_video_check_and_set_polarity(video); + + aspeed_video_enable_mode_detect(video); + + rc = wait_event_interruptible_timeout(video->wait, + res_check(video), + MODE_DETECT_TIMEOUT); + clear_bit(VIDEO_RES_DETECT, &video->flags); + if (!rc) { + v4l2_warn(&video->v4l2_dev, "Timed out; second mode detect\n"); + return; + } + + src_lr_edge = aspeed_video_read(video, VE_SRC_LR_EDGE_DET); + src_tb_edge = aspeed_video_read(video, VE_SRC_TB_EDGE_DET); + + video->frame_bottom = FIELD_GET(VE_SRC_TB_EDGE_DET_BOT, src_tb_edge); + video->frame_top = FIELD_GET(VE_SRC_TB_EDGE_DET_TOP, src_tb_edge); + + if (video->frame_top > video->frame_bottom) + continue; + + video->frame_right = FIELD_GET(VE_SRC_LR_EDGE_DET_RT, src_lr_edge); + video->frame_left = FIELD_GET(VE_SRC_LR_EDGE_DET_LEFT, src_lr_edge); + + if (video->frame_left > video->frame_right) + continue; + + invalid_resolution = false; + } while (invalid_resolution && (tries++ < INVALID_RESOLUTION_RETRIES)); + + if (invalid_resolution) { + v4l2_warn(&video->v4l2_dev, "Invalid resolution detected\n"); + return; + } + + det->height = (video->frame_bottom - video->frame_top) + 1; + det->width = (video->frame_right - video->frame_left) + 1; + video->v4l2_input_status = 0; + + aspeed_video_get_timings(video, det); + + /* + * Enable mode-detect watchdog, resolution-change watchdog and + * automatic compression after frame capture. + */ + aspeed_video_update(video, VE_INTERRUPT_CTRL, 0, + VE_INTERRUPT_MODE_DETECT_WD); + aspeed_video_update(video, VE_SEQ_CTRL, 0, + VE_SEQ_CTRL_AUTO_COMP | VE_SEQ_CTRL_EN_WATCHDOG); + + v4l2_dbg(1, debug, &video->v4l2_dev, "Got resolution: %dx%d\n", + det->width, det->height); +} + +static void aspeed_video_set_resolution(struct aspeed_video *video) +{ + struct v4l2_bt_timings *act = &video->active_timings; + unsigned int size = act->width * act->height; + + /* Set capture/compression frame sizes */ + aspeed_video_calc_compressed_size(video, size); + + if (!IS_ALIGNED(act->width, 64)) { + /* + * This is a workaround to fix a AST2500 silicon bug on A1 and + * A2 revisions. Since it doesn't break capturing operation of + * other revisions, use it for all revisions without checking + * the revision ID. It picked new width which is a very next + * 64-pixels aligned value to minimize memory bandwidth + * and to get better access speed from video engine. + */ + u32 width = ALIGN(act->width, 64); + + aspeed_video_write(video, VE_CAP_WINDOW, width << 16 | act->height); + size = width * act->height; + } else { + aspeed_video_write(video, VE_CAP_WINDOW, + act->width << 16 | act->height); + } + aspeed_video_write(video, VE_COMP_WINDOW, + act->width << 16 | act->height); + aspeed_video_write(video, VE_SRC_SCANLINE_OFFSET, act->width * 4); + + /* Don't use direct mode below 1024 x 768 (irqs don't fire) */ + if (size < DIRECT_FETCH_THRESHOLD) { + v4l2_dbg(1, debug, &video->v4l2_dev, "Capture: Sync Mode\n"); + aspeed_video_write(video, VE_TGS_0, + FIELD_PREP(VE_TGS_FIRST, + video->frame_left - 1) | + FIELD_PREP(VE_TGS_LAST, + video->frame_right)); + aspeed_video_write(video, VE_TGS_1, + FIELD_PREP(VE_TGS_FIRST, video->frame_top) | + FIELD_PREP(VE_TGS_LAST, + video->frame_bottom + 1)); + aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_INT_DE); + } else { + v4l2_dbg(1, debug, &video->v4l2_dev, "Capture: Direct Mode\n"); + aspeed_video_update(video, VE_CTRL, 0, VE_CTRL_DIRECT_FETCH); + } + + size *= 4; + + if (size != video->srcs[0].size) { + if (video->srcs[0].size) + aspeed_video_free_buf(video, &video->srcs[0]); + if (video->srcs[1].size) + aspeed_video_free_buf(video, &video->srcs[1]); + + if (!aspeed_video_alloc_buf(video, &video->srcs[0], size)) + goto err_mem; + if (!aspeed_video_alloc_buf(video, &video->srcs[1], size)) + goto err_mem; + + v4l2_dbg(1, debug, &video->v4l2_dev, "src buf0 addr(%pad) size(%d)\n", + &video->srcs[0].dma, video->srcs[0].size); + v4l2_dbg(1, debug, &video->v4l2_dev, "src buf1 addr(%pad) size(%d)\n", + &video->srcs[1].dma, video->srcs[1].size); + aspeed_video_write(video, VE_SRC0_ADDR, video->srcs[0].dma); + aspeed_video_write(video, VE_SRC1_ADDR, video->srcs[1].dma); + } + + return; + +err_mem: + dev_err(video->dev, "Failed to allocate source buffers\n"); + + if (video->srcs[0].size) + aspeed_video_free_buf(video, &video->srcs[0]); +} + +static void aspeed_video_init_regs(struct aspeed_video *video) +{ + u32 comp_ctrl = VE_COMP_CTRL_RSVD | + FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | + FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10); + u32 ctrl = VE_CTRL_AUTO_OR_CURSOR | + FIELD_PREP(VE_CTRL_CAPTURE_FMT, VIDEO_CAP_FMT_YUV_FULL_SWING); + u32 seq_ctrl = video->jpeg_mode; + + if (video->frame_rate) + ctrl |= FIELD_PREP(VE_CTRL_FRC, video->frame_rate); + + if (video->yuv420) + seq_ctrl |= VE_SEQ_CTRL_YUV420; + + /* Unlock VE registers */ + aspeed_video_write(video, VE_PROTECTION_KEY, VE_PROTECTION_KEY_UNLOCK); + + /* Disable interrupts */ + aspeed_video_write(video, VE_INTERRUPT_CTRL, 0); + aspeed_video_write(video, VE_INTERRUPT_STATUS, 0xffffffff); + + /* Clear the offset */ + aspeed_video_write(video, VE_COMP_PROC_OFFSET, 0); + aspeed_video_write(video, VE_COMP_OFFSET, 0); + + aspeed_video_write(video, VE_JPEG_ADDR, video->jpeg.dma); + + /* Set control registers */ + aspeed_video_write(video, VE_SEQ_CTRL, seq_ctrl); + aspeed_video_write(video, VE_CTRL, ctrl); + aspeed_video_write(video, VE_COMP_CTRL, comp_ctrl); + + /* Don't downscale */ + aspeed_video_write(video, VE_SCALING_FACTOR, 0x10001000); + aspeed_video_write(video, VE_SCALING_FILTER0, 0x00200000); + aspeed_video_write(video, VE_SCALING_FILTER1, 0x00200000); + aspeed_video_write(video, VE_SCALING_FILTER2, 0x00200000); + aspeed_video_write(video, VE_SCALING_FILTER3, 0x00200000); + + /* Set mode detection defaults */ + aspeed_video_write(video, VE_MODE_DETECT, + FIELD_PREP(VE_MODE_DT_HOR_TOLER, 2) | + FIELD_PREP(VE_MODE_DT_VER_TOLER, 2) | + FIELD_PREP(VE_MODE_DT_HOR_STABLE, 6) | + FIELD_PREP(VE_MODE_DT_VER_STABLE, 6) | + FIELD_PREP(VE_MODE_DT_EDG_THROD, 0x65)); +} + +static void aspeed_video_start(struct aspeed_video *video) +{ + aspeed_video_on(video); + + aspeed_video_init_regs(video); + + /* Resolution set to 640x480 if no signal found */ + aspeed_video_get_resolution(video); + + /* Set timings since the device is being opened for the first time */ + video->active_timings = video->detected_timings; + aspeed_video_set_resolution(video); + + video->pix_fmt.width = video->active_timings.width; + video->pix_fmt.height = video->active_timings.height; + video->pix_fmt.sizeimage = video->max_compressed_size; +} + +static void aspeed_video_stop(struct aspeed_video *video) +{ + set_bit(VIDEO_STOPPED, &video->flags); + cancel_delayed_work_sync(&video->res_work); + + aspeed_video_off(video); + + if (video->srcs[0].size) + aspeed_video_free_buf(video, &video->srcs[0]); + + if (video->srcs[1].size) + aspeed_video_free_buf(video, &video->srcs[1]); + + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + video->flags = 0; +} + +static int aspeed_video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DEVICE_NAME, sizeof(cap->driver)); + strscpy(cap->card, "Aspeed Video Engine", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + DEVICE_NAME); + + return 0; +} + +static int aspeed_video_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + f->pixelformat = V4L2_PIX_FMT_JPEG; + + return 0; +} + +static int aspeed_video_get_format(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct aspeed_video *video = video_drvdata(file); + + f->fmt.pix = video->pix_fmt; + + return 0; +} + +static int aspeed_video_enum_input(struct file *file, void *fh, + struct v4l2_input *inp) +{ + struct aspeed_video *video = video_drvdata(file); + + if (inp->index) + return -EINVAL; + + strscpy(inp->name, "Host VGA capture", sizeof(inp->name)); + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->capabilities = V4L2_IN_CAP_DV_TIMINGS; + inp->status = video->v4l2_input_status; + + return 0; +} + +static int aspeed_video_get_input(struct file *file, void *fh, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int aspeed_video_set_input(struct file *file, void *fh, unsigned int i) +{ + if (i) + return -EINVAL; + + return 0; +} + +static int aspeed_video_get_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + struct aspeed_video *video = video_drvdata(file); + + a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.capture.readbuffers = ASPEED_VIDEO_V4L2_MIN_BUF_REQ; + a->parm.capture.timeperframe.numerator = 1; + if (!video->frame_rate) + a->parm.capture.timeperframe.denominator = MAX_FRAME_RATE; + else + a->parm.capture.timeperframe.denominator = video->frame_rate; + + return 0; +} + +static int aspeed_video_set_parm(struct file *file, void *fh, + struct v4l2_streamparm *a) +{ + unsigned int frame_rate = 0; + struct aspeed_video *video = video_drvdata(file); + + a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.capture.readbuffers = ASPEED_VIDEO_V4L2_MIN_BUF_REQ; + + if (a->parm.capture.timeperframe.numerator) + frame_rate = a->parm.capture.timeperframe.denominator / + a->parm.capture.timeperframe.numerator; + + if (!frame_rate || frame_rate > MAX_FRAME_RATE) { + frame_rate = 0; + a->parm.capture.timeperframe.denominator = MAX_FRAME_RATE; + a->parm.capture.timeperframe.numerator = 1; + } + + if (video->frame_rate != frame_rate) { + video->frame_rate = frame_rate; + aspeed_video_update(video, VE_CTRL, VE_CTRL_FRC, + FIELD_PREP(VE_CTRL_FRC, frame_rate)); + } + + return 0; +} + +static int aspeed_video_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct aspeed_video *video = video_drvdata(file); + + if (fsize->index) + return -EINVAL; + + if (fsize->pixel_format != V4L2_PIX_FMT_JPEG) + return -EINVAL; + + fsize->discrete.width = video->pix_fmt.width; + fsize->discrete.height = video->pix_fmt.height; + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + + return 0; +} + +static int aspeed_video_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct aspeed_video *video = video_drvdata(file); + + if (fival->index) + return -EINVAL; + + if (fival->width != video->detected_timings.width || + fival->height != video->detected_timings.height) + return -EINVAL; + + if (fival->pixel_format != V4L2_PIX_FMT_JPEG) + return -EINVAL; + + fival->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + + fival->stepwise.min.denominator = MAX_FRAME_RATE; + fival->stepwise.min.numerator = 1; + fival->stepwise.max.denominator = 1; + fival->stepwise.max.numerator = 1; + fival->stepwise.step = fival->stepwise.max; + + return 0; +} + +static int aspeed_video_set_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct aspeed_video *video = video_drvdata(file); + + if (timings->bt.width == video->active_timings.width && + timings->bt.height == video->active_timings.height) + return 0; + + if (vb2_is_busy(&video->queue)) + return -EBUSY; + + video->active_timings = timings->bt; + + aspeed_video_set_resolution(video); + + video->pix_fmt.width = timings->bt.width; + video->pix_fmt.height = timings->bt.height; + video->pix_fmt.sizeimage = video->max_compressed_size; + + timings->type = V4L2_DV_BT_656_1120; + + v4l2_dbg(1, debug, &video->v4l2_dev, "set new timings(%dx%d)\n", + timings->bt.width, timings->bt.height); + + return 0; +} + +static int aspeed_video_get_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + struct aspeed_video *video = video_drvdata(file); + + timings->type = V4L2_DV_BT_656_1120; + timings->bt = video->active_timings; + + return 0; +} + +static int aspeed_video_query_dv_timings(struct file *file, void *fh, + struct v4l2_dv_timings *timings) +{ + int rc; + struct aspeed_video *video = video_drvdata(file); + + /* + * This blocks only if the driver is currently in the process of + * detecting a new resolution; in the event of no signal or timeout + * this function is woken up. + */ + if (file->f_flags & O_NONBLOCK) { + if (test_bit(VIDEO_RES_CHANGE, &video->flags)) + return -EAGAIN; + } else { + rc = wait_event_interruptible(video->wait, + !test_bit(VIDEO_RES_CHANGE, + &video->flags)); + if (rc) + return -EINTR; + } + + timings->type = V4L2_DV_BT_656_1120; + timings->bt = video->detected_timings; + + return video->v4l2_input_status ? -ENOLINK : 0; +} + +static int aspeed_video_enum_dv_timings(struct file *file, void *fh, + struct v4l2_enum_dv_timings *timings) +{ + return v4l2_enum_dv_timings_cap(timings, &aspeed_video_timings_cap, + NULL, NULL); +} + +static int aspeed_video_dv_timings_cap(struct file *file, void *fh, + struct v4l2_dv_timings_cap *cap) +{ + *cap = aspeed_video_timings_cap; + + return 0; +} + +static int aspeed_video_sub_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + } + + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static const struct v4l2_ioctl_ops aspeed_video_ioctl_ops = { + .vidioc_querycap = aspeed_video_querycap, + + .vidioc_enum_fmt_vid_cap = aspeed_video_enum_format, + .vidioc_g_fmt_vid_cap = aspeed_video_get_format, + .vidioc_s_fmt_vid_cap = aspeed_video_get_format, + .vidioc_try_fmt_vid_cap = aspeed_video_get_format, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_enum_input = aspeed_video_enum_input, + .vidioc_g_input = aspeed_video_get_input, + .vidioc_s_input = aspeed_video_set_input, + + .vidioc_g_parm = aspeed_video_get_parm, + .vidioc_s_parm = aspeed_video_set_parm, + .vidioc_enum_framesizes = aspeed_video_enum_framesizes, + .vidioc_enum_frameintervals = aspeed_video_enum_frameintervals, + + .vidioc_s_dv_timings = aspeed_video_set_dv_timings, + .vidioc_g_dv_timings = aspeed_video_get_dv_timings, + .vidioc_query_dv_timings = aspeed_video_query_dv_timings, + .vidioc_enum_dv_timings = aspeed_video_enum_dv_timings, + .vidioc_dv_timings_cap = aspeed_video_dv_timings_cap, + + .vidioc_subscribe_event = aspeed_video_sub_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void aspeed_video_update_jpeg_quality(struct aspeed_video *video) +{ + u32 comp_ctrl = FIELD_PREP(VE_COMP_CTRL_DCT_LUM, video->jpeg_quality) | + FIELD_PREP(VE_COMP_CTRL_DCT_CHR, video->jpeg_quality | 0x10); + + aspeed_video_update(video, VE_COMP_CTRL, + VE_COMP_CTRL_DCT_LUM | VE_COMP_CTRL_DCT_CHR, + comp_ctrl); +} + +static void aspeed_video_update_subsampling(struct aspeed_video *video) +{ + if (video->jpeg.virt) + aspeed_video_update_jpeg_table(video->jpeg.virt, video->yuv420); + + if (video->yuv420) + aspeed_video_update(video, VE_SEQ_CTRL, 0, VE_SEQ_CTRL_YUV420); + else + aspeed_video_update(video, VE_SEQ_CTRL, VE_SEQ_CTRL_YUV420, 0); +} + +static int aspeed_video_set_ctrl(struct v4l2_ctrl *ctrl) +{ + struct aspeed_video *video = container_of(ctrl->handler, + struct aspeed_video, + ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + video->jpeg_quality = ctrl->val; + aspeed_video_update_jpeg_quality(video); + break; + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + if (ctrl->val == V4L2_JPEG_CHROMA_SUBSAMPLING_420) { + video->yuv420 = true; + aspeed_video_update_subsampling(video); + } else { + video->yuv420 = false; + aspeed_video_update_subsampling(video); + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops aspeed_video_ctrl_ops = { + .s_ctrl = aspeed_video_set_ctrl, +}; + +static void aspeed_video_resolution_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct aspeed_video *video = container_of(dwork, struct aspeed_video, + res_work); + + aspeed_video_on(video); + + /* Exit early in case no clients remain */ + if (test_bit(VIDEO_STOPPED, &video->flags)) + goto done; + + aspeed_video_init_regs(video); + + aspeed_video_get_resolution(video); + + if (video->detected_timings.width != video->active_timings.width || + video->detected_timings.height != video->active_timings.height) { + static const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_dbg(1, debug, &video->v4l2_dev, "fire source change event\n"); + v4l2_event_queue(&video->vdev, &ev); + } else if (test_bit(VIDEO_STREAMING, &video->flags)) { + /* No resolution change so just restart streaming */ + aspeed_video_start_frame(video); + } + +done: + clear_bit(VIDEO_RES_CHANGE, &video->flags); + wake_up_interruptible_all(&video->wait); +} + +static int aspeed_video_open(struct file *file) +{ + int rc; + struct aspeed_video *video = video_drvdata(file); + + mutex_lock(&video->video_lock); + + rc = v4l2_fh_open(file); + if (rc) { + mutex_unlock(&video->video_lock); + return rc; + } + + if (v4l2_fh_is_singular_file(file)) + aspeed_video_start(video); + + mutex_unlock(&video->video_lock); + + return 0; +} + +static int aspeed_video_release(struct file *file) +{ + int rc; + struct aspeed_video *video = video_drvdata(file); + + mutex_lock(&video->video_lock); + + if (v4l2_fh_is_singular_file(file)) + aspeed_video_stop(video); + + rc = _vb2_fop_release(file, NULL); + + mutex_unlock(&video->video_lock); + + return rc; +} + +static const struct v4l2_file_operations aspeed_video_v4l2_fops = { + .owner = THIS_MODULE, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .open = aspeed_video_open, + .release = aspeed_video_release, +}; + +static int aspeed_video_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct aspeed_video *video = vb2_get_drv_priv(q); + + if (*num_planes) { + if (sizes[0] < video->max_compressed_size) + return -EINVAL; + + return 0; + } + + *num_planes = 1; + sizes[0] = video->max_compressed_size; + + return 0; +} + +static int aspeed_video_buf_prepare(struct vb2_buffer *vb) +{ + struct aspeed_video *video = vb2_get_drv_priv(vb->vb2_queue); + + if (vb2_plane_size(vb, 0) < video->max_compressed_size) + return -EINVAL; + + return 0; +} + +static int aspeed_video_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + int rc; + struct aspeed_video *video = vb2_get_drv_priv(q); + + video->sequence = 0; + video->perf.duration_max = 0; + video->perf.duration_min = 0xffffffff; + + rc = aspeed_video_start_frame(video); + if (rc) { + aspeed_video_bufs_done(video, VB2_BUF_STATE_QUEUED); + return rc; + } + + set_bit(VIDEO_STREAMING, &video->flags); + return 0; +} + +static void aspeed_video_stop_streaming(struct vb2_queue *q) +{ + int rc; + struct aspeed_video *video = vb2_get_drv_priv(q); + + clear_bit(VIDEO_STREAMING, &video->flags); + + rc = wait_event_timeout(video->wait, + !test_bit(VIDEO_FRAME_INPRG, &video->flags), + STOP_TIMEOUT); + if (!rc) { + v4l2_warn(&video->v4l2_dev, "Timed out when stopping streaming\n"); + + /* + * Need to force stop any DMA and try and get HW into a good + * state for future calls to start streaming again. + */ + aspeed_video_off(video); + aspeed_video_on(video); + + aspeed_video_init_regs(video); + + aspeed_video_get_resolution(video); + } + + aspeed_video_bufs_done(video, VB2_BUF_STATE_ERROR); +} + +static void aspeed_video_buf_queue(struct vb2_buffer *vb) +{ + bool empty; + struct aspeed_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct aspeed_video_buffer *avb = to_aspeed_video_buffer(vbuf); + unsigned long flags; + + spin_lock_irqsave(&video->lock, flags); + empty = list_empty(&video->buffers); + list_add_tail(&avb->link, &video->buffers); + spin_unlock_irqrestore(&video->lock, flags); + + if (test_bit(VIDEO_STREAMING, &video->flags) && + !test_bit(VIDEO_FRAME_INPRG, &video->flags) && empty) + aspeed_video_start_frame(video); +} + +static const struct vb2_ops aspeed_video_vb2_ops = { + .queue_setup = aspeed_video_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = aspeed_video_buf_prepare, + .start_streaming = aspeed_video_start_streaming, + .stop_streaming = aspeed_video_stop_streaming, + .buf_queue = aspeed_video_buf_queue, +}; + +#ifdef CONFIG_DEBUG_FS +static int aspeed_video_debugfs_show(struct seq_file *s, void *data) +{ + struct aspeed_video *v = s->private; + + seq_puts(s, "\n"); + + seq_printf(s, " %-20s:\t%s\n", "Signal", + v->v4l2_input_status ? "Unlock" : "Lock"); + seq_printf(s, " %-20s:\t%d\n", "Width", v->pix_fmt.width); + seq_printf(s, " %-20s:\t%d\n", "Height", v->pix_fmt.height); + seq_printf(s, " %-20s:\t%d\n", "FRC", v->frame_rate); + + seq_puts(s, "\n"); + + seq_puts(s, "Performance:\n"); + seq_printf(s, " %-20s:\t%d\n", "Frame#", v->sequence); + seq_printf(s, " %-20s:\n", "Frame Duration(ms)"); + seq_printf(s, " %-18s:\t%d\n", "Now", v->perf.duration); + seq_printf(s, " %-18s:\t%d\n", "Min", v->perf.duration_min); + seq_printf(s, " %-18s:\t%d\n", "Max", v->perf.duration_max); + seq_printf(s, " %-20s:\t%d\n", "FPS", 1000 / (v->perf.totaltime / v->sequence)); + + return 0; +} + +static int aspeed_video_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, aspeed_video_debugfs_show, inode->i_private); +} + +static const struct file_operations aspeed_video_debugfs_ops = { + .owner = THIS_MODULE, + .open = aspeed_video_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static struct dentry *debugfs_entry; + +static void aspeed_video_debugfs_remove(struct aspeed_video *video) +{ + debugfs_remove_recursive(debugfs_entry); + debugfs_entry = NULL; +} + +static int aspeed_video_debugfs_create(struct aspeed_video *video) +{ + debugfs_entry = debugfs_create_file(DEVICE_NAME, 0444, NULL, + video, + &aspeed_video_debugfs_ops); + if (!debugfs_entry) + aspeed_video_debugfs_remove(video); + + return !debugfs_entry ? -EIO : 0; +} +#else +static void aspeed_video_debugfs_remove(struct aspeed_video *video) { } +static int aspeed_video_debugfs_create(struct aspeed_video *video) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static int aspeed_video_setup_video(struct aspeed_video *video) +{ + const u64 mask = ~(BIT(V4L2_JPEG_CHROMA_SUBSAMPLING_444) | + BIT(V4L2_JPEG_CHROMA_SUBSAMPLING_420)); + struct v4l2_device *v4l2_dev = &video->v4l2_dev; + struct vb2_queue *vbq = &video->queue; + struct video_device *vdev = &video->vdev; + int rc; + + video->pix_fmt.pixelformat = V4L2_PIX_FMT_JPEG; + video->pix_fmt.field = V4L2_FIELD_NONE; + video->pix_fmt.colorspace = V4L2_COLORSPACE_SRGB; + video->pix_fmt.quantization = V4L2_QUANTIZATION_FULL_RANGE; + video->v4l2_input_status = V4L2_IN_ST_NO_SIGNAL; + + rc = v4l2_device_register(video->dev, v4l2_dev); + if (rc) { + dev_err(video->dev, "Failed to register v4l2 device\n"); + return rc; + } + + v4l2_ctrl_handler_init(&video->ctrl_handler, 2); + v4l2_ctrl_new_std(&video->ctrl_handler, &aspeed_video_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, 0, + ASPEED_VIDEO_JPEG_NUM_QUALITIES - 1, 1, 0); + v4l2_ctrl_new_std_menu(&video->ctrl_handler, &aspeed_video_ctrl_ops, + V4L2_CID_JPEG_CHROMA_SUBSAMPLING, + V4L2_JPEG_CHROMA_SUBSAMPLING_420, mask, + V4L2_JPEG_CHROMA_SUBSAMPLING_444); + + rc = video->ctrl_handler.error; + if (rc) { + v4l2_ctrl_handler_free(&video->ctrl_handler); + v4l2_device_unregister(v4l2_dev); + + dev_err(video->dev, "Failed to init controls: %d\n", rc); + return rc; + } + + v4l2_dev->ctrl_handler = &video->ctrl_handler; + + vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vbq->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + vbq->dev = v4l2_dev->dev; + vbq->lock = &video->video_lock; + vbq->ops = &aspeed_video_vb2_ops; + vbq->mem_ops = &vb2_dma_contig_memops; + vbq->drv_priv = video; + vbq->buf_struct_size = sizeof(struct aspeed_video_buffer); + vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vbq->min_buffers_needed = ASPEED_VIDEO_V4L2_MIN_BUF_REQ; + + rc = vb2_queue_init(vbq); + if (rc) { + v4l2_ctrl_handler_free(&video->ctrl_handler); + v4l2_device_unregister(v4l2_dev); + + dev_err(video->dev, "Failed to init vb2 queue\n"); + return rc; + } + + vdev->queue = vbq; + vdev->fops = &aspeed_video_v4l2_fops; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING; + vdev->v4l2_dev = v4l2_dev; + strscpy(vdev->name, DEVICE_NAME, sizeof(vdev->name)); + vdev->vfl_type = VFL_TYPE_VIDEO; + vdev->vfl_dir = VFL_DIR_RX; + vdev->release = video_device_release_empty; + vdev->ioctl_ops = &aspeed_video_ioctl_ops; + vdev->lock = &video->video_lock; + + video_set_drvdata(vdev, video); + rc = video_register_device(vdev, VFL_TYPE_VIDEO, 0); + if (rc) { + v4l2_ctrl_handler_free(&video->ctrl_handler); + v4l2_device_unregister(v4l2_dev); + + dev_err(video->dev, "Failed to register video device\n"); + return rc; + } + + return 0; +} + +static int aspeed_video_init(struct aspeed_video *video) +{ + int irq; + int rc; + struct device *dev = video->dev; + + irq = irq_of_parse_and_map(dev->of_node, 0); + if (!irq) { + dev_err(dev, "Unable to find IRQ\n"); + return -ENODEV; + } + + rc = devm_request_threaded_irq(dev, irq, NULL, aspeed_video_irq, + IRQF_ONESHOT, DEVICE_NAME, video); + if (rc < 0) { + dev_err(dev, "Unable to request IRQ %d\n", irq); + return rc; + } + dev_info(video->dev, "irq %d\n", irq); + + video->eclk = devm_clk_get(dev, "eclk"); + if (IS_ERR(video->eclk)) { + dev_err(dev, "Unable to get ECLK\n"); + return PTR_ERR(video->eclk); + } + + rc = clk_prepare(video->eclk); + if (rc) + return rc; + + video->vclk = devm_clk_get(dev, "vclk"); + if (IS_ERR(video->vclk)) { + dev_err(dev, "Unable to get VCLK\n"); + rc = PTR_ERR(video->vclk); + goto err_unprepare_eclk; + } + + rc = clk_prepare(video->vclk); + if (rc) + goto err_unprepare_eclk; + + of_reserved_mem_device_init(dev); + + rc = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (rc) { + dev_err(dev, "Failed to set DMA mask\n"); + goto err_release_reserved_mem; + } + + if (!aspeed_video_alloc_buf(video, &video->jpeg, + VE_JPEG_HEADER_SIZE)) { + dev_err(dev, "Failed to allocate DMA for JPEG header\n"); + rc = -ENOMEM; + goto err_release_reserved_mem; + } + dev_info(video->dev, "alloc mem size(%d) at %pad for jpeg header\n", + VE_JPEG_HEADER_SIZE, &video->jpeg.dma); + + aspeed_video_init_jpeg_table(video->jpeg.virt, video->yuv420); + + return 0; + +err_release_reserved_mem: + of_reserved_mem_device_release(dev); + clk_unprepare(video->vclk); +err_unprepare_eclk: + clk_unprepare(video->eclk); + + return rc; +} + +static const struct of_device_id aspeed_video_of_match[] = { + { .compatible = "aspeed,ast2400-video-engine", .data = &ast2400_config }, + { .compatible = "aspeed,ast2500-video-engine", .data = &ast2500_config }, + { .compatible = "aspeed,ast2600-video-engine", .data = &ast2600_config }, + {} +}; +MODULE_DEVICE_TABLE(of, aspeed_video_of_match); + +static int aspeed_video_probe(struct platform_device *pdev) +{ + const struct aspeed_video_config *config; + struct aspeed_video *video; + int rc; + + video = devm_kzalloc(&pdev->dev, sizeof(*video), GFP_KERNEL); + if (!video) + return -ENOMEM; + + video->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(video->base)) + return PTR_ERR(video->base); + + config = of_device_get_match_data(&pdev->dev); + if (!config) + return -ENODEV; + + video->jpeg_mode = config->jpeg_mode; + video->comp_size_read = config->comp_size_read; + + video->frame_rate = 30; + video->dev = &pdev->dev; + spin_lock_init(&video->lock); + mutex_init(&video->video_lock); + init_waitqueue_head(&video->wait); + INIT_DELAYED_WORK(&video->res_work, aspeed_video_resolution_work); + INIT_LIST_HEAD(&video->buffers); + + rc = aspeed_video_init(video); + if (rc) + return rc; + + rc = aspeed_video_setup_video(video); + if (rc) { + aspeed_video_free_buf(video, &video->jpeg); + clk_unprepare(video->vclk); + clk_unprepare(video->eclk); + return rc; + } + + rc = aspeed_video_debugfs_create(video); + if (rc) + dev_err(video->dev, "debugfs create failed\n"); + + return 0; +} + +static int aspeed_video_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct v4l2_device *v4l2_dev = dev_get_drvdata(dev); + struct aspeed_video *video = to_aspeed_video(v4l2_dev); + + aspeed_video_off(video); + + aspeed_video_debugfs_remove(video); + + clk_unprepare(video->vclk); + clk_unprepare(video->eclk); + + vb2_video_unregister_device(&video->vdev); + + v4l2_ctrl_handler_free(&video->ctrl_handler); + + v4l2_device_unregister(v4l2_dev); + + aspeed_video_free_buf(video, &video->jpeg); + + of_reserved_mem_device_release(dev); + + return 0; +} + +static struct platform_driver aspeed_video_driver = { + .driver = { + .name = DEVICE_NAME, + .of_match_table = aspeed_video_of_match, + }, + .probe = aspeed_video_probe, + .remove = aspeed_video_remove, +}; + +module_platform_driver(aspeed_video_driver); + +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0=off,1=info,2=debug,3=reg ops)"); + +MODULE_DESCRIPTION("ASPEED Video Engine Driver"); +MODULE_AUTHOR("Eddie James"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/atmel/Kconfig b/drivers/media/platform/atmel/Kconfig new file mode 100644 index 000000000..f399dba62 --- /dev/null +++ b/drivers/media/platform/atmel/Kconfig @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Atmel media platform drivers" + +config VIDEO_ATMEL_ISC + tristate "ATMEL Image Sensor Controller (ISC) support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK + depends on ARCH_AT91 || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + select V4L2_FWNODE + select VIDEO_ATMEL_ISC_BASE + help + This module makes the ATMEL Image Sensor Controller available + as a v4l2 device. + +config VIDEO_ATMEL_XISC + tristate "ATMEL eXtended Image Sensor Controller (XISC) support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK + depends on ARCH_AT91 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select REGMAP_MMIO + select V4L2_FWNODE + select VIDEO_ATMEL_ISC_BASE + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + help + This module makes the ATMEL eXtended Image Sensor Controller + available as a v4l2 device. + +config VIDEO_ATMEL_ISC_BASE + tristate + default n + help + ATMEL ISC and XISC common code base. + +config VIDEO_ATMEL_ISI + tristate "ATMEL Image Sensor Interface (ISI) support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + depends on ARCH_AT91 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + This module makes the ATMEL Image Sensor Interface available + as a v4l2 device. + +config VIDEO_MICROCHIP_CSI2DC + tristate "Microchip CSI2 Demux Controller" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK && OF + depends on ARCH_AT91 || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + CSI2 Demux Controller driver. CSI2DC is a helper chip + that converts IDI interface byte stream to a parallel pixel stream. + It supports various RAW formats as input. + + To compile this driver as a module, choose M here: the + module will be called microchip-csi2dc. diff --git a/drivers/media/platform/atmel/Makefile b/drivers/media/platform/atmel/Makefile new file mode 100644 index 000000000..794e8f739 --- /dev/null +++ b/drivers/media/platform/atmel/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +atmel-isc-objs = atmel-sama5d2-isc.o +atmel-xisc-objs = atmel-sama7g5-isc.o +atmel-isc-common-objs = atmel-isc-base.o atmel-isc-clk.o + +obj-$(CONFIG_VIDEO_ATMEL_ISI) += atmel-isi.o +obj-$(CONFIG_VIDEO_ATMEL_ISC_BASE) += atmel-isc-common.o +obj-$(CONFIG_VIDEO_ATMEL_ISC) += atmel-isc.o +obj-$(CONFIG_VIDEO_ATMEL_XISC) += atmel-xisc.o +obj-$(CONFIG_VIDEO_MICROCHIP_CSI2DC) += microchip-csi2dc.o diff --git a/drivers/media/platform/atmel/atmel-isc-base.c b/drivers/media/platform/atmel/atmel-isc-base.c new file mode 100644 index 000000000..9e5317a7d --- /dev/null +++ b/drivers/media/platform/atmel/atmel-isc-base.c @@ -0,0 +1,2011 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microchip Image Sensor Controller (ISC) common driver base + * + * Copyright (C) 2016-2019 Microchip Technology, Inc. + * + * Author: Songjun Wu + * Author: Eugen Hristev + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atmel-isc-regs.h" +#include "atmel-isc.h" + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "debug level (0-2)"); + +static unsigned int sensor_preferred = 1; +module_param(sensor_preferred, uint, 0644); +MODULE_PARM_DESC(sensor_preferred, + "Sensor is preferred to output the specified format (1-on 0-off), default 1"); + +#define ISC_IS_FORMAT_RAW(mbus_code) \ + (((mbus_code) & 0xf000) == 0x3000) + +#define ISC_IS_FORMAT_GREY(mbus_code) \ + (((mbus_code) == MEDIA_BUS_FMT_Y10_1X10) | \ + (((mbus_code) == MEDIA_BUS_FMT_Y8_1X8))) + +static inline void isc_update_v4l2_ctrls(struct isc_device *isc) +{ + struct isc_ctrls *ctrls = &isc->ctrls; + + /* In here we set the v4l2 controls w.r.t. our pipeline config */ + v4l2_ctrl_s_ctrl(isc->r_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_R]); + v4l2_ctrl_s_ctrl(isc->b_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_B]); + v4l2_ctrl_s_ctrl(isc->gr_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_GR]); + v4l2_ctrl_s_ctrl(isc->gb_gain_ctrl, ctrls->gain[ISC_HIS_CFG_MODE_GB]); + + v4l2_ctrl_s_ctrl(isc->r_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_R]); + v4l2_ctrl_s_ctrl(isc->b_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_B]); + v4l2_ctrl_s_ctrl(isc->gr_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_GR]); + v4l2_ctrl_s_ctrl(isc->gb_off_ctrl, ctrls->offset[ISC_HIS_CFG_MODE_GB]); +} + +static inline void isc_update_awb_ctrls(struct isc_device *isc) +{ + struct isc_ctrls *ctrls = &isc->ctrls; + + /* In here we set our actual hw pipeline config */ + + regmap_write(isc->regmap, ISC_WB_O_RGR, + ((ctrls->offset[ISC_HIS_CFG_MODE_R])) | + ((ctrls->offset[ISC_HIS_CFG_MODE_GR]) << 16)); + regmap_write(isc->regmap, ISC_WB_O_BGB, + ((ctrls->offset[ISC_HIS_CFG_MODE_B])) | + ((ctrls->offset[ISC_HIS_CFG_MODE_GB]) << 16)); + regmap_write(isc->regmap, ISC_WB_G_RGR, + ctrls->gain[ISC_HIS_CFG_MODE_R] | + (ctrls->gain[ISC_HIS_CFG_MODE_GR] << 16)); + regmap_write(isc->regmap, ISC_WB_G_BGB, + ctrls->gain[ISC_HIS_CFG_MODE_B] | + (ctrls->gain[ISC_HIS_CFG_MODE_GB] << 16)); +} + +static inline void isc_reset_awb_ctrls(struct isc_device *isc) +{ + unsigned int c; + + for (c = ISC_HIS_CFG_MODE_GR; c <= ISC_HIS_CFG_MODE_B; c++) { + /* gains have a fixed point at 9 decimals */ + isc->ctrls.gain[c] = 1 << 9; + /* offsets are in 2's complements */ + isc->ctrls.offset[c] = 0; + } +} + + +static int isc_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + unsigned int size = isc->fmt.fmt.pix.sizeimage; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int isc_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct isc_device *isc = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = isc->fmt.fmt.pix.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + v4l2_err(&isc->v4l2_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + vbuf->field = isc->fmt.fmt.pix.field; + + return 0; +} + +static void isc_crop_pfe(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 h, w; + + h = isc->fmt.fmt.pix.height; + w = isc->fmt.fmt.pix.width; + + /* + * In case the sensor is not RAW, it will output a pixel (12-16 bits) + * with two samples on the ISC Data bus (which is 8-12) + * ISC will count each sample, so, we need to multiply these values + * by two, to get the real number of samples for the required pixels. + */ + if (!ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) { + h <<= 1; + w <<= 1; + } + + /* + * We limit the column/row count that the ISC will output according + * to the configured resolution that we want. + * This will avoid the situation where the sensor is misconfigured, + * sending more data, and the ISC will just take it and DMA to memory, + * causing corruption. + */ + regmap_write(regmap, ISC_PFE_CFG1, + (ISC_PFE_CFG1_COLMIN(0) & ISC_PFE_CFG1_COLMIN_MASK) | + (ISC_PFE_CFG1_COLMAX(w - 1) & ISC_PFE_CFG1_COLMAX_MASK)); + + regmap_write(regmap, ISC_PFE_CFG2, + (ISC_PFE_CFG2_ROWMIN(0) & ISC_PFE_CFG2_ROWMIN_MASK) | + (ISC_PFE_CFG2_ROWMAX(h - 1) & ISC_PFE_CFG2_ROWMAX_MASK)); + + regmap_update_bits(regmap, ISC_PFE_CFG0, + ISC_PFE_CFG0_COLEN | ISC_PFE_CFG0_ROWEN, + ISC_PFE_CFG0_COLEN | ISC_PFE_CFG0_ROWEN); +} + +static void isc_start_dma(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 sizeimage = isc->fmt.fmt.pix.sizeimage; + u32 dctrl_dview; + dma_addr_t addr0; + + addr0 = vb2_dma_contig_plane_dma_addr(&isc->cur_frm->vb.vb2_buf, 0); + regmap_write(regmap, ISC_DAD0 + isc->offsets.dma, addr0); + + switch (isc->config.fourcc) { + case V4L2_PIX_FMT_YUV420: + regmap_write(regmap, ISC_DAD1 + isc->offsets.dma, + addr0 + (sizeimage * 2) / 3); + regmap_write(regmap, ISC_DAD2 + isc->offsets.dma, + addr0 + (sizeimage * 5) / 6); + break; + case V4L2_PIX_FMT_YUV422P: + regmap_write(regmap, ISC_DAD1 + isc->offsets.dma, + addr0 + sizeimage / 2); + regmap_write(regmap, ISC_DAD2 + isc->offsets.dma, + addr0 + (sizeimage * 3) / 4); + break; + default: + break; + } + + dctrl_dview = isc->config.dctrl_dview; + + regmap_write(regmap, ISC_DCTRL + isc->offsets.dma, + dctrl_dview | ISC_DCTRL_IE_IS); + spin_lock(&isc->awb_lock); + regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_CAPTURE); + spin_unlock(&isc->awb_lock); +} + +static void isc_set_pipeline(struct isc_device *isc, u32 pipeline) +{ + struct regmap *regmap = isc->regmap; + struct isc_ctrls *ctrls = &isc->ctrls; + u32 val, bay_cfg; + const u32 *gamma; + unsigned int i; + + /* WB-->CFA-->CC-->GAM-->CSC-->CBC-->SUB422-->SUB420 */ + for (i = 0; i < ISC_PIPE_LINE_NODE_NUM; i++) { + val = pipeline & BIT(i) ? 1 : 0; + regmap_field_write(isc->pipeline[i], val); + } + + if (!pipeline) + return; + + bay_cfg = isc->config.sd_format->cfa_baycfg; + + regmap_write(regmap, ISC_WB_CFG, bay_cfg); + isc_update_awb_ctrls(isc); + isc_update_v4l2_ctrls(isc); + + regmap_write(regmap, ISC_CFA_CFG, bay_cfg | ISC_CFA_CFG_EITPOL); + + gamma = &isc->gamma_table[ctrls->gamma_index][0]; + regmap_bulk_write(regmap, ISC_GAM_BENTRY, gamma, GAMMA_ENTRIES); + regmap_bulk_write(regmap, ISC_GAM_GENTRY, gamma, GAMMA_ENTRIES); + regmap_bulk_write(regmap, ISC_GAM_RENTRY, gamma, GAMMA_ENTRIES); + + isc->config_dpc(isc); + isc->config_csc(isc); + isc->config_cbc(isc); + isc->config_cc(isc); + isc->config_gam(isc); +} + +static int isc_update_profile(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 sr; + int counter = 100; + + regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_UPPRO); + + regmap_read(regmap, ISC_CTRLSR, &sr); + while ((sr & ISC_CTRL_UPPRO) && counter--) { + usleep_range(1000, 2000); + regmap_read(regmap, ISC_CTRLSR, &sr); + } + + if (counter < 0) { + v4l2_warn(&isc->v4l2_dev, "Time out to update profile\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void isc_set_histogram(struct isc_device *isc, bool enable) +{ + struct regmap *regmap = isc->regmap; + struct isc_ctrls *ctrls = &isc->ctrls; + + if (enable) { + regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his, + ISC_HIS_CFG_MODE_GR | + (isc->config.sd_format->cfa_baycfg + << ISC_HIS_CFG_BAYSEL_SHIFT) | + ISC_HIS_CFG_RAR); + regmap_write(regmap, ISC_HIS_CTRL + isc->offsets.his, + ISC_HIS_CTRL_EN); + regmap_write(regmap, ISC_INTEN, ISC_INT_HISDONE); + ctrls->hist_id = ISC_HIS_CFG_MODE_GR; + isc_update_profile(isc); + regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ); + + ctrls->hist_stat = HIST_ENABLED; + } else { + regmap_write(regmap, ISC_INTDIS, ISC_INT_HISDONE); + regmap_write(regmap, ISC_HIS_CTRL + isc->offsets.his, + ISC_HIS_CTRL_DIS); + + ctrls->hist_stat = HIST_DISABLED; + } +} + +static int isc_configure(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 pfe_cfg0, dcfg, mask, pipeline; + struct isc_subdev_entity *subdev = isc->current_subdev; + + pfe_cfg0 = isc->config.sd_format->pfe_cfg0_bps; + pipeline = isc->config.bits_pipeline; + + dcfg = isc->config.dcfg_imode | isc->dcfg; + + pfe_cfg0 |= subdev->pfe_cfg0 | ISC_PFE_CFG0_MODE_PROGRESSIVE; + mask = ISC_PFE_CFG0_BPS_MASK | ISC_PFE_CFG0_HPOL_LOW | + ISC_PFE_CFG0_VPOL_LOW | ISC_PFE_CFG0_PPOL_LOW | + ISC_PFE_CFG0_MODE_MASK | ISC_PFE_CFG0_CCIR_CRC | + ISC_PFE_CFG0_CCIR656 | ISC_PFE_CFG0_MIPI; + + regmap_update_bits(regmap, ISC_PFE_CFG0, mask, pfe_cfg0); + + isc->config_rlp(isc); + + regmap_write(regmap, ISC_DCFG + isc->offsets.dma, dcfg); + + /* Set the pipeline */ + isc_set_pipeline(isc, pipeline); + + /* + * The current implemented histogram is available for RAW R, B, GB, GR + * channels. We need to check if sensor is outputting RAW BAYER + */ + if (isc->ctrls.awb && + ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) + isc_set_histogram(isc, true); + else + isc_set_histogram(isc, false); + + /* Update profile */ + return isc_update_profile(isc); +} + +static int isc_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + struct regmap *regmap = isc->regmap; + struct isc_buffer *buf; + unsigned long flags; + int ret; + + /* Enable stream on the sub device */ + ret = v4l2_subdev_call(isc->current_subdev->sd, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) { + v4l2_err(&isc->v4l2_dev, "stream on failed in subdev %d\n", + ret); + goto err_start_stream; + } + + ret = pm_runtime_resume_and_get(isc->dev); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, "RPM resume failed in subdev %d\n", + ret); + goto err_pm_get; + } + + ret = isc_configure(isc); + if (unlikely(ret)) + goto err_configure; + + /* Enable DMA interrupt */ + regmap_write(regmap, ISC_INTEN, ISC_INT_DDONE); + + spin_lock_irqsave(&isc->dma_queue_lock, flags); + + isc->sequence = 0; + isc->stop = false; + reinit_completion(&isc->comp); + + isc->cur_frm = list_first_entry(&isc->dma_queue, + struct isc_buffer, list); + list_del(&isc->cur_frm->list); + + isc_crop_pfe(isc); + isc_start_dma(isc); + + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); + + /* if we streaming from RAW, we can do one-shot white balance adj */ + if (ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) + v4l2_ctrl_activate(isc->do_wb_ctrl, true); + + return 0; + +err_configure: + pm_runtime_put_sync(isc->dev); +err_pm_get: + v4l2_subdev_call(isc->current_subdev->sd, video, s_stream, 0); + +err_start_stream: + spin_lock_irqsave(&isc->dma_queue_lock, flags); + list_for_each_entry(buf, &isc->dma_queue, list) + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + INIT_LIST_HEAD(&isc->dma_queue); + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); + + return ret; +} + +static void isc_stop_streaming(struct vb2_queue *vq) +{ + struct isc_device *isc = vb2_get_drv_priv(vq); + unsigned long flags; + struct isc_buffer *buf; + int ret; + + mutex_lock(&isc->awb_mutex); + v4l2_ctrl_activate(isc->do_wb_ctrl, false); + + isc->stop = true; + + /* Wait until the end of the current frame */ + if (isc->cur_frm && !wait_for_completion_timeout(&isc->comp, 5 * HZ)) + v4l2_err(&isc->v4l2_dev, + "Timeout waiting for end of the capture\n"); + + mutex_unlock(&isc->awb_mutex); + + /* Disable DMA interrupt */ + regmap_write(isc->regmap, ISC_INTDIS, ISC_INT_DDONE); + + pm_runtime_put_sync(isc->dev); + + /* Disable stream on the sub device */ + ret = v4l2_subdev_call(isc->current_subdev->sd, video, s_stream, 0); + if (ret && ret != -ENOIOCTLCMD) + v4l2_err(&isc->v4l2_dev, "stream off failed in subdev\n"); + + /* Release all active buffers */ + spin_lock_irqsave(&isc->dma_queue_lock, flags); + if (unlikely(isc->cur_frm)) { + vb2_buffer_done(&isc->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + isc->cur_frm = NULL; + } + list_for_each_entry(buf, &isc->dma_queue, list) + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + INIT_LIST_HEAD(&isc->dma_queue); + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); +} + +static void isc_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct isc_buffer *buf = container_of(vbuf, struct isc_buffer, vb); + struct isc_device *isc = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&isc->dma_queue_lock, flags); + if (!isc->cur_frm && list_empty(&isc->dma_queue) && + vb2_start_streaming_called(vb->vb2_queue)) { + isc->cur_frm = buf; + isc_start_dma(isc); + } else + list_add_tail(&buf->list, &isc->dma_queue); + spin_unlock_irqrestore(&isc->dma_queue_lock, flags); +} + +static struct isc_format *find_format_by_fourcc(struct isc_device *isc, + unsigned int fourcc) +{ + unsigned int num_formats = isc->num_user_formats; + struct isc_format *fmt; + unsigned int i; + + for (i = 0; i < num_formats; i++) { + fmt = isc->user_formats[i]; + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +static const struct vb2_ops isc_vb2_ops = { + .queue_setup = isc_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = isc_buffer_prepare, + .start_streaming = isc_start_streaming, + .stop_streaming = isc_stop_streaming, + .buf_queue = isc_buffer_queue, +}; + +static int isc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct isc_device *isc = video_drvdata(file); + + strscpy(cap->driver, "microchip-isc", sizeof(cap->driver)); + strscpy(cap->card, "Atmel Image Sensor Controller", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", isc->v4l2_dev.name); + + return 0; +} + +static int isc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct isc_device *isc = video_drvdata(file); + u32 index = f->index; + u32 i, supported_index; + + if (index < isc->controller_formats_size) { + f->pixelformat = isc->controller_formats[index].fourcc; + return 0; + } + + index -= isc->controller_formats_size; + + supported_index = 0; + + for (i = 0; i < isc->formats_list_size; i++) { + if (!ISC_IS_FORMAT_RAW(isc->formats_list[i].mbus_code) || + !isc->formats_list[i].sd_support) + continue; + if (supported_index == index) { + f->pixelformat = isc->formats_list[i].fourcc; + return 0; + } + supported_index++; + } + + return -EINVAL; +} + +static int isc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct isc_device *isc = video_drvdata(file); + + *fmt = isc->fmt; + + return 0; +} + +/* + * Checks the current configured format, if ISC can output it, + * considering which type of format the ISC receives from the sensor + */ +static int isc_try_validate_formats(struct isc_device *isc) +{ + int ret; + bool bayer = false, yuv = false, rgb = false, grey = false; + + /* all formats supported by the RLP module are OK */ + switch (isc->try_config.fourcc) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SRGGB10: + case V4L2_PIX_FMT_SBGGR12: + case V4L2_PIX_FMT_SGBRG12: + case V4L2_PIX_FMT_SGRBG12: + case V4L2_PIX_FMT_SRGGB12: + ret = 0; + bayer = true; + break; + + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + ret = 0; + yuv = true; + break; + + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_XBGR32: + case V4L2_PIX_FMT_ARGB444: + case V4L2_PIX_FMT_ARGB555: + ret = 0; + rgb = true; + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y10: + case V4L2_PIX_FMT_Y16: + ret = 0; + grey = true; + break; + default: + /* any other different formats are not supported */ + ret = -EINVAL; + } + v4l2_dbg(1, debug, &isc->v4l2_dev, + "Format validation, requested rgb=%u, yuv=%u, grey=%u, bayer=%u\n", + rgb, yuv, grey, bayer); + + /* we cannot output RAW if we do not receive RAW */ + if ((bayer) && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) + return -EINVAL; + + /* we cannot output GREY if we do not receive RAW/GREY */ + if (grey && !ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code) && + !ISC_IS_FORMAT_GREY(isc->try_config.sd_format->mbus_code)) + return -EINVAL; + + return ret; +} + +/* + * Configures the RLP and DMA modules, depending on the output format + * configured for the ISC. + * If direct_dump == true, just dump raw data 8/16 bits depending on format. + */ +static int isc_try_configure_rlp_dma(struct isc_device *isc, bool direct_dump) +{ + isc->try_config.rlp_cfg_mode = 0; + + switch (isc->try_config.fourcc) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DAT8; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 8; + isc->try_config.bpp_v4l2 = 8; + break; + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SRGGB10: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DAT10; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_SBGGR12: + case V4L2_PIX_FMT_SGBRG12: + case V4L2_PIX_FMT_SGRBG12: + case V4L2_PIX_FMT_SRGGB12: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DAT12; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_RGB565: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_RGB565; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_ARGB444: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB444; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_ARGB555: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB555; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_XBGR32: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_ARGB32; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 32; + isc->try_config.bpp_v4l2 = 32; + break; + case V4L2_PIX_FMT_YUV420: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC420P; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR; + isc->try_config.bpp = 12; + isc->try_config.bpp_v4l2 = 8; /* only first plane */ + break; + case V4L2_PIX_FMT_YUV422P: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YYCC; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_YC422P; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PLANAR; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 8; /* only first plane */ + break; + case V4L2_PIX_FMT_YUYV: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_YUYV; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_UYVY: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_UYVY; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_VYUY: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_YCYC | ISC_RLP_CFG_YMODE_VYUY; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED32; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + case V4L2_PIX_FMT_GREY: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY8; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 8; + isc->try_config.bpp_v4l2 = 8; + break; + case V4L2_PIX_FMT_Y16: + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DATY10 | ISC_RLP_CFG_LSH; + fallthrough; + case V4L2_PIX_FMT_Y10: + isc->try_config.rlp_cfg_mode |= ISC_RLP_CFG_MODE_DATY10; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED16; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + isc->try_config.bpp = 16; + isc->try_config.bpp_v4l2 = 16; + break; + default: + return -EINVAL; + } + + if (direct_dump) { + isc->try_config.rlp_cfg_mode = ISC_RLP_CFG_MODE_DAT8; + isc->try_config.dcfg_imode = ISC_DCFG_IMODE_PACKED8; + isc->try_config.dctrl_dview = ISC_DCTRL_DVIEW_PACKED; + return 0; + } + + return 0; +} + +/* + * Configuring pipeline modules, depending on which format the ISC outputs + * and considering which format it has as input from the sensor. + */ +static int isc_try_configure_pipeline(struct isc_device *isc) +{ + switch (isc->try_config.fourcc) { + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_ARGB555: + case V4L2_PIX_FMT_ARGB444: + case V4L2_PIX_FMT_ABGR32: + case V4L2_PIX_FMT_XBGR32: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + WB_ENABLE | GAM_ENABLES | DPC_BLCENABLE | + CC_ENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + case V4L2_PIX_FMT_YUV420: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + CSC_ENABLE | GAM_ENABLES | WB_ENABLE | + SUB420_ENABLE | SUB422_ENABLE | CBC_ENABLE | + DPC_BLCENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + case V4L2_PIX_FMT_YUV422P: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + CSC_ENABLE | WB_ENABLE | GAM_ENABLES | + SUB422_ENABLE | CBC_ENABLE | DPC_BLCENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + CSC_ENABLE | WB_ENABLE | GAM_ENABLES | + SUB422_ENABLE | CBC_ENABLE | DPC_BLCENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y16: + /* if sensor format is RAW, we convert inside ISC */ + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) { + isc->try_config.bits_pipeline = CFA_ENABLE | + CSC_ENABLE | WB_ENABLE | GAM_ENABLES | + CBC_ENABLE | DPC_BLCENABLE; + } else { + isc->try_config.bits_pipeline = 0x0; + } + break; + default: + if (ISC_IS_FORMAT_RAW(isc->try_config.sd_format->mbus_code)) + isc->try_config.bits_pipeline = WB_ENABLE | DPC_BLCENABLE; + else + isc->try_config.bits_pipeline = 0x0; + } + + /* Tune the pipeline to product specific */ + isc->adapt_pipeline(isc); + + return 0; +} + +static void isc_try_fse(struct isc_device *isc, + struct v4l2_subdev_state *sd_state) +{ + int ret; + struct v4l2_subdev_frame_size_enum fse = {}; + + /* + * If we do not know yet which format the subdev is using, we cannot + * do anything. + */ + if (!isc->try_config.sd_format) + return; + + fse.code = isc->try_config.sd_format->mbus_code; + fse.which = V4L2_SUBDEV_FORMAT_TRY; + + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, enum_frame_size, + sd_state, &fse); + /* + * Attempt to obtain format size from subdev. If not available, + * just use the maximum ISC can receive. + */ + if (ret) { + sd_state->pads->try_crop.width = isc->max_width; + sd_state->pads->try_crop.height = isc->max_height; + } else { + sd_state->pads->try_crop.width = fse.max_width; + sd_state->pads->try_crop.height = fse.max_height; + } +} + +static int isc_try_fmt(struct isc_device *isc, struct v4l2_format *f, + u32 *code) +{ + int i; + struct isc_format *sd_fmt = NULL, *direct_fmt = NULL; + struct v4l2_pix_format *pixfmt = &f->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg = {}; + struct v4l2_subdev_state pad_state = { + .pads = &pad_cfg + }; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + u32 mbus_code; + int ret; + bool rlp_dma_direct_dump = false; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* Step 1: find a RAW format that is supported */ + for (i = 0; i < isc->num_user_formats; i++) { + if (ISC_IS_FORMAT_RAW(isc->user_formats[i]->mbus_code)) { + sd_fmt = isc->user_formats[i]; + break; + } + } + /* Step 2: We can continue with this RAW format, or we can look + * for better: maybe sensor supports directly what we need. + */ + direct_fmt = find_format_by_fourcc(isc, pixfmt->pixelformat); + + /* Step 3: We have both. We decide given the module parameter which + * one to use. + */ + if (direct_fmt && sd_fmt && sensor_preferred) + sd_fmt = direct_fmt; + + /* Step 4: we do not have RAW but we have a direct format. Use it. */ + if (direct_fmt && !sd_fmt) + sd_fmt = direct_fmt; + + /* Step 5: if we are using a direct format, we need to package + * everything as 8 bit data and just dump it + */ + if (sd_fmt == direct_fmt) + rlp_dma_direct_dump = true; + + /* Step 6: We have no format. This can happen if the userspace + * requests some weird/invalid format. + * In this case, default to whatever we have + */ + if (!sd_fmt && !direct_fmt) { + sd_fmt = isc->user_formats[isc->num_user_formats - 1]; + v4l2_dbg(1, debug, &isc->v4l2_dev, + "Sensor not supporting %.4s, using %.4s\n", + (char *)&pixfmt->pixelformat, (char *)&sd_fmt->fourcc); + } + + if (!sd_fmt) { + ret = -EINVAL; + goto isc_try_fmt_err; + } + + /* Step 7: Print out what we decided for debugging */ + v4l2_dbg(1, debug, &isc->v4l2_dev, + "Preferring to have sensor using format %.4s\n", + (char *)&sd_fmt->fourcc); + + /* Step 8: at this moment we decided which format the subdev will use */ + isc->try_config.sd_format = sd_fmt; + + /* Limit to Atmel ISC hardware capabilities */ + if (pixfmt->width > isc->max_width) + pixfmt->width = isc->max_width; + if (pixfmt->height > isc->max_height) + pixfmt->height = isc->max_height; + + /* + * The mbus format is the one the subdev outputs. + * The pixels will be transferred in this format Sensor -> ISC + */ + mbus_code = sd_fmt->mbus_code; + + /* + * Validate formats. If the required format is not OK, default to raw. + */ + + isc->try_config.fourcc = pixfmt->pixelformat; + + if (isc_try_validate_formats(isc)) { + pixfmt->pixelformat = isc->try_config.fourcc = sd_fmt->fourcc; + /* Re-try to validate the new format */ + ret = isc_try_validate_formats(isc); + if (ret) + goto isc_try_fmt_err; + } + + ret = isc_try_configure_rlp_dma(isc, rlp_dma_direct_dump); + if (ret) + goto isc_try_fmt_err; + + ret = isc_try_configure_pipeline(isc); + if (ret) + goto isc_try_fmt_err; + + /* Obtain frame sizes if possible to have crop requirements ready */ + isc_try_fse(isc, &pad_state); + + v4l2_fill_mbus_format(&format.format, pixfmt, mbus_code); + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, set_fmt, + &pad_state, &format); + if (ret < 0) + goto isc_try_fmt_subdev_err; + + v4l2_fill_pix_format(pixfmt, &format.format); + + /* Limit to Atmel ISC hardware capabilities */ + if (pixfmt->width > isc->max_width) + pixfmt->width = isc->max_width; + if (pixfmt->height > isc->max_height) + pixfmt->height = isc->max_height; + + pixfmt->field = V4L2_FIELD_NONE; + pixfmt->bytesperline = (pixfmt->width * isc->try_config.bpp_v4l2) >> 3; + pixfmt->sizeimage = ((pixfmt->width * isc->try_config.bpp) >> 3) * + pixfmt->height; + + if (code) + *code = mbus_code; + + return 0; + +isc_try_fmt_err: + v4l2_err(&isc->v4l2_dev, "Could not find any possible format for a working pipeline\n"); +isc_try_fmt_subdev_err: + memset(&isc->try_config, 0, sizeof(isc->try_config)); + + return ret; +} + +static int isc_set_fmt(struct isc_device *isc, struct v4l2_format *f) +{ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + u32 mbus_code = 0; + int ret; + + ret = isc_try_fmt(isc, f, &mbus_code); + if (ret) + return ret; + + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, mbus_code); + ret = v4l2_subdev_call(isc->current_subdev->sd, pad, + set_fmt, NULL, &format); + if (ret < 0) + return ret; + + /* Limit to Atmel ISC hardware capabilities */ + if (f->fmt.pix.width > isc->max_width) + f->fmt.pix.width = isc->max_width; + if (f->fmt.pix.height > isc->max_height) + f->fmt.pix.height = isc->max_height; + + isc->fmt = *f; + + if (isc->try_config.sd_format && isc->config.sd_format && + isc->try_config.sd_format != isc->config.sd_format) { + isc->ctrls.hist_stat = HIST_INIT; + isc_reset_awb_ctrls(isc); + isc_update_v4l2_ctrls(isc); + } + /* make the try configuration active */ + isc->config = isc->try_config; + + v4l2_dbg(1, debug, &isc->v4l2_dev, "New ISC configuration in place\n"); + + return 0; +} + +static int isc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isc_device *isc = video_drvdata(file); + + if (vb2_is_busy(&isc->vb2_vidq)) + return -EBUSY; + + return isc_set_fmt(isc, f); +} + +static int isc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct isc_device *isc = video_drvdata(file); + + return isc_try_fmt(isc, f, NULL); +} + +static int isc_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + if (inp->index != 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = 0; + strscpy(inp->name, "Camera", sizeof(inp->name)); + + return 0; +} + +static int isc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int isc_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + + return 0; +} + +static int isc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct isc_device *isc = video_drvdata(file); + + return v4l2_g_parm_cap(video_devdata(file), isc->current_subdev->sd, a); +} + +static int isc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct isc_device *isc = video_drvdata(file); + + return v4l2_s_parm_cap(video_devdata(file), isc->current_subdev->sd, a); +} + +static int isc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct isc_device *isc = video_drvdata(file); + int ret = -EINVAL; + int i; + + if (fsize->index) + return -EINVAL; + + for (i = 0; i < isc->num_user_formats; i++) + if (isc->user_formats[i]->fourcc == fsize->pixel_format) + ret = 0; + + for (i = 0; i < isc->controller_formats_size; i++) + if (isc->controller_formats[i].fourcc == fsize->pixel_format) + ret = 0; + + if (ret) + return ret; + + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + + fsize->stepwise.min_width = 16; + fsize->stepwise.max_width = isc->max_width; + fsize->stepwise.min_height = 16; + fsize->stepwise.max_height = isc->max_height; + fsize->stepwise.step_width = 1; + fsize->stepwise.step_height = 1; + + return 0; +} + +static const struct v4l2_ioctl_ops isc_ioctl_ops = { + .vidioc_querycap = isc_querycap, + .vidioc_enum_fmt_vid_cap = isc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = isc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = isc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = isc_try_fmt_vid_cap, + + .vidioc_enum_input = isc_enum_input, + .vidioc_g_input = isc_g_input, + .vidioc_s_input = isc_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_parm = isc_g_parm, + .vidioc_s_parm = isc_s_parm, + .vidioc_enum_framesizes = isc_enum_framesizes, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int isc_open(struct file *file) +{ + struct isc_device *isc = video_drvdata(file); + struct v4l2_subdev *sd = isc->current_subdev->sd; + int ret; + + if (mutex_lock_interruptible(&isc->lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + if (!v4l2_fh_is_singular_file(file)) + goto unlock; + + ret = v4l2_subdev_call(sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) { + v4l2_fh_release(file); + goto unlock; + } + + ret = isc_set_fmt(isc, &isc->fmt); + if (ret) { + v4l2_subdev_call(sd, core, s_power, 0); + v4l2_fh_release(file); + } + +unlock: + mutex_unlock(&isc->lock); + return ret; +} + +static int isc_release(struct file *file) +{ + struct isc_device *isc = video_drvdata(file); + struct v4l2_subdev *sd = isc->current_subdev->sd; + bool fh_singular; + int ret; + + mutex_lock(&isc->lock); + + fh_singular = v4l2_fh_is_singular_file(file); + + ret = _vb2_fop_release(file, NULL); + + if (fh_singular) + v4l2_subdev_call(sd, core, s_power, 0); + + mutex_unlock(&isc->lock); + + return ret; +} + +static const struct v4l2_file_operations isc_fops = { + .owner = THIS_MODULE, + .open = isc_open, + .release = isc_release, + .unlocked_ioctl = video_ioctl2, + .read = vb2_fop_read, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +irqreturn_t isc_interrupt(int irq, void *dev_id) +{ + struct isc_device *isc = (struct isc_device *)dev_id; + struct regmap *regmap = isc->regmap; + u32 isc_intsr, isc_intmask, pending; + irqreturn_t ret = IRQ_NONE; + + regmap_read(regmap, ISC_INTSR, &isc_intsr); + regmap_read(regmap, ISC_INTMASK, &isc_intmask); + + pending = isc_intsr & isc_intmask; + + if (likely(pending & ISC_INT_DDONE)) { + spin_lock(&isc->dma_queue_lock); + if (isc->cur_frm) { + struct vb2_v4l2_buffer *vbuf = &isc->cur_frm->vb; + struct vb2_buffer *vb = &vbuf->vb2_buf; + + vb->timestamp = ktime_get_ns(); + vbuf->sequence = isc->sequence++; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + isc->cur_frm = NULL; + } + + if (!list_empty(&isc->dma_queue) && !isc->stop) { + isc->cur_frm = list_first_entry(&isc->dma_queue, + struct isc_buffer, list); + list_del(&isc->cur_frm->list); + + isc_start_dma(isc); + } + + if (isc->stop) + complete(&isc->comp); + + ret = IRQ_HANDLED; + spin_unlock(&isc->dma_queue_lock); + } + + if (pending & ISC_INT_HISDONE) { + schedule_work(&isc->awb_work); + ret = IRQ_HANDLED; + } + + return ret; +} +EXPORT_SYMBOL_GPL(isc_interrupt); + +static void isc_hist_count(struct isc_device *isc, u32 *min, u32 *max) +{ + struct regmap *regmap = isc->regmap; + struct isc_ctrls *ctrls = &isc->ctrls; + u32 *hist_count = &ctrls->hist_count[ctrls->hist_id]; + u32 *hist_entry = &ctrls->hist_entry[0]; + u32 i; + + *min = 0; + *max = HIST_ENTRIES; + + regmap_bulk_read(regmap, ISC_HIS_ENTRY + isc->offsets.his_entry, + hist_entry, HIST_ENTRIES); + + *hist_count = 0; + /* + * we deliberately ignore the end of the histogram, + * the most white pixels + */ + for (i = 1; i < HIST_ENTRIES; i++) { + if (*hist_entry && !*min) + *min = i; + if (*hist_entry) + *max = i; + *hist_count += i * (*hist_entry++); + } + + if (!*min) + *min = 1; + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb: hist_id %u, hist_count %u", + ctrls->hist_id, *hist_count); +} + +static void isc_wb_update(struct isc_ctrls *ctrls) +{ + struct isc_device *isc = container_of(ctrls, struct isc_device, ctrls); + u32 *hist_count = &ctrls->hist_count[0]; + u32 c, offset[4]; + u64 avg = 0; + /* We compute two gains, stretch gain and grey world gain */ + u32 s_gain[4], gw_gain[4]; + + /* + * According to Grey World, we need to set gains for R/B to normalize + * them towards the green channel. + * Thus we want to keep Green as fixed and adjust only Red/Blue + * Compute the average of the both green channels first + */ + avg = (u64)hist_count[ISC_HIS_CFG_MODE_GR] + + (u64)hist_count[ISC_HIS_CFG_MODE_GB]; + avg >>= 1; + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb: green components average %llu\n", avg); + + /* Green histogram is null, nothing to do */ + if (!avg) + return; + + for (c = ISC_HIS_CFG_MODE_GR; c <= ISC_HIS_CFG_MODE_B; c++) { + /* + * the color offset is the minimum value of the histogram. + * we stretch this color to the full range by substracting + * this value from the color component. + */ + offset[c] = ctrls->hist_minmax[c][HIST_MIN_INDEX]; + /* + * The offset is always at least 1. If the offset is 1, we do + * not need to adjust it, so our result must be zero. + * the offset is computed in a histogram on 9 bits (0..512) + * but the offset in register is based on + * 12 bits pipeline (0..4096). + * we need to shift with the 3 bits that the histogram is + * ignoring + */ + ctrls->offset[c] = (offset[c] - 1) << 3; + + /* + * the offset is then taken and converted to 2's complements, + * and must be negative, as we subtract this value from the + * color components + */ + ctrls->offset[c] = -ctrls->offset[c]; + + /* + * the stretch gain is the total number of histogram bins + * divided by the actual range of color component (Max - Min) + * If we compute gain like this, the actual color component + * will be stretched to the full histogram. + * We need to shift 9 bits for precision, we have 9 bits for + * decimals + */ + s_gain[c] = (HIST_ENTRIES << 9) / + (ctrls->hist_minmax[c][HIST_MAX_INDEX] - + ctrls->hist_minmax[c][HIST_MIN_INDEX] + 1); + + /* + * Now we have to compute the gain w.r.t. the average. + * Add/lose gain to the component towards the average. + * If it happens that the component is zero, use the + * fixed point value : 1.0 gain. + */ + if (hist_count[c]) + gw_gain[c] = div_u64(avg << 9, hist_count[c]); + else + gw_gain[c] = 1 << 9; + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb: component %d, s_gain %u, gw_gain %u\n", + c, s_gain[c], gw_gain[c]); + /* multiply both gains and adjust for decimals */ + ctrls->gain[c] = s_gain[c] * gw_gain[c]; + ctrls->gain[c] >>= 9; + + /* make sure we are not out of range */ + ctrls->gain[c] = clamp_val(ctrls->gain[c], 0, GENMASK(12, 0)); + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb: component %d, final gain %u\n", + c, ctrls->gain[c]); + } +} + +static void isc_awb_work(struct work_struct *w) +{ + struct isc_device *isc = + container_of(w, struct isc_device, awb_work); + struct regmap *regmap = isc->regmap; + struct isc_ctrls *ctrls = &isc->ctrls; + u32 hist_id = ctrls->hist_id; + u32 baysel; + unsigned long flags; + u32 min, max; + int ret; + + if (ctrls->hist_stat != HIST_ENABLED) + return; + + isc_hist_count(isc, &min, &max); + + v4l2_dbg(1, debug, &isc->v4l2_dev, + "isc wb mode %d: hist min %u , max %u\n", hist_id, min, max); + + ctrls->hist_minmax[hist_id][HIST_MIN_INDEX] = min; + ctrls->hist_minmax[hist_id][HIST_MAX_INDEX] = max; + + if (hist_id != ISC_HIS_CFG_MODE_B) { + hist_id++; + } else { + isc_wb_update(ctrls); + hist_id = ISC_HIS_CFG_MODE_GR; + } + + ctrls->hist_id = hist_id; + baysel = isc->config.sd_format->cfa_baycfg << ISC_HIS_CFG_BAYSEL_SHIFT; + + ret = pm_runtime_resume_and_get(isc->dev); + if (ret < 0) + return; + + /* + * only update if we have all the required histograms and controls + * if awb has been disabled, we need to reset registers as well. + */ + if (hist_id == ISC_HIS_CFG_MODE_GR || ctrls->awb == ISC_WB_NONE) { + /* + * It may happen that DMA Done IRQ will trigger while we are + * updating white balance registers here. + * In that case, only parts of the controls have been updated. + * We can avoid that by locking the section. + */ + spin_lock_irqsave(&isc->awb_lock, flags); + isc_update_awb_ctrls(isc); + spin_unlock_irqrestore(&isc->awb_lock, flags); + + /* + * if we are doing just the one time white balance adjustment, + * we are basically done. + */ + if (ctrls->awb == ISC_WB_ONETIME) { + v4l2_info(&isc->v4l2_dev, + "Completed one time white-balance adjustment.\n"); + /* update the v4l2 controls values */ + isc_update_v4l2_ctrls(isc); + ctrls->awb = ISC_WB_NONE; + } + } + regmap_write(regmap, ISC_HIS_CFG + isc->offsets.his, + hist_id | baysel | ISC_HIS_CFG_RAR); + + /* + * We have to make sure the streaming has not stopped meanwhile. + * ISC requires a frame to clock the internal profile update. + * To avoid issues, lock the sequence with a mutex + */ + mutex_lock(&isc->awb_mutex); + + /* streaming is not active anymore */ + if (isc->stop) { + mutex_unlock(&isc->awb_mutex); + return; + } + + isc_update_profile(isc); + + mutex_unlock(&isc->awb_mutex); + + /* if awb has been disabled, we don't need to start another histogram */ + if (ctrls->awb) + regmap_write(regmap, ISC_CTRLEN, ISC_CTRL_HISREQ); + + pm_runtime_put_sync(isc->dev); +} + +static int isc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct isc_device *isc = container_of(ctrl->handler, + struct isc_device, ctrls.handler); + struct isc_ctrls *ctrls = &isc->ctrls; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_BRIGHTNESS: + ctrls->brightness = ctrl->val & ISC_CBC_BRIGHT_MASK; + break; + case V4L2_CID_CONTRAST: + ctrls->contrast = ctrl->val & ISC_CBC_CONTRAST_MASK; + break; + case V4L2_CID_GAMMA: + ctrls->gamma_index = ctrl->val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops isc_ctrl_ops = { + .s_ctrl = isc_s_ctrl, +}; + +static int isc_s_awb_ctrl(struct v4l2_ctrl *ctrl) +{ + struct isc_device *isc = container_of(ctrl->handler, + struct isc_device, ctrls.handler); + struct isc_ctrls *ctrls = &isc->ctrls; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + if (ctrl->val == 1) + ctrls->awb = ISC_WB_AUTO; + else + ctrls->awb = ISC_WB_NONE; + + /* configure the controls with new values from v4l2 */ + if (ctrl->cluster[ISC_CTRL_R_GAIN]->is_new) + ctrls->gain[ISC_HIS_CFG_MODE_R] = isc->r_gain_ctrl->val; + if (ctrl->cluster[ISC_CTRL_B_GAIN]->is_new) + ctrls->gain[ISC_HIS_CFG_MODE_B] = isc->b_gain_ctrl->val; + if (ctrl->cluster[ISC_CTRL_GR_GAIN]->is_new) + ctrls->gain[ISC_HIS_CFG_MODE_GR] = isc->gr_gain_ctrl->val; + if (ctrl->cluster[ISC_CTRL_GB_GAIN]->is_new) + ctrls->gain[ISC_HIS_CFG_MODE_GB] = isc->gb_gain_ctrl->val; + + if (ctrl->cluster[ISC_CTRL_R_OFF]->is_new) + ctrls->offset[ISC_HIS_CFG_MODE_R] = isc->r_off_ctrl->val; + if (ctrl->cluster[ISC_CTRL_B_OFF]->is_new) + ctrls->offset[ISC_HIS_CFG_MODE_B] = isc->b_off_ctrl->val; + if (ctrl->cluster[ISC_CTRL_GR_OFF]->is_new) + ctrls->offset[ISC_HIS_CFG_MODE_GR] = isc->gr_off_ctrl->val; + if (ctrl->cluster[ISC_CTRL_GB_OFF]->is_new) + ctrls->offset[ISC_HIS_CFG_MODE_GB] = isc->gb_off_ctrl->val; + + isc_update_awb_ctrls(isc); + + mutex_lock(&isc->awb_mutex); + if (vb2_is_streaming(&isc->vb2_vidq)) { + /* + * If we are streaming, we can update profile to + * have the new settings in place. + */ + isc_update_profile(isc); + } else { + /* + * The auto cluster will activate automatically this + * control. This has to be deactivated when not + * streaming. + */ + v4l2_ctrl_activate(isc->do_wb_ctrl, false); + } + mutex_unlock(&isc->awb_mutex); + + /* if we have autowhitebalance on, start histogram procedure */ + if (ctrls->awb == ISC_WB_AUTO && + vb2_is_streaming(&isc->vb2_vidq) && + ISC_IS_FORMAT_RAW(isc->config.sd_format->mbus_code)) + isc_set_histogram(isc, true); + + /* + * for one time whitebalance adjustment, check the button, + * if it's pressed, perform the one time operation. + */ + if (ctrls->awb == ISC_WB_NONE && + ctrl->cluster[ISC_CTRL_DO_WB]->is_new && + !(ctrl->cluster[ISC_CTRL_DO_WB]->flags & + V4L2_CTRL_FLAG_INACTIVE)) { + ctrls->awb = ISC_WB_ONETIME; + isc_set_histogram(isc, true); + v4l2_dbg(1, debug, &isc->v4l2_dev, + "One time white-balance started.\n"); + } + return 0; + } + return 0; +} + +static int isc_g_volatile_awb_ctrl(struct v4l2_ctrl *ctrl) +{ + struct isc_device *isc = container_of(ctrl->handler, + struct isc_device, ctrls.handler); + struct isc_ctrls *ctrls = &isc->ctrls; + + switch (ctrl->id) { + /* being a cluster, this id will be called for every control */ + case V4L2_CID_AUTO_WHITE_BALANCE: + ctrl->cluster[ISC_CTRL_R_GAIN]->val = + ctrls->gain[ISC_HIS_CFG_MODE_R]; + ctrl->cluster[ISC_CTRL_B_GAIN]->val = + ctrls->gain[ISC_HIS_CFG_MODE_B]; + ctrl->cluster[ISC_CTRL_GR_GAIN]->val = + ctrls->gain[ISC_HIS_CFG_MODE_GR]; + ctrl->cluster[ISC_CTRL_GB_GAIN]->val = + ctrls->gain[ISC_HIS_CFG_MODE_GB]; + + ctrl->cluster[ISC_CTRL_R_OFF]->val = + ctrls->offset[ISC_HIS_CFG_MODE_R]; + ctrl->cluster[ISC_CTRL_B_OFF]->val = + ctrls->offset[ISC_HIS_CFG_MODE_B]; + ctrl->cluster[ISC_CTRL_GR_OFF]->val = + ctrls->offset[ISC_HIS_CFG_MODE_GR]; + ctrl->cluster[ISC_CTRL_GB_OFF]->val = + ctrls->offset[ISC_HIS_CFG_MODE_GB]; + break; + } + return 0; +} + +static const struct v4l2_ctrl_ops isc_awb_ops = { + .s_ctrl = isc_s_awb_ctrl, + .g_volatile_ctrl = isc_g_volatile_awb_ctrl, +}; + +#define ISC_CTRL_OFF(_name, _id, _name_str) \ + static const struct v4l2_ctrl_config _name = { \ + .ops = &isc_awb_ops, \ + .id = _id, \ + .name = _name_str, \ + .type = V4L2_CTRL_TYPE_INTEGER, \ + .flags = V4L2_CTRL_FLAG_SLIDER, \ + .min = -4095, \ + .max = 4095, \ + .step = 1, \ + .def = 0, \ + } + +ISC_CTRL_OFF(isc_r_off_ctrl, ISC_CID_R_OFFSET, "Red Component Offset"); +ISC_CTRL_OFF(isc_b_off_ctrl, ISC_CID_B_OFFSET, "Blue Component Offset"); +ISC_CTRL_OFF(isc_gr_off_ctrl, ISC_CID_GR_OFFSET, "Green Red Component Offset"); +ISC_CTRL_OFF(isc_gb_off_ctrl, ISC_CID_GB_OFFSET, "Green Blue Component Offset"); + +#define ISC_CTRL_GAIN(_name, _id, _name_str) \ + static const struct v4l2_ctrl_config _name = { \ + .ops = &isc_awb_ops, \ + .id = _id, \ + .name = _name_str, \ + .type = V4L2_CTRL_TYPE_INTEGER, \ + .flags = V4L2_CTRL_FLAG_SLIDER, \ + .min = 0, \ + .max = 8191, \ + .step = 1, \ + .def = 512, \ + } + +ISC_CTRL_GAIN(isc_r_gain_ctrl, ISC_CID_R_GAIN, "Red Component Gain"); +ISC_CTRL_GAIN(isc_b_gain_ctrl, ISC_CID_B_GAIN, "Blue Component Gain"); +ISC_CTRL_GAIN(isc_gr_gain_ctrl, ISC_CID_GR_GAIN, "Green Red Component Gain"); +ISC_CTRL_GAIN(isc_gb_gain_ctrl, ISC_CID_GB_GAIN, "Green Blue Component Gain"); + +static int isc_ctrl_init(struct isc_device *isc) +{ + const struct v4l2_ctrl_ops *ops = &isc_ctrl_ops; + struct isc_ctrls *ctrls = &isc->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + int ret; + + ctrls->hist_stat = HIST_INIT; + isc_reset_awb_ctrls(isc); + + ret = v4l2_ctrl_handler_init(hdl, 13); + if (ret < 0) + return ret; + + /* Initialize product specific controls. For example, contrast */ + isc->config_ctrls(isc, ops); + + ctrls->brightness = 0; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_BRIGHTNESS, -1024, 1023, 1, 0); + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_GAMMA, 0, isc->gamma_max, 1, + isc->gamma_max); + isc->awb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops, + V4L2_CID_AUTO_WHITE_BALANCE, + 0, 1, 1, 1); + + /* do_white_balance is a button, so min,max,step,default are ignored */ + isc->do_wb_ctrl = v4l2_ctrl_new_std(hdl, &isc_awb_ops, + V4L2_CID_DO_WHITE_BALANCE, + 0, 0, 0, 0); + + if (!isc->do_wb_ctrl) { + ret = hdl->error; + v4l2_ctrl_handler_free(hdl); + return ret; + } + + v4l2_ctrl_activate(isc->do_wb_ctrl, false); + + isc->r_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_r_gain_ctrl, NULL); + isc->b_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_b_gain_ctrl, NULL); + isc->gr_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gr_gain_ctrl, NULL); + isc->gb_gain_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gb_gain_ctrl, NULL); + isc->r_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_r_off_ctrl, NULL); + isc->b_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_b_off_ctrl, NULL); + isc->gr_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gr_off_ctrl, NULL); + isc->gb_off_ctrl = v4l2_ctrl_new_custom(hdl, &isc_gb_off_ctrl, NULL); + + /* + * The cluster is in auto mode with autowhitebalance enabled + * and manual mode otherwise. + */ + v4l2_ctrl_auto_cluster(10, &isc->awb_ctrl, 0, true); + + v4l2_ctrl_handler_setup(hdl); + + return 0; +} + +static int isc_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct isc_device *isc = container_of(notifier->v4l2_dev, + struct isc_device, v4l2_dev); + struct isc_subdev_entity *subdev_entity = + container_of(notifier, struct isc_subdev_entity, notifier); + + if (video_is_registered(&isc->video_dev)) { + v4l2_err(&isc->v4l2_dev, "only supports one sub-device.\n"); + return -EBUSY; + } + + subdev_entity->sd = subdev; + + return 0; +} + +static void isc_async_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct isc_device *isc = container_of(notifier->v4l2_dev, + struct isc_device, v4l2_dev); + mutex_destroy(&isc->awb_mutex); + cancel_work_sync(&isc->awb_work); + video_unregister_device(&isc->video_dev); + v4l2_ctrl_handler_free(&isc->ctrls.handler); +} + +static struct isc_format *find_format_by_code(struct isc_device *isc, + unsigned int code, int *index) +{ + struct isc_format *fmt = &isc->formats_list[0]; + unsigned int i; + + for (i = 0; i < isc->formats_list_size; i++) { + if (fmt->mbus_code == code) { + *index = i; + return fmt; + } + + fmt++; + } + + return NULL; +} + +static int isc_formats_init(struct isc_device *isc) +{ + struct isc_format *fmt; + struct v4l2_subdev *subdev = isc->current_subdev->sd; + unsigned int num_fmts, i, j; + u32 list_size = isc->formats_list_size; + struct v4l2_subdev_mbus_code_enum mbus_code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + num_fmts = 0; + while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, + NULL, &mbus_code)) { + mbus_code.index++; + + fmt = find_format_by_code(isc, mbus_code.code, &i); + if (!fmt) { + v4l2_warn(&isc->v4l2_dev, "Mbus code %x not supported\n", + mbus_code.code); + continue; + } + + fmt->sd_support = true; + num_fmts++; + } + + if (!num_fmts) + return -ENXIO; + + isc->num_user_formats = num_fmts; + isc->user_formats = devm_kcalloc(isc->dev, + num_fmts, sizeof(*isc->user_formats), + GFP_KERNEL); + if (!isc->user_formats) + return -ENOMEM; + + fmt = &isc->formats_list[0]; + for (i = 0, j = 0; i < list_size; i++) { + if (fmt->sd_support) + isc->user_formats[j++] = fmt; + fmt++; + } + + return 0; +} + +static int isc_set_default_fmt(struct isc_device *isc) +{ + struct v4l2_format f = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .field = V4L2_FIELD_NONE, + .pixelformat = isc->user_formats[0]->fourcc, + }, + }; + int ret; + + ret = isc_try_fmt(isc, &f, NULL); + if (ret) + return ret; + + isc->fmt = f; + return 0; +} + +static int isc_async_complete(struct v4l2_async_notifier *notifier) +{ + struct isc_device *isc = container_of(notifier->v4l2_dev, + struct isc_device, v4l2_dev); + struct video_device *vdev = &isc->video_dev; + struct vb2_queue *q = &isc->vb2_vidq; + int ret = 0; + + INIT_WORK(&isc->awb_work, isc_awb_work); + + ret = v4l2_device_register_subdev_nodes(&isc->v4l2_dev); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, "Failed to register subdev nodes\n"); + return ret; + } + + isc->current_subdev = container_of(notifier, + struct isc_subdev_entity, notifier); + mutex_init(&isc->lock); + mutex_init(&isc->awb_mutex); + + init_completion(&isc->comp); + + /* Initialize videobuf2 queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + q->drv_priv = isc; + q->buf_struct_size = sizeof(struct isc_buffer); + q->ops = &isc_vb2_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &isc->lock; + q->min_buffers_needed = 1; + q->dev = isc->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, + "vb2_queue_init() failed: %d\n", ret); + goto isc_async_complete_err; + } + + /* Init video dma queues */ + INIT_LIST_HEAD(&isc->dma_queue); + spin_lock_init(&isc->dma_queue_lock); + spin_lock_init(&isc->awb_lock); + + ret = isc_formats_init(isc); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, + "Init format failed: %d\n", ret); + goto isc_async_complete_err; + } + + ret = isc_set_default_fmt(isc); + if (ret) { + v4l2_err(&isc->v4l2_dev, "Could not set default format\n"); + goto isc_async_complete_err; + } + + ret = isc_ctrl_init(isc); + if (ret) { + v4l2_err(&isc->v4l2_dev, "Init isc ctrols failed: %d\n", ret); + goto isc_async_complete_err; + } + + /* Register video device */ + strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name)); + vdev->release = video_device_release_empty; + vdev->fops = &isc_fops; + vdev->ioctl_ops = &isc_ioctl_ops; + vdev->v4l2_dev = &isc->v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->queue = q; + vdev->lock = &isc->lock; + vdev->ctrl_handler = &isc->ctrls.handler; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; + video_set_drvdata(vdev, isc); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + v4l2_err(&isc->v4l2_dev, + "video_register_device failed: %d\n", ret); + goto isc_async_complete_err; + } + + return 0; + +isc_async_complete_err: + mutex_destroy(&isc->awb_mutex); + mutex_destroy(&isc->lock); + return ret; +} + +const struct v4l2_async_notifier_operations isc_async_ops = { + .bound = isc_async_bound, + .unbind = isc_async_unbind, + .complete = isc_async_complete, +}; +EXPORT_SYMBOL_GPL(isc_async_ops); + +void isc_subdev_cleanup(struct isc_device *isc) +{ + struct isc_subdev_entity *subdev_entity; + + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) { + v4l2_async_nf_unregister(&subdev_entity->notifier); + v4l2_async_nf_cleanup(&subdev_entity->notifier); + } + + INIT_LIST_HEAD(&isc->subdev_entities); +} +EXPORT_SYMBOL_GPL(isc_subdev_cleanup); + +int isc_pipeline_init(struct isc_device *isc) +{ + struct device *dev = isc->dev; + struct regmap *regmap = isc->regmap; + struct regmap_field *regs; + unsigned int i; + + /* + * DPCEN-->GDCEN-->BLCEN-->WB-->CFA-->CC--> + * GAM-->VHXS-->CSC-->CBC-->SUB422-->SUB420 + */ + const struct reg_field regfields[ISC_PIPE_LINE_NODE_NUM] = { + REG_FIELD(ISC_DPC_CTRL, 0, 0), + REG_FIELD(ISC_DPC_CTRL, 1, 1), + REG_FIELD(ISC_DPC_CTRL, 2, 2), + REG_FIELD(ISC_WB_CTRL, 0, 0), + REG_FIELD(ISC_CFA_CTRL, 0, 0), + REG_FIELD(ISC_CC_CTRL, 0, 0), + REG_FIELD(ISC_GAM_CTRL, 0, 0), + REG_FIELD(ISC_GAM_CTRL, 1, 1), + REG_FIELD(ISC_GAM_CTRL, 2, 2), + REG_FIELD(ISC_GAM_CTRL, 3, 3), + REG_FIELD(ISC_VHXS_CTRL, 0, 0), + REG_FIELD(ISC_CSC_CTRL + isc->offsets.csc, 0, 0), + REG_FIELD(ISC_CBC_CTRL + isc->offsets.cbc, 0, 0), + REG_FIELD(ISC_SUB422_CTRL + isc->offsets.sub422, 0, 0), + REG_FIELD(ISC_SUB420_CTRL + isc->offsets.sub420, 0, 0), + }; + + for (i = 0; i < ISC_PIPE_LINE_NODE_NUM; i++) { + regs = devm_regmap_field_alloc(dev, regmap, regfields[i]); + if (IS_ERR(regs)) + return PTR_ERR(regs); + + isc->pipeline[i] = regs; + } + + return 0; +} +EXPORT_SYMBOL_GPL(isc_pipeline_init); + +/* regmap configuration */ +#define ATMEL_ISC_REG_MAX 0xd5c +const struct regmap_config isc_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = ATMEL_ISC_REG_MAX, +}; +EXPORT_SYMBOL_GPL(isc_regmap_config); + +MODULE_AUTHOR("Songjun Wu"); +MODULE_AUTHOR("Eugen Hristev"); +MODULE_DESCRIPTION("Atmel ISC common code base"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/atmel/atmel-isc-clk.c b/drivers/media/platform/atmel/atmel-isc-clk.c new file mode 100644 index 000000000..2059fe376 --- /dev/null +++ b/drivers/media/platform/atmel/atmel-isc-clk.c @@ -0,0 +1,311 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microchip Image Sensor Controller (ISC) common clock driver setup + * + * Copyright (C) 2016 Microchip Technology, Inc. + * + * Author: Songjun Wu + * Author: Eugen Hristev + * + */ +#include +#include +#include +#include +#include + +#include "atmel-isc-regs.h" +#include "atmel-isc.h" + +static int isc_wait_clk_stable(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + struct regmap *regmap = isc_clk->regmap; + unsigned long timeout = jiffies + usecs_to_jiffies(1000); + unsigned int status; + + while (time_before(jiffies, timeout)) { + regmap_read(regmap, ISC_CLKSR, &status); + if (!(status & ISC_CLKSR_SIP)) + return 0; + + usleep_range(10, 250); + } + + return -ETIMEDOUT; +} + +static int isc_clk_prepare(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + int ret; + + ret = pm_runtime_resume_and_get(isc_clk->dev); + if (ret < 0) + return ret; + + return isc_wait_clk_stable(hw); +} + +static void isc_clk_unprepare(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + + isc_wait_clk_stable(hw); + + pm_runtime_put_sync(isc_clk->dev); +} + +static int isc_clk_enable(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + u32 id = isc_clk->id; + struct regmap *regmap = isc_clk->regmap; + unsigned long flags; + unsigned int status; + + dev_dbg(isc_clk->dev, "ISC CLK: %s, id = %d, div = %d, parent id = %d\n", + __func__, id, isc_clk->div, isc_clk->parent_id); + + spin_lock_irqsave(&isc_clk->lock, flags); + regmap_update_bits(regmap, ISC_CLKCFG, + ISC_CLKCFG_DIV_MASK(id) | ISC_CLKCFG_SEL_MASK(id), + (isc_clk->div << ISC_CLKCFG_DIV_SHIFT(id)) | + (isc_clk->parent_id << ISC_CLKCFG_SEL_SHIFT(id))); + + regmap_write(regmap, ISC_CLKEN, ISC_CLK(id)); + spin_unlock_irqrestore(&isc_clk->lock, flags); + + regmap_read(regmap, ISC_CLKSR, &status); + if (status & ISC_CLK(id)) + return 0; + else + return -EINVAL; +} + +static void isc_clk_disable(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + u32 id = isc_clk->id; + unsigned long flags; + + spin_lock_irqsave(&isc_clk->lock, flags); + regmap_write(isc_clk->regmap, ISC_CLKDIS, ISC_CLK(id)); + spin_unlock_irqrestore(&isc_clk->lock, flags); +} + +static int isc_clk_is_enabled(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + u32 status; + int ret; + + ret = pm_runtime_resume_and_get(isc_clk->dev); + if (ret < 0) + return 0; + + regmap_read(isc_clk->regmap, ISC_CLKSR, &status); + + pm_runtime_put_sync(isc_clk->dev); + + return status & ISC_CLK(isc_clk->id) ? 1 : 0; +} + +static unsigned long +isc_clk_recalc_rate(struct clk_hw *hw, unsigned long parent_rate) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + + return DIV_ROUND_CLOSEST(parent_rate, isc_clk->div + 1); +} + +static int isc_clk_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + long best_rate = -EINVAL; + int best_diff = -1; + unsigned int i, div; + + for (i = 0; i < clk_hw_get_num_parents(hw); i++) { + struct clk_hw *parent; + unsigned long parent_rate; + + parent = clk_hw_get_parent_by_index(hw, i); + if (!parent) + continue; + + parent_rate = clk_hw_get_rate(parent); + if (!parent_rate) + continue; + + for (div = 1; div < ISC_CLK_MAX_DIV + 2; div++) { + unsigned long rate; + int diff; + + rate = DIV_ROUND_CLOSEST(parent_rate, div); + diff = abs(req->rate - rate); + + if (best_diff < 0 || best_diff > diff) { + best_rate = rate; + best_diff = diff; + req->best_parent_rate = parent_rate; + req->best_parent_hw = parent; + } + + if (!best_diff || rate < req->rate) + break; + } + + if (!best_diff) + break; + } + + dev_dbg(isc_clk->dev, + "ISC CLK: %s, best_rate = %ld, parent clk: %s @ %ld\n", + __func__, best_rate, + __clk_get_name((req->best_parent_hw)->clk), + req->best_parent_rate); + + if (best_rate < 0) + return best_rate; + + req->rate = best_rate; + + return 0; +} + +static int isc_clk_set_parent(struct clk_hw *hw, u8 index) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + + if (index >= clk_hw_get_num_parents(hw)) + return -EINVAL; + + isc_clk->parent_id = index; + + return 0; +} + +static u8 isc_clk_get_parent(struct clk_hw *hw) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + + return isc_clk->parent_id; +} + +static int isc_clk_set_rate(struct clk_hw *hw, + unsigned long rate, + unsigned long parent_rate) +{ + struct isc_clk *isc_clk = to_isc_clk(hw); + u32 div; + + if (!rate) + return -EINVAL; + + div = DIV_ROUND_CLOSEST(parent_rate, rate); + if (div > (ISC_CLK_MAX_DIV + 1) || !div) + return -EINVAL; + + isc_clk->div = div - 1; + + return 0; +} + +static const struct clk_ops isc_clk_ops = { + .prepare = isc_clk_prepare, + .unprepare = isc_clk_unprepare, + .enable = isc_clk_enable, + .disable = isc_clk_disable, + .is_enabled = isc_clk_is_enabled, + .recalc_rate = isc_clk_recalc_rate, + .determine_rate = isc_clk_determine_rate, + .set_parent = isc_clk_set_parent, + .get_parent = isc_clk_get_parent, + .set_rate = isc_clk_set_rate, +}; + +static int isc_clk_register(struct isc_device *isc, unsigned int id) +{ + struct regmap *regmap = isc->regmap; + struct device_node *np = isc->dev->of_node; + struct isc_clk *isc_clk; + struct clk_init_data init; + const char *clk_name = np->name; + const char *parent_names[3]; + int num_parents; + + if (id == ISC_ISPCK && !isc->ispck_required) + return 0; + + num_parents = of_clk_get_parent_count(np); + if (num_parents < 1 || num_parents > 3) + return -EINVAL; + + if (num_parents > 2 && id == ISC_ISPCK) + num_parents = 2; + + of_clk_parent_fill(np, parent_names, num_parents); + + if (id == ISC_MCK) + of_property_read_string(np, "clock-output-names", &clk_name); + else + clk_name = "isc-ispck"; + + init.parent_names = parent_names; + init.num_parents = num_parents; + init.name = clk_name; + init.ops = &isc_clk_ops; + init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE; + + isc_clk = &isc->isc_clks[id]; + isc_clk->hw.init = &init; + isc_clk->regmap = regmap; + isc_clk->id = id; + isc_clk->dev = isc->dev; + spin_lock_init(&isc_clk->lock); + + isc_clk->clk = clk_register(isc->dev, &isc_clk->hw); + if (IS_ERR(isc_clk->clk)) { + dev_err(isc->dev, "%s: clock register fail\n", clk_name); + return PTR_ERR(isc_clk->clk); + } else if (id == ISC_MCK) { + of_clk_add_provider(np, of_clk_src_simple_get, isc_clk->clk); + } + + return 0; +} + +int isc_clk_init(struct isc_device *isc) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) + isc->isc_clks[i].clk = ERR_PTR(-EINVAL); + + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) { + ret = isc_clk_register(isc, i); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(isc_clk_init); + +void isc_clk_cleanup(struct isc_device *isc) +{ + unsigned int i; + + of_clk_del_provider(isc->dev->of_node); + + for (i = 0; i < ARRAY_SIZE(isc->isc_clks); i++) { + struct isc_clk *isc_clk = &isc->isc_clks[i]; + + if (!IS_ERR(isc_clk->clk)) + clk_unregister(isc_clk->clk); + } +} +EXPORT_SYMBOL_GPL(isc_clk_cleanup); diff --git a/drivers/media/platform/atmel/atmel-isc-regs.h b/drivers/media/platform/atmel/atmel-isc-regs.h new file mode 100644 index 000000000..d06b72228 --- /dev/null +++ b/drivers/media/platform/atmel/atmel-isc-regs.h @@ -0,0 +1,413 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __ATMEL_ISC_REGS_H +#define __ATMEL_ISC_REGS_H + +#include + +/* ISC Control Enable Register 0 */ +#define ISC_CTRLEN 0x00000000 + +/* ISC Control Disable Register 0 */ +#define ISC_CTRLDIS 0x00000004 + +/* ISC Control Status Register 0 */ +#define ISC_CTRLSR 0x00000008 + +#define ISC_CTRL_CAPTURE BIT(0) +#define ISC_CTRL_UPPRO BIT(1) +#define ISC_CTRL_HISREQ BIT(2) +#define ISC_CTRL_HISCLR BIT(3) + +/* ISC Parallel Front End Configuration 0 Register */ +#define ISC_PFE_CFG0 0x0000000c + +#define ISC_PFE_CFG0_HPOL_LOW BIT(0) +#define ISC_PFE_CFG0_VPOL_LOW BIT(1) +#define ISC_PFE_CFG0_PPOL_LOW BIT(2) +#define ISC_PFE_CFG0_CCIR656 BIT(9) +#define ISC_PFE_CFG0_CCIR_CRC BIT(10) +#define ISC_PFE_CFG0_MIPI BIT(14) + +#define ISC_PFE_CFG0_MODE_PROGRESSIVE (0x0 << 4) +#define ISC_PFE_CFG0_MODE_MASK GENMASK(6, 4) + +#define ISC_PFE_CFG0_BPS_EIGHT (0x4 << 28) +#define ISC_PFG_CFG0_BPS_NINE (0x3 << 28) +#define ISC_PFG_CFG0_BPS_TEN (0x2 << 28) +#define ISC_PFG_CFG0_BPS_ELEVEN (0x1 << 28) +#define ISC_PFG_CFG0_BPS_TWELVE (0x0 << 28) +#define ISC_PFE_CFG0_BPS_MASK GENMASK(30, 28) + +#define ISC_PFE_CFG0_COLEN BIT(12) +#define ISC_PFE_CFG0_ROWEN BIT(13) + +/* ISC Parallel Front End Configuration 1 Register */ +#define ISC_PFE_CFG1 0x00000010 + +#define ISC_PFE_CFG1_COLMIN(v) ((v)) +#define ISC_PFE_CFG1_COLMIN_MASK GENMASK(15, 0) +#define ISC_PFE_CFG1_COLMAX(v) ((v) << 16) +#define ISC_PFE_CFG1_COLMAX_MASK GENMASK(31, 16) + +/* ISC Parallel Front End Configuration 2 Register */ +#define ISC_PFE_CFG2 0x00000014 + +#define ISC_PFE_CFG2_ROWMIN(v) ((v)) +#define ISC_PFE_CFG2_ROWMIN_MASK GENMASK(15, 0) +#define ISC_PFE_CFG2_ROWMAX(v) ((v) << 16) +#define ISC_PFE_CFG2_ROWMAX_MASK GENMASK(31, 16) + +/* ISC Clock Enable Register */ +#define ISC_CLKEN 0x00000018 + +/* ISC Clock Disable Register */ +#define ISC_CLKDIS 0x0000001c + +/* ISC Clock Status Register */ +#define ISC_CLKSR 0x00000020 +#define ISC_CLKSR_SIP BIT(31) + +#define ISC_CLK(n) BIT(n) + +/* ISC Clock Configuration Register */ +#define ISC_CLKCFG 0x00000024 +#define ISC_CLKCFG_DIV_SHIFT(n) ((n)*16) +#define ISC_CLKCFG_DIV_MASK(n) GENMASK(((n)*16 + 7), (n)*16) +#define ISC_CLKCFG_SEL_SHIFT(n) ((n)*16 + 8) +#define ISC_CLKCFG_SEL_MASK(n) GENMASK(((n)*17 + 8), ((n)*16 + 8)) + +/* ISC Interrupt Enable Register */ +#define ISC_INTEN 0x00000028 + +/* ISC Interrupt Disable Register */ +#define ISC_INTDIS 0x0000002c + +/* ISC Interrupt Mask Register */ +#define ISC_INTMASK 0x00000030 + +/* ISC Interrupt Status Register */ +#define ISC_INTSR 0x00000034 + +#define ISC_INT_DDONE BIT(8) +#define ISC_INT_HISDONE BIT(12) + +/* ISC DPC Control Register */ +#define ISC_DPC_CTRL 0x40 + +#define ISC_DPC_CTRL_DPCEN BIT(0) +#define ISC_DPC_CTRL_GDCEN BIT(1) +#define ISC_DPC_CTRL_BLCEN BIT(2) + +/* ISC DPC Config Register */ +#define ISC_DPC_CFG 0x44 + +#define ISC_DPC_CFG_BAYSEL_SHIFT 0 + +#define ISC_DPC_CFG_EITPOL BIT(4) + +#define ISC_DPC_CFG_TA_ENABLE BIT(14) +#define ISC_DPC_CFG_TC_ENABLE BIT(13) +#define ISC_DPC_CFG_TM_ENABLE BIT(12) + +#define ISC_DPC_CFG_RE_MODE BIT(17) + +#define ISC_DPC_CFG_GDCCLP_SHIFT 20 +#define ISC_DPC_CFG_GDCCLP_MASK GENMASK(22, 20) + +#define ISC_DPC_CFG_BLOFF_SHIFT 24 +#define ISC_DPC_CFG_BLOFF_MASK GENMASK(31, 24) + +#define ISC_DPC_CFG_BAYCFG_SHIFT 0 +#define ISC_DPC_CFG_BAYCFG_MASK GENMASK(1, 0) +/* ISC DPC Threshold Median Register */ +#define ISC_DPC_THRESHM 0x48 + +/* ISC DPC Threshold Closest Register */ +#define ISC_DPC_THRESHC 0x4C + +/* ISC DPC Threshold Average Register */ +#define ISC_DPC_THRESHA 0x50 + +/* ISC DPC STatus Register */ +#define ISC_DPC_SR 0x54 + +/* ISC White Balance Control Register */ +#define ISC_WB_CTRL 0x00000058 + +/* ISC White Balance Configuration Register */ +#define ISC_WB_CFG 0x0000005c + +/* ISC White Balance Offset for R, GR Register */ +#define ISC_WB_O_RGR 0x00000060 + +/* ISC White Balance Offset for B, GB Register */ +#define ISC_WB_O_BGB 0x00000064 + +/* ISC White Balance Gain for R, GR Register */ +#define ISC_WB_G_RGR 0x00000068 + +/* ISC White Balance Gain for B, GB Register */ +#define ISC_WB_G_BGB 0x0000006c + +/* ISC Color Filter Array Control Register */ +#define ISC_CFA_CTRL 0x00000070 + +/* ISC Color Filter Array Configuration Register */ +#define ISC_CFA_CFG 0x00000074 +#define ISC_CFA_CFG_EITPOL BIT(4) + +#define ISC_BAY_CFG_GRGR 0x0 +#define ISC_BAY_CFG_RGRG 0x1 +#define ISC_BAY_CFG_GBGB 0x2 +#define ISC_BAY_CFG_BGBG 0x3 + +/* ISC Color Correction Control Register */ +#define ISC_CC_CTRL 0x00000078 + +/* ISC Color Correction RR RG Register */ +#define ISC_CC_RR_RG 0x0000007c + +/* ISC Color Correction RB OR Register */ +#define ISC_CC_RB_OR 0x00000080 + +/* ISC Color Correction GR GG Register */ +#define ISC_CC_GR_GG 0x00000084 + +/* ISC Color Correction GB OG Register */ +#define ISC_CC_GB_OG 0x00000088 + +/* ISC Color Correction BR BG Register */ +#define ISC_CC_BR_BG 0x0000008c + +/* ISC Color Correction BB OB Register */ +#define ISC_CC_BB_OB 0x00000090 + +/* ISC Gamma Correction Control Register */ +#define ISC_GAM_CTRL 0x00000094 + +#define ISC_GAM_CTRL_BIPART BIT(4) + +/* ISC_Gamma Correction Blue Entry Register */ +#define ISC_GAM_BENTRY 0x00000098 + +/* ISC_Gamma Correction Green Entry Register */ +#define ISC_GAM_GENTRY 0x00000198 + +/* ISC_Gamma Correction Green Entry Register */ +#define ISC_GAM_RENTRY 0x00000298 + +/* ISC VHXS Control Register */ +#define ISC_VHXS_CTRL 0x398 + +/* ISC VHXS Source Size Register */ +#define ISC_VHXS_SS 0x39C + +/* ISC VHXS Destination Size Register */ +#define ISC_VHXS_DS 0x3A0 + +/* ISC Vertical Factor Register */ +#define ISC_VXS_FACT 0x3a4 + +/* ISC Horizontal Factor Register */ +#define ISC_HXS_FACT 0x3a8 + +/* ISC Vertical Config Register */ +#define ISC_VXS_CFG 0x3ac + +/* ISC Horizontal Config Register */ +#define ISC_HXS_CFG 0x3b0 + +/* ISC Vertical Tap Register */ +#define ISC_VXS_TAP 0x3b4 + +/* ISC Horizontal Tap Register */ +#define ISC_HXS_TAP 0x434 + +/* Offset for CSC register specific to sama5d2 product */ +#define ISC_SAMA5D2_CSC_OFFSET 0 +/* Offset for CSC register specific to sama7g5 product */ +#define ISC_SAMA7G5_CSC_OFFSET 0x11c + +/* Color Space Conversion Control Register */ +#define ISC_CSC_CTRL 0x00000398 + +/* Color Space Conversion YR YG Register */ +#define ISC_CSC_YR_YG 0x0000039c + +/* Color Space Conversion YB OY Register */ +#define ISC_CSC_YB_OY 0x000003a0 + +/* Color Space Conversion CBR CBG Register */ +#define ISC_CSC_CBR_CBG 0x000003a4 + +/* Color Space Conversion CBB OCB Register */ +#define ISC_CSC_CBB_OCB 0x000003a8 + +/* Color Space Conversion CRR CRG Register */ +#define ISC_CSC_CRR_CRG 0x000003ac + +/* Color Space Conversion CRB OCR Register */ +#define ISC_CSC_CRB_OCR 0x000003b0 + +/* Offset for CBC register specific to sama5d2 product */ +#define ISC_SAMA5D2_CBC_OFFSET 0 +/* Offset for CBC register specific to sama7g5 product */ +#define ISC_SAMA7G5_CBC_OFFSET 0x11c + +/* Contrast And Brightness Control Register */ +#define ISC_CBC_CTRL 0x000003b4 + +/* Contrast And Brightness Configuration Register */ +#define ISC_CBC_CFG 0x000003b8 + +/* Brightness Register */ +#define ISC_CBC_BRIGHT 0x000003bc +#define ISC_CBC_BRIGHT_MASK GENMASK(10, 0) + +/* Contrast Register */ +#define ISC_CBC_CONTRAST 0x000003c0 +#define ISC_CBC_CONTRAST_MASK GENMASK(11, 0) + +/* Hue Register */ +#define ISC_CBCHS_HUE 0x4e0 +/* Saturation Register */ +#define ISC_CBCHS_SAT 0x4e4 + +/* Offset for SUB422 register specific to sama5d2 product */ +#define ISC_SAMA5D2_SUB422_OFFSET 0 +/* Offset for SUB422 register specific to sama7g5 product */ +#define ISC_SAMA7G5_SUB422_OFFSET 0x124 + +/* Subsampling 4:4:4 to 4:2:2 Control Register */ +#define ISC_SUB422_CTRL 0x000003c4 + +/* Offset for SUB420 register specific to sama5d2 product */ +#define ISC_SAMA5D2_SUB420_OFFSET 0 +/* Offset for SUB420 register specific to sama7g5 product */ +#define ISC_SAMA7G5_SUB420_OFFSET 0x124 +/* Subsampling 4:2:2 to 4:2:0 Control Register */ +#define ISC_SUB420_CTRL 0x000003cc + +/* Offset for RLP register specific to sama5d2 product */ +#define ISC_SAMA5D2_RLP_OFFSET 0 +/* Offset for RLP register specific to sama7g5 product */ +#define ISC_SAMA7G5_RLP_OFFSET 0x124 +/* Rounding, Limiting and Packing Configuration Register */ +#define ISC_RLP_CFG 0x000003d0 + +#define ISC_RLP_CFG_MODE_DAT8 0x0 +#define ISC_RLP_CFG_MODE_DAT9 0x1 +#define ISC_RLP_CFG_MODE_DAT10 0x2 +#define ISC_RLP_CFG_MODE_DAT11 0x3 +#define ISC_RLP_CFG_MODE_DAT12 0x4 +#define ISC_RLP_CFG_MODE_DATY8 0x5 +#define ISC_RLP_CFG_MODE_DATY10 0x6 +#define ISC_RLP_CFG_MODE_ARGB444 0x7 +#define ISC_RLP_CFG_MODE_ARGB555 0x8 +#define ISC_RLP_CFG_MODE_RGB565 0x9 +#define ISC_RLP_CFG_MODE_ARGB32 0xa +#define ISC_RLP_CFG_MODE_YYCC 0xb +#define ISC_RLP_CFG_MODE_YYCC_LIMITED 0xc +#define ISC_RLP_CFG_MODE_YCYC 0xd +#define ISC_RLP_CFG_MODE_MASK GENMASK(3, 0) + +#define ISC_RLP_CFG_LSH BIT(5) + +#define ISC_RLP_CFG_YMODE_YUYV (3 << 6) +#define ISC_RLP_CFG_YMODE_YVYU (2 << 6) +#define ISC_RLP_CFG_YMODE_VYUY (0 << 6) +#define ISC_RLP_CFG_YMODE_UYVY (1 << 6) + +#define ISC_RLP_CFG_YMODE_MASK GENMASK(7, 6) + +/* Offset for HIS register specific to sama5d2 product */ +#define ISC_SAMA5D2_HIS_OFFSET 0 +/* Offset for HIS register specific to sama7g5 product */ +#define ISC_SAMA7G5_HIS_OFFSET 0x124 +/* Histogram Control Register */ +#define ISC_HIS_CTRL 0x000003d4 + +#define ISC_HIS_CTRL_EN BIT(0) +#define ISC_HIS_CTRL_DIS 0x0 + +/* Histogram Configuration Register */ +#define ISC_HIS_CFG 0x000003d8 + +#define ISC_HIS_CFG_MODE_GR 0x0 +#define ISC_HIS_CFG_MODE_R 0x1 +#define ISC_HIS_CFG_MODE_GB 0x2 +#define ISC_HIS_CFG_MODE_B 0x3 +#define ISC_HIS_CFG_MODE_Y 0x4 +#define ISC_HIS_CFG_MODE_RAW 0x5 +#define ISC_HIS_CFG_MODE_YCCIR656 0x6 + +#define ISC_HIS_CFG_BAYSEL_SHIFT 4 + +#define ISC_HIS_CFG_RAR BIT(8) + +/* Offset for DMA register specific to sama5d2 product */ +#define ISC_SAMA5D2_DMA_OFFSET 0 +/* Offset for DMA register specific to sama7g5 product */ +#define ISC_SAMA7G5_DMA_OFFSET 0x13c + +/* DMA Configuration Register */ +#define ISC_DCFG 0x000003e0 +#define ISC_DCFG_IMODE_PACKED8 0x0 +#define ISC_DCFG_IMODE_PACKED16 0x1 +#define ISC_DCFG_IMODE_PACKED32 0x2 +#define ISC_DCFG_IMODE_YC422SP 0x3 +#define ISC_DCFG_IMODE_YC422P 0x4 +#define ISC_DCFG_IMODE_YC420SP 0x5 +#define ISC_DCFG_IMODE_YC420P 0x6 +#define ISC_DCFG_IMODE_MASK GENMASK(2, 0) + +#define ISC_DCFG_YMBSIZE_SINGLE (0x0 << 4) +#define ISC_DCFG_YMBSIZE_BEATS4 (0x1 << 4) +#define ISC_DCFG_YMBSIZE_BEATS8 (0x2 << 4) +#define ISC_DCFG_YMBSIZE_BEATS16 (0x3 << 4) +#define ISC_DCFG_YMBSIZE_BEATS32 (0x4 << 4) +#define ISC_DCFG_YMBSIZE_MASK GENMASK(6, 4) + +#define ISC_DCFG_CMBSIZE_SINGLE (0x0 << 8) +#define ISC_DCFG_CMBSIZE_BEATS4 (0x1 << 8) +#define ISC_DCFG_CMBSIZE_BEATS8 (0x2 << 8) +#define ISC_DCFG_CMBSIZE_BEATS16 (0x3 << 8) +#define ISC_DCFG_CMBSIZE_BEATS32 (0x4 << 8) +#define ISC_DCFG_CMBSIZE_MASK GENMASK(10, 8) + +/* DMA Control Register */ +#define ISC_DCTRL 0x000003e4 + +#define ISC_DCTRL_DVIEW_PACKED (0x0 << 1) +#define ISC_DCTRL_DVIEW_SEMIPLANAR (0x1 << 1) +#define ISC_DCTRL_DVIEW_PLANAR (0x2 << 1) +#define ISC_DCTRL_DVIEW_MASK GENMASK(2, 1) + +#define ISC_DCTRL_IE_IS (0x0 << 4) + +/* DMA Descriptor Address Register */ +#define ISC_DNDA 0x000003e8 + +/* DMA Address 0 Register */ +#define ISC_DAD0 0x000003ec + +/* DMA Address 1 Register */ +#define ISC_DAD1 0x000003f4 + +/* DMA Address 2 Register */ +#define ISC_DAD2 0x000003fc + +/* Offset for version register specific to sama5d2 product */ +#define ISC_SAMA5D2_VERSION_OFFSET 0 +#define ISC_SAMA7G5_VERSION_OFFSET 0x13c +/* Version Register */ +#define ISC_VERSION 0x0000040c + +/* Offset for version register specific to sama5d2 product */ +#define ISC_SAMA5D2_HIS_ENTRY_OFFSET 0 +/* Offset for version register specific to sama7g5 product */ +#define ISC_SAMA7G5_HIS_ENTRY_OFFSET 0x14c +/* Histogram Entry */ +#define ISC_HIS_ENTRY 0x00000410 + +#endif diff --git a/drivers/media/platform/atmel/atmel-isc.h b/drivers/media/platform/atmel/atmel-isc.h new file mode 100644 index 000000000..ff60ba020 --- /dev/null +++ b/drivers/media/platform/atmel/atmel-isc.h @@ -0,0 +1,362 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Microchip Image Sensor Controller (ISC) driver header file + * + * Copyright (C) 2016-2019 Microchip Technology, Inc. + * + * Author: Songjun Wu + * Author: Eugen Hristev + * + */ +#ifndef _ATMEL_ISC_H_ + +#include +#include + +#include +#include +#include + +#define ISC_CLK_MAX_DIV 255 + +enum isc_clk_id { + ISC_ISPCK = 0, + ISC_MCK = 1, +}; + +struct isc_clk { + struct clk_hw hw; + struct clk *clk; + struct regmap *regmap; + spinlock_t lock; /* serialize access to clock registers */ + u8 id; + u8 parent_id; + u32 div; + struct device *dev; +}; + +#define to_isc_clk(v) container_of(v, struct isc_clk, hw) + +struct isc_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct isc_subdev_entity { + struct v4l2_subdev *sd; + struct v4l2_async_subdev *asd; + struct device_node *epn; + struct v4l2_async_notifier notifier; + + u32 pfe_cfg0; + + struct list_head list; +}; + +/* + * struct isc_format - ISC media bus format information + This structure represents the interface between the ISC + and the sensor. It's the input format received by + the ISC. + * @fourcc: Fourcc code for this format + * @mbus_code: V4L2 media bus format code. + * @cfa_baycfg: If this format is RAW BAYER, indicate the type of bayer. + this is either BGBG, RGRG, etc. + * @pfe_cfg0_bps: Number of hardware data lines connected to the ISC + */ + +struct isc_format { + u32 fourcc; + u32 mbus_code; + u32 cfa_baycfg; + + bool sd_support; + u32 pfe_cfg0_bps; +}; + +/* Pipeline bitmap */ +#define DPC_DPCENABLE BIT(0) +#define DPC_GDCENABLE BIT(1) +#define DPC_BLCENABLE BIT(2) +#define WB_ENABLE BIT(3) +#define CFA_ENABLE BIT(4) +#define CC_ENABLE BIT(5) +#define GAM_ENABLE BIT(6) +#define GAM_BENABLE BIT(7) +#define GAM_GENABLE BIT(8) +#define GAM_RENABLE BIT(9) +#define VHXS_ENABLE BIT(10) +#define CSC_ENABLE BIT(11) +#define CBC_ENABLE BIT(12) +#define SUB422_ENABLE BIT(13) +#define SUB420_ENABLE BIT(14) + +#define GAM_ENABLES (GAM_RENABLE | GAM_GENABLE | GAM_BENABLE | GAM_ENABLE) + +/* + * struct fmt_config - ISC format configuration and internal pipeline + This structure represents the internal configuration + of the ISC. + It also holds the format that ISC will present to v4l2. + * @sd_format: Pointer to an isc_format struct that holds the sensor + configuration. + * @fourcc: Fourcc code for this format. + * @bpp: Bytes per pixel in the current format. + * @bpp_v4l2: Bytes per pixel in the current format, for v4l2. + This differs from 'bpp' in the sense that in planar + formats, it refers only to the first plane. + * @rlp_cfg_mode: Configuration of the RLP (rounding, limiting packaging) + * @dcfg_imode: Configuration of the input of the DMA module + * @dctrl_dview: Configuration of the output of the DMA module + * @bits_pipeline: Configuration of the pipeline, which modules are enabled + */ +struct fmt_config { + struct isc_format *sd_format; + + u32 fourcc; + u8 bpp; + u8 bpp_v4l2; + + u32 rlp_cfg_mode; + u32 dcfg_imode; + u32 dctrl_dview; + + u32 bits_pipeline; +}; + +#define HIST_ENTRIES 512 +#define HIST_BAYER (ISC_HIS_CFG_MODE_B + 1) + +enum{ + HIST_INIT = 0, + HIST_ENABLED, + HIST_DISABLED, +}; + +struct isc_ctrls { + struct v4l2_ctrl_handler handler; + + u32 brightness; + u32 contrast; + u8 gamma_index; +#define ISC_WB_NONE 0 +#define ISC_WB_AUTO 1 +#define ISC_WB_ONETIME 2 + u8 awb; + + /* one for each component : GR, R, GB, B */ + u32 gain[HIST_BAYER]; + s32 offset[HIST_BAYER]; + + u32 hist_entry[HIST_ENTRIES]; + u32 hist_count[HIST_BAYER]; + u8 hist_id; + u8 hist_stat; +#define HIST_MIN_INDEX 0 +#define HIST_MAX_INDEX 1 + u32 hist_minmax[HIST_BAYER][2]; +}; + +#define ISC_PIPE_LINE_NODE_NUM 15 + +/* + * struct isc_reg_offsets - ISC device register offsets + * @csc: Offset for the CSC register + * @cbc: Offset for the CBC register + * @sub422: Offset for the SUB422 register + * @sub420: Offset for the SUB420 register + * @rlp: Offset for the RLP register + * @his: Offset for the HIS related registers + * @dma: Offset for the DMA related registers + * @version: Offset for the version register + * @his_entry: Offset for the HIS entries registers + */ +struct isc_reg_offsets { + u32 csc; + u32 cbc; + u32 sub422; + u32 sub420; + u32 rlp; + u32 his; + u32 dma; + u32 version; + u32 his_entry; +}; + +/* + * struct isc_device - ISC device driver data/config struct + * @regmap: Register map + * @hclock: Hclock clock input (refer datasheet) + * @ispck: iscpck clock (refer datasheet) + * @isc_clks: ISC clocks + * @ispck_required: ISC requires ISP Clock initialization + * @dcfg: DMA master configuration, architecture dependent + * + * @dev: Registered device driver + * @v4l2_dev: v4l2 registered device + * @video_dev: registered video device + * + * @vb2_vidq: video buffer 2 video queue + * @dma_queue_lock: lock to serialize the dma buffer queue + * @dma_queue: the queue for dma buffers + * @cur_frm: current isc frame/buffer + * @sequence: current frame number + * @stop: true if isc is not streaming, false if streaming + * @comp: completion reference that signals frame completion + * + * @fmt: current v42l format + * @user_formats: list of formats that are supported and agreed with sd + * @num_user_formats: how many formats are in user_formats + * + * @config: current ISC format configuration + * @try_config: the current ISC try format , not yet activated + * + * @ctrls: holds information about ISC controls + * @do_wb_ctrl: control regarding the DO_WHITE_BALANCE button + * @awb_work: workqueue reference for autowhitebalance histogram + * analysis + * + * @lock: lock for serializing userspace file operations + * with ISC operations + * @awb_mutex: serialize access to streaming status from awb work queue + * @awb_lock: lock for serializing awb work queue operations + * with DMA/buffer operations + * + * @pipeline: configuration of the ISC pipeline + * + * @current_subdev: current subdevice: the sensor + * @subdev_entities: list of subdevice entitites + * + * @gamma_table: pointer to the table with gamma values, has + * gamma_max sets of GAMMA_ENTRIES entries each + * @gamma_max: maximum number of sets of inside the gamma_table + * + * @max_width: maximum frame width, dependent on the internal RAM + * @max_height: maximum frame height, dependent on the internal RAM + * + * @config_dpc: pointer to a function that initializes product + * specific DPC module + * @config_csc: pointer to a function that initializes product + * specific CSC module + * @config_cbc: pointer to a function that initializes product + * specific CBC module + * @config_cc: pointer to a function that initializes product + * specific CC module + * @config_gam: pointer to a function that initializes product + * specific GAMMA module + * @config_rlp: pointer to a function that initializes product + * specific RLP module + * @config_ctrls: pointer to a functoin that initializes product + * specific v4l2 controls. + * + * @adapt_pipeline: pointer to a function that adapts the pipeline bits + * to the product specific pipeline + * + * @offsets: struct holding the product specific register offsets + * @controller_formats: pointer to the array of possible formats that the + * controller can output + * @formats_list: pointer to the array of possible formats that can + * be used as an input to the controller + * @controller_formats_size: size of controller_formats array + * @formats_list_size: size of formats_list array + */ +struct isc_device { + struct regmap *regmap; + struct clk *hclock; + struct clk *ispck; + struct isc_clk isc_clks[2]; + bool ispck_required; + u32 dcfg; + + struct device *dev; + struct v4l2_device v4l2_dev; + struct video_device video_dev; + + struct vb2_queue vb2_vidq; + spinlock_t dma_queue_lock; + struct list_head dma_queue; + struct isc_buffer *cur_frm; + unsigned int sequence; + bool stop; + struct completion comp; + + struct v4l2_format fmt; + struct isc_format **user_formats; + unsigned int num_user_formats; + + struct fmt_config config; + struct fmt_config try_config; + + struct isc_ctrls ctrls; + struct work_struct awb_work; + + struct mutex lock; + struct mutex awb_mutex; + spinlock_t awb_lock; + + struct regmap_field *pipeline[ISC_PIPE_LINE_NODE_NUM]; + + struct isc_subdev_entity *current_subdev; + struct list_head subdev_entities; + + struct { +#define ISC_CTRL_DO_WB 1 +#define ISC_CTRL_R_GAIN 2 +#define ISC_CTRL_B_GAIN 3 +#define ISC_CTRL_GR_GAIN 4 +#define ISC_CTRL_GB_GAIN 5 +#define ISC_CTRL_R_OFF 6 +#define ISC_CTRL_B_OFF 7 +#define ISC_CTRL_GR_OFF 8 +#define ISC_CTRL_GB_OFF 9 + struct v4l2_ctrl *awb_ctrl; + struct v4l2_ctrl *do_wb_ctrl; + struct v4l2_ctrl *r_gain_ctrl; + struct v4l2_ctrl *b_gain_ctrl; + struct v4l2_ctrl *gr_gain_ctrl; + struct v4l2_ctrl *gb_gain_ctrl; + struct v4l2_ctrl *r_off_ctrl; + struct v4l2_ctrl *b_off_ctrl; + struct v4l2_ctrl *gr_off_ctrl; + struct v4l2_ctrl *gb_off_ctrl; + }; + +#define GAMMA_ENTRIES 64 + /* pointer to the defined gamma table */ + const u32 (*gamma_table)[GAMMA_ENTRIES]; + u32 gamma_max; + + u32 max_width; + u32 max_height; + + struct { + void (*config_dpc)(struct isc_device *isc); + void (*config_csc)(struct isc_device *isc); + void (*config_cbc)(struct isc_device *isc); + void (*config_cc)(struct isc_device *isc); + void (*config_gam)(struct isc_device *isc); + void (*config_rlp)(struct isc_device *isc); + + void (*config_ctrls)(struct isc_device *isc, + const struct v4l2_ctrl_ops *ops); + + void (*adapt_pipeline)(struct isc_device *isc); + }; + + struct isc_reg_offsets offsets; + const struct isc_format *controller_formats; + struct isc_format *formats_list; + u32 controller_formats_size; + u32 formats_list_size; +}; + +extern const struct regmap_config isc_regmap_config; +extern const struct v4l2_async_notifier_operations isc_async_ops; + +irqreturn_t isc_interrupt(int irq, void *dev_id); +int isc_pipeline_init(struct isc_device *isc); +int isc_clk_init(struct isc_device *isc); +void isc_subdev_cleanup(struct isc_device *isc); +void isc_clk_cleanup(struct isc_device *isc); + +#endif diff --git a/drivers/media/platform/atmel/atmel-isi.c b/drivers/media/platform/atmel/atmel-isi.c new file mode 100644 index 000000000..4d15814e4 --- /dev/null +++ b/drivers/media/platform/atmel/atmel-isi.c @@ -0,0 +1,1378 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2011 Atmel Corporation + * Josh Wu, + * + * Based on previous work by Lars Haring, + * and Sedji Gaouaou + * Based on the bttv driver for Bt848 with respective copyright holders + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atmel-isi.h" + +#define MAX_SUPPORT_WIDTH 2048U +#define MAX_SUPPORT_HEIGHT 2048U +#define MIN_FRAME_RATE 15 +#define FRAME_INTERVAL_MILLI_SEC (1000 / MIN_FRAME_RATE) + +/* Frame buffer descriptor */ +struct fbd { + /* Physical address of the frame buffer */ + u32 fb_address; + /* DMA Control Register(only in HISI2) */ + u32 dma_ctrl; + /* Physical address of the next fbd */ + u32 next_fbd_address; +}; + +static void set_dma_ctrl(struct fbd *fb_desc, u32 ctrl) +{ + fb_desc->dma_ctrl = ctrl; +} + +struct isi_dma_desc { + struct list_head list; + struct fbd *p_fbd; + dma_addr_t fbd_phys; +}; + +/* Frame buffer data */ +struct frame_buffer { + struct vb2_v4l2_buffer vb; + struct isi_dma_desc *p_dma_desc; + struct list_head list; +}; + +struct isi_graph_entity { + struct device_node *node; + + struct v4l2_subdev *subdev; +}; + +/* + * struct isi_format - ISI media bus format information + * @fourcc: Fourcc code for this format + * @mbus_code: V4L2 media bus format code. + * @bpp: Bytes per pixel (when stored in memory) + * @swap: Byte swap configuration value + * @support: Indicates format supported by subdev + * @skip: Skip duplicate format supported by subdev + */ +struct isi_format { + u32 fourcc; + u32 mbus_code; + u8 bpp; + u32 swap; +}; + + +struct atmel_isi { + /* Protects the access of variables shared with the ISR */ + spinlock_t irqlock; + struct device *dev; + void __iomem *regs; + + int sequence; + + /* Allocate descriptors for dma buffer use */ + struct fbd *p_fb_descriptors; + dma_addr_t fb_descriptors_phys; + struct list_head dma_desc_head; + struct isi_dma_desc dma_desc[VIDEO_MAX_FRAME]; + bool enable_preview_path; + + struct completion complete; + /* ISI peripheral clock */ + struct clk *pclk; + unsigned int irq; + + struct isi_platform_data pdata; + u16 width_flags; /* max 12 bits */ + + struct list_head video_buffer_list; + struct frame_buffer *active; + + struct v4l2_device v4l2_dev; + struct video_device *vdev; + struct v4l2_async_notifier notifier; + struct isi_graph_entity entity; + struct v4l2_format fmt; + + const struct isi_format **user_formats; + unsigned int num_user_formats; + const struct isi_format *current_fmt; + + struct mutex lock; + struct vb2_queue queue; +}; + +#define notifier_to_isi(n) container_of(n, struct atmel_isi, notifier) + +static void isi_writel(struct atmel_isi *isi, u32 reg, u32 val) +{ + writel(val, isi->regs + reg); +} +static u32 isi_readl(struct atmel_isi *isi, u32 reg) +{ + return readl(isi->regs + reg); +} + +static void configure_geometry(struct atmel_isi *isi) +{ + u32 cfg2, psize; + u32 fourcc = isi->current_fmt->fourcc; + + isi->enable_preview_path = fourcc == V4L2_PIX_FMT_RGB565 || + fourcc == V4L2_PIX_FMT_RGB32 || + fourcc == V4L2_PIX_FMT_Y16; + + /* According to sensor's output format to set cfg2 */ + cfg2 = isi->current_fmt->swap; + + isi_writel(isi, ISI_CTRL, ISI_CTRL_DIS); + /* Set width */ + cfg2 |= ((isi->fmt.fmt.pix.width - 1) << ISI_CFG2_IM_HSIZE_OFFSET) & + ISI_CFG2_IM_HSIZE_MASK; + /* Set height */ + cfg2 |= ((isi->fmt.fmt.pix.height - 1) << ISI_CFG2_IM_VSIZE_OFFSET) + & ISI_CFG2_IM_VSIZE_MASK; + isi_writel(isi, ISI_CFG2, cfg2); + + /* No down sampling, preview size equal to sensor output size */ + psize = ((isi->fmt.fmt.pix.width - 1) << ISI_PSIZE_PREV_HSIZE_OFFSET) & + ISI_PSIZE_PREV_HSIZE_MASK; + psize |= ((isi->fmt.fmt.pix.height - 1) << ISI_PSIZE_PREV_VSIZE_OFFSET) & + ISI_PSIZE_PREV_VSIZE_MASK; + isi_writel(isi, ISI_PSIZE, psize); + isi_writel(isi, ISI_PDECF, ISI_PDECF_NO_SAMPLING); +} + +static irqreturn_t atmel_isi_handle_streaming(struct atmel_isi *isi) +{ + if (isi->active) { + struct vb2_v4l2_buffer *vbuf = &isi->active->vb; + struct frame_buffer *buf = isi->active; + + list_del_init(&buf->list); + vbuf->vb2_buf.timestamp = ktime_get_ns(); + vbuf->sequence = isi->sequence++; + vbuf->field = V4L2_FIELD_NONE; + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); + } + + if (list_empty(&isi->video_buffer_list)) { + isi->active = NULL; + } else { + /* start next dma frame. */ + isi->active = list_entry(isi->video_buffer_list.next, + struct frame_buffer, list); + if (!isi->enable_preview_path) { + isi_writel(isi, ISI_DMA_C_DSCR, + (u32)isi->active->p_dma_desc->fbd_phys); + isi_writel(isi, ISI_DMA_C_CTRL, + ISI_DMA_CTRL_FETCH | ISI_DMA_CTRL_DONE); + isi_writel(isi, ISI_DMA_CHER, ISI_DMA_CHSR_C_CH); + } else { + isi_writel(isi, ISI_DMA_P_DSCR, + (u32)isi->active->p_dma_desc->fbd_phys); + isi_writel(isi, ISI_DMA_P_CTRL, + ISI_DMA_CTRL_FETCH | ISI_DMA_CTRL_DONE); + isi_writel(isi, ISI_DMA_CHER, ISI_DMA_CHSR_P_CH); + } + } + return IRQ_HANDLED; +} + +/* ISI interrupt service routine */ +static irqreturn_t isi_interrupt(int irq, void *dev_id) +{ + struct atmel_isi *isi = dev_id; + u32 status, mask, pending; + irqreturn_t ret = IRQ_NONE; + + spin_lock(&isi->irqlock); + + status = isi_readl(isi, ISI_STATUS); + mask = isi_readl(isi, ISI_INTMASK); + pending = status & mask; + + if (pending & ISI_CTRL_SRST) { + complete(&isi->complete); + isi_writel(isi, ISI_INTDIS, ISI_CTRL_SRST); + ret = IRQ_HANDLED; + } else if (pending & ISI_CTRL_DIS) { + complete(&isi->complete); + isi_writel(isi, ISI_INTDIS, ISI_CTRL_DIS); + ret = IRQ_HANDLED; + } else { + if (likely(pending & ISI_SR_CXFR_DONE) || + likely(pending & ISI_SR_PXFR_DONE)) + ret = atmel_isi_handle_streaming(isi); + } + + spin_unlock(&isi->irqlock); + return ret; +} + +#define WAIT_ISI_RESET 1 +#define WAIT_ISI_DISABLE 0 +static int atmel_isi_wait_status(struct atmel_isi *isi, int wait_reset) +{ + unsigned long timeout; + /* + * The reset or disable will only succeed if we have a + * pixel clock from the camera. + */ + init_completion(&isi->complete); + + if (wait_reset) { + isi_writel(isi, ISI_INTEN, ISI_CTRL_SRST); + isi_writel(isi, ISI_CTRL, ISI_CTRL_SRST); + } else { + isi_writel(isi, ISI_INTEN, ISI_CTRL_DIS); + isi_writel(isi, ISI_CTRL, ISI_CTRL_DIS); + } + + timeout = wait_for_completion_timeout(&isi->complete, + msecs_to_jiffies(500)); + if (timeout == 0) + return -ETIMEDOUT; + + return 0; +} + +/* ------------------------------------------------------------------ + Videobuf operations + ------------------------------------------------------------------*/ +static int queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct atmel_isi *isi = vb2_get_drv_priv(vq); + unsigned long size; + + size = isi->fmt.fmt.pix.sizeimage; + + /* Make sure the image size is large enough. */ + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + isi->active = NULL; + + dev_dbg(isi->dev, "%s, count=%d, size=%ld\n", __func__, + *nbuffers, size); + + return 0; +} + +static int buffer_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct frame_buffer *buf = container_of(vbuf, struct frame_buffer, vb); + + buf->p_dma_desc = NULL; + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct frame_buffer *buf = container_of(vbuf, struct frame_buffer, vb); + struct atmel_isi *isi = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size; + struct isi_dma_desc *desc; + + size = isi->fmt.fmt.pix.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(isi->dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + if (!buf->p_dma_desc) { + if (list_empty(&isi->dma_desc_head)) { + dev_err(isi->dev, "Not enough dma descriptors.\n"); + return -EINVAL; + } else { + /* Get an available descriptor */ + desc = list_entry(isi->dma_desc_head.next, + struct isi_dma_desc, list); + /* Delete the descriptor since now it is used */ + list_del_init(&desc->list); + + /* Initialize the dma descriptor */ + desc->p_fbd->fb_address = + vb2_dma_contig_plane_dma_addr(vb, 0); + desc->p_fbd->next_fbd_address = 0; + set_dma_ctrl(desc->p_fbd, ISI_DMA_CTRL_WB); + + buf->p_dma_desc = desc; + } + } + return 0; +} + +static void buffer_cleanup(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct atmel_isi *isi = vb2_get_drv_priv(vb->vb2_queue); + struct frame_buffer *buf = container_of(vbuf, struct frame_buffer, vb); + + /* This descriptor is available now and we add to head list */ + if (buf->p_dma_desc) + list_add(&buf->p_dma_desc->list, &isi->dma_desc_head); +} + +static void start_dma(struct atmel_isi *isi, struct frame_buffer *buffer) +{ + u32 ctrl, cfg1; + + cfg1 = isi_readl(isi, ISI_CFG1); + /* Enable irq: cxfr for the codec path, pxfr for the preview path */ + isi_writel(isi, ISI_INTEN, + ISI_SR_CXFR_DONE | ISI_SR_PXFR_DONE); + + /* Check if already in a frame */ + if (!isi->enable_preview_path) { + if (isi_readl(isi, ISI_STATUS) & ISI_CTRL_CDC) { + dev_err(isi->dev, "Already in frame handling.\n"); + return; + } + + isi_writel(isi, ISI_DMA_C_DSCR, + (u32)buffer->p_dma_desc->fbd_phys); + isi_writel(isi, ISI_DMA_C_CTRL, + ISI_DMA_CTRL_FETCH | ISI_DMA_CTRL_DONE); + isi_writel(isi, ISI_DMA_CHER, ISI_DMA_CHSR_C_CH); + } else { + isi_writel(isi, ISI_DMA_P_DSCR, + (u32)buffer->p_dma_desc->fbd_phys); + isi_writel(isi, ISI_DMA_P_CTRL, + ISI_DMA_CTRL_FETCH | ISI_DMA_CTRL_DONE); + isi_writel(isi, ISI_DMA_CHER, ISI_DMA_CHSR_P_CH); + } + + cfg1 &= ~ISI_CFG1_FRATE_DIV_MASK; + /* Enable linked list */ + cfg1 |= isi->pdata.frate | ISI_CFG1_DISCR; + + /* Enable ISI */ + ctrl = ISI_CTRL_EN; + + if (!isi->enable_preview_path) + ctrl |= ISI_CTRL_CDC; + + isi_writel(isi, ISI_CTRL, ctrl); + isi_writel(isi, ISI_CFG1, cfg1); +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct atmel_isi *isi = vb2_get_drv_priv(vb->vb2_queue); + struct frame_buffer *buf = container_of(vbuf, struct frame_buffer, vb); + unsigned long flags = 0; + + spin_lock_irqsave(&isi->irqlock, flags); + list_add_tail(&buf->list, &isi->video_buffer_list); + + if (!isi->active) { + isi->active = buf; + if (vb2_is_streaming(vb->vb2_queue)) + start_dma(isi, buf); + } + spin_unlock_irqrestore(&isi->irqlock, flags); +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct atmel_isi *isi = vb2_get_drv_priv(vq); + struct frame_buffer *buf, *node; + int ret; + + ret = pm_runtime_resume_and_get(isi->dev); + if (ret < 0) + return ret; + + /* Enable stream on the sub device */ + ret = v4l2_subdev_call(isi->entity.subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) { + dev_err(isi->dev, "stream on failed in subdev\n"); + goto err_start_stream; + } + + /* Reset ISI */ + ret = atmel_isi_wait_status(isi, WAIT_ISI_RESET); + if (ret < 0) { + dev_err(isi->dev, "Reset ISI timed out\n"); + goto err_reset; + } + /* Disable all interrupts */ + isi_writel(isi, ISI_INTDIS, (u32)~0UL); + + isi->sequence = 0; + configure_geometry(isi); + + spin_lock_irq(&isi->irqlock); + /* Clear any pending interrupt */ + isi_readl(isi, ISI_STATUS); + + start_dma(isi, isi->active); + spin_unlock_irq(&isi->irqlock); + + return 0; + +err_reset: + v4l2_subdev_call(isi->entity.subdev, video, s_stream, 0); + +err_start_stream: + pm_runtime_put(isi->dev); + + spin_lock_irq(&isi->irqlock); + isi->active = NULL; + /* Release all active buffers */ + list_for_each_entry_safe(buf, node, &isi->video_buffer_list, list) { + list_del_init(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irq(&isi->irqlock); + + return ret; +} + +/* abort streaming and wait for last buffer */ +static void stop_streaming(struct vb2_queue *vq) +{ + struct atmel_isi *isi = vb2_get_drv_priv(vq); + struct frame_buffer *buf, *node; + int ret = 0; + unsigned long timeout; + + /* Disable stream on the sub device */ + ret = v4l2_subdev_call(isi->entity.subdev, video, s_stream, 0); + if (ret && ret != -ENOIOCTLCMD) + dev_err(isi->dev, "stream off failed in subdev\n"); + + spin_lock_irq(&isi->irqlock); + isi->active = NULL; + /* Release all active buffers */ + list_for_each_entry_safe(buf, node, &isi->video_buffer_list, list) { + list_del_init(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irq(&isi->irqlock); + + if (!isi->enable_preview_path) { + timeout = jiffies + (FRAME_INTERVAL_MILLI_SEC * HZ) / 1000; + /* Wait until the end of the current frame. */ + while ((isi_readl(isi, ISI_STATUS) & ISI_CTRL_CDC) && + time_before(jiffies, timeout)) + msleep(1); + + if (time_after(jiffies, timeout)) + dev_err(isi->dev, + "Timeout waiting for finishing codec request\n"); + } + + /* Disable interrupts */ + isi_writel(isi, ISI_INTDIS, + ISI_SR_CXFR_DONE | ISI_SR_PXFR_DONE); + + /* Disable ISI and wait for it is done */ + ret = atmel_isi_wait_status(isi, WAIT_ISI_DISABLE); + if (ret < 0) + dev_err(isi->dev, "Disable ISI timed out\n"); + + pm_runtime_put(isi->dev); +} + +static const struct vb2_ops isi_video_qops = { + .queue_setup = queue_setup, + .buf_init = buffer_init, + .buf_prepare = buffer_prepare, + .buf_cleanup = buffer_cleanup, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int isi_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct atmel_isi *isi = video_drvdata(file); + + *fmt = isi->fmt; + + return 0; +} + +static const struct isi_format *find_format_by_fourcc(struct atmel_isi *isi, + unsigned int fourcc) +{ + unsigned int num_formats = isi->num_user_formats; + const struct isi_format *fmt; + unsigned int i; + + for (i = 0; i < num_formats; i++) { + fmt = isi->user_formats[i]; + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +static void isi_try_fse(struct atmel_isi *isi, const struct isi_format *isi_fmt, + struct v4l2_subdev_state *sd_state) +{ + int ret; + struct v4l2_subdev_frame_size_enum fse = { + .code = isi_fmt->mbus_code, + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + + ret = v4l2_subdev_call(isi->entity.subdev, pad, enum_frame_size, + sd_state, &fse); + /* + * Attempt to obtain format size from subdev. If not available, + * just use the maximum ISI can receive. + */ + if (ret) { + sd_state->pads->try_crop.width = MAX_SUPPORT_WIDTH; + sd_state->pads->try_crop.height = MAX_SUPPORT_HEIGHT; + } else { + sd_state->pads->try_crop.width = fse.max_width; + sd_state->pads->try_crop.height = fse.max_height; + } +} + +static int isi_try_fmt(struct atmel_isi *isi, struct v4l2_format *f, + const struct isi_format **current_fmt) +{ + const struct isi_format *isi_fmt; + struct v4l2_pix_format *pixfmt = &f->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg = {}; + struct v4l2_subdev_state pad_state = { + .pads = &pad_cfg + }; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + int ret; + + isi_fmt = find_format_by_fourcc(isi, pixfmt->pixelformat); + if (!isi_fmt) { + isi_fmt = isi->user_formats[isi->num_user_formats - 1]; + pixfmt->pixelformat = isi_fmt->fourcc; + } + + /* Limit to Atmel ISI hardware capabilities */ + pixfmt->width = clamp(pixfmt->width, 0U, MAX_SUPPORT_WIDTH); + pixfmt->height = clamp(pixfmt->height, 0U, MAX_SUPPORT_HEIGHT); + + v4l2_fill_mbus_format(&format.format, pixfmt, isi_fmt->mbus_code); + + isi_try_fse(isi, isi_fmt, &pad_state); + + ret = v4l2_subdev_call(isi->entity.subdev, pad, set_fmt, + &pad_state, &format); + if (ret < 0) + return ret; + + v4l2_fill_pix_format(pixfmt, &format.format); + + pixfmt->field = V4L2_FIELD_NONE; + pixfmt->bytesperline = pixfmt->width * isi_fmt->bpp; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + + if (current_fmt) + *current_fmt = isi_fmt; + + return 0; +} + +static int isi_set_fmt(struct atmel_isi *isi, struct v4l2_format *f) +{ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const struct isi_format *current_fmt; + int ret; + + ret = isi_try_fmt(isi, f, ¤t_fmt); + if (ret) + return ret; + + v4l2_fill_mbus_format(&format.format, &f->fmt.pix, + current_fmt->mbus_code); + ret = v4l2_subdev_call(isi->entity.subdev, pad, + set_fmt, NULL, &format); + if (ret < 0) + return ret; + + isi->fmt = *f; + isi->current_fmt = current_fmt; + + return 0; +} + +static int isi_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct atmel_isi *isi = video_drvdata(file); + + if (vb2_is_streaming(&isi->queue)) + return -EBUSY; + + return isi_set_fmt(isi, f); +} + +static int isi_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct atmel_isi *isi = video_drvdata(file); + + return isi_try_fmt(isi, f, NULL); +} + +static int isi_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct atmel_isi *isi = video_drvdata(file); + + if (f->index >= isi->num_user_formats) + return -EINVAL; + + f->pixelformat = isi->user_formats[f->index]->fourcc; + return 0; +} + +static int isi_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, "atmel-isi", sizeof(cap->driver)); + strscpy(cap->card, "Atmel Image Sensor Interface", sizeof(cap->card)); + strscpy(cap->bus_info, "platform:isi", sizeof(cap->bus_info)); + return 0; +} + +static int isi_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(i->name, "Camera", sizeof(i->name)); + return 0; +} + +static int isi_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int isi_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + return 0; +} + +static int isi_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct atmel_isi *isi = video_drvdata(file); + + return v4l2_g_parm_cap(video_devdata(file), isi->entity.subdev, a); +} + +static int isi_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct atmel_isi *isi = video_drvdata(file); + + return v4l2_s_parm_cap(video_devdata(file), isi->entity.subdev, a); +} + +static int isi_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct atmel_isi *isi = video_drvdata(file); + const struct isi_format *isi_fmt; + struct v4l2_subdev_frame_size_enum fse = { + .index = fsize->index, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + isi_fmt = find_format_by_fourcc(isi, fsize->pixel_format); + if (!isi_fmt) + return -EINVAL; + + fse.code = isi_fmt->mbus_code; + + ret = v4l2_subdev_call(isi->entity.subdev, pad, enum_frame_size, + NULL, &fse); + if (ret) + return ret; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.max_width; + fsize->discrete.height = fse.max_height; + + return 0; +} + +static int isi_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct atmel_isi *isi = video_drvdata(file); + const struct isi_format *isi_fmt; + struct v4l2_subdev_frame_interval_enum fie = { + .index = fival->index, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + isi_fmt = find_format_by_fourcc(isi, fival->pixel_format); + if (!isi_fmt) + return -EINVAL; + + fie.code = isi_fmt->mbus_code; + + ret = v4l2_subdev_call(isi->entity.subdev, pad, + enum_frame_interval, NULL, &fie); + if (ret) + return ret; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + + return 0; +} + +static int isi_camera_set_bus_param(struct atmel_isi *isi) +{ + u32 cfg1 = 0; + int ret; + + /* set bus param for ISI */ + if (isi->pdata.hsync_act_low) + cfg1 |= ISI_CFG1_HSYNC_POL_ACTIVE_LOW; + if (isi->pdata.vsync_act_low) + cfg1 |= ISI_CFG1_VSYNC_POL_ACTIVE_LOW; + if (isi->pdata.pclk_act_falling) + cfg1 |= ISI_CFG1_PIXCLK_POL_ACTIVE_FALLING; + if (isi->pdata.has_emb_sync) + cfg1 |= ISI_CFG1_EMB_SYNC; + if (isi->pdata.full_mode) + cfg1 |= ISI_CFG1_FULL_MODE; + + cfg1 |= ISI_CFG1_THMASK_BEATS_16; + + /* Enable PM and peripheral clock before operate isi registers */ + ret = pm_runtime_resume_and_get(isi->dev); + if (ret < 0) + return ret; + + isi_writel(isi, ISI_CTRL, ISI_CTRL_DIS); + isi_writel(isi, ISI_CFG1, cfg1); + + pm_runtime_put(isi->dev); + + return 0; +} + +/* -----------------------------------------------------------------------*/ +static int atmel_isi_parse_dt(struct atmel_isi *isi, + struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct v4l2_fwnode_endpoint ep = { .bus_type = 0 }; + int err; + + /* Default settings for ISI */ + isi->pdata.full_mode = 1; + isi->pdata.frate = ISI_CFG1_FRATE_CAPTURE_ALL; + + np = of_graph_get_next_endpoint(np, NULL); + if (!np) { + dev_err(&pdev->dev, "Could not find the endpoint\n"); + return -EINVAL; + } + + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); + of_node_put(np); + if (err) { + dev_err(&pdev->dev, "Could not parse the endpoint\n"); + return err; + } + + switch (ep.bus.parallel.bus_width) { + case 8: + isi->pdata.data_width_flags = ISI_DATAWIDTH_8; + break; + case 10: + isi->pdata.data_width_flags = + ISI_DATAWIDTH_8 | ISI_DATAWIDTH_10; + break; + default: + dev_err(&pdev->dev, "Unsupported bus width: %d\n", + ep.bus.parallel.bus_width); + return -EINVAL; + } + + if (ep.bus.parallel.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + isi->pdata.hsync_act_low = true; + if (ep.bus.parallel.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + isi->pdata.vsync_act_low = true; + if (ep.bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + isi->pdata.pclk_act_falling = true; + + if (ep.bus_type == V4L2_MBUS_BT656) + isi->pdata.has_emb_sync = true; + + return 0; +} + +static int isi_open(struct file *file) +{ + struct atmel_isi *isi = video_drvdata(file); + struct v4l2_subdev *sd = isi->entity.subdev; + int ret; + + if (mutex_lock_interruptible(&isi->lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + if (!v4l2_fh_is_singular_file(file)) + goto fh_rel; + + ret = v4l2_subdev_call(sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto fh_rel; + + ret = isi_set_fmt(isi, &isi->fmt); + if (ret) + v4l2_subdev_call(sd, core, s_power, 0); +fh_rel: + if (ret) + v4l2_fh_release(file); +unlock: + mutex_unlock(&isi->lock); + return ret; +} + +static int isi_release(struct file *file) +{ + struct atmel_isi *isi = video_drvdata(file); + struct v4l2_subdev *sd = isi->entity.subdev; + bool fh_singular; + int ret; + + mutex_lock(&isi->lock); + + fh_singular = v4l2_fh_is_singular_file(file); + + ret = _vb2_fop_release(file, NULL); + + if (fh_singular) + v4l2_subdev_call(sd, core, s_power, 0); + + mutex_unlock(&isi->lock); + + return ret; +} + +static const struct v4l2_ioctl_ops isi_ioctl_ops = { + .vidioc_querycap = isi_querycap, + + .vidioc_try_fmt_vid_cap = isi_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = isi_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = isi_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = isi_enum_fmt_vid_cap, + + .vidioc_enum_input = isi_enum_input, + .vidioc_g_input = isi_g_input, + .vidioc_s_input = isi_s_input, + + .vidioc_g_parm = isi_g_parm, + .vidioc_s_parm = isi_s_parm, + .vidioc_enum_framesizes = isi_enum_framesizes, + .vidioc_enum_frameintervals = isi_enum_frameintervals, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations isi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = isi_open, + .release = isi_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +static int isi_set_default_fmt(struct atmel_isi *isi) +{ + struct v4l2_format f = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .field = V4L2_FIELD_NONE, + .pixelformat = isi->user_formats[0]->fourcc, + }, + }; + int ret; + + ret = isi_try_fmt(isi, &f, NULL); + if (ret) + return ret; + isi->current_fmt = isi->user_formats[0]; + isi->fmt = f; + return 0; +} + +static const struct isi_format isi_formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 2, + .swap = ISI_CFG2_YCC_SWAP_DEFAULT, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .bpp = 2, + .swap = ISI_CFG2_YCC_SWAP_MODE_1, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .bpp = 2, + .swap = ISI_CFG2_YCC_SWAP_MODE_2, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .bpp = 2, + .swap = ISI_CFG2_YCC_SWAP_MODE_3, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 2, + .swap = ISI_CFG2_YCC_SWAP_MODE_2, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .bpp = 2, + .swap = ISI_CFG2_YCC_SWAP_MODE_3, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .bpp = 2, + .swap = ISI_CFG2_YCC_SWAP_DEFAULT, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .bpp = 2, + .swap = ISI_CFG2_YCC_SWAP_MODE_1, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .bpp = 1, + .swap = ISI_CFG2_GS_MODE_2_PIXEL | ISI_CFG2_GRAYSCALE, + }, { + .fourcc = V4L2_PIX_FMT_Y16, + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .bpp = 2, + .swap = ISI_CFG2_GS_MODE_2_PIXEL | ISI_CFG2_GRAYSCALE, + }, +}; + +static int isi_formats_init(struct atmel_isi *isi) +{ + const struct isi_format *isi_fmts[ARRAY_SIZE(isi_formats)]; + unsigned int num_fmts = 0, i, j; + struct v4l2_subdev *subdev = isi->entity.subdev; + struct v4l2_subdev_mbus_code_enum mbus_code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, + NULL, &mbus_code)) { + for (i = 0; i < ARRAY_SIZE(isi_formats); i++) { + if (isi_formats[i].mbus_code != mbus_code.code) + continue; + + /* Code supported, have we got this fourcc yet? */ + for (j = 0; j < num_fmts; j++) + if (isi_fmts[j]->fourcc == isi_formats[i].fourcc) + /* Already available */ + break; + if (j == num_fmts) + /* new */ + isi_fmts[num_fmts++] = isi_formats + i; + } + mbus_code.index++; + } + + if (!num_fmts) + return -ENXIO; + + isi->num_user_formats = num_fmts; + isi->user_formats = devm_kcalloc(isi->dev, + num_fmts, sizeof(struct isi_format *), + GFP_KERNEL); + if (!isi->user_formats) + return -ENOMEM; + + memcpy(isi->user_formats, isi_fmts, + num_fmts * sizeof(struct isi_format *)); + isi->current_fmt = isi->user_formats[0]; + + return 0; +} + +static int isi_graph_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct atmel_isi *isi = notifier_to_isi(notifier); + int ret; + + isi->vdev->ctrl_handler = isi->entity.subdev->ctrl_handler; + ret = isi_formats_init(isi); + if (ret) { + dev_err(isi->dev, "No supported mediabus format found\n"); + return ret; + } + ret = isi_camera_set_bus_param(isi); + if (ret) { + dev_err(isi->dev, "Can't wake up device\n"); + return ret; + } + + ret = isi_set_default_fmt(isi); + if (ret) { + dev_err(isi->dev, "Could not set default format\n"); + return ret; + } + + ret = video_register_device(isi->vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(isi->dev, "Failed to register video device\n"); + return ret; + } + + dev_dbg(isi->dev, "Device registered as %s\n", + video_device_node_name(isi->vdev)); + return 0; +} + +static void isi_graph_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct atmel_isi *isi = notifier_to_isi(notifier); + + dev_dbg(isi->dev, "Removing %s\n", video_device_node_name(isi->vdev)); + + /* Checks internally if vdev have been init or not */ + video_unregister_device(isi->vdev); +} + +static int isi_graph_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct atmel_isi *isi = notifier_to_isi(notifier); + + dev_dbg(isi->dev, "subdev %s bound\n", subdev->name); + + isi->entity.subdev = subdev; + + return 0; +} + +static const struct v4l2_async_notifier_operations isi_graph_notify_ops = { + .bound = isi_graph_notify_bound, + .unbind = isi_graph_notify_unbind, + .complete = isi_graph_notify_complete, +}; + +static int isi_graph_init(struct atmel_isi *isi) +{ + struct v4l2_async_subdev *asd; + struct device_node *ep; + int ret; + + ep = of_graph_get_next_endpoint(isi->dev->of_node, NULL); + if (!ep) + return -EINVAL; + + v4l2_async_nf_init(&isi->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&isi->notifier, + of_fwnode_handle(ep), + struct v4l2_async_subdev); + of_node_put(ep); + + if (IS_ERR(asd)) + return PTR_ERR(asd); + + isi->notifier.ops = &isi_graph_notify_ops; + + ret = v4l2_async_nf_register(&isi->v4l2_dev, &isi->notifier); + if (ret < 0) { + dev_err(isi->dev, "Notifier registration failed\n"); + v4l2_async_nf_cleanup(&isi->notifier); + return ret; + } + + return 0; +} + + +static int atmel_isi_probe(struct platform_device *pdev) +{ + int irq; + struct atmel_isi *isi; + struct vb2_queue *q; + struct resource *regs; + int ret, i; + + isi = devm_kzalloc(&pdev->dev, sizeof(struct atmel_isi), GFP_KERNEL); + if (!isi) + return -ENOMEM; + + isi->pclk = devm_clk_get(&pdev->dev, "isi_clk"); + if (IS_ERR(isi->pclk)) + return PTR_ERR(isi->pclk); + + ret = atmel_isi_parse_dt(isi, pdev); + if (ret) + return ret; + + isi->active = NULL; + isi->dev = &pdev->dev; + mutex_init(&isi->lock); + spin_lock_init(&isi->irqlock); + INIT_LIST_HEAD(&isi->video_buffer_list); + INIT_LIST_HEAD(&isi->dma_desc_head); + + q = &isi->queue; + + /* Initialize the top-level structure */ + ret = v4l2_device_register(&pdev->dev, &isi->v4l2_dev); + if (ret) + return ret; + + isi->vdev = video_device_alloc(); + if (!isi->vdev) { + ret = -ENOMEM; + goto err_vdev_alloc; + } + + /* video node */ + isi->vdev->fops = &isi_fops; + isi->vdev->v4l2_dev = &isi->v4l2_dev; + isi->vdev->queue = &isi->queue; + strscpy(isi->vdev->name, KBUILD_MODNAME, sizeof(isi->vdev->name)); + isi->vdev->release = video_device_release; + isi->vdev->ioctl_ops = &isi_ioctl_ops; + isi->vdev->lock = &isi->lock; + isi->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + video_set_drvdata(isi->vdev, isi); + + /* buffer queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + q->lock = &isi->lock; + q->drv_priv = isi; + q->buf_struct_size = sizeof(struct frame_buffer); + q->ops = &isi_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->dev = &pdev->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(&pdev->dev, "failed to initialize VB2 queue\n"); + goto err_vb2_queue; + } + isi->p_fb_descriptors = dma_alloc_coherent(&pdev->dev, + sizeof(struct fbd) * VIDEO_MAX_FRAME, + &isi->fb_descriptors_phys, + GFP_KERNEL); + if (!isi->p_fb_descriptors) { + dev_err(&pdev->dev, "Can't allocate descriptors!\n"); + ret = -ENOMEM; + goto err_dma_alloc; + } + + for (i = 0; i < VIDEO_MAX_FRAME; i++) { + isi->dma_desc[i].p_fbd = isi->p_fb_descriptors + i; + isi->dma_desc[i].fbd_phys = isi->fb_descriptors_phys + + i * sizeof(struct fbd); + list_add(&isi->dma_desc[i].list, &isi->dma_desc_head); + } + + regs = platform_get_resource(pdev, IORESOURCE_MEM, 0); + isi->regs = devm_ioremap_resource(&pdev->dev, regs); + if (IS_ERR(isi->regs)) { + ret = PTR_ERR(isi->regs); + goto err_ioremap; + } + + if (isi->pdata.data_width_flags & ISI_DATAWIDTH_8) + isi->width_flags = 1 << 7; + if (isi->pdata.data_width_flags & ISI_DATAWIDTH_10) + isi->width_flags |= 1 << 9; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_req_irq; + } + + ret = devm_request_irq(&pdev->dev, irq, isi_interrupt, 0, "isi", isi); + if (ret) { + dev_err(&pdev->dev, "Unable to request irq %d\n", irq); + goto err_req_irq; + } + isi->irq = irq; + + ret = isi_graph_init(isi); + if (ret < 0) + goto err_req_irq; + + pm_suspend_ignore_children(&pdev->dev, true); + pm_runtime_enable(&pdev->dev); + platform_set_drvdata(pdev, isi); + return 0; + +err_req_irq: +err_ioremap: + dma_free_coherent(&pdev->dev, + sizeof(struct fbd) * VIDEO_MAX_FRAME, + isi->p_fb_descriptors, + isi->fb_descriptors_phys); +err_dma_alloc: +err_vb2_queue: + video_device_release(isi->vdev); +err_vdev_alloc: + v4l2_device_unregister(&isi->v4l2_dev); + + return ret; +} + +static int atmel_isi_remove(struct platform_device *pdev) +{ + struct atmel_isi *isi = platform_get_drvdata(pdev); + + dma_free_coherent(&pdev->dev, + sizeof(struct fbd) * VIDEO_MAX_FRAME, + isi->p_fb_descriptors, + isi->fb_descriptors_phys); + pm_runtime_disable(&pdev->dev); + v4l2_async_nf_unregister(&isi->notifier); + v4l2_async_nf_cleanup(&isi->notifier); + v4l2_device_unregister(&isi->v4l2_dev); + + return 0; +} + +#ifdef CONFIG_PM +static int atmel_isi_runtime_suspend(struct device *dev) +{ + struct atmel_isi *isi = dev_get_drvdata(dev); + + clk_disable_unprepare(isi->pclk); + + return 0; +} +static int atmel_isi_runtime_resume(struct device *dev) +{ + struct atmel_isi *isi = dev_get_drvdata(dev); + + return clk_prepare_enable(isi->pclk); +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops atmel_isi_dev_pm_ops = { + SET_RUNTIME_PM_OPS(atmel_isi_runtime_suspend, + atmel_isi_runtime_resume, NULL) +}; + +static const struct of_device_id atmel_isi_of_match[] = { + { .compatible = "atmel,at91sam9g45-isi" }, + { } +}; +MODULE_DEVICE_TABLE(of, atmel_isi_of_match); + +static struct platform_driver atmel_isi_driver = { + .driver = { + .name = "atmel_isi", + .of_match_table = of_match_ptr(atmel_isi_of_match), + .pm = &atmel_isi_dev_pm_ops, + }, + .probe = atmel_isi_probe, + .remove = atmel_isi_remove, +}; + +module_platform_driver(atmel_isi_driver); + +MODULE_AUTHOR("Josh Wu "); +MODULE_DESCRIPTION("The V4L2 driver for Atmel Linux"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/atmel/atmel-isi.h b/drivers/media/platform/atmel/atmel-isi.h new file mode 100644 index 000000000..7ad3895a2 --- /dev/null +++ b/drivers/media/platform/atmel/atmel-isi.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definitions for the Atmel Image Sensor Interface. + * + * Copyright (C) 2011 Atmel Corporation + * Josh Wu, + * + * Based on previous work by Lars Haring, + * and Sedji Gaouaou + */ +#ifndef __ATMEL_ISI_H__ +#define __ATMEL_ISI_H__ + +#include + +/* ISI_V2 register offsets */ +#define ISI_CFG1 0x0000 +#define ISI_CFG2 0x0004 +#define ISI_PSIZE 0x0008 +#define ISI_PDECF 0x000c +#define ISI_Y2R_SET0 0x0010 +#define ISI_Y2R_SET1 0x0014 +#define ISI_R2Y_SET0 0x0018 +#define ISI_R2Y_SET1 0x001C +#define ISI_R2Y_SET2 0x0020 +#define ISI_CTRL 0x0024 +#define ISI_STATUS 0x0028 +#define ISI_INTEN 0x002C +#define ISI_INTDIS 0x0030 +#define ISI_INTMASK 0x0034 +#define ISI_DMA_CHER 0x0038 +#define ISI_DMA_CHDR 0x003C +#define ISI_DMA_CHSR 0x0040 +#define ISI_DMA_P_ADDR 0x0044 +#define ISI_DMA_P_CTRL 0x0048 +#define ISI_DMA_P_DSCR 0x004C +#define ISI_DMA_C_ADDR 0x0050 +#define ISI_DMA_C_CTRL 0x0054 +#define ISI_DMA_C_DSCR 0x0058 + +/* Bitfields in CFG1 */ +#define ISI_CFG1_HSYNC_POL_ACTIVE_LOW (1 << 2) +#define ISI_CFG1_VSYNC_POL_ACTIVE_LOW (1 << 3) +#define ISI_CFG1_PIXCLK_POL_ACTIVE_FALLING (1 << 4) +#define ISI_CFG1_EMB_SYNC (1 << 6) +#define ISI_CFG1_CRC_SYNC (1 << 7) +/* Constants for FRATE(ISI_V2) */ +#define ISI_CFG1_FRATE_CAPTURE_ALL (0 << 8) +#define ISI_CFG1_FRATE_DIV_2 (1 << 8) +#define ISI_CFG1_FRATE_DIV_3 (2 << 8) +#define ISI_CFG1_FRATE_DIV_4 (3 << 8) +#define ISI_CFG1_FRATE_DIV_5 (4 << 8) +#define ISI_CFG1_FRATE_DIV_6 (5 << 8) +#define ISI_CFG1_FRATE_DIV_7 (6 << 8) +#define ISI_CFG1_FRATE_DIV_8 (7 << 8) +#define ISI_CFG1_FRATE_DIV_MASK (7 << 8) +#define ISI_CFG1_DISCR (1 << 11) +#define ISI_CFG1_FULL_MODE (1 << 12) +/* Definition for THMASK(ISI_V2) */ +#define ISI_CFG1_THMASK_BEATS_4 (0 << 13) +#define ISI_CFG1_THMASK_BEATS_8 (1 << 13) +#define ISI_CFG1_THMASK_BEATS_16 (2 << 13) + +/* Bitfields in CFG2 */ +#define ISI_CFG2_GS_MODE_2_PIXEL (0 << 11) +#define ISI_CFG2_GS_MODE_1_PIXEL (1 << 11) +#define ISI_CFG2_GRAYSCALE (1 << 13) +#define ISI_CFG2_COL_SPACE_YCbCr (0 << 15) +#define ISI_CFG2_COL_SPACE_RGB (1 << 15) +/* Constants for YCC_SWAP(ISI_V2) */ +#define ISI_CFG2_YCC_SWAP_DEFAULT (0 << 28) +#define ISI_CFG2_YCC_SWAP_MODE_1 (1 << 28) +#define ISI_CFG2_YCC_SWAP_MODE_2 (2 << 28) +#define ISI_CFG2_YCC_SWAP_MODE_3 (3 << 28) +#define ISI_CFG2_YCC_SWAP_MODE_MASK (3 << 28) +#define ISI_CFG2_IM_VSIZE_OFFSET 0 +#define ISI_CFG2_IM_HSIZE_OFFSET 16 +#define ISI_CFG2_IM_VSIZE_MASK (0x7FF << ISI_CFG2_IM_VSIZE_OFFSET) +#define ISI_CFG2_IM_HSIZE_MASK (0x7FF << ISI_CFG2_IM_HSIZE_OFFSET) + +/* Bitfields in PSIZE */ +#define ISI_PSIZE_PREV_VSIZE_OFFSET 0 +#define ISI_PSIZE_PREV_HSIZE_OFFSET 16 +#define ISI_PSIZE_PREV_VSIZE_MASK (0x3FF << ISI_PSIZE_PREV_VSIZE_OFFSET) +#define ISI_PSIZE_PREV_HSIZE_MASK (0x3FF << ISI_PSIZE_PREV_HSIZE_OFFSET) + +/* Bitfields in PDECF */ +#define ISI_PDECF_DEC_FACTOR_MASK (0xFF << 0) +#define ISI_PDECF_NO_SAMPLING (16) + +/* Bitfields in CTRL */ +/* Also using in SR(ISI_V2) */ +#define ISI_CTRL_EN (1 << 0) +#define ISI_CTRL_CDC (1 << 8) +/* Also using in SR/IER/IDR/IMR(ISI_V2) */ +#define ISI_CTRL_DIS (1 << 1) +#define ISI_CTRL_SRST (1 << 2) + +/* Bitfields in SR */ +#define ISI_SR_SIP (1 << 19) +/* Also using in SR/IER/IDR/IMR */ +#define ISI_SR_VSYNC (1 << 10) +#define ISI_SR_PXFR_DONE (1 << 16) +#define ISI_SR_CXFR_DONE (1 << 17) +#define ISI_SR_P_OVR (1 << 24) +#define ISI_SR_C_OVR (1 << 25) +#define ISI_SR_CRC_ERR (1 << 26) +#define ISI_SR_FR_OVR (1 << 27) + +/* Bitfields in DMA_C_CTRL & in DMA_P_CTRL */ +#define ISI_DMA_CTRL_FETCH (1 << 0) +#define ISI_DMA_CTRL_WB (1 << 1) +#define ISI_DMA_CTRL_IEN (1 << 2) +#define ISI_DMA_CTRL_DONE (1 << 3) + +/* Bitfields in DMA_CHSR/CHER/CHDR */ +#define ISI_DMA_CHSR_P_CH (1 << 0) +#define ISI_DMA_CHSR_C_CH (1 << 1) + +/* Definition for isi_platform_data */ +#define ISI_DATAWIDTH_8 0x01 +#define ISI_DATAWIDTH_10 0x02 + +struct v4l2_async_subdev; + +struct isi_platform_data { + u8 has_emb_sync; + u8 hsync_act_low; + u8 vsync_act_low; + u8 pclk_act_falling; + u8 full_mode; + u32 data_width_flags; + /* Using for ISI_CFG1 */ + u32 frate; +}; + +#endif /* __ATMEL_ISI_H__ */ diff --git a/drivers/media/platform/atmel/atmel-sama5d2-isc.c b/drivers/media/platform/atmel/atmel-sama5d2-isc.c new file mode 100644 index 000000000..9881d89a6 --- /dev/null +++ b/drivers/media/platform/atmel/atmel-sama5d2-isc.c @@ -0,0 +1,653 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip Image Sensor Controller (ISC) driver + * + * Copyright (C) 2016-2019 Microchip Technology, Inc. + * + * Author: Songjun Wu + * Author: Eugen Hristev + * + * + * Sensor-->PFE-->WB-->CFA-->CC-->GAM-->CSC-->CBC-->SUB-->RLP-->DMA + * + * ISC video pipeline integrates the following submodules: + * PFE: Parallel Front End to sample the camera sensor input stream + * WB: Programmable white balance in the Bayer domain + * CFA: Color filter array interpolation module + * CC: Programmable color correction + * GAM: Gamma correction + * CSC: Programmable color space conversion + * CBC: Contrast and Brightness control + * SUB: This module performs YCbCr444 to YCbCr420 chrominance subsampling + * RLP: This module performs rounding, range limiting + * and packing of the incoming data + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atmel-isc-regs.h" +#include "atmel-isc.h" + +#define ISC_SAMA5D2_MAX_SUPPORT_WIDTH 2592 +#define ISC_SAMA5D2_MAX_SUPPORT_HEIGHT 1944 + +#define ISC_SAMA5D2_PIPELINE \ + (WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ + CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE) + +/* This is a list of the formats that the ISC can *output* */ +static const struct isc_format sama5d2_controller_formats[] = { + { + .fourcc = V4L2_PIX_FMT_ARGB444, + }, { + .fourcc = V4L2_PIX_FMT_ARGB555, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + }, { + .fourcc = V4L2_PIX_FMT_ABGR32, + }, { + .fourcc = V4L2_PIX_FMT_XBGR32, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + }, { + .fourcc = V4L2_PIX_FMT_Y10, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + }, +}; + +/* This is a list of formats that the ISC can receive as *input* */ +static struct isc_format sama5d2_formats_list[] = { + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_BGBG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_BGBG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_GREY, + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_Y10, + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + }, + +}; + +static void isc_sama5d2_config_csc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Convert RGB to YUV */ + regmap_write(regmap, ISC_CSC_YR_YG + isc->offsets.csc, + 0x42 | (0x81 << 16)); + regmap_write(regmap, ISC_CSC_YB_OY + isc->offsets.csc, + 0x19 | (0x10 << 16)); + regmap_write(regmap, ISC_CSC_CBR_CBG + isc->offsets.csc, + 0xFDA | (0xFB6 << 16)); + regmap_write(regmap, ISC_CSC_CBB_OCB + isc->offsets.csc, + 0x70 | (0x80 << 16)); + regmap_write(regmap, ISC_CSC_CRR_CRG + isc->offsets.csc, + 0x70 | (0xFA2 << 16)); + regmap_write(regmap, ISC_CSC_CRB_OCR + isc->offsets.csc, + 0xFEE | (0x80 << 16)); +} + +static void isc_sama5d2_config_cbc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + regmap_write(regmap, ISC_CBC_BRIGHT + isc->offsets.cbc, + isc->ctrls.brightness); + regmap_write(regmap, ISC_CBC_CONTRAST + isc->offsets.cbc, + isc->ctrls.contrast); +} + +static void isc_sama5d2_config_cc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Configure each register at the neutral fixed point 1.0 or 0.0 */ + regmap_write(regmap, ISC_CC_RR_RG, (1 << 8)); + regmap_write(regmap, ISC_CC_RB_OR, 0); + regmap_write(regmap, ISC_CC_GR_GG, (1 << 8) << 16); + regmap_write(regmap, ISC_CC_GB_OG, 0); + regmap_write(regmap, ISC_CC_BR_BG, 0); + regmap_write(regmap, ISC_CC_BB_OB, (1 << 8)); +} + +static void isc_sama5d2_config_ctrls(struct isc_device *isc, + const struct v4l2_ctrl_ops *ops) +{ + struct isc_ctrls *ctrls = &isc->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + + ctrls->contrast = 256; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -2048, 2047, 1, 256); +} + +static void isc_sama5d2_config_dpc(struct isc_device *isc) +{ + /* This module is not present on sama5d2 pipeline */ +} + +static void isc_sama5d2_config_gam(struct isc_device *isc) +{ + /* No specific gamma configuration */ +} + +static void isc_sama5d2_config_rlp(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 rlp_mode = isc->config.rlp_cfg_mode; + + /* + * In sama5d2, the YUV planar modes and the YUYV modes are treated + * in the same way in RLP register. + * Normally, YYCC mode should be Luma(n) - Color B(n) - Color R (n) + * and YCYC should be Luma(n + 1) - Color B (n) - Luma (n) - Color R (n) + * but in sama5d2, the YCYC mode does not exist, and YYCC must be + * selected for both planar and interleaved modes, as in fact + * both modes are supported. + * + * Thus, if the YCYC mode is selected, replace it with the + * sama5d2-compliant mode which is YYCC . + */ + if ((rlp_mode & ISC_RLP_CFG_MODE_MASK) == ISC_RLP_CFG_MODE_YCYC) { + rlp_mode &= ~ISC_RLP_CFG_MODE_MASK; + rlp_mode |= ISC_RLP_CFG_MODE_YYCC; + } + + regmap_update_bits(regmap, ISC_RLP_CFG + isc->offsets.rlp, + ISC_RLP_CFG_MODE_MASK, rlp_mode); +} + +static void isc_sama5d2_adapt_pipeline(struct isc_device *isc) +{ + isc->try_config.bits_pipeline &= ISC_SAMA5D2_PIPELINE; +} + +/* Gamma table with gamma 1/2.2 */ +static const u32 isc_sama5d2_gamma_table[][GAMMA_ENTRIES] = { + /* 0 --> gamma 1/1.8 */ + { 0x65, 0x66002F, 0x950025, 0xBB0020, 0xDB001D, 0xF8001A, + 0x1130018, 0x12B0017, 0x1420016, 0x1580014, 0x16D0013, 0x1810012, + 0x1940012, 0x1A60012, 0x1B80011, 0x1C90010, 0x1DA0010, 0x1EA000F, + 0x1FA000F, 0x209000F, 0x218000F, 0x227000E, 0x235000E, 0x243000E, + 0x251000E, 0x25F000D, 0x26C000D, 0x279000D, 0x286000D, 0x293000C, + 0x2A0000C, 0x2AC000C, 0x2B8000C, 0x2C4000C, 0x2D0000B, 0x2DC000B, + 0x2E7000B, 0x2F3000B, 0x2FE000B, 0x309000B, 0x314000B, 0x31F000A, + 0x32A000A, 0x334000B, 0x33F000A, 0x349000A, 0x354000A, 0x35E000A, + 0x368000A, 0x372000A, 0x37C000A, 0x386000A, 0x3900009, 0x399000A, + 0x3A30009, 0x3AD0009, 0x3B60009, 0x3BF000A, 0x3C90009, 0x3D20009, + 0x3DB0009, 0x3E40009, 0x3ED0009, 0x3F60009 }, + + /* 1 --> gamma 1/2 */ + { 0x7F, 0x800034, 0xB50028, 0xDE0021, 0x100001E, 0x11E001B, + 0x1390019, 0x1520017, 0x16A0015, 0x1800014, 0x1940014, 0x1A80013, + 0x1BB0012, 0x1CD0011, 0x1DF0010, 0x1EF0010, 0x200000F, 0x20F000F, + 0x21F000E, 0x22D000F, 0x23C000E, 0x24A000E, 0x258000D, 0x265000D, + 0x273000C, 0x27F000D, 0x28C000C, 0x299000C, 0x2A5000C, 0x2B1000B, + 0x2BC000C, 0x2C8000B, 0x2D3000C, 0x2DF000B, 0x2EA000A, 0x2F5000A, + 0x2FF000B, 0x30A000A, 0x314000B, 0x31F000A, 0x329000A, 0x333000A, + 0x33D0009, 0x3470009, 0x350000A, 0x35A0009, 0x363000A, 0x36D0009, + 0x3760009, 0x37F0009, 0x3880009, 0x3910009, 0x39A0009, 0x3A30009, + 0x3AC0008, 0x3B40009, 0x3BD0008, 0x3C60008, 0x3CE0008, 0x3D60009, + 0x3DF0008, 0x3E70008, 0x3EF0008, 0x3F70008 }, + + /* 2 --> gamma 1/2.2 */ + { 0x99, 0x9B0038, 0xD4002A, 0xFF0023, 0x122001F, 0x141001B, + 0x15D0019, 0x1760017, 0x18E0015, 0x1A30015, 0x1B80013, 0x1CC0012, + 0x1DE0011, 0x1F00010, 0x2010010, 0x2110010, 0x221000F, 0x230000F, + 0x23F000E, 0x24D000E, 0x25B000D, 0x269000C, 0x276000C, 0x283000C, + 0x28F000C, 0x29B000C, 0x2A7000C, 0x2B3000B, 0x2BF000B, 0x2CA000B, + 0x2D5000B, 0x2E0000A, 0x2EB000A, 0x2F5000A, 0x2FF000A, 0x30A000A, + 0x3140009, 0x31E0009, 0x327000A, 0x3310009, 0x33A0009, 0x3440009, + 0x34D0009, 0x3560009, 0x35F0009, 0x3680008, 0x3710008, 0x3790009, + 0x3820008, 0x38A0008, 0x3930008, 0x39B0008, 0x3A30008, 0x3AB0008, + 0x3B30008, 0x3BB0008, 0x3C30008, 0x3CB0007, 0x3D20008, 0x3DA0007, + 0x3E20007, 0x3E90007, 0x3F00008, 0x3F80007 }, +}; + +static int isc_parse_dt(struct device *dev, struct isc_device *isc) +{ + struct device_node *np = dev->of_node; + struct device_node *epn = NULL; + struct isc_subdev_entity *subdev_entity; + unsigned int flags; + int ret; + + INIT_LIST_HEAD(&isc->subdev_entities); + + while (1) { + struct v4l2_fwnode_endpoint v4l2_epn = { .bus_type = 0 }; + + epn = of_graph_get_next_endpoint(np, epn); + if (!epn) + return 0; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), + &v4l2_epn); + if (ret) { + ret = -EINVAL; + dev_err(dev, "Could not parse the endpoint\n"); + break; + } + + subdev_entity = devm_kzalloc(dev, sizeof(*subdev_entity), + GFP_KERNEL); + if (!subdev_entity) { + ret = -ENOMEM; + break; + } + subdev_entity->epn = epn; + + flags = v4l2_epn.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + subdev_entity->pfe_cfg0 = ISC_PFE_CFG0_HPOL_LOW; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_VPOL_LOW; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_PPOL_LOW; + + if (v4l2_epn.bus_type == V4L2_MBUS_BT656) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_CCIR_CRC | + ISC_PFE_CFG0_CCIR656; + + list_add_tail(&subdev_entity->list, &isc->subdev_entities); + } + of_node_put(epn); + + return ret; +} + +static int atmel_isc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct isc_device *isc; + struct resource *res; + void __iomem *io_base; + struct isc_subdev_entity *subdev_entity; + int irq; + int ret; + u32 ver; + + isc = devm_kzalloc(dev, sizeof(*isc), GFP_KERNEL); + if (!isc) + return -ENOMEM; + + platform_set_drvdata(pdev, isc); + isc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + isc->regmap = devm_regmap_init_mmio(dev, io_base, &isc_regmap_config); + if (IS_ERR(isc->regmap)) { + ret = PTR_ERR(isc->regmap); + dev_err(dev, "failed to init register map: %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, isc_interrupt, 0, + "atmel-sama5d2-isc", isc); + if (ret < 0) { + dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", + irq, ret); + return ret; + } + + isc->gamma_table = isc_sama5d2_gamma_table; + isc->gamma_max = 2; + + isc->max_width = ISC_SAMA5D2_MAX_SUPPORT_WIDTH; + isc->max_height = ISC_SAMA5D2_MAX_SUPPORT_HEIGHT; + + isc->config_dpc = isc_sama5d2_config_dpc; + isc->config_csc = isc_sama5d2_config_csc; + isc->config_cbc = isc_sama5d2_config_cbc; + isc->config_cc = isc_sama5d2_config_cc; + isc->config_gam = isc_sama5d2_config_gam; + isc->config_rlp = isc_sama5d2_config_rlp; + isc->config_ctrls = isc_sama5d2_config_ctrls; + + isc->adapt_pipeline = isc_sama5d2_adapt_pipeline; + + isc->offsets.csc = ISC_SAMA5D2_CSC_OFFSET; + isc->offsets.cbc = ISC_SAMA5D2_CBC_OFFSET; + isc->offsets.sub422 = ISC_SAMA5D2_SUB422_OFFSET; + isc->offsets.sub420 = ISC_SAMA5D2_SUB420_OFFSET; + isc->offsets.rlp = ISC_SAMA5D2_RLP_OFFSET; + isc->offsets.his = ISC_SAMA5D2_HIS_OFFSET; + isc->offsets.dma = ISC_SAMA5D2_DMA_OFFSET; + isc->offsets.version = ISC_SAMA5D2_VERSION_OFFSET; + isc->offsets.his_entry = ISC_SAMA5D2_HIS_ENTRY_OFFSET; + + isc->controller_formats = sama5d2_controller_formats; + isc->controller_formats_size = ARRAY_SIZE(sama5d2_controller_formats); + isc->formats_list = sama5d2_formats_list; + isc->formats_list_size = ARRAY_SIZE(sama5d2_formats_list); + + /* sama5d2-isc - 8 bits per beat */ + isc->dcfg = ISC_DCFG_YMBSIZE_BEATS8 | ISC_DCFG_CMBSIZE_BEATS8; + + /* sama5d2-isc : ISPCK is required and mandatory */ + isc->ispck_required = true; + + ret = isc_pipeline_init(isc); + if (ret) + return ret; + + isc->hclock = devm_clk_get(dev, "hclock"); + if (IS_ERR(isc->hclock)) { + ret = PTR_ERR(isc->hclock); + dev_err(dev, "failed to get hclock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(isc->hclock); + if (ret) { + dev_err(dev, "failed to enable hclock: %d\n", ret); + return ret; + } + + ret = isc_clk_init(isc); + if (ret) { + dev_err(dev, "failed to init isc clock: %d\n", ret); + goto unprepare_hclk; + } + ret = v4l2_device_register(dev, &isc->v4l2_dev); + if (ret) { + dev_err(dev, "unable to register v4l2 device.\n"); + goto unprepare_clk; + } + + ret = isc_parse_dt(dev, isc); + if (ret) { + dev_err(dev, "fail to parse device tree\n"); + goto unregister_v4l2_device; + } + + if (list_empty(&isc->subdev_entities)) { + dev_err(dev, "no subdev found\n"); + ret = -ENODEV; + goto unregister_v4l2_device; + } + + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) { + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode = + of_fwnode_handle(subdev_entity->epn); + + v4l2_async_nf_init(&subdev_entity->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&subdev_entity->notifier, + fwnode, + struct v4l2_async_subdev); + + of_node_put(subdev_entity->epn); + subdev_entity->epn = NULL; + + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto cleanup_subdev; + } + + subdev_entity->notifier.ops = &isc_async_ops; + + ret = v4l2_async_nf_register(&isc->v4l2_dev, + &subdev_entity->notifier); + if (ret) { + dev_err(dev, "fail to register async notifier\n"); + goto cleanup_subdev; + } + + if (video_is_registered(&isc->video_dev)) + break; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_request_idle(dev); + + isc->ispck = isc->isc_clks[ISC_ISPCK].clk; + + ret = clk_prepare_enable(isc->ispck); + if (ret) { + dev_err(dev, "failed to enable ispck: %d\n", ret); + goto disable_pm; + } + + /* ispck should be greater or equal to hclock */ + ret = clk_set_rate(isc->ispck, clk_get_rate(isc->hclock)); + if (ret) { + dev_err(dev, "failed to set ispck rate: %d\n", ret); + goto unprepare_clk; + } + + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver); + dev_info(dev, "Microchip ISC version %x\n", ver); + + return 0; + +unprepare_clk: + clk_disable_unprepare(isc->ispck); + +disable_pm: + pm_runtime_disable(dev); + +cleanup_subdev: + isc_subdev_cleanup(isc); + +unregister_v4l2_device: + v4l2_device_unregister(&isc->v4l2_dev); + +unprepare_hclk: + clk_disable_unprepare(isc->hclock); + + isc_clk_cleanup(isc); + + return ret; +} + +static int atmel_isc_remove(struct platform_device *pdev) +{ + struct isc_device *isc = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + isc_subdev_cleanup(isc); + + v4l2_device_unregister(&isc->v4l2_dev); + + clk_disable_unprepare(isc->ispck); + clk_disable_unprepare(isc->hclock); + + isc_clk_cleanup(isc); + + return 0; +} + +static int __maybe_unused isc_runtime_suspend(struct device *dev) +{ + struct isc_device *isc = dev_get_drvdata(dev); + + clk_disable_unprepare(isc->ispck); + clk_disable_unprepare(isc->hclock); + + return 0; +} + +static int __maybe_unused isc_runtime_resume(struct device *dev) +{ + struct isc_device *isc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(isc->hclock); + if (ret) + return ret; + + ret = clk_prepare_enable(isc->ispck); + if (ret) + clk_disable_unprepare(isc->hclock); + + return ret; +} + +static const struct dev_pm_ops atmel_isc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(isc_runtime_suspend, isc_runtime_resume, NULL) +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id atmel_isc_of_match[] = { + { .compatible = "atmel,sama5d2-isc" }, + { } +}; +MODULE_DEVICE_TABLE(of, atmel_isc_of_match); +#endif + +static struct platform_driver atmel_isc_driver = { + .probe = atmel_isc_probe, + .remove = atmel_isc_remove, + .driver = { + .name = "atmel-sama5d2-isc", + .pm = &atmel_isc_dev_pm_ops, + .of_match_table = of_match_ptr(atmel_isc_of_match), + }, +}; + +module_platform_driver(atmel_isc_driver); + +MODULE_AUTHOR("Songjun Wu"); +MODULE_DESCRIPTION("The V4L2 driver for Atmel-ISC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/atmel/atmel-sama7g5-isc.c b/drivers/media/platform/atmel/atmel-sama7g5-isc.c new file mode 100644 index 000000000..8b11aa834 --- /dev/null +++ b/drivers/media/platform/atmel/atmel-sama7g5-isc.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Microchip eXtended Image Sensor Controller (XISC) driver + * + * Copyright (C) 2019-2021 Microchip Technology, Inc. and its subsidiaries + * + * Author: Eugen Hristev + * + * Sensor-->PFE-->DPC-->WB-->CFA-->CC-->GAM-->VHXS-->CSC-->CBHS-->SUB-->RLP-->DMA-->HIS + * + * ISC video pipeline integrates the following submodules: + * PFE: Parallel Front End to sample the camera sensor input stream + * DPC: Defective Pixel Correction with black offset correction, green disparity + * correction and defective pixel correction (3 modules total) + * WB: Programmable white balance in the Bayer domain + * CFA: Color filter array interpolation module + * CC: Programmable color correction + * GAM: Gamma correction + *VHXS: Vertical and Horizontal Scaler + * CSC: Programmable color space conversion + *CBHS: Contrast Brightness Hue and Saturation control + * SUB: This module performs YCbCr444 to YCbCr420 chrominance subsampling + * RLP: This module performs rounding, range limiting + * and packing of the incoming data + * DMA: This module performs DMA master accesses to write frames to external RAM + * HIS: Histogram module performs statistic counters on the frames + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "atmel-isc-regs.h" +#include "atmel-isc.h" + +#define ISC_SAMA7G5_MAX_SUPPORT_WIDTH 3264 +#define ISC_SAMA7G5_MAX_SUPPORT_HEIGHT 2464 + +#define ISC_SAMA7G5_PIPELINE \ + (WB_ENABLE | CFA_ENABLE | CC_ENABLE | GAM_ENABLES | CSC_ENABLE | \ + CBC_ENABLE | SUB422_ENABLE | SUB420_ENABLE) + +/* This is a list of the formats that the ISC can *output* */ +static const struct isc_format sama7g5_controller_formats[] = { + { + .fourcc = V4L2_PIX_FMT_ARGB444, + }, { + .fourcc = V4L2_PIX_FMT_ARGB555, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + }, { + .fourcc = V4L2_PIX_FMT_ABGR32, + }, { + .fourcc = V4L2_PIX_FMT_XBGR32, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + }, { + .fourcc = V4L2_PIX_FMT_Y10, + }, { + .fourcc = V4L2_PIX_FMT_Y16, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + }, +}; + +/* This is a list of formats that the ISC can receive as *input* */ +static struct isc_format sama7g5_formats_list[] = { + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_BGBG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_BGBG, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_GBGB, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_GRGR, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TWELVE, + .cfa_baycfg = ISC_BAY_CFG_RGRG, + }, + { + .fourcc = V4L2_PIX_FMT_GREY, + .mbus_code = MEDIA_BUS_FMT_Y8_1X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .pfe_cfg0_bps = ISC_PFE_CFG0_BPS_EIGHT, + }, + { + .fourcc = V4L2_PIX_FMT_Y10, + .mbus_code = MEDIA_BUS_FMT_Y10_1X10, + .pfe_cfg0_bps = ISC_PFG_CFG0_BPS_TEN, + }, +}; + +static void isc_sama7g5_config_csc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Convert RGB to YUV */ + regmap_write(regmap, ISC_CSC_YR_YG + isc->offsets.csc, + 0x42 | (0x81 << 16)); + regmap_write(regmap, ISC_CSC_YB_OY + isc->offsets.csc, + 0x19 | (0x10 << 16)); + regmap_write(regmap, ISC_CSC_CBR_CBG + isc->offsets.csc, + 0xFDA | (0xFB6 << 16)); + regmap_write(regmap, ISC_CSC_CBB_OCB + isc->offsets.csc, + 0x70 | (0x80 << 16)); + regmap_write(regmap, ISC_CSC_CRR_CRG + isc->offsets.csc, + 0x70 | (0xFA2 << 16)); + regmap_write(regmap, ISC_CSC_CRB_OCR + isc->offsets.csc, + 0xFEE | (0x80 << 16)); +} + +static void isc_sama7g5_config_cbc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Configure what is set via v4l2 ctrls */ + regmap_write(regmap, ISC_CBC_BRIGHT + isc->offsets.cbc, isc->ctrls.brightness); + regmap_write(regmap, ISC_CBC_CONTRAST + isc->offsets.cbc, isc->ctrls.contrast); + /* Configure Hue and Saturation as neutral midpoint */ + regmap_write(regmap, ISC_CBCHS_HUE, 0); + regmap_write(regmap, ISC_CBCHS_SAT, (1 << 4)); +} + +static void isc_sama7g5_config_cc(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + /* Configure each register at the neutral fixed point 1.0 or 0.0 */ + regmap_write(regmap, ISC_CC_RR_RG, (1 << 8)); + regmap_write(regmap, ISC_CC_RB_OR, 0); + regmap_write(regmap, ISC_CC_GR_GG, (1 << 8) << 16); + regmap_write(regmap, ISC_CC_GB_OG, 0); + regmap_write(regmap, ISC_CC_BR_BG, 0); + regmap_write(regmap, ISC_CC_BB_OB, (1 << 8)); +} + +static void isc_sama7g5_config_ctrls(struct isc_device *isc, + const struct v4l2_ctrl_ops *ops) +{ + struct isc_ctrls *ctrls = &isc->ctrls; + struct v4l2_ctrl_handler *hdl = &ctrls->handler; + + ctrls->contrast = 16; + + v4l2_ctrl_new_std(hdl, ops, V4L2_CID_CONTRAST, -2048, 2047, 1, 16); +} + +static void isc_sama7g5_config_dpc(struct isc_device *isc) +{ + u32 bay_cfg = isc->config.sd_format->cfa_baycfg; + struct regmap *regmap = isc->regmap; + + regmap_update_bits(regmap, ISC_DPC_CFG, ISC_DPC_CFG_BLOFF_MASK, + (64 << ISC_DPC_CFG_BLOFF_SHIFT)); + regmap_update_bits(regmap, ISC_DPC_CFG, ISC_DPC_CFG_BAYCFG_MASK, + (bay_cfg << ISC_DPC_CFG_BAYCFG_SHIFT)); +} + +static void isc_sama7g5_config_gam(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + + regmap_update_bits(regmap, ISC_GAM_CTRL, ISC_GAM_CTRL_BIPART, + ISC_GAM_CTRL_BIPART); +} + +static void isc_sama7g5_config_rlp(struct isc_device *isc) +{ + struct regmap *regmap = isc->regmap; + u32 rlp_mode = isc->config.rlp_cfg_mode; + + regmap_update_bits(regmap, ISC_RLP_CFG + isc->offsets.rlp, + ISC_RLP_CFG_MODE_MASK | ISC_RLP_CFG_LSH | + ISC_RLP_CFG_YMODE_MASK, rlp_mode); +} + +static void isc_sama7g5_adapt_pipeline(struct isc_device *isc) +{ + isc->try_config.bits_pipeline &= ISC_SAMA7G5_PIPELINE; +} + +/* Gamma table with gamma 1/2.2 */ +static const u32 isc_sama7g5_gamma_table[][GAMMA_ENTRIES] = { + /* index 0 --> gamma bipartite */ + { + 0x980, 0x4c0320, 0x650260, 0x7801e0, 0x8701a0, 0x940180, + 0xa00160, 0xab0120, 0xb40120, 0xbd0120, 0xc60100, 0xce0100, + 0xd600e0, 0xdd00e0, 0xe400e0, 0xeb00c0, 0xf100c0, 0xf700c0, + 0xfd00c0, 0x10300a0, 0x10800c0, 0x10e00a0, 0x11300a0, 0x11800a0, + 0x11d00a0, 0x12200a0, 0x12700a0, 0x12c0080, 0x13000a0, 0x1350080, + 0x13900a0, 0x13e0080, 0x1420076, 0x17d0062, 0x1ae0054, 0x1d8004a, + 0x1fd0044, 0x21f003e, 0x23e003a, 0x25b0036, 0x2760032, 0x28f0030, + 0x2a7002e, 0x2be002c, 0x2d4002c, 0x2ea0028, 0x2fe0028, 0x3120026, + 0x3250024, 0x3370024, 0x3490022, 0x35a0022, 0x36b0020, 0x37b0020, + 0x38b0020, 0x39b001e, 0x3aa001e, 0x3b9001c, 0x3c7001c, 0x3d5001c, + 0x3e3001c, 0x3f1001c, 0x3ff001a, 0x40c001a }, +}; + +static int xisc_parse_dt(struct device *dev, struct isc_device *isc) +{ + struct device_node *np = dev->of_node; + struct device_node *epn = NULL; + struct isc_subdev_entity *subdev_entity; + unsigned int flags; + int ret; + bool mipi_mode; + + INIT_LIST_HEAD(&isc->subdev_entities); + + mipi_mode = of_property_read_bool(np, "microchip,mipi-mode"); + + while (1) { + struct v4l2_fwnode_endpoint v4l2_epn = { .bus_type = 0 }; + + epn = of_graph_get_next_endpoint(np, epn); + if (!epn) + return 0; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(epn), + &v4l2_epn); + if (ret) { + ret = -EINVAL; + dev_err(dev, "Could not parse the endpoint\n"); + break; + } + + subdev_entity = devm_kzalloc(dev, sizeof(*subdev_entity), + GFP_KERNEL); + if (!subdev_entity) { + ret = -ENOMEM; + break; + } + subdev_entity->epn = epn; + + flags = v4l2_epn.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + subdev_entity->pfe_cfg0 = ISC_PFE_CFG0_HPOL_LOW; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_VPOL_LOW; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_PPOL_LOW; + + if (v4l2_epn.bus_type == V4L2_MBUS_BT656) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_CCIR_CRC | + ISC_PFE_CFG0_CCIR656; + + if (mipi_mode) + subdev_entity->pfe_cfg0 |= ISC_PFE_CFG0_MIPI; + + list_add_tail(&subdev_entity->list, &isc->subdev_entities); + } + of_node_put(epn); + + return ret; +} + +static int microchip_xisc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct isc_device *isc; + struct resource *res; + void __iomem *io_base; + struct isc_subdev_entity *subdev_entity; + int irq; + int ret; + u32 ver; + + isc = devm_kzalloc(dev, sizeof(*isc), GFP_KERNEL); + if (!isc) + return -ENOMEM; + + platform_set_drvdata(pdev, isc); + isc->dev = dev; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + io_base = devm_ioremap_resource(dev, res); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + isc->regmap = devm_regmap_init_mmio(dev, io_base, &isc_regmap_config); + if (IS_ERR(isc->regmap)) { + ret = PTR_ERR(isc->regmap); + dev_err(dev, "failed to init register map: %d\n", ret); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, isc_interrupt, 0, + "microchip-sama7g5-xisc", isc); + if (ret < 0) { + dev_err(dev, "can't register ISR for IRQ %u (ret=%i)\n", + irq, ret); + return ret; + } + + isc->gamma_table = isc_sama7g5_gamma_table; + isc->gamma_max = 0; + + isc->max_width = ISC_SAMA7G5_MAX_SUPPORT_WIDTH; + isc->max_height = ISC_SAMA7G5_MAX_SUPPORT_HEIGHT; + + isc->config_dpc = isc_sama7g5_config_dpc; + isc->config_csc = isc_sama7g5_config_csc; + isc->config_cbc = isc_sama7g5_config_cbc; + isc->config_cc = isc_sama7g5_config_cc; + isc->config_gam = isc_sama7g5_config_gam; + isc->config_rlp = isc_sama7g5_config_rlp; + isc->config_ctrls = isc_sama7g5_config_ctrls; + + isc->adapt_pipeline = isc_sama7g5_adapt_pipeline; + + isc->offsets.csc = ISC_SAMA7G5_CSC_OFFSET; + isc->offsets.cbc = ISC_SAMA7G5_CBC_OFFSET; + isc->offsets.sub422 = ISC_SAMA7G5_SUB422_OFFSET; + isc->offsets.sub420 = ISC_SAMA7G5_SUB420_OFFSET; + isc->offsets.rlp = ISC_SAMA7G5_RLP_OFFSET; + isc->offsets.his = ISC_SAMA7G5_HIS_OFFSET; + isc->offsets.dma = ISC_SAMA7G5_DMA_OFFSET; + isc->offsets.version = ISC_SAMA7G5_VERSION_OFFSET; + isc->offsets.his_entry = ISC_SAMA7G5_HIS_ENTRY_OFFSET; + + isc->controller_formats = sama7g5_controller_formats; + isc->controller_formats_size = ARRAY_SIZE(sama7g5_controller_formats); + isc->formats_list = sama7g5_formats_list; + isc->formats_list_size = ARRAY_SIZE(sama7g5_formats_list); + + /* sama7g5-isc RAM access port is full AXI4 - 32 bits per beat */ + isc->dcfg = ISC_DCFG_YMBSIZE_BEATS32 | ISC_DCFG_CMBSIZE_BEATS32; + + /* sama7g5-isc : ISPCK does not exist, ISC is clocked by MCK */ + isc->ispck_required = false; + + ret = isc_pipeline_init(isc); + if (ret) + return ret; + + isc->hclock = devm_clk_get(dev, "hclock"); + if (IS_ERR(isc->hclock)) { + ret = PTR_ERR(isc->hclock); + dev_err(dev, "failed to get hclock: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(isc->hclock); + if (ret) { + dev_err(dev, "failed to enable hclock: %d\n", ret); + return ret; + } + + ret = isc_clk_init(isc); + if (ret) { + dev_err(dev, "failed to init isc clock: %d\n", ret); + goto unprepare_hclk; + } + + ret = v4l2_device_register(dev, &isc->v4l2_dev); + if (ret) { + dev_err(dev, "unable to register v4l2 device.\n"); + goto unprepare_hclk; + } + + ret = xisc_parse_dt(dev, isc); + if (ret) { + dev_err(dev, "fail to parse device tree\n"); + goto unregister_v4l2_device; + } + + if (list_empty(&isc->subdev_entities)) { + dev_err(dev, "no subdev found\n"); + ret = -ENODEV; + goto unregister_v4l2_device; + } + + list_for_each_entry(subdev_entity, &isc->subdev_entities, list) { + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode = + of_fwnode_handle(subdev_entity->epn); + + v4l2_async_nf_init(&subdev_entity->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&subdev_entity->notifier, + fwnode, + struct v4l2_async_subdev); + + of_node_put(subdev_entity->epn); + subdev_entity->epn = NULL; + + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto cleanup_subdev; + } + + subdev_entity->notifier.ops = &isc_async_ops; + + ret = v4l2_async_nf_register(&isc->v4l2_dev, + &subdev_entity->notifier); + if (ret) { + dev_err(dev, "fail to register async notifier\n"); + goto cleanup_subdev; + } + + if (video_is_registered(&isc->video_dev)) + break; + } + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + pm_request_idle(dev); + + regmap_read(isc->regmap, ISC_VERSION + isc->offsets.version, &ver); + dev_info(dev, "Microchip XISC version %x\n", ver); + + return 0; + +cleanup_subdev: + isc_subdev_cleanup(isc); + +unregister_v4l2_device: + v4l2_device_unregister(&isc->v4l2_dev); + +unprepare_hclk: + clk_disable_unprepare(isc->hclock); + + isc_clk_cleanup(isc); + + return ret; +} + +static int microchip_xisc_remove(struct platform_device *pdev) +{ + struct isc_device *isc = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + isc_subdev_cleanup(isc); + + v4l2_device_unregister(&isc->v4l2_dev); + + clk_disable_unprepare(isc->hclock); + + isc_clk_cleanup(isc); + + return 0; +} + +static int __maybe_unused xisc_runtime_suspend(struct device *dev) +{ + struct isc_device *isc = dev_get_drvdata(dev); + + clk_disable_unprepare(isc->hclock); + + return 0; +} + +static int __maybe_unused xisc_runtime_resume(struct device *dev) +{ + struct isc_device *isc = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(isc->hclock); + if (ret) + return ret; + + return ret; +} + +static const struct dev_pm_ops microchip_xisc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(xisc_runtime_suspend, xisc_runtime_resume, NULL) +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id microchip_xisc_of_match[] = { + { .compatible = "microchip,sama7g5-isc" }, + { } +}; +MODULE_DEVICE_TABLE(of, microchip_xisc_of_match); +#endif + +static struct platform_driver microchip_xisc_driver = { + .probe = microchip_xisc_probe, + .remove = microchip_xisc_remove, + .driver = { + .name = "microchip-sama7g5-xisc", + .pm = µchip_xisc_dev_pm_ops, + .of_match_table = of_match_ptr(microchip_xisc_of_match), + }, +}; + +module_platform_driver(microchip_xisc_driver); + +MODULE_AUTHOR("Eugen Hristev "); +MODULE_DESCRIPTION("The V4L2 driver for Microchip-XISC"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/atmel/microchip-csi2dc.c b/drivers/media/platform/atmel/microchip-csi2dc.c new file mode 100644 index 000000000..d5b359f60 --- /dev/null +++ b/drivers/media/platform/atmel/microchip-csi2dc.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Microchip CSI2 Demux Controller (CSI2DC) driver + * + * Copyright (C) 2018 Microchip Technology, Inc. + * + * Author: Eugen Hristev + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* Global configuration register */ +#define CSI2DC_GCFG 0x0 + +/* MIPI sensor pixel clock is free running */ +#define CSI2DC_GCFG_MIPIFRN BIT(0) +/* GPIO parallel interface selection */ +#define CSI2DC_GCFG_GPIOSEL BIT(1) +/* Output waveform inter-line minimum delay */ +#define CSI2DC_GCFG_HLC(v) ((v) << 4) +#define CSI2DC_GCFG_HLC_MASK GENMASK(7, 4) +/* SAMA7G5 requires a HLC delay of 15 */ +#define SAMA7G5_HLC (15) + +/* Global control register */ +#define CSI2DC_GCTLR 0x04 +#define CSI2DC_GCTLR_SWRST BIT(0) + +/* Global status register */ +#define CSI2DC_GS 0x08 + +/* SSP interrupt status register */ +#define CSI2DC_SSPIS 0x28 +/* Pipe update register */ +#define CSI2DC_PU 0xc0 +/* Video pipe attributes update */ +#define CSI2DC_PU_VP BIT(0) + +/* Pipe update status register */ +#define CSI2DC_PUS 0xc4 + +/* Video pipeline Interrupt Status Register */ +#define CSI2DC_VPISR 0xf4 + +/* Video pipeline enable register */ +#define CSI2DC_VPE 0xf8 +#define CSI2DC_VPE_ENABLE BIT(0) + +/* Video pipeline configuration register */ +#define CSI2DC_VPCFG 0xfc +/* Data type */ +#define CSI2DC_VPCFG_DT(v) ((v) << 0) +#define CSI2DC_VPCFG_DT_MASK GENMASK(5, 0) +/* Virtual channel identifier */ +#define CSI2DC_VPCFG_VC(v) ((v) << 6) +#define CSI2DC_VPCFG_VC_MASK GENMASK(7, 6) +/* Decompression enable */ +#define CSI2DC_VPCFG_DE BIT(8) +/* Decoder mode */ +#define CSI2DC_VPCFG_DM(v) ((v) << 9) +#define CSI2DC_VPCFG_DM_DECODER8TO12 0 +/* Decoder predictor 2 selection */ +#define CSI2DC_VPCFG_DP2 BIT(12) +/* Recommended memory storage */ +#define CSI2DC_VPCFG_RMS BIT(13) +/* Post adjustment */ +#define CSI2DC_VPCFG_PA BIT(14) + +/* Video pipeline column register */ +#define CSI2DC_VPCOL 0x100 +/* Column number */ +#define CSI2DC_VPCOL_COL(v) ((v) << 0) +#define CSI2DC_VPCOL_COL_MASK GENMASK(15, 0) + +/* Video pipeline row register */ +#define CSI2DC_VPROW 0x104 +/* Row number */ +#define CSI2DC_VPROW_ROW(v) ((v) << 0) +#define CSI2DC_VPROW_ROW_MASK GENMASK(15, 0) + +/* Version register */ +#define CSI2DC_VERSION 0x1fc + +/* register read/write helpers */ +#define csi2dc_readl(st, reg) readl_relaxed((st)->base + (reg)) +#define csi2dc_writel(st, reg, val) writel_relaxed((val), \ + (st)->base + (reg)) + +/* supported RAW data types */ +#define CSI2DC_DT_RAW6 0x28 +#define CSI2DC_DT_RAW7 0x29 +#define CSI2DC_DT_RAW8 0x2a +#define CSI2DC_DT_RAW10 0x2b +#define CSI2DC_DT_RAW12 0x2c +#define CSI2DC_DT_RAW14 0x2d +/* YUV data types */ +#define CSI2DC_DT_YUV422_8B 0x1e + +/* + * struct csi2dc_format - CSI2DC format type struct + * @mbus_code: Media bus code for the format + * @dt: Data type constant for this format + */ +struct csi2dc_format { + u32 mbus_code; + u32 dt; +}; + +static const struct csi2dc_format csi2dc_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .dt = CSI2DC_DT_RAW8, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .dt = CSI2DC_DT_RAW8, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .dt = CSI2DC_DT_RAW8, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .dt = CSI2DC_DT_RAW8, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .dt = CSI2DC_DT_RAW10, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .dt = CSI2DC_DT_RAW10, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .dt = CSI2DC_DT_RAW10, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .dt = CSI2DC_DT_RAW10, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .dt = CSI2DC_DT_YUV422_8B, + }, +}; + +enum mipi_csi_pads { + CSI2DC_PAD_SINK = 0, + CSI2DC_PAD_SOURCE = 1, + CSI2DC_PADS_NUM = 2, +}; + +/* + * struct csi2dc_device - CSI2DC device driver data/config struct + * @base: Register map base address + * @csi2dc_sd: v4l2 subdevice for the csi2dc device + * This is the subdevice that the csi2dc device itself + * registers in v4l2 subsystem + * @dev: struct device for this csi2dc device + * @pclk: Peripheral clock reference + * Input clock that clocks the hardware block internal + * logic + * @scck: Sensor Controller clock reference + * Input clock that is used to generate the pixel clock + * @format: Current saved format used in g/s fmt + * @cur_fmt: Current state format + * @try_fmt: Try format that is being tried + * @pads: Media entity pads for the csi2dc subdevice + * @clk_gated: Whether the clock is gated or free running + * @video_pipe: Whether video pipeline is configured + * @parallel_mode: The underlying subdevice is connected on a parallel bus + * @vc: Current set virtual channel + * @notifier: Async notifier that is used to bound the underlying + * subdevice to the csi2dc subdevice + * @input_sd: Reference to the underlying subdevice bound to the + * csi2dc subdevice + * @remote_pad: Pad number of the underlying subdevice that is linked + * to the csi2dc subdevice sink pad. + */ +struct csi2dc_device { + void __iomem *base; + struct v4l2_subdev csi2dc_sd; + struct device *dev; + struct clk *pclk; + struct clk *scck; + + struct v4l2_mbus_framefmt format; + + const struct csi2dc_format *cur_fmt; + const struct csi2dc_format *try_fmt; + + struct media_pad pads[CSI2DC_PADS_NUM]; + + bool clk_gated; + bool video_pipe; + bool parallel_mode; + u32 vc; + + struct v4l2_async_notifier notifier; + + struct v4l2_subdev *input_sd; + + u32 remote_pad; +}; + +static inline struct csi2dc_device * +csi2dc_sd_to_csi2dc_device(struct v4l2_subdev *csi2dc_sd) +{ + return container_of(csi2dc_sd, struct csi2dc_device, csi2dc_sd); +} + +static int csi2dc_enum_mbus_code(struct v4l2_subdev *csi2dc_sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(csi2dc_formats)) + return -EINVAL; + + code->code = csi2dc_formats[code->index].mbus_code; + + return 0; +} + +static int csi2dc_get_fmt(struct v4l2_subdev *csi2dc_sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd); + struct v4l2_mbus_framefmt *v4l2_try_fmt; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state, + format->pad); + format->format = *v4l2_try_fmt; + + return 0; + } + + format->format = csi2dc->format; + + return 0; +} + +static int csi2dc_set_fmt(struct v4l2_subdev *csi2dc_sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *req_fmt) +{ + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd); + const struct csi2dc_format *fmt, *try_fmt = NULL; + struct v4l2_mbus_framefmt *v4l2_try_fmt; + unsigned int i; + + /* + * Setting the source pad is disabled. + * The same format is being propagated from the sink to source. + */ + if (req_fmt->pad == CSI2DC_PAD_SOURCE) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(csi2dc_formats); i++) { + fmt = &csi2dc_formats[i]; + if (req_fmt->format.code == fmt->mbus_code) + try_fmt = fmt; + fmt++; + } + + /* in case we could not find the desired format, default to something */ + if (!try_fmt) { + try_fmt = &csi2dc_formats[0]; + + dev_dbg(csi2dc->dev, + "CSI2DC unsupported format 0x%x, defaulting to 0x%x\n", + req_fmt->format.code, csi2dc_formats[0].mbus_code); + } + + req_fmt->format.code = try_fmt->mbus_code; + req_fmt->format.colorspace = V4L2_COLORSPACE_SRGB; + req_fmt->format.field = V4L2_FIELD_NONE; + + if (req_fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, sd_state, + req_fmt->pad); + *v4l2_try_fmt = req_fmt->format; + /* Trying on the sink pad makes the source pad change too */ + v4l2_try_fmt = v4l2_subdev_get_try_format(csi2dc_sd, + sd_state, + CSI2DC_PAD_SOURCE); + *v4l2_try_fmt = req_fmt->format; + + /* if we are just trying, we are done */ + return 0; + } + + /* save the format for later requests */ + csi2dc->format = req_fmt->format; + + /* update config */ + csi2dc->cur_fmt = try_fmt; + + dev_dbg(csi2dc->dev, "new format set: 0x%x @%dx%d\n", + csi2dc->format.code, csi2dc->format.width, + csi2dc->format.height); + + return 0; +} + +static int csi2dc_power(struct csi2dc_device *csi2dc, int on) +{ + int ret = 0; + + if (on) { + ret = clk_prepare_enable(csi2dc->pclk); + if (ret) { + dev_err(csi2dc->dev, "failed to enable pclk:%d\n", ret); + return ret; + } + + ret = clk_prepare_enable(csi2dc->scck); + if (ret) { + dev_err(csi2dc->dev, "failed to enable scck:%d\n", ret); + clk_disable_unprepare(csi2dc->pclk); + return ret; + } + + /* if powering up, deassert reset line */ + csi2dc_writel(csi2dc, CSI2DC_GCTLR, CSI2DC_GCTLR_SWRST); + } else { + /* if powering down, assert reset line */ + csi2dc_writel(csi2dc, CSI2DC_GCTLR, 0); + + clk_disable_unprepare(csi2dc->scck); + clk_disable_unprepare(csi2dc->pclk); + } + + return ret; +} + +static int csi2dc_get_mbus_config(struct csi2dc_device *csi2dc) +{ + struct v4l2_mbus_config mbus_config = { 0 }; + int ret; + + ret = v4l2_subdev_call(csi2dc->input_sd, pad, get_mbus_config, + csi2dc->remote_pad, &mbus_config); + if (ret == -ENOIOCTLCMD) { + dev_dbg(csi2dc->dev, + "no remote mbus configuration available\n"); + return 0; + } + + if (ret) { + dev_err(csi2dc->dev, + "failed to get remote mbus configuration\n"); + return 0; + } + + dev_dbg(csi2dc->dev, "subdev sending on channel %d\n", csi2dc->vc); + + csi2dc->clk_gated = mbus_config.bus.parallel.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + + dev_dbg(csi2dc->dev, "mbus_config: %s clock\n", + csi2dc->clk_gated ? "gated" : "free running"); + + return 0; +} + +static void csi2dc_vp_update(struct csi2dc_device *csi2dc) +{ + u32 vp, gcfg; + + if (!csi2dc->video_pipe) { + dev_err(csi2dc->dev, "video pipeline unavailable\n"); + return; + } + + if (csi2dc->parallel_mode) { + /* In parallel mode, GPIO parallel interface must be selected */ + gcfg = csi2dc_readl(csi2dc, CSI2DC_GCFG); + gcfg |= CSI2DC_GCFG_GPIOSEL; + csi2dc_writel(csi2dc, CSI2DC_GCFG, gcfg); + return; + } + + /* serial video pipeline */ + + csi2dc_writel(csi2dc, CSI2DC_GCFG, + (SAMA7G5_HLC & CSI2DC_GCFG_HLC_MASK) | + (csi2dc->clk_gated ? 0 : CSI2DC_GCFG_MIPIFRN)); + + vp = CSI2DC_VPCFG_DT(csi2dc->cur_fmt->dt) & CSI2DC_VPCFG_DT_MASK; + vp |= CSI2DC_VPCFG_VC(csi2dc->vc) & CSI2DC_VPCFG_VC_MASK; + vp &= ~CSI2DC_VPCFG_DE; + vp |= CSI2DC_VPCFG_DM(CSI2DC_VPCFG_DM_DECODER8TO12); + vp &= ~CSI2DC_VPCFG_DP2; + vp &= ~CSI2DC_VPCFG_RMS; + vp |= CSI2DC_VPCFG_PA; + + csi2dc_writel(csi2dc, CSI2DC_VPCFG, vp); + csi2dc_writel(csi2dc, CSI2DC_VPE, CSI2DC_VPE_ENABLE); + csi2dc_writel(csi2dc, CSI2DC_PU, CSI2DC_PU_VP); +} + +static int csi2dc_s_stream(struct v4l2_subdev *csi2dc_sd, int enable) +{ + struct csi2dc_device *csi2dc = csi2dc_sd_to_csi2dc_device(csi2dc_sd); + int ret; + + if (enable) { + ret = pm_runtime_resume_and_get(csi2dc->dev); + if (ret < 0) + return ret; + + csi2dc_get_mbus_config(csi2dc); + + csi2dc_vp_update(csi2dc); + + return v4l2_subdev_call(csi2dc->input_sd, video, s_stream, + true); + } + + dev_dbg(csi2dc->dev, + "Last frame received: VPCOLR = %u, VPROWR= %u, VPISR = %x\n", + csi2dc_readl(csi2dc, CSI2DC_VPCOL), + csi2dc_readl(csi2dc, CSI2DC_VPROW), + csi2dc_readl(csi2dc, CSI2DC_VPISR)); + + /* stop streaming scenario */ + ret = v4l2_subdev_call(csi2dc->input_sd, video, s_stream, false); + + pm_runtime_put_sync(csi2dc->dev); + + return ret; +} + +static int csi2dc_init_cfg(struct v4l2_subdev *csi2dc_sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *v4l2_try_fmt = + v4l2_subdev_get_try_format(csi2dc_sd, sd_state, 0); + + v4l2_try_fmt->height = 480; + v4l2_try_fmt->width = 640; + v4l2_try_fmt->code = csi2dc_formats[0].mbus_code; + v4l2_try_fmt->colorspace = V4L2_COLORSPACE_SRGB; + v4l2_try_fmt->field = V4L2_FIELD_NONE; + v4l2_try_fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + v4l2_try_fmt->quantization = V4L2_QUANTIZATION_DEFAULT; + v4l2_try_fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static const struct media_entity_operations csi2dc_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_pad_ops csi2dc_pad_ops = { + .enum_mbus_code = csi2dc_enum_mbus_code, + .set_fmt = csi2dc_set_fmt, + .get_fmt = csi2dc_get_fmt, + .init_cfg = csi2dc_init_cfg, +}; + +static const struct v4l2_subdev_video_ops csi2dc_video_ops = { + .s_stream = csi2dc_s_stream, +}; + +static const struct v4l2_subdev_ops csi2dc_subdev_ops = { + .pad = &csi2dc_pad_ops, + .video = &csi2dc_video_ops, +}; + +static int csi2dc_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct csi2dc_device *csi2dc = container_of(notifier, + struct csi2dc_device, notifier); + int pad; + int ret; + + csi2dc->input_sd = subdev; + + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) { + dev_err(csi2dc->dev, "Failed to find pad for %s\n", + subdev->name); + return pad; + } + + csi2dc->remote_pad = pad; + + ret = media_create_pad_link(&csi2dc->input_sd->entity, + csi2dc->remote_pad, + &csi2dc->csi2dc_sd.entity, 0, + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(csi2dc->dev, + "Failed to create pad link: %s to %s\n", + csi2dc->input_sd->entity.name, + csi2dc->csi2dc_sd.entity.name); + return ret; + } + + dev_dbg(csi2dc->dev, "link with %s pad: %d\n", + csi2dc->input_sd->name, csi2dc->remote_pad); + + return ret; +} + +static const struct v4l2_async_notifier_operations csi2dc_async_ops = { + .bound = csi2dc_async_bound, +}; + +static int csi2dc_prepare_notifier(struct csi2dc_device *csi2dc, + struct fwnode_handle *input_fwnode) +{ + struct v4l2_async_subdev *asd; + int ret = 0; + + v4l2_async_nf_init(&csi2dc->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&csi2dc->notifier, + input_fwnode, + struct v4l2_async_subdev); + + fwnode_handle_put(input_fwnode); + + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + dev_err(csi2dc->dev, + "failed to add async notifier for node %pOF: %d\n", + to_of_node(input_fwnode), ret); + v4l2_async_nf_cleanup(&csi2dc->notifier); + return ret; + } + + csi2dc->notifier.ops = &csi2dc_async_ops; + + ret = v4l2_async_subdev_nf_register(&csi2dc->csi2dc_sd, + &csi2dc->notifier); + if (ret) { + dev_err(csi2dc->dev, "fail to register async notifier: %d\n", + ret); + v4l2_async_nf_cleanup(&csi2dc->notifier); + } + + return ret; +} + +static int csi2dc_of_parse(struct csi2dc_device *csi2dc, + struct device_node *of_node) +{ + struct fwnode_handle *input_fwnode, *output_fwnode; + struct v4l2_fwnode_endpoint input_endpoint = { 0 }, + output_endpoint = { 0 }; + int ret; + + input_fwnode = fwnode_graph_get_next_endpoint(of_fwnode_handle(of_node), + NULL); + if (!input_fwnode) { + dev_err(csi2dc->dev, + "missing port node at %pOF, input node is mandatory.\n", + of_node); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(input_fwnode, &input_endpoint); + if (ret) { + dev_err(csi2dc->dev, "endpoint not defined at %pOF\n", of_node); + goto csi2dc_of_parse_err; + } + + if (input_endpoint.bus_type == V4L2_MBUS_PARALLEL || + input_endpoint.bus_type == V4L2_MBUS_BT656) { + csi2dc->parallel_mode = true; + dev_dbg(csi2dc->dev, + "subdevice connected on parallel interface\n"); + } + + if (input_endpoint.bus_type == V4L2_MBUS_CSI2_DPHY) { + csi2dc->clk_gated = input_endpoint.bus.mipi_csi2.flags & + V4L2_MBUS_CSI2_NONCONTINUOUS_CLOCK; + dev_dbg(csi2dc->dev, + "subdevice connected on serial interface\n"); + dev_dbg(csi2dc->dev, "DT: %s clock\n", + csi2dc->clk_gated ? "gated" : "free running"); + } + + output_fwnode = fwnode_graph_get_next_endpoint + (of_fwnode_handle(of_node), input_fwnode); + + if (output_fwnode) + ret = v4l2_fwnode_endpoint_parse(output_fwnode, + &output_endpoint); + + fwnode_handle_put(output_fwnode); + + if (!output_fwnode || ret) { + dev_info(csi2dc->dev, + "missing output node at %pOF, data pipe available only.\n", + of_node); + } else { + if (output_endpoint.bus_type != V4L2_MBUS_PARALLEL && + output_endpoint.bus_type != V4L2_MBUS_BT656) { + dev_err(csi2dc->dev, + "output port must be parallel/bt656.\n"); + ret = -EINVAL; + goto csi2dc_of_parse_err; + } + + csi2dc->video_pipe = true; + + dev_dbg(csi2dc->dev, + "block %pOF [%d.%d]->[%d.%d] video pipeline\n", + of_node, input_endpoint.base.port, + input_endpoint.base.id, output_endpoint.base.port, + output_endpoint.base.id); + } + + /* prepare async notifier for subdevice completion */ + return csi2dc_prepare_notifier(csi2dc, input_fwnode); + +csi2dc_of_parse_err: + fwnode_handle_put(input_fwnode); + return ret; +} + +static void csi2dc_default_format(struct csi2dc_device *csi2dc) +{ + csi2dc->cur_fmt = &csi2dc_formats[0]; + + csi2dc->format.height = 480; + csi2dc->format.width = 640; + csi2dc->format.code = csi2dc_formats[0].mbus_code; + csi2dc->format.colorspace = V4L2_COLORSPACE_SRGB; + csi2dc->format.field = V4L2_FIELD_NONE; + csi2dc->format.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + csi2dc->format.quantization = V4L2_QUANTIZATION_DEFAULT; + csi2dc->format.xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int csi2dc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct csi2dc_device *csi2dc; + int ret = 0; + u32 ver; + + csi2dc = devm_kzalloc(dev, sizeof(*csi2dc), GFP_KERNEL); + if (!csi2dc) + return -ENOMEM; + + csi2dc->dev = dev; + + csi2dc->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2dc->base)) { + dev_err(dev, "base address not set\n"); + return PTR_ERR(csi2dc->base); + } + + csi2dc->pclk = devm_clk_get(dev, "pclk"); + if (IS_ERR(csi2dc->pclk)) { + ret = PTR_ERR(csi2dc->pclk); + dev_err(dev, "failed to get pclk: %d\n", ret); + return ret; + } + + csi2dc->scck = devm_clk_get(dev, "scck"); + if (IS_ERR(csi2dc->scck)) { + ret = PTR_ERR(csi2dc->scck); + dev_err(dev, "failed to get scck: %d\n", ret); + return ret; + } + + v4l2_subdev_init(&csi2dc->csi2dc_sd, &csi2dc_subdev_ops); + + csi2dc->csi2dc_sd.owner = THIS_MODULE; + csi2dc->csi2dc_sd.dev = dev; + snprintf(csi2dc->csi2dc_sd.name, sizeof(csi2dc->csi2dc_sd.name), + "csi2dc"); + + csi2dc->csi2dc_sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + csi2dc->csi2dc_sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2dc->csi2dc_sd.entity.ops = &csi2dc_entity_ops; + + platform_set_drvdata(pdev, csi2dc); + + ret = csi2dc_of_parse(csi2dc, dev->of_node); + if (ret) + goto csi2dc_probe_cleanup_entity; + + csi2dc->pads[CSI2DC_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + if (csi2dc->video_pipe) + csi2dc->pads[CSI2DC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&csi2dc->csi2dc_sd.entity, + csi2dc->video_pipe ? CSI2DC_PADS_NUM : 1, + csi2dc->pads); + if (ret < 0) { + dev_err(dev, "media entity init failed\n"); + goto csi2dc_probe_cleanup_notifier; + } + + csi2dc_default_format(csi2dc); + + /* turn power on to validate capabilities */ + ret = csi2dc_power(csi2dc, true); + if (ret < 0) + goto csi2dc_probe_cleanup_notifier; + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + ver = csi2dc_readl(csi2dc, CSI2DC_VERSION); + + /* + * we must register the subdev after PM runtime has been requested, + * otherwise we might bound immediately and request pm_runtime_resume + * before runtime_enable. + */ + ret = v4l2_async_register_subdev(&csi2dc->csi2dc_sd); + if (ret) { + dev_err(csi2dc->dev, "failed to register the subdevice\n"); + goto csi2dc_probe_cleanup_notifier; + } + + dev_info(dev, "Microchip CSI2DC version %x\n", ver); + + return 0; + +csi2dc_probe_cleanup_notifier: + v4l2_async_nf_cleanup(&csi2dc->notifier); +csi2dc_probe_cleanup_entity: + media_entity_cleanup(&csi2dc->csi2dc_sd.entity); + + return ret; +} + +static int csi2dc_remove(struct platform_device *pdev) +{ + struct csi2dc_device *csi2dc = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + v4l2_async_unregister_subdev(&csi2dc->csi2dc_sd); + v4l2_async_nf_unregister(&csi2dc->notifier); + v4l2_async_nf_cleanup(&csi2dc->notifier); + media_entity_cleanup(&csi2dc->csi2dc_sd.entity); + + return 0; +} + +static int __maybe_unused csi2dc_runtime_suspend(struct device *dev) +{ + struct csi2dc_device *csi2dc = dev_get_drvdata(dev); + + return csi2dc_power(csi2dc, false); +} + +static int __maybe_unused csi2dc_runtime_resume(struct device *dev) +{ + struct csi2dc_device *csi2dc = dev_get_drvdata(dev); + + return csi2dc_power(csi2dc, true); +} + +static const struct dev_pm_ops csi2dc_dev_pm_ops = { + SET_RUNTIME_PM_OPS(csi2dc_runtime_suspend, csi2dc_runtime_resume, NULL) +}; + +static const struct of_device_id csi2dc_of_match[] = { + { .compatible = "microchip,sama7g5-csi2dc" }, + { } +}; + +MODULE_DEVICE_TABLE(of, csi2dc_of_match); + +static struct platform_driver csi2dc_driver = { + .probe = csi2dc_probe, + .remove = csi2dc_remove, + .driver = { + .name = "microchip-csi2dc", + .pm = &csi2dc_dev_pm_ops, + .of_match_table = of_match_ptr(csi2dc_of_match), + }, +}; + +module_platform_driver(csi2dc_driver); + +MODULE_AUTHOR("Eugen Hristev "); +MODULE_DESCRIPTION("Microchip CSI2 Demux Controller driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/cadence/Kconfig b/drivers/media/platform/cadence/Kconfig new file mode 100644 index 000000000..480325d05 --- /dev/null +++ b/drivers/media/platform/cadence/Kconfig @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Cadence media platform drivers" + +config VIDEO_CADENCE_CSI2RX + tristate "Cadence MIPI-CSI2 RX Controller" + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + Support for the Cadence MIPI CSI2 Receiver controller. + + To compile this driver as a module, choose M here: the module will be + called cdns-csi2rx. + +config VIDEO_CADENCE_CSI2TX + tristate "Cadence MIPI-CSI2 TX Controller" + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + Support for the Cadence MIPI CSI2 Transceiver controller. + + To compile this driver as a module, choose M here: the module will be + called cdns-csi2tx. diff --git a/drivers/media/platform/cadence/Makefile b/drivers/media/platform/cadence/Makefile new file mode 100644 index 000000000..be59a8728 --- /dev/null +++ b/drivers/media/platform/cadence/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_VIDEO_CADENCE_CSI2RX) += cdns-csi2rx.o +obj-$(CONFIG_VIDEO_CADENCE_CSI2TX) += cdns-csi2tx.o diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c new file mode 100644 index 000000000..2a23da6a0 --- /dev/null +++ b/drivers/media/platform/cadence/cdns-csi2rx.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Cadence MIPI-CSI2 RX Controller v1.3 + * + * Copyright (C) 2017 Cadence Design Systems Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define CSI2RX_DEVICE_CFG_REG 0x000 + +#define CSI2RX_SOFT_RESET_REG 0x004 +#define CSI2RX_SOFT_RESET_PROTOCOL BIT(1) +#define CSI2RX_SOFT_RESET_FRONT BIT(0) + +#define CSI2RX_STATIC_CFG_REG 0x008 +#define CSI2RX_STATIC_CFG_DLANE_MAP(llane, plane) ((plane) << (16 + (llane) * 4)) +#define CSI2RX_STATIC_CFG_LANES_MASK GENMASK(11, 8) + +#define CSI2RX_STREAM_BASE(n) (((n) + 1) * 0x100) + +#define CSI2RX_STREAM_CTRL_REG(n) (CSI2RX_STREAM_BASE(n) + 0x000) +#define CSI2RX_STREAM_CTRL_START BIT(0) + +#define CSI2RX_STREAM_DATA_CFG_REG(n) (CSI2RX_STREAM_BASE(n) + 0x008) +#define CSI2RX_STREAM_DATA_CFG_EN_VC_SELECT BIT(31) +#define CSI2RX_STREAM_DATA_CFG_VC_SELECT(n) BIT((n) + 16) + +#define CSI2RX_STREAM_CFG_REG(n) (CSI2RX_STREAM_BASE(n) + 0x00c) +#define CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF (1 << 8) + +#define CSI2RX_LANES_MAX 4 +#define CSI2RX_STREAMS_MAX 4 + +enum csi2rx_pads { + CSI2RX_PAD_SINK, + CSI2RX_PAD_SOURCE_STREAM0, + CSI2RX_PAD_SOURCE_STREAM1, + CSI2RX_PAD_SOURCE_STREAM2, + CSI2RX_PAD_SOURCE_STREAM3, + CSI2RX_PAD_MAX, +}; + +struct csi2rx_priv { + struct device *dev; + unsigned int count; + + /* + * Used to prevent race conditions between multiple, + * concurrent calls to start and stop. + */ + struct mutex lock; + + void __iomem *base; + struct clk *sys_clk; + struct clk *p_clk; + struct clk *pixel_clk[CSI2RX_STREAMS_MAX]; + struct phy *dphy; + + u8 lanes[CSI2RX_LANES_MAX]; + u8 num_lanes; + u8 max_lanes; + u8 max_streams; + bool has_internal_dphy; + + struct v4l2_subdev subdev; + struct v4l2_async_notifier notifier; + struct media_pad pads[CSI2RX_PAD_MAX]; + + /* Remote source */ + struct v4l2_subdev *source_subdev; + int source_pad; +}; + +static inline +struct csi2rx_priv *v4l2_subdev_to_csi2rx(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct csi2rx_priv, subdev); +} + +static void csi2rx_reset(struct csi2rx_priv *csi2rx) +{ + writel(CSI2RX_SOFT_RESET_PROTOCOL | CSI2RX_SOFT_RESET_FRONT, + csi2rx->base + CSI2RX_SOFT_RESET_REG); + + udelay(10); + + writel(0, csi2rx->base + CSI2RX_SOFT_RESET_REG); +} + +static int csi2rx_start(struct csi2rx_priv *csi2rx) +{ + unsigned int i; + unsigned long lanes_used = 0; + u32 reg; + int ret; + + ret = clk_prepare_enable(csi2rx->p_clk); + if (ret) + return ret; + + csi2rx_reset(csi2rx); + + reg = csi2rx->num_lanes << 8; + for (i = 0; i < csi2rx->num_lanes; i++) { + reg |= CSI2RX_STATIC_CFG_DLANE_MAP(i, csi2rx->lanes[i]); + set_bit(csi2rx->lanes[i], &lanes_used); + } + + /* + * Even the unused lanes need to be mapped. In order to avoid + * to map twice to the same physical lane, keep the lanes used + * in the previous loop, and only map unused physical lanes to + * the rest of our logical lanes. + */ + for (i = csi2rx->num_lanes; i < csi2rx->max_lanes; i++) { + unsigned int idx = find_first_zero_bit(&lanes_used, + csi2rx->max_lanes); + set_bit(idx, &lanes_used); + reg |= CSI2RX_STATIC_CFG_DLANE_MAP(i, i + 1); + } + + writel(reg, csi2rx->base + CSI2RX_STATIC_CFG_REG); + + ret = v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, true); + if (ret) + goto err_disable_pclk; + + /* + * Create a static mapping between the CSI virtual channels + * and the output stream. + * + * This should be enhanced, but v4l2 lacks the support for + * changing that mapping dynamically. + * + * We also cannot enable and disable independent streams here, + * hence the reference counting. + */ + for (i = 0; i < csi2rx->max_streams; i++) { + ret = clk_prepare_enable(csi2rx->pixel_clk[i]); + if (ret) + goto err_disable_pixclk; + + writel(CSI2RX_STREAM_CFG_FIFO_MODE_LARGE_BUF, + csi2rx->base + CSI2RX_STREAM_CFG_REG(i)); + + writel(CSI2RX_STREAM_DATA_CFG_EN_VC_SELECT | + CSI2RX_STREAM_DATA_CFG_VC_SELECT(i), + csi2rx->base + CSI2RX_STREAM_DATA_CFG_REG(i)); + + writel(CSI2RX_STREAM_CTRL_START, + csi2rx->base + CSI2RX_STREAM_CTRL_REG(i)); + } + + ret = clk_prepare_enable(csi2rx->sys_clk); + if (ret) + goto err_disable_pixclk; + + clk_disable_unprepare(csi2rx->p_clk); + + return 0; + +err_disable_pixclk: + for (; i > 0; i--) + clk_disable_unprepare(csi2rx->pixel_clk[i - 1]); + +err_disable_pclk: + clk_disable_unprepare(csi2rx->p_clk); + + return ret; +} + +static void csi2rx_stop(struct csi2rx_priv *csi2rx) +{ + unsigned int i; + + clk_prepare_enable(csi2rx->p_clk); + clk_disable_unprepare(csi2rx->sys_clk); + + for (i = 0; i < csi2rx->max_streams; i++) { + writel(0, csi2rx->base + CSI2RX_STREAM_CTRL_REG(i)); + + clk_disable_unprepare(csi2rx->pixel_clk[i]); + } + + clk_disable_unprepare(csi2rx->p_clk); + + if (v4l2_subdev_call(csi2rx->source_subdev, video, s_stream, false)) + dev_warn(csi2rx->dev, "Couldn't disable our subdev\n"); +} + +static int csi2rx_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev); + int ret = 0; + + mutex_lock(&csi2rx->lock); + + if (enable) { + /* + * If we're not the first users, there's no need to + * enable the whole controller. + */ + if (!csi2rx->count) { + ret = csi2rx_start(csi2rx); + if (ret) + goto out; + } + + csi2rx->count++; + } else { + csi2rx->count--; + + /* + * Let the last user turn off the lights. + */ + if (!csi2rx->count) + csi2rx_stop(csi2rx); + } + +out: + mutex_unlock(&csi2rx->lock); + return ret; +} + +static const struct v4l2_subdev_video_ops csi2rx_video_ops = { + .s_stream = csi2rx_s_stream, +}; + +static const struct v4l2_subdev_ops csi2rx_subdev_ops = { + .video = &csi2rx_video_ops, +}; + +static int csi2rx_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *s_subdev, + struct v4l2_async_subdev *asd) +{ + struct v4l2_subdev *subdev = notifier->sd; + struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev); + + csi2rx->source_pad = media_entity_get_fwnode_pad(&s_subdev->entity, + s_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (csi2rx->source_pad < 0) { + dev_err(csi2rx->dev, "Couldn't find output pad for subdev %s\n", + s_subdev->name); + return csi2rx->source_pad; + } + + csi2rx->source_subdev = s_subdev; + + dev_dbg(csi2rx->dev, "Bound %s pad: %d\n", s_subdev->name, + csi2rx->source_pad); + + return media_create_pad_link(&csi2rx->source_subdev->entity, + csi2rx->source_pad, + &csi2rx->subdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static const struct v4l2_async_notifier_operations csi2rx_notifier_ops = { + .bound = csi2rx_async_bound, +}; + +static int csi2rx_get_resources(struct csi2rx_priv *csi2rx, + struct platform_device *pdev) +{ + unsigned char i; + u32 dev_cfg; + int ret; + + csi2rx->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2rx->base)) + return PTR_ERR(csi2rx->base); + + csi2rx->sys_clk = devm_clk_get(&pdev->dev, "sys_clk"); + if (IS_ERR(csi2rx->sys_clk)) { + dev_err(&pdev->dev, "Couldn't get sys clock\n"); + return PTR_ERR(csi2rx->sys_clk); + } + + csi2rx->p_clk = devm_clk_get(&pdev->dev, "p_clk"); + if (IS_ERR(csi2rx->p_clk)) { + dev_err(&pdev->dev, "Couldn't get P clock\n"); + return PTR_ERR(csi2rx->p_clk); + } + + csi2rx->dphy = devm_phy_optional_get(&pdev->dev, "dphy"); + if (IS_ERR(csi2rx->dphy)) { + dev_err(&pdev->dev, "Couldn't get external D-PHY\n"); + return PTR_ERR(csi2rx->dphy); + } + + /* + * FIXME: Once we'll have external D-PHY support, the check + * will need to be removed. + */ + if (csi2rx->dphy) { + dev_err(&pdev->dev, "External D-PHY not supported yet\n"); + return -EINVAL; + } + + ret = clk_prepare_enable(csi2rx->p_clk); + if (ret) { + dev_err(&pdev->dev, "Couldn't prepare and enable P clock\n"); + return ret; + } + + dev_cfg = readl(csi2rx->base + CSI2RX_DEVICE_CFG_REG); + clk_disable_unprepare(csi2rx->p_clk); + + csi2rx->max_lanes = dev_cfg & 7; + if (csi2rx->max_lanes > CSI2RX_LANES_MAX) { + dev_err(&pdev->dev, "Invalid number of lanes: %u\n", + csi2rx->max_lanes); + return -EINVAL; + } + + csi2rx->max_streams = (dev_cfg >> 4) & 7; + if (csi2rx->max_streams > CSI2RX_STREAMS_MAX) { + dev_err(&pdev->dev, "Invalid number of streams: %u\n", + csi2rx->max_streams); + return -EINVAL; + } + + csi2rx->has_internal_dphy = dev_cfg & BIT(3) ? true : false; + + /* + * FIXME: Once we'll have internal D-PHY support, the check + * will need to be removed. + */ + if (csi2rx->has_internal_dphy) { + dev_err(&pdev->dev, "Internal D-PHY not supported yet\n"); + return -EINVAL; + } + + for (i = 0; i < csi2rx->max_streams; i++) { + char clk_name[16]; + + snprintf(clk_name, sizeof(clk_name), "pixel_if%u_clk", i); + csi2rx->pixel_clk[i] = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(csi2rx->pixel_clk[i])) { + dev_err(&pdev->dev, "Couldn't get clock %s\n", clk_name); + return PTR_ERR(csi2rx->pixel_clk[i]); + } + } + + return 0; +} + +static int csi2rx_parse_dt(struct csi2rx_priv *csi2rx) +{ + struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwh; + struct device_node *ep; + int ret; + + ep = of_graph_get_endpoint_by_regs(csi2rx->dev->of_node, 0, 0); + if (!ep) + return -EINVAL; + + fwh = of_fwnode_handle(ep); + ret = v4l2_fwnode_endpoint_parse(fwh, &v4l2_ep); + if (ret) { + dev_err(csi2rx->dev, "Could not parse v4l2 endpoint\n"); + of_node_put(ep); + return ret; + } + + if (v4l2_ep.bus_type != V4L2_MBUS_CSI2_DPHY) { + dev_err(csi2rx->dev, "Unsupported media bus type: 0x%x\n", + v4l2_ep.bus_type); + of_node_put(ep); + return -EINVAL; + } + + memcpy(csi2rx->lanes, v4l2_ep.bus.mipi_csi2.data_lanes, + sizeof(csi2rx->lanes)); + csi2rx->num_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; + if (csi2rx->num_lanes > csi2rx->max_lanes) { + dev_err(csi2rx->dev, "Unsupported number of data-lanes: %d\n", + csi2rx->num_lanes); + of_node_put(ep); + return -EINVAL; + } + + v4l2_async_nf_init(&csi2rx->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&csi2rx->notifier, fwh, + struct v4l2_async_subdev); + of_node_put(ep); + if (IS_ERR(asd)) { + v4l2_async_nf_cleanup(&csi2rx->notifier); + return PTR_ERR(asd); + } + + csi2rx->notifier.ops = &csi2rx_notifier_ops; + + ret = v4l2_async_subdev_nf_register(&csi2rx->subdev, &csi2rx->notifier); + if (ret) + v4l2_async_nf_cleanup(&csi2rx->notifier); + + return ret; +} + +static int csi2rx_probe(struct platform_device *pdev) +{ + struct csi2rx_priv *csi2rx; + unsigned int i; + int ret; + + csi2rx = kzalloc(sizeof(*csi2rx), GFP_KERNEL); + if (!csi2rx) + return -ENOMEM; + platform_set_drvdata(pdev, csi2rx); + csi2rx->dev = &pdev->dev; + mutex_init(&csi2rx->lock); + + ret = csi2rx_get_resources(csi2rx, pdev); + if (ret) + goto err_free_priv; + + ret = csi2rx_parse_dt(csi2rx); + if (ret) + goto err_free_priv; + + csi2rx->subdev.owner = THIS_MODULE; + csi2rx->subdev.dev = &pdev->dev; + v4l2_subdev_init(&csi2rx->subdev, &csi2rx_subdev_ops); + v4l2_set_subdevdata(&csi2rx->subdev, &pdev->dev); + snprintf(csi2rx->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.%s", + KBUILD_MODNAME, dev_name(&pdev->dev)); + + /* Create our media pads */ + csi2rx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2rx->pads[CSI2RX_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + for (i = CSI2RX_PAD_SOURCE_STREAM0; i < CSI2RX_PAD_MAX; i++) + csi2rx->pads[i].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&csi2rx->subdev.entity, CSI2RX_PAD_MAX, + csi2rx->pads); + if (ret) + goto err_cleanup; + + ret = v4l2_async_register_subdev(&csi2rx->subdev); + if (ret < 0) + goto err_cleanup; + + dev_info(&pdev->dev, + "Probed CSI2RX with %u/%u lanes, %u streams, %s D-PHY\n", + csi2rx->num_lanes, csi2rx->max_lanes, csi2rx->max_streams, + csi2rx->has_internal_dphy ? "internal" : "no"); + + return 0; + +err_cleanup: + v4l2_async_nf_unregister(&csi2rx->notifier); + v4l2_async_nf_cleanup(&csi2rx->notifier); +err_free_priv: + kfree(csi2rx); + return ret; +} + +static int csi2rx_remove(struct platform_device *pdev) +{ + struct csi2rx_priv *csi2rx = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&csi2rx->notifier); + v4l2_async_nf_cleanup(&csi2rx->notifier); + v4l2_async_unregister_subdev(&csi2rx->subdev); + kfree(csi2rx); + + return 0; +} + +static const struct of_device_id csi2rx_of_table[] = { + { .compatible = "cdns,csi2rx" }, + { }, +}; +MODULE_DEVICE_TABLE(of, csi2rx_of_table); + +static struct platform_driver csi2rx_driver = { + .probe = csi2rx_probe, + .remove = csi2rx_remove, + + .driver = { + .name = "cdns-csi2rx", + .of_match_table = csi2rx_of_table, + }, +}; +module_platform_driver(csi2rx_driver); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_DESCRIPTION("Cadence CSI2-RX controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/cadence/cdns-csi2tx.c b/drivers/media/platform/cadence/cdns-csi2tx.c new file mode 100644 index 000000000..58e405b69 --- /dev/null +++ b/drivers/media/platform/cadence/cdns-csi2tx.c @@ -0,0 +1,660 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Cadence MIPI-CSI2 TX Controller + * + * Copyright (C) 2017-2019 Cadence Design Systems Inc. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define CSI2TX_DEVICE_CONFIG_REG 0x00 +#define CSI2TX_DEVICE_CONFIG_STREAMS_MASK GENMASK(6, 4) +#define CSI2TX_DEVICE_CONFIG_HAS_DPHY BIT(3) +#define CSI2TX_DEVICE_CONFIG_LANES_MASK GENMASK(2, 0) + +#define CSI2TX_CONFIG_REG 0x20 +#define CSI2TX_CONFIG_CFG_REQ BIT(2) +#define CSI2TX_CONFIG_SRST_REQ BIT(1) + +#define CSI2TX_DPHY_CFG_REG 0x28 +#define CSI2TX_DPHY_CFG_CLK_RESET BIT(16) +#define CSI2TX_DPHY_CFG_LANE_RESET(n) BIT((n) + 12) +#define CSI2TX_DPHY_CFG_MODE_MASK GENMASK(9, 8) +#define CSI2TX_DPHY_CFG_MODE_LPDT (2 << 8) +#define CSI2TX_DPHY_CFG_MODE_HS (1 << 8) +#define CSI2TX_DPHY_CFG_MODE_ULPS (0 << 8) +#define CSI2TX_DPHY_CFG_CLK_ENABLE BIT(4) +#define CSI2TX_DPHY_CFG_LANE_ENABLE(n) BIT(n) + +#define CSI2TX_DPHY_CLK_WAKEUP_REG 0x2c +#define CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(n) ((n) & 0xffff) + +#define CSI2TX_DT_CFG_REG(n) (0x80 + (n) * 8) +#define CSI2TX_DT_CFG_DT(n) (((n) & 0x3f) << 2) + +#define CSI2TX_DT_FORMAT_REG(n) (0x84 + (n) * 8) +#define CSI2TX_DT_FORMAT_BYTES_PER_LINE(n) (((n) & 0xffff) << 16) +#define CSI2TX_DT_FORMAT_MAX_LINE_NUM(n) ((n) & 0xffff) + +#define CSI2TX_STREAM_IF_CFG_REG(n) (0x100 + (n) * 4) +#define CSI2TX_STREAM_IF_CFG_FILL_LEVEL(n) ((n) & 0x1f) + +/* CSI2TX V2 Registers */ +#define CSI2TX_V2_DPHY_CFG_REG 0x28 +#define CSI2TX_V2_DPHY_CFG_RESET BIT(16) +#define CSI2TX_V2_DPHY_CFG_CLOCK_MODE BIT(10) +#define CSI2TX_V2_DPHY_CFG_MODE_MASK GENMASK(9, 8) +#define CSI2TX_V2_DPHY_CFG_MODE_LPDT (2 << 8) +#define CSI2TX_V2_DPHY_CFG_MODE_HS (1 << 8) +#define CSI2TX_V2_DPHY_CFG_MODE_ULPS (0 << 8) +#define CSI2TX_V2_DPHY_CFG_CLK_ENABLE BIT(4) +#define CSI2TX_V2_DPHY_CFG_LANE_ENABLE(n) BIT(n) + +#define CSI2TX_LANES_MAX 4 +#define CSI2TX_STREAMS_MAX 4 + +enum csi2tx_pads { + CSI2TX_PAD_SOURCE, + CSI2TX_PAD_SINK_STREAM0, + CSI2TX_PAD_SINK_STREAM1, + CSI2TX_PAD_SINK_STREAM2, + CSI2TX_PAD_SINK_STREAM3, + CSI2TX_PAD_MAX, +}; + +struct csi2tx_fmt { + u32 mbus; + u32 dt; + u32 bpp; +}; + +struct csi2tx_priv; + +/* CSI2TX Variant Operations */ +struct csi2tx_vops { + void (*dphy_setup)(struct csi2tx_priv *csi2tx); +}; + +struct csi2tx_priv { + struct device *dev; + unsigned int count; + + /* + * Used to prevent race conditions between multiple, + * concurrent calls to start and stop. + */ + struct mutex lock; + + void __iomem *base; + + struct csi2tx_vops *vops; + + struct clk *esc_clk; + struct clk *p_clk; + struct clk *pixel_clk[CSI2TX_STREAMS_MAX]; + + struct v4l2_subdev subdev; + struct media_pad pads[CSI2TX_PAD_MAX]; + struct v4l2_mbus_framefmt pad_fmts[CSI2TX_PAD_MAX]; + + bool has_internal_dphy; + u8 lanes[CSI2TX_LANES_MAX]; + unsigned int num_lanes; + unsigned int max_lanes; + unsigned int max_streams; +}; + +static const struct csi2tx_fmt csi2tx_formats[] = { + { + .mbus = MEDIA_BUS_FMT_UYVY8_1X16, + .bpp = 2, + .dt = MIPI_CSI2_DT_YUV422_8B, + }, + { + .mbus = MEDIA_BUS_FMT_RGB888_1X24, + .bpp = 3, + .dt = MIPI_CSI2_DT_RGB888, + }, +}; + +static const struct v4l2_mbus_framefmt fmt_default = { + .width = 1280, + .height = 720, + .code = MEDIA_BUS_FMT_RGB888_1X24, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_DEFAULT, +}; + +static inline +struct csi2tx_priv *v4l2_subdev_to_csi2tx(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct csi2tx_priv, subdev); +} + +static const struct csi2tx_fmt *csi2tx_get_fmt_from_mbus(u32 mbus) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(csi2tx_formats); i++) + if (csi2tx_formats[i].mbus == mbus) + return &csi2tx_formats[i]; + + return NULL; +} + +static int csi2tx_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->pad || code->index >= ARRAY_SIZE(csi2tx_formats)) + return -EINVAL; + + code->code = csi2tx_formats[code->index].mbus; + + return 0; +} + +static struct v4l2_mbus_framefmt * +__csi2tx_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(subdev, sd_state, + fmt->pad); + + return &csi2tx->pad_fmts[fmt->pad]; +} + +static int csi2tx_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + const struct v4l2_mbus_framefmt *format; + + /* Multiplexed pad? */ + if (fmt->pad == CSI2TX_PAD_SOURCE) + return -EINVAL; + + format = __csi2tx_get_pad_format(subdev, sd_state, fmt); + if (!format) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +static int csi2tx_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + const struct v4l2_mbus_framefmt *src_format = &fmt->format; + struct v4l2_mbus_framefmt *dst_format; + + /* Multiplexed pad? */ + if (fmt->pad == CSI2TX_PAD_SOURCE) + return -EINVAL; + + if (!csi2tx_get_fmt_from_mbus(fmt->format.code)) + src_format = &fmt_default; + + dst_format = __csi2tx_get_pad_format(subdev, sd_state, fmt); + if (!dst_format) + return -EINVAL; + + *dst_format = *src_format; + + return 0; +} + +static const struct v4l2_subdev_pad_ops csi2tx_pad_ops = { + .enum_mbus_code = csi2tx_enum_mbus_code, + .get_fmt = csi2tx_get_pad_format, + .set_fmt = csi2tx_set_pad_format, +}; + +/* Set Wake Up value in the D-PHY */ +static void csi2tx_dphy_set_wakeup(struct csi2tx_priv *csi2tx) +{ + writel(CSI2TX_DPHY_CLK_WAKEUP_ULPS_CYCLES(32), + csi2tx->base + CSI2TX_DPHY_CLK_WAKEUP_REG); +} + +/* + * Finishes the D-PHY initialization + * reg dphy cfg value to be used + */ +static void csi2tx_dphy_init_finish(struct csi2tx_priv *csi2tx, u32 reg) +{ + unsigned int i; + + udelay(10); + + /* Enable our (clock and data) lanes */ + reg |= CSI2TX_DPHY_CFG_CLK_ENABLE; + for (i = 0; i < csi2tx->num_lanes; i++) + reg |= CSI2TX_DPHY_CFG_LANE_ENABLE(csi2tx->lanes[i] - 1); + writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); + + udelay(10); + + /* Switch to HS mode */ + reg &= ~CSI2TX_DPHY_CFG_MODE_MASK; + writel(reg | CSI2TX_DPHY_CFG_MODE_HS, + csi2tx->base + CSI2TX_DPHY_CFG_REG); +} + +/* Configures D-PHY in CSIv1.3 */ +static void csi2tx_dphy_setup(struct csi2tx_priv *csi2tx) +{ + u32 reg; + unsigned int i; + + csi2tx_dphy_set_wakeup(csi2tx); + + /* Put our lanes (clock and data) out of reset */ + reg = CSI2TX_DPHY_CFG_CLK_RESET | CSI2TX_DPHY_CFG_MODE_LPDT; + for (i = 0; i < csi2tx->num_lanes; i++) + reg |= CSI2TX_DPHY_CFG_LANE_RESET(csi2tx->lanes[i] - 1); + writel(reg, csi2tx->base + CSI2TX_DPHY_CFG_REG); + + csi2tx_dphy_init_finish(csi2tx, reg); +} + +/* Configures D-PHY in CSIv2 */ +static void csi2tx_v2_dphy_setup(struct csi2tx_priv *csi2tx) +{ + u32 reg; + + csi2tx_dphy_set_wakeup(csi2tx); + + /* Put our lanes (clock and data) out of reset */ + reg = CSI2TX_V2_DPHY_CFG_RESET | CSI2TX_V2_DPHY_CFG_MODE_LPDT; + writel(reg, csi2tx->base + CSI2TX_V2_DPHY_CFG_REG); + + csi2tx_dphy_init_finish(csi2tx, reg); +} + +static void csi2tx_reset(struct csi2tx_priv *csi2tx) +{ + writel(CSI2TX_CONFIG_SRST_REQ, csi2tx->base + CSI2TX_CONFIG_REG); + + udelay(10); +} + +static int csi2tx_start(struct csi2tx_priv *csi2tx) +{ + struct media_entity *entity = &csi2tx->subdev.entity; + struct media_link *link; + unsigned int i; + + csi2tx_reset(csi2tx); + + writel(CSI2TX_CONFIG_CFG_REQ, csi2tx->base + CSI2TX_CONFIG_REG); + + udelay(10); + + if (csi2tx->vops && csi2tx->vops->dphy_setup) { + csi2tx->vops->dphy_setup(csi2tx); + udelay(10); + } + + /* + * Create a static mapping between the CSI virtual channels + * and the input streams. + * + * This should be enhanced, but v4l2 lacks the support for + * changing that mapping dynamically at the moment. + * + * We're protected from the userspace setting up links at the + * same time by the upper layer having called + * media_pipeline_start(). + */ + list_for_each_entry(link, &entity->links, list) { + struct v4l2_mbus_framefmt *mfmt; + const struct csi2tx_fmt *fmt; + unsigned int stream; + int pad_idx = -1; + + /* Only consider our enabled input pads */ + for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) { + struct media_pad *pad = &csi2tx->pads[i]; + + if ((pad == link->sink) && + (link->flags & MEDIA_LNK_FL_ENABLED)) { + pad_idx = i; + break; + } + } + + if (pad_idx < 0) + continue; + + mfmt = &csi2tx->pad_fmts[pad_idx]; + fmt = csi2tx_get_fmt_from_mbus(mfmt->code); + if (!fmt) + continue; + + stream = pad_idx - CSI2TX_PAD_SINK_STREAM0; + + /* + * We use the stream ID there, but it's wrong. + * + * A stream could very well send a data type that is + * not equal to its stream ID. We need to find a + * proper way to address it. + */ + writel(CSI2TX_DT_CFG_DT(fmt->dt), + csi2tx->base + CSI2TX_DT_CFG_REG(stream)); + + writel(CSI2TX_DT_FORMAT_BYTES_PER_LINE(mfmt->width * fmt->bpp) | + CSI2TX_DT_FORMAT_MAX_LINE_NUM(mfmt->height + 1), + csi2tx->base + CSI2TX_DT_FORMAT_REG(stream)); + + /* + * TODO: This needs to be calculated based on the + * output CSI2 clock rate. + */ + writel(CSI2TX_STREAM_IF_CFG_FILL_LEVEL(4), + csi2tx->base + CSI2TX_STREAM_IF_CFG_REG(stream)); + } + + /* Disable the configuration mode */ + writel(0, csi2tx->base + CSI2TX_CONFIG_REG); + + return 0; +} + +static void csi2tx_stop(struct csi2tx_priv *csi2tx) +{ + writel(CSI2TX_CONFIG_CFG_REQ | CSI2TX_CONFIG_SRST_REQ, + csi2tx->base + CSI2TX_CONFIG_REG); +} + +static int csi2tx_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct csi2tx_priv *csi2tx = v4l2_subdev_to_csi2tx(subdev); + int ret = 0; + + mutex_lock(&csi2tx->lock); + + if (enable) { + /* + * If we're not the first users, there's no need to + * enable the whole controller. + */ + if (!csi2tx->count) { + ret = csi2tx_start(csi2tx); + if (ret) + goto out; + } + + csi2tx->count++; + } else { + csi2tx->count--; + + /* + * Let the last user turn off the lights. + */ + if (!csi2tx->count) + csi2tx_stop(csi2tx); + } + +out: + mutex_unlock(&csi2tx->lock); + return ret; +} + +static const struct v4l2_subdev_video_ops csi2tx_video_ops = { + .s_stream = csi2tx_s_stream, +}; + +static const struct v4l2_subdev_ops csi2tx_subdev_ops = { + .pad = &csi2tx_pad_ops, + .video = &csi2tx_video_ops, +}; + +static int csi2tx_get_resources(struct csi2tx_priv *csi2tx, + struct platform_device *pdev) +{ + unsigned int i; + u32 dev_cfg; + int ret; + + csi2tx->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi2tx->base)) + return PTR_ERR(csi2tx->base); + + csi2tx->p_clk = devm_clk_get(&pdev->dev, "p_clk"); + if (IS_ERR(csi2tx->p_clk)) { + dev_err(&pdev->dev, "Couldn't get p_clk\n"); + return PTR_ERR(csi2tx->p_clk); + } + + csi2tx->esc_clk = devm_clk_get(&pdev->dev, "esc_clk"); + if (IS_ERR(csi2tx->esc_clk)) { + dev_err(&pdev->dev, "Couldn't get the esc_clk\n"); + return PTR_ERR(csi2tx->esc_clk); + } + + ret = clk_prepare_enable(csi2tx->p_clk); + if (ret) { + dev_err(&pdev->dev, "Couldn't prepare and enable p_clk\n"); + return ret; + } + + dev_cfg = readl(csi2tx->base + CSI2TX_DEVICE_CONFIG_REG); + clk_disable_unprepare(csi2tx->p_clk); + + csi2tx->max_lanes = dev_cfg & CSI2TX_DEVICE_CONFIG_LANES_MASK; + if (csi2tx->max_lanes > CSI2TX_LANES_MAX) { + dev_err(&pdev->dev, "Invalid number of lanes: %u\n", + csi2tx->max_lanes); + return -EINVAL; + } + + csi2tx->max_streams = (dev_cfg & CSI2TX_DEVICE_CONFIG_STREAMS_MASK) >> 4; + if (csi2tx->max_streams > CSI2TX_STREAMS_MAX) { + dev_err(&pdev->dev, "Invalid number of streams: %u\n", + csi2tx->max_streams); + return -EINVAL; + } + + csi2tx->has_internal_dphy = !!(dev_cfg & CSI2TX_DEVICE_CONFIG_HAS_DPHY); + + for (i = 0; i < csi2tx->max_streams; i++) { + char clk_name[16]; + + snprintf(clk_name, sizeof(clk_name), "pixel_if%u_clk", i); + csi2tx->pixel_clk[i] = devm_clk_get(&pdev->dev, clk_name); + if (IS_ERR(csi2tx->pixel_clk[i])) { + dev_err(&pdev->dev, "Couldn't get clock %s\n", + clk_name); + return PTR_ERR(csi2tx->pixel_clk[i]); + } + } + + return 0; +} + +static int csi2tx_check_lanes(struct csi2tx_priv *csi2tx) +{ + struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = 0 }; + struct device_node *ep; + int ret, i; + + ep = of_graph_get_endpoint_by_regs(csi2tx->dev->of_node, 0, 0); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &v4l2_ep); + if (ret) { + dev_err(csi2tx->dev, "Could not parse v4l2 endpoint\n"); + goto out; + } + + if (v4l2_ep.bus_type != V4L2_MBUS_CSI2_DPHY) { + dev_err(csi2tx->dev, "Unsupported media bus type: 0x%x\n", + v4l2_ep.bus_type); + ret = -EINVAL; + goto out; + } + + csi2tx->num_lanes = v4l2_ep.bus.mipi_csi2.num_data_lanes; + if (csi2tx->num_lanes > csi2tx->max_lanes) { + dev_err(csi2tx->dev, + "Current configuration uses more lanes than supported\n"); + ret = -EINVAL; + goto out; + } + + for (i = 0; i < csi2tx->num_lanes; i++) { + if (v4l2_ep.bus.mipi_csi2.data_lanes[i] < 1) { + dev_err(csi2tx->dev, "Invalid lane[%d] number: %u\n", + i, v4l2_ep.bus.mipi_csi2.data_lanes[i]); + ret = -EINVAL; + goto out; + } + } + + memcpy(csi2tx->lanes, v4l2_ep.bus.mipi_csi2.data_lanes, + sizeof(csi2tx->lanes)); + +out: + of_node_put(ep); + return ret; +} + +static const struct csi2tx_vops csi2tx_vops = { + .dphy_setup = csi2tx_dphy_setup, +}; + +static const struct csi2tx_vops csi2tx_v2_vops = { + .dphy_setup = csi2tx_v2_dphy_setup, +}; + +static const struct of_device_id csi2tx_of_table[] = { + { + .compatible = "cdns,csi2tx", + .data = &csi2tx_vops + }, + { + .compatible = "cdns,csi2tx-1.3", + .data = &csi2tx_vops + }, + { + .compatible = "cdns,csi2tx-2.1", + .data = &csi2tx_v2_vops + }, + { } +}; +MODULE_DEVICE_TABLE(of, csi2tx_of_table); + +static int csi2tx_probe(struct platform_device *pdev) +{ + struct csi2tx_priv *csi2tx; + const struct of_device_id *of_id; + unsigned int i; + int ret; + + csi2tx = kzalloc(sizeof(*csi2tx), GFP_KERNEL); + if (!csi2tx) + return -ENOMEM; + platform_set_drvdata(pdev, csi2tx); + mutex_init(&csi2tx->lock); + csi2tx->dev = &pdev->dev; + + ret = csi2tx_get_resources(csi2tx, pdev); + if (ret) + goto err_free_priv; + + of_id = of_match_node(csi2tx_of_table, pdev->dev.of_node); + csi2tx->vops = (struct csi2tx_vops *)of_id->data; + + v4l2_subdev_init(&csi2tx->subdev, &csi2tx_subdev_ops); + csi2tx->subdev.owner = THIS_MODULE; + csi2tx->subdev.dev = &pdev->dev; + csi2tx->subdev.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(csi2tx->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s.%s", + KBUILD_MODNAME, dev_name(&pdev->dev)); + + ret = csi2tx_check_lanes(csi2tx); + if (ret) + goto err_free_priv; + + /* Create our media pads */ + csi2tx->subdev.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + csi2tx->pads[CSI2TX_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) + csi2tx->pads[i].flags = MEDIA_PAD_FL_SINK; + + /* + * Only the input pads are considered to have a format at the + * moment. The CSI link can multiplex various streams with + * different formats, and we can't expose this in v4l2 right + * now. + */ + for (i = CSI2TX_PAD_SINK_STREAM0; i < CSI2TX_PAD_MAX; i++) + csi2tx->pad_fmts[i] = fmt_default; + + ret = media_entity_pads_init(&csi2tx->subdev.entity, CSI2TX_PAD_MAX, + csi2tx->pads); + if (ret) + goto err_free_priv; + + ret = v4l2_async_register_subdev(&csi2tx->subdev); + if (ret < 0) + goto err_free_priv; + + dev_info(&pdev->dev, + "Probed CSI2TX with %u/%u lanes, %u streams, %s D-PHY\n", + csi2tx->num_lanes, csi2tx->max_lanes, csi2tx->max_streams, + csi2tx->has_internal_dphy ? "internal" : "no"); + + return 0; + +err_free_priv: + kfree(csi2tx); + return ret; +} + +static int csi2tx_remove(struct platform_device *pdev) +{ + struct csi2tx_priv *csi2tx = platform_get_drvdata(pdev); + + v4l2_async_unregister_subdev(&csi2tx->subdev); + kfree(csi2tx); + + return 0; +} + +static struct platform_driver csi2tx_driver = { + .probe = csi2tx_probe, + .remove = csi2tx_remove, + + .driver = { + .name = "cdns-csi2tx", + .of_match_table = csi2tx_of_table, + }, +}; +module_platform_driver(csi2tx_driver); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_DESCRIPTION("Cadence CSI2-TX controller"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/chips-media/Kconfig b/drivers/media/platform/chips-media/Kconfig new file mode 100644 index 000000000..57f8f8a22 --- /dev/null +++ b/drivers/media/platform/chips-media/Kconfig @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Chips&Media media platform drivers" + +config VIDEO_CODA + tristate "Chips&Media Coda multi-standard codec IP" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV && OF && (ARCH_MXC || COMPILE_TEST) + select SRAM + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC + select V4L2_JPEG_HELPER + select V4L2_MEM2MEM_DEV + select GENERIC_ALLOCATOR + help + Coda is a range of video codec IPs that supports + H.264, MPEG-4, and other video formats. + +config VIDEO_IMX_VDOA + def_tristate VIDEO_CODA if SOC_IMX6Q || COMPILE_TEST diff --git a/drivers/media/platform/chips-media/Makefile b/drivers/media/platform/chips-media/Makefile new file mode 100644 index 000000000..bbb16425a --- /dev/null +++ b/drivers/media/platform/chips-media/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only + +coda-vpu-objs := coda-common.o coda-bit.o coda-gdi.o coda-h264.o coda-mpeg2.o coda-mpeg4.o coda-jpeg.o + +obj-$(CONFIG_VIDEO_CODA) += coda-vpu.o +obj-$(CONFIG_VIDEO_IMX_VDOA) += imx-vdoa.o diff --git a/drivers/media/platform/chips-media/coda-bit.c b/drivers/media/platform/chips-media/coda-bit.c new file mode 100644 index 000000000..ed47d5bd8 --- /dev/null +++ b/drivers/media/platform/chips-media/coda-bit.c @@ -0,0 +1,2666 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Coda multi-standard codec IP - BIT processor functions + * + * Copyright (C) 2012 Vista Silicon S.L. + * Javier Martin, + * Xavier Duret + * Copyright (C) 2012-2014 Philipp Zabel, Pengutronix + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "coda.h" +#include "imx-vdoa.h" +#define CREATE_TRACE_POINTS +#include "trace.h" + +#define CODA_PARA_BUF_SIZE (10 * 1024) +#define CODA7_PS_BUF_SIZE 0x28000 +#define CODA9_PS_SAVE_SIZE (512 * 1024) + +#define CODA_DEFAULT_GAMMA 4096 +#define CODA9_DEFAULT_GAMMA 24576 /* 0.75 * 32768 */ + +static void coda_free_bitstream_buffer(struct coda_ctx *ctx); + +static inline int coda_is_initialized(struct coda_dev *dev) +{ + return coda_read(dev, CODA_REG_BIT_CUR_PC) != 0; +} + +static inline unsigned long coda_isbusy(struct coda_dev *dev) +{ + return coda_read(dev, CODA_REG_BIT_BUSY); +} + +static int coda_wait_timeout(struct coda_dev *dev) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + while (coda_isbusy(dev)) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + } + return 0; +} + +static void coda_command_async(struct coda_ctx *ctx, int cmd) +{ + struct coda_dev *dev = ctx->dev; + + if (dev->devtype->product == CODA_HX4 || + dev->devtype->product == CODA_7541 || + dev->devtype->product == CODA_960) { + /* Restore context related registers to CODA */ + coda_write(dev, ctx->bit_stream_param, + CODA_REG_BIT_BIT_STREAM_PARAM); + coda_write(dev, ctx->frm_dis_flg, + CODA_REG_BIT_FRM_DIS_FLG(ctx->reg_idx)); + coda_write(dev, ctx->frame_mem_ctrl, + CODA_REG_BIT_FRAME_MEM_CTRL); + coda_write(dev, ctx->workbuf.paddr, CODA_REG_BIT_WORK_BUF_ADDR); + } + + if (dev->devtype->product == CODA_960) { + coda_write(dev, 1, CODA9_GDI_WPROT_ERR_CLR); + coda_write(dev, 0, CODA9_GDI_WPROT_RGN_EN); + } + + coda_write(dev, CODA_REG_BIT_BUSY_FLAG, CODA_REG_BIT_BUSY); + + coda_write(dev, ctx->idx, CODA_REG_BIT_RUN_INDEX); + coda_write(dev, ctx->params.codec_mode, CODA_REG_BIT_RUN_COD_STD); + coda_write(dev, ctx->params.codec_mode_aux, CODA7_REG_BIT_RUN_AUX_STD); + + trace_coda_bit_run(ctx, cmd); + + coda_write(dev, cmd, CODA_REG_BIT_RUN_COMMAND); +} + +static int coda_command_sync(struct coda_ctx *ctx, int cmd) +{ + struct coda_dev *dev = ctx->dev; + int ret; + + lockdep_assert_held(&dev->coda_mutex); + + coda_command_async(ctx, cmd); + ret = coda_wait_timeout(dev); + trace_coda_bit_done(ctx); + + return ret; +} + +int coda_hw_reset(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + unsigned long timeout; + unsigned int idx; + int ret; + + lockdep_assert_held(&dev->coda_mutex); + + if (!dev->rstc) + return -ENOENT; + + idx = coda_read(dev, CODA_REG_BIT_RUN_INDEX); + + if (dev->devtype->product == CODA_960) { + timeout = jiffies + msecs_to_jiffies(100); + coda_write(dev, 0x11, CODA9_GDI_BUS_CTRL); + while (coda_read(dev, CODA9_GDI_BUS_STATUS) != 0x77) { + if (time_after(jiffies, timeout)) + return -ETIME; + cpu_relax(); + } + } + + ret = reset_control_reset(dev->rstc); + if (ret < 0) + return ret; + + if (dev->devtype->product == CODA_960) + coda_write(dev, 0x00, CODA9_GDI_BUS_CTRL); + coda_write(dev, CODA_REG_BIT_BUSY_FLAG, CODA_REG_BIT_BUSY); + coda_write(dev, CODA_REG_RUN_ENABLE, CODA_REG_BIT_CODE_RUN); + ret = coda_wait_timeout(dev); + coda_write(dev, idx, CODA_REG_BIT_RUN_INDEX); + + return ret; +} + +static void coda_kfifo_sync_from_device(struct coda_ctx *ctx) +{ + struct __kfifo *kfifo = &ctx->bitstream_fifo.kfifo; + struct coda_dev *dev = ctx->dev; + u32 rd_ptr; + + rd_ptr = coda_read(dev, CODA_REG_BIT_RD_PTR(ctx->reg_idx)); + kfifo->out = (kfifo->in & ~kfifo->mask) | + (rd_ptr - ctx->bitstream.paddr); + if (kfifo->out > kfifo->in) + kfifo->out -= kfifo->mask + 1; +} + +static void coda_kfifo_sync_to_device_full(struct coda_ctx *ctx) +{ + struct __kfifo *kfifo = &ctx->bitstream_fifo.kfifo; + struct coda_dev *dev = ctx->dev; + u32 rd_ptr, wr_ptr; + + rd_ptr = ctx->bitstream.paddr + (kfifo->out & kfifo->mask); + coda_write(dev, rd_ptr, CODA_REG_BIT_RD_PTR(ctx->reg_idx)); + wr_ptr = ctx->bitstream.paddr + (kfifo->in & kfifo->mask); + coda_write(dev, wr_ptr, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); +} + +static void coda_kfifo_sync_to_device_write(struct coda_ctx *ctx) +{ + struct __kfifo *kfifo = &ctx->bitstream_fifo.kfifo; + struct coda_dev *dev = ctx->dev; + u32 wr_ptr; + + wr_ptr = ctx->bitstream.paddr + (kfifo->in & kfifo->mask); + coda_write(dev, wr_ptr, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); +} + +static int coda_h264_bitstream_pad(struct coda_ctx *ctx, u32 size) +{ + unsigned char *buf; + u32 n; + + if (size < 6) + size = 6; + + buf = kmalloc(size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + coda_h264_filler_nal(size, buf); + n = kfifo_in(&ctx->bitstream_fifo, buf, size); + kfree(buf); + + return (n < size) ? -ENOSPC : 0; +} + +int coda_bitstream_flush(struct coda_ctx *ctx) +{ + int ret; + + if (ctx->inst_type != CODA_INST_DECODER || !ctx->use_bit) + return 0; + + ret = coda_command_sync(ctx, CODA_COMMAND_DEC_BUF_FLUSH); + if (ret < 0) { + v4l2_err(&ctx->dev->v4l2_dev, "failed to flush bitstream\n"); + return ret; + } + + kfifo_init(&ctx->bitstream_fifo, ctx->bitstream.vaddr, + ctx->bitstream.size); + coda_kfifo_sync_to_device_full(ctx); + + return 0; +} + +static int coda_bitstream_queue(struct coda_ctx *ctx, const u8 *buf, u32 size) +{ + u32 n = kfifo_in(&ctx->bitstream_fifo, buf, size); + + return (n < size) ? -ENOSPC : 0; +} + +static u32 coda_buffer_parse_headers(struct coda_ctx *ctx, + struct vb2_v4l2_buffer *src_buf, + u32 payload) +{ + u8 *vaddr = vb2_plane_vaddr(&src_buf->vb2_buf, 0); + u32 size = 0; + + switch (ctx->codec->src_fourcc) { + case V4L2_PIX_FMT_MPEG2: + size = coda_mpeg2_parse_headers(ctx, vaddr, payload); + break; + case V4L2_PIX_FMT_MPEG4: + size = coda_mpeg4_parse_headers(ctx, vaddr, payload); + break; + default: + break; + } + + return size; +} + +static bool coda_bitstream_try_queue(struct coda_ctx *ctx, + struct vb2_v4l2_buffer *src_buf) +{ + unsigned long payload = vb2_get_plane_payload(&src_buf->vb2_buf, 0); + u8 *vaddr = vb2_plane_vaddr(&src_buf->vb2_buf, 0); + int ret; + int i; + + if (coda_get_bitstream_payload(ctx) + payload + 512 >= + ctx->bitstream.size) + return false; + + if (!vaddr) { + v4l2_err(&ctx->dev->v4l2_dev, "trying to queue empty buffer\n"); + return true; + } + + if (ctx->qsequence == 0 && payload < 512) { + /* + * Add padding after the first buffer, if it is too small to be + * fetched by the CODA, by repeating the headers. Without + * repeated headers, or the first frame already queued, decoder + * sequence initialization fails with error code 0x2000 on i.MX6 + * or error code 0x1 on i.MX51. + */ + u32 header_size = coda_buffer_parse_headers(ctx, src_buf, + payload); + + if (header_size) { + coda_dbg(1, ctx, "pad with %u-byte header\n", + header_size); + for (i = payload; i < 512; i += header_size) { + ret = coda_bitstream_queue(ctx, vaddr, + header_size); + if (ret < 0) { + v4l2_err(&ctx->dev->v4l2_dev, + "bitstream buffer overflow\n"); + return false; + } + if (ctx->dev->devtype->product == CODA_960) + break; + } + } else { + coda_dbg(1, ctx, + "could not parse header, sequence initialization might fail\n"); + } + + /* Add padding before the first buffer, if it is too small */ + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264) + coda_h264_bitstream_pad(ctx, 512 - payload); + } + + ret = coda_bitstream_queue(ctx, vaddr, payload); + if (ret < 0) { + v4l2_err(&ctx->dev->v4l2_dev, "bitstream buffer overflow\n"); + return false; + } + + src_buf->sequence = ctx->qsequence++; + + /* Sync read pointer to device */ + if (ctx == v4l2_m2m_get_curr_priv(ctx->dev->m2m_dev)) + coda_kfifo_sync_to_device_write(ctx); + + /* Set the stream-end flag after the last buffer is queued */ + if (src_buf->flags & V4L2_BUF_FLAG_LAST) + coda_bit_stream_end_flag(ctx); + ctx->hold = false; + + return true; +} + +void coda_fill_bitstream(struct coda_ctx *ctx, struct list_head *buffer_list) +{ + struct vb2_v4l2_buffer *src_buf; + struct coda_buffer_meta *meta; + u32 start; + + lockdep_assert_held(&ctx->bitstream_mutex); + + if (ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG) + return; + + while (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) > 0) { + /* + * Only queue two JPEGs into the bitstream buffer to keep + * latency low. We need at least one complete buffer and the + * header of another buffer (for prescan) in the bitstream. + */ + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_JPEG && + ctx->num_metas > 1) + break; + + if (ctx->num_internal_frames && + ctx->num_metas >= ctx->num_internal_frames) { + meta = list_first_entry(&ctx->buffer_meta_list, + struct coda_buffer_meta, list); + + /* + * If we managed to fill in at least a full reorder + * window of buffers (num_internal_frames is a + * conservative estimate for this) and the bitstream + * prefetcher has at least 2 256 bytes periods beyond + * the first buffer to fetch, we can safely stop queuing + * in order to limit the decoder drain latency. + */ + if (coda_bitstream_can_fetch_past(ctx, meta->end)) + break; + } + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + + /* Drop frames that do not start/end with a SOI/EOI markers */ + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_JPEG && + !coda_jpeg_check_buffer(ctx, &src_buf->vb2_buf)) { + v4l2_err(&ctx->dev->v4l2_dev, + "dropping invalid JPEG frame %d\n", + ctx->qsequence); + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + if (buffer_list) { + struct v4l2_m2m_buffer *m2m_buf; + + m2m_buf = container_of(src_buf, + struct v4l2_m2m_buffer, + vb); + list_add_tail(&m2m_buf->list, buffer_list); + } else { + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + } + continue; + } + + /* Dump empty buffers */ + if (!vb2_get_plane_payload(&src_buf->vb2_buf, 0)) { + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + continue; + } + + /* Buffer start position */ + start = ctx->bitstream_fifo.kfifo.in; + + if (coda_bitstream_try_queue(ctx, src_buf)) { + /* + * Source buffer is queued in the bitstream ringbuffer; + * queue the timestamp and mark source buffer as done + */ + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + + meta = kmalloc(sizeof(*meta), GFP_KERNEL); + if (meta) { + meta->sequence = src_buf->sequence; + meta->timecode = src_buf->timecode; + meta->timestamp = src_buf->vb2_buf.timestamp; + meta->start = start; + meta->end = ctx->bitstream_fifo.kfifo.in; + meta->last = src_buf->flags & V4L2_BUF_FLAG_LAST; + if (meta->last) + coda_dbg(1, ctx, "marking last meta"); + spin_lock(&ctx->buffer_meta_lock); + list_add_tail(&meta->list, + &ctx->buffer_meta_list); + ctx->num_metas++; + spin_unlock(&ctx->buffer_meta_lock); + + trace_coda_bit_queue(ctx, src_buf, meta); + } + + if (buffer_list) { + struct v4l2_m2m_buffer *m2m_buf; + + m2m_buf = container_of(src_buf, + struct v4l2_m2m_buffer, + vb); + list_add_tail(&m2m_buf->list, buffer_list); + } else { + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + } + } else { + break; + } + } +} + +void coda_bit_stream_end_flag(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + + ctx->bit_stream_param |= CODA_BIT_STREAM_END_FLAG; + + /* If this context is currently running, update the hardware flag */ + if ((dev->devtype->product == CODA_960) && + coda_isbusy(dev) && + (ctx->idx == coda_read(dev, CODA_REG_BIT_RUN_INDEX))) { + coda_write(dev, ctx->bit_stream_param, + CODA_REG_BIT_BIT_STREAM_PARAM); + } +} + +static void coda_parabuf_write(struct coda_ctx *ctx, int index, u32 value) +{ + struct coda_dev *dev = ctx->dev; + u32 *p = ctx->parabuf.vaddr; + + if (dev->devtype->product == CODA_DX6) + p[index] = value; + else + p[index ^ 1] = value; +} + +static inline int coda_alloc_context_buf(struct coda_ctx *ctx, + struct coda_aux_buf *buf, size_t size, + const char *name) +{ + return coda_alloc_aux_buf(ctx->dev, buf, size, name, ctx->debugfs_entry); +} + + +static void coda_free_framebuffers(struct coda_ctx *ctx) +{ + int i; + + for (i = 0; i < CODA_MAX_FRAMEBUFFERS; i++) + coda_free_aux_buf(ctx->dev, &ctx->internal_frames[i].buf); +} + +static int coda_alloc_framebuffers(struct coda_ctx *ctx, + struct coda_q_data *q_data, u32 fourcc) +{ + struct coda_dev *dev = ctx->dev; + unsigned int ysize, ycbcr_size; + int ret; + int i; + + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264 || + ctx->codec->dst_fourcc == V4L2_PIX_FMT_H264 || + ctx->codec->src_fourcc == V4L2_PIX_FMT_MPEG4 || + ctx->codec->dst_fourcc == V4L2_PIX_FMT_MPEG4) + ysize = round_up(q_data->rect.width, 16) * + round_up(q_data->rect.height, 16); + else + ysize = round_up(q_data->rect.width, 8) * q_data->rect.height; + + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) + ycbcr_size = round_up(ysize, 4096) + ysize / 2; + else + ycbcr_size = ysize + ysize / 2; + + /* Allocate frame buffers */ + for (i = 0; i < ctx->num_internal_frames; i++) { + size_t size = ycbcr_size; + char *name; + + /* Add space for mvcol buffers */ + if (dev->devtype->product != CODA_DX6 && + (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264 || + (ctx->codec->src_fourcc == V4L2_PIX_FMT_MPEG4 && i == 0))) + size += ysize / 4; + name = kasprintf(GFP_KERNEL, "fb%d", i); + if (!name) { + coda_free_framebuffers(ctx); + return -ENOMEM; + } + ret = coda_alloc_context_buf(ctx, &ctx->internal_frames[i].buf, + size, name); + kfree(name); + if (ret < 0) { + coda_free_framebuffers(ctx); + return ret; + } + } + + /* Register frame buffers in the parameter buffer */ + for (i = 0; i < ctx->num_internal_frames; i++) { + u32 y, cb, cr, mvcol; + + /* Start addresses of Y, Cb, Cr planes */ + y = ctx->internal_frames[i].buf.paddr; + cb = y + ysize; + cr = y + ysize + ysize/4; + mvcol = y + ysize + ysize/4 + ysize/4; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) { + cb = round_up(cb, 4096); + mvcol = cb + ysize/2; + cr = 0; + /* Packed 20-bit MSB of base addresses */ + /* YYYYYCCC, CCyyyyyc, cccc.... */ + y = (y & 0xfffff000) | cb >> 20; + cb = (cb & 0x000ff000) << 12; + } + coda_parabuf_write(ctx, i * 3 + 0, y); + coda_parabuf_write(ctx, i * 3 + 1, cb); + coda_parabuf_write(ctx, i * 3 + 2, cr); + + if (dev->devtype->product == CODA_DX6) + continue; + + /* mvcol buffer for h.264 and mpeg4 */ + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_H264) + coda_parabuf_write(ctx, 96 + i, mvcol); + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_MPEG4 && i == 0) + coda_parabuf_write(ctx, 97, mvcol); + } + + return 0; +} + +static void coda_free_context_buffers(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + + coda_free_aux_buf(dev, &ctx->slicebuf); + coda_free_aux_buf(dev, &ctx->psbuf); + if (dev->devtype->product != CODA_DX6) + coda_free_aux_buf(dev, &ctx->workbuf); + coda_free_aux_buf(dev, &ctx->parabuf); +} + +static int coda_alloc_context_buffers(struct coda_ctx *ctx, + struct coda_q_data *q_data) +{ + struct coda_dev *dev = ctx->dev; + size_t size; + int ret; + + if (!ctx->parabuf.vaddr) { + ret = coda_alloc_context_buf(ctx, &ctx->parabuf, + CODA_PARA_BUF_SIZE, "parabuf"); + if (ret < 0) + return ret; + } + + if (dev->devtype->product == CODA_DX6) + return 0; + + if (!ctx->slicebuf.vaddr && q_data->fourcc == V4L2_PIX_FMT_H264) { + /* worst case slice size */ + size = (DIV_ROUND_UP(q_data->rect.width, 16) * + DIV_ROUND_UP(q_data->rect.height, 16)) * 3200 / 8 + 512; + ret = coda_alloc_context_buf(ctx, &ctx->slicebuf, size, + "slicebuf"); + if (ret < 0) + goto err; + } + + if (!ctx->psbuf.vaddr && (dev->devtype->product == CODA_HX4 || + dev->devtype->product == CODA_7541)) { + ret = coda_alloc_context_buf(ctx, &ctx->psbuf, + CODA7_PS_BUF_SIZE, "psbuf"); + if (ret < 0) + goto err; + } + + if (!ctx->workbuf.vaddr) { + size = dev->devtype->workbuf_size; + if (dev->devtype->product == CODA_960 && + q_data->fourcc == V4L2_PIX_FMT_H264) + size += CODA9_PS_SAVE_SIZE; + ret = coda_alloc_context_buf(ctx, &ctx->workbuf, size, + "workbuf"); + if (ret < 0) + goto err; + } + + return 0; + +err: + coda_free_context_buffers(ctx); + return ret; +} + +static int coda_encode_header(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf, + int header_code, u8 *header, int *size) +{ + struct vb2_buffer *vb = &buf->vb2_buf; + struct coda_dev *dev = ctx->dev; + struct coda_q_data *q_data_src; + struct v4l2_rect *r; + size_t bufsize; + int ret; + int i; + + if (dev->devtype->product == CODA_960) + memset(vb2_plane_vaddr(vb, 0), 0, 64); + + coda_write(dev, vb2_dma_contig_plane_dma_addr(vb, 0), + CODA_CMD_ENC_HEADER_BB_START); + bufsize = vb2_plane_size(vb, 0); + if (dev->devtype->product == CODA_960) + bufsize /= 1024; + coda_write(dev, bufsize, CODA_CMD_ENC_HEADER_BB_SIZE); + if (dev->devtype->product == CODA_960 && + ctx->codec->dst_fourcc == V4L2_PIX_FMT_H264 && + header_code == CODA_HEADER_H264_SPS) { + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + r = &q_data_src->rect; + + if (r->width % 16 || r->height % 16) { + u32 crop_right = round_up(r->width, 16) - r->width; + u32 crop_bottom = round_up(r->height, 16) - r->height; + + coda_write(dev, crop_right, + CODA9_CMD_ENC_HEADER_FRAME_CROP_H); + coda_write(dev, crop_bottom, + CODA9_CMD_ENC_HEADER_FRAME_CROP_V); + header_code |= CODA9_HEADER_FRAME_CROP; + } + } + coda_write(dev, header_code, CODA_CMD_ENC_HEADER_CODE); + ret = coda_command_sync(ctx, CODA_COMMAND_ENCODE_HEADER); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "CODA_COMMAND_ENCODE_HEADER timeout\n"); + return ret; + } + + if (dev->devtype->product == CODA_960) { + for (i = 63; i > 0; i--) + if (((char *)vb2_plane_vaddr(vb, 0))[i] != 0) + break; + *size = i + 1; + } else { + *size = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->reg_idx)) - + coda_read(dev, CODA_CMD_ENC_HEADER_BB_START); + } + memcpy(header, vb2_plane_vaddr(vb, 0), *size); + + return 0; +} + +static u32 coda_slice_mode(struct coda_ctx *ctx) +{ + int size, unit; + + switch (ctx->params.slice_mode) { + case V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE: + default: + return 0; + case V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB: + size = ctx->params.slice_max_mb; + unit = 1; + break; + case V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES: + size = ctx->params.slice_max_bits; + unit = 0; + break; + } + + return ((size & CODA_SLICING_SIZE_MASK) << CODA_SLICING_SIZE_OFFSET) | + ((unit & CODA_SLICING_UNIT_MASK) << CODA_SLICING_UNIT_OFFSET) | + ((1 & CODA_SLICING_MODE_MASK) << CODA_SLICING_MODE_OFFSET); +} + +static int coda_enc_param_change(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + u32 change_enable = 0; + u32 success; + int ret; + + if (ctx->params.gop_size_changed) { + change_enable |= CODA_PARAM_CHANGE_RC_GOP; + coda_write(dev, ctx->params.gop_size, + CODA_CMD_ENC_PARAM_RC_GOP); + ctx->gopcounter = ctx->params.gop_size - 1; + ctx->params.gop_size_changed = false; + } + if (ctx->params.h264_intra_qp_changed) { + coda_dbg(1, ctx, "parameter change: intra Qp %u\n", + ctx->params.h264_intra_qp); + + if (ctx->params.bitrate) { + change_enable |= CODA_PARAM_CHANGE_RC_INTRA_QP; + coda_write(dev, ctx->params.h264_intra_qp, + CODA_CMD_ENC_PARAM_RC_INTRA_QP); + } + ctx->params.h264_intra_qp_changed = false; + } + if (ctx->params.bitrate_changed) { + coda_dbg(1, ctx, "parameter change: bitrate %u kbit/s\n", + ctx->params.bitrate); + change_enable |= CODA_PARAM_CHANGE_RC_BITRATE; + coda_write(dev, ctx->params.bitrate, + CODA_CMD_ENC_PARAM_RC_BITRATE); + ctx->params.bitrate_changed = false; + } + if (ctx->params.framerate_changed) { + coda_dbg(1, ctx, "parameter change: frame rate %u/%u Hz\n", + ctx->params.framerate & 0xffff, + (ctx->params.framerate >> 16) + 1); + change_enable |= CODA_PARAM_CHANGE_RC_FRAME_RATE; + coda_write(dev, ctx->params.framerate, + CODA_CMD_ENC_PARAM_RC_FRAME_RATE); + ctx->params.framerate_changed = false; + } + if (ctx->params.intra_refresh_changed) { + coda_dbg(1, ctx, "parameter change: intra refresh MBs %u\n", + ctx->params.intra_refresh); + change_enable |= CODA_PARAM_CHANGE_INTRA_MB_NUM; + coda_write(dev, ctx->params.intra_refresh, + CODA_CMD_ENC_PARAM_INTRA_MB_NUM); + ctx->params.intra_refresh_changed = false; + } + if (ctx->params.slice_mode_changed) { + change_enable |= CODA_PARAM_CHANGE_SLICE_MODE; + coda_write(dev, coda_slice_mode(ctx), + CODA_CMD_ENC_PARAM_SLICE_MODE); + ctx->params.slice_mode_changed = false; + } + + if (!change_enable) + return 0; + + coda_write(dev, change_enable, CODA_CMD_ENC_PARAM_CHANGE_ENABLE); + + ret = coda_command_sync(ctx, CODA_COMMAND_RC_CHANGE_PARAMETER); + if (ret < 0) + return ret; + + success = coda_read(dev, CODA_RET_ENC_PARAM_CHANGE_SUCCESS); + if (success != 1) + coda_dbg(1, ctx, "parameter change failed: %u\n", success); + + return 0; +} + +static phys_addr_t coda_iram_alloc(struct coda_iram_info *iram, size_t size) +{ + phys_addr_t ret; + + size = round_up(size, 1024); + if (size > iram->remaining) + return 0; + iram->remaining -= size; + + ret = iram->next_paddr; + iram->next_paddr += size; + + return ret; +} + +static void coda_setup_iram(struct coda_ctx *ctx) +{ + struct coda_iram_info *iram_info = &ctx->iram_info; + struct coda_dev *dev = ctx->dev; + int w64, w128; + int mb_width; + int dbk_bits; + int bit_bits; + int ip_bits; + int me_bits; + + memset(iram_info, 0, sizeof(*iram_info)); + iram_info->next_paddr = dev->iram.paddr; + iram_info->remaining = dev->iram.size; + + if (!dev->iram.vaddr) + return; + + switch (dev->devtype->product) { + case CODA_HX4: + dbk_bits = CODA7_USE_HOST_DBK_ENABLE; + bit_bits = CODA7_USE_HOST_BIT_ENABLE; + ip_bits = CODA7_USE_HOST_IP_ENABLE; + me_bits = CODA7_USE_HOST_ME_ENABLE; + break; + case CODA_7541: + dbk_bits = CODA7_USE_HOST_DBK_ENABLE | CODA7_USE_DBK_ENABLE; + bit_bits = CODA7_USE_HOST_BIT_ENABLE | CODA7_USE_BIT_ENABLE; + ip_bits = CODA7_USE_HOST_IP_ENABLE | CODA7_USE_IP_ENABLE; + me_bits = CODA7_USE_HOST_ME_ENABLE | CODA7_USE_ME_ENABLE; + break; + case CODA_960: + dbk_bits = CODA9_USE_HOST_DBK_ENABLE | CODA9_USE_DBK_ENABLE; + bit_bits = CODA9_USE_HOST_BIT_ENABLE | CODA7_USE_BIT_ENABLE; + ip_bits = CODA9_USE_HOST_IP_ENABLE | CODA7_USE_IP_ENABLE; + me_bits = 0; + break; + default: /* CODA_DX6 */ + return; + } + + if (ctx->inst_type == CODA_INST_ENCODER) { + struct coda_q_data *q_data_src; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + mb_width = DIV_ROUND_UP(q_data_src->rect.width, 16); + w128 = mb_width * 128; + w64 = mb_width * 64; + + /* Prioritize in case IRAM is too small for everything */ + if (dev->devtype->product == CODA_HX4 || + dev->devtype->product == CODA_7541) { + iram_info->search_ram_size = round_up(mb_width * 16 * + 36 + 2048, 1024); + iram_info->search_ram_paddr = coda_iram_alloc(iram_info, + iram_info->search_ram_size); + if (!iram_info->search_ram_paddr) { + pr_err("IRAM is smaller than the search ram size\n"); + goto out; + } + iram_info->axi_sram_use |= me_bits; + } + + /* Only H.264BP and H.263P3 are considered */ + iram_info->buf_dbk_y_use = coda_iram_alloc(iram_info, w64); + iram_info->buf_dbk_c_use = coda_iram_alloc(iram_info, w64); + if (!iram_info->buf_dbk_y_use || !iram_info->buf_dbk_c_use) + goto out; + iram_info->axi_sram_use |= dbk_bits; + + iram_info->buf_bit_use = coda_iram_alloc(iram_info, w128); + if (!iram_info->buf_bit_use) + goto out; + iram_info->axi_sram_use |= bit_bits; + + iram_info->buf_ip_ac_dc_use = coda_iram_alloc(iram_info, w128); + if (!iram_info->buf_ip_ac_dc_use) + goto out; + iram_info->axi_sram_use |= ip_bits; + + /* OVL and BTP disabled for encoder */ + } else if (ctx->inst_type == CODA_INST_DECODER) { + struct coda_q_data *q_data_dst; + + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + mb_width = DIV_ROUND_UP(q_data_dst->width, 16); + w128 = mb_width * 128; + + iram_info->buf_dbk_y_use = coda_iram_alloc(iram_info, w128); + iram_info->buf_dbk_c_use = coda_iram_alloc(iram_info, w128); + if (!iram_info->buf_dbk_y_use || !iram_info->buf_dbk_c_use) + goto out; + iram_info->axi_sram_use |= dbk_bits; + + iram_info->buf_bit_use = coda_iram_alloc(iram_info, w128); + if (!iram_info->buf_bit_use) + goto out; + iram_info->axi_sram_use |= bit_bits; + + iram_info->buf_ip_ac_dc_use = coda_iram_alloc(iram_info, w128); + if (!iram_info->buf_ip_ac_dc_use) + goto out; + iram_info->axi_sram_use |= ip_bits; + + /* OVL and BTP unused as there is no VC1 support yet */ + } + +out: + if (!(iram_info->axi_sram_use & CODA7_USE_HOST_IP_ENABLE)) + coda_dbg(1, ctx, "IRAM smaller than needed\n"); + + if (dev->devtype->product == CODA_HX4 || + dev->devtype->product == CODA_7541) { + /* TODO - Enabling these causes picture errors on CODA7541 */ + if (ctx->inst_type == CODA_INST_DECODER) { + /* fw 1.4.50 */ + iram_info->axi_sram_use &= ~(CODA7_USE_HOST_IP_ENABLE | + CODA7_USE_IP_ENABLE); + } else { + /* fw 13.4.29 */ + iram_info->axi_sram_use &= ~(CODA7_USE_HOST_IP_ENABLE | + CODA7_USE_HOST_DBK_ENABLE | + CODA7_USE_IP_ENABLE | + CODA7_USE_DBK_ENABLE); + } + } +} + +static u32 coda_supported_firmwares[] = { + CODA_FIRMWARE_VERNUM(CODA_DX6, 2, 2, 5), + CODA_FIRMWARE_VERNUM(CODA_HX4, 1, 4, 50), + CODA_FIRMWARE_VERNUM(CODA_7541, 1, 4, 50), + CODA_FIRMWARE_VERNUM(CODA_960, 2, 1, 5), + CODA_FIRMWARE_VERNUM(CODA_960, 2, 1, 9), + CODA_FIRMWARE_VERNUM(CODA_960, 2, 3, 10), + CODA_FIRMWARE_VERNUM(CODA_960, 3, 1, 1), +}; + +static bool coda_firmware_supported(u32 vernum) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(coda_supported_firmwares); i++) + if (vernum == coda_supported_firmwares[i]) + return true; + return false; +} + +int coda_check_firmware(struct coda_dev *dev) +{ + u16 product, major, minor, release; + u32 data; + int ret; + + ret = clk_prepare_enable(dev->clk_per); + if (ret) + goto err_clk_per; + + ret = clk_prepare_enable(dev->clk_ahb); + if (ret) + goto err_clk_ahb; + + coda_write(dev, 0, CODA_CMD_FIRMWARE_VERNUM); + coda_write(dev, CODA_REG_BIT_BUSY_FLAG, CODA_REG_BIT_BUSY); + coda_write(dev, 0, CODA_REG_BIT_RUN_INDEX); + coda_write(dev, 0, CODA_REG_BIT_RUN_COD_STD); + coda_write(dev, CODA_COMMAND_FIRMWARE_GET, CODA_REG_BIT_RUN_COMMAND); + if (coda_wait_timeout(dev)) { + v4l2_err(&dev->v4l2_dev, "firmware get command error\n"); + ret = -EIO; + goto err_run_cmd; + } + + if (dev->devtype->product == CODA_960) { + data = coda_read(dev, CODA9_CMD_FIRMWARE_CODE_REV); + v4l2_info(&dev->v4l2_dev, "Firmware code revision: %d\n", + data); + } + + /* Check we are compatible with the loaded firmware */ + data = coda_read(dev, CODA_CMD_FIRMWARE_VERNUM); + product = CODA_FIRMWARE_PRODUCT(data); + major = CODA_FIRMWARE_MAJOR(data); + minor = CODA_FIRMWARE_MINOR(data); + release = CODA_FIRMWARE_RELEASE(data); + + clk_disable_unprepare(dev->clk_per); + clk_disable_unprepare(dev->clk_ahb); + + if (product != dev->devtype->product) { + v4l2_err(&dev->v4l2_dev, + "Wrong firmware. Hw: %s, Fw: %s, Version: %u.%u.%u\n", + coda_product_name(dev->devtype->product), + coda_product_name(product), major, minor, release); + return -EINVAL; + } + + v4l2_info(&dev->v4l2_dev, "Initialized %s.\n", + coda_product_name(product)); + + if (coda_firmware_supported(data)) { + v4l2_info(&dev->v4l2_dev, "Firmware version: %u.%u.%u\n", + major, minor, release); + } else { + v4l2_warn(&dev->v4l2_dev, + "Unsupported firmware version: %u.%u.%u\n", + major, minor, release); + } + + return 0; + +err_run_cmd: + clk_disable_unprepare(dev->clk_ahb); +err_clk_ahb: + clk_disable_unprepare(dev->clk_per); +err_clk_per: + return ret; +} + +static void coda9_set_frame_cache(struct coda_ctx *ctx, u32 fourcc) +{ + u32 cache_size, cache_config; + + if (ctx->tiled_map_type == GDI_LINEAR_FRAME_MAP) { + /* Luma 2x0 page, 2x6 cache, chroma 2x0 page, 2x4 cache size */ + cache_size = 0x20262024; + cache_config = 2 << CODA9_CACHE_PAGEMERGE_OFFSET; + } else { + /* Luma 0x2 page, 4x4 cache, chroma 0x2 page, 4x3 cache size */ + cache_size = 0x02440243; + cache_config = 1 << CODA9_CACHE_PAGEMERGE_OFFSET; + } + coda_write(ctx->dev, cache_size, CODA9_CMD_SET_FRAME_CACHE_SIZE); + if (fourcc == V4L2_PIX_FMT_NV12 || fourcc == V4L2_PIX_FMT_YUYV) { + cache_config |= 32 << CODA9_CACHE_LUMA_BUFFER_SIZE_OFFSET | + 16 << CODA9_CACHE_CR_BUFFER_SIZE_OFFSET | + 0 << CODA9_CACHE_CB_BUFFER_SIZE_OFFSET; + } else { + cache_config |= 32 << CODA9_CACHE_LUMA_BUFFER_SIZE_OFFSET | + 8 << CODA9_CACHE_CR_BUFFER_SIZE_OFFSET | + 8 << CODA9_CACHE_CB_BUFFER_SIZE_OFFSET; + } + coda_write(ctx->dev, cache_config, CODA9_CMD_SET_FRAME_CACHE_CONFIG); +} + +/* + * Encoder context operations + */ + +static int coda_encoder_reqbufs(struct coda_ctx *ctx, + struct v4l2_requestbuffers *rb) +{ + struct coda_q_data *q_data_src; + int ret; + + if (rb->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return 0; + + if (rb->count) { + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + ret = coda_alloc_context_buffers(ctx, q_data_src); + if (ret < 0) + return ret; + } else { + coda_free_context_buffers(ctx); + } + + return 0; +} + +static int coda_start_encoding(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + struct v4l2_device *v4l2_dev = &dev->v4l2_dev; + struct coda_q_data *q_data_src, *q_data_dst; + u32 bitstream_buf, bitstream_size; + struct vb2_v4l2_buffer *buf; + int gamma, ret, value; + u32 dst_fourcc; + int num_fb; + u32 stride; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + dst_fourcc = q_data_dst->fourcc; + + buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + bitstream_buf = vb2_dma_contig_plane_dma_addr(&buf->vb2_buf, 0); + bitstream_size = q_data_dst->sizeimage; + + if (!coda_is_initialized(dev)) { + v4l2_err(v4l2_dev, "coda is not initialized.\n"); + return -EFAULT; + } + + if (dst_fourcc == V4L2_PIX_FMT_JPEG) { + if (!ctx->params.jpeg_qmat_tab[0]) { + ctx->params.jpeg_qmat_tab[0] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[0]) + return -ENOMEM; + } + if (!ctx->params.jpeg_qmat_tab[1]) { + ctx->params.jpeg_qmat_tab[1] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[1]) + return -ENOMEM; + } + coda_set_jpeg_compression_quality(ctx, ctx->params.jpeg_quality); + } + + mutex_lock(&dev->coda_mutex); + + coda_write(dev, ctx->parabuf.paddr, CODA_REG_BIT_PARA_BUF_ADDR); + coda_write(dev, bitstream_buf, CODA_REG_BIT_RD_PTR(ctx->reg_idx)); + coda_write(dev, bitstream_buf, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); + switch (dev->devtype->product) { + case CODA_DX6: + coda_write(dev, CODADX6_STREAM_BUF_DYNALLOC_EN | + CODADX6_STREAM_BUF_PIC_RESET, CODA_REG_BIT_STREAM_CTRL); + break; + case CODA_960: + coda_write(dev, 0, CODA9_GDI_WPROT_RGN_EN); + fallthrough; + case CODA_HX4: + case CODA_7541: + coda_write(dev, CODA7_STREAM_BUF_DYNALLOC_EN | + CODA7_STREAM_BUF_PIC_RESET, CODA_REG_BIT_STREAM_CTRL); + break; + } + + ctx->frame_mem_ctrl &= ~(CODA_FRAME_CHROMA_INTERLEAVE | (0x3 << 9) | + CODA9_FRAME_TILED2LINEAR); + if (q_data_src->fourcc == V4L2_PIX_FMT_NV12) + ctx->frame_mem_ctrl |= CODA_FRAME_CHROMA_INTERLEAVE; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) + ctx->frame_mem_ctrl |= (0x3 << 9) | CODA9_FRAME_TILED2LINEAR; + coda_write(dev, ctx->frame_mem_ctrl, CODA_REG_BIT_FRAME_MEM_CTRL); + + if (dev->devtype->product == CODA_DX6) { + /* Configure the coda */ + coda_write(dev, dev->iram.paddr, + CODADX6_REG_BIT_SEARCH_RAM_BASE_ADDR); + } + + /* Could set rotation here if needed */ + value = 0; + switch (dev->devtype->product) { + case CODA_DX6: + value = (q_data_src->rect.width & CODADX6_PICWIDTH_MASK) + << CODADX6_PICWIDTH_OFFSET; + value |= (q_data_src->rect.height & CODADX6_PICHEIGHT_MASK) + << CODA_PICHEIGHT_OFFSET; + break; + case CODA_HX4: + case CODA_7541: + if (dst_fourcc == V4L2_PIX_FMT_H264) { + value = (round_up(q_data_src->rect.width, 16) & + CODA7_PICWIDTH_MASK) << CODA7_PICWIDTH_OFFSET; + value |= (round_up(q_data_src->rect.height, 16) & + CODA7_PICHEIGHT_MASK) << CODA_PICHEIGHT_OFFSET; + break; + } + fallthrough; + case CODA_960: + value = (q_data_src->rect.width & CODA7_PICWIDTH_MASK) + << CODA7_PICWIDTH_OFFSET; + value |= (q_data_src->rect.height & CODA7_PICHEIGHT_MASK) + << CODA_PICHEIGHT_OFFSET; + } + coda_write(dev, value, CODA_CMD_ENC_SEQ_SRC_SIZE); + if (dst_fourcc == V4L2_PIX_FMT_JPEG) + ctx->params.framerate = 0; + coda_write(dev, ctx->params.framerate, + CODA_CMD_ENC_SEQ_SRC_F_RATE); + + ctx->params.codec_mode = ctx->codec->mode; + switch (dst_fourcc) { + case V4L2_PIX_FMT_MPEG4: + if (dev->devtype->product == CODA_960) + coda_write(dev, CODA9_STD_MPEG4, + CODA_CMD_ENC_SEQ_COD_STD); + else + coda_write(dev, CODA_STD_MPEG4, + CODA_CMD_ENC_SEQ_COD_STD); + coda_write(dev, 0, CODA_CMD_ENC_SEQ_MP4_PARA); + break; + case V4L2_PIX_FMT_H264: + if (dev->devtype->product == CODA_960) + coda_write(dev, CODA9_STD_H264, + CODA_CMD_ENC_SEQ_COD_STD); + else + coda_write(dev, CODA_STD_H264, + CODA_CMD_ENC_SEQ_COD_STD); + value = ((ctx->params.h264_disable_deblocking_filter_idc & + CODA_264PARAM_DISABLEDEBLK_MASK) << + CODA_264PARAM_DISABLEDEBLK_OFFSET) | + ((ctx->params.h264_slice_alpha_c0_offset_div2 & + CODA_264PARAM_DEBLKFILTEROFFSETALPHA_MASK) << + CODA_264PARAM_DEBLKFILTEROFFSETALPHA_OFFSET) | + ((ctx->params.h264_slice_beta_offset_div2 & + CODA_264PARAM_DEBLKFILTEROFFSETBETA_MASK) << + CODA_264PARAM_DEBLKFILTEROFFSETBETA_OFFSET) | + (ctx->params.h264_constrained_intra_pred_flag << + CODA_264PARAM_CONSTRAINEDINTRAPREDFLAG_OFFSET) | + (ctx->params.h264_chroma_qp_index_offset & + CODA_264PARAM_CHROMAQPOFFSET_MASK); + coda_write(dev, value, CODA_CMD_ENC_SEQ_264_PARA); + break; + case V4L2_PIX_FMT_JPEG: + coda_write(dev, 0, CODA_CMD_ENC_SEQ_JPG_PARA); + coda_write(dev, ctx->params.jpeg_restart_interval, + CODA_CMD_ENC_SEQ_JPG_RST_INTERVAL); + coda_write(dev, 0, CODA_CMD_ENC_SEQ_JPG_THUMB_EN); + coda_write(dev, 0, CODA_CMD_ENC_SEQ_JPG_THUMB_SIZE); + coda_write(dev, 0, CODA_CMD_ENC_SEQ_JPG_THUMB_OFFSET); + + coda_jpeg_write_tables(ctx); + break; + default: + v4l2_err(v4l2_dev, + "dst format (0x%08x) invalid.\n", dst_fourcc); + ret = -EINVAL; + goto out; + } + + /* + * slice mode and GOP size registers are used for thumb size/offset + * in JPEG mode + */ + if (dst_fourcc != V4L2_PIX_FMT_JPEG) { + value = coda_slice_mode(ctx); + coda_write(dev, value, CODA_CMD_ENC_SEQ_SLICE_MODE); + value = ctx->params.gop_size; + coda_write(dev, value, CODA_CMD_ENC_SEQ_GOP_SIZE); + } + + if (ctx->params.bitrate && (ctx->params.frame_rc_enable || + ctx->params.mb_rc_enable)) { + ctx->params.bitrate_changed = false; + ctx->params.h264_intra_qp_changed = false; + + /* Rate control enabled */ + value = (ctx->params.bitrate & CODA_RATECONTROL_BITRATE_MASK) + << CODA_RATECONTROL_BITRATE_OFFSET; + value |= 1 & CODA_RATECONTROL_ENABLE_MASK; + value |= (ctx->params.vbv_delay & + CODA_RATECONTROL_INITIALDELAY_MASK) + << CODA_RATECONTROL_INITIALDELAY_OFFSET; + if (dev->devtype->product == CODA_960) + value |= BIT(31); /* disable autoskip */ + } else { + value = 0; + } + coda_write(dev, value, CODA_CMD_ENC_SEQ_RC_PARA); + + coda_write(dev, ctx->params.vbv_size, CODA_CMD_ENC_SEQ_RC_BUF_SIZE); + coda_write(dev, ctx->params.intra_refresh, + CODA_CMD_ENC_SEQ_INTRA_REFRESH); + + coda_write(dev, bitstream_buf, CODA_CMD_ENC_SEQ_BB_START); + coda_write(dev, bitstream_size / 1024, CODA_CMD_ENC_SEQ_BB_SIZE); + + + value = 0; + if (dev->devtype->product == CODA_960) + gamma = CODA9_DEFAULT_GAMMA; + else + gamma = CODA_DEFAULT_GAMMA; + if (gamma > 0) { + coda_write(dev, (gamma & CODA_GAMMA_MASK) << CODA_GAMMA_OFFSET, + CODA_CMD_ENC_SEQ_RC_GAMMA); + } + + if (ctx->params.h264_min_qp || ctx->params.h264_max_qp) { + coda_write(dev, + ctx->params.h264_min_qp << CODA_QPMIN_OFFSET | + ctx->params.h264_max_qp << CODA_QPMAX_OFFSET, + CODA_CMD_ENC_SEQ_RC_QP_MIN_MAX); + } + if (dev->devtype->product == CODA_960) { + if (ctx->params.h264_max_qp) + value |= 1 << CODA9_OPTION_RCQPMAX_OFFSET; + if (CODA_DEFAULT_GAMMA > 0) + value |= 1 << CODA9_OPTION_GAMMA_OFFSET; + } else { + if (CODA_DEFAULT_GAMMA > 0) { + if (dev->devtype->product == CODA_DX6) + value |= 1 << CODADX6_OPTION_GAMMA_OFFSET; + else + value |= 1 << CODA7_OPTION_GAMMA_OFFSET; + } + if (ctx->params.h264_min_qp) + value |= 1 << CODA7_OPTION_RCQPMIN_OFFSET; + if (ctx->params.h264_max_qp) + value |= 1 << CODA7_OPTION_RCQPMAX_OFFSET; + } + coda_write(dev, value, CODA_CMD_ENC_SEQ_OPTION); + + if (ctx->params.frame_rc_enable && !ctx->params.mb_rc_enable) + value = 1; + else + value = 0; + coda_write(dev, value, CODA_CMD_ENC_SEQ_RC_INTERVAL_MODE); + + coda_setup_iram(ctx); + + if (dst_fourcc == V4L2_PIX_FMT_H264) { + switch (dev->devtype->product) { + case CODA_DX6: + value = FMO_SLICE_SAVE_BUF_SIZE << 7; + coda_write(dev, value, CODADX6_CMD_ENC_SEQ_FMO); + break; + case CODA_HX4: + case CODA_7541: + coda_write(dev, ctx->iram_info.search_ram_paddr, + CODA7_CMD_ENC_SEQ_SEARCH_BASE); + coda_write(dev, ctx->iram_info.search_ram_size, + CODA7_CMD_ENC_SEQ_SEARCH_SIZE); + break; + case CODA_960: + coda_write(dev, 0, CODA9_CMD_ENC_SEQ_ME_OPTION); + coda_write(dev, 0, CODA9_CMD_ENC_SEQ_INTRA_WEIGHT); + } + } + + ret = coda_command_sync(ctx, CODA_COMMAND_SEQ_INIT); + if (ret < 0) { + v4l2_err(v4l2_dev, "CODA_COMMAND_SEQ_INIT timeout\n"); + goto out; + } + + if (coda_read(dev, CODA_RET_ENC_SEQ_SUCCESS) == 0) { + v4l2_err(v4l2_dev, "CODA_COMMAND_SEQ_INIT failed\n"); + ret = -EFAULT; + goto out; + } + ctx->initialized = 1; + + if (dst_fourcc != V4L2_PIX_FMT_JPEG) { + if (dev->devtype->product == CODA_960) + ctx->num_internal_frames = 4; + else + ctx->num_internal_frames = 2; + ret = coda_alloc_framebuffers(ctx, q_data_src, dst_fourcc); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to allocate framebuffers\n"); + goto out; + } + num_fb = 2; + stride = q_data_src->bytesperline; + } else { + ctx->num_internal_frames = 0; + num_fb = 0; + stride = 0; + } + coda_write(dev, num_fb, CODA_CMD_SET_FRAME_BUF_NUM); + coda_write(dev, stride, CODA_CMD_SET_FRAME_BUF_STRIDE); + + if (dev->devtype->product == CODA_HX4 || + dev->devtype->product == CODA_7541) { + coda_write(dev, q_data_src->bytesperline, + CODA7_CMD_SET_FRAME_SOURCE_BUF_STRIDE); + } + if (dev->devtype->product != CODA_DX6) { + coda_write(dev, ctx->iram_info.buf_bit_use, + CODA7_CMD_SET_FRAME_AXI_BIT_ADDR); + coda_write(dev, ctx->iram_info.buf_ip_ac_dc_use, + CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR); + coda_write(dev, ctx->iram_info.buf_dbk_y_use, + CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR); + coda_write(dev, ctx->iram_info.buf_dbk_c_use, + CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR); + coda_write(dev, ctx->iram_info.buf_ovl_use, + CODA7_CMD_SET_FRAME_AXI_OVL_ADDR); + if (dev->devtype->product == CODA_960) { + coda_write(dev, ctx->iram_info.buf_btp_use, + CODA9_CMD_SET_FRAME_AXI_BTP_ADDR); + + coda9_set_frame_cache(ctx, q_data_src->fourcc); + + /* FIXME */ + coda_write(dev, ctx->internal_frames[2].buf.paddr, + CODA9_CMD_SET_FRAME_SUBSAMP_A); + coda_write(dev, ctx->internal_frames[3].buf.paddr, + CODA9_CMD_SET_FRAME_SUBSAMP_B); + } + } + + ret = coda_command_sync(ctx, CODA_COMMAND_SET_FRAME_BUF); + if (ret < 0) { + v4l2_err(v4l2_dev, "CODA_COMMAND_SET_FRAME_BUF timeout\n"); + goto out; + } + + coda_dbg(1, ctx, "start encoding %dx%d %4.4s->%4.4s @ %d/%d Hz\n", + q_data_src->rect.width, q_data_src->rect.height, + (char *)&ctx->codec->src_fourcc, (char *)&dst_fourcc, + ctx->params.framerate & 0xffff, + (ctx->params.framerate >> 16) + 1); + + /* Save stream headers */ + buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + switch (dst_fourcc) { + case V4L2_PIX_FMT_H264: + /* + * Get SPS in the first frame and copy it to an + * intermediate buffer. + */ + ret = coda_encode_header(ctx, buf, CODA_HEADER_H264_SPS, + &ctx->vpu_header[0][0], + &ctx->vpu_header_size[0]); + if (ret < 0) + goto out; + + /* + * If visible width or height are not aligned to macroblock + * size, the crop_right and crop_bottom SPS fields must be set + * to the difference between visible and coded size. This is + * only supported by CODA960 firmware. All others do not allow + * writing frame cropping parameters, so we have to manually + * fix up the SPS RBSP (Sequence Parameter Set Raw Byte + * Sequence Payload) ourselves. + */ + if (ctx->dev->devtype->product != CODA_960 && + ((q_data_src->rect.width % 16) || + (q_data_src->rect.height % 16))) { + ret = coda_h264_sps_fixup(ctx, q_data_src->rect.width, + q_data_src->rect.height, + &ctx->vpu_header[0][0], + &ctx->vpu_header_size[0], + sizeof(ctx->vpu_header[0])); + if (ret < 0) + goto out; + } + + /* + * Get PPS in the first frame and copy it to an + * intermediate buffer. + */ + ret = coda_encode_header(ctx, buf, CODA_HEADER_H264_PPS, + &ctx->vpu_header[1][0], + &ctx->vpu_header_size[1]); + if (ret < 0) + goto out; + + /* + * Length of H.264 headers is variable and thus it might not be + * aligned for the coda to append the encoded frame. In that is + * the case a filler NAL must be added to header 2. + */ + ctx->vpu_header_size[2] = coda_h264_padding( + (ctx->vpu_header_size[0] + + ctx->vpu_header_size[1]), + ctx->vpu_header[2]); + break; + case V4L2_PIX_FMT_MPEG4: + /* + * Get VOS in the first frame and copy it to an + * intermediate buffer + */ + ret = coda_encode_header(ctx, buf, CODA_HEADER_MP4V_VOS, + &ctx->vpu_header[0][0], + &ctx->vpu_header_size[0]); + if (ret < 0) + goto out; + + ret = coda_encode_header(ctx, buf, CODA_HEADER_MP4V_VIS, + &ctx->vpu_header[1][0], + &ctx->vpu_header_size[1]); + if (ret < 0) + goto out; + + ret = coda_encode_header(ctx, buf, CODA_HEADER_MP4V_VOL, + &ctx->vpu_header[2][0], + &ctx->vpu_header_size[2]); + if (ret < 0) + goto out; + break; + default: + /* No more formats need to save headers at the moment */ + break; + } + +out: + mutex_unlock(&dev->coda_mutex); + return ret; +} + +static int coda_prepare_encode(struct coda_ctx *ctx) +{ + struct coda_q_data *q_data_src, *q_data_dst; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct coda_dev *dev = ctx->dev; + int force_ipicture; + int quant_param = 0; + u32 pic_stream_buffer_addr, pic_stream_buffer_size; + u32 rot_mode = 0; + u32 dst_fourcc; + u32 reg; + int ret; + + ret = coda_enc_param_change(ctx); + if (ret < 0) { + v4l2_warn(&ctx->dev->v4l2_dev, "parameter change failed: %d\n", + ret); + } + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + dst_fourcc = q_data_dst->fourcc; + + src_buf->sequence = ctx->osequence; + dst_buf->sequence = ctx->osequence; + ctx->osequence++; + + force_ipicture = ctx->params.force_ipicture; + if (force_ipicture) + ctx->params.force_ipicture = false; + else if (ctx->params.gop_size != 0 && + (src_buf->sequence % ctx->params.gop_size) == 0) + force_ipicture = 1; + + /* + * Workaround coda firmware BUG that only marks the first + * frame as IDR. This is a problem for some decoders that can't + * recover when a frame is lost. + */ + if (!force_ipicture) { + src_buf->flags |= V4L2_BUF_FLAG_PFRAME; + src_buf->flags &= ~V4L2_BUF_FLAG_KEYFRAME; + } else { + src_buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + src_buf->flags &= ~V4L2_BUF_FLAG_PFRAME; + } + + if (dev->devtype->product == CODA_960) + coda_set_gdi_regs(ctx); + + /* + * Copy headers in front of the first frame and forced I frames for + * H.264 only. In MPEG4 they are already copied by the CODA. + */ + if (src_buf->sequence == 0 || force_ipicture) { + pic_stream_buffer_addr = + vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0) + + ctx->vpu_header_size[0] + + ctx->vpu_header_size[1] + + ctx->vpu_header_size[2]; + pic_stream_buffer_size = q_data_dst->sizeimage - + ctx->vpu_header_size[0] - + ctx->vpu_header_size[1] - + ctx->vpu_header_size[2]; + memcpy(vb2_plane_vaddr(&dst_buf->vb2_buf, 0), + &ctx->vpu_header[0][0], ctx->vpu_header_size[0]); + memcpy(vb2_plane_vaddr(&dst_buf->vb2_buf, 0) + + ctx->vpu_header_size[0], &ctx->vpu_header[1][0], + ctx->vpu_header_size[1]); + memcpy(vb2_plane_vaddr(&dst_buf->vb2_buf, 0) + + ctx->vpu_header_size[0] + ctx->vpu_header_size[1], + &ctx->vpu_header[2][0], ctx->vpu_header_size[2]); + } else { + pic_stream_buffer_addr = + vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + pic_stream_buffer_size = q_data_dst->sizeimage; + } + + if (force_ipicture) { + switch (dst_fourcc) { + case V4L2_PIX_FMT_H264: + quant_param = ctx->params.h264_intra_qp; + break; + case V4L2_PIX_FMT_MPEG4: + quant_param = ctx->params.mpeg4_intra_qp; + break; + case V4L2_PIX_FMT_JPEG: + quant_param = 30; + break; + default: + v4l2_warn(&ctx->dev->v4l2_dev, + "cannot set intra qp, fmt not supported\n"); + break; + } + } else { + switch (dst_fourcc) { + case V4L2_PIX_FMT_H264: + quant_param = ctx->params.h264_inter_qp; + break; + case V4L2_PIX_FMT_MPEG4: + quant_param = ctx->params.mpeg4_inter_qp; + break; + default: + v4l2_warn(&ctx->dev->v4l2_dev, + "cannot set inter qp, fmt not supported\n"); + break; + } + } + + /* submit */ + if (ctx->params.rot_mode) + rot_mode = CODA_ROT_MIR_ENABLE | ctx->params.rot_mode; + coda_write(dev, rot_mode, CODA_CMD_ENC_PIC_ROT_MODE); + coda_write(dev, quant_param, CODA_CMD_ENC_PIC_QS); + + if (dev->devtype->product == CODA_960) { + coda_write(dev, 4/*FIXME: 0*/, CODA9_CMD_ENC_PIC_SRC_INDEX); + coda_write(dev, q_data_src->bytesperline, + CODA9_CMD_ENC_PIC_SRC_STRIDE); + coda_write(dev, 0, CODA9_CMD_ENC_PIC_SUB_FRAME_SYNC); + + reg = CODA9_CMD_ENC_PIC_SRC_ADDR_Y; + } else { + reg = CODA_CMD_ENC_PIC_SRC_ADDR_Y; + } + coda_write_base(ctx, q_data_src, src_buf, reg); + + coda_write(dev, force_ipicture << 1 & 0x2, + CODA_CMD_ENC_PIC_OPTION); + + coda_write(dev, pic_stream_buffer_addr, CODA_CMD_ENC_PIC_BB_START); + coda_write(dev, pic_stream_buffer_size / 1024, + CODA_CMD_ENC_PIC_BB_SIZE); + + if (!ctx->streamon_out) { + /* After streamoff on the output side, set stream end flag */ + ctx->bit_stream_param |= CODA_BIT_STREAM_END_FLAG; + coda_write(dev, ctx->bit_stream_param, + CODA_REG_BIT_BIT_STREAM_PARAM); + } + + if (dev->devtype->product != CODA_DX6) + coda_write(dev, ctx->iram_info.axi_sram_use, + CODA7_REG_BIT_AXI_SRAM_USE); + + trace_coda_enc_pic_run(ctx, src_buf); + + coda_command_async(ctx, CODA_COMMAND_PIC_RUN); + + return 0; +} + +static char coda_frame_type_char(u32 flags) +{ + return (flags & V4L2_BUF_FLAG_KEYFRAME) ? 'I' : + (flags & V4L2_BUF_FLAG_PFRAME) ? 'P' : + (flags & V4L2_BUF_FLAG_BFRAME) ? 'B' : '?'; +} + +static void coda_finish_encode(struct coda_ctx *ctx) +{ + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct coda_dev *dev = ctx->dev; + u32 wr_ptr, start_ptr; + + if (ctx->aborting) + return; + + /* + * Lock to make sure that an encoder stop command running in parallel + * will either already have marked src_buf as last, or it will wake up + * the capture queue after the buffers are returned. + */ + mutex_lock(&ctx->wakeup_mutex); + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + trace_coda_enc_pic_done(ctx, dst_buf); + + /* Get results from the coda */ + start_ptr = coda_read(dev, CODA_CMD_ENC_PIC_BB_START); + wr_ptr = coda_read(dev, CODA_REG_BIT_WR_PTR(ctx->reg_idx)); + + /* Calculate bytesused field */ + if (dst_buf->sequence == 0 || + src_buf->flags & V4L2_BUF_FLAG_KEYFRAME) { + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, wr_ptr - start_ptr + + ctx->vpu_header_size[0] + + ctx->vpu_header_size[1] + + ctx->vpu_header_size[2]); + } else { + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, wr_ptr - start_ptr); + } + + coda_dbg(1, ctx, "frame size = %u\n", wr_ptr - start_ptr); + + coda_read(dev, CODA_RET_ENC_PIC_SLICE_NUM); + coda_read(dev, CODA_RET_ENC_PIC_FLAG); + + dst_buf->flags &= ~(V4L2_BUF_FLAG_KEYFRAME | + V4L2_BUF_FLAG_PFRAME | + V4L2_BUF_FLAG_LAST); + if (coda_read(dev, CODA_RET_ENC_PIC_TYPE) == 0) + dst_buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + else + dst_buf->flags |= V4L2_BUF_FLAG_PFRAME; + dst_buf->flags |= src_buf->flags & V4L2_BUF_FLAG_LAST; + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, false); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + coda_m2m_buf_done(ctx, dst_buf, VB2_BUF_STATE_DONE); + mutex_unlock(&ctx->wakeup_mutex); + + ctx->gopcounter--; + if (ctx->gopcounter < 0) + ctx->gopcounter = ctx->params.gop_size - 1; + + coda_dbg(1, ctx, "job finished: encoded %c frame (%d)%s\n", + coda_frame_type_char(dst_buf->flags), dst_buf->sequence, + (dst_buf->flags & V4L2_BUF_FLAG_LAST) ? " (last)" : ""); +} + +static void coda_seq_end_work(struct work_struct *work) +{ + struct coda_ctx *ctx = container_of(work, struct coda_ctx, seq_end_work); + struct coda_dev *dev = ctx->dev; + + mutex_lock(&ctx->buffer_mutex); + mutex_lock(&dev->coda_mutex); + + if (ctx->initialized == 0) + goto out; + + coda_dbg(1, ctx, "%s: sent command 'SEQ_END' to coda\n", __func__); + if (coda_command_sync(ctx, CODA_COMMAND_SEQ_END)) { + v4l2_err(&dev->v4l2_dev, + "CODA_COMMAND_SEQ_END failed\n"); + } + + /* + * FIXME: Sometimes h.264 encoding fails with 8-byte sequences missing + * from the output stream after the h.264 decoder has run. Resetting the + * hardware after the decoder has finished seems to help. + */ + if (dev->devtype->product == CODA_960) + coda_hw_reset(ctx); + + kfifo_init(&ctx->bitstream_fifo, + ctx->bitstream.vaddr, ctx->bitstream.size); + + coda_free_framebuffers(ctx); + + ctx->initialized = 0; + +out: + mutex_unlock(&dev->coda_mutex); + mutex_unlock(&ctx->buffer_mutex); +} + +static void coda_bit_release(struct coda_ctx *ctx) +{ + mutex_lock(&ctx->buffer_mutex); + coda_free_framebuffers(ctx); + coda_free_context_buffers(ctx); + coda_free_bitstream_buffer(ctx); + mutex_unlock(&ctx->buffer_mutex); +} + +const struct coda_context_ops coda_bit_encode_ops = { + .queue_init = coda_encoder_queue_init, + .reqbufs = coda_encoder_reqbufs, + .start_streaming = coda_start_encoding, + .prepare_run = coda_prepare_encode, + .finish_run = coda_finish_encode, + .seq_end_work = coda_seq_end_work, + .release = coda_bit_release, +}; + +/* + * Decoder context operations + */ + +static int coda_alloc_bitstream_buffer(struct coda_ctx *ctx, + struct coda_q_data *q_data) +{ + if (ctx->bitstream.vaddr) + return 0; + + ctx->bitstream.size = roundup_pow_of_two(q_data->sizeimage * 2); + ctx->bitstream.vaddr = dma_alloc_wc(ctx->dev->dev, ctx->bitstream.size, + &ctx->bitstream.paddr, GFP_KERNEL); + if (!ctx->bitstream.vaddr) { + v4l2_err(&ctx->dev->v4l2_dev, + "failed to allocate bitstream ringbuffer"); + return -ENOMEM; + } + kfifo_init(&ctx->bitstream_fifo, + ctx->bitstream.vaddr, ctx->bitstream.size); + + return 0; +} + +static void coda_free_bitstream_buffer(struct coda_ctx *ctx) +{ + if (ctx->bitstream.vaddr == NULL) + return; + + dma_free_wc(ctx->dev->dev, ctx->bitstream.size, ctx->bitstream.vaddr, + ctx->bitstream.paddr); + ctx->bitstream.vaddr = NULL; + kfifo_init(&ctx->bitstream_fifo, NULL, 0); +} + +static int coda_decoder_reqbufs(struct coda_ctx *ctx, + struct v4l2_requestbuffers *rb) +{ + struct coda_q_data *q_data_src; + int ret; + + if (rb->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return 0; + + if (rb->count) { + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + ret = coda_alloc_context_buffers(ctx, q_data_src); + if (ret < 0) + return ret; + ret = coda_alloc_bitstream_buffer(ctx, q_data_src); + if (ret < 0) { + coda_free_context_buffers(ctx); + return ret; + } + } else { + coda_free_bitstream_buffer(ctx); + coda_free_context_buffers(ctx); + } + + return 0; +} + +static bool coda_reorder_enable(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + int profile; + + if (dev->devtype->product != CODA_HX4 && + dev->devtype->product != CODA_7541 && + dev->devtype->product != CODA_960) + return false; + + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_JPEG) + return false; + + if (ctx->codec->src_fourcc != V4L2_PIX_FMT_H264) + return true; + + profile = coda_h264_profile(ctx->params.h264_profile_idc); + if (profile < 0) + v4l2_warn(&dev->v4l2_dev, "Unknown H264 Profile: %u\n", + ctx->params.h264_profile_idc); + + /* Baseline profile does not support reordering */ + return profile > V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; +} + +static void coda_decoder_drop_used_metas(struct coda_ctx *ctx) +{ + struct coda_buffer_meta *meta, *tmp; + + /* + * All metas that end at or before the RD pointer (fifo out), + * are now consumed by the VPU and should be released. + */ + spin_lock(&ctx->buffer_meta_lock); + list_for_each_entry_safe(meta, tmp, &ctx->buffer_meta_list, list) { + if (ctx->bitstream_fifo.kfifo.out >= meta->end) { + coda_dbg(2, ctx, "releasing meta: seq=%d start=%d end=%d\n", + meta->sequence, meta->start, meta->end); + + list_del(&meta->list); + ctx->num_metas--; + ctx->first_frame_sequence++; + kfree(meta); + } + } + spin_unlock(&ctx->buffer_meta_lock); +} + +static int __coda_decoder_seq_init(struct coda_ctx *ctx) +{ + struct coda_q_data *q_data_src, *q_data_dst; + u32 bitstream_buf, bitstream_size; + struct coda_dev *dev = ctx->dev; + int width, height; + u32 src_fourcc, dst_fourcc; + u32 val; + int ret; + + lockdep_assert_held(&dev->coda_mutex); + + coda_dbg(1, ctx, "Video Data Order Adapter: %s\n", + ctx->use_vdoa ? "Enabled" : "Disabled"); + + /* Start decoding */ + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + bitstream_buf = ctx->bitstream.paddr; + bitstream_size = ctx->bitstream.size; + src_fourcc = q_data_src->fourcc; + dst_fourcc = q_data_dst->fourcc; + + /* Update coda bitstream read and write pointers from kfifo */ + coda_kfifo_sync_to_device_full(ctx); + + ctx->frame_mem_ctrl &= ~(CODA_FRAME_CHROMA_INTERLEAVE | (0x3 << 9) | + CODA9_FRAME_TILED2LINEAR); + if (dst_fourcc == V4L2_PIX_FMT_NV12 || dst_fourcc == V4L2_PIX_FMT_YUYV) + ctx->frame_mem_ctrl |= CODA_FRAME_CHROMA_INTERLEAVE; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) + ctx->frame_mem_ctrl |= (0x3 << 9) | + ((ctx->use_vdoa) ? 0 : CODA9_FRAME_TILED2LINEAR); + coda_write(dev, ctx->frame_mem_ctrl, CODA_REG_BIT_FRAME_MEM_CTRL); + + ctx->display_idx = -1; + ctx->frm_dis_flg = 0; + coda_write(dev, 0, CODA_REG_BIT_FRM_DIS_FLG(ctx->reg_idx)); + + coda_write(dev, bitstream_buf, CODA_CMD_DEC_SEQ_BB_START); + coda_write(dev, bitstream_size / 1024, CODA_CMD_DEC_SEQ_BB_SIZE); + val = 0; + if (coda_reorder_enable(ctx)) + val |= CODA_REORDER_ENABLE; + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_JPEG) + val |= CODA_NO_INT_ENABLE; + coda_write(dev, val, CODA_CMD_DEC_SEQ_OPTION); + + ctx->params.codec_mode = ctx->codec->mode; + if (dev->devtype->product == CODA_960 && + src_fourcc == V4L2_PIX_FMT_MPEG4) + ctx->params.codec_mode_aux = CODA_MP4_AUX_MPEG4; + else + ctx->params.codec_mode_aux = 0; + if (src_fourcc == V4L2_PIX_FMT_MPEG4) { + coda_write(dev, CODA_MP4_CLASS_MPEG4, + CODA_CMD_DEC_SEQ_MP4_ASP_CLASS); + } + if (src_fourcc == V4L2_PIX_FMT_H264) { + if (dev->devtype->product == CODA_HX4 || + dev->devtype->product == CODA_7541) { + coda_write(dev, ctx->psbuf.paddr, + CODA_CMD_DEC_SEQ_PS_BB_START); + coda_write(dev, (CODA7_PS_BUF_SIZE / 1024), + CODA_CMD_DEC_SEQ_PS_BB_SIZE); + } + if (dev->devtype->product == CODA_960) { + coda_write(dev, 0, CODA_CMD_DEC_SEQ_X264_MV_EN); + coda_write(dev, 512, CODA_CMD_DEC_SEQ_SPP_CHUNK_SIZE); + } + } + if (src_fourcc == V4L2_PIX_FMT_JPEG) + coda_write(dev, 0, CODA_CMD_DEC_SEQ_JPG_THUMB_EN); + if (dev->devtype->product != CODA_960) + coda_write(dev, 0, CODA_CMD_DEC_SEQ_SRC_SIZE); + + ctx->bit_stream_param = CODA_BIT_DEC_SEQ_INIT_ESCAPE; + ret = coda_command_sync(ctx, CODA_COMMAND_SEQ_INIT); + ctx->bit_stream_param = 0; + if (ret) { + v4l2_err(&dev->v4l2_dev, "CODA_COMMAND_SEQ_INIT timeout\n"); + return ret; + } + ctx->sequence_offset = ~0U; + ctx->initialized = 1; + ctx->first_frame_sequence = 0; + + /* Update kfifo out pointer from coda bitstream read pointer */ + coda_kfifo_sync_from_device(ctx); + + /* + * After updating the read pointer, we need to check if + * any metas are consumed and should be released. + */ + coda_decoder_drop_used_metas(ctx); + + if (coda_read(dev, CODA_RET_DEC_SEQ_SUCCESS) == 0) { + v4l2_err(&dev->v4l2_dev, + "CODA_COMMAND_SEQ_INIT failed, error code = 0x%x\n", + coda_read(dev, CODA_RET_DEC_SEQ_ERR_REASON)); + return -EAGAIN; + } + + val = coda_read(dev, CODA_RET_DEC_SEQ_SRC_SIZE); + if (dev->devtype->product == CODA_DX6) { + width = (val >> CODADX6_PICWIDTH_OFFSET) & CODADX6_PICWIDTH_MASK; + height = val & CODADX6_PICHEIGHT_MASK; + } else { + width = (val >> CODA7_PICWIDTH_OFFSET) & CODA7_PICWIDTH_MASK; + height = val & CODA7_PICHEIGHT_MASK; + } + + if (width > q_data_dst->bytesperline || height > q_data_dst->height) { + v4l2_err(&dev->v4l2_dev, "stream is %dx%d, not %dx%d\n", + width, height, q_data_dst->bytesperline, + q_data_dst->height); + return -EINVAL; + } + + width = round_up(width, 16); + height = round_up(height, 16); + + coda_dbg(1, ctx, "start decoding: %dx%d\n", width, height); + + ctx->num_internal_frames = coda_read(dev, CODA_RET_DEC_SEQ_FRAME_NEED); + /* + * If the VDOA is used, the decoder needs one additional frame, + * because the frames are freed when the next frame is decoded. + * Otherwise there are visible errors in the decoded frames (green + * regions in displayed frames) and a broken order of frames (earlier + * frames are sporadically displayed after later frames). + */ + if (ctx->use_vdoa) + ctx->num_internal_frames += 1; + if (ctx->num_internal_frames > CODA_MAX_FRAMEBUFFERS) { + v4l2_err(&dev->v4l2_dev, + "not enough framebuffers to decode (%d < %d)\n", + CODA_MAX_FRAMEBUFFERS, ctx->num_internal_frames); + return -EINVAL; + } + + if (src_fourcc == V4L2_PIX_FMT_H264) { + u32 left_right; + u32 top_bottom; + + left_right = coda_read(dev, CODA_RET_DEC_SEQ_CROP_LEFT_RIGHT); + top_bottom = coda_read(dev, CODA_RET_DEC_SEQ_CROP_TOP_BOTTOM); + + q_data_dst->rect.left = (left_right >> 10) & 0x3ff; + q_data_dst->rect.top = (top_bottom >> 10) & 0x3ff; + q_data_dst->rect.width = width - q_data_dst->rect.left - + (left_right & 0x3ff); + q_data_dst->rect.height = height - q_data_dst->rect.top - + (top_bottom & 0x3ff); + } + + if (dev->devtype->product != CODA_DX6) { + u8 profile, level; + + val = coda_read(dev, CODA7_RET_DEC_SEQ_HEADER_REPORT); + profile = val & 0xff; + level = (val >> 8) & 0x7f; + + if (profile || level) + coda_update_profile_level_ctrls(ctx, profile, level); + } + + return 0; +} + +static void coda_dec_seq_init_work(struct work_struct *work) +{ + struct coda_ctx *ctx = container_of(work, + struct coda_ctx, seq_init_work); + struct coda_dev *dev = ctx->dev; + + mutex_lock(&ctx->buffer_mutex); + mutex_lock(&dev->coda_mutex); + + if (!ctx->initialized) + __coda_decoder_seq_init(ctx); + + mutex_unlock(&dev->coda_mutex); + mutex_unlock(&ctx->buffer_mutex); +} + +static int __coda_start_decoding(struct coda_ctx *ctx) +{ + struct coda_q_data *q_data_src, *q_data_dst; + struct coda_dev *dev = ctx->dev; + u32 src_fourcc, dst_fourcc; + int ret; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + src_fourcc = q_data_src->fourcc; + dst_fourcc = q_data_dst->fourcc; + + if (!ctx->initialized) { + ret = __coda_decoder_seq_init(ctx); + if (ret < 0) + return ret; + } else { + ctx->frame_mem_ctrl &= ~(CODA_FRAME_CHROMA_INTERLEAVE | (0x3 << 9) | + CODA9_FRAME_TILED2LINEAR); + if (dst_fourcc == V4L2_PIX_FMT_NV12 || dst_fourcc == V4L2_PIX_FMT_YUYV) + ctx->frame_mem_ctrl |= CODA_FRAME_CHROMA_INTERLEAVE; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) + ctx->frame_mem_ctrl |= (0x3 << 9) | + ((ctx->use_vdoa) ? 0 : CODA9_FRAME_TILED2LINEAR); + } + + coda_write(dev, ctx->parabuf.paddr, CODA_REG_BIT_PARA_BUF_ADDR); + + ret = coda_alloc_framebuffers(ctx, q_data_dst, src_fourcc); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to allocate framebuffers\n"); + return ret; + } + + /* Tell the decoder how many frame buffers we allocated. */ + coda_write(dev, ctx->num_internal_frames, CODA_CMD_SET_FRAME_BUF_NUM); + coda_write(dev, round_up(q_data_dst->rect.width, 16), + CODA_CMD_SET_FRAME_BUF_STRIDE); + + if (dev->devtype->product != CODA_DX6) { + /* Set secondary AXI IRAM */ + coda_setup_iram(ctx); + + coda_write(dev, ctx->iram_info.buf_bit_use, + CODA7_CMD_SET_FRAME_AXI_BIT_ADDR); + coda_write(dev, ctx->iram_info.buf_ip_ac_dc_use, + CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR); + coda_write(dev, ctx->iram_info.buf_dbk_y_use, + CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR); + coda_write(dev, ctx->iram_info.buf_dbk_c_use, + CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR); + coda_write(dev, ctx->iram_info.buf_ovl_use, + CODA7_CMD_SET_FRAME_AXI_OVL_ADDR); + if (dev->devtype->product == CODA_960) { + coda_write(dev, ctx->iram_info.buf_btp_use, + CODA9_CMD_SET_FRAME_AXI_BTP_ADDR); + + coda_write(dev, -1, CODA9_CMD_SET_FRAME_DELAY); + coda9_set_frame_cache(ctx, dst_fourcc); + } + } + + if (src_fourcc == V4L2_PIX_FMT_H264) { + coda_write(dev, ctx->slicebuf.paddr, + CODA_CMD_SET_FRAME_SLICE_BB_START); + coda_write(dev, ctx->slicebuf.size / 1024, + CODA_CMD_SET_FRAME_SLICE_BB_SIZE); + } + + if (dev->devtype->product == CODA_HX4 || + dev->devtype->product == CODA_7541) { + int max_mb_x = 1920 / 16; + int max_mb_y = 1088 / 16; + int max_mb_num = max_mb_x * max_mb_y; + + coda_write(dev, max_mb_num << 16 | max_mb_x << 8 | max_mb_y, + CODA7_CMD_SET_FRAME_MAX_DEC_SIZE); + } else if (dev->devtype->product == CODA_960) { + int max_mb_x = 1920 / 16; + int max_mb_y = 1088 / 16; + int max_mb_num = max_mb_x * max_mb_y; + + coda_write(dev, max_mb_num << 16 | max_mb_x << 8 | max_mb_y, + CODA9_CMD_SET_FRAME_MAX_DEC_SIZE); + } + + if (coda_command_sync(ctx, CODA_COMMAND_SET_FRAME_BUF)) { + v4l2_err(&ctx->dev->v4l2_dev, + "CODA_COMMAND_SET_FRAME_BUF timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static int coda_start_decoding(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + int ret; + + mutex_lock(&dev->coda_mutex); + ret = __coda_start_decoding(ctx); + mutex_unlock(&dev->coda_mutex); + + return ret; +} + +static int coda_prepare_decode(struct coda_ctx *ctx) +{ + struct vb2_v4l2_buffer *dst_buf; + struct coda_dev *dev = ctx->dev; + struct coda_q_data *q_data_dst; + struct coda_buffer_meta *meta; + u32 rot_mode = 0; + u32 reg_addr, reg_stride; + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + /* Try to copy source buffer contents into the bitstream ringbuffer */ + mutex_lock(&ctx->bitstream_mutex); + coda_fill_bitstream(ctx, NULL); + mutex_unlock(&ctx->bitstream_mutex); + + if (coda_get_bitstream_payload(ctx) < 512 && + (!(ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG))) { + coda_dbg(1, ctx, "bitstream payload: %d, skipping\n", + coda_get_bitstream_payload(ctx)); + return -EAGAIN; + } + + /* Run coda_start_decoding (again) if not yet initialized */ + if (!ctx->initialized) { + int ret = __coda_start_decoding(ctx); + + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to start decoding\n"); + return -EAGAIN; + } else { + ctx->initialized = 1; + } + } + + if (dev->devtype->product == CODA_960) + coda_set_gdi_regs(ctx); + + if (ctx->use_vdoa && + ctx->display_idx >= 0 && + ctx->display_idx < ctx->num_internal_frames) { + vdoa_device_run(ctx->vdoa, + vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0), + ctx->internal_frames[ctx->display_idx].buf.paddr); + } else { + if (dev->devtype->product == CODA_960) { + /* + * It was previously assumed that the CODA960 has an + * internal list of 64 buffer entries that contains + * both the registered internal frame buffers as well + * as the rotator buffer output, and that the ROT_INDEX + * register must be set to a value between the last + * internal frame buffers' index and 64. + * At least on firmware version 3.1.1 it turns out that + * setting ROT_INDEX to any value >= 32 causes CODA + * hangups that it can not recover from with the SRC VPU + * reset. + * It does appear to work however, to just set it to a + * fixed value in the [ctx->num_internal_frames, 31] + * range, for example CODA_MAX_FRAMEBUFFERS. + */ + coda_write(dev, CODA_MAX_FRAMEBUFFERS, + CODA9_CMD_DEC_PIC_ROT_INDEX); + + reg_addr = CODA9_CMD_DEC_PIC_ROT_ADDR_Y; + reg_stride = CODA9_CMD_DEC_PIC_ROT_STRIDE; + } else { + reg_addr = CODA_CMD_DEC_PIC_ROT_ADDR_Y; + reg_stride = CODA_CMD_DEC_PIC_ROT_STRIDE; + } + coda_write_base(ctx, q_data_dst, dst_buf, reg_addr); + coda_write(dev, q_data_dst->bytesperline, reg_stride); + + rot_mode = CODA_ROT_MIR_ENABLE | ctx->params.rot_mode; + } + + coda_write(dev, rot_mode, CODA_CMD_DEC_PIC_ROT_MODE); + + switch (dev->devtype->product) { + case CODA_DX6: + /* TBD */ + case CODA_HX4: + case CODA_7541: + coda_write(dev, CODA_PRE_SCAN_EN, CODA_CMD_DEC_PIC_OPTION); + break; + case CODA_960: + /* 'hardcode to use interrupt disable mode'? */ + coda_write(dev, (1 << 10), CODA_CMD_DEC_PIC_OPTION); + break; + } + + coda_write(dev, 0, CODA_CMD_DEC_PIC_SKIP_NUM); + + coda_write(dev, 0, CODA_CMD_DEC_PIC_BB_START); + coda_write(dev, 0, CODA_CMD_DEC_PIC_START_BYTE); + + if (dev->devtype->product != CODA_DX6) + coda_write(dev, ctx->iram_info.axi_sram_use, + CODA7_REG_BIT_AXI_SRAM_USE); + + spin_lock(&ctx->buffer_meta_lock); + meta = list_first_entry_or_null(&ctx->buffer_meta_list, + struct coda_buffer_meta, list); + + if (meta && ctx->codec->src_fourcc == V4L2_PIX_FMT_JPEG) { + + /* If this is the last buffer in the bitstream, add padding */ + if (meta->end == ctx->bitstream_fifo.kfifo.in) { + static unsigned char buf[512]; + unsigned int pad; + + /* Pad to multiple of 256 and then add 256 more */ + pad = ((0 - meta->end) & 0xff) + 256; + + memset(buf, 0xff, sizeof(buf)); + + kfifo_in(&ctx->bitstream_fifo, buf, pad); + } + } + spin_unlock(&ctx->buffer_meta_lock); + + coda_kfifo_sync_to_device_full(ctx); + + /* Clear decode success flag */ + coda_write(dev, 0, CODA_RET_DEC_PIC_SUCCESS); + + /* Clear error return value */ + coda_write(dev, 0, CODA_RET_DEC_PIC_ERR_MB); + + trace_coda_dec_pic_run(ctx, meta); + + coda_command_async(ctx, CODA_COMMAND_PIC_RUN); + + return 0; +} + +static void coda_finish_decode(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + struct coda_q_data *q_data_src; + struct coda_q_data *q_data_dst; + struct vb2_v4l2_buffer *dst_buf; + struct coda_buffer_meta *meta; + int width, height; + int decoded_idx; + int display_idx; + struct coda_internal_frame *decoded_frame = NULL; + u32 src_fourcc; + int success; + u32 err_mb; + int err_vdoa = 0; + u32 val; + + if (ctx->aborting) + return; + + /* Update kfifo out pointer from coda bitstream read pointer */ + coda_kfifo_sync_from_device(ctx); + + /* + * in stream-end mode, the read pointer can overshoot the write pointer + * by up to 512 bytes + */ + if (ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG) { + if (coda_get_bitstream_payload(ctx) >= ctx->bitstream.size - 512) + kfifo_init(&ctx->bitstream_fifo, + ctx->bitstream.vaddr, ctx->bitstream.size); + } + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + src_fourcc = q_data_src->fourcc; + + val = coda_read(dev, CODA_RET_DEC_PIC_SUCCESS); + if (val != 1) + pr_err("DEC_PIC_SUCCESS = %d\n", val); + + success = val & 0x1; + if (!success) + v4l2_err(&dev->v4l2_dev, "decode failed\n"); + + if (src_fourcc == V4L2_PIX_FMT_H264) { + if (val & (1 << 3)) + v4l2_err(&dev->v4l2_dev, + "insufficient PS buffer space (%d bytes)\n", + ctx->psbuf.size); + if (val & (1 << 2)) + v4l2_err(&dev->v4l2_dev, + "insufficient slice buffer space (%d bytes)\n", + ctx->slicebuf.size); + } + + val = coda_read(dev, CODA_RET_DEC_PIC_SIZE); + width = (val >> 16) & 0xffff; + height = val & 0xffff; + + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + + /* frame crop information */ + if (src_fourcc == V4L2_PIX_FMT_H264) { + u32 left_right; + u32 top_bottom; + + left_right = coda_read(dev, CODA_RET_DEC_PIC_CROP_LEFT_RIGHT); + top_bottom = coda_read(dev, CODA_RET_DEC_PIC_CROP_TOP_BOTTOM); + + if (left_right == 0xffffffff && top_bottom == 0xffffffff) { + /* Keep current crop information */ + } else { + struct v4l2_rect *rect = &q_data_dst->rect; + + rect->left = left_right >> 16 & 0xffff; + rect->top = top_bottom >> 16 & 0xffff; + rect->width = width - rect->left - + (left_right & 0xffff); + rect->height = height - rect->top - + (top_bottom & 0xffff); + } + } else { + /* no cropping */ + } + + err_mb = coda_read(dev, CODA_RET_DEC_PIC_ERR_MB); + if (err_mb > 0) { + if (__ratelimit(&dev->mb_err_rs)) + coda_dbg(1, ctx, "errors in %d macroblocks\n", err_mb); + v4l2_ctrl_s_ctrl(ctx->mb_err_cnt_ctrl, + v4l2_ctrl_g_ctrl(ctx->mb_err_cnt_ctrl) + err_mb); + } + + if (dev->devtype->product == CODA_HX4 || + dev->devtype->product == CODA_7541) { + val = coda_read(dev, CODA_RET_DEC_PIC_OPTION); + if (val == 0) { + /* not enough bitstream data */ + coda_dbg(1, ctx, "prescan failed: %d\n", val); + ctx->hold = true; + return; + } + } + + /* Wait until the VDOA finished writing the previous display frame */ + if (ctx->use_vdoa && + ctx->display_idx >= 0 && + ctx->display_idx < ctx->num_internal_frames) { + err_vdoa = vdoa_wait_for_completion(ctx->vdoa); + } + + ctx->frm_dis_flg = coda_read(dev, + CODA_REG_BIT_FRM_DIS_FLG(ctx->reg_idx)); + + /* The previous display frame was copied out and can be overwritten */ + if (ctx->display_idx >= 0 && + ctx->display_idx < ctx->num_internal_frames) { + ctx->frm_dis_flg &= ~(1 << ctx->display_idx); + coda_write(dev, ctx->frm_dis_flg, + CODA_REG_BIT_FRM_DIS_FLG(ctx->reg_idx)); + } + + /* + * The index of the last decoded frame, not necessarily in + * display order, and the index of the next display frame. + * The latter could have been decoded in a previous run. + */ + decoded_idx = coda_read(dev, CODA_RET_DEC_PIC_CUR_IDX); + display_idx = coda_read(dev, CODA_RET_DEC_PIC_FRAME_IDX); + + if (decoded_idx == -1) { + /* no frame was decoded, but we might have a display frame */ + if (display_idx >= 0 && display_idx < ctx->num_internal_frames) + ctx->sequence_offset++; + else if (ctx->display_idx < 0) + ctx->hold = true; + } else if (decoded_idx == -2) { + if (ctx->display_idx >= 0 && + ctx->display_idx < ctx->num_internal_frames) + ctx->sequence_offset++; + /* no frame was decoded, we still return remaining buffers */ + } else if (decoded_idx < 0 || decoded_idx >= ctx->num_internal_frames) { + v4l2_err(&dev->v4l2_dev, + "decoded frame index out of range: %d\n", decoded_idx); + } else { + int sequence; + + decoded_frame = &ctx->internal_frames[decoded_idx]; + + val = coda_read(dev, CODA_RET_DEC_PIC_FRAME_NUM); + if (ctx->sequence_offset == -1) + ctx->sequence_offset = val; + + sequence = val + ctx->first_frame_sequence + - ctx->sequence_offset; + spin_lock(&ctx->buffer_meta_lock); + if (!list_empty(&ctx->buffer_meta_list)) { + meta = list_first_entry(&ctx->buffer_meta_list, + struct coda_buffer_meta, list); + list_del(&meta->list); + ctx->num_metas--; + spin_unlock(&ctx->buffer_meta_lock); + /* + * Clamp counters to 16 bits for comparison, as the HW + * counter rolls over at this point for h.264. This + * may be different for other formats, but using 16 bits + * should be enough to detect most errors and saves us + * from doing different things based on the format. + */ + if ((sequence & 0xffff) != (meta->sequence & 0xffff)) { + v4l2_err(&dev->v4l2_dev, + "sequence number mismatch (%d(%d) != %d)\n", + sequence, ctx->sequence_offset, + meta->sequence); + } + decoded_frame->meta = *meta; + kfree(meta); + } else { + spin_unlock(&ctx->buffer_meta_lock); + v4l2_err(&dev->v4l2_dev, "empty timestamp list!\n"); + memset(&decoded_frame->meta, 0, + sizeof(struct coda_buffer_meta)); + decoded_frame->meta.sequence = sequence; + decoded_frame->meta.last = false; + ctx->sequence_offset++; + } + + trace_coda_dec_pic_done(ctx, &decoded_frame->meta); + + val = coda_read(dev, CODA_RET_DEC_PIC_TYPE) & 0x7; + decoded_frame->type = (val == 0) ? V4L2_BUF_FLAG_KEYFRAME : + (val == 1) ? V4L2_BUF_FLAG_PFRAME : + V4L2_BUF_FLAG_BFRAME; + + decoded_frame->error = err_mb; + } + + if (display_idx == -1) { + /* + * no more frames to be decoded, but there could still + * be rotator output to dequeue + */ + ctx->hold = true; + } else if (display_idx == -3) { + /* possibly prescan failure */ + } else if (display_idx < 0 || display_idx >= ctx->num_internal_frames) { + v4l2_err(&dev->v4l2_dev, + "presentation frame index out of range: %d\n", + display_idx); + } + + /* If a frame was copied out, return it */ + if (ctx->display_idx >= 0 && + ctx->display_idx < ctx->num_internal_frames) { + struct coda_internal_frame *ready_frame; + + ready_frame = &ctx->internal_frames[ctx->display_idx]; + + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + dst_buf->sequence = ctx->osequence++; + + dst_buf->field = V4L2_FIELD_NONE; + dst_buf->flags &= ~(V4L2_BUF_FLAG_KEYFRAME | + V4L2_BUF_FLAG_PFRAME | + V4L2_BUF_FLAG_BFRAME); + dst_buf->flags |= ready_frame->type; + meta = &ready_frame->meta; + if (meta->last && !coda_reorder_enable(ctx)) { + /* + * If this was the last decoded frame, and reordering + * is disabled, this will be the last display frame. + */ + coda_dbg(1, ctx, "last meta, marking as last frame\n"); + dst_buf->flags |= V4L2_BUF_FLAG_LAST; + } else if (ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG && + display_idx == -1) { + /* + * If there is no designated presentation frame anymore, + * this frame has to be the last one. + */ + coda_dbg(1, ctx, + "no more frames to return, marking as last frame\n"); + dst_buf->flags |= V4L2_BUF_FLAG_LAST; + } + dst_buf->timecode = meta->timecode; + dst_buf->vb2_buf.timestamp = meta->timestamp; + + trace_coda_dec_rot_done(ctx, dst_buf, meta); + + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, + q_data_dst->sizeimage); + + if (ready_frame->error || err_vdoa) + coda_m2m_buf_done(ctx, dst_buf, VB2_BUF_STATE_ERROR); + else + coda_m2m_buf_done(ctx, dst_buf, VB2_BUF_STATE_DONE); + + if (decoded_frame) { + coda_dbg(1, ctx, "job finished: decoded %c frame %u, returned %c frame %u (%u/%u)%s\n", + coda_frame_type_char(decoded_frame->type), + decoded_frame->meta.sequence, + coda_frame_type_char(dst_buf->flags), + ready_frame->meta.sequence, + dst_buf->sequence, ctx->qsequence, + (dst_buf->flags & V4L2_BUF_FLAG_LAST) ? + " (last)" : ""); + } else { + coda_dbg(1, ctx, "job finished: no frame decoded (%d), returned %c frame %u (%u/%u)%s\n", + decoded_idx, + coda_frame_type_char(dst_buf->flags), + ready_frame->meta.sequence, + dst_buf->sequence, ctx->qsequence, + (dst_buf->flags & V4L2_BUF_FLAG_LAST) ? + " (last)" : ""); + } + } else { + if (decoded_frame) { + coda_dbg(1, ctx, "job finished: decoded %c frame %u, no frame returned (%d)\n", + coda_frame_type_char(decoded_frame->type), + decoded_frame->meta.sequence, + ctx->display_idx); + } else { + coda_dbg(1, ctx, "job finished: no frame decoded (%d) or returned (%d)\n", + decoded_idx, ctx->display_idx); + } + } + + /* The rotator will copy the current display frame next time */ + ctx->display_idx = display_idx; + + /* + * The current decode run might have brought the bitstream fill level + * below the size where we can start the next decode run. As userspace + * might have filled the output queue completely and might thus be + * blocked, we can't rely on the next qbuf to trigger the bitstream + * refill. Check if we have data to refill the bitstream now. + */ + mutex_lock(&ctx->bitstream_mutex); + coda_fill_bitstream(ctx, NULL); + mutex_unlock(&ctx->bitstream_mutex); +} + +static void coda_decode_timeout(struct coda_ctx *ctx) +{ + struct vb2_v4l2_buffer *dst_buf; + + /* + * For now this only handles the case where we would deadlock with + * userspace, i.e. userspace issued DEC_CMD_STOP and waits for EOS, + * but after a failed decode run we would hold the context and wait for + * userspace to queue more buffers. + */ + if (!(ctx->bit_stream_param & CODA_BIT_STREAM_END_FLAG)) + return; + + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + dst_buf->sequence = ctx->qsequence - 1; + + coda_m2m_buf_done(ctx, dst_buf, VB2_BUF_STATE_ERROR); +} + +const struct coda_context_ops coda_bit_decode_ops = { + .queue_init = coda_decoder_queue_init, + .reqbufs = coda_decoder_reqbufs, + .start_streaming = coda_start_decoding, + .prepare_run = coda_prepare_decode, + .finish_run = coda_finish_decode, + .run_timeout = coda_decode_timeout, + .seq_init_work = coda_dec_seq_init_work, + .seq_end_work = coda_seq_end_work, + .release = coda_bit_release, +}; + +irqreturn_t coda_irq_handler(int irq, void *data) +{ + struct coda_dev *dev = data; + struct coda_ctx *ctx; + + /* read status register to attend the IRQ */ + coda_read(dev, CODA_REG_BIT_INT_STATUS); + coda_write(dev, 0, CODA_REG_BIT_INT_REASON); + coda_write(dev, CODA_REG_BIT_INT_CLEAR_SET, + CODA_REG_BIT_INT_CLEAR); + + ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + if (ctx == NULL) { + v4l2_err(&dev->v4l2_dev, + "Instance released before the end of transaction\n"); + return IRQ_HANDLED; + } + + trace_coda_bit_done(ctx); + + if (ctx->aborting) { + coda_dbg(1, ctx, "task has been aborted\n"); + } + + if (coda_isbusy(ctx->dev)) { + coda_dbg(1, ctx, "coda is still busy!!!!\n"); + return IRQ_NONE; + } + + complete(&ctx->completion); + + return IRQ_HANDLED; +} diff --git a/drivers/media/platform/chips-media/coda-common.c b/drivers/media/platform/chips-media/coda-common.c new file mode 100644 index 000000000..af71eea04 --- /dev/null +++ b/drivers/media/platform/chips-media/coda-common.c @@ -0,0 +1,3362 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Coda multi-standard codec IP + * + * Copyright (C) 2012 Vista Silicon S.L. + * Javier Martin, + * Xavier Duret + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "coda.h" +#include "imx-vdoa.h" + +#define CODA_NAME "coda" + +#define CODADX6_MAX_INSTANCES 4 +#define CODA_MAX_FORMATS 5 + +#define CODA_ISRAM_SIZE (2048 * 2) + +#define MIN_W 48 +#define MIN_H 16 + +#define S_ALIGN 1 /* multiple of 2 */ +#define W_ALIGN 1 /* multiple of 2 */ +#define H_ALIGN 1 /* multiple of 2 */ + +#define fh_to_ctx(__fh) container_of(__fh, struct coda_ctx, fh) + +int coda_debug; +module_param(coda_debug, int, 0644); +MODULE_PARM_DESC(coda_debug, "Debug level (0-2)"); + +static int disable_tiling; +module_param(disable_tiling, int, 0644); +MODULE_PARM_DESC(disable_tiling, "Disable tiled frame buffers"); + +static int disable_vdoa; +module_param(disable_vdoa, int, 0644); +MODULE_PARM_DESC(disable_vdoa, "Disable Video Data Order Adapter tiled to raster-scan conversion"); + +static int enable_bwb = 0; +module_param(enable_bwb, int, 0644); +MODULE_PARM_DESC(enable_bwb, "Enable BWB unit for decoding, may crash on certain streams"); + +void coda_write(struct coda_dev *dev, u32 data, u32 reg) +{ + v4l2_dbg(3, coda_debug, &dev->v4l2_dev, + "%s: data=0x%x, reg=0x%x\n", __func__, data, reg); + writel(data, dev->regs_base + reg); +} + +unsigned int coda_read(struct coda_dev *dev, u32 reg) +{ + u32 data; + + data = readl(dev->regs_base + reg); + v4l2_dbg(3, coda_debug, &dev->v4l2_dev, + "%s: data=0x%x, reg=0x%x\n", __func__, data, reg); + return data; +} + +void coda_write_base(struct coda_ctx *ctx, struct coda_q_data *q_data, + struct vb2_v4l2_buffer *buf, unsigned int reg_y) +{ + u32 base_y = vb2_dma_contig_plane_dma_addr(&buf->vb2_buf, 0); + u32 base_cb, base_cr; + + switch (q_data->fourcc) { + case V4L2_PIX_FMT_YUYV: + /* Fallthrough: IN -H264-> CODA -NV12 MB-> VDOA -YUYV-> OUT */ + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YUV420: + default: + base_cb = base_y + q_data->bytesperline * q_data->height; + base_cr = base_cb + q_data->bytesperline * q_data->height / 4; + break; + case V4L2_PIX_FMT_YVU420: + /* Switch Cb and Cr for YVU420 format */ + base_cr = base_y + q_data->bytesperline * q_data->height; + base_cb = base_cr + q_data->bytesperline * q_data->height / 4; + break; + case V4L2_PIX_FMT_YUV422P: + base_cb = base_y + q_data->bytesperline * q_data->height; + base_cr = base_cb + q_data->bytesperline * q_data->height / 2; + } + + coda_write(ctx->dev, base_y, reg_y); + coda_write(ctx->dev, base_cb, reg_y + 4); + coda_write(ctx->dev, base_cr, reg_y + 8); +} + +#define CODA_CODEC(mode, src_fourcc, dst_fourcc, max_w, max_h) \ + { mode, src_fourcc, dst_fourcc, max_w, max_h } + +/* + * Arrays of codecs supported by each given version of Coda: + * i.MX27 -> codadx6 + * i.MX51 -> codahx4 + * i.MX53 -> coda7 + * i.MX6 -> coda960 + * Use V4L2_PIX_FMT_YUV420 as placeholder for all supported YUV 4:2:0 variants + */ +static const struct coda_codec codadx6_codecs[] = { + CODA_CODEC(CODADX6_MODE_ENCODE_H264, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_H264, 720, 576), + CODA_CODEC(CODADX6_MODE_ENCODE_MP4, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_MPEG4, 720, 576), +}; + +static const struct coda_codec codahx4_codecs[] = { + CODA_CODEC(CODA7_MODE_ENCODE_H264, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_H264, 720, 576), + CODA_CODEC(CODA7_MODE_DECODE_H264, V4L2_PIX_FMT_H264, V4L2_PIX_FMT_YUV420, 1920, 1088), + CODA_CODEC(CODA7_MODE_DECODE_MP2, V4L2_PIX_FMT_MPEG2, V4L2_PIX_FMT_YUV420, 1920, 1088), + CODA_CODEC(CODA7_MODE_DECODE_MP4, V4L2_PIX_FMT_MPEG4, V4L2_PIX_FMT_YUV420, 1280, 720), +}; + +static const struct coda_codec coda7_codecs[] = { + CODA_CODEC(CODA7_MODE_ENCODE_H264, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_H264, 1280, 720), + CODA_CODEC(CODA7_MODE_ENCODE_MP4, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_MPEG4, 1280, 720), + CODA_CODEC(CODA7_MODE_ENCODE_MJPG, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_JPEG, 8192, 8192), + CODA_CODEC(CODA7_MODE_DECODE_H264, V4L2_PIX_FMT_H264, V4L2_PIX_FMT_YUV420, 1920, 1088), + CODA_CODEC(CODA7_MODE_DECODE_MP2, V4L2_PIX_FMT_MPEG2, V4L2_PIX_FMT_YUV420, 1920, 1088), + CODA_CODEC(CODA7_MODE_DECODE_MP4, V4L2_PIX_FMT_MPEG4, V4L2_PIX_FMT_YUV420, 1920, 1088), + CODA_CODEC(CODA7_MODE_DECODE_MJPG, V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUV420, 8192, 8192), +}; + +static const struct coda_codec coda9_codecs[] = { + CODA_CODEC(CODA9_MODE_ENCODE_H264, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_H264, 1920, 1088), + CODA_CODEC(CODA9_MODE_ENCODE_MP4, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_MPEG4, 1920, 1088), + CODA_CODEC(CODA9_MODE_ENCODE_MJPG, V4L2_PIX_FMT_YUV420, V4L2_PIX_FMT_JPEG, 8192, 8192), + CODA_CODEC(CODA9_MODE_DECODE_H264, V4L2_PIX_FMT_H264, V4L2_PIX_FMT_YUV420, 1920, 1088), + CODA_CODEC(CODA9_MODE_DECODE_MP2, V4L2_PIX_FMT_MPEG2, V4L2_PIX_FMT_YUV420, 1920, 1088), + CODA_CODEC(CODA9_MODE_DECODE_MP4, V4L2_PIX_FMT_MPEG4, V4L2_PIX_FMT_YUV420, 1920, 1088), + CODA_CODEC(CODA9_MODE_DECODE_MJPG, V4L2_PIX_FMT_JPEG, V4L2_PIX_FMT_YUV420, 8192, 8192), +}; + +struct coda_video_device { + const char *name; + enum coda_inst_type type; + const struct coda_context_ops *ops; + bool direct; + u32 src_formats[CODA_MAX_FORMATS]; + u32 dst_formats[CODA_MAX_FORMATS]; +}; + +static const struct coda_video_device coda_bit_encoder = { + .name = "coda-video-encoder", + .type = CODA_INST_ENCODER, + .ops = &coda_bit_encode_ops, + .src_formats = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + }, + .dst_formats = { + V4L2_PIX_FMT_H264, + V4L2_PIX_FMT_MPEG4, + }, +}; + +static const struct coda_video_device coda_bit_jpeg_encoder = { + .name = "coda-jpeg-encoder", + .type = CODA_INST_ENCODER, + .ops = &coda_bit_encode_ops, + .src_formats = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_YUV422P, + }, + .dst_formats = { + V4L2_PIX_FMT_JPEG, + }, +}; + +static const struct coda_video_device coda_bit_decoder = { + .name = "coda-video-decoder", + .type = CODA_INST_DECODER, + .ops = &coda_bit_decode_ops, + .src_formats = { + V4L2_PIX_FMT_H264, + V4L2_PIX_FMT_MPEG2, + V4L2_PIX_FMT_MPEG4, + }, + .dst_formats = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + /* + * If V4L2_PIX_FMT_YUYV should be default, + * set_default_params() must be adjusted. + */ + V4L2_PIX_FMT_YUYV, + }, +}; + +static const struct coda_video_device coda_bit_jpeg_decoder = { + .name = "coda-jpeg-decoder", + .type = CODA_INST_DECODER, + .ops = &coda_bit_decode_ops, + .src_formats = { + V4L2_PIX_FMT_JPEG, + }, + .dst_formats = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_YUV422P, + }, +}; + +static const struct coda_video_device coda9_jpeg_encoder = { + .name = "coda-jpeg-encoder", + .type = CODA_INST_ENCODER, + .ops = &coda9_jpeg_encode_ops, + .direct = true, + .src_formats = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_YUV422P, + V4L2_PIX_FMT_GREY, + }, + .dst_formats = { + V4L2_PIX_FMT_JPEG, + }, +}; + +static const struct coda_video_device coda9_jpeg_decoder = { + .name = "coda-jpeg-decoder", + .type = CODA_INST_DECODER, + .ops = &coda9_jpeg_decode_ops, + .direct = true, + .src_formats = { + V4L2_PIX_FMT_JPEG, + }, + .dst_formats = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_YUV422P, + }, +}; + +static const struct coda_video_device *codadx6_video_devices[] = { + &coda_bit_encoder, +}; + +static const struct coda_video_device *codahx4_video_devices[] = { + &coda_bit_encoder, + &coda_bit_decoder, +}; + +static const struct coda_video_device *coda7_video_devices[] = { + &coda_bit_jpeg_encoder, + &coda_bit_jpeg_decoder, + &coda_bit_encoder, + &coda_bit_decoder, +}; + +static const struct coda_video_device *coda9_video_devices[] = { + &coda9_jpeg_encoder, + &coda9_jpeg_decoder, + &coda_bit_encoder, + &coda_bit_decoder, +}; + +/* + * Normalize all supported YUV 4:2:0 formats to the value used in the codec + * tables. + */ +static u32 coda_format_normalize_yuv(u32 fourcc) +{ + switch (fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YUYV: + return V4L2_PIX_FMT_YUV420; + default: + return fourcc; + } +} + +static const struct coda_codec *coda_find_codec(struct coda_dev *dev, + int src_fourcc, int dst_fourcc) +{ + const struct coda_codec *codecs = dev->devtype->codecs; + int num_codecs = dev->devtype->num_codecs; + int k; + + src_fourcc = coda_format_normalize_yuv(src_fourcc); + dst_fourcc = coda_format_normalize_yuv(dst_fourcc); + if (src_fourcc == dst_fourcc) + return NULL; + + for (k = 0; k < num_codecs; k++) { + if (codecs[k].src_fourcc == src_fourcc && + codecs[k].dst_fourcc == dst_fourcc) + break; + } + + if (k == num_codecs) + return NULL; + + return &codecs[k]; +} + +static void coda_get_max_dimensions(struct coda_dev *dev, + const struct coda_codec *codec, + int *max_w, int *max_h) +{ + const struct coda_codec *codecs = dev->devtype->codecs; + int num_codecs = dev->devtype->num_codecs; + unsigned int w, h; + int k; + + if (codec) { + w = codec->max_w; + h = codec->max_h; + } else { + for (k = 0, w = 0, h = 0; k < num_codecs; k++) { + w = max(w, codecs[k].max_w); + h = max(h, codecs[k].max_h); + } + } + + if (max_w) + *max_w = w; + if (max_h) + *max_h = h; +} + +static const struct coda_video_device *to_coda_video_device(struct video_device + *vdev) +{ + struct coda_dev *dev = video_get_drvdata(vdev); + unsigned int i = vdev - dev->vfd; + + if (i >= dev->devtype->num_vdevs) + return NULL; + + return dev->devtype->vdevs[i]; +} + +const char *coda_product_name(int product) +{ + static char buf[9]; + + switch (product) { + case CODA_DX6: + return "CodaDx6"; + case CODA_HX4: + return "CodaHx4"; + case CODA_7541: + return "CODA7541"; + case CODA_960: + return "CODA960"; + default: + snprintf(buf, sizeof(buf), "(0x%04x)", product); + return buf; + } +} + +static struct vdoa_data *coda_get_vdoa_data(void) +{ + struct device_node *vdoa_node; + struct platform_device *vdoa_pdev; + struct vdoa_data *vdoa_data = NULL; + + vdoa_node = of_find_compatible_node(NULL, NULL, "fsl,imx6q-vdoa"); + if (!vdoa_node) + return NULL; + + vdoa_pdev = of_find_device_by_node(vdoa_node); + if (!vdoa_pdev) + goto out; + + vdoa_data = platform_get_drvdata(vdoa_pdev); + if (!vdoa_data) + vdoa_data = ERR_PTR(-EPROBE_DEFER); + + put_device(&vdoa_pdev->dev); +out: + of_node_put(vdoa_node); + + return vdoa_data; +} + +/* + * V4L2 ioctl() operations. + */ +static int coda_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + + strscpy(cap->driver, CODA_NAME, sizeof(cap->driver)); + strscpy(cap->card, coda_product_name(ctx->dev->devtype->product), + sizeof(cap->card)); + strscpy(cap->bus_info, "platform:" CODA_NAME, sizeof(cap->bus_info)); + return 0; +} + +static const u32 coda_formats_420[CODA_MAX_FORMATS] = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, +}; + +static int coda_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct video_device *vdev = video_devdata(file); + const struct coda_video_device *cvd = to_coda_video_device(vdev); + struct coda_ctx *ctx = fh_to_ctx(priv); + const u32 *formats; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + formats = cvd->src_formats; + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + struct coda_q_data *q_data_src; + struct vb2_queue *src_vq; + + formats = cvd->dst_formats; + + /* + * If the source format is already fixed, only allow the same + * chroma subsampling. + */ + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + src_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (q_data_src->fourcc == V4L2_PIX_FMT_JPEG && + vb2_is_streaming(src_vq)) { + if (ctx->params.jpeg_chroma_subsampling == + V4L2_JPEG_CHROMA_SUBSAMPLING_420) { + formats = coda_formats_420; + } else if (ctx->params.jpeg_chroma_subsampling == + V4L2_JPEG_CHROMA_SUBSAMPLING_422) { + f->pixelformat = V4L2_PIX_FMT_YUV422P; + return f->index ? -EINVAL : 0; + } + } + } else { + return -EINVAL; + } + + if (f->index >= CODA_MAX_FORMATS || formats[f->index] == 0) + return -EINVAL; + + /* Skip YUYV if the vdoa is not available */ + if (!ctx->vdoa && f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + formats[f->index] == V4L2_PIX_FMT_YUYV) + return -EINVAL; + + f->pixelformat = formats[f->index]; + + return 0; +} + +static int coda_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct coda_q_data *q_data; + struct coda_ctx *ctx = fh_to_ctx(priv); + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fourcc; + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.bytesperline = q_data->bytesperline; + + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quantization; + + return 0; +} + +static int coda_try_pixelformat(struct coda_ctx *ctx, struct v4l2_format *f) +{ + struct coda_q_data *q_data; + const u32 *formats; + int i; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + formats = ctx->cvd->src_formats; + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + formats = ctx->cvd->dst_formats; + else + return -EINVAL; + + for (i = 0; i < CODA_MAX_FORMATS; i++) { + /* Skip YUYV if the vdoa is not available */ + if (!ctx->vdoa && f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + formats[i] == V4L2_PIX_FMT_YUYV) + continue; + + if (formats[i] == f->fmt.pix.pixelformat) { + f->fmt.pix.pixelformat = formats[i]; + return 0; + } + } + + /* Fall back to currently set pixelformat */ + q_data = get_q_data(ctx, f->type); + f->fmt.pix.pixelformat = q_data->fourcc; + + return 0; +} + +static int coda_try_fmt_vdoa(struct coda_ctx *ctx, struct v4l2_format *f, + bool *use_vdoa) +{ + int err; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (!use_vdoa) + return -EINVAL; + + if (!ctx->vdoa) { + *use_vdoa = false; + return 0; + } + + err = vdoa_context_configure(NULL, round_up(f->fmt.pix.width, 16), + f->fmt.pix.height, f->fmt.pix.pixelformat); + if (err) { + *use_vdoa = false; + return 0; + } + + *use_vdoa = true; + return 0; +} + +static unsigned int coda_estimate_sizeimage(struct coda_ctx *ctx, u32 sizeimage, + u32 width, u32 height) +{ + /* + * This is a rough estimate for sensible compressed buffer + * sizes (between 1 and 16 bits per pixel). This could be + * improved by better format specific worst case estimates. + */ + return round_up(clamp(sizeimage, width * height / 8, + width * height * 2), PAGE_SIZE); +} + +static int coda_try_fmt(struct coda_ctx *ctx, const struct coda_codec *codec, + struct v4l2_format *f) +{ + struct coda_dev *dev = ctx->dev; + unsigned int max_w, max_h; + enum v4l2_field field; + + field = f->fmt.pix.field; + if (field == V4L2_FIELD_ANY) + field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != field) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported */ + f->fmt.pix.field = field; + + coda_get_max_dimensions(dev, codec, &max_w, &max_h); + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, max_w, W_ALIGN, + &f->fmt.pix.height, MIN_H, max_h, H_ALIGN, + S_ALIGN); + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + /* + * Frame stride must be at least multiple of 8, + * but multiple of 16 for h.264 or JPEG 4:2:x + */ + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 16); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 16) * 2; + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height; + break; + case V4L2_PIX_FMT_YUV422P: + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 16); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height * 2; + break; + case V4L2_PIX_FMT_GREY: + /* keep 16 pixel alignment of 8-bit pixel data */ + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 16); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height; + break; + case V4L2_PIX_FMT_JPEG: + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_MPEG4: + case V4L2_PIX_FMT_MPEG2: + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = coda_estimate_sizeimage(ctx, + f->fmt.pix.sizeimage, + f->fmt.pix.width, + f->fmt.pix.height); + break; + default: + BUG(); + } + + return 0; +} + +static int coda_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + const struct coda_q_data *q_data_src; + const struct coda_codec *codec; + struct vb2_queue *src_vq; + int hscale = 0; + int vscale = 0; + int ret; + bool use_vdoa; + + ret = coda_try_pixelformat(ctx, f); + if (ret < 0) + return ret; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + /* + * If the source format is already fixed, only allow the same output + * resolution. When decoding JPEG images, we also have to make sure to + * use the same chroma subsampling. + */ + src_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (vb2_is_streaming(src_vq)) { + if (q_data_src->fourcc == V4L2_PIX_FMT_JPEG && + ctx->dev->devtype->product == CODA_960) { + hscale = coda_jpeg_scale(q_data_src->width, f->fmt.pix.width); + vscale = coda_jpeg_scale(q_data_src->height, f->fmt.pix.height); + } + f->fmt.pix.width = q_data_src->width >> hscale; + f->fmt.pix.height = q_data_src->height >> vscale; + + if (q_data_src->fourcc == V4L2_PIX_FMT_JPEG) { + if (ctx->params.jpeg_chroma_subsampling == + V4L2_JPEG_CHROMA_SUBSAMPLING_420 && + f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12; + else if (ctx->params.jpeg_chroma_subsampling == + V4L2_JPEG_CHROMA_SUBSAMPLING_422) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; + } + } + + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quantization; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + codec = coda_find_codec(ctx->dev, q_data_src->fourcc, + f->fmt.pix.pixelformat); + if (!codec) + return -EINVAL; + + ret = coda_try_fmt(ctx, codec, f); + if (ret < 0) + return ret; + + /* The decoders always write complete macroblocks or MCUs */ + if (ctx->inst_type == CODA_INST_DECODER) { + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 16 >> hscale); + f->fmt.pix.height = round_up(f->fmt.pix.height, 16 >> vscale); + if (codec->src_fourcc == V4L2_PIX_FMT_JPEG && + f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV422P) { + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height * 2; + } else { + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height * 3 / 2; + } + + ret = coda_try_fmt_vdoa(ctx, f, &use_vdoa); + if (ret < 0) + return ret; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV) { + if (!use_vdoa) + return -EINVAL; + + f->fmt.pix.bytesperline = round_up(f->fmt.pix.width, 16) * 2; + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height; + } + } + + return 0; +} + +static void coda_set_default_colorspace(struct v4l2_pix_format *fmt) +{ + enum v4l2_colorspace colorspace; + + if (fmt->pixelformat == V4L2_PIX_FMT_JPEG) + colorspace = V4L2_COLORSPACE_JPEG; + else if (fmt->width <= 720 && fmt->height <= 576) + colorspace = V4L2_COLORSPACE_SMPTE170M; + else + colorspace = V4L2_COLORSPACE_REC709; + + fmt->colorspace = colorspace; + fmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; + fmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + fmt->quantization = V4L2_QUANTIZATION_DEFAULT; +} + +static int coda_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + struct coda_dev *dev = ctx->dev; + const struct coda_q_data *q_data_dst; + const struct coda_codec *codec; + int ret; + + ret = coda_try_pixelformat(ctx, f); + if (ret < 0) + return ret; + + if (f->fmt.pix.colorspace == V4L2_COLORSPACE_DEFAULT) + coda_set_default_colorspace(&f->fmt.pix); + + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + codec = coda_find_codec(dev, f->fmt.pix.pixelformat, q_data_dst->fourcc); + + return coda_try_fmt(ctx, codec, f); +} + +static int coda_s_fmt(struct coda_ctx *ctx, struct v4l2_format *f, + struct v4l2_rect *r) +{ + struct coda_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s: %s queue busy: %d\n", + __func__, v4l2_type_names[f->type], vq->num_buffers); + return -EBUSY; + } + + q_data->fourcc = f->fmt.pix.pixelformat; + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->bytesperline = f->fmt.pix.bytesperline; + q_data->sizeimage = f->fmt.pix.sizeimage; + if (r) { + q_data->rect = *r; + } else { + q_data->rect.left = 0; + q_data->rect.top = 0; + q_data->rect.width = f->fmt.pix.width; + q_data->rect.height = f->fmt.pix.height; + } + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUYV: + ctx->tiled_map_type = GDI_TILED_FRAME_MB_RASTER_MAP; + break; + case V4L2_PIX_FMT_NV12: + if (!disable_tiling && ctx->use_bit && + ctx->dev->devtype->product == CODA_960) { + ctx->tiled_map_type = GDI_TILED_FRAME_MB_RASTER_MAP; + break; + } + fallthrough; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_YUV422P: + ctx->tiled_map_type = GDI_LINEAR_FRAME_MAP; + break; + default: + break; + } + + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP && + !coda_try_fmt_vdoa(ctx, f, &ctx->use_vdoa) && + ctx->use_vdoa) + vdoa_context_configure(ctx->vdoa, + round_up(f->fmt.pix.width, 16), + f->fmt.pix.height, + f->fmt.pix.pixelformat); + else + ctx->use_vdoa = false; + + coda_dbg(1, ctx, "Setting %s format, wxh: %dx%d, fmt: %4.4s %c\n", + v4l2_type_names[f->type], q_data->width, q_data->height, + (char *)&q_data->fourcc, + (ctx->tiled_map_type == GDI_LINEAR_FRAME_MAP) ? 'L' : 'T'); + + return 0; +} + +static int coda_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + struct coda_q_data *q_data_src; + const struct coda_codec *codec; + struct v4l2_rect r; + int hscale = 0; + int vscale = 0; + int ret; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + if (q_data_src->fourcc == V4L2_PIX_FMT_JPEG && + ctx->dev->devtype->product == CODA_960) { + hscale = coda_jpeg_scale(q_data_src->width, f->fmt.pix.width); + vscale = coda_jpeg_scale(q_data_src->height, f->fmt.pix.height); + } + + ret = coda_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + r.left = 0; + r.top = 0; + r.width = q_data_src->width >> hscale; + r.height = q_data_src->height >> vscale; + + ret = coda_s_fmt(ctx, f, &r); + if (ret) + return ret; + + if (ctx->inst_type != CODA_INST_ENCODER) + return 0; + + /* Setting the coded format determines the selected codec */ + codec = coda_find_codec(ctx->dev, q_data_src->fourcc, + f->fmt.pix.pixelformat); + if (!codec) { + v4l2_err(&ctx->dev->v4l2_dev, "failed to determine codec\n"); + return -EINVAL; + } + ctx->codec = codec; + + ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->quantization = f->fmt.pix.quantization; + + return 0; +} + +static int coda_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + const struct coda_codec *codec; + struct v4l2_format f_cap; + struct vb2_queue *dst_vq; + int ret; + + ret = coda_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = coda_s_fmt(ctx, f, NULL); + if (ret) + return ret; + + ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->quantization = f->fmt.pix.quantization; + + if (ctx->inst_type != CODA_INST_DECODER) + return 0; + + /* Setting the coded format determines the selected codec */ + codec = coda_find_codec(ctx->dev, f->fmt.pix.pixelformat, + V4L2_PIX_FMT_YUV420); + if (!codec) { + v4l2_err(&ctx->dev->v4l2_dev, "failed to determine codec\n"); + return -EINVAL; + } + ctx->codec = codec; + + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (!dst_vq) + return -EINVAL; + + /* + * Setting the capture queue format is not possible while the capture + * queue is still busy. This is not an error, but the user will have to + * make sure themselves that the capture format is set correctly before + * starting the output queue again. + */ + if (vb2_is_busy(dst_vq)) + return 0; + + memset(&f_cap, 0, sizeof(f_cap)); + f_cap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + coda_g_fmt(file, priv, &f_cap); + f_cap.fmt.pix.width = f->fmt.pix.width; + f_cap.fmt.pix.height = f->fmt.pix.height; + + return coda_s_fmt_vid_cap(file, priv, &f_cap); +} + +static int coda_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + int ret; + + ret = v4l2_m2m_reqbufs(file, ctx->fh.m2m_ctx, rb); + if (ret) + return ret; + + /* + * Allow to allocate instance specific per-context buffers, such as + * bitstream ringbuffer, slice buffer, work buffer, etc. if needed. + */ + if (rb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT && ctx->ops->reqbufs) + return ctx->ops->reqbufs(ctx, rb); + + return 0; +} + +static int coda_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + + if (ctx->inst_type == CODA_INST_DECODER && + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + buf->flags &= ~V4L2_BUF_FLAG_LAST; + + return v4l2_m2m_qbuf(file, ctx->fh.m2m_ctx, buf); +} + +static int coda_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct coda_ctx *ctx = fh_to_ctx(priv); + int ret; + + ret = v4l2_m2m_dqbuf(file, ctx->fh.m2m_ctx, buf); + + if (ctx->inst_type == CODA_INST_DECODER && + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + buf->flags &= ~V4L2_BUF_FLAG_LAST; + + return ret; +} + +void coda_m2m_buf_done(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf, + enum vb2_buffer_state state) +{ + const struct v4l2_event eos_event = { + .type = V4L2_EVENT_EOS + }; + + if (buf->flags & V4L2_BUF_FLAG_LAST) + v4l2_event_queue_fh(&ctx->fh, &eos_event); + + v4l2_m2m_buf_done(buf, state); +} + +static int coda_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct coda_q_data *q_data; + struct v4l2_rect r, *rsel; + + q_data = get_q_data(ctx, s->type); + if (!q_data) + return -EINVAL; + + r.left = 0; + r.top = 0; + r.width = q_data->width; + r.height = q_data->height; + rsel = &q_data->rect; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + rsel = &r; + fallthrough; + case V4L2_SEL_TGT_CROP: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + ctx->inst_type == CODA_INST_DECODER) + return -EINVAL; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_PADDED: + rsel = &r; + fallthrough; + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + ctx->inst_type == CODA_INST_ENCODER) + return -EINVAL; + break; + default: + return -EINVAL; + } + + s->r = *rsel; + + return 0; +} + +static int coda_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct coda_q_data *q_data; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + if (ctx->inst_type == CODA_INST_ENCODER && + s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + q_data = get_q_data(ctx, s->type); + if (!q_data) + return -EINVAL; + + s->r.left = 0; + s->r.top = 0; + s->r.width = clamp(s->r.width, 2U, q_data->width); + s->r.height = clamp(s->r.height, 2U, q_data->height); + + if (s->flags & V4L2_SEL_FLAG_LE) { + s->r.width = round_up(s->r.width, 2); + s->r.height = round_up(s->r.height, 2); + } else { + s->r.width = round_down(s->r.width, 2); + s->r.height = round_down(s->r.height, 2); + } + + q_data->rect = s->r; + + coda_dbg(1, ctx, "Setting crop rectangle: %dx%d\n", + s->r.width, s->r.height); + + return 0; + } + fallthrough; + case V4L2_SEL_TGT_NATIVE_SIZE: + case V4L2_SEL_TGT_COMPOSE: + return coda_g_selection(file, fh, s); + default: + /* v4l2-compliance expects this to fail for read-only targets */ + return -EINVAL; + } +} + +static void coda_wake_up_capture_queue(struct coda_ctx *ctx) +{ + struct vb2_queue *dst_vq; + + coda_dbg(1, ctx, "waking up capture queue\n"); + + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + dst_vq->last_buffer_dequeued = true; + wake_up(&dst_vq->done_wq); +} + +static int coda_encoder_cmd(struct file *file, void *fh, + struct v4l2_encoder_cmd *ec) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct vb2_v4l2_buffer *buf; + int ret; + + ret = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, ec); + if (ret < 0) + return ret; + + mutex_lock(&ctx->wakeup_mutex); + buf = v4l2_m2m_last_src_buf(ctx->fh.m2m_ctx); + if (buf) { + /* + * If the last output buffer is still on the queue, make sure + * that decoder finish_run will see the last flag and report it + * to userspace. + */ + buf->flags |= V4L2_BUF_FLAG_LAST; + } else { + /* Set the stream-end flag on this context */ + ctx->bit_stream_param |= CODA_BIT_STREAM_END_FLAG; + + /* + * If the last output buffer has already been taken from the + * queue, wake up the capture queue and signal end of stream + * via the -EPIPE mechanism. + */ + coda_wake_up_capture_queue(ctx); + } + mutex_unlock(&ctx->wakeup_mutex); + + return 0; +} + +static bool coda_mark_last_meta(struct coda_ctx *ctx) +{ + struct coda_buffer_meta *meta; + + coda_dbg(1, ctx, "marking last meta\n"); + + spin_lock(&ctx->buffer_meta_lock); + if (list_empty(&ctx->buffer_meta_list)) { + spin_unlock(&ctx->buffer_meta_lock); + return false; + } + + meta = list_last_entry(&ctx->buffer_meta_list, struct coda_buffer_meta, + list); + meta->last = true; + + spin_unlock(&ctx->buffer_meta_lock); + return true; +} + +static bool coda_mark_last_dst_buf(struct coda_ctx *ctx) +{ + struct vb2_v4l2_buffer *buf; + struct vb2_buffer *dst_vb; + struct vb2_queue *dst_vq; + unsigned long flags; + + coda_dbg(1, ctx, "marking last capture buffer\n"); + + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + spin_lock_irqsave(&dst_vq->done_lock, flags); + if (list_empty(&dst_vq->done_list)) { + spin_unlock_irqrestore(&dst_vq->done_lock, flags); + return false; + } + + dst_vb = list_last_entry(&dst_vq->done_list, struct vb2_buffer, + done_entry); + buf = to_vb2_v4l2_buffer(dst_vb); + buf->flags |= V4L2_BUF_FLAG_LAST; + + spin_unlock_irqrestore(&dst_vq->done_lock, flags); + return true; +} + +static int coda_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *dc) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct coda_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *buf; + struct vb2_queue *dst_vq; + bool stream_end; + bool wakeup; + int ret; + + ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, dc); + if (ret < 0) + return ret; + + switch (dc->cmd) { + case V4L2_DEC_CMD_START: + mutex_lock(&dev->coda_mutex); + mutex_lock(&ctx->bitstream_mutex); + coda_bitstream_flush(ctx); + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + vb2_clear_last_buffer_dequeued(dst_vq); + ctx->bit_stream_param &= ~CODA_BIT_STREAM_END_FLAG; + coda_fill_bitstream(ctx, NULL); + mutex_unlock(&ctx->bitstream_mutex); + mutex_unlock(&dev->coda_mutex); + break; + case V4L2_DEC_CMD_STOP: + stream_end = false; + wakeup = false; + + mutex_lock(&ctx->wakeup_mutex); + + buf = v4l2_m2m_last_src_buf(ctx->fh.m2m_ctx); + if (buf) { + coda_dbg(1, ctx, "marking last pending buffer\n"); + + /* Mark last buffer */ + buf->flags |= V4L2_BUF_FLAG_LAST; + + if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) == 0) { + coda_dbg(1, ctx, "all remaining buffers queued\n"); + stream_end = true; + } + } else { + if (ctx->use_bit) + if (coda_mark_last_meta(ctx)) + stream_end = true; + else + wakeup = true; + else + if (!coda_mark_last_dst_buf(ctx)) + wakeup = true; + } + + if (stream_end) { + coda_dbg(1, ctx, "all remaining buffers queued\n"); + + /* Set the stream-end flag on this context */ + coda_bit_stream_end_flag(ctx); + ctx->hold = false; + v4l2_m2m_try_schedule(ctx->fh.m2m_ctx); + } + + if (wakeup) { + /* If there is no buffer in flight, wake up */ + coda_wake_up_capture_queue(ctx); + } + + mutex_unlock(&ctx->wakeup_mutex); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int coda_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct coda_q_data *q_data_dst; + const struct coda_codec *codec; + + if (fsize->index) + return -EINVAL; + + if (coda_format_normalize_yuv(fsize->pixel_format) == + V4L2_PIX_FMT_YUV420) { + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + codec = coda_find_codec(ctx->dev, fsize->pixel_format, + q_data_dst->fourcc); + } else { + codec = coda_find_codec(ctx->dev, V4L2_PIX_FMT_YUV420, + fsize->pixel_format); + } + if (!codec) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = MIN_W; + fsize->stepwise.max_width = codec->max_w; + fsize->stepwise.step_width = 1; + fsize->stepwise.min_height = MIN_H; + fsize->stepwise.max_height = codec->max_h; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int coda_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *f) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct coda_q_data *q_data; + const struct coda_codec *codec; + + if (f->index) + return -EINVAL; + + /* Disallow YUYV if the vdoa is not available */ + if (!ctx->vdoa && f->pixel_format == V4L2_PIX_FMT_YUYV) + return -EINVAL; + + if (coda_format_normalize_yuv(f->pixel_format) == V4L2_PIX_FMT_YUV420) { + q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + codec = coda_find_codec(ctx->dev, f->pixel_format, + q_data->fourcc); + } else { + codec = coda_find_codec(ctx->dev, V4L2_PIX_FMT_YUV420, + f->pixel_format); + } + if (!codec) + return -EINVAL; + + if (f->width < MIN_W || f->width > codec->max_w || + f->height < MIN_H || f->height > codec->max_h) + return -EINVAL; + + f->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + f->stepwise.min.numerator = 1; + f->stepwise.min.denominator = 65535; + f->stepwise.max.numerator = 65536; + f->stepwise.max.denominator = 1; + f->stepwise.step.numerator = 1; + f->stepwise.step.denominator = 1; + + return 0; +} + +static int coda_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct v4l2_fract *tpf; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + tpf = &a->parm.output.timeperframe; + tpf->denominator = ctx->params.framerate & CODA_FRATE_RES_MASK; + tpf->numerator = 1 + (ctx->params.framerate >> + CODA_FRATE_DIV_OFFSET); + + return 0; +} + +/* + * Approximate timeperframe v4l2_fract with values that can be written + * into the 16-bit CODA_FRATE_DIV and CODA_FRATE_RES fields. + */ +static void coda_approximate_timeperframe(struct v4l2_fract *timeperframe) +{ + struct v4l2_fract s = *timeperframe; + struct v4l2_fract f0; + struct v4l2_fract f1 = { 1, 0 }; + struct v4l2_fract f2 = { 0, 1 }; + unsigned int i, div, s_denominator; + + /* Lower bound is 1/65535 */ + if (s.numerator == 0 || s.denominator / s.numerator > 65535) { + timeperframe->numerator = 1; + timeperframe->denominator = 65535; + return; + } + + /* Upper bound is 65536/1 */ + if (s.denominator == 0 || s.numerator / s.denominator > 65536) { + timeperframe->numerator = 65536; + timeperframe->denominator = 1; + return; + } + + /* Reduce fraction to lowest terms */ + div = gcd(s.numerator, s.denominator); + if (div > 1) { + s.numerator /= div; + s.denominator /= div; + } + + if (s.numerator <= 65536 && s.denominator < 65536) { + *timeperframe = s; + return; + } + + /* Find successive convergents from continued fraction expansion */ + while (f2.numerator <= 65536 && f2.denominator < 65536) { + f0 = f1; + f1 = f2; + + /* Stop when f2 exactly equals timeperframe */ + if (s.numerator == 0) + break; + + i = s.denominator / s.numerator; + + f2.numerator = f0.numerator + i * f1.numerator; + f2.denominator = f0.denominator + i * f2.denominator; + + s_denominator = s.numerator; + s.numerator = s.denominator % s.numerator; + s.denominator = s_denominator; + } + + *timeperframe = f1; +} + +static uint32_t coda_timeperframe_to_frate(struct v4l2_fract *timeperframe) +{ + return ((timeperframe->numerator - 1) << CODA_FRATE_DIV_OFFSET) | + timeperframe->denominator; +} + +static int coda_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + struct v4l2_fract *tpf; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + tpf = &a->parm.output.timeperframe; + coda_approximate_timeperframe(tpf); + ctx->params.framerate = coda_timeperframe_to_frate(tpf); + ctx->params.framerate_changed = true; + + return 0; +} + +static int coda_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + struct coda_ctx *ctx = fh_to_ctx(fh); + + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + if (ctx->inst_type == CODA_INST_DECODER) + return v4l2_event_subscribe(fh, sub, 0, NULL); + else + return -EINVAL; + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static const struct v4l2_ioctl_ops coda_ioctl_ops = { + .vidioc_querycap = coda_querycap, + + .vidioc_enum_fmt_vid_cap = coda_enum_fmt, + .vidioc_g_fmt_vid_cap = coda_g_fmt, + .vidioc_try_fmt_vid_cap = coda_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = coda_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = coda_enum_fmt, + .vidioc_g_fmt_vid_out = coda_g_fmt, + .vidioc_try_fmt_vid_out = coda_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = coda_s_fmt_vid_out, + + .vidioc_reqbufs = coda_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + + .vidioc_qbuf = coda_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = coda_dqbuf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_g_selection = coda_g_selection, + .vidioc_s_selection = coda_s_selection, + + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, + .vidioc_encoder_cmd = coda_encoder_cmd, + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, + .vidioc_decoder_cmd = coda_decoder_cmd, + + .vidioc_g_parm = coda_g_parm, + .vidioc_s_parm = coda_s_parm, + + .vidioc_enum_framesizes = coda_enum_framesizes, + .vidioc_enum_frameintervals = coda_enum_frameintervals, + + .vidioc_subscribe_event = coda_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * Mem-to-mem operations. + */ + +static void coda_device_run(void *m2m_priv) +{ + struct coda_ctx *ctx = m2m_priv; + struct coda_dev *dev = ctx->dev; + + queue_work(dev->workqueue, &ctx->pic_run_work); +} + +static void coda_pic_run_work(struct work_struct *work) +{ + struct coda_ctx *ctx = container_of(work, struct coda_ctx, pic_run_work); + struct coda_dev *dev = ctx->dev; + int ret; + + mutex_lock(&ctx->buffer_mutex); + mutex_lock(&dev->coda_mutex); + + ret = ctx->ops->prepare_run(ctx); + if (ret < 0 && ctx->inst_type == CODA_INST_DECODER) + goto out; + + if (!wait_for_completion_timeout(&ctx->completion, + msecs_to_jiffies(1000))) { + if (ctx->use_bit) { + dev_err(dev->dev, "CODA PIC_RUN timeout\n"); + + ctx->hold = true; + + coda_hw_reset(ctx); + } + + if (ctx->ops->run_timeout) + ctx->ops->run_timeout(ctx); + } else { + ctx->ops->finish_run(ctx); + } + + if ((ctx->aborting || (!ctx->streamon_cap && !ctx->streamon_out)) && + ctx->ops->seq_end_work) + queue_work(dev->workqueue, &ctx->seq_end_work); + +out: + mutex_unlock(&dev->coda_mutex); + mutex_unlock(&ctx->buffer_mutex); + + v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx); +} + +static int coda_job_ready(void *m2m_priv) +{ + struct coda_ctx *ctx = m2m_priv; + int src_bufs = v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx); + + /* + * For both 'P' and 'key' frame cases 1 picture + * and 1 frame are needed. In the decoder case, + * the compressed frame can be in the bitstream. + */ + if (!src_bufs && ctx->inst_type != CODA_INST_DECODER) { + coda_dbg(1, ctx, "not ready: not enough vid-out buffers.\n"); + return 0; + } + + if (!v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx)) { + coda_dbg(1, ctx, "not ready: not enough vid-cap buffers.\n"); + return 0; + } + + if (ctx->inst_type == CODA_INST_DECODER && ctx->use_bit) { + bool stream_end = ctx->bit_stream_param & + CODA_BIT_STREAM_END_FLAG; + int num_metas = ctx->num_metas; + struct coda_buffer_meta *meta; + unsigned int count; + + count = hweight32(ctx->frm_dis_flg); + if (ctx->use_vdoa && count >= (ctx->num_internal_frames - 1)) { + coda_dbg(1, ctx, + "not ready: all internal buffers in use: %d/%d (0x%x)", + count, ctx->num_internal_frames, + ctx->frm_dis_flg); + return 0; + } + + if (ctx->hold && !src_bufs) { + coda_dbg(1, ctx, + "not ready: on hold for more buffers.\n"); + return 0; + } + + if (!stream_end && (num_metas + src_bufs) < 2) { + coda_dbg(1, ctx, + "not ready: need 2 buffers available (queue:%d + bitstream:%d)\n", + num_metas, src_bufs); + return 0; + } + + meta = list_first_entry(&ctx->buffer_meta_list, + struct coda_buffer_meta, list); + if (!coda_bitstream_can_fetch_past(ctx, meta->end) && + !stream_end) { + coda_dbg(1, ctx, + "not ready: not enough bitstream data to read past %u (%u)\n", + meta->end, ctx->bitstream_fifo.kfifo.in); + return 0; + } + } + + if (ctx->aborting) { + coda_dbg(1, ctx, "not ready: aborting\n"); + return 0; + } + + coda_dbg(2, ctx, "job ready\n"); + + return 1; +} + +static void coda_job_abort(void *priv) +{ + struct coda_ctx *ctx = priv; + + ctx->aborting = 1; + + coda_dbg(1, ctx, "job abort\n"); +} + +static const struct v4l2_m2m_ops coda_m2m_ops = { + .device_run = coda_device_run, + .job_ready = coda_job_ready, + .job_abort = coda_job_abort, +}; + +static void set_default_params(struct coda_ctx *ctx) +{ + unsigned int max_w, max_h, usize, csize; + + ctx->codec = coda_find_codec(ctx->dev, ctx->cvd->src_formats[0], + ctx->cvd->dst_formats[0]); + max_w = min(ctx->codec->max_w, 1920U); + max_h = min(ctx->codec->max_h, 1088U); + usize = max_w * max_h * 3 / 2; + csize = coda_estimate_sizeimage(ctx, usize, max_w, max_h); + + ctx->params.codec_mode = ctx->codec->mode; + if (ctx->cvd->src_formats[0] == V4L2_PIX_FMT_JPEG || + ctx->cvd->dst_formats[0] == V4L2_PIX_FMT_JPEG) { + ctx->colorspace = V4L2_COLORSPACE_SRGB; + ctx->xfer_func = V4L2_XFER_FUNC_SRGB; + ctx->ycbcr_enc = V4L2_YCBCR_ENC_601; + ctx->quantization = V4L2_QUANTIZATION_FULL_RANGE; + } else { + ctx->colorspace = V4L2_COLORSPACE_REC709; + ctx->xfer_func = V4L2_XFER_FUNC_DEFAULT; + ctx->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + ctx->quantization = V4L2_QUANTIZATION_DEFAULT; + } + ctx->params.framerate = 30; + + /* Default formats for output and input queues */ + ctx->q_data[V4L2_M2M_SRC].fourcc = ctx->cvd->src_formats[0]; + ctx->q_data[V4L2_M2M_DST].fourcc = ctx->cvd->dst_formats[0]; + ctx->q_data[V4L2_M2M_SRC].width = max_w; + ctx->q_data[V4L2_M2M_SRC].height = max_h; + ctx->q_data[V4L2_M2M_DST].width = max_w; + ctx->q_data[V4L2_M2M_DST].height = max_h; + if (ctx->codec->src_fourcc == V4L2_PIX_FMT_YUV420) { + ctx->q_data[V4L2_M2M_SRC].bytesperline = max_w; + ctx->q_data[V4L2_M2M_SRC].sizeimage = usize; + ctx->q_data[V4L2_M2M_DST].bytesperline = 0; + ctx->q_data[V4L2_M2M_DST].sizeimage = csize; + } else { + ctx->q_data[V4L2_M2M_SRC].bytesperline = 0; + ctx->q_data[V4L2_M2M_SRC].sizeimage = csize; + ctx->q_data[V4L2_M2M_DST].bytesperline = max_w; + ctx->q_data[V4L2_M2M_DST].sizeimage = usize; + } + ctx->q_data[V4L2_M2M_SRC].rect.width = max_w; + ctx->q_data[V4L2_M2M_SRC].rect.height = max_h; + ctx->q_data[V4L2_M2M_DST].rect.width = max_w; + ctx->q_data[V4L2_M2M_DST].rect.height = max_h; + + /* + * Since the RBC2AXI logic only supports a single chroma plane, + * macroblock tiling only works for to NV12 pixel format. + */ + ctx->tiled_map_type = GDI_LINEAR_FRAME_MAP; +} + +/* + * Queue operations + */ +static int coda_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(vq); + struct coda_q_data *q_data; + unsigned int size; + + q_data = get_q_data(ctx, vq->type); + size = q_data->sizeimage; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + coda_dbg(1, ctx, "get %d buffer(s) of size %d each.\n", *nbuffers, + size); + + return 0; +} + +static int coda_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct coda_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct coda_q_data *q_data; + + q_data = get_q_data(ctx, vb->vb2_queue->type); + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) { + v4l2_warn(&ctx->dev->v4l2_dev, + "%s field isn't supported\n", __func__); + return -EINVAL; + } + } + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + v4l2_warn(&ctx->dev->v4l2_dev, + "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + return 0; +} + +static void coda_update_menu_ctrl(struct v4l2_ctrl *ctrl, int value) +{ + if (!ctrl) + return; + + v4l2_ctrl_lock(ctrl); + + /* + * Extend the control range if the parsed stream contains a known but + * unsupported value or level. + */ + if (value > ctrl->maximum) { + __v4l2_ctrl_modify_range(ctrl, ctrl->minimum, value, + ctrl->menu_skip_mask & ~(1 << value), + ctrl->default_value); + } else if (value < ctrl->minimum) { + __v4l2_ctrl_modify_range(ctrl, value, ctrl->maximum, + ctrl->menu_skip_mask & ~(1 << value), + ctrl->default_value); + } + + __v4l2_ctrl_s_ctrl(ctrl, value); + + v4l2_ctrl_unlock(ctrl); +} + +void coda_update_profile_level_ctrls(struct coda_ctx *ctx, u8 profile_idc, + u8 level_idc) +{ + const char * const *profile_names; + const char * const *level_names; + struct v4l2_ctrl *profile_ctrl; + struct v4l2_ctrl *level_ctrl; + const char *codec_name; + u32 profile_cid; + u32 level_cid; + int profile; + int level; + + switch (ctx->codec->src_fourcc) { + case V4L2_PIX_FMT_H264: + codec_name = "H264"; + profile_cid = V4L2_CID_MPEG_VIDEO_H264_PROFILE; + level_cid = V4L2_CID_MPEG_VIDEO_H264_LEVEL; + profile_ctrl = ctx->h264_profile_ctrl; + level_ctrl = ctx->h264_level_ctrl; + profile = coda_h264_profile(profile_idc); + level = coda_h264_level(level_idc); + break; + case V4L2_PIX_FMT_MPEG2: + codec_name = "MPEG-2"; + profile_cid = V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE; + level_cid = V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL; + profile_ctrl = ctx->mpeg2_profile_ctrl; + level_ctrl = ctx->mpeg2_level_ctrl; + profile = coda_mpeg2_profile(profile_idc); + level = coda_mpeg2_level(level_idc); + break; + case V4L2_PIX_FMT_MPEG4: + codec_name = "MPEG-4"; + profile_cid = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE; + level_cid = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL; + profile_ctrl = ctx->mpeg4_profile_ctrl; + level_ctrl = ctx->mpeg4_level_ctrl; + profile = coda_mpeg4_profile(profile_idc); + level = coda_mpeg4_level(level_idc); + break; + default: + return; + } + + profile_names = v4l2_ctrl_get_menu(profile_cid); + level_names = v4l2_ctrl_get_menu(level_cid); + + if (profile < 0) { + v4l2_warn(&ctx->dev->v4l2_dev, "Invalid %s profile: %u\n", + codec_name, profile_idc); + } else { + coda_dbg(1, ctx, "Parsed %s profile: %s\n", codec_name, + profile_names[profile]); + coda_update_menu_ctrl(profile_ctrl, profile); + } + + if (level < 0) { + v4l2_warn(&ctx->dev->v4l2_dev, "Invalid %s level: %u\n", + codec_name, level_idc); + } else { + coda_dbg(1, ctx, "Parsed %s level: %s\n", codec_name, + level_names[level]); + coda_update_menu_ctrl(level_ctrl, level); + } +} + +static void coda_queue_source_change_event(struct coda_ctx *ctx) +{ + static const struct v4l2_event source_change_event = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_event_queue_fh(&ctx->fh, &source_change_event); +} + +static void coda_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct coda_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_queue *vq = vb->vb2_queue; + struct coda_q_data *q_data; + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + /* + * In the decoder case, immediately try to copy the buffer into the + * bitstream ringbuffer and mark it as ready to be dequeued. + */ + if (ctx->bitstream.size && vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + /* + * For backwards compatibility, queuing an empty buffer marks + * the stream end + */ + if (vb2_get_plane_payload(vb, 0) == 0) + coda_bit_stream_end_flag(ctx); + + if (q_data->fourcc == V4L2_PIX_FMT_H264) { + /* + * Unless already done, try to obtain profile_idc and + * level_idc from the SPS header. This allows to decide + * whether to enable reordering during sequence + * initialization. + */ + if (!ctx->params.h264_profile_idc) { + coda_sps_parse_profile(ctx, vb); + coda_update_profile_level_ctrls(ctx, + ctx->params.h264_profile_idc, + ctx->params.h264_level_idc); + } + } + + mutex_lock(&ctx->bitstream_mutex); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); + if (vb2_is_streaming(vb->vb2_queue)) + /* This set buf->sequence = ctx->qsequence++ */ + coda_fill_bitstream(ctx, NULL); + mutex_unlock(&ctx->bitstream_mutex); + + if (!ctx->initialized) { + /* + * Run sequence initialization in case the queued + * buffer contained headers. + */ + if (vb2_is_streaming(vb->vb2_queue) && + ctx->ops->seq_init_work) { + queue_work(ctx->dev->workqueue, + &ctx->seq_init_work); + flush_work(&ctx->seq_init_work); + } + + if (ctx->initialized) + coda_queue_source_change_event(ctx); + } + } else { + if ((ctx->inst_type == CODA_INST_ENCODER || !ctx->use_bit) && + vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + vbuf->sequence = ctx->qsequence++; + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); + } +} + +int coda_alloc_aux_buf(struct coda_dev *dev, struct coda_aux_buf *buf, + size_t size, const char *name, struct dentry *parent) +{ + buf->vaddr = dma_alloc_coherent(dev->dev, size, &buf->paddr, + GFP_KERNEL); + if (!buf->vaddr) { + v4l2_err(&dev->v4l2_dev, + "Failed to allocate %s buffer of size %zu\n", + name, size); + return -ENOMEM; + } + + buf->size = size; + + if (name && parent) { + buf->blob.data = buf->vaddr; + buf->blob.size = size; + buf->dentry = debugfs_create_blob(name, 0444, parent, + &buf->blob); + } + + return 0; +} + +void coda_free_aux_buf(struct coda_dev *dev, + struct coda_aux_buf *buf) +{ + if (buf->vaddr) { + dma_free_coherent(dev->dev, buf->size, buf->vaddr, buf->paddr); + buf->vaddr = NULL; + buf->size = 0; + debugfs_remove(buf->dentry); + buf->dentry = NULL; + } +} + +static int coda_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(q); + struct v4l2_device *v4l2_dev = &ctx->dev->v4l2_dev; + struct coda_q_data *q_data_src, *q_data_dst; + struct v4l2_m2m_buffer *m2m_buf, *tmp; + struct vb2_v4l2_buffer *buf; + struct list_head list; + int ret = 0; + + if (count < 1) + return -EINVAL; + + coda_dbg(1, ctx, "start streaming %s\n", v4l2_type_names[q->type]); + + INIT_LIST_HEAD(&list); + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (ctx->inst_type == CODA_INST_DECODER && ctx->use_bit) { + /* copy the buffers that were queued before streamon */ + mutex_lock(&ctx->bitstream_mutex); + coda_fill_bitstream(ctx, &list); + mutex_unlock(&ctx->bitstream_mutex); + + if (ctx->dev->devtype->product != CODA_960 && + coda_get_bitstream_payload(ctx) < 512) { + v4l2_err(v4l2_dev, "start payload < 512\n"); + ret = -EINVAL; + goto err; + } + + if (!ctx->initialized) { + /* Run sequence initialization */ + if (ctx->ops->seq_init_work) { + queue_work(ctx->dev->workqueue, + &ctx->seq_init_work); + flush_work(&ctx->seq_init_work); + } + } + } + + /* + * Check the first input JPEG buffer to determine chroma + * subsampling. + */ + if (q_data_src->fourcc == V4L2_PIX_FMT_JPEG) { + buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + coda_jpeg_decode_header(ctx, &buf->vb2_buf); + /* + * We have to start streaming even if the first buffer + * does not contain a valid JPEG image. The error will + * be caught during device run and will be signalled + * via the capture buffer error flag. + */ + + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + q_data_dst->width = round_up(q_data_src->width, 16); + q_data_dst->height = round_up(q_data_src->height, 16); + q_data_dst->bytesperline = q_data_dst->width; + if (ctx->params.jpeg_chroma_subsampling == + V4L2_JPEG_CHROMA_SUBSAMPLING_420) { + q_data_dst->sizeimage = + q_data_dst->bytesperline * + q_data_dst->height * 3 / 2; + if (q_data_dst->fourcc != V4L2_PIX_FMT_YUV420) + q_data_dst->fourcc = V4L2_PIX_FMT_NV12; + } else { + q_data_dst->sizeimage = + q_data_dst->bytesperline * + q_data_dst->height * 2; + q_data_dst->fourcc = V4L2_PIX_FMT_YUV422P; + } + q_data_dst->rect.left = 0; + q_data_dst->rect.top = 0; + q_data_dst->rect.width = q_data_src->width; + q_data_dst->rect.height = q_data_src->height; + } + ctx->streamon_out = 1; + } else { + ctx->streamon_cap = 1; + } + + /* Don't start the coda unless both queues are on */ + if (!(ctx->streamon_out && ctx->streamon_cap)) + goto out; + + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if ((q_data_src->rect.width != q_data_dst->width && + round_up(q_data_src->rect.width, 16) != q_data_dst->width) || + (q_data_src->rect.height != q_data_dst->height && + round_up(q_data_src->rect.height, 16) != q_data_dst->height)) { + v4l2_err(v4l2_dev, "can't convert %dx%d to %dx%d\n", + q_data_src->rect.width, q_data_src->rect.height, + q_data_dst->width, q_data_dst->height); + ret = -EINVAL; + goto err; + } + + /* Allow BIT decoder device_run with no new buffers queued */ + if (ctx->inst_type == CODA_INST_DECODER && ctx->use_bit) + v4l2_m2m_set_src_buffered(ctx->fh.m2m_ctx, true); + + ctx->gopcounter = ctx->params.gop_size - 1; + + if (q_data_dst->fourcc == V4L2_PIX_FMT_JPEG) + ctx->params.gop_size = 1; + ctx->gopcounter = ctx->params.gop_size - 1; + /* Only decoders have this control */ + if (ctx->mb_err_cnt_ctrl) + v4l2_ctrl_s_ctrl(ctx->mb_err_cnt_ctrl, 0); + + ret = ctx->ops->start_streaming(ctx); + if (ctx->inst_type == CODA_INST_DECODER) { + if (ret == -EAGAIN) + goto out; + } + if (ret < 0) + goto err; + +out: + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + list_for_each_entry_safe(m2m_buf, tmp, &list, list) { + list_del(&m2m_buf->list); + v4l2_m2m_buf_done(&m2m_buf->vb, VB2_BUF_STATE_DONE); + } + } + return 0; + +err: + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + list_for_each_entry_safe(m2m_buf, tmp, &list, list) { + list_del(&m2m_buf->list); + v4l2_m2m_buf_done(&m2m_buf->vb, VB2_BUF_STATE_QUEUED); + } + while ((buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + } else { + while ((buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + } + return ret; +} + +static void coda_stop_streaming(struct vb2_queue *q) +{ + struct coda_ctx *ctx = vb2_get_drv_priv(q); + struct coda_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *buf; + bool stop; + + stop = ctx->streamon_out && ctx->streamon_cap; + + coda_dbg(1, ctx, "stop streaming %s\n", v4l2_type_names[q->type]); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ctx->streamon_out = 0; + + coda_bit_stream_end_flag(ctx); + + ctx->qsequence = 0; + + while ((buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); + } else { + ctx->streamon_cap = 0; + + ctx->osequence = 0; + ctx->sequence_offset = 0; + + while ((buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); + } + + if (stop) { + struct coda_buffer_meta *meta; + + if (ctx->ops->seq_end_work) { + queue_work(dev->workqueue, &ctx->seq_end_work); + flush_work(&ctx->seq_end_work); + } + spin_lock(&ctx->buffer_meta_lock); + while (!list_empty(&ctx->buffer_meta_list)) { + meta = list_first_entry(&ctx->buffer_meta_list, + struct coda_buffer_meta, list); + list_del(&meta->list); + kfree(meta); + } + ctx->num_metas = 0; + spin_unlock(&ctx->buffer_meta_lock); + kfifo_init(&ctx->bitstream_fifo, + ctx->bitstream.vaddr, ctx->bitstream.size); + ctx->runcounter = 0; + ctx->aborting = 0; + ctx->hold = false; + } + + if (!ctx->streamon_out && !ctx->streamon_cap) + ctx->bit_stream_param &= ~CODA_BIT_STREAM_END_FLAG; +} + +static const struct vb2_ops coda_qops = { + .queue_setup = coda_queue_setup, + .buf_prepare = coda_buf_prepare, + .buf_queue = coda_buf_queue, + .start_streaming = coda_start_streaming, + .stop_streaming = coda_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int coda_s_ctrl(struct v4l2_ctrl *ctrl) +{ + const char * const *val_names = v4l2_ctrl_get_menu(ctrl->id); + struct coda_ctx *ctx = + container_of(ctrl->handler, struct coda_ctx, ctrls); + + if (val_names) + coda_dbg(2, ctx, "s_ctrl: id = 0x%x, name = \"%s\", val = %d (\"%s\")\n", + ctrl->id, ctrl->name, ctrl->val, val_names[ctrl->val]); + else + coda_dbg(2, ctx, "s_ctrl: id = 0x%x, name = \"%s\", val = %d\n", + ctrl->id, ctrl->name, ctrl->val); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctrl->val) + ctx->params.rot_mode |= CODA_MIR_HOR; + else + ctx->params.rot_mode &= ~CODA_MIR_HOR; + break; + case V4L2_CID_VFLIP: + if (ctrl->val) + ctx->params.rot_mode |= CODA_MIR_VER; + else + ctx->params.rot_mode &= ~CODA_MIR_VER; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + ctx->params.bitrate = ctrl->val / 1000; + ctx->params.bitrate_changed = true; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ctx->params.gop_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + ctx->params.h264_intra_qp = ctrl->val; + ctx->params.h264_intra_qp_changed = true; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + ctx->params.h264_inter_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + ctx->params.h264_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + ctx->params.h264_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA: + ctx->params.h264_slice_alpha_c0_offset_div2 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA: + ctx->params.h264_slice_beta_offset_div2 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: + ctx->params.h264_disable_deblocking_filter_idc = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_CONSTRAINED_INTRA_PREDICTION: + ctx->params.h264_constrained_intra_pred_flag = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + ctx->params.frame_rc_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE: + ctx->params.mb_rc_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_CHROMA_QP_INDEX_OFFSET: + ctx->params.h264_chroma_qp_index_offset = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + /* TODO: switch between baseline and constrained baseline */ + if (ctx->inst_type == CODA_INST_ENCODER) + ctx->params.h264_profile_idc = 66; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + /* nothing to do, this is set by the encoder */ + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP: + ctx->params.mpeg4_intra_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP: + ctx->params.mpeg4_inter_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE: + case V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL: + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + /* nothing to do, these are fixed */ + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + ctx->params.slice_mode = ctrl->val; + ctx->params.slice_mode_changed = true; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + ctx->params.slice_max_mb = ctrl->val; + ctx->params.slice_mode_changed = true; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + ctx->params.slice_max_bits = ctrl->val * 8; + ctx->params.slice_mode_changed = true; + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + break; + case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB: + ctx->params.intra_refresh = ctrl->val; + ctx->params.intra_refresh_changed = true; + break; + case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: + ctx->params.force_ipicture = true; + break; + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + coda_set_jpeg_compression_quality(ctx, ctrl->val); + break; + case V4L2_CID_JPEG_RESTART_INTERVAL: + ctx->params.jpeg_restart_interval = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VBV_DELAY: + ctx->params.vbv_delay = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VBV_SIZE: + ctx->params.vbv_size = min(ctrl->val * 8192, 0x7fffffff); + break; + default: + coda_dbg(1, ctx, "Invalid control, id=%d, val=%d\n", + ctrl->id, ctrl->val); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops coda_ctrl_ops = { + .s_ctrl = coda_s_ctrl, +}; + +static void coda_encode_ctrls(struct coda_ctx *ctx) +{ + int max_gop_size = (ctx->dev->devtype->product == CODA_DX6) ? 60 : 99; + + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, 0, 32767000, 1000, 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 0, max_gop_size, 1, 16); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, 0, 51, 1, 25); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, 0, 51, 1, 25); + if (ctx->dev->devtype->product != CODA_960) { + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 0, 51, 1, 12); + } + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 0, 51, 1, 51); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA, -6, 6, 1, 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA, -6, 6, 1, 0); + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, + V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY, + 0x0, V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_CONSTRAINED_INTRA_PREDICTION, 0, 1, 1, + 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, 0, 1, 1, 1); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE, 0, 1, 1, 1); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_CHROMA_QP_INDEX_OFFSET, -12, 12, 1, 0); + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE, 0x0, + V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE); + if (ctx->dev->devtype->product == CODA_HX4 || + ctx->dev->devtype->product == CODA_7541) { + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_3_1, + ~((1 << V4L2_MPEG_VIDEO_H264_LEVEL_2_0) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_3_0) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_3_1)), + V4L2_MPEG_VIDEO_H264_LEVEL_3_1); + } + if (ctx->dev->devtype->product == CODA_960) { + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_4_2, + ~((1 << V4L2_MPEG_VIDEO_H264_LEVEL_1_0) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_2_0) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_3_0) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_3_1) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_3_2) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_4_0) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_4_1) | + (1 << V4L2_MPEG_VIDEO_H264_LEVEL_4_2)), + V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + } + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP, 1, 31, 1, 2); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP, 1, 31, 1, 2); + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE, 0x0, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE); + if (ctx->dev->devtype->product == CODA_HX4 || + ctx->dev->devtype->product == CODA_7541 || + ctx->dev->devtype->product == CODA_960) { + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5, + ~(1 << V4L2_MPEG_VIDEO_MPEG4_LEVEL_5), + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5); + } + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE, + V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES, 0x0, + V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB, 1, 0x3fffffff, 1, 1); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES, 1, 0x3fffffff, 1, + 500); + v4l2_ctrl_new_std_menu(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEADER_MODE, + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + (1 << V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE), + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB, 0, + 1920 * 1088 / 256, 1, 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VBV_DELAY, 0, 0x7fff, 1, 0); + /* + * The maximum VBV size value is 0x7fffffff bits, + * one bit less than 262144 KiB + */ + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VBV_SIZE, 0, 262144, 1, 0); +} + +static void coda_jpeg_encode_ctrls(struct coda_ctx *ctx) +{ + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, 5, 100, 1, 50); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_JPEG_RESTART_INTERVAL, 0, 100, 1, 0); +} + +static void coda_decode_ctrls(struct coda_ctx *ctx) +{ + u8 max; + + ctx->h264_profile_ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrls, + &coda_ctrl_ops, V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + if (ctx->h264_profile_ctrl) + ctx->h264_profile_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + if (ctx->dev->devtype->product == CODA_HX4 || + ctx->dev->devtype->product == CODA_7541) + max = V4L2_MPEG_VIDEO_H264_LEVEL_4_0; + else if (ctx->dev->devtype->product == CODA_960) + max = V4L2_MPEG_VIDEO_H264_LEVEL_4_1; + else + return; + ctx->h264_level_ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrls, + &coda_ctrl_ops, V4L2_CID_MPEG_VIDEO_H264_LEVEL, max, 0, max); + if (ctx->h264_level_ctrl) + ctx->h264_level_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctx->mpeg2_profile_ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrls, + &coda_ctrl_ops, V4L2_CID_MPEG_VIDEO_MPEG2_PROFILE, + V4L2_MPEG_VIDEO_MPEG2_PROFILE_HIGH, 0, + V4L2_MPEG_VIDEO_MPEG2_PROFILE_HIGH); + if (ctx->mpeg2_profile_ctrl) + ctx->mpeg2_profile_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctx->mpeg2_level_ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrls, + &coda_ctrl_ops, V4L2_CID_MPEG_VIDEO_MPEG2_LEVEL, + V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH, 0, + V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH); + if (ctx->mpeg2_level_ctrl) + ctx->mpeg2_level_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctx->mpeg4_profile_ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrls, + &coda_ctrl_ops, V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY, 0, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY); + if (ctx->mpeg4_profile_ctrl) + ctx->mpeg4_profile_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctx->mpeg4_level_ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrls, + &coda_ctrl_ops, V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5, 0, + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5); + if (ctx->mpeg4_level_ctrl) + ctx->mpeg4_level_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; +} + +static const struct v4l2_ctrl_config coda_mb_err_cnt_ctrl_config = { + .id = V4L2_CID_CODA_MB_ERR_CNT, + .name = "Macroblocks Error Count", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0, + .max = 0x7fffffff, + .step = 1, +}; + +static int coda_ctrls_setup(struct coda_ctx *ctx) +{ + v4l2_ctrl_handler_init(&ctx->ctrls, 2); + + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (ctx->inst_type == CODA_INST_ENCODER) { + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, + 1, 1, 1, 1); + if (ctx->cvd->dst_formats[0] == V4L2_PIX_FMT_JPEG) + coda_jpeg_encode_ctrls(ctx); + else + coda_encode_ctrls(ctx); + } else { + v4l2_ctrl_new_std(&ctx->ctrls, &coda_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, + 1, 1, 1, 1); + if (ctx->cvd->src_formats[0] == V4L2_PIX_FMT_H264) + coda_decode_ctrls(ctx); + + ctx->mb_err_cnt_ctrl = v4l2_ctrl_new_custom(&ctx->ctrls, + &coda_mb_err_cnt_ctrl_config, + NULL); + if (ctx->mb_err_cnt_ctrl) + ctx->mb_err_cnt_ctrl->flags |= V4L2_CTRL_FLAG_READ_ONLY; + } + + if (ctx->ctrls.error) { + v4l2_err(&ctx->dev->v4l2_dev, + "control initialization error (%d)", + ctx->ctrls.error); + return -EINVAL; + } + + return v4l2_ctrl_handler_setup(&ctx->ctrls); +} + +static int coda_queue_init(struct coda_ctx *ctx, struct vb2_queue *vq) +{ + vq->drv_priv = ctx; + vq->ops = &coda_qops; + vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + vq->lock = &ctx->dev->dev_mutex; + /* One way to indicate end-of-stream for coda is to set the + * bytesused == 0. However by default videobuf2 handles bytesused + * equal to 0 as a special case and changes its value to the size + * of the buffer. Set the allow_zero_bytesused flag, so + * that videobuf2 will keep the value of bytesused intact. + */ + vq->allow_zero_bytesused = 1; + /* + * We might be fine with no buffers on some of the queues, but that + * would need to be reflected in job_ready(). Currently we expect all + * queues to have at least one buffer queued. + */ + vq->min_buffers_needed = 1; + vq->dev = ctx->dev->dev; + + return vb2_queue_init(vq); +} + +int coda_encoder_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_DMABUF | VB2_MMAP; + src_vq->mem_ops = &vb2_dma_contig_memops; + + ret = coda_queue_init(priv, src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_DMABUF | VB2_MMAP; + dst_vq->mem_ops = &vb2_dma_contig_memops; + + return coda_queue_init(priv, dst_vq); +} + +int coda_decoder_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_DMABUF | VB2_MMAP | VB2_USERPTR; + src_vq->mem_ops = &vb2_vmalloc_memops; + + ret = coda_queue_init(priv, src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_DMABUF | VB2_MMAP; + dst_vq->dma_attrs = DMA_ATTR_NO_KERNEL_MAPPING; + dst_vq->mem_ops = &vb2_dma_contig_memops; + + return coda_queue_init(priv, dst_vq); +} + +/* + * File operations + */ + +static int coda_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct coda_dev *dev = video_get_drvdata(vdev); + struct coda_ctx *ctx; + unsigned int max = ~0; + char *name; + int ret; + int idx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (dev->devtype->product == CODA_DX6) + max = CODADX6_MAX_INSTANCES - 1; + idx = ida_alloc_max(&dev->ida, max, GFP_KERNEL); + if (idx < 0) { + ret = idx; + goto err_coda_max; + } + + name = kasprintf(GFP_KERNEL, "context%d", idx); + if (!name) { + ret = -ENOMEM; + goto err_coda_name_init; + } + + ctx->debugfs_entry = debugfs_create_dir(name, dev->debugfs_root); + kfree(name); + + ctx->cvd = to_coda_video_device(vdev); + ctx->inst_type = ctx->cvd->type; + ctx->ops = ctx->cvd->ops; + ctx->use_bit = !ctx->cvd->direct; + init_completion(&ctx->completion); + INIT_WORK(&ctx->pic_run_work, coda_pic_run_work); + if (ctx->ops->seq_init_work) + INIT_WORK(&ctx->seq_init_work, ctx->ops->seq_init_work); + if (ctx->ops->seq_end_work) + INIT_WORK(&ctx->seq_end_work, ctx->ops->seq_end_work); + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + ctx->dev = dev; + ctx->idx = idx; + + coda_dbg(1, ctx, "open instance (%p)\n", ctx); + + switch (dev->devtype->product) { + case CODA_960: + /* + * Enabling the BWB when decoding can hang the firmware with + * certain streams. The issue was tracked as ENGR00293425 by + * Freescale. As a workaround, disable BWB for all decoders. + * The enable_bwb module parameter allows to override this. + */ + if (enable_bwb || ctx->inst_type == CODA_INST_ENCODER) + ctx->frame_mem_ctrl = CODA9_FRAME_ENABLE_BWB; + fallthrough; + case CODA_HX4: + case CODA_7541: + ctx->reg_idx = 0; + break; + default: + ctx->reg_idx = idx; + } + if (ctx->dev->vdoa && !disable_vdoa) { + ctx->vdoa = vdoa_context_create(dev->vdoa); + if (!ctx->vdoa) + v4l2_warn(&dev->v4l2_dev, + "Failed to create vdoa context: not using vdoa"); + } + ctx->use_vdoa = false; + + /* Power up and upload firmware if necessary */ + ret = pm_runtime_resume_and_get(dev->dev); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to power up: %d\n", ret); + goto err_pm_get; + } + + ret = clk_prepare_enable(dev->clk_per); + if (ret) + goto err_clk_enable; + + ret = clk_prepare_enable(dev->clk_ahb); + if (ret) + goto err_clk_ahb; + + set_default_params(ctx); + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, + ctx->ops->queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + + v4l2_err(&dev->v4l2_dev, "%s return error (%d)\n", + __func__, ret); + goto err_ctx_init; + } + + ret = coda_ctrls_setup(ctx); + if (ret) { + v4l2_err(&dev->v4l2_dev, "failed to setup coda controls\n"); + goto err_ctrls_setup; + } + + ctx->fh.ctrl_handler = &ctx->ctrls; + + mutex_init(&ctx->bitstream_mutex); + mutex_init(&ctx->buffer_mutex); + mutex_init(&ctx->wakeup_mutex); + INIT_LIST_HEAD(&ctx->buffer_meta_list); + spin_lock_init(&ctx->buffer_meta_lock); + + return 0; + +err_ctrls_setup: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); +err_ctx_init: + clk_disable_unprepare(dev->clk_ahb); +err_clk_ahb: + clk_disable_unprepare(dev->clk_per); +err_clk_enable: + pm_runtime_put_sync(dev->dev); +err_pm_get: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); +err_coda_name_init: + ida_free(&dev->ida, ctx->idx); +err_coda_max: + kfree(ctx); + return ret; +} + +static int coda_release(struct file *file) +{ + struct coda_dev *dev = video_drvdata(file); + struct coda_ctx *ctx = fh_to_ctx(file->private_data); + + coda_dbg(1, ctx, "release instance (%p)\n", ctx); + + if (ctx->inst_type == CODA_INST_DECODER && ctx->use_bit) + coda_bit_stream_end_flag(ctx); + + /* If this instance is running, call .job_abort and wait for it to end */ + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + if (ctx->vdoa) + vdoa_context_destroy(ctx->vdoa); + + /* In case the instance was not running, we still need to call SEQ_END */ + if (ctx->ops->seq_end_work) { + queue_work(dev->workqueue, &ctx->seq_end_work); + flush_work(&ctx->seq_end_work); + } + + if (ctx->dev->devtype->product == CODA_DX6) + coda_free_aux_buf(dev, &ctx->workbuf); + + v4l2_ctrl_handler_free(&ctx->ctrls); + clk_disable_unprepare(dev->clk_ahb); + clk_disable_unprepare(dev->clk_per); + pm_runtime_put_sync(dev->dev); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + ida_free(&dev->ida, ctx->idx); + if (ctx->ops->release) + ctx->ops->release(ctx); + debugfs_remove_recursive(ctx->debugfs_entry); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations coda_fops = { + .owner = THIS_MODULE, + .open = coda_open, + .release = coda_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int coda_hw_init(struct coda_dev *dev) +{ + u32 data; + u16 *p; + int i, ret; + + ret = clk_prepare_enable(dev->clk_per); + if (ret) + goto err_clk_per; + + ret = clk_prepare_enable(dev->clk_ahb); + if (ret) + goto err_clk_ahb; + + reset_control_reset(dev->rstc); + + /* + * Copy the first CODA_ISRAM_SIZE in the internal SRAM. + * The 16-bit chars in the code buffer are in memory access + * order, re-sort them to CODA order for register download. + * Data in this SRAM survives a reboot. + */ + p = (u16 *)dev->codebuf.vaddr; + if (dev->devtype->product == CODA_DX6) { + for (i = 0; i < (CODA_ISRAM_SIZE / 2); i++) { + data = CODA_DOWN_ADDRESS_SET(i) | + CODA_DOWN_DATA_SET(p[i ^ 1]); + coda_write(dev, data, CODA_REG_BIT_CODE_DOWN); + } + } else { + for (i = 0; i < (CODA_ISRAM_SIZE / 2); i++) { + data = CODA_DOWN_ADDRESS_SET(i) | + CODA_DOWN_DATA_SET(p[round_down(i, 4) + + 3 - (i % 4)]); + coda_write(dev, data, CODA_REG_BIT_CODE_DOWN); + } + } + + /* Clear registers */ + for (i = 0; i < 64; i++) + coda_write(dev, 0, CODA_REG_BIT_CODE_BUF_ADDR + i * 4); + + /* Tell the BIT where to find everything it needs */ + if (dev->devtype->product == CODA_960 || + dev->devtype->product == CODA_7541 || + dev->devtype->product == CODA_HX4) { + coda_write(dev, dev->tempbuf.paddr, + CODA_REG_BIT_TEMP_BUF_ADDR); + coda_write(dev, 0, CODA_REG_BIT_BIT_STREAM_PARAM); + } else { + coda_write(dev, dev->workbuf.paddr, + CODA_REG_BIT_WORK_BUF_ADDR); + } + coda_write(dev, dev->codebuf.paddr, + CODA_REG_BIT_CODE_BUF_ADDR); + coda_write(dev, 0, CODA_REG_BIT_CODE_RUN); + + /* Set default values */ + switch (dev->devtype->product) { + case CODA_DX6: + coda_write(dev, CODADX6_STREAM_BUF_PIC_FLUSH, + CODA_REG_BIT_STREAM_CTRL); + break; + default: + coda_write(dev, CODA7_STREAM_BUF_PIC_FLUSH, + CODA_REG_BIT_STREAM_CTRL); + } + if (dev->devtype->product == CODA_960) + coda_write(dev, CODA9_FRAME_ENABLE_BWB, + CODA_REG_BIT_FRAME_MEM_CTRL); + else + coda_write(dev, 0, CODA_REG_BIT_FRAME_MEM_CTRL); + + if (dev->devtype->product != CODA_DX6) + coda_write(dev, 0, CODA7_REG_BIT_AXI_SRAM_USE); + + coda_write(dev, CODA_INT_INTERRUPT_ENABLE, + CODA_REG_BIT_INT_ENABLE); + + /* Reset VPU and start processor */ + data = coda_read(dev, CODA_REG_BIT_CODE_RESET); + data |= CODA_REG_RESET_ENABLE; + coda_write(dev, data, CODA_REG_BIT_CODE_RESET); + udelay(10); + data &= ~CODA_REG_RESET_ENABLE; + coda_write(dev, data, CODA_REG_BIT_CODE_RESET); + coda_write(dev, CODA_REG_RUN_ENABLE, CODA_REG_BIT_CODE_RUN); + + clk_disable_unprepare(dev->clk_ahb); + clk_disable_unprepare(dev->clk_per); + + return 0; + +err_clk_ahb: + clk_disable_unprepare(dev->clk_per); +err_clk_per: + return ret; +} + +static int coda_register_device(struct coda_dev *dev, int i) +{ + struct video_device *vfd = &dev->vfd[i]; + const char *name; + int ret; + + if (i >= dev->devtype->num_vdevs) + return -EINVAL; + name = dev->devtype->vdevs[i]->name; + + strscpy(vfd->name, dev->devtype->vdevs[i]->name, sizeof(vfd->name)); + vfd->fops = &coda_fops; + vfd->ioctl_ops = &coda_ioctl_ops; + vfd->release = video_device_release_empty; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->vfl_dir = VFL_DIR_M2M; + vfd->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; + video_set_drvdata(vfd, dev); + + /* Not applicable, use the selection API instead */ + v4l2_disable_ioctl(vfd, VIDIOC_CROPCAP); + v4l2_disable_ioctl(vfd, VIDIOC_G_CROP); + v4l2_disable_ioctl(vfd, VIDIOC_S_CROP); + + if (dev->devtype->vdevs[i]->type == CODA_INST_ENCODER) { + v4l2_disable_ioctl(vfd, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_DECODER_CMD); + if (dev->devtype->vdevs[i]->dst_formats[0] == V4L2_PIX_FMT_JPEG) { + v4l2_disable_ioctl(vfd, VIDIOC_ENUM_FRAMEINTERVALS); + v4l2_disable_ioctl(vfd, VIDIOC_G_PARM); + v4l2_disable_ioctl(vfd, VIDIOC_S_PARM); + } + } else { + v4l2_disable_ioctl(vfd, VIDIOC_ENCODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_TRY_ENCODER_CMD); + v4l2_disable_ioctl(vfd, VIDIOC_ENUM_FRAMESIZES); + v4l2_disable_ioctl(vfd, VIDIOC_ENUM_FRAMEINTERVALS); + v4l2_disable_ioctl(vfd, VIDIOC_G_PARM); + v4l2_disable_ioctl(vfd, VIDIOC_S_PARM); + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (!ret) + v4l2_info(&dev->v4l2_dev, "%s registered as %s\n", + name, video_device_node_name(vfd)); + return ret; +} + +static void coda_copy_firmware(struct coda_dev *dev, const u8 * const buf, + size_t size) +{ + u32 *src = (u32 *)buf; + + /* Check if the firmware has a 16-byte Freescale header, skip it */ + if (buf[0] == 'M' && buf[1] == 'X') + src += 4; + /* + * Check whether the firmware is in native order or pre-reordered for + * memory access. The first instruction opcode always is 0xe40e. + */ + if (__le16_to_cpup((__le16 *)src) == 0xe40e) { + u32 *dst = dev->codebuf.vaddr; + int i; + + /* Firmware in native order, reorder while copying */ + if (dev->devtype->product == CODA_DX6) { + for (i = 0; i < (size - 16) / 4; i++) + dst[i] = (src[i] << 16) | (src[i] >> 16); + } else { + for (i = 0; i < (size - 16) / 4; i += 2) { + dst[i] = (src[i + 1] << 16) | (src[i + 1] >> 16); + dst[i + 1] = (src[i] << 16) | (src[i] >> 16); + } + } + } else { + /* Copy the already reordered firmware image */ + memcpy(dev->codebuf.vaddr, src, size); + } +} + +static void coda_fw_callback(const struct firmware *fw, void *context); + +static int coda_firmware_request(struct coda_dev *dev) +{ + char *fw; + + if (dev->firmware >= ARRAY_SIZE(dev->devtype->firmware)) + return -EINVAL; + + fw = dev->devtype->firmware[dev->firmware]; + + dev_dbg(dev->dev, "requesting firmware '%s' for %s\n", fw, + coda_product_name(dev->devtype->product)); + + return request_firmware_nowait(THIS_MODULE, true, fw, dev->dev, + GFP_KERNEL, dev, coda_fw_callback); +} + +static void coda_fw_callback(const struct firmware *fw, void *context) +{ + struct coda_dev *dev = context; + int i, ret; + + if (!fw) { + dev->firmware++; + ret = coda_firmware_request(dev); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "firmware request failed\n"); + goto put_pm; + } + return; + } + if (dev->firmware > 0) { + /* + * Since we can't suppress warnings for failed asynchronous + * firmware requests, report that the fallback firmware was + * found. + */ + dev_info(dev->dev, "Using fallback firmware %s\n", + dev->devtype->firmware[dev->firmware]); + } + + /* allocate auxiliary per-device code buffer for the BIT processor */ + ret = coda_alloc_aux_buf(dev, &dev->codebuf, fw->size, "codebuf", + dev->debugfs_root); + if (ret < 0) + goto put_pm; + + coda_copy_firmware(dev, fw->data, fw->size); + release_firmware(fw); + + ret = coda_hw_init(dev); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "HW initialization failed\n"); + goto put_pm; + } + + ret = coda_check_firmware(dev); + if (ret < 0) + goto put_pm; + + dev->m2m_dev = v4l2_m2m_init(&coda_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + goto put_pm; + } + + for (i = 0; i < dev->devtype->num_vdevs; i++) { + ret = coda_register_device(dev, i); + if (ret) { + v4l2_err(&dev->v4l2_dev, + "Failed to register %s video device: %d\n", + dev->devtype->vdevs[i]->name, ret); + goto rel_vfd; + } + } + + pm_runtime_put_sync(dev->dev); + return; + +rel_vfd: + while (--i >= 0) + video_unregister_device(&dev->vfd[i]); + v4l2_m2m_release(dev->m2m_dev); +put_pm: + pm_runtime_put_sync(dev->dev); +} + +enum coda_platform { + CODA_IMX27, + CODA_IMX51, + CODA_IMX53, + CODA_IMX6Q, + CODA_IMX6DL, +}; + +static const struct coda_devtype coda_devdata[] = { + [CODA_IMX27] = { + .firmware = { + "vpu_fw_imx27_TO2.bin", + "vpu/vpu_fw_imx27_TO2.bin", + "v4l-codadx6-imx27.bin" + }, + .product = CODA_DX6, + .codecs = codadx6_codecs, + .num_codecs = ARRAY_SIZE(codadx6_codecs), + .vdevs = codadx6_video_devices, + .num_vdevs = ARRAY_SIZE(codadx6_video_devices), + .workbuf_size = 288 * 1024 + FMO_SLICE_SAVE_BUF_SIZE * 8 * 1024, + .iram_size = 0xb000, + }, + [CODA_IMX51] = { + .firmware = { + "vpu_fw_imx51.bin", + "vpu/vpu_fw_imx51.bin", + "v4l-codahx4-imx51.bin" + }, + .product = CODA_HX4, + .codecs = codahx4_codecs, + .num_codecs = ARRAY_SIZE(codahx4_codecs), + .vdevs = codahx4_video_devices, + .num_vdevs = ARRAY_SIZE(codahx4_video_devices), + .workbuf_size = 128 * 1024, + .tempbuf_size = 304 * 1024, + .iram_size = 0x14000, + }, + [CODA_IMX53] = { + .firmware = { + "vpu_fw_imx53.bin", + "vpu/vpu_fw_imx53.bin", + "v4l-coda7541-imx53.bin" + }, + .product = CODA_7541, + .codecs = coda7_codecs, + .num_codecs = ARRAY_SIZE(coda7_codecs), + .vdevs = coda7_video_devices, + .num_vdevs = ARRAY_SIZE(coda7_video_devices), + .workbuf_size = 128 * 1024, + .tempbuf_size = 304 * 1024, + .iram_size = 0x14000, + }, + [CODA_IMX6Q] = { + .firmware = { + "vpu_fw_imx6q.bin", + "vpu/vpu_fw_imx6q.bin", + "v4l-coda960-imx6q.bin" + }, + .product = CODA_960, + .codecs = coda9_codecs, + .num_codecs = ARRAY_SIZE(coda9_codecs), + .vdevs = coda9_video_devices, + .num_vdevs = ARRAY_SIZE(coda9_video_devices), + .workbuf_size = 80 * 1024, + .tempbuf_size = 204 * 1024, + .iram_size = 0x21000, + }, + [CODA_IMX6DL] = { + .firmware = { + "vpu_fw_imx6d.bin", + "vpu/vpu_fw_imx6d.bin", + "v4l-coda960-imx6dl.bin" + }, + .product = CODA_960, + .codecs = coda9_codecs, + .num_codecs = ARRAY_SIZE(coda9_codecs), + .vdevs = coda9_video_devices, + .num_vdevs = ARRAY_SIZE(coda9_video_devices), + .workbuf_size = 80 * 1024, + .tempbuf_size = 204 * 1024, + .iram_size = 0x1f000, /* leave 4k for suspend code */ + }, +}; + +static const struct of_device_id coda_dt_ids[] = { + { .compatible = "fsl,imx27-vpu", .data = &coda_devdata[CODA_IMX27] }, + { .compatible = "fsl,imx51-vpu", .data = &coda_devdata[CODA_IMX51] }, + { .compatible = "fsl,imx53-vpu", .data = &coda_devdata[CODA_IMX53] }, + { .compatible = "fsl,imx6q-vpu", .data = &coda_devdata[CODA_IMX6Q] }, + { .compatible = "fsl,imx6dl-vpu", .data = &coda_devdata[CODA_IMX6DL] }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, coda_dt_ids); + +static int coda_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct gen_pool *pool; + struct coda_dev *dev; + int ret, irq; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->devtype = of_device_get_match_data(&pdev->dev); + + dev->dev = &pdev->dev; + dev->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(dev->clk_per)) { + dev_err(&pdev->dev, "Could not get per clock\n"); + return PTR_ERR(dev->clk_per); + } + + dev->clk_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(dev->clk_ahb)) { + dev_err(&pdev->dev, "Could not get ahb clock\n"); + return PTR_ERR(dev->clk_ahb); + } + + /* Get memory for physical registers */ + dev->regs_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->regs_base)) + return PTR_ERR(dev->regs_base); + + /* IRQ */ + irq = platform_get_irq_byname(pdev, "bit"); + if (irq < 0) + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(&pdev->dev, irq, coda_irq_handler, 0, + CODA_NAME "-video", dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request irq: %d\n", ret); + return ret; + } + + /* JPEG IRQ */ + if (dev->devtype->product == CODA_960) { + irq = platform_get_irq_byname(pdev, "jpeg"); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + coda9_jpeg_irq_handler, + IRQF_ONESHOT, CODA_NAME "-jpeg", + dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request jpeg irq\n"); + return ret; + } + } + + dev->rstc = devm_reset_control_get_optional_exclusive(&pdev->dev, + NULL); + if (IS_ERR(dev->rstc)) { + ret = PTR_ERR(dev->rstc); + dev_err(&pdev->dev, "failed get reset control: %d\n", ret); + return ret; + } + + /* Get IRAM pool from device tree */ + pool = of_gen_pool_get(np, "iram", 0); + if (!pool) { + dev_err(&pdev->dev, "iram pool not available\n"); + return -ENOMEM; + } + dev->iram_pool = pool; + + /* Get vdoa_data if supported by the platform */ + dev->vdoa = coda_get_vdoa_data(); + if (PTR_ERR(dev->vdoa) == -EPROBE_DEFER) + return -EPROBE_DEFER; + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + return ret; + + ratelimit_default_init(&dev->mb_err_rs); + mutex_init(&dev->dev_mutex); + mutex_init(&dev->coda_mutex); + ida_init(&dev->ida); + + dev->debugfs_root = debugfs_create_dir("coda", NULL); + + /* allocate auxiliary per-device buffers for the BIT processor */ + if (dev->devtype->product == CODA_DX6) { + ret = coda_alloc_aux_buf(dev, &dev->workbuf, + dev->devtype->workbuf_size, "workbuf", + dev->debugfs_root); + if (ret < 0) + goto err_v4l2_register; + } + + if (dev->devtype->tempbuf_size) { + ret = coda_alloc_aux_buf(dev, &dev->tempbuf, + dev->devtype->tempbuf_size, "tempbuf", + dev->debugfs_root); + if (ret < 0) + goto err_v4l2_register; + } + + dev->iram.size = dev->devtype->iram_size; + dev->iram.vaddr = gen_pool_dma_alloc(dev->iram_pool, dev->iram.size, + &dev->iram.paddr); + if (!dev->iram.vaddr) { + dev_warn(&pdev->dev, "unable to alloc iram\n"); + } else { + memset(dev->iram.vaddr, 0, dev->iram.size); + dev->iram.blob.data = dev->iram.vaddr; + dev->iram.blob.size = dev->iram.size; + dev->iram.dentry = debugfs_create_blob("iram", 0444, + dev->debugfs_root, + &dev->iram.blob); + } + + dev->workqueue = alloc_workqueue("coda", WQ_UNBOUND | WQ_MEM_RECLAIM, 1); + if (!dev->workqueue) { + dev_err(&pdev->dev, "unable to alloc workqueue\n"); + ret = -ENOMEM; + goto err_v4l2_register; + } + + platform_set_drvdata(pdev, dev); + + /* + * Start activated so we can directly call coda_hw_init in + * coda_fw_callback regardless of whether CONFIG_PM is + * enabled or whether the device is associated with a PM domain. + */ + pm_runtime_get_noresume(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = coda_firmware_request(dev); + if (ret) + goto err_alloc_workqueue; + return 0; + +err_alloc_workqueue: + pm_runtime_disable(&pdev->dev); + pm_runtime_put_noidle(&pdev->dev); + destroy_workqueue(dev->workqueue); +err_v4l2_register: + v4l2_device_unregister(&dev->v4l2_dev); + return ret; +} + +static int coda_remove(struct platform_device *pdev) +{ + struct coda_dev *dev = platform_get_drvdata(pdev); + int i; + + for (i = 0; i < ARRAY_SIZE(dev->vfd); i++) { + if (video_get_drvdata(&dev->vfd[i])) + video_unregister_device(&dev->vfd[i]); + } + if (dev->m2m_dev) + v4l2_m2m_release(dev->m2m_dev); + pm_runtime_disable(&pdev->dev); + v4l2_device_unregister(&dev->v4l2_dev); + destroy_workqueue(dev->workqueue); + if (dev->iram.vaddr) + gen_pool_free(dev->iram_pool, (unsigned long)dev->iram.vaddr, + dev->iram.size); + coda_free_aux_buf(dev, &dev->codebuf); + coda_free_aux_buf(dev, &dev->tempbuf); + coda_free_aux_buf(dev, &dev->workbuf); + debugfs_remove_recursive(dev->debugfs_root); + ida_destroy(&dev->ida); + return 0; +} + +#ifdef CONFIG_PM +static int coda_runtime_resume(struct device *dev) +{ + struct coda_dev *cdev = dev_get_drvdata(dev); + int ret = 0; + + if (dev->pm_domain && cdev->codebuf.vaddr) { + ret = coda_hw_init(cdev); + if (ret) + v4l2_err(&cdev->v4l2_dev, "HW initialization failed\n"); + } + + return ret; +} +#endif + +static const struct dev_pm_ops coda_pm_ops = { + SET_RUNTIME_PM_OPS(NULL, coda_runtime_resume, NULL) +}; + +static struct platform_driver coda_driver = { + .probe = coda_probe, + .remove = coda_remove, + .driver = { + .name = CODA_NAME, + .of_match_table = coda_dt_ids, + .pm = &coda_pm_ops, + }, +}; + +module_platform_driver(coda_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Javier Martin "); +MODULE_DESCRIPTION("Coda multi-standard codec V4L2 driver"); diff --git a/drivers/media/platform/chips-media/coda-gdi.c b/drivers/media/platform/chips-media/coda-gdi.c new file mode 100644 index 000000000..59d65daca --- /dev/null +++ b/drivers/media/platform/chips-media/coda-gdi.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Coda multi-standard codec IP + * + * Copyright (C) 2014 Philipp Zabel, Pengutronix + */ + +#include +#include "coda.h" + +#define XY2_INVERT BIT(7) +#define XY2_ZERO BIT(6) +#define XY2_TB_XOR BIT(5) +#define XY2_XYSEL BIT(4) +#define XY2_Y (1 << 4) +#define XY2_X (0 << 4) + +#define XY2(luma_sel, luma_bit, chroma_sel, chroma_bit) \ + (((XY2_##luma_sel) | (luma_bit)) << 8 | \ + (XY2_##chroma_sel) | (chroma_bit)) + +static const u16 xy2ca_zero_map[16] = { + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), +}; + +static const u16 xy2ca_tiled_map[16] = { + XY2(Y, 0, Y, 0), + XY2(Y, 1, Y, 1), + XY2(Y, 2, Y, 2), + XY2(Y, 3, X, 3), + XY2(X, 3, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), + XY2(ZERO, 0, ZERO, 0), +}; + +/* + * RA[15:0], CA[15:8] are hardwired to contain the 24-bit macroblock + * start offset (macroblock size is 16x16 for luma, 16x8 for chroma). + * Bits CA[4:0] are set using XY2CA above. BA[3:0] seems to be unused. + */ + +#define RBC_CA (0 << 4) +#define RBC_BA (1 << 4) +#define RBC_RA (2 << 4) +#define RBC_ZERO (3 << 4) + +#define RBC(luma_sel, luma_bit, chroma_sel, chroma_bit) \ + (((RBC_##luma_sel) | (luma_bit)) << 6 | \ + (RBC_##chroma_sel) | (chroma_bit)) + +static const u16 rbc2axi_tiled_map[32] = { + RBC(ZERO, 0, ZERO, 0), + RBC(ZERO, 0, ZERO, 0), + RBC(ZERO, 0, ZERO, 0), + RBC(CA, 0, CA, 0), + RBC(CA, 1, CA, 1), + RBC(CA, 2, CA, 2), + RBC(CA, 3, CA, 3), + RBC(CA, 4, CA, 8), + RBC(CA, 8, CA, 9), + RBC(CA, 9, CA, 10), + RBC(CA, 10, CA, 11), + RBC(CA, 11, CA, 12), + RBC(CA, 12, CA, 13), + RBC(CA, 13, CA, 14), + RBC(CA, 14, CA, 15), + RBC(CA, 15, RA, 0), + RBC(RA, 0, RA, 1), + RBC(RA, 1, RA, 2), + RBC(RA, 2, RA, 3), + RBC(RA, 3, RA, 4), + RBC(RA, 4, RA, 5), + RBC(RA, 5, RA, 6), + RBC(RA, 6, RA, 7), + RBC(RA, 7, RA, 8), + RBC(RA, 8, RA, 9), + RBC(RA, 9, RA, 10), + RBC(RA, 10, RA, 11), + RBC(RA, 11, RA, 12), + RBC(RA, 12, RA, 13), + RBC(RA, 13, RA, 14), + RBC(RA, 14, RA, 15), + RBC(RA, 15, ZERO, 0), +}; + +void coda_set_gdi_regs(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + const u16 *xy2ca_map; + u32 xy2rbc_config; + int i; + + switch (ctx->tiled_map_type) { + case GDI_LINEAR_FRAME_MAP: + default: + xy2ca_map = xy2ca_zero_map; + xy2rbc_config = 0; + break; + case GDI_TILED_FRAME_MB_RASTER_MAP: + xy2ca_map = xy2ca_tiled_map; + xy2rbc_config = CODA9_XY2RBC_TILED_MAP | + CODA9_XY2RBC_CA_INC_HOR | + (16 - 1) << 12 | (8 - 1) << 4; + break; + } + + for (i = 0; i < 16; i++) + coda_write(dev, xy2ca_map[i], + CODA9_GDI_XY2_CAS_0 + 4 * i); + for (i = 0; i < 4; i++) + coda_write(dev, XY2(ZERO, 0, ZERO, 0), + CODA9_GDI_XY2_BA_0 + 4 * i); + for (i = 0; i < 16; i++) + coda_write(dev, XY2(ZERO, 0, ZERO, 0), + CODA9_GDI_XY2_RAS_0 + 4 * i); + coda_write(dev, xy2rbc_config, CODA9_GDI_XY2_RBC_CONFIG); + if (xy2rbc_config) { + for (i = 0; i < 32; i++) + coda_write(dev, rbc2axi_tiled_map[i], + CODA9_GDI_RBC2_AXI_0 + 4 * i); + } +} diff --git a/drivers/media/platform/chips-media/coda-h264.c b/drivers/media/platform/chips-media/coda-h264.c new file mode 100644 index 000000000..8bd0aa8af --- /dev/null +++ b/drivers/media/platform/chips-media/coda-h264.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Coda multi-standard codec IP - H.264 helper functions + * + * Copyright (C) 2012 Vista Silicon S.L. + * Javier Martin, + * Xavier Duret + */ + +#include +#include +#include + +#include "coda.h" + +static const u8 coda_filler_size[8] = { 0, 7, 14, 13, 12, 11, 10, 9 }; + +static const u8 *coda_find_nal_header(const u8 *buf, const u8 *end) +{ + u32 val = 0xffffffff; + + do { + val = val << 8 | *buf++; + if (buf >= end) + return NULL; + } while (val != 0x00000001); + + return buf; +} + +int coda_sps_parse_profile(struct coda_ctx *ctx, struct vb2_buffer *vb) +{ + const u8 *buf = vb2_plane_vaddr(vb, 0); + const u8 *end = buf + vb2_get_plane_payload(vb, 0); + + /* Find SPS header */ + do { + buf = coda_find_nal_header(buf, end); + if (!buf) + return -EINVAL; + } while ((*buf++ & 0x1f) != 0x7); + + ctx->params.h264_profile_idc = buf[0]; + ctx->params.h264_level_idc = buf[2]; + + return 0; +} + +int coda_h264_filler_nal(int size, char *p) +{ + if (size < 6) + return -EINVAL; + + p[0] = 0x00; + p[1] = 0x00; + p[2] = 0x00; + p[3] = 0x01; + p[4] = 0x0c; + memset(p + 5, 0xff, size - 6); + /* Add rbsp stop bit and trailing at the end */ + p[size - 1] = 0x80; + + return 0; +} + +int coda_h264_padding(int size, char *p) +{ + int nal_size; + int diff; + + diff = size - (size & ~0x7); + if (diff == 0) + return 0; + + nal_size = coda_filler_size[diff]; + coda_h264_filler_nal(nal_size, p); + + return nal_size; +} + +int coda_h264_profile(int profile_idc) +{ + switch (profile_idc) { + case 66: return V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE; + case 77: return V4L2_MPEG_VIDEO_H264_PROFILE_MAIN; + case 88: return V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED; + case 100: return V4L2_MPEG_VIDEO_H264_PROFILE_HIGH; + default: return -EINVAL; + } +} + +int coda_h264_level(int level_idc) +{ + switch (level_idc) { + case 10: return V4L2_MPEG_VIDEO_H264_LEVEL_1_0; + case 9: return V4L2_MPEG_VIDEO_H264_LEVEL_1B; + case 11: return V4L2_MPEG_VIDEO_H264_LEVEL_1_1; + case 12: return V4L2_MPEG_VIDEO_H264_LEVEL_1_2; + case 13: return V4L2_MPEG_VIDEO_H264_LEVEL_1_3; + case 20: return V4L2_MPEG_VIDEO_H264_LEVEL_2_0; + case 21: return V4L2_MPEG_VIDEO_H264_LEVEL_2_1; + case 22: return V4L2_MPEG_VIDEO_H264_LEVEL_2_2; + case 30: return V4L2_MPEG_VIDEO_H264_LEVEL_3_0; + case 31: return V4L2_MPEG_VIDEO_H264_LEVEL_3_1; + case 32: return V4L2_MPEG_VIDEO_H264_LEVEL_3_2; + case 40: return V4L2_MPEG_VIDEO_H264_LEVEL_4_0; + case 41: return V4L2_MPEG_VIDEO_H264_LEVEL_4_1; + case 42: return V4L2_MPEG_VIDEO_H264_LEVEL_4_2; + case 50: return V4L2_MPEG_VIDEO_H264_LEVEL_5_0; + case 51: return V4L2_MPEG_VIDEO_H264_LEVEL_5_1; + default: return -EINVAL; + } +} + +struct rbsp { + char *buf; + int size; + int pos; +}; + +static inline int rbsp_read_bit(struct rbsp *rbsp) +{ + int shift = 7 - (rbsp->pos % 8); + int ofs = rbsp->pos++ / 8; + + if (ofs >= rbsp->size) + return -EINVAL; + + return (rbsp->buf[ofs] >> shift) & 1; +} + +static inline int rbsp_write_bit(struct rbsp *rbsp, int bit) +{ + int shift = 7 - (rbsp->pos % 8); + int ofs = rbsp->pos++ / 8; + + if (ofs >= rbsp->size) + return -EINVAL; + + rbsp->buf[ofs] &= ~(1 << shift); + rbsp->buf[ofs] |= bit << shift; + + return 0; +} + +static inline int rbsp_read_bits(struct rbsp *rbsp, int num, int *val) +{ + int i, ret; + int tmp = 0; + + if (num > 32) + return -EINVAL; + + for (i = 0; i < num; i++) { + ret = rbsp_read_bit(rbsp); + if (ret < 0) + return ret; + tmp |= ret << (num - i - 1); + } + + if (val) + *val = tmp; + + return 0; +} + +static int rbsp_write_bits(struct rbsp *rbsp, int num, int value) +{ + int ret; + + while (num--) { + ret = rbsp_write_bit(rbsp, (value >> num) & 1); + if (ret) + return ret; + } + + return 0; +} + +static int rbsp_read_uev(struct rbsp *rbsp, unsigned int *val) +{ + int leading_zero_bits = 0; + unsigned int tmp = 0; + int ret; + + while ((ret = rbsp_read_bit(rbsp)) == 0) + leading_zero_bits++; + if (ret < 0) + return ret; + + if (leading_zero_bits > 0) { + ret = rbsp_read_bits(rbsp, leading_zero_bits, &tmp); + if (ret) + return ret; + } + + if (val) + *val = (1 << leading_zero_bits) - 1 + tmp; + + return 0; +} + +static int rbsp_write_uev(struct rbsp *rbsp, unsigned int value) +{ + int i; + int ret; + int tmp = value + 1; + int leading_zero_bits = fls(tmp) - 1; + + for (i = 0; i < leading_zero_bits; i++) { + ret = rbsp_write_bit(rbsp, 0); + if (ret) + return ret; + } + + return rbsp_write_bits(rbsp, leading_zero_bits + 1, tmp); +} + +static int rbsp_read_sev(struct rbsp *rbsp, int *val) +{ + unsigned int tmp; + int ret; + + ret = rbsp_read_uev(rbsp, &tmp); + if (ret) + return ret; + + if (val) { + if (tmp & 1) + *val = (tmp + 1) / 2; + else + *val = -(tmp / 2); + } + + return 0; +} + +/** + * coda_h264_sps_fixup - fixes frame cropping values in h.264 SPS + * @ctx: encoder context + * @width: visible width + * @height: visible height + * @buf: buffer containing h.264 SPS RBSP, starting with NAL header + * @size: modified RBSP size return value + * @max_size: available size in buf + * + * Rewrites the frame cropping values in an h.264 SPS RBSP correctly for the + * given visible width and height. + */ +int coda_h264_sps_fixup(struct coda_ctx *ctx, int width, int height, char *buf, + int *size, int max_size) +{ + int profile_idc; + unsigned int pic_order_cnt_type; + int pic_width_in_mbs_minus1, pic_height_in_map_units_minus1; + int frame_mbs_only_flag, frame_cropping_flag; + int vui_parameters_present_flag; + unsigned int crop_right, crop_bottom; + struct rbsp sps; + int pos; + int ret; + + if (*size < 8 || *size >= max_size) + return -EINVAL; + + sps.buf = buf + 5; /* Skip NAL header */ + sps.size = *size - 5; + + profile_idc = sps.buf[0]; + /* Skip constraint_set[0-5]_flag, reserved_zero_2bits */ + /* Skip level_idc */ + sps.pos = 24; + + /* seq_parameter_set_id */ + ret = rbsp_read_uev(&sps, NULL); + if (ret) + return ret; + + if (profile_idc == 100 || profile_idc == 110 || profile_idc == 122 || + profile_idc == 244 || profile_idc == 44 || profile_idc == 83 || + profile_idc == 86 || profile_idc == 118 || profile_idc == 128 || + profile_idc == 138 || profile_idc == 139 || profile_idc == 134 || + profile_idc == 135) { + dev_err(ctx->fh.vdev->dev_parent, + "%s: Handling profile_idc %d not implemented\n", + __func__, profile_idc); + return -EINVAL; + } + + /* log2_max_frame_num_minus4 */ + ret = rbsp_read_uev(&sps, NULL); + if (ret) + return ret; + + ret = rbsp_read_uev(&sps, &pic_order_cnt_type); + if (ret) + return ret; + + if (pic_order_cnt_type == 0) { + /* log2_max_pic_order_cnt_lsb_minus4 */ + ret = rbsp_read_uev(&sps, NULL); + if (ret) + return ret; + } else if (pic_order_cnt_type == 1) { + unsigned int i, num_ref_frames_in_pic_order_cnt_cycle; + + /* delta_pic_order_always_zero_flag */ + ret = rbsp_read_bit(&sps); + if (ret < 0) + return ret; + /* offset_for_non_ref_pic */ + ret = rbsp_read_sev(&sps, NULL); + if (ret) + return ret; + /* offset_for_top_to_bottom_field */ + ret = rbsp_read_sev(&sps, NULL); + if (ret) + return ret; + + ret = rbsp_read_uev(&sps, + &num_ref_frames_in_pic_order_cnt_cycle); + if (ret) + return ret; + for (i = 0; i < num_ref_frames_in_pic_order_cnt_cycle; i++) { + /* offset_for_ref_frame */ + ret = rbsp_read_sev(&sps, NULL); + if (ret) + return ret; + } + } + + /* max_num_ref_frames */ + ret = rbsp_read_uev(&sps, NULL); + if (ret) + return ret; + + /* gaps_in_frame_num_value_allowed_flag */ + ret = rbsp_read_bit(&sps); + if (ret < 0) + return ret; + ret = rbsp_read_uev(&sps, &pic_width_in_mbs_minus1); + if (ret) + return ret; + ret = rbsp_read_uev(&sps, &pic_height_in_map_units_minus1); + if (ret) + return ret; + frame_mbs_only_flag = ret = rbsp_read_bit(&sps); + if (ret < 0) + return ret; + if (!frame_mbs_only_flag) { + /* mb_adaptive_frame_field_flag */ + ret = rbsp_read_bit(&sps); + if (ret < 0) + return ret; + } + /* direct_8x8_inference_flag */ + ret = rbsp_read_bit(&sps); + if (ret < 0) + return ret; + + /* Mark position of the frame cropping flag */ + pos = sps.pos; + frame_cropping_flag = ret = rbsp_read_bit(&sps); + if (ret < 0) + return ret; + if (frame_cropping_flag) { + unsigned int crop_left, crop_top; + + ret = rbsp_read_uev(&sps, &crop_left); + if (ret) + return ret; + ret = rbsp_read_uev(&sps, &crop_right); + if (ret) + return ret; + ret = rbsp_read_uev(&sps, &crop_top); + if (ret) + return ret; + ret = rbsp_read_uev(&sps, &crop_bottom); + if (ret) + return ret; + } + vui_parameters_present_flag = ret = rbsp_read_bit(&sps); + if (ret < 0) + return ret; + if (vui_parameters_present_flag) { + dev_err(ctx->fh.vdev->dev_parent, + "%s: Handling vui_parameters not implemented\n", + __func__); + return -EINVAL; + } + + crop_right = round_up(width, 16) - width; + crop_bottom = round_up(height, 16) - height; + crop_right /= 2; + if (frame_mbs_only_flag) + crop_bottom /= 2; + else + crop_bottom /= 4; + + + sps.size = max_size - 5; + sps.pos = pos; + frame_cropping_flag = 1; + ret = rbsp_write_bit(&sps, frame_cropping_flag); + if (ret) + return ret; + ret = rbsp_write_uev(&sps, 0); /* crop_left */ + if (ret) + return ret; + ret = rbsp_write_uev(&sps, crop_right); + if (ret) + return ret; + ret = rbsp_write_uev(&sps, 0); /* crop_top */ + if (ret) + return ret; + ret = rbsp_write_uev(&sps, crop_bottom); + if (ret) + return ret; + ret = rbsp_write_bit(&sps, 0); /* vui_parameters_present_flag */ + if (ret) + return ret; + ret = rbsp_write_bit(&sps, 1); + if (ret) + return ret; + + *size = 5 + DIV_ROUND_UP(sps.pos, 8); + + return 0; +} diff --git a/drivers/media/platform/chips-media/coda-jpeg.c b/drivers/media/platform/chips-media/coda-jpeg.c new file mode 100644 index 000000000..ba8f41002 --- /dev/null +++ b/drivers/media/platform/chips-media/coda-jpeg.c @@ -0,0 +1,1547 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Coda multi-standard codec IP - JPEG support functions + * + * Copyright (C) 2014 Philipp Zabel, Pengutronix + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "coda.h" +#include "trace.h" + +#define SOI_MARKER 0xffd8 +#define APP9_MARKER 0xffe9 +#define DRI_MARKER 0xffdd +#define DQT_MARKER 0xffdb +#define DHT_MARKER 0xffc4 +#define SOF_MARKER 0xffc0 +#define SOS_MARKER 0xffda +#define EOI_MARKER 0xffd9 + +enum { + CODA9_JPEG_FORMAT_420, + CODA9_JPEG_FORMAT_422, + CODA9_JPEG_FORMAT_224, + CODA9_JPEG_FORMAT_444, + CODA9_JPEG_FORMAT_400, +}; + +struct coda_huff_tab { + u8 luma_dc[16 + 12]; + u8 chroma_dc[16 + 12]; + u8 luma_ac[16 + 162]; + u8 chroma_ac[16 + 162]; + + /* DC Luma, DC Chroma, AC Luma, AC Chroma */ + s16 min[4 * 16]; + s16 max[4 * 16]; + s8 ptr[4 * 16]; +}; + +#define CODA9_JPEG_ENC_HUFF_DATA_SIZE (256 + 256 + 16 + 16) + +/* + * Typical Huffman tables for 8-bit precision luminance and + * chrominance from JPEG ITU-T.81 (ISO/IEC 10918-1) Annex K.3 + */ + +static const unsigned char luma_dc[16 + 12] = { + /* bits */ + 0x00, 0x01, 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + /* values */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, +}; + +static const unsigned char chroma_dc[16 + 12] = { + /* bits */ + 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, + /* values */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, +}; + +static const unsigned char luma_ac[16 + 162 + 2] = { + /* bits */ + 0x00, 0x02, 0x01, 0x03, 0x03, 0x02, 0x04, 0x03, + 0x05, 0x05, 0x04, 0x04, 0x00, 0x00, 0x01, 0x7d, + /* values */ + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, /* padded to 32-bit */ +}; + +static const unsigned char chroma_ac[16 + 162 + 2] = { + /* bits */ + 0x00, 0x02, 0x01, 0x02, 0x04, 0x04, 0x03, 0x04, + 0x07, 0x05, 0x04, 0x04, 0x00, 0x01, 0x02, 0x77, + /* values */ + 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa, /* padded to 32-bit */ +}; + +/* + * Quantization tables for luminance and chrominance components in + * zig-zag scan order from the Freescale i.MX VPU libraries + */ + +static unsigned char luma_q[64] = { + 0x06, 0x04, 0x04, 0x04, 0x05, 0x04, 0x06, 0x05, + 0x05, 0x06, 0x09, 0x06, 0x05, 0x06, 0x09, 0x0b, + 0x08, 0x06, 0x06, 0x08, 0x0b, 0x0c, 0x0a, 0x0a, + 0x0b, 0x0a, 0x0a, 0x0c, 0x10, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x10, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, +}; + +static unsigned char chroma_q[64] = { + 0x07, 0x07, 0x07, 0x0d, 0x0c, 0x0d, 0x18, 0x10, + 0x10, 0x18, 0x14, 0x0e, 0x0e, 0x0e, 0x14, 0x14, + 0x0e, 0x0e, 0x0e, 0x0e, 0x14, 0x11, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x11, 0x11, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x11, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, + 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, 0x0c, +}; + +static const unsigned char width_align[] = { + [CODA9_JPEG_FORMAT_420] = 16, + [CODA9_JPEG_FORMAT_422] = 16, + [CODA9_JPEG_FORMAT_224] = 8, + [CODA9_JPEG_FORMAT_444] = 8, + [CODA9_JPEG_FORMAT_400] = 8, +}; + +static const unsigned char height_align[] = { + [CODA9_JPEG_FORMAT_420] = 16, + [CODA9_JPEG_FORMAT_422] = 8, + [CODA9_JPEG_FORMAT_224] = 16, + [CODA9_JPEG_FORMAT_444] = 8, + [CODA9_JPEG_FORMAT_400] = 8, +}; + +static int coda9_jpeg_chroma_format(u32 pixfmt) +{ + switch (pixfmt) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + return CODA9_JPEG_FORMAT_420; + case V4L2_PIX_FMT_YUV422P: + return CODA9_JPEG_FORMAT_422; + case V4L2_PIX_FMT_YUV444: + return CODA9_JPEG_FORMAT_444; + case V4L2_PIX_FMT_GREY: + return CODA9_JPEG_FORMAT_400; + } + return -EINVAL; +} + +struct coda_memcpy_desc { + int offset; + const void *src; + size_t len; +}; + +static void coda_memcpy_parabuf(void *parabuf, + const struct coda_memcpy_desc *desc) +{ + u32 *dst = parabuf + desc->offset; + const u32 *src = desc->src; + int len = desc->len / 4; + int i; + + for (i = 0; i < len; i += 2) { + dst[i + 1] = swab32(src[i]); + dst[i] = swab32(src[i + 1]); + } +} + +int coda_jpeg_write_tables(struct coda_ctx *ctx) +{ + int i; + static const struct coda_memcpy_desc huff[8] = { + { 0, luma_dc, sizeof(luma_dc) }, + { 32, luma_ac, sizeof(luma_ac) }, + { 216, chroma_dc, sizeof(chroma_dc) }, + { 248, chroma_ac, sizeof(chroma_ac) }, + }; + struct coda_memcpy_desc qmat[3] = { + { 512, ctx->params.jpeg_qmat_tab[0], 64 }, + { 576, ctx->params.jpeg_qmat_tab[1], 64 }, + { 640, ctx->params.jpeg_qmat_tab[1], 64 }, + }; + + /* Write huffman tables to parameter memory */ + for (i = 0; i < ARRAY_SIZE(huff); i++) + coda_memcpy_parabuf(ctx->parabuf.vaddr, huff + i); + + /* Write Q-matrix to parameter memory */ + for (i = 0; i < ARRAY_SIZE(qmat); i++) + coda_memcpy_parabuf(ctx->parabuf.vaddr, qmat + i); + + return 0; +} + +bool coda_jpeg_check_buffer(struct coda_ctx *ctx, struct vb2_buffer *vb) +{ + void *vaddr = vb2_plane_vaddr(vb, 0); + u16 soi, eoi; + int len, i; + + soi = be16_to_cpup((__be16 *)vaddr); + if (soi != SOI_MARKER) + return false; + + len = vb2_get_plane_payload(vb, 0); + vaddr += len - 2; + for (i = 0; i < 32; i++) { + eoi = be16_to_cpup((__be16 *)(vaddr - i)); + if (eoi == EOI_MARKER) { + if (i > 0) + vb2_set_plane_payload(vb, 0, len - i); + return true; + } + } + + return false; +} + +static int coda9_jpeg_gen_dec_huff_tab(struct coda_ctx *ctx, int tab_num); + +int coda_jpeg_decode_header(struct coda_ctx *ctx, struct vb2_buffer *vb) +{ + struct coda_dev *dev = ctx->dev; + u8 *buf = vb2_plane_vaddr(vb, 0); + size_t len = vb2_get_plane_payload(vb, 0); + struct v4l2_jpeg_scan_header scan_header; + struct v4l2_jpeg_reference quantization_tables[4] = { }; + struct v4l2_jpeg_reference huffman_tables[4] = { }; + struct v4l2_jpeg_header header = { + .scan = &scan_header, + .quantization_tables = quantization_tables, + .huffman_tables = huffman_tables, + }; + struct coda_q_data *q_data_src; + struct coda_huff_tab *huff_tab; + int i, j, ret; + + ret = v4l2_jpeg_parse_header(buf, len, &header); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "failed to parse JPEG header: %pe\n", + ERR_PTR(ret)); + return ret; + } + + ctx->params.jpeg_restart_interval = header.restart_interval; + + /* check frame header */ + if (header.frame.height > ctx->codec->max_h || + header.frame.width > ctx->codec->max_w) { + v4l2_err(&dev->v4l2_dev, "invalid dimensions: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (header.frame.height != q_data_src->height || + header.frame.width != q_data_src->width) { + v4l2_err(&dev->v4l2_dev, + "dimensions don't match format: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + + if (header.frame.num_components != 3) { + v4l2_err(&dev->v4l2_dev, + "unsupported number of components: %d\n", + header.frame.num_components); + return -EINVAL; + } + + /* install quantization tables */ + if (quantization_tables[3].start) { + v4l2_err(&dev->v4l2_dev, + "only 3 quantization tables supported\n"); + return -EINVAL; + } + for (i = 0; i < 3; i++) { + if (!quantization_tables[i].start) + continue; + if (quantization_tables[i].length != 64) { + v4l2_err(&dev->v4l2_dev, + "only 8-bit quantization tables supported\n"); + continue; + } + if (!ctx->params.jpeg_qmat_tab[i]) { + ctx->params.jpeg_qmat_tab[i] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[i]) + return -ENOMEM; + } + memcpy(ctx->params.jpeg_qmat_tab[i], + quantization_tables[i].start, 64); + } + + /* install Huffman tables */ + for (i = 0; i < 4; i++) { + if (!huffman_tables[i].start) { + v4l2_err(&dev->v4l2_dev, "missing Huffman table\n"); + return -EINVAL; + } + /* AC tables should be between 17 -> 178, DC between 17 -> 28 */ + if (huffman_tables[i].length < 17 || + huffman_tables[i].length > 178 || + ((i & 2) == 0 && huffman_tables[i].length > 28)) { + v4l2_err(&dev->v4l2_dev, + "invalid Huffman table %d length: %zu\n", + i, huffman_tables[i].length); + return -EINVAL; + } + } + huff_tab = ctx->params.jpeg_huff_tab; + if (!huff_tab) { + huff_tab = kzalloc(sizeof(struct coda_huff_tab), GFP_KERNEL); + if (!huff_tab) + return -ENOMEM; + ctx->params.jpeg_huff_tab = huff_tab; + } + + memset(huff_tab, 0, sizeof(*huff_tab)); + memcpy(huff_tab->luma_dc, huffman_tables[0].start, huffman_tables[0].length); + memcpy(huff_tab->chroma_dc, huffman_tables[1].start, huffman_tables[1].length); + memcpy(huff_tab->luma_ac, huffman_tables[2].start, huffman_tables[2].length); + memcpy(huff_tab->chroma_ac, huffman_tables[3].start, huffman_tables[3].length); + + /* check scan header */ + for (i = 0; i < scan_header.num_components; i++) { + struct v4l2_jpeg_scan_component_spec *scan_component; + + scan_component = &scan_header.component[i]; + for (j = 0; j < header.frame.num_components; j++) { + if (header.frame.component[j].component_identifier == + scan_component->component_selector) + break; + } + if (j == header.frame.num_components) + continue; + + ctx->params.jpeg_huff_dc_index[j] = + scan_component->dc_entropy_coding_table_selector; + ctx->params.jpeg_huff_ac_index[j] = + scan_component->ac_entropy_coding_table_selector; + } + + /* Generate Huffman table information */ + for (i = 0; i < 4; i++) + coda9_jpeg_gen_dec_huff_tab(ctx, i); + + /* start of entropy coded segment */ + ctx->jpeg_ecs_offset = header.ecs_offset; + + switch (header.frame.subsampling) { + case V4L2_JPEG_CHROMA_SUBSAMPLING_420: + case V4L2_JPEG_CHROMA_SUBSAMPLING_422: + ctx->params.jpeg_chroma_subsampling = header.frame.subsampling; + break; + default: + v4l2_err(&dev->v4l2_dev, "chroma subsampling not supported: %d", + header.frame.subsampling); + return -EINVAL; + } + + return 0; +} + +static inline void coda9_jpeg_write_huff_values(struct coda_dev *dev, u8 *bits, + int num_values) +{ + s8 *values = (s8 *)(bits + 16); + int huff_length, i; + + for (huff_length = 0, i = 0; i < 16; i++) + huff_length += bits[i]; + for (i = huff_length; i < num_values; i++) + values[i] = -1; + for (i = 0; i < num_values; i++) + coda_write(dev, (s32)values[i], CODA9_REG_JPEG_HUFF_DATA); +} + +static void coda9_jpeg_dec_huff_setup(struct coda_ctx *ctx) +{ + struct coda_huff_tab *huff_tab = ctx->params.jpeg_huff_tab; + struct coda_dev *dev = ctx->dev; + s16 *huff_min = huff_tab->min; + s16 *huff_max = huff_tab->max; + s8 *huff_ptr = huff_tab->ptr; + int i; + + /* MIN Tables */ + coda_write(dev, 0x003, CODA9_REG_JPEG_HUFF_CTRL); + coda_write(dev, 0x000, CODA9_REG_JPEG_HUFF_ADDR); + for (i = 0; i < 4 * 16; i++) + coda_write(dev, (s32)huff_min[i], CODA9_REG_JPEG_HUFF_DATA); + + /* MAX Tables */ + coda_write(dev, 0x403, CODA9_REG_JPEG_HUFF_CTRL); + coda_write(dev, 0x440, CODA9_REG_JPEG_HUFF_ADDR); + for (i = 0; i < 4 * 16; i++) + coda_write(dev, (s32)huff_max[i], CODA9_REG_JPEG_HUFF_DATA); + + /* PTR Tables */ + coda_write(dev, 0x803, CODA9_REG_JPEG_HUFF_CTRL); + coda_write(dev, 0x880, CODA9_REG_JPEG_HUFF_ADDR); + for (i = 0; i < 4 * 16; i++) + coda_write(dev, (s32)huff_ptr[i], CODA9_REG_JPEG_HUFF_DATA); + + /* VAL Tables: DC Luma, DC Chroma, AC Luma, AC Chroma */ + coda_write(dev, 0xc03, CODA9_REG_JPEG_HUFF_CTRL); + coda9_jpeg_write_huff_values(dev, huff_tab->luma_dc, 12); + coda9_jpeg_write_huff_values(dev, huff_tab->chroma_dc, 12); + coda9_jpeg_write_huff_values(dev, huff_tab->luma_ac, 162); + coda9_jpeg_write_huff_values(dev, huff_tab->chroma_ac, 162); + coda_write(dev, 0x000, CODA9_REG_JPEG_HUFF_CTRL); +} + +static inline void coda9_jpeg_write_qmat_tab(struct coda_dev *dev, + u8 *qmat, int index) +{ + int i; + + coda_write(dev, index | 0x3, CODA9_REG_JPEG_QMAT_CTRL); + for (i = 0; i < 64; i++) + coda_write(dev, qmat[i], CODA9_REG_JPEG_QMAT_DATA); + coda_write(dev, 0, CODA9_REG_JPEG_QMAT_CTRL); +} + +static void coda9_jpeg_qmat_setup(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + int *qmat_index = ctx->params.jpeg_qmat_index; + u8 **qmat_tab = ctx->params.jpeg_qmat_tab; + + coda9_jpeg_write_qmat_tab(dev, qmat_tab[qmat_index[0]], 0x00); + coda9_jpeg_write_qmat_tab(dev, qmat_tab[qmat_index[1]], 0x40); + coda9_jpeg_write_qmat_tab(dev, qmat_tab[qmat_index[2]], 0x80); +} + +static void coda9_jpeg_dec_bbc_gbu_setup(struct coda_ctx *ctx, + struct vb2_buffer *buf, u32 ecs_offset) +{ + struct coda_dev *dev = ctx->dev; + int page_ptr, word_ptr, bit_ptr; + u32 bbc_base_addr, end_addr; + int bbc_cur_pos; + int ret, val; + + bbc_base_addr = vb2_dma_contig_plane_dma_addr(buf, 0); + end_addr = bbc_base_addr + vb2_get_plane_payload(buf, 0); + + page_ptr = ecs_offset / 256; + word_ptr = (ecs_offset % 256) / 4; + if (page_ptr & 1) + word_ptr += 64; + bit_ptr = (ecs_offset % 4) * 8; + if (word_ptr & 1) + bit_ptr += 32; + word_ptr &= ~0x1; + + coda_write(dev, end_addr, CODA9_REG_JPEG_BBC_WR_PTR); + coda_write(dev, bbc_base_addr, CODA9_REG_JPEG_BBC_BAS_ADDR); + + /* Leave 3 256-byte page margin to avoid a BBC interrupt */ + coda_write(dev, end_addr + 256 * 3 + 256, CODA9_REG_JPEG_BBC_END_ADDR); + val = DIV_ROUND_UP(vb2_plane_size(buf, 0), 256) + 3; + coda_write(dev, BIT(31) | val, CODA9_REG_JPEG_BBC_STRM_CTRL); + + bbc_cur_pos = page_ptr; + coda_write(dev, bbc_cur_pos, CODA9_REG_JPEG_BBC_CUR_POS); + coda_write(dev, bbc_base_addr + (bbc_cur_pos << 8), + CODA9_REG_JPEG_BBC_EXT_ADDR); + coda_write(dev, (bbc_cur_pos & 1) << 6, CODA9_REG_JPEG_BBC_INT_ADDR); + coda_write(dev, 64, CODA9_REG_JPEG_BBC_DATA_CNT); + coda_write(dev, 0, CODA9_REG_JPEG_BBC_COMMAND); + do { + ret = coda_read(dev, CODA9_REG_JPEG_BBC_BUSY); + } while (ret == 1); + + bbc_cur_pos++; + coda_write(dev, bbc_cur_pos, CODA9_REG_JPEG_BBC_CUR_POS); + coda_write(dev, bbc_base_addr + (bbc_cur_pos << 8), + CODA9_REG_JPEG_BBC_EXT_ADDR); + coda_write(dev, (bbc_cur_pos & 1) << 6, CODA9_REG_JPEG_BBC_INT_ADDR); + coda_write(dev, 64, CODA9_REG_JPEG_BBC_DATA_CNT); + coda_write(dev, 0, CODA9_REG_JPEG_BBC_COMMAND); + do { + ret = coda_read(dev, CODA9_REG_JPEG_BBC_BUSY); + } while (ret == 1); + + bbc_cur_pos++; + coda_write(dev, bbc_cur_pos, CODA9_REG_JPEG_BBC_CUR_POS); + coda_write(dev, 1, CODA9_REG_JPEG_BBC_CTRL); + + coda_write(dev, 0, CODA9_REG_JPEG_GBU_TT_CNT); + coda_write(dev, word_ptr, CODA9_REG_JPEG_GBU_WD_PTR); + coda_write(dev, 0, CODA9_REG_JPEG_GBU_BBSR); + coda_write(dev, 127, CODA9_REG_JPEG_GBU_BBER); + if (page_ptr & 1) { + coda_write(dev, 0, CODA9_REG_JPEG_GBU_BBIR); + coda_write(dev, 0, CODA9_REG_JPEG_GBU_BBHR); + } else { + coda_write(dev, 64, CODA9_REG_JPEG_GBU_BBIR); + coda_write(dev, 64, CODA9_REG_JPEG_GBU_BBHR); + } + coda_write(dev, 4, CODA9_REG_JPEG_GBU_CTRL); + coda_write(dev, bit_ptr, CODA9_REG_JPEG_GBU_FF_RPTR); + coda_write(dev, 3, CODA9_REG_JPEG_GBU_CTRL); +} + +static const int bus_req_num[] = { + [CODA9_JPEG_FORMAT_420] = 2, + [CODA9_JPEG_FORMAT_422] = 3, + [CODA9_JPEG_FORMAT_224] = 3, + [CODA9_JPEG_FORMAT_444] = 4, + [CODA9_JPEG_FORMAT_400] = 4, +}; + +#define MCU_INFO(mcu_block_num, comp_num, comp0_info, comp1_info, comp2_info) \ + (((mcu_block_num) << CODA9_JPEG_MCU_BLOCK_NUM_OFFSET) | \ + ((comp_num) << CODA9_JPEG_COMP_NUM_OFFSET) | \ + ((comp0_info) << CODA9_JPEG_COMP0_INFO_OFFSET) | \ + ((comp1_info) << CODA9_JPEG_COMP1_INFO_OFFSET) | \ + ((comp2_info) << CODA9_JPEG_COMP2_INFO_OFFSET)) + +static const u32 mcu_info[] = { + [CODA9_JPEG_FORMAT_420] = MCU_INFO(6, 3, 10, 5, 5), + [CODA9_JPEG_FORMAT_422] = MCU_INFO(4, 3, 9, 5, 5), + [CODA9_JPEG_FORMAT_224] = MCU_INFO(4, 3, 6, 5, 5), + [CODA9_JPEG_FORMAT_444] = MCU_INFO(3, 3, 5, 5, 5), + [CODA9_JPEG_FORMAT_400] = MCU_INFO(1, 1, 5, 0, 0), +}; + +/* + * Convert Huffman table specifcations to tables of codes and code lengths. + * For reference, see JPEG ITU-T.81 (ISO/IEC 10918-1) [1] + * + * [1] https://www.w3.org/Graphics/JPEG/itu-t81.pdf + */ +static int coda9_jpeg_gen_enc_huff_tab(struct coda_ctx *ctx, int tab_num, + int *ehufsi, int *ehufco) +{ + int i, j, k, lastk, si, code, maxsymbol; + const u8 *bits, *huffval; + struct { + int size[256]; + int code[256]; + } *huff; + static const unsigned char *huff_tabs[4] = { + luma_dc, luma_ac, chroma_dc, chroma_ac, + }; + int ret = -EINVAL; + + huff = kzalloc(sizeof(*huff), GFP_KERNEL); + if (!huff) + return -ENOMEM; + + bits = huff_tabs[tab_num]; + huffval = huff_tabs[tab_num] + 16; + + maxsymbol = tab_num & 1 ? 256 : 16; + + /* Figure C.1 - Generation of table of Huffman code sizes */ + k = 0; + for (i = 1; i <= 16; i++) { + j = bits[i - 1]; + if (k + j > maxsymbol) + goto out; + while (j--) + huff->size[k++] = i; + } + lastk = k; + + /* Figure C.2 - Generation of table of Huffman codes */ + k = 0; + code = 0; + si = huff->size[0]; + while (k < lastk) { + while (huff->size[k] == si) { + huff->code[k++] = code; + code++; + } + if (code >= (1 << si)) + goto out; + code <<= 1; + si++; + } + + /* Figure C.3 - Ordering procedure for encoding procedure code tables */ + for (k = 0; k < lastk; k++) { + i = huffval[k]; + if (i >= maxsymbol || ehufsi[i]) + goto out; + ehufco[i] = huff->code[k]; + ehufsi[i] = huff->size[k]; + } + + ret = 0; +out: + kfree(huff); + return ret; +} + +#define DC_TABLE_INDEX0 0 +#define AC_TABLE_INDEX0 1 +#define DC_TABLE_INDEX1 2 +#define AC_TABLE_INDEX1 3 + +static u8 *coda9_jpeg_get_huff_bits(struct coda_ctx *ctx, int tab_num) +{ + struct coda_huff_tab *huff_tab = ctx->params.jpeg_huff_tab; + + if (!huff_tab) + return NULL; + + switch (tab_num) { + case DC_TABLE_INDEX0: return huff_tab->luma_dc; + case AC_TABLE_INDEX0: return huff_tab->luma_ac; + case DC_TABLE_INDEX1: return huff_tab->chroma_dc; + case AC_TABLE_INDEX1: return huff_tab->chroma_ac; + } + + return NULL; +} + +static int coda9_jpeg_gen_dec_huff_tab(struct coda_ctx *ctx, int tab_num) +{ + int ptr_cnt = 0, huff_code = 0, zero_flag = 0, data_flag = 0; + u8 *huff_bits; + s16 *huff_max; + s16 *huff_min; + s8 *huff_ptr; + int ofs; + int i; + + huff_bits = coda9_jpeg_get_huff_bits(ctx, tab_num); + if (!huff_bits) + return -EINVAL; + + /* DC/AC Luma, DC/AC Chroma -> DC Luma/Chroma, AC Luma/Chroma */ + ofs = ((tab_num & 1) << 1) | ((tab_num >> 1) & 1); + ofs *= 16; + + huff_ptr = ctx->params.jpeg_huff_tab->ptr + ofs; + huff_max = ctx->params.jpeg_huff_tab->max + ofs; + huff_min = ctx->params.jpeg_huff_tab->min + ofs; + + for (i = 0; i < 16; i++) { + if (huff_bits[i]) { + huff_ptr[i] = ptr_cnt; + ptr_cnt += huff_bits[i]; + huff_min[i] = huff_code; + huff_max[i] = huff_code + (huff_bits[i] - 1); + data_flag = 1; + zero_flag = 0; + } else { + huff_ptr[i] = -1; + huff_min[i] = -1; + huff_max[i] = -1; + zero_flag = 1; + } + + if (data_flag == 1) { + if (zero_flag == 1) + huff_code <<= 1; + else + huff_code = (huff_max[i] + 1) << 1; + } + } + + return 0; +} + +static int coda9_jpeg_load_huff_tab(struct coda_ctx *ctx) +{ + struct { + int size[4][256]; + int code[4][256]; + } *huff; + u32 *huff_data; + int i, j; + int ret; + + huff = kzalloc(sizeof(*huff), GFP_KERNEL); + if (!huff) + return -ENOMEM; + + /* Generate all four (luma/chroma DC/AC) code/size lookup tables */ + for (i = 0; i < 4; i++) { + ret = coda9_jpeg_gen_enc_huff_tab(ctx, i, huff->size[i], + huff->code[i]); + if (ret) + goto out; + } + + if (!ctx->params.jpeg_huff_data) { + ctx->params.jpeg_huff_data = + kzalloc(sizeof(u32) * CODA9_JPEG_ENC_HUFF_DATA_SIZE, + GFP_KERNEL); + if (!ctx->params.jpeg_huff_data) { + ret = -ENOMEM; + goto out; + } + } + huff_data = ctx->params.jpeg_huff_data; + + for (j = 0; j < 4; j++) { + /* Store Huffman lookup tables in AC0, AC1, DC0, DC1 order */ + int t = (j == 0) ? AC_TABLE_INDEX0 : + (j == 1) ? AC_TABLE_INDEX1 : + (j == 2) ? DC_TABLE_INDEX0 : + DC_TABLE_INDEX1; + /* DC tables only have 16 entries */ + int len = (j < 2) ? 256 : 16; + + for (i = 0; i < len; i++) { + if (huff->size[t][i] == 0 && huff->code[t][i] == 0) + *(huff_data++) = 0; + else + *(huff_data++) = + ((huff->size[t][i] - 1) << 16) | + huff->code[t][i]; + } + } + + ret = 0; +out: + kfree(huff); + return ret; +} + +static void coda9_jpeg_write_huff_tab(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + u32 *huff_data = ctx->params.jpeg_huff_data; + int i; + + /* Write Huffman size/code lookup tables in AC0, AC1, DC0, DC1 order */ + coda_write(dev, 0x3, CODA9_REG_JPEG_HUFF_CTRL); + for (i = 0; i < CODA9_JPEG_ENC_HUFF_DATA_SIZE; i++) + coda_write(dev, *(huff_data++), CODA9_REG_JPEG_HUFF_DATA); + coda_write(dev, 0x0, CODA9_REG_JPEG_HUFF_CTRL); +} + +static inline void coda9_jpeg_write_qmat_quotients(struct coda_dev *dev, + u8 *qmat, int index) +{ + int i; + + coda_write(dev, index | 0x3, CODA9_REG_JPEG_QMAT_CTRL); + for (i = 0; i < 64; i++) + coda_write(dev, 0x80000 / qmat[i], CODA9_REG_JPEG_QMAT_DATA); + coda_write(dev, index, CODA9_REG_JPEG_QMAT_CTRL); +} + +static void coda9_jpeg_load_qmat_tab(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + u8 *luma_tab; + u8 *chroma_tab; + + luma_tab = ctx->params.jpeg_qmat_tab[0]; + if (!luma_tab) + luma_tab = luma_q; + + chroma_tab = ctx->params.jpeg_qmat_tab[1]; + if (!chroma_tab) + chroma_tab = chroma_q; + + coda9_jpeg_write_qmat_quotients(dev, luma_tab, 0x00); + coda9_jpeg_write_qmat_quotients(dev, chroma_tab, 0x40); + coda9_jpeg_write_qmat_quotients(dev, chroma_tab, 0x80); +} + +struct coda_jpeg_stream { + u8 *curr; + u8 *end; +}; + +static inline int coda_jpeg_put_byte(u8 byte, struct coda_jpeg_stream *stream) +{ + if (stream->curr >= stream->end) + return -EINVAL; + + *stream->curr++ = byte; + + return 0; +} + +static inline int coda_jpeg_put_word(u16 word, struct coda_jpeg_stream *stream) +{ + if (stream->curr + sizeof(__be16) > stream->end) + return -EINVAL; + + put_unaligned_be16(word, stream->curr); + stream->curr += sizeof(__be16); + + return 0; +} + +static int coda_jpeg_put_table(u16 marker, u8 index, const u8 *table, + size_t len, struct coda_jpeg_stream *stream) +{ + int i, ret; + + ret = coda_jpeg_put_word(marker, stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_word(3 + len, stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_byte(index, stream); + for (i = 0; i < len && ret == 0; i++) + ret = coda_jpeg_put_byte(table[i], stream); + + return ret; +} + +static int coda_jpeg_define_quantization_table(struct coda_ctx *ctx, u8 index, + struct coda_jpeg_stream *stream) +{ + return coda_jpeg_put_table(DQT_MARKER, index, + ctx->params.jpeg_qmat_tab[index], 64, + stream); +} + +static int coda_jpeg_define_huffman_table(u8 index, const u8 *table, size_t len, + struct coda_jpeg_stream *stream) +{ + return coda_jpeg_put_table(DHT_MARKER, index, table, len, stream); +} + +static int coda9_jpeg_encode_header(struct coda_ctx *ctx, int len, u8 *buf) +{ + struct coda_jpeg_stream stream = { buf, buf + len }; + struct coda_q_data *q_data_src; + int chroma_format, comp_num; + int i, ret, pad; + + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + chroma_format = coda9_jpeg_chroma_format(q_data_src->fourcc); + if (chroma_format < 0) + return 0; + + /* Start Of Image */ + ret = coda_jpeg_put_word(SOI_MARKER, &stream); + if (ret < 0) + return ret; + + /* Define Restart Interval */ + if (ctx->params.jpeg_restart_interval) { + ret = coda_jpeg_put_word(DRI_MARKER, &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_word(4, &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_word(ctx->params.jpeg_restart_interval, + &stream); + if (ret < 0) + return ret; + } + + /* Define Quantization Tables */ + ret = coda_jpeg_define_quantization_table(ctx, 0x00, &stream); + if (ret < 0) + return ret; + if (chroma_format != CODA9_JPEG_FORMAT_400) { + ret = coda_jpeg_define_quantization_table(ctx, 0x01, &stream); + if (ret < 0) + return ret; + } + + /* Define Huffman Tables */ + ret = coda_jpeg_define_huffman_table(0x00, luma_dc, 16 + 12, &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_define_huffman_table(0x10, luma_ac, 16 + 162, &stream); + if (ret < 0) + return ret; + if (chroma_format != CODA9_JPEG_FORMAT_400) { + ret = coda_jpeg_define_huffman_table(0x01, chroma_dc, 16 + 12, + &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_define_huffman_table(0x11, chroma_ac, 16 + 162, + &stream); + if (ret < 0) + return ret; + } + + /* Start Of Frame */ + ret = coda_jpeg_put_word(SOF_MARKER, &stream); + if (ret < 0) + return ret; + comp_num = (chroma_format == CODA9_JPEG_FORMAT_400) ? 1 : 3; + ret = coda_jpeg_put_word(8 + comp_num * 3, &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_byte(0x08, &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_word(q_data_src->height, &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_word(q_data_src->width, &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_byte(comp_num, &stream); + if (ret < 0) + return ret; + for (i = 0; i < comp_num; i++) { + static unsigned char subsampling[5][3] = { + [CODA9_JPEG_FORMAT_420] = { 0x22, 0x11, 0x11 }, + [CODA9_JPEG_FORMAT_422] = { 0x21, 0x11, 0x11 }, + [CODA9_JPEG_FORMAT_224] = { 0x12, 0x11, 0x11 }, + [CODA9_JPEG_FORMAT_444] = { 0x11, 0x11, 0x11 }, + [CODA9_JPEG_FORMAT_400] = { 0x11 }, + }; + + /* Component identifier, matches SOS */ + ret = coda_jpeg_put_byte(i + 1, &stream); + if (ret < 0) + return ret; + ret = coda_jpeg_put_byte(subsampling[chroma_format][i], + &stream); + if (ret < 0) + return ret; + /* Chroma table index */ + ret = coda_jpeg_put_byte((i == 0) ? 0 : 1, &stream); + if (ret < 0) + return ret; + } + + /* Pad to multiple of 8 bytes */ + pad = (stream.curr - buf) % 8; + if (pad) { + pad = 8 - pad; + while (pad--) { + ret = coda_jpeg_put_byte(0x00, &stream); + if (ret < 0) + return ret; + } + } + + return stream.curr - buf; +} + +/* + * Scale quantization table using nonlinear scaling factor + * u8 qtab[64], scale [50,190] + */ +static void coda_scale_quant_table(u8 *q_tab, int scale) +{ + unsigned int temp; + int i; + + for (i = 0; i < 64; i++) { + temp = DIV_ROUND_CLOSEST((unsigned int)q_tab[i] * scale, 100); + if (temp <= 0) + temp = 1; + if (temp > 255) + temp = 255; + q_tab[i] = (unsigned char)temp; + } +} + +void coda_set_jpeg_compression_quality(struct coda_ctx *ctx, int quality) +{ + unsigned int scale; + + ctx->params.jpeg_quality = quality; + + /* Clip quality setting to [5,100] interval */ + if (quality > 100) + quality = 100; + if (quality < 5) + quality = 5; + + /* + * Non-linear scaling factor: + * [5,50] -> [1000..100], [51,100] -> [98..0] + */ + if (quality < 50) + scale = 5000 / quality; + else + scale = 200 - 2 * quality; + + if (ctx->params.jpeg_qmat_tab[0]) { + memcpy(ctx->params.jpeg_qmat_tab[0], luma_q, 64); + coda_scale_quant_table(ctx->params.jpeg_qmat_tab[0], scale); + } + if (ctx->params.jpeg_qmat_tab[1]) { + memcpy(ctx->params.jpeg_qmat_tab[1], chroma_q, 64); + coda_scale_quant_table(ctx->params.jpeg_qmat_tab[1], scale); + } +} + +/* + * Encoder context operations + */ + +static int coda9_jpeg_start_encoding(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + int ret; + + ret = coda9_jpeg_load_huff_tab(ctx); + if (ret < 0) { + v4l2_err(&dev->v4l2_dev, "error loading Huffman tables\n"); + return ret; + } + if (!ctx->params.jpeg_qmat_tab[0]) { + ctx->params.jpeg_qmat_tab[0] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[0]) + return -ENOMEM; + } + if (!ctx->params.jpeg_qmat_tab[1]) { + ctx->params.jpeg_qmat_tab[1] = kmalloc(64, GFP_KERNEL); + if (!ctx->params.jpeg_qmat_tab[1]) + return -ENOMEM; + } + coda_set_jpeg_compression_quality(ctx, ctx->params.jpeg_quality); + + return 0; +} + +static int coda9_jpeg_prepare_encode(struct coda_ctx *ctx) +{ + struct coda_q_data *q_data_src; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct coda_dev *dev = ctx->dev; + u32 start_addr, end_addr; + u16 aligned_width, aligned_height; + bool chroma_interleave; + int chroma_format; + int header_len; + int ret; + ktime_t timeout; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + if (vb2_get_plane_payload(&src_buf->vb2_buf, 0) == 0) + vb2_set_plane_payload(&src_buf->vb2_buf, 0, + vb2_plane_size(&src_buf->vb2_buf, 0)); + + src_buf->sequence = ctx->osequence; + dst_buf->sequence = ctx->osequence; + ctx->osequence++; + + src_buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + src_buf->flags &= ~V4L2_BUF_FLAG_PFRAME; + + coda_set_gdi_regs(ctx); + + start_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + end_addr = start_addr + vb2_plane_size(&dst_buf->vb2_buf, 0); + + chroma_format = coda9_jpeg_chroma_format(q_data_src->fourcc); + if (chroma_format < 0) + return chroma_format; + + /* Round image dimensions to multiple of MCU size */ + aligned_width = round_up(q_data_src->width, width_align[chroma_format]); + aligned_height = round_up(q_data_src->height, + height_align[chroma_format]); + if (aligned_width != q_data_src->bytesperline) { + v4l2_err(&dev->v4l2_dev, "wrong stride: %d instead of %d\n", + aligned_width, q_data_src->bytesperline); + } + + header_len = + coda9_jpeg_encode_header(ctx, + vb2_plane_size(&dst_buf->vb2_buf, 0), + vb2_plane_vaddr(&dst_buf->vb2_buf, 0)); + if (header_len < 0) + return header_len; + + coda_write(dev, start_addr + header_len, CODA9_REG_JPEG_BBC_BAS_ADDR); + coda_write(dev, end_addr, CODA9_REG_JPEG_BBC_END_ADDR); + coda_write(dev, start_addr + header_len, CODA9_REG_JPEG_BBC_WR_PTR); + coda_write(dev, start_addr + header_len, CODA9_REG_JPEG_BBC_RD_PTR); + coda_write(dev, 0, CODA9_REG_JPEG_BBC_CUR_POS); + /* 64 words per 256-byte page */ + coda_write(dev, 64, CODA9_REG_JPEG_BBC_DATA_CNT); + coda_write(dev, start_addr, CODA9_REG_JPEG_BBC_EXT_ADDR); + coda_write(dev, 0, CODA9_REG_JPEG_BBC_INT_ADDR); + + coda_write(dev, 0, CODA9_REG_JPEG_GBU_BT_PTR); + coda_write(dev, 0, CODA9_REG_JPEG_GBU_WD_PTR); + coda_write(dev, 0, CODA9_REG_JPEG_GBU_BBSR); + coda_write(dev, BIT(31) | ((end_addr - start_addr - header_len) / 256), + CODA9_REG_JPEG_BBC_STRM_CTRL); + coda_write(dev, 0, CODA9_REG_JPEG_GBU_CTRL); + coda_write(dev, 0, CODA9_REG_JPEG_GBU_FF_RPTR); + coda_write(dev, 127, CODA9_REG_JPEG_GBU_BBER); + coda_write(dev, 64, CODA9_REG_JPEG_GBU_BBIR); + coda_write(dev, 64, CODA9_REG_JPEG_GBU_BBHR); + + chroma_interleave = (q_data_src->fourcc == V4L2_PIX_FMT_NV12); + coda_write(dev, CODA9_JPEG_PIC_CTRL_TC_DIRECTION | + CODA9_JPEG_PIC_CTRL_ENCODER_EN, CODA9_REG_JPEG_PIC_CTRL); + coda_write(dev, 0, CODA9_REG_JPEG_SCL_INFO); + coda_write(dev, chroma_interleave, CODA9_REG_JPEG_DPB_CONFIG); + coda_write(dev, ctx->params.jpeg_restart_interval, + CODA9_REG_JPEG_RST_INTVAL); + coda_write(dev, 1, CODA9_REG_JPEG_BBC_CTRL); + + coda_write(dev, bus_req_num[chroma_format], CODA9_REG_JPEG_OP_INFO); + + coda9_jpeg_write_huff_tab(ctx); + coda9_jpeg_load_qmat_tab(ctx); + + if (ctx->params.rot_mode & CODA_ROT_90) { + aligned_width = aligned_height; + aligned_height = q_data_src->bytesperline; + if (chroma_format == CODA9_JPEG_FORMAT_422) + chroma_format = CODA9_JPEG_FORMAT_224; + else if (chroma_format == CODA9_JPEG_FORMAT_224) + chroma_format = CODA9_JPEG_FORMAT_422; + } + /* These need to be multiples of MCU size */ + coda_write(dev, aligned_width << 16 | aligned_height, + CODA9_REG_JPEG_PIC_SIZE); + coda_write(dev, ctx->params.rot_mode ? + (CODA_ROT_MIR_ENABLE | ctx->params.rot_mode) : 0, + CODA9_REG_JPEG_ROT_INFO); + + coda_write(dev, mcu_info[chroma_format], CODA9_REG_JPEG_MCU_INFO); + + coda_write(dev, 1, CODA9_GDI_CONTROL); + timeout = ktime_add_us(ktime_get(), 100000); + do { + ret = coda_read(dev, CODA9_GDI_STATUS); + if (ktime_compare(ktime_get(), timeout) > 0) { + v4l2_err(&dev->v4l2_dev, "timeout waiting for GDI\n"); + return -ETIMEDOUT; + } + } while (!ret); + + coda_write(dev, (chroma_format << 17) | (chroma_interleave << 16) | + q_data_src->bytesperline, CODA9_GDI_INFO_CONTROL); + /* The content of this register seems to be irrelevant: */ + coda_write(dev, aligned_width << 16 | aligned_height, + CODA9_GDI_INFO_PIC_SIZE); + + coda_write_base(ctx, q_data_src, src_buf, CODA9_GDI_INFO_BASE_Y); + + coda_write(dev, 0, CODA9_REG_JPEG_DPB_BASE00); + coda_write(dev, 0, CODA9_GDI_CONTROL); + coda_write(dev, 1, CODA9_GDI_PIC_INIT_HOST); + + coda_write(dev, 1, CODA9_GDI_WPROT_ERR_CLR); + coda_write(dev, 0, CODA9_GDI_WPROT_RGN_EN); + + trace_coda_jpeg_run(ctx, src_buf); + + coda_write(dev, 1, CODA9_REG_JPEG_PIC_START); + + return 0; +} + +static void coda9_jpeg_finish_encode(struct coda_ctx *ctx) +{ + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct coda_dev *dev = ctx->dev; + u32 wr_ptr, start_ptr; + u32 err_mb; + + if (ctx->aborting) { + coda_write(ctx->dev, 0, CODA9_REG_JPEG_BBC_FLUSH_CMD); + return; + } + + /* + * Lock to make sure that an encoder stop command running in parallel + * will either already have marked src_buf as last, or it will wake up + * the capture queue after the buffers are returned. + */ + mutex_lock(&ctx->wakeup_mutex); + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + trace_coda_jpeg_done(ctx, dst_buf); + + /* + * Set plane payload to the number of bytes written out + * by the JPEG processing unit + */ + start_ptr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + wr_ptr = coda_read(dev, CODA9_REG_JPEG_BBC_WR_PTR); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, wr_ptr - start_ptr); + + err_mb = coda_read(dev, CODA9_REG_JPEG_PIC_ERRMB); + if (err_mb) + coda_dbg(1, ctx, "ERRMB: 0x%x\n", err_mb); + + coda_write(dev, 0, CODA9_REG_JPEG_BBC_FLUSH_CMD); + + dst_buf->flags &= ~(V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_LAST); + dst_buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + dst_buf->flags |= src_buf->flags & V4L2_BUF_FLAG_LAST; + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, false); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + coda_m2m_buf_done(ctx, dst_buf, err_mb ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_DONE); + mutex_unlock(&ctx->wakeup_mutex); + + coda_dbg(1, ctx, "job finished: encoded frame (%u)%s\n", + dst_buf->sequence, + (dst_buf->flags & V4L2_BUF_FLAG_LAST) ? " (last)" : ""); + + /* + * Reset JPEG processing unit after each encode run to work + * around hangups when switching context between encoder and + * decoder. + */ + coda_hw_reset(ctx); +} + +static void coda9_jpeg_encode_timeout(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + u32 end_addr, wr_ptr; + + /* Handle missing BBC overflow interrupt via timeout */ + end_addr = coda_read(dev, CODA9_REG_JPEG_BBC_END_ADDR); + wr_ptr = coda_read(dev, CODA9_REG_JPEG_BBC_WR_PTR); + if (wr_ptr >= end_addr - 256) { + v4l2_err(&dev->v4l2_dev, "JPEG too large for capture buffer\n"); + coda9_jpeg_finish_encode(ctx); + return; + } + + coda_hw_reset(ctx); +} + +static void coda9_jpeg_release(struct coda_ctx *ctx) +{ + int i; + + if (ctx->params.jpeg_qmat_tab[0] == luma_q) + ctx->params.jpeg_qmat_tab[0] = NULL; + if (ctx->params.jpeg_qmat_tab[1] == chroma_q) + ctx->params.jpeg_qmat_tab[1] = NULL; + for (i = 0; i < 3; i++) + kfree(ctx->params.jpeg_qmat_tab[i]); + kfree(ctx->params.jpeg_huff_data); + kfree(ctx->params.jpeg_huff_tab); +} + +const struct coda_context_ops coda9_jpeg_encode_ops = { + .queue_init = coda_encoder_queue_init, + .start_streaming = coda9_jpeg_start_encoding, + .prepare_run = coda9_jpeg_prepare_encode, + .finish_run = coda9_jpeg_finish_encode, + .run_timeout = coda9_jpeg_encode_timeout, + .release = coda9_jpeg_release, +}; + +/* + * Decoder context operations + */ + +static int coda9_jpeg_start_decoding(struct coda_ctx *ctx) +{ + ctx->params.jpeg_qmat_index[0] = 0; + ctx->params.jpeg_qmat_index[1] = 1; + ctx->params.jpeg_qmat_index[2] = 1; + ctx->params.jpeg_qmat_tab[0] = luma_q; + ctx->params.jpeg_qmat_tab[1] = chroma_q; + /* nothing more to do here */ + + /* TODO: we could already scan the first header to get the chroma + * format. + */ + + return 0; +} + +static int coda9_jpeg_prepare_decode(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + int aligned_width, aligned_height; + int chroma_format; + int ret; + u32 val, dst_fourcc; + struct coda_q_data *q_data_src, *q_data_dst; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + int chroma_interleave; + int scl_hor_mode, scl_ver_mode; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + q_data_src = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + dst_fourcc = q_data_dst->fourcc; + + scl_hor_mode = coda_jpeg_scale(q_data_src->width, q_data_dst->width); + scl_ver_mode = coda_jpeg_scale(q_data_src->height, q_data_dst->height); + + if (vb2_get_plane_payload(&src_buf->vb2_buf, 0) == 0) + vb2_set_plane_payload(&src_buf->vb2_buf, 0, + vb2_plane_size(&src_buf->vb2_buf, 0)); + + chroma_format = coda9_jpeg_chroma_format(q_data_dst->fourcc); + if (chroma_format < 0) + return chroma_format; + + ret = coda_jpeg_decode_header(ctx, &src_buf->vb2_buf); + if (ret < 0) { + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + + return ret; + } + + /* Round image dimensions to multiple of MCU size */ + aligned_width = round_up(q_data_src->width, width_align[chroma_format]); + aligned_height = round_up(q_data_src->height, height_align[chroma_format]); + if (aligned_width != q_data_dst->bytesperline) { + v4l2_err(&dev->v4l2_dev, "stride mismatch: %d != %d\n", + aligned_width, q_data_dst->bytesperline); + } + + coda_set_gdi_regs(ctx); + + val = ctx->params.jpeg_huff_ac_index[0] << 12 | + ctx->params.jpeg_huff_ac_index[1] << 11 | + ctx->params.jpeg_huff_ac_index[2] << 10 | + ctx->params.jpeg_huff_dc_index[0] << 9 | + ctx->params.jpeg_huff_dc_index[1] << 8 | + ctx->params.jpeg_huff_dc_index[2] << 7; + if (ctx->params.jpeg_huff_tab) + val |= CODA9_JPEG_PIC_CTRL_USER_HUFFMAN_EN; + coda_write(dev, val, CODA9_REG_JPEG_PIC_CTRL); + + coda_write(dev, aligned_width << 16 | aligned_height, + CODA9_REG_JPEG_PIC_SIZE); + + chroma_interleave = (dst_fourcc == V4L2_PIX_FMT_NV12); + coda_write(dev, 0, CODA9_REG_JPEG_ROT_INFO); + coda_write(dev, bus_req_num[chroma_format], CODA9_REG_JPEG_OP_INFO); + coda_write(dev, mcu_info[chroma_format], CODA9_REG_JPEG_MCU_INFO); + if (scl_hor_mode || scl_ver_mode) + val = CODA9_JPEG_SCL_ENABLE | (scl_hor_mode << 2) | scl_ver_mode; + else + val = 0; + coda_write(dev, val, CODA9_REG_JPEG_SCL_INFO); + coda_write(dev, chroma_interleave, CODA9_REG_JPEG_DPB_CONFIG); + coda_write(dev, ctx->params.jpeg_restart_interval, + CODA9_REG_JPEG_RST_INTVAL); + + if (ctx->params.jpeg_huff_tab) + coda9_jpeg_dec_huff_setup(ctx); + + coda9_jpeg_qmat_setup(ctx); + + coda9_jpeg_dec_bbc_gbu_setup(ctx, &src_buf->vb2_buf, + ctx->jpeg_ecs_offset); + + coda_write(dev, 0, CODA9_REG_JPEG_RST_INDEX); + coda_write(dev, 0, CODA9_REG_JPEG_RST_COUNT); + + coda_write(dev, 0, CODA9_REG_JPEG_DPCM_DIFF_Y); + coda_write(dev, 0, CODA9_REG_JPEG_DPCM_DIFF_CB); + coda_write(dev, 0, CODA9_REG_JPEG_DPCM_DIFF_CR); + + coda_write(dev, 0, CODA9_REG_JPEG_ROT_INFO); + + coda_write(dev, 1, CODA9_GDI_CONTROL); + do { + ret = coda_read(dev, CODA9_GDI_STATUS); + } while (!ret); + + val = (chroma_format << 17) | (chroma_interleave << 16) | + q_data_dst->bytesperline; + if (ctx->tiled_map_type == GDI_TILED_FRAME_MB_RASTER_MAP) + val |= 3 << 20; + coda_write(dev, val, CODA9_GDI_INFO_CONTROL); + + coda_write(dev, aligned_width << 16 | aligned_height, + CODA9_GDI_INFO_PIC_SIZE); + + coda_write_base(ctx, q_data_dst, dst_buf, CODA9_GDI_INFO_BASE_Y); + + coda_write(dev, 0, CODA9_REG_JPEG_DPB_BASE00); + coda_write(dev, 0, CODA9_GDI_CONTROL); + coda_write(dev, 1, CODA9_GDI_PIC_INIT_HOST); + + trace_coda_jpeg_run(ctx, src_buf); + + coda_write(dev, 1, CODA9_REG_JPEG_PIC_START); + + return 0; +} + +static void coda9_jpeg_finish_decode(struct coda_ctx *ctx) +{ + struct coda_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *dst_buf, *src_buf; + struct coda_q_data *q_data_dst; + u32 err_mb; + + err_mb = coda_read(dev, CODA9_REG_JPEG_PIC_ERRMB); + if (err_mb) + v4l2_err(&dev->v4l2_dev, "ERRMB: 0x%x\n", err_mb); + + coda_write(dev, 0, CODA9_REG_JPEG_BBC_FLUSH_CMD); + + /* + * Lock to make sure that a decoder stop command running in parallel + * will either already have marked src_buf as last, or it will wake up + * the capture queue after the buffers are returned. + */ + mutex_lock(&ctx->wakeup_mutex); + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + dst_buf->sequence = ctx->osequence++; + + trace_coda_jpeg_done(ctx, dst_buf); + + dst_buf->flags &= ~(V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_LAST); + dst_buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + dst_buf->flags |= src_buf->flags & V4L2_BUF_FLAG_LAST; + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, false); + + q_data_dst = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, q_data_dst->sizeimage); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + coda_m2m_buf_done(ctx, dst_buf, err_mb ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_DONE); + + mutex_unlock(&ctx->wakeup_mutex); + + coda_dbg(1, ctx, "job finished: decoded frame (%u)%s\n", + dst_buf->sequence, + (dst_buf->flags & V4L2_BUF_FLAG_LAST) ? " (last)" : ""); + + /* + * Reset JPEG processing unit after each decode run to work + * around hangups when switching context between encoder and + * decoder. + */ + coda_hw_reset(ctx); +} + +const struct coda_context_ops coda9_jpeg_decode_ops = { + .queue_init = coda_encoder_queue_init, /* non-bitstream operation */ + .start_streaming = coda9_jpeg_start_decoding, + .prepare_run = coda9_jpeg_prepare_decode, + .finish_run = coda9_jpeg_finish_decode, + .release = coda9_jpeg_release, +}; + +irqreturn_t coda9_jpeg_irq_handler(int irq, void *data) +{ + struct coda_dev *dev = data; + struct coda_ctx *ctx; + int status; + int err_mb; + + status = coda_read(dev, CODA9_REG_JPEG_PIC_STATUS); + if (status == 0) + return IRQ_HANDLED; + coda_write(dev, status, CODA9_REG_JPEG_PIC_STATUS); + + if (status & CODA9_JPEG_STATUS_OVERFLOW) + v4l2_err(&dev->v4l2_dev, "JPEG overflow\n"); + + if (status & CODA9_JPEG_STATUS_BBC_INT) + v4l2_err(&dev->v4l2_dev, "JPEG BBC interrupt\n"); + + if (status & CODA9_JPEG_STATUS_ERROR) { + v4l2_err(&dev->v4l2_dev, "JPEG error\n"); + + err_mb = coda_read(dev, CODA9_REG_JPEG_PIC_ERRMB); + if (err_mb) { + v4l2_err(&dev->v4l2_dev, + "ERRMB: 0x%x: rst idx %d, mcu pos (%d,%d)\n", + err_mb, err_mb >> 24, (err_mb >> 12) & 0xfff, + err_mb & 0xfff); + } + } + + ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + if (!ctx) { + v4l2_err(&dev->v4l2_dev, + "Instance released before the end of transaction\n"); + mutex_unlock(&dev->coda_mutex); + return IRQ_HANDLED; + } + + complete(&ctx->completion); + + return IRQ_HANDLED; +} diff --git a/drivers/media/platform/chips-media/coda-mpeg2.c b/drivers/media/platform/chips-media/coda-mpeg2.c new file mode 100644 index 000000000..6f3f6721d --- /dev/null +++ b/drivers/media/platform/chips-media/coda-mpeg2.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Coda multi-standard codec IP - MPEG-2 helper functions + * + * Copyright (C) 2019 Pengutronix, Philipp Zabel + */ + +#include +#include +#include "coda.h" + +int coda_mpeg2_profile(int profile_idc) +{ + switch (profile_idc) { + case 5: + return V4L2_MPEG_VIDEO_MPEG2_PROFILE_SIMPLE; + case 4: + return V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN; + case 3: + return V4L2_MPEG_VIDEO_MPEG2_PROFILE_SNR_SCALABLE; + case 2: + return V4L2_MPEG_VIDEO_MPEG2_PROFILE_SPATIALLY_SCALABLE; + case 1: + return V4L2_MPEG_VIDEO_MPEG2_PROFILE_HIGH; + default: + return -EINVAL; + } +} + +int coda_mpeg2_level(int level_idc) +{ + switch (level_idc) { + case 10: + return V4L2_MPEG_VIDEO_MPEG2_LEVEL_LOW; + case 8: + return V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN; + case 6: + return V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH_1440; + case 4: + return V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH; + default: + return -EINVAL; + } +} + +/* + * Check if the buffer starts with the MPEG-2 sequence header (with or without + * quantization matrix) and extension header, for example: + * + * 00 00 01 b3 2d 01 e0 34 08 8b a3 81 + * 10 11 11 12 12 12 13 13 13 13 14 14 14 14 14 15 + * 15 15 15 15 15 16 16 16 16 16 16 16 17 17 17 17 + * 17 17 17 17 18 18 18 19 18 18 18 19 1a 1a 1a 1a + * 19 1b 1b 1b 1b 1b 1c 1c 1c 1c 1e 1e 1e 1f 1f 21 + * 00 00 01 b5 14 8a 00 01 00 00 + * + * or: + * + * 00 00 01 b3 08 00 40 15 ff ff e0 28 + * 00 00 01 b5 14 8a 00 01 00 00 + * + * Returns the detected header size in bytes or 0. + */ +u32 coda_mpeg2_parse_headers(struct coda_ctx *ctx, u8 *buf, u32 size) +{ + static const u8 sequence_header_start[4] = { 0x00, 0x00, 0x01, 0xb3 }; + static const union { + u8 extension_start[4]; + u8 start_code_prefix[3]; + } u = { { 0x00, 0x00, 0x01, 0xb5 } }; + + if (size < 22 || + memcmp(buf, sequence_header_start, 4) != 0) + return 0; + + if ((size == 22 || + (size >= 25 && memcmp(buf + 22, u.start_code_prefix, 3) == 0)) && + memcmp(buf + 12, u.extension_start, 4) == 0) + return 22; + + if ((size == 86 || + (size > 89 && memcmp(buf + 86, u.start_code_prefix, 3) == 0)) && + memcmp(buf + 76, u.extension_start, 4) == 0) + return 86; + + return 0; +} diff --git a/drivers/media/platform/chips-media/coda-mpeg4.c b/drivers/media/platform/chips-media/coda-mpeg4.c new file mode 100644 index 000000000..483a4fba1 --- /dev/null +++ b/drivers/media/platform/chips-media/coda-mpeg4.c @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Coda multi-standard codec IP - MPEG-4 helper functions + * + * Copyright (C) 2019 Pengutronix, Philipp Zabel + */ + +#include +#include + +#include "coda.h" + +int coda_mpeg4_profile(int profile_idc) +{ + switch (profile_idc) { + case 0: + return V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE; + case 15: + return V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE; + case 2: + return V4L2_MPEG_VIDEO_MPEG4_PROFILE_CORE; + case 1: + return V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE_SCALABLE; + case 11: + return V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY; + default: + return -EINVAL; + } +} + +int coda_mpeg4_level(int level_idc) +{ + switch (level_idc) { + case 0: + return V4L2_MPEG_VIDEO_MPEG4_LEVEL_0; + case 1: + return V4L2_MPEG_VIDEO_MPEG4_LEVEL_1; + case 2: + return V4L2_MPEG_VIDEO_MPEG4_LEVEL_2; + case 3: + return V4L2_MPEG_VIDEO_MPEG4_LEVEL_3; + case 4: + return V4L2_MPEG_VIDEO_MPEG4_LEVEL_4; + case 5: + return V4L2_MPEG_VIDEO_MPEG4_LEVEL_5; + default: + return -EINVAL; + } +} + +/* + * Check if the buffer starts with the MPEG-4 visual object sequence and visual + * object headers, for example: + * + * 00 00 01 b0 f1 + * 00 00 01 b5 a9 13 00 00 01 00 00 00 01 20 08 + * d4 8d 88 00 f5 04 04 08 14 30 3f + * + * Returns the detected header size in bytes or 0. + */ +u32 coda_mpeg4_parse_headers(struct coda_ctx *ctx, u8 *buf, u32 size) +{ + static const u8 vos_start[4] = { 0x00, 0x00, 0x01, 0xb0 }; + static const union { + u8 vo_start[4]; + u8 start_code_prefix[3]; + } u = { { 0x00, 0x00, 0x01, 0xb5 } }; + + if (size < 30 || + memcmp(buf, vos_start, 4) != 0 || + memcmp(buf + 5, u.vo_start, 4) != 0) + return 0; + + if (size == 30 || + (size >= 33 && memcmp(buf + 30, u.start_code_prefix, 3) == 0)) + return 30; + + if (size == 31 || + (size >= 34 && memcmp(buf + 31, u.start_code_prefix, 3) == 0)) + return 31; + + if (size == 32 || + (size >= 35 && memcmp(buf + 32, u.start_code_prefix, 3) == 0)) + return 32; + + return 0; +} diff --git a/drivers/media/platform/chips-media/coda.h b/drivers/media/platform/chips-media/coda.h new file mode 100644 index 000000000..ddfd0a32c --- /dev/null +++ b/drivers/media/platform/chips-media/coda.h @@ -0,0 +1,403 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Coda multi-standard codec IP + * + * Copyright (C) 2012 Vista Silicon S.L. + * Javier Martin, + * Xavier Duret + * Copyright (C) 2012-2014 Philipp Zabel, Pengutronix + */ + +#ifndef __CODA_H__ +#define __CODA_H__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "coda_regs.h" + +#define CODA_MAX_FRAMEBUFFERS 19 +#define FMO_SLICE_SAVE_BUF_SIZE (32) + +/* + * This control allows applications to read the per-stream + * (i.e. per-context) Macroblocks Error Count. This value + * is CODA specific. + */ +#define V4L2_CID_CODA_MB_ERR_CNT (V4L2_CID_USER_CODA_BASE + 0) + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +enum coda_inst_type { + CODA_INST_ENCODER, + CODA_INST_DECODER, +}; + +enum coda_product { + CODA_DX6 = 0xf001, + CODA_HX4 = 0xf00a, + CODA_7541 = 0xf012, + CODA_960 = 0xf020, +}; + +struct coda_video_device; + +struct coda_devtype { + char *firmware[3]; + enum coda_product product; + const struct coda_codec *codecs; + unsigned int num_codecs; + const struct coda_video_device **vdevs; + unsigned int num_vdevs; + size_t workbuf_size; + size_t tempbuf_size; + size_t iram_size; +}; + +struct coda_aux_buf { + void *vaddr; + dma_addr_t paddr; + u32 size; + struct debugfs_blob_wrapper blob; + struct dentry *dentry; +}; + +struct coda_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd[6]; + struct device *dev; + const struct coda_devtype *devtype; + int firmware; + struct vdoa_data *vdoa; + + void __iomem *regs_base; + struct clk *clk_per; + struct clk *clk_ahb; + struct reset_control *rstc; + + struct coda_aux_buf codebuf; + struct coda_aux_buf tempbuf; + struct coda_aux_buf workbuf; + struct gen_pool *iram_pool; + struct coda_aux_buf iram; + + struct mutex dev_mutex; + struct mutex coda_mutex; + struct workqueue_struct *workqueue; + struct v4l2_m2m_dev *m2m_dev; + struct ida ida; + struct dentry *debugfs_root; + struct ratelimit_state mb_err_rs; +}; + +struct coda_codec { + u32 mode; + u32 src_fourcc; + u32 dst_fourcc; + u32 max_w; + u32 max_h; +}; + +struct coda_huff_tab; + +struct coda_params { + u8 rot_mode; + u8 h264_intra_qp; + u8 h264_inter_qp; + u8 h264_min_qp; + u8 h264_max_qp; + u8 h264_disable_deblocking_filter_idc; + s8 h264_slice_alpha_c0_offset_div2; + s8 h264_slice_beta_offset_div2; + bool h264_constrained_intra_pred_flag; + s8 h264_chroma_qp_index_offset; + u8 h264_profile_idc; + u8 h264_level_idc; + u8 mpeg2_profile_idc; + u8 mpeg2_level_idc; + u8 mpeg4_intra_qp; + u8 mpeg4_inter_qp; + u8 gop_size; + int intra_refresh; + enum v4l2_jpeg_chroma_subsampling jpeg_chroma_subsampling; + u8 jpeg_quality; + u8 jpeg_restart_interval; + u8 *jpeg_qmat_tab[3]; + int jpeg_qmat_index[3]; + int jpeg_huff_dc_index[3]; + int jpeg_huff_ac_index[3]; + u32 *jpeg_huff_data; + struct coda_huff_tab *jpeg_huff_tab; + int codec_mode; + int codec_mode_aux; + enum v4l2_mpeg_video_multi_slice_mode slice_mode; + u32 framerate; + u16 bitrate; + u16 vbv_delay; + u32 vbv_size; + u32 slice_max_bits; + u32 slice_max_mb; + bool force_ipicture; + bool gop_size_changed; + bool bitrate_changed; + bool framerate_changed; + bool h264_intra_qp_changed; + bool intra_refresh_changed; + bool slice_mode_changed; + bool frame_rc_enable; + bool mb_rc_enable; +}; + +struct coda_buffer_meta { + struct list_head list; + u32 sequence; + struct v4l2_timecode timecode; + u64 timestamp; + unsigned int start; + unsigned int end; + bool last; +}; + +/* Per-queue, driver-specific private data */ +struct coda_q_data { + unsigned int width; + unsigned int height; + unsigned int bytesperline; + unsigned int sizeimage; + unsigned int fourcc; + struct v4l2_rect rect; +}; + +struct coda_iram_info { + u32 axi_sram_use; + phys_addr_t buf_bit_use; + phys_addr_t buf_ip_ac_dc_use; + phys_addr_t buf_dbk_y_use; + phys_addr_t buf_dbk_c_use; + phys_addr_t buf_ovl_use; + phys_addr_t buf_btp_use; + phys_addr_t search_ram_paddr; + int search_ram_size; + int remaining; + phys_addr_t next_paddr; +}; + +#define GDI_LINEAR_FRAME_MAP 0 +#define GDI_TILED_FRAME_MB_RASTER_MAP 1 + +struct coda_ctx; + +struct coda_context_ops { + int (*queue_init)(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); + int (*reqbufs)(struct coda_ctx *ctx, struct v4l2_requestbuffers *rb); + int (*start_streaming)(struct coda_ctx *ctx); + int (*prepare_run)(struct coda_ctx *ctx); + void (*finish_run)(struct coda_ctx *ctx); + void (*run_timeout)(struct coda_ctx *ctx); + void (*seq_init_work)(struct work_struct *work); + void (*seq_end_work)(struct work_struct *work); + void (*release)(struct coda_ctx *ctx); +}; + +struct coda_internal_frame { + struct coda_aux_buf buf; + struct coda_buffer_meta meta; + u32 type; + u32 error; +}; + +struct coda_ctx { + struct coda_dev *dev; + struct mutex buffer_mutex; + struct work_struct pic_run_work; + struct work_struct seq_init_work; + struct work_struct seq_end_work; + struct completion completion; + const struct coda_video_device *cvd; + const struct coda_context_ops *ops; + int aborting; + int initialized; + int streamon_out; + int streamon_cap; + u32 qsequence; + u32 osequence; + u32 sequence_offset; + struct coda_q_data q_data[2]; + enum coda_inst_type inst_type; + const struct coda_codec *codec; + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + struct coda_params params; + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *h264_profile_ctrl; + struct v4l2_ctrl *h264_level_ctrl; + struct v4l2_ctrl *mpeg2_profile_ctrl; + struct v4l2_ctrl *mpeg2_level_ctrl; + struct v4l2_ctrl *mpeg4_profile_ctrl; + struct v4l2_ctrl *mpeg4_level_ctrl; + struct v4l2_ctrl *mb_err_cnt_ctrl; + struct v4l2_fh fh; + int gopcounter; + int runcounter; + int jpeg_ecs_offset; + char vpu_header[3][64]; + int vpu_header_size[3]; + struct kfifo bitstream_fifo; + struct mutex bitstream_mutex; + struct coda_aux_buf bitstream; + bool hold; + struct coda_aux_buf parabuf; + struct coda_aux_buf psbuf; + struct coda_aux_buf slicebuf; + struct coda_internal_frame internal_frames[CODA_MAX_FRAMEBUFFERS]; + struct list_head buffer_meta_list; + spinlock_t buffer_meta_lock; + int num_metas; + unsigned int first_frame_sequence; + struct coda_aux_buf workbuf; + int num_internal_frames; + int idx; + int reg_idx; + struct coda_iram_info iram_info; + int tiled_map_type; + u32 bit_stream_param; + u32 frm_dis_flg; + u32 frame_mem_ctrl; + u32 para_change; + int display_idx; + struct dentry *debugfs_entry; + bool use_bit; + bool use_vdoa; + struct vdoa_ctx *vdoa; + /* + * wakeup mutex used to serialize encoder stop command and finish_run, + * ensures that finish_run always either flags the last returned buffer + * or wakes up the capture queue to signal EOS afterwards. + */ + struct mutex wakeup_mutex; +}; + +extern int coda_debug; + +#define coda_dbg(level, ctx, fmt, arg...) \ + do { \ + if (coda_debug >= (level)) \ + v4l2_dbg((level), coda_debug, &(ctx)->dev->v4l2_dev, \ + "%u: " fmt, (ctx)->idx, ##arg); \ + } while (0) + +void coda_write(struct coda_dev *dev, u32 data, u32 reg); +unsigned int coda_read(struct coda_dev *dev, u32 reg); +void coda_write_base(struct coda_ctx *ctx, struct coda_q_data *q_data, + struct vb2_v4l2_buffer *buf, unsigned int reg_y); + +int coda_alloc_aux_buf(struct coda_dev *dev, struct coda_aux_buf *buf, + size_t size, const char *name, struct dentry *parent); +void coda_free_aux_buf(struct coda_dev *dev, struct coda_aux_buf *buf); + +int coda_encoder_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); +int coda_decoder_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); + +int coda_hw_reset(struct coda_ctx *ctx); + +void coda_fill_bitstream(struct coda_ctx *ctx, struct list_head *buffer_list); + +void coda_set_gdi_regs(struct coda_ctx *ctx); + +static inline struct coda_q_data *get_q_data(struct coda_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &(ctx->q_data[V4L2_M2M_SRC]); + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &(ctx->q_data[V4L2_M2M_DST]); + default: + return NULL; + } +} + +const char *coda_product_name(int product); + +int coda_check_firmware(struct coda_dev *dev); + +static inline unsigned int coda_get_bitstream_payload(struct coda_ctx *ctx) +{ + return kfifo_len(&ctx->bitstream_fifo); +} + +/* + * The bitstream prefetcher needs to read at least 2 256 byte periods past + * the desired bitstream position for all data to reach the decoder. + */ +static inline bool coda_bitstream_can_fetch_past(struct coda_ctx *ctx, + unsigned int pos) +{ + return (int)(ctx->bitstream_fifo.kfifo.in - ALIGN(pos, 256)) > 512; +} + +bool coda_bitstream_can_fetch_past(struct coda_ctx *ctx, unsigned int pos); +int coda_bitstream_flush(struct coda_ctx *ctx); + +void coda_bit_stream_end_flag(struct coda_ctx *ctx); + +void coda_m2m_buf_done(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf, + enum vb2_buffer_state state); + +int coda_h264_filler_nal(int size, char *p); +int coda_h264_padding(int size, char *p); +int coda_h264_profile(int profile_idc); +int coda_h264_level(int level_idc); +int coda_sps_parse_profile(struct coda_ctx *ctx, struct vb2_buffer *vb); +int coda_h264_sps_fixup(struct coda_ctx *ctx, int width, int height, char *buf, + int *size, int max_size); + +int coda_mpeg2_profile(int profile_idc); +int coda_mpeg2_level(int level_idc); +u32 coda_mpeg2_parse_headers(struct coda_ctx *ctx, u8 *buf, u32 size); +int coda_mpeg4_profile(int profile_idc); +int coda_mpeg4_level(int level_idc); +u32 coda_mpeg4_parse_headers(struct coda_ctx *ctx, u8 *buf, u32 size); + +void coda_update_profile_level_ctrls(struct coda_ctx *ctx, u8 profile_idc, + u8 level_idc); + +static inline int coda_jpeg_scale(int src, int dst) +{ + return (dst <= src / 8) ? 3 : + (dst <= src / 4) ? 2 : + (dst <= src / 2) ? 1 : 0; +} + +bool coda_jpeg_check_buffer(struct coda_ctx *ctx, struct vb2_buffer *vb); +int coda_jpeg_decode_header(struct coda_ctx *ctx, struct vb2_buffer *vb); +int coda_jpeg_write_tables(struct coda_ctx *ctx); +void coda_set_jpeg_compression_quality(struct coda_ctx *ctx, int quality); + +extern const struct coda_context_ops coda_bit_encode_ops; +extern const struct coda_context_ops coda_bit_decode_ops; +extern const struct coda_context_ops coda9_jpeg_encode_ops; +extern const struct coda_context_ops coda9_jpeg_decode_ops; + +irqreturn_t coda_irq_handler(int irq, void *data); +irqreturn_t coda9_jpeg_irq_handler(int irq, void *data); + +#endif /* __CODA_H__ */ diff --git a/drivers/media/platform/chips-media/coda_regs.h b/drivers/media/platform/chips-media/coda_regs.h new file mode 100644 index 000000000..db81a904c --- /dev/null +++ b/drivers/media/platform/chips-media/coda_regs.h @@ -0,0 +1,563 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * linux/drivers/media/platform/chips-media/coda_regs.h + * + * Copyright (C) 2012 Vista Silicon SL + * Javier Martin + * Xavier Duret + */ + +#ifndef _REGS_CODA_H_ +#define _REGS_CODA_H_ + +/* HW registers */ +#define CODA_REG_BIT_CODE_RUN 0x000 +#define CODA_REG_RUN_ENABLE (1 << 0) +#define CODA_REG_BIT_CODE_DOWN 0x004 +#define CODA_DOWN_ADDRESS_SET(x) (((x) & 0xffff) << 16) +#define CODA_DOWN_DATA_SET(x) ((x) & 0xffff) +#define CODA_REG_BIT_HOST_IN_REQ 0x008 +#define CODA_REG_BIT_INT_CLEAR 0x00c +#define CODA_REG_BIT_INT_CLEAR_SET 0x1 +#define CODA_REG_BIT_INT_STATUS 0x010 +#define CODA_REG_BIT_CODE_RESET 0x014 +#define CODA_REG_RESET_ENABLE (1 << 0) +#define CODA_REG_BIT_CUR_PC 0x018 +#define CODA9_REG_BIT_SW_RESET 0x024 +#define CODA9_SW_RESET_BPU_CORE 0x008 +#define CODA9_SW_RESET_BPU_BUS 0x010 +#define CODA9_SW_RESET_VCE_CORE 0x020 +#define CODA9_SW_RESET_VCE_BUS 0x040 +#define CODA9_SW_RESET_GDI_CORE 0x080 +#define CODA9_SW_RESET_GDI_BUS 0x100 +#define CODA9_REG_BIT_SW_RESET_STATUS 0x034 + +/* Static SW registers */ +#define CODA_REG_BIT_CODE_BUF_ADDR 0x100 +#define CODA_REG_BIT_WORK_BUF_ADDR 0x104 +#define CODA_REG_BIT_PARA_BUF_ADDR 0x108 +#define CODA_REG_BIT_STREAM_CTRL 0x10c +#define CODA7_STREAM_BUF_PIC_RESET (1 << 4) +#define CODADX6_STREAM_BUF_PIC_RESET (1 << 3) +#define CODA7_STREAM_BUF_PIC_FLUSH (1 << 3) +#define CODADX6_STREAM_BUF_PIC_FLUSH (1 << 2) +#define CODA7_STREAM_BUF_DYNALLOC_EN (1 << 5) +#define CODADX6_STREAM_BUF_DYNALLOC_EN (1 << 4) +#define CODADX6_STREAM_CHKDIS_OFFSET (1 << 1) +#define CODA7_STREAM_SEL_64BITS_ENDIAN (1 << 1) +#define CODA_STREAM_ENDIAN_SELECT (1 << 0) +#define CODA_REG_BIT_FRAME_MEM_CTRL 0x110 +#define CODA9_FRAME_ENABLE_BWB (1 << 12) +#define CODA9_FRAME_TILED2LINEAR (1 << 11) +#define CODA_FRAME_CHROMA_INTERLEAVE (1 << 2) +#define CODA_IMAGE_ENDIAN_SELECT (1 << 0) +#define CODA_REG_BIT_BIT_STREAM_PARAM 0x114 +#define CODA_BIT_STREAM_END_FLAG (1 << 2) +#define CODA_BIT_DEC_SEQ_INIT_ESCAPE (1 << 0) +#define CODA_REG_BIT_TEMP_BUF_ADDR 0x118 +#define CODA_REG_BIT_RD_PTR(x) (0x120 + 8 * (x)) +#define CODA_REG_BIT_WR_PTR(x) (0x124 + 8 * (x)) +#define CODA_REG_BIT_FRM_DIS_FLG(x) (0x150 + 4 * (x)) +#define CODADX6_REG_BIT_SEARCH_RAM_BASE_ADDR 0x140 +#define CODA7_REG_BIT_AXI_SRAM_USE 0x140 +#define CODA9_USE_HOST_BTP_ENABLE (1 << 13) +#define CODA9_USE_HOST_OVL_ENABLE (1 << 12) +#define CODA7_USE_HOST_ME_ENABLE (1 << 11) +#define CODA9_USE_HOST_DBK_ENABLE (3 << 10) +#define CODA7_USE_HOST_OVL_ENABLE (1 << 10) +#define CODA7_USE_HOST_DBK_ENABLE (1 << 9) +#define CODA9_USE_HOST_IP_ENABLE (1 << 9) +#define CODA7_USE_HOST_IP_ENABLE (1 << 8) +#define CODA9_USE_HOST_BIT_ENABLE (1 << 8) +#define CODA7_USE_HOST_BIT_ENABLE (1 << 7) +#define CODA9_USE_BTP_ENABLE (1 << 5) +#define CODA7_USE_ME_ENABLE (1 << 4) +#define CODA9_USE_OVL_ENABLE (1 << 4) +#define CODA7_USE_OVL_ENABLE (1 << 3) +#define CODA9_USE_DBK_ENABLE (3 << 2) +#define CODA7_USE_DBK_ENABLE (1 << 2) +#define CODA7_USE_IP_ENABLE (1 << 1) +#define CODA7_USE_BIT_ENABLE (1 << 0) + +#define CODA_REG_BIT_BUSY 0x160 +#define CODA_REG_BIT_BUSY_FLAG 1 +#define CODA_REG_BIT_RUN_COMMAND 0x164 +#define CODA_COMMAND_SEQ_INIT 1 +#define CODA_COMMAND_SEQ_END 2 +#define CODA_COMMAND_PIC_RUN 3 +#define CODA_COMMAND_SET_FRAME_BUF 4 +#define CODA_COMMAND_ENCODE_HEADER 5 +#define CODA_COMMAND_ENC_PARA_SET 6 +#define CODA_COMMAND_DEC_PARA_SET 7 +#define CODA_COMMAND_DEC_BUF_FLUSH 8 +#define CODA_COMMAND_RC_CHANGE_PARAMETER 9 +#define CODA_COMMAND_FIRMWARE_GET 0xf +#define CODA_REG_BIT_RUN_INDEX 0x168 +#define CODA_INDEX_SET(x) ((x) & 0x3) +#define CODA_REG_BIT_RUN_COD_STD 0x16c +#define CODADX6_MODE_DECODE_MP4 0 +#define CODADX6_MODE_ENCODE_MP4 1 +#define CODADX6_MODE_DECODE_H264 2 +#define CODADX6_MODE_ENCODE_H264 3 +#define CODA7_MODE_DECODE_H264 0 +#define CODA7_MODE_DECODE_VC1 1 +#define CODA7_MODE_DECODE_MP2 2 +#define CODA7_MODE_DECODE_MP4 3 +#define CODA7_MODE_DECODE_DV3 3 +#define CODA7_MODE_DECODE_RV 4 +#define CODA7_MODE_DECODE_MJPG 5 +#define CODA7_MODE_ENCODE_H264 8 +#define CODA7_MODE_ENCODE_MP4 11 +#define CODA7_MODE_ENCODE_MJPG 13 +#define CODA9_MODE_DECODE_H264 0 +#define CODA9_MODE_DECODE_VC1 1 +#define CODA9_MODE_DECODE_MP2 2 +#define CODA9_MODE_DECODE_MP4 3 +#define CODA9_MODE_DECODE_DV3 3 +#define CODA9_MODE_DECODE_RV 4 +#define CODA9_MODE_DECODE_AVS 5 +#define CODA9_MODE_DECODE_MJPG 6 +#define CODA9_MODE_DECODE_VPX 7 +#define CODA9_MODE_ENCODE_H264 8 +#define CODA9_MODE_ENCODE_MP4 11 +#define CODA9_MODE_ENCODE_MJPG 13 +#define CODA_MODE_INVALID 0xffff +#define CODA_REG_BIT_INT_ENABLE 0x170 +#define CODA_INT_INTERRUPT_ENABLE (1 << 3) +#define CODA_REG_BIT_INT_REASON 0x174 +#define CODA7_REG_BIT_RUN_AUX_STD 0x178 +#define CODA_MP4_AUX_MPEG4 0 +#define CODA_MP4_AUX_DIVX3 1 +#define CODA_VPX_AUX_THO 0 +#define CODA_VPX_AUX_VP6 1 +#define CODA_VPX_AUX_VP8 2 +#define CODA_H264_AUX_AVC 0 +#define CODA_H264_AUX_MVC 1 + +/* + * Commands' mailbox: + * registers with offsets in the range 0x180-0x1d0 + * have different meaning depending on the command being + * issued. + */ + +/* Decoder Sequence Initialization */ +#define CODA_CMD_DEC_SEQ_BB_START 0x180 +#define CODA_CMD_DEC_SEQ_BB_SIZE 0x184 +#define CODA_CMD_DEC_SEQ_OPTION 0x188 +#define CODA_NO_INT_ENABLE (1 << 10) +#define CODA_REORDER_ENABLE (1 << 1) +#define CODADX6_QP_REPORT (1 << 0) +#define CODA7_MP4_DEBLK_ENABLE (1 << 0) +#define CODA_CMD_DEC_SEQ_SRC_SIZE 0x18c +#define CODA_CMD_DEC_SEQ_START_BYTE 0x190 +#define CODA_CMD_DEC_SEQ_PS_BB_START 0x194 +#define CODA_CMD_DEC_SEQ_PS_BB_SIZE 0x198 +#define CODA_CMD_DEC_SEQ_JPG_THUMB_EN 0x19c +#define CODA_CMD_DEC_SEQ_MP4_ASP_CLASS 0x19c +#define CODA_MP4_CLASS_MPEG4 0 +#define CODA_CMD_DEC_SEQ_X264_MV_EN 0x19c +#define CODA_CMD_DEC_SEQ_SPP_CHUNK_SIZE 0x1a0 + +#define CODA7_RET_DEC_SEQ_ASPECT 0x1b0 +#define CODA9_RET_DEC_SEQ_BITRATE 0x1b4 +#define CODA_RET_DEC_SEQ_SUCCESS 0x1c0 +#define CODA_RET_DEC_SEQ_SRC_FMT 0x1c4 /* SRC_SIZE on CODA7 */ +#define CODA_RET_DEC_SEQ_SRC_SIZE 0x1c4 +#define CODA_RET_DEC_SEQ_SRC_F_RATE 0x1c8 +#define CODA9_RET_DEC_SEQ_ASPECT 0x1c8 +#define CODA_RET_DEC_SEQ_FRAME_NEED 0x1cc +#define CODA_RET_DEC_SEQ_FRAME_DELAY 0x1d0 +#define CODA_RET_DEC_SEQ_INFO 0x1d4 +#define CODA_RET_DEC_SEQ_CROP_LEFT_RIGHT 0x1d8 +#define CODA_RET_DEC_SEQ_CROP_TOP_BOTTOM 0x1dc +#define CODA_RET_DEC_SEQ_NEXT_FRAME_NUM 0x1e0 +#define CODA_RET_DEC_SEQ_ERR_REASON 0x1e0 +#define CODA_RET_DEC_SEQ_FRATE_NR 0x1e4 +#define CODA_RET_DEC_SEQ_FRATE_DR 0x1e8 +#define CODA_RET_DEC_SEQ_JPG_PARA 0x1e4 +#define CODA_RET_DEC_SEQ_JPG_THUMB_IND 0x1e8 +#define CODA7_RET_DEC_SEQ_HEADER_REPORT 0x1ec + +/* Decoder Picture Run */ +#define CODA_CMD_DEC_PIC_ROT_MODE 0x180 +#define CODA_CMD_DEC_PIC_ROT_ADDR_Y 0x184 +#define CODA9_CMD_DEC_PIC_ROT_INDEX 0x184 +#define CODA_CMD_DEC_PIC_ROT_ADDR_CB 0x188 +#define CODA9_CMD_DEC_PIC_ROT_ADDR_Y 0x188 +#define CODA_CMD_DEC_PIC_ROT_ADDR_CR 0x18c +#define CODA9_CMD_DEC_PIC_ROT_ADDR_CB 0x18c +#define CODA_CMD_DEC_PIC_ROT_STRIDE 0x190 +#define CODA9_CMD_DEC_PIC_ROT_ADDR_CR 0x190 +#define CODA9_CMD_DEC_PIC_ROT_STRIDE 0x1b8 + +#define CODA_CMD_DEC_PIC_OPTION 0x194 +#define CODA_PRE_SCAN_EN (1 << 0) +#define CODA_PRE_SCAN_MODE_DECODE (0 << 1) +#define CODA_PRE_SCAN_MODE_RETURN (1 << 1) +#define CODA_IFRAME_SEARCH_EN (1 << 2) +#define CODA_SKIP_FRAME_MODE (0x3 << 3) +#define CODA_CMD_DEC_PIC_SKIP_NUM 0x198 +#define CODA_CMD_DEC_PIC_CHUNK_SIZE 0x19c +#define CODA_CMD_DEC_PIC_BB_START 0x1a0 +#define CODA_CMD_DEC_PIC_START_BYTE 0x1a4 +#define CODA_RET_DEC_PIC_SIZE 0x1bc +#define CODA_RET_DEC_PIC_FRAME_NUM 0x1c0 +#define CODA_RET_DEC_PIC_FRAME_IDX 0x1c4 +#define CODA_RET_DEC_PIC_ERR_MB 0x1c8 +#define CODA_RET_DEC_PIC_TYPE 0x1cc +#define CODA_PIC_TYPE_MASK 0x7 +#define CODA_PIC_TYPE_MASK_VC1 0x3f +#define CODA9_PIC_TYPE_FIRST_MASK (0x7 << 3) +#define CODA9_PIC_TYPE_IDR_MASK (0x3 << 6) +#define CODA7_PIC_TYPE_H264_NPF_MASK (0x3 << 16) +#define CODA7_PIC_TYPE_INTERLACED (1 << 18) +#define CODA_RET_DEC_PIC_POST 0x1d0 +#define CODA_RET_DEC_PIC_MVC_REPORT 0x1d0 +#define CODA_RET_DEC_PIC_OPTION 0x1d4 +#define CODA_RET_DEC_PIC_SUCCESS 0x1d8 +#define CODA_RET_DEC_PIC_CUR_IDX 0x1dc +#define CODA_RET_DEC_PIC_CROP_LEFT_RIGHT 0x1e0 +#define CODA_RET_DEC_PIC_CROP_TOP_BOTTOM 0x1e4 +#define CODA_RET_DEC_PIC_FRAME_NEED 0x1ec + +#define CODA9_RET_DEC_PIC_VP8_PIC_REPORT 0x1e8 +#define CODA9_RET_DEC_PIC_ASPECT 0x1f0 +#define CODA9_RET_DEC_PIC_VP8_SCALE_INFO 0x1f0 +#define CODA9_RET_DEC_PIC_FRATE_NR 0x1f4 +#define CODA9_RET_DEC_PIC_FRATE_DR 0x1f8 + +/* Encoder Sequence Initialization */ +#define CODA_CMD_ENC_SEQ_BB_START 0x180 +#define CODA_CMD_ENC_SEQ_BB_SIZE 0x184 +#define CODA_CMD_ENC_SEQ_OPTION 0x188 +#define CODA7_OPTION_AVCINTRA16X16ONLY_OFFSET 9 +#define CODA9_OPTION_MVC_PREFIX_NAL_OFFSET 9 +#define CODA7_OPTION_GAMMA_OFFSET 8 +#define CODA9_OPTION_MVC_PARASET_REFRESH_OFFSET 8 +#define CODA7_OPTION_RCQPMAX_OFFSET 7 +#define CODA9_OPTION_GAMMA_OFFSET 7 +#define CODADX6_OPTION_GAMMA_OFFSET 7 +#define CODA7_OPTION_RCQPMIN_OFFSET 6 +#define CODA9_OPTION_RCQPMAX_OFFSET 6 +#define CODA_OPTION_LIMITQP_OFFSET 6 +#define CODA_OPTION_RCINTRAQP_OFFSET 5 +#define CODA_OPTION_FMO_OFFSET 4 +#define CODA9_OPTION_MVC_INTERVIEW_OFFSET 4 +#define CODA_OPTION_AVC_AUD_OFFSET 2 +#define CODA_OPTION_SLICEREPORT_OFFSET 1 +#define CODA_CMD_ENC_SEQ_COD_STD 0x18c +#define CODA_STD_MPEG4 0 +#define CODA9_STD_H264 0 +#define CODA_STD_H263 1 +#define CODA_STD_H264 2 +#define CODA9_STD_MPEG4 3 + +#define CODA_CMD_ENC_SEQ_SRC_SIZE 0x190 +#define CODA7_PICWIDTH_OFFSET 16 +#define CODA7_PICWIDTH_MASK 0xffff +#define CODADX6_PICWIDTH_OFFSET 10 +#define CODADX6_PICWIDTH_MASK 0x3ff +#define CODA_PICHEIGHT_OFFSET 0 +#define CODADX6_PICHEIGHT_MASK 0x3ff +#define CODA7_PICHEIGHT_MASK 0xffff +#define CODA_CMD_ENC_SEQ_SRC_F_RATE 0x194 +#define CODA_FRATE_RES_OFFSET 0 +#define CODA_FRATE_RES_MASK 0xffff +#define CODA_FRATE_DIV_OFFSET 16 +#define CODA_FRATE_DIV_MASK 0xffff +#define CODA_CMD_ENC_SEQ_MP4_PARA 0x198 +#define CODA_MP4PARAM_VERID_OFFSET 6 +#define CODA_MP4PARAM_VERID_MASK 0x01 +#define CODA_MP4PARAM_INTRADCVLCTHR_OFFSET 2 +#define CODA_MP4PARAM_INTRADCVLCTHR_MASK 0x07 +#define CODA_MP4PARAM_REVERSIBLEVLCENABLE_OFFSET 1 +#define CODA_MP4PARAM_REVERSIBLEVLCENABLE_MASK 0x01 +#define CODA_MP4PARAM_DATAPARTITIONENABLE_OFFSET 0 +#define CODA_MP4PARAM_DATAPARTITIONENABLE_MASK 0x01 +#define CODA_CMD_ENC_SEQ_263_PARA 0x19c +#define CODA_263PARAM_ANNEXJENABLE_OFFSET 2 +#define CODA_263PARAM_ANNEXJENABLE_MASK 0x01 +#define CODA_263PARAM_ANNEXKENABLE_OFFSET 1 +#define CODA_263PARAM_ANNEXKENABLE_MASK 0x01 +#define CODA_263PARAM_ANNEXTENABLE_OFFSET 0 +#define CODA_263PARAM_ANNEXTENABLE_MASK 0x01 +#define CODA_CMD_ENC_SEQ_264_PARA 0x1a0 +#define CODA_264PARAM_DEBLKFILTEROFFSETBETA_OFFSET 12 +#define CODA_264PARAM_DEBLKFILTEROFFSETBETA_MASK 0x0f +#define CODA_264PARAM_DEBLKFILTEROFFSETALPHA_OFFSET 8 +#define CODA_264PARAM_DEBLKFILTEROFFSETALPHA_MASK 0x0f +#define CODA_264PARAM_DISABLEDEBLK_OFFSET 6 +#define CODA_264PARAM_DISABLEDEBLK_MASK 0x03 +#define CODA_264PARAM_CONSTRAINEDINTRAPREDFLAG_OFFSET 5 +#define CODA_264PARAM_CONSTRAINEDINTRAPREDFLAG_MASK 0x01 +#define CODA_264PARAM_CHROMAQPOFFSET_OFFSET 0 +#define CODA_264PARAM_CHROMAQPOFFSET_MASK 0x1f +#define CODA_CMD_ENC_SEQ_SLICE_MODE 0x1a4 +#define CODA_SLICING_SIZE_OFFSET 2 +#define CODA_SLICING_SIZE_MASK 0x3fffffff +#define CODA_SLICING_UNIT_OFFSET 1 +#define CODA_SLICING_UNIT_MASK 0x01 +#define CODA_SLICING_MODE_OFFSET 0 +#define CODA_SLICING_MODE_MASK 0x01 +#define CODA_CMD_ENC_SEQ_GOP_SIZE 0x1a8 +#define CODA_GOP_SIZE_OFFSET 0 +#define CODA_GOP_SIZE_MASK 0x3f +#define CODA_CMD_ENC_SEQ_RC_PARA 0x1ac +#define CODA_RATECONTROL_AUTOSKIP_OFFSET 31 +#define CODA_RATECONTROL_AUTOSKIP_MASK 0x01 +#define CODA_RATECONTROL_INITIALDELAY_OFFSET 16 +#define CODA_RATECONTROL_INITIALDELAY_MASK 0x7fff +#define CODA_RATECONTROL_BITRATE_OFFSET 1 +#define CODA_RATECONTROL_BITRATE_MASK 0x7fff +#define CODA_RATECONTROL_ENABLE_OFFSET 0 +#define CODA_RATECONTROL_ENABLE_MASK 0x01 +#define CODA_CMD_ENC_SEQ_RC_BUF_SIZE 0x1b0 +#define CODA_CMD_ENC_SEQ_INTRA_REFRESH 0x1b4 +#define CODADX6_CMD_ENC_SEQ_FMO 0x1b8 +#define CODA_FMOPARAM_TYPE_OFFSET 4 +#define CODA_FMOPARAM_TYPE_MASK 1 +#define CODA_FMOPARAM_SLICENUM_OFFSET 0 +#define CODA_FMOPARAM_SLICENUM_MASK 0x0f +#define CODADX6_CMD_ENC_SEQ_INTRA_QP 0x1bc +#define CODA7_CMD_ENC_SEQ_SEARCH_BASE 0x1b8 +#define CODA7_CMD_ENC_SEQ_SEARCH_SIZE 0x1bc +#define CODA7_CMD_ENC_SEQ_INTRA_QP 0x1c4 +#define CODA_CMD_ENC_SEQ_RC_QP_MIN_MAX 0x1c8 +#define CODA_QPMIN_OFFSET 8 +#define CODA_QPMIN_MASK 0x3f +#define CODA_QPMAX_OFFSET 0 +#define CODA_QPMAX_MASK 0x3f +#define CODA_CMD_ENC_SEQ_RC_GAMMA 0x1cc +#define CODA_GAMMA_OFFSET 0 +#define CODA_GAMMA_MASK 0xffff +#define CODA_CMD_ENC_SEQ_RC_INTERVAL_MODE 0x1d0 +#define CODA9_CMD_ENC_SEQ_INTRA_WEIGHT 0x1d4 +#define CODA9_CMD_ENC_SEQ_ME_OPTION 0x1d8 +#define CODA_RET_ENC_SEQ_SUCCESS 0x1c0 + +#define CODA_CMD_ENC_SEQ_JPG_PARA 0x198 +#define CODA_CMD_ENC_SEQ_JPG_RST_INTERVAL 0x19C +#define CODA_CMD_ENC_SEQ_JPG_THUMB_EN 0x1a0 +#define CODA_CMD_ENC_SEQ_JPG_THUMB_SIZE 0x1a4 +#define CODA_CMD_ENC_SEQ_JPG_THUMB_OFFSET 0x1a8 + +/* Encoder Parameter Change */ +#define CODA_CMD_ENC_PARAM_CHANGE_ENABLE 0x180 +#define CODA_PARAM_CHANGE_RC_GOP BIT(0) +#define CODA_PARAM_CHANGE_RC_INTRA_QP BIT(1) +#define CODA_PARAM_CHANGE_RC_BITRATE BIT(2) +#define CODA_PARAM_CHANGE_RC_FRAME_RATE BIT(3) +#define CODA_PARAM_CHANGE_INTRA_MB_NUM BIT(4) +#define CODA_PARAM_CHANGE_SLICE_MODE BIT(5) +#define CODA_PARAM_CHANGE_HEC_MODE BIT(6) +#define CODA_CMD_ENC_PARAM_RC_GOP 0x184 +#define CODA_CMD_ENC_PARAM_RC_INTRA_QP 0x188 +#define CODA_CMD_ENC_PARAM_RC_BITRATE 0x18c +#define CODA_CMD_ENC_PARAM_RC_FRAME_RATE 0x190 +#define CODA_CMD_ENC_PARAM_INTRA_MB_NUM 0x194 +#define CODA_CMD_ENC_PARAM_SLICE_MODE 0x198 +#define CODA_CMD_ENC_PARAM_HEC_MODE 0x19c +#define CODA_RET_ENC_PARAM_CHANGE_SUCCESS 0x1c0 + +/* Encoder Picture Run */ +#define CODA9_CMD_ENC_PIC_SRC_INDEX 0x180 +#define CODA9_CMD_ENC_PIC_SRC_STRIDE 0x184 +#define CODA9_CMD_ENC_PIC_SUB_FRAME_SYNC 0x1a4 +#define CODA9_CMD_ENC_PIC_SRC_ADDR_Y 0x1a8 +#define CODA9_CMD_ENC_PIC_SRC_ADDR_CB 0x1ac +#define CODA9_CMD_ENC_PIC_SRC_ADDR_CR 0x1b0 +#define CODA_CMD_ENC_PIC_SRC_ADDR_Y 0x180 +#define CODA_CMD_ENC_PIC_SRC_ADDR_CB 0x184 +#define CODA_CMD_ENC_PIC_SRC_ADDR_CR 0x188 +#define CODA_CMD_ENC_PIC_QS 0x18c +#define CODA_CMD_ENC_PIC_ROT_MODE 0x190 +#define CODA_ROT_MIR_ENABLE (1 << 4) +#define CODA_ROT_0 (0x0 << 0) +#define CODA_ROT_90 (0x1 << 0) +#define CODA_ROT_180 (0x2 << 0) +#define CODA_ROT_270 (0x3 << 0) +#define CODA_MIR_NONE (0x0 << 2) +#define CODA_MIR_VER (0x1 << 2) +#define CODA_MIR_HOR (0x2 << 2) +#define CODA_MIR_VER_HOR (0x3 << 2) +#define CODA_CMD_ENC_PIC_OPTION 0x194 +#define CODA_FORCE_IPICTURE BIT(1) +#define CODA_REPORT_MB_INFO BIT(3) +#define CODA_REPORT_MV_INFO BIT(4) +#define CODA_REPORT_SLICE_INFO BIT(5) +#define CODA_CMD_ENC_PIC_BB_START 0x198 +#define CODA_CMD_ENC_PIC_BB_SIZE 0x19c +#define CODA_RET_ENC_FRAME_NUM 0x1c0 +#define CODA_RET_ENC_PIC_TYPE 0x1c4 +#define CODA_RET_ENC_PIC_FRAME_IDX 0x1c8 +#define CODA_RET_ENC_PIC_SLICE_NUM 0x1cc +#define CODA_RET_ENC_PIC_FLAG 0x1d0 +#define CODA_RET_ENC_PIC_SUCCESS 0x1d8 + +/* Set Frame Buffer */ +#define CODA_CMD_SET_FRAME_BUF_NUM 0x180 +#define CODA_CMD_SET_FRAME_BUF_STRIDE 0x184 +#define CODA_CMD_SET_FRAME_SLICE_BB_START 0x188 +#define CODA_CMD_SET_FRAME_SLICE_BB_SIZE 0x18c +#define CODA9_CMD_SET_FRAME_SUBSAMP_A 0x188 +#define CODA9_CMD_SET_FRAME_SUBSAMP_B 0x18c +#define CODA7_CMD_SET_FRAME_AXI_BIT_ADDR 0x190 +#define CODA7_CMD_SET_FRAME_AXI_IPACDC_ADDR 0x194 +#define CODA7_CMD_SET_FRAME_AXI_DBKY_ADDR 0x198 +#define CODA7_CMD_SET_FRAME_AXI_DBKC_ADDR 0x19c +#define CODA7_CMD_SET_FRAME_AXI_OVL_ADDR 0x1a0 +#define CODA7_CMD_SET_FRAME_MAX_DEC_SIZE 0x1a4 +#define CODA9_CMD_SET_FRAME_AXI_BTP_ADDR 0x1a4 +#define CODA7_CMD_SET_FRAME_SOURCE_BUF_STRIDE 0x1a8 +#define CODA9_CMD_SET_FRAME_CACHE_SIZE 0x1a8 +#define CODA9_CMD_SET_FRAME_CACHE_CONFIG 0x1ac +#define CODA9_CACHE_BYPASS_OFFSET 28 +#define CODA9_CACHE_DUALCONF_OFFSET 26 +#define CODA9_CACHE_PAGEMERGE_OFFSET 24 +#define CODA9_CACHE_LUMA_BUFFER_SIZE_OFFSET 16 +#define CODA9_CACHE_CB_BUFFER_SIZE_OFFSET 8 +#define CODA9_CACHE_CR_BUFFER_SIZE_OFFSET 0 +#define CODA9_CMD_SET_FRAME_SUBSAMP_A_MVC 0x1b0 +#define CODA9_CMD_SET_FRAME_SUBSAMP_B_MVC 0x1b4 +#define CODA9_CMD_SET_FRAME_DP_BUF_BASE 0x1b0 +#define CODA9_CMD_SET_FRAME_DP_BUF_SIZE 0x1b4 +#define CODA9_CMD_SET_FRAME_MAX_DEC_SIZE 0x1b8 +#define CODA9_CMD_SET_FRAME_DELAY 0x1bc + +/* Encoder Header */ +#define CODA_CMD_ENC_HEADER_CODE 0x180 +#define CODA_GAMMA_OFFSET 0 +#define CODA_HEADER_H264_SPS 0 +#define CODA_HEADER_H264_PPS 1 +#define CODA_HEADER_MP4V_VOL 0 +#define CODA_HEADER_MP4V_VOS 1 +#define CODA_HEADER_MP4V_VIS 2 +#define CODA9_HEADER_FRAME_CROP (1 << 3) +#define CODA_CMD_ENC_HEADER_BB_START 0x184 +#define CODA_CMD_ENC_HEADER_BB_SIZE 0x188 +#define CODA9_CMD_ENC_HEADER_FRAME_CROP_H 0x18c +#define CODA9_CMD_ENC_HEADER_FRAME_CROP_V 0x190 + +/* Get Version */ +#define CODA_CMD_FIRMWARE_VERNUM 0x1c0 +#define CODA_FIRMWARE_PRODUCT(x) (((x) >> 16) & 0xffff) +#define CODA_FIRMWARE_MAJOR(x) (((x) >> 12) & 0x0f) +#define CODA_FIRMWARE_MINOR(x) (((x) >> 8) & 0x0f) +#define CODA_FIRMWARE_RELEASE(x) ((x) & 0xff) +#define CODA_FIRMWARE_VERNUM(product, major, minor, release) \ + ((product) << 16 | ((major) << 12) | \ + ((minor) << 8) | (release)) +#define CODA9_CMD_FIRMWARE_CODE_REV 0x1c4 + +#define CODA9_GDMA_BASE 0x1000 +#define CODA9_GDI_CONTROL (CODA9_GDMA_BASE + 0x034) +#define CODA9_GDI_PIC_INIT_HOST (CODA9_GDMA_BASE + 0x038) +#define CODA9_GDI_STATUS (CODA9_GDMA_BASE + 0x080) +#define CODA9_GDI_WPROT_ERR_CLR (CODA9_GDMA_BASE + 0x0a0) +#define CODA9_GDI_WPROT_RGN_EN (CODA9_GDMA_BASE + 0x0ac) + +#define CODA9_GDI_BUS_CTRL (CODA9_GDMA_BASE + 0x0f0) +#define CODA9_GDI_BUS_STATUS (CODA9_GDMA_BASE + 0x0f4) + +#define CODA9_GDI_INFO_CONTROL (CODA9_GDMA_BASE + 0x400) +#define CODA9_GDI_INFO_PIC_SIZE (CODA9_GDMA_BASE + 0x404) +#define CODA9_GDI_INFO_BASE_Y (CODA9_GDMA_BASE + 0x408) +#define CODA9_GDI_INFO_BASE_CB (CODA9_GDMA_BASE + 0x40c) +#define CODA9_GDI_INFO_BASE_CR (CODA9_GDMA_BASE + 0x410) + +#define CODA9_GDI_XY2_CAS_0 (CODA9_GDMA_BASE + 0x800) +#define CODA9_GDI_XY2_CAS_F (CODA9_GDMA_BASE + 0x83c) + +#define CODA9_GDI_XY2_BA_0 (CODA9_GDMA_BASE + 0x840) +#define CODA9_GDI_XY2_BA_1 (CODA9_GDMA_BASE + 0x844) +#define CODA9_GDI_XY2_BA_2 (CODA9_GDMA_BASE + 0x848) +#define CODA9_GDI_XY2_BA_3 (CODA9_GDMA_BASE + 0x84c) + +#define CODA9_GDI_XY2_RAS_0 (CODA9_GDMA_BASE + 0x850) +#define CODA9_GDI_XY2_RAS_F (CODA9_GDMA_BASE + 0x88c) + +#define CODA9_GDI_XY2_RBC_CONFIG (CODA9_GDMA_BASE + 0x890) +#define CODA9_XY2RBC_SEPARATE_MAP BIT(19) +#define CODA9_XY2RBC_TOP_BOT_SPLIT BIT(18) +#define CODA9_XY2RBC_TILED_MAP BIT(17) +#define CODA9_XY2RBC_CA_INC_HOR BIT(16) +#define CODA9_GDI_RBC2_AXI_0 (CODA9_GDMA_BASE + 0x8a0) +#define CODA9_GDI_RBC2_AXI_1F (CODA9_GDMA_BASE + 0x91c) +#define CODA9_GDI_TILEDBUF_BASE (CODA9_GDMA_BASE + 0x920) + +#define CODA9_JPEG_BASE 0x3000 +#define CODA9_REG_JPEG_PIC_START (CODA9_JPEG_BASE + 0x000) +#define CODA9_REG_JPEG_PIC_STATUS (CODA9_JPEG_BASE + 0x004) +#define CODA9_JPEG_STATUS_OVERFLOW BIT(3) +#define CODA9_JPEG_STATUS_BBC_INT BIT(2) +#define CODA9_JPEG_STATUS_ERROR BIT(1) +#define CODA9_JPEG_STATUS_DONE BIT(0) +#define CODA9_REG_JPEG_PIC_ERRMB (CODA9_JPEG_BASE + 0x008) +#define CODA9_JPEG_ERRMB_RESTART_IDX_MASK (0xf << 24) +#define CODA9_JPEG_ERRMB_MCU_POS_X_MASK (0xfff << 12) +#define CODA9_JPEG_ERRMB_MCU_POS_Y_MASK 0xfff +#define CODA9_REG_JPEG_PIC_CTRL (CODA9_JPEG_BASE + 0x010) +#define CODA9_JPEG_PIC_CTRL_USER_HUFFMAN_EN BIT(6) +#define CODA9_JPEG_PIC_CTRL_TC_DIRECTION BIT(4) +#define CODA9_JPEG_PIC_CTRL_ENCODER_EN BIT(3) +#define CODA9_REG_JPEG_PIC_SIZE (CODA9_JPEG_BASE + 0x014) +#define CODA9_REG_JPEG_MCU_INFO (CODA9_JPEG_BASE + 0x018) +#define CODA9_JPEG_MCU_BLOCK_NUM_OFFSET 16 +#define CODA9_JPEG_COMP_NUM_OFFSET 12 +#define CODA9_JPEG_COMP0_INFO_OFFSET 8 +#define CODA9_JPEG_COMP1_INFO_OFFSET 4 +#define CODA9_JPEG_COMP2_INFO_OFFSET 0 +#define CODA9_REG_JPEG_ROT_INFO (CODA9_JPEG_BASE + 0x01c) +#define CODA9_JPEG_ROT_MIR_ENABLE BIT(4) +#define CODA9_JPEG_ROT_MIR_MODE_MASK 0xf +#define CODA9_REG_JPEG_SCL_INFO (CODA9_JPEG_BASE + 0x020) +#define CODA9_JPEG_SCL_ENABLE BIT(4) +#define CODA9_JPEG_SCL_HOR_MODE_MASK (0x3 << 2) +#define CODA9_JPEG_SCL_VER_MODE_MASK (0x3 << 0) +#define CODA9_REG_JPEG_IF_INFO (CODA9_JPEG_BASE + 0x024) +#define CODA9_JPEG_SENS_IF_CLR BIT(1) +#define CODA9_JPEG_DISP_IF_CLR BIT(0) +#define CODA9_REG_JPEG_OP_INFO (CODA9_JPEG_BASE + 0x02c) +#define CODA9_JPEG_BUS_REQ_NUM_OFFSET 0 +#define CODA9_JPEG_BUS_REQ_NUM_MASK 0x7 +#define CODA9_REG_JPEG_DPB_CONFIG (CODA9_JPEG_BASE + 0x030) +#define CODA9_REG_JPEG_DPB_BASE00 (CODA9_JPEG_BASE + 0x040) +#define CODA9_REG_JPEG_HUFF_CTRL (CODA9_JPEG_BASE + 0x080) +#define CODA9_REG_JPEG_HUFF_ADDR (CODA9_JPEG_BASE + 0x084) +#define CODA9_REG_JPEG_HUFF_DATA (CODA9_JPEG_BASE + 0x088) +#define CODA9_REG_JPEG_QMAT_CTRL (CODA9_JPEG_BASE + 0x090) +#define CODA9_REG_JPEG_QMAT_ADDR (CODA9_JPEG_BASE + 0x094) +#define CODA9_REG_JPEG_QMAT_DATA (CODA9_JPEG_BASE + 0x098) +#define CODA9_REG_JPEG_RST_INTVAL (CODA9_JPEG_BASE + 0x0b0) +#define CODA9_REG_JPEG_RST_INDEX (CODA9_JPEG_BASE + 0x0b4) +#define CODA9_REG_JPEG_RST_COUNT (CODA9_JPEG_BASE + 0x0b8) +#define CODA9_REG_JPEG_DPCM_DIFF_Y (CODA9_JPEG_BASE + 0x0f0) +#define CODA9_REG_JPEG_DPCM_DIFF_CB (CODA9_JPEG_BASE + 0x0f4) +#define CODA9_REG_JPEG_DPCM_DIFF_CR (CODA9_JPEG_BASE + 0x0f8) +#define CODA9_REG_JPEG_GBU_CTRL (CODA9_JPEG_BASE + 0x100) +#define CODA9_REG_JPEG_GBU_BT_PTR (CODA9_JPEG_BASE + 0x110) +#define CODA9_REG_JPEG_GBU_WD_PTR (CODA9_JPEG_BASE + 0x114) +#define CODA9_REG_JPEG_GBU_TT_CNT (CODA9_JPEG_BASE + 0x118) +#define CODA9_REG_JPEG_GBU_BBSR (CODA9_JPEG_BASE + 0x140) +#define CODA9_REG_JPEG_GBU_BBER (CODA9_JPEG_BASE + 0x144) +#define CODA9_REG_JPEG_GBU_BBIR (CODA9_JPEG_BASE + 0x148) +#define CODA9_REG_JPEG_GBU_BBHR (CODA9_JPEG_BASE + 0x14c) +#define CODA9_REG_JPEG_GBU_BCNT (CODA9_JPEG_BASE + 0x158) +#define CODA9_REG_JPEG_GBU_FF_RPTR (CODA9_JPEG_BASE + 0x160) +#define CODA9_REG_JPEG_GBU_FF_WPTR (CODA9_JPEG_BASE + 0x164) +#define CODA9_REG_JPEG_BBC_END_ADDR (CODA9_JPEG_BASE + 0x208) +#define CODA9_REG_JPEG_BBC_WR_PTR (CODA9_JPEG_BASE + 0x20c) +#define CODA9_REG_JPEG_BBC_RD_PTR (CODA9_JPEG_BASE + 0x210) +#define CODA9_REG_JPEG_BBC_EXT_ADDR (CODA9_JPEG_BASE + 0x214) +#define CODA9_REG_JPEG_BBC_INT_ADDR (CODA9_JPEG_BASE + 0x218) +#define CODA9_REG_JPEG_BBC_DATA_CNT (CODA9_JPEG_BASE + 0x21c) +#define CODA9_REG_JPEG_BBC_COMMAND (CODA9_JPEG_BASE + 0x220) +#define CODA9_REG_JPEG_BBC_BUSY (CODA9_JPEG_BASE + 0x224) +#define CODA9_REG_JPEG_BBC_CTRL (CODA9_JPEG_BASE + 0x228) +#define CODA9_REG_JPEG_BBC_CUR_POS (CODA9_JPEG_BASE + 0x22c) +#define CODA9_REG_JPEG_BBC_BAS_ADDR (CODA9_JPEG_BASE + 0x230) +#define CODA9_REG_JPEG_BBC_STRM_CTRL (CODA9_JPEG_BASE + 0x234) +#define CODA9_REG_JPEG_BBC_FLUSH_CMD (CODA9_JPEG_BASE + 0x238) + +#endif diff --git a/drivers/media/platform/chips-media/imx-vdoa.c b/drivers/media/platform/chips-media/imx-vdoa.c new file mode 100644 index 000000000..c70871bae --- /dev/null +++ b/drivers/media/platform/chips-media/imx-vdoa.c @@ -0,0 +1,352 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * i.MX6 Video Data Order Adapter (VDOA) + * + * Copyright (C) 2014 Philipp Zabel + * Copyright (C) 2016 Pengutronix, Michael Tretter + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "imx-vdoa.h" + +#define VDOA_NAME "imx-vdoa" + +#define VDOAC 0x00 +#define VDOASRR 0x04 +#define VDOAIE 0x08 +#define VDOAIST 0x0c +#define VDOAFP 0x10 +#define VDOAIEBA00 0x14 +#define VDOAIEBA01 0x18 +#define VDOAIEBA02 0x1c +#define VDOAIEBA10 0x20 +#define VDOAIEBA11 0x24 +#define VDOAIEBA12 0x28 +#define VDOASL 0x2c +#define VDOAIUBO 0x30 +#define VDOAVEBA0 0x34 +#define VDOAVEBA1 0x38 +#define VDOAVEBA2 0x3c +#define VDOAVUBO 0x40 +#define VDOASR 0x44 + +#define VDOAC_ISEL BIT(6) +#define VDOAC_PFS BIT(5) +#define VDOAC_SO BIT(4) +#define VDOAC_SYNC BIT(3) +#define VDOAC_NF BIT(2) +#define VDOAC_BNDM_MASK 0x3 +#define VDOAC_BAND_HEIGHT_8 0x0 +#define VDOAC_BAND_HEIGHT_16 0x1 +#define VDOAC_BAND_HEIGHT_32 0x2 + +#define VDOASRR_START BIT(1) +#define VDOASRR_SWRST BIT(0) + +#define VDOAIE_EITERR BIT(1) +#define VDOAIE_EIEOT BIT(0) + +#define VDOAIST_TERR BIT(1) +#define VDOAIST_EOT BIT(0) + +#define VDOAFP_FH_MASK (0x1fff << 16) +#define VDOAFP_FW_MASK (0x3fff) + +#define VDOASL_VSLY_MASK (0x3fff << 16) +#define VDOASL_ISLY_MASK (0x7fff) + +#define VDOASR_ERRW BIT(4) +#define VDOASR_EOB BIT(3) +#define VDOASR_CURRENT_FRAME (0x3 << 1) +#define VDOASR_CURRENT_BUFFER BIT(1) + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +struct vdoa_data { + struct vdoa_ctx *curr_ctx; + struct device *dev; + struct clk *vdoa_clk; + void __iomem *regs; +}; + +struct vdoa_q_data { + unsigned int width; + unsigned int height; + unsigned int bytesperline; + unsigned int sizeimage; + u32 pixelformat; +}; + +struct vdoa_ctx { + struct vdoa_data *vdoa; + struct completion completion; + struct vdoa_q_data q_data[2]; + unsigned int submitted_job; + unsigned int completed_job; +}; + +static irqreturn_t vdoa_irq_handler(int irq, void *data) +{ + struct vdoa_data *vdoa = data; + struct vdoa_ctx *curr_ctx; + u32 val; + + /* Disable interrupts */ + writel(0, vdoa->regs + VDOAIE); + + curr_ctx = vdoa->curr_ctx; + if (!curr_ctx) { + dev_warn(vdoa->dev, + "Instance released before the end of transaction\n"); + return IRQ_HANDLED; + } + + val = readl(vdoa->regs + VDOAIST); + writel(val, vdoa->regs + VDOAIST); + if (val & VDOAIST_TERR) { + val = readl(vdoa->regs + VDOASR) & VDOASR_ERRW; + dev_err(vdoa->dev, "AXI %s error\n", val ? "write" : "read"); + } else if (!(val & VDOAIST_EOT)) { + dev_warn(vdoa->dev, "Spurious interrupt\n"); + } + curr_ctx->completed_job++; + complete(&curr_ctx->completion); + + return IRQ_HANDLED; +} + +int vdoa_wait_for_completion(struct vdoa_ctx *ctx) +{ + struct vdoa_data *vdoa = ctx->vdoa; + + if (ctx->submitted_job == ctx->completed_job) + return 0; + + if (!wait_for_completion_timeout(&ctx->completion, + msecs_to_jiffies(300))) { + dev_err(vdoa->dev, + "Timeout waiting for transfer result\n"); + return -ETIMEDOUT; + } + + return 0; +} +EXPORT_SYMBOL(vdoa_wait_for_completion); + +void vdoa_device_run(struct vdoa_ctx *ctx, dma_addr_t dst, dma_addr_t src) +{ + struct vdoa_q_data *src_q_data, *dst_q_data; + struct vdoa_data *vdoa = ctx->vdoa; + u32 val; + + if (vdoa->curr_ctx) + vdoa_wait_for_completion(vdoa->curr_ctx); + + vdoa->curr_ctx = ctx; + + reinit_completion(&ctx->completion); + ctx->submitted_job++; + + src_q_data = &ctx->q_data[V4L2_M2M_SRC]; + dst_q_data = &ctx->q_data[V4L2_M2M_DST]; + + /* Progressive, no sync, 1 frame per run */ + if (dst_q_data->pixelformat == V4L2_PIX_FMT_YUYV) + val = VDOAC_PFS; + else + val = 0; + writel(val, vdoa->regs + VDOAC); + + writel(dst_q_data->height << 16 | dst_q_data->width, + vdoa->regs + VDOAFP); + + val = dst; + writel(val, vdoa->regs + VDOAIEBA00); + + writel(src_q_data->bytesperline << 16 | dst_q_data->bytesperline, + vdoa->regs + VDOASL); + + if (dst_q_data->pixelformat == V4L2_PIX_FMT_NV12 || + dst_q_data->pixelformat == V4L2_PIX_FMT_NV21) + val = dst_q_data->bytesperline * dst_q_data->height; + else + val = 0; + writel(val, vdoa->regs + VDOAIUBO); + + val = src; + writel(val, vdoa->regs + VDOAVEBA0); + val = round_up(src_q_data->bytesperline * src_q_data->height, 4096); + writel(val, vdoa->regs + VDOAVUBO); + + /* Enable interrupts and start transfer */ + writel(VDOAIE_EITERR | VDOAIE_EIEOT, vdoa->regs + VDOAIE); + writel(VDOASRR_START, vdoa->regs + VDOASRR); +} +EXPORT_SYMBOL(vdoa_device_run); + +struct vdoa_ctx *vdoa_context_create(struct vdoa_data *vdoa) +{ + struct vdoa_ctx *ctx; + int err; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + + err = clk_prepare_enable(vdoa->vdoa_clk); + if (err) { + kfree(ctx); + return NULL; + } + + init_completion(&ctx->completion); + ctx->vdoa = vdoa; + + return ctx; +} +EXPORT_SYMBOL(vdoa_context_create); + +void vdoa_context_destroy(struct vdoa_ctx *ctx) +{ + struct vdoa_data *vdoa = ctx->vdoa; + + if (vdoa->curr_ctx == ctx) { + vdoa_wait_for_completion(vdoa->curr_ctx); + vdoa->curr_ctx = NULL; + } + + clk_disable_unprepare(vdoa->vdoa_clk); + kfree(ctx); +} +EXPORT_SYMBOL(vdoa_context_destroy); + +int vdoa_context_configure(struct vdoa_ctx *ctx, + unsigned int width, unsigned int height, + u32 pixelformat) +{ + struct vdoa_q_data *src_q_data; + struct vdoa_q_data *dst_q_data; + + if (width < 16 || width > 8192 || width % 16 != 0 || + height < 16 || height > 4096 || height % 16 != 0) + return -EINVAL; + + if (pixelformat != V4L2_PIX_FMT_YUYV && + pixelformat != V4L2_PIX_FMT_NV12) + return -EINVAL; + + /* If no context is passed, only check if the format is valid */ + if (!ctx) + return 0; + + src_q_data = &ctx->q_data[V4L2_M2M_SRC]; + dst_q_data = &ctx->q_data[V4L2_M2M_DST]; + + src_q_data->width = width; + src_q_data->height = height; + src_q_data->bytesperline = width; + src_q_data->sizeimage = + round_up(src_q_data->bytesperline * height, 4096) + + src_q_data->bytesperline * height / 2; + + dst_q_data->width = width; + dst_q_data->height = height; + dst_q_data->pixelformat = pixelformat; + switch (pixelformat) { + case V4L2_PIX_FMT_YUYV: + dst_q_data->bytesperline = width * 2; + dst_q_data->sizeimage = dst_q_data->bytesperline * height; + break; + case V4L2_PIX_FMT_NV12: + default: + dst_q_data->bytesperline = width; + dst_q_data->sizeimage = + dst_q_data->bytesperline * height * 3 / 2; + break; + } + + return 0; +} +EXPORT_SYMBOL(vdoa_context_configure); + +static int vdoa_probe(struct platform_device *pdev) +{ + struct vdoa_data *vdoa; + int ret; + + ret = dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "DMA enable failed\n"); + return ret; + } + + vdoa = devm_kzalloc(&pdev->dev, sizeof(*vdoa), GFP_KERNEL); + if (!vdoa) + return -ENOMEM; + + vdoa->dev = &pdev->dev; + + vdoa->vdoa_clk = devm_clk_get(vdoa->dev, NULL); + if (IS_ERR(vdoa->vdoa_clk)) { + dev_err(vdoa->dev, "Failed to get clock\n"); + return PTR_ERR(vdoa->vdoa_clk); + } + + vdoa->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(vdoa->regs)) + return PTR_ERR(vdoa->regs); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + ret = devm_request_threaded_irq(&pdev->dev, ret, NULL, + vdoa_irq_handler, IRQF_ONESHOT, + "vdoa", vdoa); + if (ret < 0) { + dev_err(vdoa->dev, "Failed to get irq\n"); + return ret; + } + + platform_set_drvdata(pdev, vdoa); + + return 0; +} + +static int vdoa_remove(struct platform_device *pdev) +{ + return 0; +} + +static const struct of_device_id vdoa_dt_ids[] = { + { .compatible = "fsl,imx6q-vdoa" }, + {} +}; +MODULE_DEVICE_TABLE(of, vdoa_dt_ids); + +static struct platform_driver vdoa_driver = { + .probe = vdoa_probe, + .remove = vdoa_remove, + .driver = { + .name = VDOA_NAME, + .of_match_table = vdoa_dt_ids, + }, +}; + +module_platform_driver(vdoa_driver); + +MODULE_DESCRIPTION("Video Data Order Adapter"); +MODULE_AUTHOR("Philipp Zabel "); +MODULE_ALIAS("platform:imx-vdoa"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/chips-media/imx-vdoa.h b/drivers/media/platform/chips-media/imx-vdoa.h new file mode 100644 index 000000000..a62eab476 --- /dev/null +++ b/drivers/media/platform/chips-media/imx-vdoa.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2016 Pengutronix + */ + +#ifndef IMX_VDOA_H +#define IMX_VDOA_H + +struct vdoa_data; +struct vdoa_ctx; + +#if (defined CONFIG_VIDEO_IMX_VDOA || defined CONFIG_VIDEO_IMX_VDOA_MODULE) + +struct vdoa_ctx *vdoa_context_create(struct vdoa_data *vdoa); +int vdoa_context_configure(struct vdoa_ctx *ctx, + unsigned int width, unsigned int height, + u32 pixelformat); +void vdoa_context_destroy(struct vdoa_ctx *ctx); + +void vdoa_device_run(struct vdoa_ctx *ctx, dma_addr_t dst, dma_addr_t src); +int vdoa_wait_for_completion(struct vdoa_ctx *ctx); + +#else + +static inline struct vdoa_ctx *vdoa_context_create(struct vdoa_data *vdoa) +{ + return NULL; +} + +static inline int vdoa_context_configure(struct vdoa_ctx *ctx, + unsigned int width, + unsigned int height, + u32 pixelformat) +{ + return 0; +} + +static inline void vdoa_context_destroy(struct vdoa_ctx *ctx) { }; + +static inline void vdoa_device_run(struct vdoa_ctx *ctx, + dma_addr_t dst, dma_addr_t src) { }; + +static inline int vdoa_wait_for_completion(struct vdoa_ctx *ctx) +{ + return 0; +}; + +#endif + +#endif /* IMX_VDOA_H */ diff --git a/drivers/media/platform/chips-media/trace.h b/drivers/media/platform/chips-media/trace.h new file mode 100644 index 000000000..19f98e6da --- /dev/null +++ b/drivers/media/platform/chips-media/trace.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM coda + +#if !defined(__CODA_TRACE_H__) || defined(TRACE_HEADER_MULTI_READ) +#define __CODA_TRACE_H__ + +#include +#include + +#include "coda.h" + +TRACE_EVENT(coda_bit_run, + TP_PROTO(struct coda_ctx *ctx, int cmd), + + TP_ARGS(ctx, cmd), + + TP_STRUCT__entry( + __field(int, minor) + __field(int, ctx) + __field(int, cmd) + ), + + TP_fast_assign( + __entry->minor = ctx->fh.vdev->minor; + __entry->ctx = ctx->idx; + __entry->cmd = cmd; + ), + + TP_printk("minor = %d, ctx = %d, cmd = %d", + __entry->minor, __entry->ctx, __entry->cmd) +); + +TRACE_EVENT(coda_bit_done, + TP_PROTO(struct coda_ctx *ctx), + + TP_ARGS(ctx), + + TP_STRUCT__entry( + __field(int, minor) + __field(int, ctx) + ), + + TP_fast_assign( + __entry->minor = ctx->fh.vdev->minor; + __entry->ctx = ctx->idx; + ), + + TP_printk("minor = %d, ctx = %d", __entry->minor, __entry->ctx) +); + +DECLARE_EVENT_CLASS(coda_buf_class, + TP_PROTO(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf), + + TP_ARGS(ctx, buf), + + TP_STRUCT__entry( + __field(int, minor) + __field(int, index) + __field(int, ctx) + ), + + TP_fast_assign( + __entry->minor = ctx->fh.vdev->minor; + __entry->index = buf->vb2_buf.index; + __entry->ctx = ctx->idx; + ), + + TP_printk("minor = %d, index = %d, ctx = %d", + __entry->minor, __entry->index, __entry->ctx) +); + +DEFINE_EVENT(coda_buf_class, coda_enc_pic_run, + TP_PROTO(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf), + TP_ARGS(ctx, buf) +); + +DEFINE_EVENT(coda_buf_class, coda_enc_pic_done, + TP_PROTO(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf), + TP_ARGS(ctx, buf) +); + +DECLARE_EVENT_CLASS(coda_buf_meta_class, + TP_PROTO(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf, + struct coda_buffer_meta *meta), + + TP_ARGS(ctx, buf, meta), + + TP_STRUCT__entry( + __field(int, minor) + __field(int, index) + __field(int, start) + __field(int, end) + __field(int, ctx) + ), + + TP_fast_assign( + __entry->minor = ctx->fh.vdev->minor; + __entry->index = buf->vb2_buf.index; + __entry->start = meta->start & ctx->bitstream_fifo.kfifo.mask; + __entry->end = meta->end & ctx->bitstream_fifo.kfifo.mask; + __entry->ctx = ctx->idx; + ), + + TP_printk("minor = %d, index = %d, start = 0x%x, end = 0x%x, ctx = %d", + __entry->minor, __entry->index, __entry->start, __entry->end, + __entry->ctx) +); + +DEFINE_EVENT(coda_buf_meta_class, coda_bit_queue, + TP_PROTO(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf, + struct coda_buffer_meta *meta), + TP_ARGS(ctx, buf, meta) +); + +DECLARE_EVENT_CLASS(coda_meta_class, + TP_PROTO(struct coda_ctx *ctx, struct coda_buffer_meta *meta), + + TP_ARGS(ctx, meta), + + TP_STRUCT__entry( + __field(int, minor) + __field(int, start) + __field(int, end) + __field(int, ctx) + ), + + TP_fast_assign( + __entry->minor = ctx->fh.vdev->minor; + __entry->start = meta ? (meta->start & + ctx->bitstream_fifo.kfifo.mask) : 0; + __entry->end = meta ? (meta->end & + ctx->bitstream_fifo.kfifo.mask) : 0; + __entry->ctx = ctx->idx; + ), + + TP_printk("minor = %d, start = 0x%x, end = 0x%x, ctx = %d", + __entry->minor, __entry->start, __entry->end, __entry->ctx) +); + +DEFINE_EVENT(coda_meta_class, coda_dec_pic_run, + TP_PROTO(struct coda_ctx *ctx, struct coda_buffer_meta *meta), + TP_ARGS(ctx, meta) +); + +DEFINE_EVENT(coda_meta_class, coda_dec_pic_done, + TP_PROTO(struct coda_ctx *ctx, struct coda_buffer_meta *meta), + TP_ARGS(ctx, meta) +); + +DEFINE_EVENT(coda_buf_meta_class, coda_dec_rot_done, + TP_PROTO(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf, + struct coda_buffer_meta *meta), + TP_ARGS(ctx, buf, meta) +); + +DEFINE_EVENT(coda_buf_class, coda_jpeg_run, + TP_PROTO(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf), + TP_ARGS(ctx, buf) +); + +DEFINE_EVENT(coda_buf_class, coda_jpeg_done, + TP_PROTO(struct coda_ctx *ctx, struct vb2_v4l2_buffer *buf), + TP_ARGS(ctx, buf) +); + +#endif /* __CODA_TRACE_H__ */ + +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH ../../drivers/media/platform/chips-media +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE trace + +/* This part must be outside protection */ +#include diff --git a/drivers/media/platform/intel/Kconfig b/drivers/media/platform/intel/Kconfig new file mode 100644 index 000000000..724e80a90 --- /dev/null +++ b/drivers/media/platform/intel/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Intel media platform drivers" + +config VIDEO_PXA27x + tristate "PXA27x Quick Capture Interface driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on PXA27x || COMPILE_TEST + select VIDEOBUF2_DMA_SG + select SG_SPLIT + select V4L2_FWNODE + help + This is a v4l2 driver for the PXA27x Quick Capture Interface diff --git a/drivers/media/platform/intel/Makefile b/drivers/media/platform/intel/Makefile new file mode 100644 index 000000000..7e8889cbd --- /dev/null +++ b/drivers/media/platform/intel/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o diff --git a/drivers/media/platform/intel/pxa_camera.c b/drivers/media/platform/intel/pxa_camera.c new file mode 100644 index 000000000..54270d6b6 --- /dev/null +++ b/drivers/media/platform/intel/pxa_camera.c @@ -0,0 +1,2471 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * V4L2 Driver for PXA camera host + * + * Copyright (C) 2006, Sascha Hauer, Pengutronix + * Copyright (C) 2008, Guennadi Liakhovetski + * Copyright (C) 2016, Robert Jarzmik + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +#define PXA_CAM_VERSION "0.0.6" +#define PXA_CAM_DRV_NAME "pxa27x-camera" + +#define DEFAULT_WIDTH 640 +#define DEFAULT_HEIGHT 480 + +/* Camera Interface */ +#define CICR0 0x0000 +#define CICR1 0x0004 +#define CICR2 0x0008 +#define CICR3 0x000C +#define CICR4 0x0010 +#define CISR 0x0014 +#define CIFR 0x0018 +#define CITOR 0x001C +#define CIBR0 0x0028 +#define CIBR1 0x0030 +#define CIBR2 0x0038 + +#define CICR0_DMAEN (1UL << 31) /* DMA request enable */ +#define CICR0_PAR_EN (1 << 30) /* Parity enable */ +#define CICR0_SL_CAP_EN (1 << 29) /* Capture enable for slave mode */ +#define CICR0_ENB (1 << 28) /* Camera interface enable */ +#define CICR0_DIS (1 << 27) /* Camera interface disable */ +#define CICR0_SIM (0x7 << 24) /* Sensor interface mode mask */ +#define CICR0_TOM (1 << 9) /* Time-out mask */ +#define CICR0_RDAVM (1 << 8) /* Receive-data-available mask */ +#define CICR0_FEM (1 << 7) /* FIFO-empty mask */ +#define CICR0_EOLM (1 << 6) /* End-of-line mask */ +#define CICR0_PERRM (1 << 5) /* Parity-error mask */ +#define CICR0_QDM (1 << 4) /* Quick-disable mask */ +#define CICR0_CDM (1 << 3) /* Disable-done mask */ +#define CICR0_SOFM (1 << 2) /* Start-of-frame mask */ +#define CICR0_EOFM (1 << 1) /* End-of-frame mask */ +#define CICR0_FOM (1 << 0) /* FIFO-overrun mask */ + +#define CICR1_TBIT (1UL << 31) /* Transparency bit */ +#define CICR1_RGBT_CONV (0x3 << 29) /* RGBT conversion mask */ +#define CICR1_PPL (0x7ff << 15) /* Pixels per line mask */ +#define CICR1_RGB_CONV (0x7 << 12) /* RGB conversion mask */ +#define CICR1_RGB_F (1 << 11) /* RGB format */ +#define CICR1_YCBCR_F (1 << 10) /* YCbCr format */ +#define CICR1_RGB_BPP (0x7 << 7) /* RGB bis per pixel mask */ +#define CICR1_RAW_BPP (0x3 << 5) /* Raw bis per pixel mask */ +#define CICR1_COLOR_SP (0x3 << 3) /* Color space mask */ +#define CICR1_DW (0x7 << 0) /* Data width mask */ + +#define CICR2_BLW (0xff << 24) /* Beginning-of-line pixel clock + wait count mask */ +#define CICR2_ELW (0xff << 16) /* End-of-line pixel clock + wait count mask */ +#define CICR2_HSW (0x3f << 10) /* Horizontal sync pulse width mask */ +#define CICR2_BFPW (0x3f << 3) /* Beginning-of-frame pixel clock + wait count mask */ +#define CICR2_FSW (0x7 << 0) /* Frame stabilization + wait count mask */ + +#define CICR3_BFW (0xff << 24) /* Beginning-of-frame line clock + wait count mask */ +#define CICR3_EFW (0xff << 16) /* End-of-frame line clock + wait count mask */ +#define CICR3_VSW (0x3f << 10) /* Vertical sync pulse width mask */ +#define CICR3_BFPW (0x3f << 3) /* Beginning-of-frame pixel clock + wait count mask */ +#define CICR3_LPF (0x7ff << 0) /* Lines per frame mask */ + +#define CICR4_MCLK_DLY (0x3 << 24) /* MCLK Data Capture Delay mask */ +#define CICR4_PCLK_EN (1 << 23) /* Pixel clock enable */ +#define CICR4_PCP (1 << 22) /* Pixel clock polarity */ +#define CICR4_HSP (1 << 21) /* Horizontal sync polarity */ +#define CICR4_VSP (1 << 20) /* Vertical sync polarity */ +#define CICR4_MCLK_EN (1 << 19) /* MCLK enable */ +#define CICR4_FR_RATE (0x7 << 8) /* Frame rate mask */ +#define CICR4_DIV (0xff << 0) /* Clock divisor mask */ + +#define CISR_FTO (1 << 15) /* FIFO time-out */ +#define CISR_RDAV_2 (1 << 14) /* Channel 2 receive data available */ +#define CISR_RDAV_1 (1 << 13) /* Channel 1 receive data available */ +#define CISR_RDAV_0 (1 << 12) /* Channel 0 receive data available */ +#define CISR_FEMPTY_2 (1 << 11) /* Channel 2 FIFO empty */ +#define CISR_FEMPTY_1 (1 << 10) /* Channel 1 FIFO empty */ +#define CISR_FEMPTY_0 (1 << 9) /* Channel 0 FIFO empty */ +#define CISR_EOL (1 << 8) /* End of line */ +#define CISR_PAR_ERR (1 << 7) /* Parity error */ +#define CISR_CQD (1 << 6) /* Camera interface quick disable */ +#define CISR_CDD (1 << 5) /* Camera interface disable done */ +#define CISR_SOF (1 << 4) /* Start of frame */ +#define CISR_EOF (1 << 3) /* End of frame */ +#define CISR_IFO_2 (1 << 2) /* FIFO overrun for Channel 2 */ +#define CISR_IFO_1 (1 << 1) /* FIFO overrun for Channel 1 */ +#define CISR_IFO_0 (1 << 0) /* FIFO overrun for Channel 0 */ + +#define CIFR_FLVL2 (0x7f << 23) /* FIFO 2 level mask */ +#define CIFR_FLVL1 (0x7f << 16) /* FIFO 1 level mask */ +#define CIFR_FLVL0 (0xff << 8) /* FIFO 0 level mask */ +#define CIFR_THL_0 (0x3 << 4) /* Threshold Level for Channel 0 FIFO */ +#define CIFR_RESET_F (1 << 3) /* Reset input FIFOs */ +#define CIFR_FEN2 (1 << 2) /* FIFO enable for channel 2 */ +#define CIFR_FEN1 (1 << 1) /* FIFO enable for channel 1 */ +#define CIFR_FEN0 (1 << 0) /* FIFO enable for channel 0 */ + +#define CICR0_SIM_MP (0 << 24) +#define CICR0_SIM_SP (1 << 24) +#define CICR0_SIM_MS (2 << 24) +#define CICR0_SIM_EP (3 << 24) +#define CICR0_SIM_ES (4 << 24) + +#define CICR1_DW_VAL(x) ((x) & CICR1_DW) /* Data bus width */ +#define CICR1_PPL_VAL(x) (((x) << 15) & CICR1_PPL) /* Pixels per line */ +#define CICR1_COLOR_SP_VAL(x) (((x) << 3) & CICR1_COLOR_SP) /* color space */ +#define CICR1_RGB_BPP_VAL(x) (((x) << 7) & CICR1_RGB_BPP) /* bpp for rgb */ +#define CICR1_RGBT_CONV_VAL(x) (((x) << 29) & CICR1_RGBT_CONV) /* rgbt conv */ + +#define CICR2_BLW_VAL(x) (((x) << 24) & CICR2_BLW) /* Beginning-of-line pixel clock wait count */ +#define CICR2_ELW_VAL(x) (((x) << 16) & CICR2_ELW) /* End-of-line pixel clock wait count */ +#define CICR2_HSW_VAL(x) (((x) << 10) & CICR2_HSW) /* Horizontal sync pulse width */ +#define CICR2_BFPW_VAL(x) (((x) << 3) & CICR2_BFPW) /* Beginning-of-frame pixel clock wait count */ +#define CICR2_FSW_VAL(x) (((x) << 0) & CICR2_FSW) /* Frame stabilization wait count */ + +#define CICR3_BFW_VAL(x) (((x) << 24) & CICR3_BFW) /* Beginning-of-frame line clock wait count */ +#define CICR3_EFW_VAL(x) (((x) << 16) & CICR3_EFW) /* End-of-frame line clock wait count */ +#define CICR3_VSW_VAL(x) (((x) << 11) & CICR3_VSW) /* Vertical sync pulse width */ +#define CICR3_LPF_VAL(x) (((x) << 0) & CICR3_LPF) /* Lines per frame */ + +#define CICR0_IRQ_MASK (CICR0_TOM | CICR0_RDAVM | CICR0_FEM | CICR0_EOLM | \ + CICR0_PERRM | CICR0_QDM | CICR0_CDM | CICR0_SOFM | \ + CICR0_EOFM | CICR0_FOM) + +#define sensor_call(cam, o, f, args...) \ + v4l2_subdev_call(cam->sensor, o, f, ##args) + +/* + * Format handling + */ + +/** + * enum pxa_mbus_packing - data packing types on the media-bus + * @PXA_MBUS_PACKING_NONE: no packing, bit-for-bit transfer to RAM, one + * sample represents one pixel + * @PXA_MBUS_PACKING_2X8_PADHI: 16 bits transferred in 2 8-bit samples, in the + * possibly incomplete byte high bits are padding + * @PXA_MBUS_PACKING_EXTEND16: sample width (e.g., 10 bits) has to be extended + * to 16 bits + */ +enum pxa_mbus_packing { + PXA_MBUS_PACKING_NONE, + PXA_MBUS_PACKING_2X8_PADHI, + PXA_MBUS_PACKING_EXTEND16, +}; + +/** + * enum pxa_mbus_order - sample order on the media bus + * @PXA_MBUS_ORDER_LE: least significant sample first + * @PXA_MBUS_ORDER_BE: most significant sample first + */ +enum pxa_mbus_order { + PXA_MBUS_ORDER_LE, + PXA_MBUS_ORDER_BE, +}; + +/** + * enum pxa_mbus_layout - planes layout in memory + * @PXA_MBUS_LAYOUT_PACKED: color components packed + * @PXA_MBUS_LAYOUT_PLANAR_2Y_U_V: YUV components stored in 3 planes (4:2:2) + * @PXA_MBUS_LAYOUT_PLANAR_2Y_C: YUV components stored in a luma and a + * chroma plane (C plane is half the size + * of Y plane) + * @PXA_MBUS_LAYOUT_PLANAR_Y_C: YUV components stored in a luma and a + * chroma plane (C plane is the same size + * as Y plane) + */ +enum pxa_mbus_layout { + PXA_MBUS_LAYOUT_PACKED = 0, + PXA_MBUS_LAYOUT_PLANAR_2Y_U_V, + PXA_MBUS_LAYOUT_PLANAR_2Y_C, + PXA_MBUS_LAYOUT_PLANAR_Y_C, +}; + +/** + * struct pxa_mbus_pixelfmt - Data format on the media bus + * @name: Name of the format + * @fourcc: Fourcc code, that will be obtained if the data is + * stored in memory in the following way: + * @packing: Type of sample-packing, that has to be used + * @order: Sample order when storing in memory + * @layout: Planes layout in memory + * @bits_per_sample: How many bits the bridge has to sample + */ +struct pxa_mbus_pixelfmt { + const char *name; + u32 fourcc; + enum pxa_mbus_packing packing; + enum pxa_mbus_order order; + enum pxa_mbus_layout layout; + u8 bits_per_sample; +}; + +/** + * struct pxa_mbus_lookup - Lookup FOURCC IDs by mediabus codes for pass-through + * @code: mediabus pixel-code + * @fmt: pixel format description + */ +struct pxa_mbus_lookup { + u32 code; + struct pxa_mbus_pixelfmt fmt; +}; + +static const struct pxa_mbus_lookup mbus_fmt[] = { +{ + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_YUYV, + .name = "YUYV", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_YVYU, + .name = "YVYU", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_UYVY, + .name = "UYVY", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_VYUY, + .name = "VYUY", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB555, + .name = "RGB555", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB555X, + .name = "RGB555X", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_BE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB565, + .name = "RGB565", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB565X, + .name = "RGB565X", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_BE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .name = "Bayer 8 BGGR", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .name = "Bayer 8 GBRG", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .name = "Bayer 8 GRBG", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .name = "Bayer 8 RGGB", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .fmt = { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .name = "Bayer 10 BGGR", + .bits_per_sample = 10, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_Y8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_GREY, + .name = "Grey", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .fmt = { + .fourcc = V4L2_PIX_FMT_Y10, + .name = "Grey 10bit", + .bits_per_sample = 10, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, + .fmt = { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .name = "Bayer 10 BGGR", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_BE, + .fmt = { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .name = "Bayer 10 BGGR", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_BE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE, + .fmt = { + .fourcc = V4L2_PIX_FMT_RGB444, + .name = "RGB444", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_BE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .fmt = { + .fourcc = V4L2_PIX_FMT_UYVY, + .name = "UYVY 16bit", + .bits_per_sample = 16, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_VYUY8_1X16, + .fmt = { + .fourcc = V4L2_PIX_FMT_VYUY, + .name = "VYUY 16bit", + .bits_per_sample = 16, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .fmt = { + .fourcc = V4L2_PIX_FMT_YUYV, + .name = "YUYV 16bit", + .bits_per_sample = 16, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_YVYU8_1X16, + .fmt = { + .fourcc = V4L2_PIX_FMT_YVYU, + .name = "YVYU 16bit", + .bits_per_sample = 16, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGRBG10_DPCM8_1X8, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGRBG10DPCM8, + .name = "Bayer 10 BGGR DPCM 8", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_NONE, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .name = "Bayer 10 GBRG", + .bits_per_sample = 10, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .name = "Bayer 10 GRBG", + .bits_per_sample = 10, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .fmt = { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .name = "Bayer 10 RGGB", + .bits_per_sample = 10, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .fmt = { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .name = "Bayer 12 BGGR", + .bits_per_sample = 12, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .name = "Bayer 12 GBRG", + .bits_per_sample = 12, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .fmt = { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .name = "Bayer 12 GRBG", + .bits_per_sample = 12, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .fmt = { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .name = "Bayer 12 RGGB", + .bits_per_sample = 12, + .packing = PXA_MBUS_PACKING_EXTEND16, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PACKED, + }, +}, +}; + +static s32 pxa_mbus_bytes_per_line(u32 width, const struct pxa_mbus_pixelfmt *mf) +{ + if (mf->layout != PXA_MBUS_LAYOUT_PACKED) + return width * mf->bits_per_sample / 8; + + switch (mf->packing) { + case PXA_MBUS_PACKING_NONE: + return width * mf->bits_per_sample / 8; + case PXA_MBUS_PACKING_2X8_PADHI: + case PXA_MBUS_PACKING_EXTEND16: + return width * 2; + } + return -EINVAL; +} + +static s32 pxa_mbus_image_size(const struct pxa_mbus_pixelfmt *mf, + u32 bytes_per_line, u32 height) +{ + if (mf->layout == PXA_MBUS_LAYOUT_PACKED) + return bytes_per_line * height; + + switch (mf->packing) { + case PXA_MBUS_PACKING_2X8_PADHI: + return bytes_per_line * height * 2; + default: + return -EINVAL; + } +} + +static const struct pxa_mbus_pixelfmt *pxa_mbus_find_fmtdesc( + u32 code, + const struct pxa_mbus_lookup *lookup, + int n) +{ + int i; + + for (i = 0; i < n; i++) + if (lookup[i].code == code) + return &lookup[i].fmt; + + return NULL; +} + +static const struct pxa_mbus_pixelfmt *pxa_mbus_get_fmtdesc( + u32 code) +{ + return pxa_mbus_find_fmtdesc(code, mbus_fmt, ARRAY_SIZE(mbus_fmt)); +} + +/** + * struct pxa_camera_format_xlate - match between host and sensor formats + * @code: code of a sensor provided format + * @host_fmt: host format after host translation from code + * + * Host and sensor translation structure. Used in table of host and sensor + * formats matchings in pxa_camera_device. A host can override the generic list + * generation by implementing get_formats(), and use it for format checks and + * format setup. + */ +struct pxa_camera_format_xlate { + u32 code; + const struct pxa_mbus_pixelfmt *host_fmt; +}; + +/* + * Structures + */ +enum pxa_camera_active_dma { + DMA_Y = 0x1, + DMA_U = 0x2, + DMA_V = 0x4, +}; + +/* buffer for one video frame */ +struct pxa_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vbuf; + struct list_head queue; + u32 code; + int nb_planes; + /* our descriptor lists for Y, U and V channels */ + struct dma_async_tx_descriptor *descs[3]; + dma_cookie_t cookie[3]; + struct scatterlist *sg[3]; + int sg_len[3]; + size_t plane_sizes[3]; + int inwork; + enum pxa_camera_active_dma active_dma; +}; + +struct pxa_camera_dev { + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct v4l2_async_notifier notifier; + struct vb2_queue vb2_vq; + struct v4l2_subdev *sensor; + struct pxa_camera_format_xlate *user_formats; + const struct pxa_camera_format_xlate *current_fmt; + struct v4l2_pix_format current_pix; + + /* + * PXA27x is only supposed to handle one camera on its Quick Capture + * interface. If anyone ever builds hardware to enable more than + * one camera, they will have to modify this driver too + */ + struct clk *clk; + + unsigned int irq; + void __iomem *base; + + int channels; + struct dma_chan *dma_chans[3]; + + struct pxacamera_platform_data *pdata; + struct resource *res; + unsigned long platform_flags; + unsigned long ciclk; + unsigned long mclk; + u32 mclk_divisor; + u16 width_flags; /* max 10 bits */ + + struct list_head capture; + + spinlock_t lock; + struct mutex mlock; + unsigned int buf_sequence; + + struct pxa_buffer *active; + struct tasklet_struct task_eof; + + u32 save_cicr[5]; +}; + +struct pxa_cam { + unsigned long flags; +}; + +static const char *pxa_cam_driver_description = "PXA_Camera"; + +/* + * Format translation functions + */ +static const struct pxa_camera_format_xlate +*pxa_mbus_xlate_by_fourcc(struct pxa_camera_format_xlate *user_formats, + unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; user_formats[i].code; i++) + if (user_formats[i].host_fmt->fourcc == fourcc) + return user_formats + i; + return NULL; +} + +static struct pxa_camera_format_xlate *pxa_mbus_build_fmts_xlate( + struct v4l2_device *v4l2_dev, struct v4l2_subdev *subdev, + int (*get_formats)(struct v4l2_device *, unsigned int, + struct pxa_camera_format_xlate *xlate)) +{ + unsigned int i, fmts = 0, raw_fmts = 0; + int ret; + struct v4l2_subdev_mbus_code_enum code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct pxa_camera_format_xlate *user_formats; + + while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL, &code)) { + raw_fmts++; + code.index++; + } + + /* + * First pass - only count formats this host-sensor + * configuration can provide + */ + for (i = 0; i < raw_fmts; i++) { + ret = get_formats(v4l2_dev, i, NULL); + if (ret < 0) + return ERR_PTR(ret); + fmts += ret; + } + + if (!fmts) + return ERR_PTR(-ENXIO); + + user_formats = kcalloc(fmts + 1, sizeof(*user_formats), GFP_KERNEL); + if (!user_formats) + return ERR_PTR(-ENOMEM); + + /* Second pass - actually fill data formats */ + fmts = 0; + for (i = 0; i < raw_fmts; i++) { + ret = get_formats(v4l2_dev, i, user_formats + fmts); + if (ret < 0) + goto egfmt; + fmts += ret; + } + user_formats[fmts].code = 0; + + return user_formats; +egfmt: + kfree(user_formats); + return ERR_PTR(ret); +} + +/* + * Videobuf operations + */ +static struct pxa_buffer *vb2_to_pxa_buffer(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + return container_of(vbuf, struct pxa_buffer, vbuf); +} + +static struct device *pcdev_to_dev(struct pxa_camera_dev *pcdev) +{ + return pcdev->v4l2_dev.dev; +} + +static struct pxa_camera_dev *v4l2_dev_to_pcdev(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct pxa_camera_dev, v4l2_dev); +} + +static void pxa_camera_dma_irq(struct pxa_camera_dev *pcdev, + enum pxa_camera_active_dma act_dma); + +static void pxa_camera_dma_irq_y(void *data) +{ + struct pxa_camera_dev *pcdev = data; + + pxa_camera_dma_irq(pcdev, DMA_Y); +} + +static void pxa_camera_dma_irq_u(void *data) +{ + struct pxa_camera_dev *pcdev = data; + + pxa_camera_dma_irq(pcdev, DMA_U); +} + +static void pxa_camera_dma_irq_v(void *data) +{ + struct pxa_camera_dev *pcdev = data; + + pxa_camera_dma_irq(pcdev, DMA_V); +} + +/** + * pxa_init_dma_channel - init dma descriptors + * @pcdev: pxa camera device + * @buf: pxa camera buffer + * @channel: dma channel (0 => 'Y', 1 => 'U', 2 => 'V') + * @sg: dma scatter list + * @sglen: dma scatter list length + * + * Prepares the pxa dma descriptors to transfer one camera channel. + * + * Returns 0 if success or -ENOMEM if no memory is available + */ +static int pxa_init_dma_channel(struct pxa_camera_dev *pcdev, + struct pxa_buffer *buf, int channel, + struct scatterlist *sg, int sglen) +{ + struct dma_chan *dma_chan = pcdev->dma_chans[channel]; + struct dma_async_tx_descriptor *tx; + + tx = dmaengine_prep_slave_sg(dma_chan, sg, sglen, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_REUSE); + if (!tx) { + dev_err(pcdev_to_dev(pcdev), + "dmaengine_prep_slave_sg failed\n"); + goto fail; + } + + tx->callback_param = pcdev; + switch (channel) { + case 0: + tx->callback = pxa_camera_dma_irq_y; + break; + case 1: + tx->callback = pxa_camera_dma_irq_u; + break; + case 2: + tx->callback = pxa_camera_dma_irq_v; + break; + } + + buf->descs[channel] = tx; + return 0; +fail: + dev_dbg(pcdev_to_dev(pcdev), + "%s (vb=%p) dma_tx=%p\n", + __func__, buf, tx); + + return -ENOMEM; +} + +static void pxa_video_buf_set_actdma(struct pxa_camera_dev *pcdev, + struct pxa_buffer *buf) +{ + buf->active_dma = DMA_Y; + if (buf->nb_planes == 3) + buf->active_dma |= DMA_U | DMA_V; +} + +/** + * pxa_dma_start_channels - start DMA channel for active buffer + * @pcdev: pxa camera device + * + * Initialize DMA channels to the beginning of the active video buffer, and + * start these channels. + */ +static void pxa_dma_start_channels(struct pxa_camera_dev *pcdev) +{ + int i; + + for (i = 0; i < pcdev->channels; i++) { + dev_dbg(pcdev_to_dev(pcdev), + "%s (channel=%d)\n", __func__, i); + dma_async_issue_pending(pcdev->dma_chans[i]); + } +} + +static void pxa_dma_stop_channels(struct pxa_camera_dev *pcdev) +{ + int i; + + for (i = 0; i < pcdev->channels; i++) { + dev_dbg(pcdev_to_dev(pcdev), + "%s (channel=%d)\n", __func__, i); + dmaengine_terminate_all(pcdev->dma_chans[i]); + } +} + +static void pxa_dma_add_tail_buf(struct pxa_camera_dev *pcdev, + struct pxa_buffer *buf) +{ + int i; + + for (i = 0; i < pcdev->channels; i++) { + buf->cookie[i] = dmaengine_submit(buf->descs[i]); + dev_dbg(pcdev_to_dev(pcdev), + "%s (channel=%d) : submit vb=%p cookie=%d\n", + __func__, i, buf, buf->descs[i]->cookie); + } +} + +/** + * pxa_camera_start_capture - start video capturing + * @pcdev: camera device + * + * Launch capturing. DMA channels should not be active yet. They should get + * activated at the end of frame interrupt, to capture only whole frames, and + * never begin the capture of a partial frame. + */ +static void pxa_camera_start_capture(struct pxa_camera_dev *pcdev) +{ + unsigned long cicr0; + + dev_dbg(pcdev_to_dev(pcdev), "%s\n", __func__); + __raw_writel(__raw_readl(pcdev->base + CISR), pcdev->base + CISR); + /* Enable End-Of-Frame Interrupt */ + cicr0 = __raw_readl(pcdev->base + CICR0) | CICR0_ENB; + cicr0 &= ~CICR0_EOFM; + __raw_writel(cicr0, pcdev->base + CICR0); +} + +static void pxa_camera_stop_capture(struct pxa_camera_dev *pcdev) +{ + unsigned long cicr0; + + pxa_dma_stop_channels(pcdev); + + cicr0 = __raw_readl(pcdev->base + CICR0) & ~CICR0_ENB; + __raw_writel(cicr0, pcdev->base + CICR0); + + pcdev->active = NULL; + dev_dbg(pcdev_to_dev(pcdev), "%s\n", __func__); +} + +static void pxa_camera_wakeup(struct pxa_camera_dev *pcdev, + struct pxa_buffer *buf, + enum vb2_buffer_state state) +{ + struct vb2_buffer *vb = &buf->vbuf.vb2_buf; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + /* _init is used to debug races, see comment in pxa_camera_reqbufs() */ + list_del_init(&buf->queue); + vb->timestamp = ktime_get_ns(); + vbuf->sequence = pcdev->buf_sequence++; + vbuf->field = V4L2_FIELD_NONE; + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + dev_dbg(pcdev_to_dev(pcdev), "%s dequeued buffer (buf=0x%p)\n", + __func__, buf); + + if (list_empty(&pcdev->capture)) { + pxa_camera_stop_capture(pcdev); + return; + } + + pcdev->active = list_entry(pcdev->capture.next, + struct pxa_buffer, queue); +} + +/** + * pxa_camera_check_link_miss - check missed DMA linking + * @pcdev: camera device + * @last_submitted: an opaque DMA cookie for last submitted + * @last_issued: an opaque DMA cookie for last issued + * + * The DMA chaining is done with DMA running. This means a tiny temporal window + * remains, where a buffer is queued on the chain, while the chain is already + * stopped. This means the tailed buffer would never be transferred by DMA. + * This function restarts the capture for this corner case, where : + * - DADR() == DADDR_STOP + * - a video buffer is queued on the pcdev->capture list + * + * Please check the "DMA hot chaining timeslice issue" in + * Documentation/driver-api/media/drivers/pxa_camera.rst + * + * Context: should only be called within the dma irq handler + */ +static void pxa_camera_check_link_miss(struct pxa_camera_dev *pcdev, + dma_cookie_t last_submitted, + dma_cookie_t last_issued) +{ + bool is_dma_stopped = last_submitted != last_issued; + + dev_dbg(pcdev_to_dev(pcdev), + "%s : top queued buffer=%p, is_dma_stopped=%d\n", + __func__, pcdev->active, is_dma_stopped); + + if (pcdev->active && is_dma_stopped) + pxa_camera_start_capture(pcdev); +} + +static void pxa_camera_dma_irq(struct pxa_camera_dev *pcdev, + enum pxa_camera_active_dma act_dma) +{ + struct pxa_buffer *buf, *last_buf; + unsigned long flags; + u32 camera_status, overrun; + int chan; + enum dma_status last_status; + dma_cookie_t last_issued; + + spin_lock_irqsave(&pcdev->lock, flags); + + camera_status = __raw_readl(pcdev->base + CISR); + dev_dbg(pcdev_to_dev(pcdev), "camera dma irq, cisr=0x%x dma=%d\n", + camera_status, act_dma); + overrun = CISR_IFO_0; + if (pcdev->channels == 3) + overrun |= CISR_IFO_1 | CISR_IFO_2; + + /* + * pcdev->active should not be NULL in DMA irq handler. + * + * But there is one corner case : if capture was stopped due to an + * overrun of channel 1, and at that same channel 2 was completed. + * + * When handling the overrun in DMA irq for channel 1, we'll stop the + * capture and restart it (and thus set pcdev->active to NULL). But the + * DMA irq handler will already be pending for channel 2. So on entering + * the DMA irq handler for channel 2 there will be no active buffer, yet + * that is normal. + */ + if (!pcdev->active) + goto out; + + buf = pcdev->active; + WARN_ON(buf->inwork || list_empty(&buf->queue)); + + /* + * It's normal if the last frame creates an overrun, as there + * are no more DMA descriptors to fetch from QCI fifos + */ + switch (act_dma) { + case DMA_U: + chan = 1; + break; + case DMA_V: + chan = 2; + break; + default: + chan = 0; + break; + } + last_buf = list_entry(pcdev->capture.prev, + struct pxa_buffer, queue); + last_status = dma_async_is_tx_complete(pcdev->dma_chans[chan], + last_buf->cookie[chan], + NULL, &last_issued); + if (camera_status & overrun && + last_status != DMA_COMPLETE) { + dev_dbg(pcdev_to_dev(pcdev), "FIFO overrun! CISR: %x\n", + camera_status); + pxa_camera_stop_capture(pcdev); + list_for_each_entry(buf, &pcdev->capture, queue) + pxa_dma_add_tail_buf(pcdev, buf); + pxa_camera_start_capture(pcdev); + goto out; + } + buf->active_dma &= ~act_dma; + if (!buf->active_dma) { + pxa_camera_wakeup(pcdev, buf, VB2_BUF_STATE_DONE); + pxa_camera_check_link_miss(pcdev, last_buf->cookie[chan], + last_issued); + } + +out: + spin_unlock_irqrestore(&pcdev->lock, flags); +} + +static u32 mclk_get_divisor(struct platform_device *pdev, + struct pxa_camera_dev *pcdev) +{ + unsigned long mclk = pcdev->mclk; + u32 div; + unsigned long lcdclk; + + lcdclk = clk_get_rate(pcdev->clk); + pcdev->ciclk = lcdclk; + + /* mclk <= ciclk / 4 (27.4.2) */ + if (mclk > lcdclk / 4) { + mclk = lcdclk / 4; + dev_warn(&pdev->dev, + "Limiting master clock to %lu\n", mclk); + } + + /* We verify mclk != 0, so if anyone breaks it, here comes their Oops */ + div = (lcdclk + 2 * mclk - 1) / (2 * mclk) - 1; + + /* If we're not supplying MCLK, leave it at 0 */ + if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN) + pcdev->mclk = lcdclk / (2 * (div + 1)); + + dev_dbg(&pdev->dev, "LCD clock %luHz, target freq %luHz, divisor %u\n", + lcdclk, mclk, div); + + return div; +} + +static void recalculate_fifo_timeout(struct pxa_camera_dev *pcdev, + unsigned long pclk) +{ + /* We want a timeout > 1 pixel time, not ">=" */ + u32 ciclk_per_pixel = pcdev->ciclk / pclk + 1; + + __raw_writel(ciclk_per_pixel, pcdev->base + CITOR); +} + +static void pxa_camera_activate(struct pxa_camera_dev *pcdev) +{ + u32 cicr4 = 0; + + /* disable all interrupts */ + __raw_writel(0x3ff, pcdev->base + CICR0); + + if (pcdev->platform_flags & PXA_CAMERA_PCLK_EN) + cicr4 |= CICR4_PCLK_EN; + if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN) + cicr4 |= CICR4_MCLK_EN; + if (pcdev->platform_flags & PXA_CAMERA_PCP) + cicr4 |= CICR4_PCP; + if (pcdev->platform_flags & PXA_CAMERA_HSP) + cicr4 |= CICR4_HSP; + if (pcdev->platform_flags & PXA_CAMERA_VSP) + cicr4 |= CICR4_VSP; + + __raw_writel(pcdev->mclk_divisor | cicr4, pcdev->base + CICR4); + + if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN) + /* Initialise the timeout under the assumption pclk = mclk */ + recalculate_fifo_timeout(pcdev, pcdev->mclk); + else + /* "Safe default" - 13MHz */ + recalculate_fifo_timeout(pcdev, 13000000); + + clk_prepare_enable(pcdev->clk); +} + +static void pxa_camera_deactivate(struct pxa_camera_dev *pcdev) +{ + clk_disable_unprepare(pcdev->clk); +} + +static void pxa_camera_eof(struct tasklet_struct *t) +{ + struct pxa_camera_dev *pcdev = from_tasklet(pcdev, t, task_eof); + unsigned long cifr; + struct pxa_buffer *buf; + + dev_dbg(pcdev_to_dev(pcdev), + "Camera interrupt status 0x%x\n", + __raw_readl(pcdev->base + CISR)); + + /* Reset the FIFOs */ + cifr = __raw_readl(pcdev->base + CIFR) | CIFR_RESET_F; + __raw_writel(cifr, pcdev->base + CIFR); + + pcdev->active = list_first_entry(&pcdev->capture, + struct pxa_buffer, queue); + buf = pcdev->active; + pxa_video_buf_set_actdma(pcdev, buf); + + pxa_dma_start_channels(pcdev); +} + +static irqreturn_t pxa_camera_irq(int irq, void *data) +{ + struct pxa_camera_dev *pcdev = data; + unsigned long status, cicr0; + + status = __raw_readl(pcdev->base + CISR); + dev_dbg(pcdev_to_dev(pcdev), + "Camera interrupt status 0x%lx\n", status); + + if (!status) + return IRQ_NONE; + + __raw_writel(status, pcdev->base + CISR); + + if (status & CISR_EOF) { + cicr0 = __raw_readl(pcdev->base + CICR0) | CICR0_EOFM; + __raw_writel(cicr0, pcdev->base + CICR0); + tasklet_schedule(&pcdev->task_eof); + } + + return IRQ_HANDLED; +} + +static void pxa_camera_setup_cicr(struct pxa_camera_dev *pcdev, + unsigned long flags, __u32 pixfmt) +{ + unsigned long dw, bpp; + u32 cicr0, cicr1, cicr2, cicr3, cicr4 = 0, y_skip_top; + int ret = sensor_call(pcdev, sensor, g_skip_top_lines, &y_skip_top); + + if (ret < 0) + y_skip_top = 0; + + /* + * Datawidth is now guaranteed to be equal to one of the three values. + * We fix bit-per-pixel equal to data-width... + */ + switch (pcdev->current_fmt->host_fmt->bits_per_sample) { + case 10: + dw = 4; + bpp = 0x40; + break; + case 9: + dw = 3; + bpp = 0x20; + break; + default: + /* + * Actually it can only be 8 now, + * default is just to silence compiler warnings + */ + case 8: + dw = 2; + bpp = 0; + } + + if (pcdev->platform_flags & PXA_CAMERA_PCLK_EN) + cicr4 |= CICR4_PCLK_EN; + if (pcdev->platform_flags & PXA_CAMERA_MCLK_EN) + cicr4 |= CICR4_MCLK_EN; + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + cicr4 |= CICR4_PCP; + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + cicr4 |= CICR4_HSP; + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + cicr4 |= CICR4_VSP; + + cicr0 = __raw_readl(pcdev->base + CICR0); + if (cicr0 & CICR0_ENB) + __raw_writel(cicr0 & ~CICR0_ENB, pcdev->base + CICR0); + + cicr1 = CICR1_PPL_VAL(pcdev->current_pix.width - 1) | bpp | dw; + + switch (pixfmt) { + case V4L2_PIX_FMT_YUV422P: + pcdev->channels = 3; + cicr1 |= CICR1_YCBCR_F; + /* + * Normally, pxa bus wants as input UYVY format. We allow all + * reorderings of the YUV422 format, as no processing is done, + * and the YUV stream is just passed through without any + * transformation. Note that UYVY is the only format that + * should be used if pxa framebuffer Overlay2 is used. + */ + fallthrough; + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + cicr1 |= CICR1_COLOR_SP_VAL(2); + break; + case V4L2_PIX_FMT_RGB555: + cicr1 |= CICR1_RGB_BPP_VAL(1) | CICR1_RGBT_CONV_VAL(2) | + CICR1_TBIT | CICR1_COLOR_SP_VAL(1); + break; + case V4L2_PIX_FMT_RGB565: + cicr1 |= CICR1_COLOR_SP_VAL(1) | CICR1_RGB_BPP_VAL(2); + break; + } + + cicr2 = 0; + cicr3 = CICR3_LPF_VAL(pcdev->current_pix.height - 1) | + CICR3_BFW_VAL(min((u32)255, y_skip_top)); + cicr4 |= pcdev->mclk_divisor; + + __raw_writel(cicr1, pcdev->base + CICR1); + __raw_writel(cicr2, pcdev->base + CICR2); + __raw_writel(cicr3, pcdev->base + CICR3); + __raw_writel(cicr4, pcdev->base + CICR4); + + /* CIF interrupts are not used, only DMA */ + cicr0 = (cicr0 & CICR0_ENB) | (pcdev->platform_flags & PXA_CAMERA_MASTER ? + CICR0_SIM_MP : (CICR0_SL_CAP_EN | CICR0_SIM_SP)); + cicr0 |= CICR0_DMAEN | CICR0_IRQ_MASK; + __raw_writel(cicr0, pcdev->base + CICR0); +} + +/* + * Videobuf2 section + */ +static void pxa_buffer_cleanup(struct pxa_buffer *buf) +{ + int i; + + for (i = 0; i < 3 && buf->descs[i]; i++) { + dmaengine_desc_free(buf->descs[i]); + kfree(buf->sg[i]); + buf->descs[i] = NULL; + buf->sg[i] = NULL; + buf->sg_len[i] = 0; + buf->plane_sizes[i] = 0; + } + buf->nb_planes = 0; +} + +static int pxa_buffer_init(struct pxa_camera_dev *pcdev, + struct pxa_buffer *buf) +{ + struct vb2_buffer *vb = &buf->vbuf.vb2_buf; + struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, 0); + int nb_channels = pcdev->channels; + int i, ret = 0; + unsigned long size = vb2_plane_size(vb, 0); + + switch (nb_channels) { + case 1: + buf->plane_sizes[0] = size; + break; + case 3: + buf->plane_sizes[0] = size / 2; + buf->plane_sizes[1] = size / 4; + buf->plane_sizes[2] = size / 4; + break; + default: + return -EINVAL; + } + buf->nb_planes = nb_channels; + + ret = sg_split(sgt->sgl, sgt->nents, 0, nb_channels, + buf->plane_sizes, buf->sg, buf->sg_len, GFP_KERNEL); + if (ret < 0) { + dev_err(pcdev_to_dev(pcdev), + "sg_split failed: %d\n", ret); + return ret; + } + for (i = 0; i < nb_channels; i++) { + ret = pxa_init_dma_channel(pcdev, buf, i, + buf->sg[i], buf->sg_len[i]); + if (ret) { + pxa_buffer_cleanup(buf); + return ret; + } + } + INIT_LIST_HEAD(&buf->queue); + + return ret; +} + +static void pxac_vb2_cleanup(struct vb2_buffer *vb) +{ + struct pxa_buffer *buf = vb2_to_pxa_buffer(vb); + struct pxa_camera_dev *pcdev = vb2_get_drv_priv(vb->vb2_queue); + + dev_dbg(pcdev_to_dev(pcdev), + "%s(vb=%p)\n", __func__, vb); + pxa_buffer_cleanup(buf); +} + +static void pxac_vb2_queue(struct vb2_buffer *vb) +{ + struct pxa_buffer *buf = vb2_to_pxa_buffer(vb); + struct pxa_camera_dev *pcdev = vb2_get_drv_priv(vb->vb2_queue); + + dev_dbg(pcdev_to_dev(pcdev), + "%s(vb=%p) nb_channels=%d size=%lu active=%p\n", + __func__, vb, pcdev->channels, vb2_get_plane_payload(vb, 0), + pcdev->active); + + list_add_tail(&buf->queue, &pcdev->capture); + + pxa_dma_add_tail_buf(pcdev, buf); +} + +/* + * Please check the DMA prepared buffer structure in : + * Documentation/driver-api/media/drivers/pxa_camera.rst + * Please check also in pxa_camera_check_link_miss() to understand why DMA chain + * modification while DMA chain is running will work anyway. + */ +static int pxac_vb2_prepare(struct vb2_buffer *vb) +{ + struct pxa_camera_dev *pcdev = vb2_get_drv_priv(vb->vb2_queue); + struct pxa_buffer *buf = vb2_to_pxa_buffer(vb); + int ret = 0; +#ifdef DEBUG + int i; +#endif + + switch (pcdev->channels) { + case 1: + case 3: + vb2_set_plane_payload(vb, 0, pcdev->current_pix.sizeimage); + break; + default: + return -EINVAL; + } + + dev_dbg(pcdev_to_dev(pcdev), + "%s (vb=%p) nb_channels=%d size=%lu\n", + __func__, vb, pcdev->channels, vb2_get_plane_payload(vb, 0)); + + WARN_ON(!pcdev->current_fmt); + +#ifdef DEBUG + /* + * This can be useful if you want to see if we actually fill + * the buffer with something + */ + for (i = 0; i < vb->num_planes; i++) + memset((void *)vb2_plane_vaddr(vb, i), + 0xaa, vb2_get_plane_payload(vb, i)); +#endif + + /* + * I think, in buf_prepare you only have to protect global data, + * the actual buffer is yours + */ + buf->inwork = 0; + pxa_video_buf_set_actdma(pcdev, buf); + + return ret; +} + +static int pxac_vb2_init(struct vb2_buffer *vb) +{ + struct pxa_camera_dev *pcdev = vb2_get_drv_priv(vb->vb2_queue); + struct pxa_buffer *buf = vb2_to_pxa_buffer(vb); + + dev_dbg(pcdev_to_dev(pcdev), + "%s(nb_channels=%d)\n", + __func__, pcdev->channels); + + return pxa_buffer_init(pcdev, buf); +} + +static int pxac_vb2_queue_setup(struct vb2_queue *vq, + unsigned int *nbufs, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct pxa_camera_dev *pcdev = vb2_get_drv_priv(vq); + int size = pcdev->current_pix.sizeimage; + + dev_dbg(pcdev_to_dev(pcdev), + "%s(vq=%p nbufs=%d num_planes=%d size=%d)\n", + __func__, vq, *nbufs, *num_planes, size); + /* + * Called from VIDIOC_REQBUFS or in compatibility mode For YUV422P + * format, even if there are 3 planes Y, U and V, we reply there is only + * one plane, containing Y, U and V data, one after the other. + */ + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + + *num_planes = 1; + switch (pcdev->channels) { + case 1: + case 3: + sizes[0] = size; + break; + default: + return -EINVAL; + } + + if (!*nbufs) + *nbufs = 1; + + return 0; +} + +static int pxac_vb2_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct pxa_camera_dev *pcdev = vb2_get_drv_priv(vq); + + dev_dbg(pcdev_to_dev(pcdev), "%s(count=%d) active=%p\n", + __func__, count, pcdev->active); + + pcdev->buf_sequence = 0; + if (!pcdev->active) + pxa_camera_start_capture(pcdev); + + return 0; +} + +static void pxac_vb2_stop_streaming(struct vb2_queue *vq) +{ + struct pxa_camera_dev *pcdev = vb2_get_drv_priv(vq); + struct pxa_buffer *buf, *tmp; + + dev_dbg(pcdev_to_dev(pcdev), "%s active=%p\n", + __func__, pcdev->active); + pxa_camera_stop_capture(pcdev); + + list_for_each_entry_safe(buf, tmp, &pcdev->capture, queue) + pxa_camera_wakeup(pcdev, buf, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops pxac_vb2_ops = { + .queue_setup = pxac_vb2_queue_setup, + .buf_init = pxac_vb2_init, + .buf_prepare = pxac_vb2_prepare, + .buf_queue = pxac_vb2_queue, + .buf_cleanup = pxac_vb2_cleanup, + .start_streaming = pxac_vb2_start_streaming, + .stop_streaming = pxac_vb2_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int pxa_camera_init_videobuf2(struct pxa_camera_dev *pcdev) +{ + int ret; + struct vb2_queue *vq = &pcdev->vb2_vq; + + memset(vq, 0, sizeof(*vq)); + vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + vq->drv_priv = pcdev; + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vq->buf_struct_size = sizeof(struct pxa_buffer); + vq->dev = pcdev->v4l2_dev.dev; + + vq->ops = &pxac_vb2_ops; + vq->mem_ops = &vb2_dma_sg_memops; + vq->lock = &pcdev->mlock; + + ret = vb2_queue_init(vq); + dev_dbg(pcdev_to_dev(pcdev), + "vb2_queue_init(vq=%p): %d\n", vq, ret); + + return ret; +} + +/* + * Video ioctls section + */ +static int pxa_camera_set_bus_param(struct pxa_camera_dev *pcdev) +{ + unsigned int bus_width = pcdev->current_fmt->host_fmt->bits_per_sample; + struct v4l2_mbus_config cfg = {.type = V4L2_MBUS_PARALLEL,}; + u32 pixfmt = pcdev->current_fmt->host_fmt->fourcc; + int mbus_config; + int ret; + + if (!((1 << (bus_width - 1)) & pcdev->width_flags)) { + dev_err(pcdev_to_dev(pcdev), "Unsupported bus width %u", + bus_width); + return -EINVAL; + } + + pcdev->channels = 1; + + /* Make choices, based on platform preferences */ + mbus_config = 0; + if (pcdev->platform_flags & PXA_CAMERA_MASTER) + mbus_config |= V4L2_MBUS_MASTER; + else + mbus_config |= V4L2_MBUS_SLAVE; + + if (pcdev->platform_flags & PXA_CAMERA_HSP) + mbus_config |= V4L2_MBUS_HSYNC_ACTIVE_HIGH; + else + mbus_config |= V4L2_MBUS_HSYNC_ACTIVE_LOW; + + if (pcdev->platform_flags & PXA_CAMERA_VSP) + mbus_config |= V4L2_MBUS_VSYNC_ACTIVE_HIGH; + else + mbus_config |= V4L2_MBUS_VSYNC_ACTIVE_LOW; + + if (pcdev->platform_flags & PXA_CAMERA_PCP) + mbus_config |= V4L2_MBUS_PCLK_SAMPLE_RISING; + else + mbus_config |= V4L2_MBUS_PCLK_SAMPLE_FALLING; + mbus_config |= V4L2_MBUS_DATA_ACTIVE_HIGH; + + ret = sensor_call(pcdev, pad, get_mbus_config, 0, &cfg); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_err(pcdev_to_dev(pcdev), + "Failed to call get_mbus_config: %d\n", ret); + return ret; + } + + /* + * If the media bus configuration of the sensor differs, make sure it + * is supported by the platform. + * + * PXA does not support V4L2_MBUS_DATA_ACTIVE_LOW and the bus mastering + * roles should match. + */ + if (cfg.bus.parallel.flags != mbus_config) { + unsigned int pxa_mbus_role = mbus_config & (V4L2_MBUS_MASTER | + V4L2_MBUS_SLAVE); + unsigned int flags = cfg.bus.parallel.flags; + + if (pxa_mbus_role != (flags & (V4L2_MBUS_MASTER | + V4L2_MBUS_SLAVE))) { + dev_err(pcdev_to_dev(pcdev), + "Unsupported mbus configuration: bus mastering\n"); + return -EINVAL; + } + + if (flags & V4L2_MBUS_DATA_ACTIVE_LOW) { + dev_err(pcdev_to_dev(pcdev), + "Unsupported mbus configuration: DATA_ACTIVE_LOW\n"); + return -EINVAL; + } + } + + pxa_camera_setup_cicr(pcdev, cfg.bus.parallel.flags, pixfmt); + + return 0; +} + +static const struct pxa_mbus_pixelfmt pxa_camera_formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUV422P, + .name = "Planar YUV422 16 bit", + .bits_per_sample = 8, + .packing = PXA_MBUS_PACKING_2X8_PADHI, + .order = PXA_MBUS_ORDER_LE, + .layout = PXA_MBUS_LAYOUT_PLANAR_2Y_U_V, + }, +}; + +/* This will be corrected as we get more formats */ +static bool pxa_camera_packing_supported(const struct pxa_mbus_pixelfmt *fmt) +{ + return fmt->packing == PXA_MBUS_PACKING_NONE || + (fmt->bits_per_sample == 8 && + fmt->packing == PXA_MBUS_PACKING_2X8_PADHI) || + (fmt->bits_per_sample > 8 && + fmt->packing == PXA_MBUS_PACKING_EXTEND16); +} + +static int pxa_camera_get_formats(struct v4l2_device *v4l2_dev, + unsigned int idx, + struct pxa_camera_format_xlate *xlate) +{ + struct pxa_camera_dev *pcdev = v4l2_dev_to_pcdev(v4l2_dev); + int formats = 0, ret; + struct v4l2_subdev_mbus_code_enum code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .index = idx, + }; + const struct pxa_mbus_pixelfmt *fmt; + + ret = sensor_call(pcdev, pad, enum_mbus_code, NULL, &code); + if (ret < 0) + /* No more formats */ + return 0; + + fmt = pxa_mbus_get_fmtdesc(code.code); + if (!fmt) { + dev_err(pcdev_to_dev(pcdev), + "Invalid format code #%u: %d\n", idx, code.code); + return 0; + } + + switch (code.code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + formats++; + if (xlate) { + xlate->host_fmt = &pxa_camera_formats[0]; + xlate->code = code.code; + xlate++; + dev_dbg(pcdev_to_dev(pcdev), + "Providing format %s using code %d\n", + pxa_camera_formats[0].name, code.code); + } + fallthrough; + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_RGB565_2X8_LE: + case MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE: + if (xlate) + dev_dbg(pcdev_to_dev(pcdev), + "Providing format %s packed\n", + fmt->name); + break; + default: + if (!pxa_camera_packing_supported(fmt)) + return 0; + if (xlate) + dev_dbg(pcdev_to_dev(pcdev), + "Providing format %s in pass-through mode\n", + fmt->name); + break; + } + + /* Generic pass-through */ + formats++; + if (xlate) { + xlate->host_fmt = fmt; + xlate->code = code.code; + xlate++; + } + + return formats; +} + +static int pxa_camera_build_formats(struct pxa_camera_dev *pcdev) +{ + struct pxa_camera_format_xlate *xlate; + + xlate = pxa_mbus_build_fmts_xlate(&pcdev->v4l2_dev, pcdev->sensor, + pxa_camera_get_formats); + if (IS_ERR(xlate)) + return PTR_ERR(xlate); + + pcdev->user_formats = xlate; + return 0; +} + +static void pxa_camera_destroy_formats(struct pxa_camera_dev *pcdev) +{ + kfree(pcdev->user_formats); +} + +static int pxa_camera_check_frame(u32 width, u32 height) +{ + /* limit to pxa hardware capabilities */ + return height < 32 || height > 2048 || width < 48 || width > 2048 || + (width & 0x01); +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int pxac_vidioc_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct pxa_camera_dev *pcdev = video_drvdata(file); + + if (reg->reg > CIBR2) + return -ERANGE; + + reg->val = __raw_readl(pcdev->base + reg->reg); + reg->size = sizeof(__u32); + return 0; +} + +static int pxac_vidioc_s_register(struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct pxa_camera_dev *pcdev = video_drvdata(file); + + if (reg->reg > CIBR2) + return -ERANGE; + if (reg->size != sizeof(__u32)) + return -EINVAL; + __raw_writel(reg->val, pcdev->base + reg->reg); + return 0; +} +#endif + +static int pxac_vidioc_enum_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_fmtdesc *f) +{ + struct pxa_camera_dev *pcdev = video_drvdata(filp); + const struct pxa_mbus_pixelfmt *format; + unsigned int idx; + + for (idx = 0; pcdev->user_formats[idx].code; idx++); + if (f->index >= idx) + return -EINVAL; + + format = pcdev->user_formats[f->index].host_fmt; + f->pixelformat = format->fourcc; + return 0; +} + +static int pxac_vidioc_g_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *f) +{ + struct pxa_camera_dev *pcdev = video_drvdata(filp); + struct v4l2_pix_format *pix = &f->fmt.pix; + + pix->width = pcdev->current_pix.width; + pix->height = pcdev->current_pix.height; + pix->bytesperline = pcdev->current_pix.bytesperline; + pix->sizeimage = pcdev->current_pix.sizeimage; + pix->field = pcdev->current_pix.field; + pix->pixelformat = pcdev->current_fmt->host_fmt->fourcc; + pix->colorspace = pcdev->current_pix.colorspace; + dev_dbg(pcdev_to_dev(pcdev), "current_fmt->fourcc: 0x%08x\n", + pcdev->current_fmt->host_fmt->fourcc); + return 0; +} + +static int pxac_vidioc_try_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *f) +{ + struct pxa_camera_dev *pcdev = video_drvdata(filp); + const struct pxa_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_state pad_state = { + .pads = &pad_cfg + }; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + struct v4l2_mbus_framefmt *mf = &format.format; + __u32 pixfmt = pix->pixelformat; + int ret; + + xlate = pxa_mbus_xlate_by_fourcc(pcdev->user_formats, pixfmt); + if (!xlate) { + dev_warn(pcdev_to_dev(pcdev), "Format %x not found\n", pixfmt); + return -EINVAL; + } + + /* + * Limit to pxa hardware capabilities. YUV422P planar format requires + * images size to be a multiple of 16 bytes. If not, zeros will be + * inserted between Y and U planes, and U and V planes, which violates + * the YUV422P standard. + */ + v4l_bound_align_image(&pix->width, 48, 2048, 1, + &pix->height, 32, 2048, 0, + pixfmt == V4L2_PIX_FMT_YUV422P ? 4 : 0); + + v4l2_fill_mbus_format(mf, pix, xlate->code); + ret = sensor_call(pcdev, pad, set_fmt, &pad_state, &format); + if (ret < 0) + return ret; + + v4l2_fill_pix_format(pix, mf); + + /* Only progressive video supported so far */ + switch (mf->field) { + case V4L2_FIELD_ANY: + case V4L2_FIELD_NONE: + pix->field = V4L2_FIELD_NONE; + break; + default: + /* TODO: support interlaced at least in pass-through mode */ + dev_err(pcdev_to_dev(pcdev), "Field type %d unsupported.\n", + mf->field); + return -EINVAL; + } + + ret = pxa_mbus_bytes_per_line(pix->width, xlate->host_fmt); + if (ret < 0) + return ret; + + pix->bytesperline = ret; + ret = pxa_mbus_image_size(xlate->host_fmt, pix->bytesperline, + pix->height); + if (ret < 0) + return ret; + + pix->sizeimage = ret; + return 0; +} + +static int pxac_vidioc_s_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *f) +{ + struct pxa_camera_dev *pcdev = video_drvdata(filp); + const struct pxa_camera_format_xlate *xlate; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + unsigned long flags; + int ret, is_busy; + + dev_dbg(pcdev_to_dev(pcdev), + "s_fmt_vid_cap(pix=%dx%d:%x)\n", + pix->width, pix->height, pix->pixelformat); + + spin_lock_irqsave(&pcdev->lock, flags); + is_busy = pcdev->active || vb2_is_busy(&pcdev->vb2_vq); + spin_unlock_irqrestore(&pcdev->lock, flags); + + if (is_busy) + return -EBUSY; + + ret = pxac_vidioc_try_fmt_vid_cap(filp, priv, f); + if (ret) + return ret; + + xlate = pxa_mbus_xlate_by_fourcc(pcdev->user_formats, + pix->pixelformat); + v4l2_fill_mbus_format(&format.format, pix, xlate->code); + ret = sensor_call(pcdev, pad, set_fmt, NULL, &format); + if (ret < 0) { + dev_warn(pcdev_to_dev(pcdev), + "Failed to configure for format %x\n", + pix->pixelformat); + } else if (pxa_camera_check_frame(pix->width, pix->height)) { + dev_warn(pcdev_to_dev(pcdev), + "Camera driver produced an unsupported frame %dx%d\n", + pix->width, pix->height); + return -EINVAL; + } + + pcdev->current_fmt = xlate; + pcdev->current_pix = *pix; + + ret = pxa_camera_set_bus_param(pcdev); + return ret; +} + +static int pxac_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->bus_info, "platform:pxa-camera", sizeof(cap->bus_info)); + strscpy(cap->driver, PXA_CAM_DRV_NAME, sizeof(cap->driver)); + strscpy(cap->card, pxa_cam_driver_description, sizeof(cap->card)); + return 0; +} + +static int pxac_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index > 0) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(i->name, "Camera", sizeof(i->name)); + + return 0; +} + +static int pxac_vidioc_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int pxac_vidioc_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + + return 0; +} + +static int pxac_sensor_set_power(struct pxa_camera_dev *pcdev, int on) +{ + int ret; + + ret = sensor_call(pcdev, core, s_power, on); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) { + dev_warn(pcdev_to_dev(pcdev), + "Failed to put subdevice in %s mode: %d\n", + on ? "normal operation" : "power saving", ret); + } + + return ret; +} + +static int pxac_fops_camera_open(struct file *filp) +{ + struct pxa_camera_dev *pcdev = video_drvdata(filp); + int ret; + + mutex_lock(&pcdev->mlock); + ret = v4l2_fh_open(filp); + if (ret < 0) + goto out; + + if (!v4l2_fh_is_singular_file(filp)) + goto out; + + ret = pxac_sensor_set_power(pcdev, 1); + if (ret) + v4l2_fh_release(filp); +out: + mutex_unlock(&pcdev->mlock); + return ret; +} + +static int pxac_fops_camera_release(struct file *filp) +{ + struct pxa_camera_dev *pcdev = video_drvdata(filp); + int ret; + bool fh_singular; + + mutex_lock(&pcdev->mlock); + + fh_singular = v4l2_fh_is_singular_file(filp); + + ret = _vb2_fop_release(filp, NULL); + + if (fh_singular) + ret = pxac_sensor_set_power(pcdev, 0); + + mutex_unlock(&pcdev->mlock); + + return ret; +} + +static const struct v4l2_file_operations pxa_camera_fops = { + .owner = THIS_MODULE, + .open = pxac_fops_camera_open, + .release = pxac_fops_camera_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static const struct v4l2_ioctl_ops pxa_camera_ioctl_ops = { + .vidioc_querycap = pxac_vidioc_querycap, + + .vidioc_enum_input = pxac_vidioc_enum_input, + .vidioc_g_input = pxac_vidioc_g_input, + .vidioc_s_input = pxac_vidioc_s_input, + + .vidioc_enum_fmt_vid_cap = pxac_vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = pxac_vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = pxac_vidioc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = pxac_vidioc_try_fmt_vid_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = pxac_vidioc_g_register, + .vidioc_s_register = pxac_vidioc_s_register, +#endif + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device pxa_camera_videodev_template = { + .name = "pxa-camera", + .minor = -1, + .fops = &pxa_camera_fops, + .ioctl_ops = &pxa_camera_ioctl_ops, + .release = video_device_release_empty, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING, +}; + +static int pxa_camera_sensor_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + int err; + struct v4l2_device *v4l2_dev = notifier->v4l2_dev; + struct pxa_camera_dev *pcdev = v4l2_dev_to_pcdev(v4l2_dev); + struct video_device *vdev = &pcdev->vdev; + struct v4l2_pix_format *pix = &pcdev->current_pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mf = &format.format; + + dev_info(pcdev_to_dev(pcdev), "%s(): trying to bind a device\n", + __func__); + mutex_lock(&pcdev->mlock); + *vdev = pxa_camera_videodev_template; + vdev->v4l2_dev = v4l2_dev; + vdev->lock = &pcdev->mlock; + pcdev->sensor = subdev; + pcdev->vdev.queue = &pcdev->vb2_vq; + pcdev->vdev.v4l2_dev = &pcdev->v4l2_dev; + pcdev->vdev.ctrl_handler = subdev->ctrl_handler; + video_set_drvdata(&pcdev->vdev, pcdev); + + err = pxa_camera_build_formats(pcdev); + if (err) { + dev_err(pcdev_to_dev(pcdev), "building formats failed: %d\n", + err); + goto out; + } + + pcdev->current_fmt = pcdev->user_formats; + pix->field = V4L2_FIELD_NONE; + pix->width = DEFAULT_WIDTH; + pix->height = DEFAULT_HEIGHT; + pix->bytesperline = + pxa_mbus_bytes_per_line(pix->width, + pcdev->current_fmt->host_fmt); + pix->sizeimage = + pxa_mbus_image_size(pcdev->current_fmt->host_fmt, + pix->bytesperline, pix->height); + pix->pixelformat = pcdev->current_fmt->host_fmt->fourcc; + v4l2_fill_mbus_format(mf, pix, pcdev->current_fmt->code); + + err = pxac_sensor_set_power(pcdev, 1); + if (err) + goto out; + + err = sensor_call(pcdev, pad, set_fmt, NULL, &format); + if (err) + goto out_sensor_poweroff; + + v4l2_fill_pix_format(pix, mf); + pr_info("%s(): colorspace=0x%x pixfmt=0x%x\n", + __func__, pix->colorspace, pix->pixelformat); + + err = pxa_camera_init_videobuf2(pcdev); + if (err) + goto out_sensor_poweroff; + + err = video_register_device(&pcdev->vdev, VFL_TYPE_VIDEO, -1); + if (err) { + v4l2_err(v4l2_dev, "register video device failed: %d\n", err); + pcdev->sensor = NULL; + } else { + dev_info(pcdev_to_dev(pcdev), + "PXA Camera driver attached to camera %s\n", + subdev->name); + } + +out_sensor_poweroff: + err = pxac_sensor_set_power(pcdev, 0); +out: + mutex_unlock(&pcdev->mlock); + return err; +} + +static void pxa_camera_sensor_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct pxa_camera_dev *pcdev = v4l2_dev_to_pcdev(notifier->v4l2_dev); + + mutex_lock(&pcdev->mlock); + dev_info(pcdev_to_dev(pcdev), + "PXA Camera driver detached from camera %s\n", + subdev->name); + + /* disable capture, disable interrupts */ + __raw_writel(0x3ff, pcdev->base + CICR0); + + /* Stop DMA engine */ + pxa_dma_stop_channels(pcdev); + + pxa_camera_destroy_formats(pcdev); + + video_unregister_device(&pcdev->vdev); + pcdev->sensor = NULL; + + mutex_unlock(&pcdev->mlock); +} + +static const struct v4l2_async_notifier_operations pxa_camera_sensor_ops = { + .bound = pxa_camera_sensor_bound, + .unbind = pxa_camera_sensor_unbind, +}; + +/* + * Driver probe, remove, suspend and resume operations + */ +static int pxa_camera_suspend(struct device *dev) +{ + struct pxa_camera_dev *pcdev = dev_get_drvdata(dev); + int i = 0, ret = 0; + + pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR0); + pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR1); + pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR2); + pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR3); + pcdev->save_cicr[i++] = __raw_readl(pcdev->base + CICR4); + + if (pcdev->sensor) + ret = pxac_sensor_set_power(pcdev, 0); + + return ret; +} + +static int pxa_camera_resume(struct device *dev) +{ + struct pxa_camera_dev *pcdev = dev_get_drvdata(dev); + int i = 0, ret = 0; + + __raw_writel(pcdev->save_cicr[i++] & ~CICR0_ENB, pcdev->base + CICR0); + __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR1); + __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR2); + __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR3); + __raw_writel(pcdev->save_cicr[i++], pcdev->base + CICR4); + + if (pcdev->sensor) { + ret = pxac_sensor_set_power(pcdev, 1); + } + + /* Restart frame capture if active buffer exists */ + if (!ret && pcdev->active) + pxa_camera_start_capture(pcdev); + + return ret; +} + +static int pxa_camera_pdata_from_dt(struct device *dev, + struct pxa_camera_dev *pcdev) +{ + u32 mclk_rate; + struct v4l2_async_subdev *asd; + struct device_node *np = dev->of_node; + struct v4l2_fwnode_endpoint ep = { .bus_type = 0 }; + int err = of_property_read_u32(np, "clock-frequency", + &mclk_rate); + if (!err) { + pcdev->platform_flags |= PXA_CAMERA_MCLK_EN; + pcdev->mclk = mclk_rate; + } + + np = of_graph_get_next_endpoint(np, NULL); + if (!np) { + dev_err(dev, "could not find endpoint\n"); + return -EINVAL; + } + + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); + if (err) { + dev_err(dev, "could not parse endpoint\n"); + goto out; + } + + switch (ep.bus.parallel.bus_width) { + case 4: + pcdev->platform_flags |= PXA_CAMERA_DATAWIDTH_4; + break; + case 5: + pcdev->platform_flags |= PXA_CAMERA_DATAWIDTH_5; + break; + case 8: + pcdev->platform_flags |= PXA_CAMERA_DATAWIDTH_8; + break; + case 9: + pcdev->platform_flags |= PXA_CAMERA_DATAWIDTH_9; + break; + case 10: + pcdev->platform_flags |= PXA_CAMERA_DATAWIDTH_10; + break; + default: + break; + } + + if (ep.bus.parallel.flags & V4L2_MBUS_MASTER) + pcdev->platform_flags |= PXA_CAMERA_MASTER; + if (ep.bus.parallel.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + pcdev->platform_flags |= PXA_CAMERA_HSP; + if (ep.bus.parallel.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + pcdev->platform_flags |= PXA_CAMERA_VSP; + if (ep.bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + pcdev->platform_flags |= PXA_CAMERA_PCLK_EN | PXA_CAMERA_PCP; + if (ep.bus.parallel.flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + pcdev->platform_flags |= PXA_CAMERA_PCLK_EN; + + asd = v4l2_async_nf_add_fwnode_remote(&pcdev->notifier, + of_fwnode_handle(np), + struct v4l2_async_subdev); + if (IS_ERR(asd)) + err = PTR_ERR(asd); +out: + of_node_put(np); + + return err; +} + +static int pxa_camera_probe(struct platform_device *pdev) +{ + struct pxa_camera_dev *pcdev; + struct resource *res; + void __iomem *base; + struct dma_slave_config config = { + .src_addr_width = 0, + .src_maxburst = 8, + .direction = DMA_DEV_TO_MEM, + }; + int irq; + int err = 0, i; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + if (!res || irq < 0) + return -ENODEV; + + pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) { + dev_err(&pdev->dev, "Could not allocate pcdev\n"); + return -ENOMEM; + } + + pcdev->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(pcdev->clk)) + return PTR_ERR(pcdev->clk); + + v4l2_async_nf_init(&pcdev->notifier); + pcdev->res = res; + pcdev->pdata = pdev->dev.platform_data; + if (pcdev->pdata) { + struct v4l2_async_subdev *asd; + + pcdev->platform_flags = pcdev->pdata->flags; + pcdev->mclk = pcdev->pdata->mclk_10khz * 10000; + asd = v4l2_async_nf_add_i2c(&pcdev->notifier, + pcdev->pdata->sensor_i2c_adapter_id, + pcdev->pdata->sensor_i2c_address, + struct v4l2_async_subdev); + if (IS_ERR(asd)) + err = PTR_ERR(asd); + } else if (pdev->dev.of_node) { + err = pxa_camera_pdata_from_dt(&pdev->dev, pcdev); + } else { + return -ENODEV; + } + if (err < 0) + return err; + + if (!(pcdev->platform_flags & (PXA_CAMERA_DATAWIDTH_8 | + PXA_CAMERA_DATAWIDTH_9 | PXA_CAMERA_DATAWIDTH_10))) { + /* + * Platform hasn't set available data widths. This is bad. + * Warn and use a default. + */ + dev_warn(&pdev->dev, "WARNING! Platform hasn't set available data widths, using default 10 bit\n"); + pcdev->platform_flags |= PXA_CAMERA_DATAWIDTH_10; + } + if (pcdev->platform_flags & PXA_CAMERA_DATAWIDTH_8) + pcdev->width_flags = 1 << 7; + if (pcdev->platform_flags & PXA_CAMERA_DATAWIDTH_9) + pcdev->width_flags |= 1 << 8; + if (pcdev->platform_flags & PXA_CAMERA_DATAWIDTH_10) + pcdev->width_flags |= 1 << 9; + if (!pcdev->mclk) { + dev_warn(&pdev->dev, + "mclk == 0! Please, fix your platform data. Using default 20MHz\n"); + pcdev->mclk = 20000000; + } + + pcdev->mclk_divisor = mclk_get_divisor(pdev, pcdev); + + INIT_LIST_HEAD(&pcdev->capture); + spin_lock_init(&pcdev->lock); + mutex_init(&pcdev->mlock); + + /* + * Request the regions. + */ + base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + pcdev->irq = irq; + pcdev->base = base; + + /* request dma */ + pcdev->dma_chans[0] = dma_request_chan(&pdev->dev, "CI_Y"); + if (IS_ERR(pcdev->dma_chans[0])) { + dev_err(&pdev->dev, "Can't request DMA for Y\n"); + return PTR_ERR(pcdev->dma_chans[0]); + } + + pcdev->dma_chans[1] = dma_request_chan(&pdev->dev, "CI_U"); + if (IS_ERR(pcdev->dma_chans[1])) { + dev_err(&pdev->dev, "Can't request DMA for U\n"); + err = PTR_ERR(pcdev->dma_chans[1]); + goto exit_free_dma_y; + } + + pcdev->dma_chans[2] = dma_request_chan(&pdev->dev, "CI_V"); + if (IS_ERR(pcdev->dma_chans[2])) { + dev_err(&pdev->dev, "Can't request DMA for V\n"); + err = PTR_ERR(pcdev->dma_chans[2]); + goto exit_free_dma_u; + } + + for (i = 0; i < 3; i++) { + config.src_addr = pcdev->res->start + CIBR0 + i * 8; + err = dmaengine_slave_config(pcdev->dma_chans[i], &config); + if (err < 0) { + dev_err(&pdev->dev, "dma slave config failed: %d\n", + err); + goto exit_free_dma; + } + } + + /* request irq */ + err = devm_request_irq(&pdev->dev, pcdev->irq, pxa_camera_irq, 0, + PXA_CAM_DRV_NAME, pcdev); + if (err) { + dev_err(&pdev->dev, "Camera interrupt register failed\n"); + goto exit_free_dma; + } + + tasklet_setup(&pcdev->task_eof, pxa_camera_eof); + + pxa_camera_activate(pcdev); + + platform_set_drvdata(pdev, pcdev); + err = v4l2_device_register(&pdev->dev, &pcdev->v4l2_dev); + if (err) + goto exit_deactivate; + + err = pxa_camera_init_videobuf2(pcdev); + if (err) + goto exit_notifier_cleanup; + + pcdev->notifier.ops = &pxa_camera_sensor_ops; + err = v4l2_async_nf_register(&pcdev->v4l2_dev, &pcdev->notifier); + if (err) + goto exit_notifier_cleanup; + + return 0; +exit_notifier_cleanup: + v4l2_async_nf_cleanup(&pcdev->notifier); + v4l2_device_unregister(&pcdev->v4l2_dev); +exit_deactivate: + pxa_camera_deactivate(pcdev); + tasklet_kill(&pcdev->task_eof); +exit_free_dma: + dma_release_channel(pcdev->dma_chans[2]); +exit_free_dma_u: + dma_release_channel(pcdev->dma_chans[1]); +exit_free_dma_y: + dma_release_channel(pcdev->dma_chans[0]); + return err; +} + +static int pxa_camera_remove(struct platform_device *pdev) +{ + struct pxa_camera_dev *pcdev = platform_get_drvdata(pdev); + + pxa_camera_deactivate(pcdev); + tasklet_kill(&pcdev->task_eof); + dma_release_channel(pcdev->dma_chans[0]); + dma_release_channel(pcdev->dma_chans[1]); + dma_release_channel(pcdev->dma_chans[2]); + + v4l2_async_nf_unregister(&pcdev->notifier); + v4l2_async_nf_cleanup(&pcdev->notifier); + + v4l2_device_unregister(&pcdev->v4l2_dev); + + dev_info(&pdev->dev, "PXA Camera driver unloaded\n"); + + return 0; +} + +static const struct dev_pm_ops pxa_camera_pm = { + .suspend = pxa_camera_suspend, + .resume = pxa_camera_resume, +}; + +static const struct of_device_id pxa_camera_of_match[] = { + { .compatible = "marvell,pxa270-qci", }, + {}, +}; +MODULE_DEVICE_TABLE(of, pxa_camera_of_match); + +static struct platform_driver pxa_camera_driver = { + .driver = { + .name = PXA_CAM_DRV_NAME, + .pm = &pxa_camera_pm, + .of_match_table = of_match_ptr(pxa_camera_of_match), + }, + .probe = pxa_camera_probe, + .remove = pxa_camera_remove, +}; + +module_platform_driver(pxa_camera_driver); + +MODULE_DESCRIPTION("PXA27x Camera Driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL"); +MODULE_VERSION(PXA_CAM_VERSION); +MODULE_ALIAS("platform:" PXA_CAM_DRV_NAME); diff --git a/drivers/media/platform/m2m-deinterlace.c b/drivers/media/platform/m2m-deinterlace.c new file mode 100644 index 000000000..1f89e71cd --- /dev/null +++ b/drivers/media/platform/m2m-deinterlace.c @@ -0,0 +1,1008 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * V4L2 deinterlacing support. + * + * Copyright (c) 2012 Vista Silicon S.L. + * Javier Martin + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define MEM2MEM_TEST_MODULE_NAME "mem2mem-deinterlace" + +MODULE_DESCRIPTION("mem2mem device which supports deinterlacing using dmaengine"); +MODULE_AUTHOR("Javier Martin v4l2_dev, "%s: " fmt, __func__, ## arg) + +struct deinterlace_fmt { + u32 fourcc; + /* Types the format can be used for */ + u32 types; +}; + +static struct deinterlace_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUV420, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +/* Per-queue, driver-specific private data */ +struct deinterlace_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct deinterlace_fmt *fmt; + enum v4l2_field field; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +enum { + YUV420_DMA_Y_ODD, + YUV420_DMA_Y_EVEN, + YUV420_DMA_U_ODD, + YUV420_DMA_U_EVEN, + YUV420_DMA_V_ODD, + YUV420_DMA_V_EVEN, + YUV420_DMA_Y_ODD_DOUBLING, + YUV420_DMA_U_ODD_DOUBLING, + YUV420_DMA_V_ODD_DOUBLING, + YUYV_DMA_ODD, + YUYV_DMA_EVEN, + YUYV_DMA_EVEN_DOUBLING, +}; + +/* Source and destination queue data */ +static struct deinterlace_q_data q_data[2]; + +static struct deinterlace_q_data *get_q_data(enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &q_data[V4L2_M2M_SRC]; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &q_data[V4L2_M2M_DST]; + default: + BUG(); + } + return NULL; +} + +static struct deinterlace_fmt *find_format(struct v4l2_format *f) +{ + struct deinterlace_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if ((fmt->types & f->type) && + (fmt->fourcc == f->fmt.pix.pixelformat)) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct deinterlace_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; + + atomic_t busy; + struct mutex dev_mutex; + spinlock_t irqlock; + + struct dma_chan *dma_chan; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct deinterlace_ctx { + struct v4l2_fh fh; + struct deinterlace_dev *dev; + + /* Abort requested by m2m */ + int aborting; + enum v4l2_colorspace colorspace; + dma_cookie_t cookie; + struct dma_interleaved_template *xt; +}; + +/* + * mem2mem callbacks + */ +static int deinterlace_job_ready(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_dev *pcdev = ctx->dev; + + if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) > 0 && + v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) > 0 && + !atomic_read(&ctx->dev->busy)) { + dprintk(pcdev, "Task ready\n"); + return 1; + } + + dprintk(pcdev, "Task not ready to run\n"); + + return 0; +} + +static void deinterlace_job_abort(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_dev *pcdev = ctx->dev; + + ctx->aborting = 1; + + dprintk(pcdev, "Aborting task\n"); + + v4l2_m2m_job_finish(pcdev->m2m_dev, ctx->fh.m2m_ctx); +} + +static void dma_callback(void *data) +{ + struct deinterlace_ctx *curr_ctx = data; + struct deinterlace_dev *pcdev = curr_ctx->dev; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + + atomic_set(&pcdev->busy, 0); + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + dst_vb->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->flags |= + src_vb->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->timecode = src_vb->timecode; + + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + + v4l2_m2m_job_finish(pcdev->m2m_dev, curr_ctx->fh.m2m_ctx); + + dprintk(pcdev, "dma transfers completed.\n"); +} + +static void deinterlace_issue_dma(struct deinterlace_ctx *ctx, int op, + int do_callback) +{ + struct deinterlace_q_data *s_q_data; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct deinterlace_dev *pcdev = ctx->dev; + struct dma_chan *chan = pcdev->dma_chan; + struct dma_device *dmadev = chan->device; + struct dma_async_tx_descriptor *tx; + unsigned int s_width, s_height; + unsigned int s_size; + dma_addr_t p_in, p_out; + enum dma_ctrl_flags flags; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + s_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_OUTPUT); + s_width = s_q_data->width; + s_height = s_q_data->height; + s_size = s_width * s_height; + + p_in = (dma_addr_t)vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + p_out = (dma_addr_t)vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, + 0); + if (!p_in || !p_out) { + v4l2_err(&pcdev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return; + } + + switch (op) { + case YUV420_DMA_Y_ODD: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width; + ctx->xt->sgl[0].icg = s_width; + ctx->xt->src_start = p_in; + ctx->xt->dst_start = p_out; + break; + case YUV420_DMA_Y_EVEN: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width; + ctx->xt->sgl[0].icg = s_width; + ctx->xt->src_start = p_in + s_size / 2; + ctx->xt->dst_start = p_out + s_width; + break; + case YUV420_DMA_U_ODD: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + s_size; + ctx->xt->dst_start = p_out + s_size; + break; + case YUV420_DMA_U_EVEN: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + (9 * s_size) / 8; + ctx->xt->dst_start = p_out + s_size + s_width / 2; + break; + case YUV420_DMA_V_ODD: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + (5 * s_size) / 4; + ctx->xt->dst_start = p_out + (5 * s_size) / 4; + break; + case YUV420_DMA_V_EVEN: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + (11 * s_size) / 8; + ctx->xt->dst_start = p_out + (5 * s_size) / 4 + s_width / 2; + break; + case YUV420_DMA_Y_ODD_DOUBLING: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width; + ctx->xt->sgl[0].icg = s_width; + ctx->xt->src_start = p_in; + ctx->xt->dst_start = p_out + s_width; + break; + case YUV420_DMA_U_ODD_DOUBLING: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + s_size; + ctx->xt->dst_start = p_out + s_size + s_width / 2; + break; + case YUV420_DMA_V_ODD_DOUBLING: + ctx->xt->numf = s_height / 4; + ctx->xt->sgl[0].size = s_width / 2; + ctx->xt->sgl[0].icg = s_width / 2; + ctx->xt->src_start = p_in + (5 * s_size) / 4; + ctx->xt->dst_start = p_out + (5 * s_size) / 4 + s_width / 2; + break; + case YUYV_DMA_ODD: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width * 2; + ctx->xt->sgl[0].icg = s_width * 2; + ctx->xt->src_start = p_in; + ctx->xt->dst_start = p_out; + break; + case YUYV_DMA_EVEN: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width * 2; + ctx->xt->sgl[0].icg = s_width * 2; + ctx->xt->src_start = p_in + s_size; + ctx->xt->dst_start = p_out + s_width * 2; + break; + case YUYV_DMA_EVEN_DOUBLING: + default: + ctx->xt->numf = s_height / 2; + ctx->xt->sgl[0].size = s_width * 2; + ctx->xt->sgl[0].icg = s_width * 2; + ctx->xt->src_start = p_in; + ctx->xt->dst_start = p_out + s_width * 2; + break; + } + + /* Common parameters for al transfers */ + ctx->xt->frame_size = 1; + ctx->xt->dir = DMA_MEM_TO_MEM; + ctx->xt->src_sgl = false; + ctx->xt->dst_sgl = true; + flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT; + + tx = dmadev->device_prep_interleaved_dma(chan, ctx->xt, flags); + if (tx == NULL) { + v4l2_warn(&pcdev->v4l2_dev, "DMA interleaved prep error\n"); + return; + } + + if (do_callback) { + tx->callback = dma_callback; + tx->callback_param = ctx; + } + + ctx->cookie = dmaengine_submit(tx); + if (dma_submit_error(ctx->cookie)) { + v4l2_warn(&pcdev->v4l2_dev, + "DMA submit error %d with src=0x%x dst=0x%x len=0x%x\n", + ctx->cookie, (unsigned)p_in, (unsigned)p_out, + s_size * 3/2); + return; + } + + dma_async_issue_pending(chan); +} + +static void deinterlace_device_run(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_q_data *dst_q_data; + + atomic_set(&ctx->dev->busy, 1); + + dprintk(ctx->dev, "%s: DMA try issue.\n", __func__); + + dst_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_CAPTURE); + + /* + * 4 possible field conversions are possible at the moment: + * V4L2_FIELD_SEQ_TB --> V4L2_FIELD_INTERLACED_TB: + * two separate fields in the same input buffer are interlaced + * in the output buffer using weaving. Top field comes first. + * V4L2_FIELD_SEQ_TB --> V4L2_FIELD_NONE: + * top field from the input buffer is copied to the output buffer + * using line doubling. Bottom field from the input buffer is discarded. + * V4L2_FIELD_SEQ_BT --> V4L2_FIELD_INTERLACED_BT: + * two separate fields in the same input buffer are interlaced + * in the output buffer using weaving. Bottom field comes first. + * V4L2_FIELD_SEQ_BT --> V4L2_FIELD_NONE: + * bottom field from the input buffer is copied to the output buffer + * using line doubling. Top field from the input buffer is discarded. + */ + switch (dst_q_data->fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + switch (dst_q_data->field) { + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + dprintk(ctx->dev, "%s: yuv420 interlaced tb.\n", + __func__); + deinterlace_issue_dma(ctx, YUV420_DMA_Y_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_Y_EVEN, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_U_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_U_EVEN, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_V_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_V_EVEN, 1); + break; + case V4L2_FIELD_NONE: + default: + dprintk(ctx->dev, "%s: yuv420 interlaced line doubling.\n", + __func__); + deinterlace_issue_dma(ctx, YUV420_DMA_Y_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_Y_ODD_DOUBLING, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_U_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_U_ODD_DOUBLING, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_V_ODD, 0); + deinterlace_issue_dma(ctx, YUV420_DMA_V_ODD_DOUBLING, 1); + break; + } + break; + case V4L2_PIX_FMT_YUYV: + default: + switch (dst_q_data->field) { + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + dprintk(ctx->dev, "%s: yuyv interlaced_tb.\n", + __func__); + deinterlace_issue_dma(ctx, YUYV_DMA_ODD, 0); + deinterlace_issue_dma(ctx, YUYV_DMA_EVEN, 1); + break; + case V4L2_FIELD_NONE: + default: + dprintk(ctx->dev, "%s: yuyv interlaced line doubling.\n", + __func__); + deinterlace_issue_dma(ctx, YUYV_DMA_ODD, 0); + deinterlace_issue_dma(ctx, YUYV_DMA_EVEN_DOUBLING, 1); + break; + } + break; + } + + dprintk(ctx->dev, "%s: DMA issue done.\n", __func__); +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strscpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); + strscpy(cap->bus_info, MEM2MEM_NAME, sizeof(cap->bus_info)); + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct deinterlace_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int vidioc_g_fmt(struct deinterlace_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct deinterlace_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = q_data->field; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + + switch (q_data->fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.bytesperline = q_data->width * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + default: + f->fmt.pix.bytesperline = q_data->width * 2; + } + + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = ctx->colorspace; + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_try_fmt(struct v4l2_format *f, struct deinterlace_fmt *fmt) +{ + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + default: + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + } + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_fmt *fmt; + struct deinterlace_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + + f->fmt.pix.colorspace = ctx->colorspace; + + if (f->fmt.pix.field != V4L2_FIELD_INTERLACED_TB && + f->fmt.pix.field != V4L2_FIELD_INTERLACED_BT && + f->fmt.pix.field != V4L2_FIELD_NONE) + f->fmt.pix.field = V4L2_FIELD_INTERLACED_TB; + + return vidioc_try_fmt(f, fmt); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_fmt *fmt; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; + + if (!f->fmt.pix.colorspace) + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + if (f->fmt.pix.field != V4L2_FIELD_SEQ_TB && + f->fmt.pix.field != V4L2_FIELD_SEQ_BT) + f->fmt.pix.field = V4L2_FIELD_SEQ_TB; + + return vidioc_try_fmt(f, fmt); +} + +static int vidioc_s_fmt(struct deinterlace_ctx *ctx, struct v4l2_format *f) +{ + struct deinterlace_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = find_format(f); + if (!q_data->fmt) { + v4l2_err(&ctx->dev->v4l2_dev, + "Couldn't set format type %d, wxh: %dx%d. fmt: %d, field: %d\n", + f->type, f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.pixelformat, f->fmt.pix.field); + return -EINVAL; + } + + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->field = f->fmt.pix.field; + + switch (f->fmt.pix.pixelformat) { + case V4L2_PIX_FMT_YUV420: + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + q_data->sizeimage = (q_data->width * q_data->height * 3) / 2; + break; + case V4L2_PIX_FMT_YUYV: + default: + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + q_data->sizeimage = q_data->width * q_data->height * 2; + } + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d, field: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc, + q_data->field); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + return vidioc_s_fmt(priv, f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_ctx *ctx = priv; + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = vidioc_s_fmt(priv, f); + if (!ret) + ctx->colorspace = f->fmt.pix.colorspace; + + return ret; +} + +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct deinterlace_q_data *s_q_data, *d_q_data; + struct deinterlace_ctx *ctx = priv; + + s_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_OUTPUT); + d_q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_CAPTURE); + + /* Check that src and dst queues have the same pix format */ + if (s_q_data->fmt->fourcc != d_q_data->fmt->fourcc) { + v4l2_err(&ctx->dev->v4l2_dev, + "src and dst formats don't match.\n"); + return -EINVAL; + } + + /* Check that input and output deinterlacing types are compatible */ + switch (s_q_data->field) { + case V4L2_FIELD_SEQ_BT: + if (d_q_data->field != V4L2_FIELD_NONE && + d_q_data->field != V4L2_FIELD_INTERLACED_BT) { + v4l2_err(&ctx->dev->v4l2_dev, + "src and dst field conversion [(%d)->(%d)] not supported.\n", + s_q_data->field, d_q_data->field); + return -EINVAL; + } + break; + case V4L2_FIELD_SEQ_TB: + if (d_q_data->field != V4L2_FIELD_NONE && + d_q_data->field != V4L2_FIELD_INTERLACED_TB) { + v4l2_err(&ctx->dev->v4l2_dev, + "src and dst field conversion [(%d)->(%d)] not supported.\n", + s_q_data->field, d_q_data->field); + return -EINVAL; + } + break; + default: + return -EINVAL; + } + + return v4l2_m2m_streamon(file, ctx->fh.m2m_ctx, type); +} + +static const struct v4l2_ioctl_ops deinterlace_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + + +/* + * Queue operations + */ +struct vb2_dc_conf { + struct device *dev; +}; + +static int deinterlace_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); + struct deinterlace_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(vq->type); + + switch (q_data->fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + size = q_data->width * q_data->height * 3 / 2; + break; + case V4L2_PIX_FMT_YUYV: + default: + size = q_data->width * q_data->height * 2; + } + + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int deinterlace_buf_prepare(struct vb2_buffer *vb) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct deinterlace_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void deinterlace_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static const struct vb2_ops deinterlace_qops = { + .queue_setup = deinterlace_queue_setup, + .buf_prepare = deinterlace_buf_prepare, + .buf_queue = deinterlace_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct deinterlace_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &deinterlace_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->dev = ctx->dev->v4l2_dev.dev; + src_vq->lock = &ctx->dev->dev_mutex; + q_data[V4L2_M2M_SRC].fmt = &formats[0]; + q_data[V4L2_M2M_SRC].width = 640; + q_data[V4L2_M2M_SRC].height = 480; + q_data[V4L2_M2M_SRC].sizeimage = (640 * 480 * 3) / 2; + q_data[V4L2_M2M_SRC].field = V4L2_FIELD_SEQ_TB; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &deinterlace_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + dst_vq->lock = &ctx->dev->dev_mutex; + q_data[V4L2_M2M_DST].fmt = &formats[0]; + q_data[V4L2_M2M_DST].width = 640; + q_data[V4L2_M2M_DST].height = 480; + q_data[V4L2_M2M_DST].sizeimage = (640 * 480 * 3) / 2; + q_data[V4L2_M2M_SRC].field = V4L2_FIELD_INTERLACED_TB; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int deinterlace_open(struct file *file) +{ + struct deinterlace_dev *pcdev = video_drvdata(file); + struct deinterlace_ctx *ctx = NULL; + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = pcdev; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(pcdev->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + int ret = PTR_ERR(ctx->fh.m2m_ctx); + + kfree(ctx); + return ret; + } + + ctx->xt = kzalloc(sizeof(struct dma_interleaved_template) + + sizeof(struct data_chunk), GFP_KERNEL); + if (!ctx->xt) { + kfree(ctx); + return -ENOMEM; + } + + ctx->colorspace = V4L2_COLORSPACE_REC709; + v4l2_fh_add(&ctx->fh); + + dprintk(pcdev, "Created instance %p, m2m_ctx: %p\n", + ctx, ctx->fh.m2m_ctx); + + return 0; +} + +static int deinterlace_release(struct file *file) +{ + struct deinterlace_dev *pcdev = video_drvdata(file); + struct deinterlace_ctx *ctx = file->private_data; + + dprintk(pcdev, "Releasing instance %p\n", ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + kfree(ctx->xt); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations deinterlace_fops = { + .owner = THIS_MODULE, + .open = deinterlace_open, + .release = deinterlace_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device deinterlace_videodev = { + .name = MEM2MEM_NAME, + .fops = &deinterlace_fops, + .ioctl_ops = &deinterlace_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, + .vfl_dir = VFL_DIR_M2M, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = deinterlace_device_run, + .job_ready = deinterlace_job_ready, + .job_abort = deinterlace_job_abort, +}; + +static int deinterlace_probe(struct platform_device *pdev) +{ + struct deinterlace_dev *pcdev; + struct video_device *vfd; + dma_cap_mask_t mask; + int ret = 0; + + pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) + return -ENOMEM; + + spin_lock_init(&pcdev->irqlock); + + dma_cap_zero(mask); + dma_cap_set(DMA_INTERLEAVE, mask); + pcdev->dma_chan = dma_request_channel(mask, NULL, pcdev); + if (!pcdev->dma_chan) + return -ENODEV; + + if (!dma_has_cap(DMA_INTERLEAVE, pcdev->dma_chan->device->cap_mask)) { + dev_err(&pdev->dev, "DMA does not support INTERLEAVE\n"); + ret = -ENODEV; + goto rel_dma; + } + + ret = v4l2_device_register(&pdev->dev, &pcdev->v4l2_dev); + if (ret) + goto rel_dma; + + atomic_set(&pcdev->busy, 0); + mutex_init(&pcdev->dev_mutex); + + vfd = &pcdev->vfd; + *vfd = deinterlace_videodev; + vfd->lock = &pcdev->dev_mutex; + vfd->v4l2_dev = &pcdev->v4l2_dev; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&pcdev->v4l2_dev, "Failed to register video device\n"); + goto unreg_dev; + } + + video_set_drvdata(vfd, pcdev); + v4l2_info(&pcdev->v4l2_dev, MEM2MEM_TEST_MODULE_NAME + " Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, pcdev); + + pcdev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(pcdev->m2m_dev)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(pcdev->m2m_dev); + goto err_m2m; + } + + return 0; + +err_m2m: + video_unregister_device(&pcdev->vfd); +unreg_dev: + v4l2_device_unregister(&pcdev->v4l2_dev); +rel_dma: + dma_release_channel(pcdev->dma_chan); + + return ret; +} + +static int deinterlace_remove(struct platform_device *pdev) +{ + struct deinterlace_dev *pcdev = platform_get_drvdata(pdev); + + v4l2_info(&pcdev->v4l2_dev, "Removing " MEM2MEM_TEST_MODULE_NAME); + v4l2_m2m_release(pcdev->m2m_dev); + video_unregister_device(&pcdev->vfd); + v4l2_device_unregister(&pcdev->v4l2_dev); + dma_release_channel(pcdev->dma_chan); + + return 0; +} + +static struct platform_driver deinterlace_pdrv = { + .probe = deinterlace_probe, + .remove = deinterlace_remove, + .driver = { + .name = MEM2MEM_NAME, + }, +}; +module_platform_driver(deinterlace_pdrv); + diff --git a/drivers/media/platform/marvell/Kconfig b/drivers/media/platform/marvell/Kconfig new file mode 100644 index 000000000..d6499ffe3 --- /dev/null +++ b/drivers/media/platform/marvell/Kconfig @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Marvell media platform drivers" + +config VIDEO_CAFE_CCIC + tristate "Marvell 88ALP01 (Cafe) CMOS Camera Controller support" + depends on V4L_PLATFORM_DRIVERS + depends on PCI && I2C && VIDEO_DEV + depends on COMMON_CLK + select VIDEO_OV7670 if MEDIA_SUBDRV_AUTOSELECT && VIDEO_CAMERA_SENSOR + select VIDEOBUF2_VMALLOC + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_DMA_SG + help + This is a video4linux2 driver for the Marvell 88ALP01 integrated + CMOS camera controller. This is the controller found on first- + generation OLPC systems. + +config VIDEO_MMP_CAMERA + tristate "Marvell Armada 610 integrated camera controller support" + depends on V4L_PLATFORM_DRIVERS + depends on I2C && VIDEO_DEV + depends on ARCH_MMP || COMPILE_TEST + depends on COMMON_CLK + select VIDEO_OV7670 if MEDIA_SUBDRV_AUTOSELECT && VIDEO_CAMERA_SENSOR + select I2C_GPIO + select VIDEOBUF2_VMALLOC + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_DMA_SG + help + This is a Video4Linux2 driver for the integrated camera + controller found on Marvell Armada 610 application + processors (and likely beyond). This is the controller found + in OLPC XO 1.75 systems. + diff --git a/drivers/media/platform/marvell/Makefile b/drivers/media/platform/marvell/Makefile new file mode 100644 index 000000000..90c3c2bc6 --- /dev/null +++ b/drivers/media/platform/marvell/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o mcam-core.o +cafe_ccic-y := cafe-driver.o + +obj-$(CONFIG_VIDEO_MMP_CAMERA) += mmp_camera.o mcam-core.o +mmp_camera-y := mmp-driver.o diff --git a/drivers/media/platform/marvell/cafe-driver.c b/drivers/media/platform/marvell/cafe-driver.c new file mode 100644 index 000000000..ae97ce4ea --- /dev/null +++ b/drivers/media/platform/marvell/cafe-driver.c @@ -0,0 +1,673 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * A driver for the CMOS camera controller in the Marvell 88ALP01 "cafe" + * multifunction chip. Currently works with the Omnivision OV7670 + * sensor. + * + * The data sheet for this device can be found at: + * http://wiki.laptop.org/images/5/5c/88ALP01_Datasheet_July_2007.pdf + * + * Copyright 2006-11 One Laptop Per Child Association, Inc. + * Copyright 2006-11 Jonathan Corbet + * Copyright 2018 Lubomir Rintel + * + * Written by Jonathan Corbet, corbet@lwn.net. + * + * v4l2_device/v4l2_subdev conversion by: + * Copyright (C) 2009 Hans Verkuil + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mcam-core.h" + +#define CAFE_VERSION 0x000002 + + +/* + * Parameters. + */ +MODULE_AUTHOR("Jonathan Corbet "); +MODULE_DESCRIPTION("Marvell 88ALP01 CMOS Camera Controller driver"); +MODULE_LICENSE("GPL"); + +struct cafe_camera { + int registered; /* Fully initialized? */ + struct mcam_camera mcam; + struct pci_dev *pdev; + struct i2c_adapter *i2c_adapter; + wait_queue_head_t smbus_wait; /* Waiting on i2c events */ +}; + +/* + * Most of the camera controller registers are defined in mcam-core.h, + * but the Cafe platform has some additional registers of its own; + * they are described here. + */ + +/* + * "General purpose register" has a couple of GPIOs used for sensor + * power and reset on OLPC XO 1.0 systems. + */ +#define REG_GPR 0xb4 +#define GPR_C1EN 0x00000020 /* Pad 1 (power down) enable */ +#define GPR_C0EN 0x00000010 /* Pad 0 (reset) enable */ +#define GPR_C1 0x00000002 /* Control 1 value */ +/* + * Control 0 is wired to reset on OLPC machines. For ov7x sensors, + * it is active low. + */ +#define GPR_C0 0x00000001 /* Control 0 value */ + +/* + * These registers control the SMBUS module for communicating + * with the sensor. + */ +#define REG_TWSIC0 0xb8 /* TWSI (smbus) control 0 */ +#define TWSIC0_EN 0x00000001 /* TWSI enable */ +#define TWSIC0_MODE 0x00000002 /* 1 = 16-bit, 0 = 8-bit */ +#define TWSIC0_SID 0x000003fc /* Slave ID */ +/* + * Subtle trickery: the slave ID field starts with bit 2. But the + * Linux i2c stack wants to treat the bottommost bit as a separate + * read/write bit, which is why slave ID's are usually presented + * >>1. For consistency with that behavior, we shift over three + * bits instead of two. + */ +#define TWSIC0_SID_SHIFT 3 +#define TWSIC0_CLKDIV 0x0007fc00 /* Clock divider */ +#define TWSIC0_MASKACK 0x00400000 /* Mask ack from sensor */ +#define TWSIC0_OVMAGIC 0x00800000 /* Make it work on OV sensors */ + +#define REG_TWSIC1 0xbc /* TWSI control 1 */ +#define TWSIC1_DATA 0x0000ffff /* Data to/from camchip */ +#define TWSIC1_ADDR 0x00ff0000 /* Address (register) */ +#define TWSIC1_ADDR_SHIFT 16 +#define TWSIC1_READ 0x01000000 /* Set for read op */ +#define TWSIC1_WSTAT 0x02000000 /* Write status */ +#define TWSIC1_RVALID 0x04000000 /* Read data valid */ +#define TWSIC1_ERROR 0x08000000 /* Something screwed up */ + +/* + * Here's the weird global control registers + */ +#define REG_GL_CSR 0x3004 /* Control/status register */ +#define GCSR_SRS 0x00000001 /* SW Reset set */ +#define GCSR_SRC 0x00000002 /* SW Reset clear */ +#define GCSR_MRS 0x00000004 /* Master reset set */ +#define GCSR_MRC 0x00000008 /* HW Reset clear */ +#define GCSR_CCIC_EN 0x00004000 /* CCIC Clock enable */ +#define REG_GL_IMASK 0x300c /* Interrupt mask register */ +#define GIMSK_CCIC_EN 0x00000004 /* CCIC Interrupt enable */ + +#define REG_GL_FCR 0x3038 /* GPIO functional control register */ +#define GFCR_GPIO_ON 0x08 /* Camera GPIO enabled */ +#define REG_GL_GPIOR 0x315c /* GPIO register */ +#define GGPIO_OUT 0x80000 /* GPIO output */ +#define GGPIO_VAL 0x00008 /* Output pin value */ + +#define REG_LEN (REG_GL_IMASK + 4) + + +/* + * Debugging and related. + */ +#define cam_err(cam, fmt, arg...) \ + dev_err(&(cam)->pdev->dev, fmt, ##arg); +#define cam_warn(cam, fmt, arg...) \ + dev_warn(&(cam)->pdev->dev, fmt, ##arg); + +/* -------------------------------------------------------------------- */ +/* + * The I2C/SMBUS interface to the camera itself starts here. The + * controller handles SMBUS itself, presenting a relatively simple register + * interface; all we have to do is to tell it where to route the data. + */ +#define CAFE_SMBUS_TIMEOUT (HZ) /* generous */ + +static int cafe_smbus_write_done(struct mcam_camera *mcam) +{ + unsigned long flags; + int c1; + + /* + * We must delay after the interrupt, or the controller gets confused + * and never does give us good status. Fortunately, we don't do this + * often. + */ + udelay(20); + spin_lock_irqsave(&mcam->dev_lock, flags); + c1 = mcam_reg_read(mcam, REG_TWSIC1); + spin_unlock_irqrestore(&mcam->dev_lock, flags); + return (c1 & (TWSIC1_WSTAT|TWSIC1_ERROR)) != TWSIC1_WSTAT; +} + +static int cafe_smbus_write_data(struct cafe_camera *cam, + u16 addr, u8 command, u8 value) +{ + unsigned int rval; + unsigned long flags; + struct mcam_camera *mcam = &cam->mcam; + + spin_lock_irqsave(&mcam->dev_lock, flags); + rval = TWSIC0_EN | ((addr << TWSIC0_SID_SHIFT) & TWSIC0_SID); + rval |= TWSIC0_OVMAGIC; /* Make OV sensors work */ + /* + * Marvell sez set clkdiv to all 1's for now. + */ + rval |= TWSIC0_CLKDIV; + mcam_reg_write(mcam, REG_TWSIC0, rval); + (void) mcam_reg_read(mcam, REG_TWSIC1); /* force write */ + rval = value | ((command << TWSIC1_ADDR_SHIFT) & TWSIC1_ADDR); + mcam_reg_write(mcam, REG_TWSIC1, rval); + spin_unlock_irqrestore(&mcam->dev_lock, flags); + + /* Unfortunately, reading TWSIC1 too soon after sending a command + * causes the device to die. + * Use a busy-wait because we often send a large quantity of small + * commands at-once; using msleep() would cause a lot of context + * switches which take longer than 2ms, resulting in a noticeable + * boot-time and capture-start delays. + */ + mdelay(2); + + /* + * Another sad fact is that sometimes, commands silently complete but + * cafe_smbus_write_done() never becomes aware of this. + * This happens at random and appears to possible occur with any + * command. + * We don't understand why this is. We work around this issue + * with the timeout in the wait below, assuming that all commands + * complete within the timeout. + */ + wait_event_timeout(cam->smbus_wait, cafe_smbus_write_done(mcam), + CAFE_SMBUS_TIMEOUT); + + spin_lock_irqsave(&mcam->dev_lock, flags); + rval = mcam_reg_read(mcam, REG_TWSIC1); + spin_unlock_irqrestore(&mcam->dev_lock, flags); + + if (rval & TWSIC1_WSTAT) { + cam_err(cam, "SMBUS write (%02x/%02x/%02x) timed out\n", addr, + command, value); + return -EIO; + } + if (rval & TWSIC1_ERROR) { + cam_err(cam, "SMBUS write (%02x/%02x/%02x) error\n", addr, + command, value); + return -EIO; + } + return 0; +} + + + +static int cafe_smbus_read_done(struct mcam_camera *mcam) +{ + unsigned long flags; + int c1; + + /* + * We must delay after the interrupt, or the controller gets confused + * and never does give us good status. Fortunately, we don't do this + * often. + */ + udelay(20); + spin_lock_irqsave(&mcam->dev_lock, flags); + c1 = mcam_reg_read(mcam, REG_TWSIC1); + spin_unlock_irqrestore(&mcam->dev_lock, flags); + return c1 & (TWSIC1_RVALID|TWSIC1_ERROR); +} + + + +static int cafe_smbus_read_data(struct cafe_camera *cam, + u16 addr, u8 command, u8 *value) +{ + unsigned int rval; + unsigned long flags; + struct mcam_camera *mcam = &cam->mcam; + + spin_lock_irqsave(&mcam->dev_lock, flags); + rval = TWSIC0_EN | ((addr << TWSIC0_SID_SHIFT) & TWSIC0_SID); + rval |= TWSIC0_OVMAGIC; /* Make OV sensors work */ + /* + * Marvel sez set clkdiv to all 1's for now. + */ + rval |= TWSIC0_CLKDIV; + mcam_reg_write(mcam, REG_TWSIC0, rval); + (void) mcam_reg_read(mcam, REG_TWSIC1); /* force write */ + rval = TWSIC1_READ | ((command << TWSIC1_ADDR_SHIFT) & TWSIC1_ADDR); + mcam_reg_write(mcam, REG_TWSIC1, rval); + spin_unlock_irqrestore(&mcam->dev_lock, flags); + + wait_event_timeout(cam->smbus_wait, + cafe_smbus_read_done(mcam), CAFE_SMBUS_TIMEOUT); + spin_lock_irqsave(&mcam->dev_lock, flags); + rval = mcam_reg_read(mcam, REG_TWSIC1); + spin_unlock_irqrestore(&mcam->dev_lock, flags); + + if (rval & TWSIC1_ERROR) { + cam_err(cam, "SMBUS read (%02x/%02x) error\n", addr, command); + return -EIO; + } + if (!(rval & TWSIC1_RVALID)) { + cam_err(cam, "SMBUS read (%02x/%02x) timed out\n", addr, + command); + return -EIO; + } + *value = rval & 0xff; + return 0; +} + +/* + * Perform a transfer over SMBUS. This thing is called under + * the i2c bus lock, so we shouldn't race with ourselves... + */ +static int cafe_smbus_xfer(struct i2c_adapter *adapter, u16 addr, + unsigned short flags, char rw, u8 command, + int size, union i2c_smbus_data *data) +{ + struct cafe_camera *cam = i2c_get_adapdata(adapter); + int ret = -EINVAL; + + /* + * This interface would appear to only do byte data ops. OK + * it can do word too, but the cam chip has no use for that. + */ + if (size != I2C_SMBUS_BYTE_DATA) { + cam_err(cam, "funky xfer size %d\n", size); + return -EINVAL; + } + + if (rw == I2C_SMBUS_WRITE) + ret = cafe_smbus_write_data(cam, addr, command, data->byte); + else if (rw == I2C_SMBUS_READ) + ret = cafe_smbus_read_data(cam, addr, command, &data->byte); + return ret; +} + + +static void cafe_smbus_enable_irq(struct cafe_camera *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->mcam.dev_lock, flags); + mcam_reg_set_bit(&cam->mcam, REG_IRQMASK, TWSIIRQS); + spin_unlock_irqrestore(&cam->mcam.dev_lock, flags); +} + +static u32 cafe_smbus_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_SMBUS_READ_BYTE_DATA | + I2C_FUNC_SMBUS_WRITE_BYTE_DATA; +} + +static const struct i2c_algorithm cafe_smbus_algo = { + .smbus_xfer = cafe_smbus_xfer, + .functionality = cafe_smbus_func +}; + +static int cafe_smbus_setup(struct cafe_camera *cam) +{ + struct i2c_adapter *adap; + int ret; + + adap = kzalloc(sizeof(*adap), GFP_KERNEL); + if (adap == NULL) + return -ENOMEM; + adap->owner = THIS_MODULE; + adap->algo = &cafe_smbus_algo; + strscpy(adap->name, "cafe_ccic", sizeof(adap->name)); + adap->dev.parent = &cam->pdev->dev; + i2c_set_adapdata(adap, cam); + ret = i2c_add_adapter(adap); + if (ret) { + printk(KERN_ERR "Unable to register cafe i2c adapter\n"); + kfree(adap); + return ret; + } + + cam->i2c_adapter = adap; + cafe_smbus_enable_irq(cam); + return 0; +} + +static void cafe_smbus_shutdown(struct cafe_camera *cam) +{ + i2c_del_adapter(cam->i2c_adapter); + kfree(cam->i2c_adapter); +} + + +/* + * Controller-level stuff + */ + +static void cafe_ctlr_init(struct mcam_camera *mcam) +{ + unsigned long flags; + + spin_lock_irqsave(&mcam->dev_lock, flags); + /* + * Added magic to bring up the hardware on the B-Test board + */ + mcam_reg_write(mcam, 0x3038, 0x8); + mcam_reg_write(mcam, 0x315c, 0x80008); + /* + * Go through the dance needed to wake the device up. + * Note that these registers are global and shared + * with the NAND and SD devices. Interaction between the + * three still needs to be examined. + */ + mcam_reg_write(mcam, REG_GL_CSR, GCSR_SRS|GCSR_MRS); /* Needed? */ + mcam_reg_write(mcam, REG_GL_CSR, GCSR_SRC|GCSR_MRC); + mcam_reg_write(mcam, REG_GL_CSR, GCSR_SRC|GCSR_MRS); + /* + * Here we must wait a bit for the controller to come around. + */ + spin_unlock_irqrestore(&mcam->dev_lock, flags); + msleep(5); + spin_lock_irqsave(&mcam->dev_lock, flags); + + mcam_reg_write(mcam, REG_GL_CSR, GCSR_CCIC_EN|GCSR_SRC|GCSR_MRC); + mcam_reg_set_bit(mcam, REG_GL_IMASK, GIMSK_CCIC_EN); + /* + * Mask all interrupts. + */ + mcam_reg_write(mcam, REG_IRQMASK, 0); + spin_unlock_irqrestore(&mcam->dev_lock, flags); +} + + +static int cafe_ctlr_power_up(struct mcam_camera *mcam) +{ + /* + * Part one of the sensor dance: turn the global + * GPIO signal on. + */ + mcam_reg_write(mcam, REG_GL_FCR, GFCR_GPIO_ON); + mcam_reg_write(mcam, REG_GL_GPIOR, GGPIO_OUT|GGPIO_VAL); + /* + * Put the sensor into operational mode (assumes OLPC-style + * wiring). Control 0 is reset - set to 1 to operate. + * Control 1 is power down, set to 0 to operate. + */ + mcam_reg_write(mcam, REG_GPR, GPR_C1EN|GPR_C0EN); /* pwr up, reset */ + mcam_reg_write(mcam, REG_GPR, GPR_C1EN|GPR_C0EN|GPR_C0); + + return 0; +} + +static void cafe_ctlr_power_down(struct mcam_camera *mcam) +{ + mcam_reg_write(mcam, REG_GPR, GPR_C1EN|GPR_C0EN|GPR_C1); + mcam_reg_write(mcam, REG_GL_FCR, GFCR_GPIO_ON); + mcam_reg_write(mcam, REG_GL_GPIOR, GGPIO_OUT); +} + + + +/* + * The platform interrupt handler. + */ +static irqreturn_t cafe_irq(int irq, void *data) +{ + struct cafe_camera *cam = data; + struct mcam_camera *mcam = &cam->mcam; + unsigned int irqs, handled; + + spin_lock(&mcam->dev_lock); + irqs = mcam_reg_read(mcam, REG_IRQSTAT); + handled = cam->registered && mccic_irq(mcam, irqs); + if (irqs & TWSIIRQS) { + mcam_reg_write(mcam, REG_IRQSTAT, TWSIIRQS); + wake_up(&cam->smbus_wait); + handled = 1; + } + spin_unlock(&mcam->dev_lock); + return IRQ_RETVAL(handled); +} + +/* -------------------------------------------------------------------------- */ + +static struct ov7670_config sensor_cfg = { + /* + * Exclude QCIF mode, because it only captures a tiny portion + * of the sensor FOV + */ + .min_width = 320, + .min_height = 240, + + /* + * Set the clock speed for the XO 1; I don't believe this + * driver has ever run anywhere else. + */ + .clock_speed = 45, + .use_smbus = 1, +}; + +static struct i2c_board_info ov7670_info = { + .type = "ov7670", + .addr = 0x42 >> 1, + .platform_data = &sensor_cfg, +}; + +/* -------------------------------------------------------------------------- */ +/* + * PCI interface stuff. + */ + +static int cafe_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + int ret; + struct cafe_camera *cam; + struct mcam_camera *mcam; + struct v4l2_async_subdev *asd; + struct i2c_client *i2c_dev; + + /* + * Start putting together one of our big camera structures. + */ + ret = -ENOMEM; + cam = kzalloc(sizeof(struct cafe_camera), GFP_KERNEL); + if (cam == NULL) + goto out; + pci_set_drvdata(pdev, cam); + cam->pdev = pdev; + mcam = &cam->mcam; + mcam->chip_id = MCAM_CAFE; + spin_lock_init(&mcam->dev_lock); + init_waitqueue_head(&cam->smbus_wait); + mcam->plat_power_up = cafe_ctlr_power_up; + mcam->plat_power_down = cafe_ctlr_power_down; + mcam->dev = &pdev->dev; + /* + * Vmalloc mode for buffers is traditional with this driver. + * We *might* be able to run DMA_contig, especially on a system + * with CMA in it. + */ + mcam->buffer_mode = B_vmalloc; + /* + * Get set up on the PCI bus. + */ + ret = pci_enable_device(pdev); + if (ret) + goto out_free; + pci_set_master(pdev); + + ret = -EIO; + mcam->regs = pci_iomap(pdev, 0, 0); + if (!mcam->regs) { + printk(KERN_ERR "Unable to ioremap cafe-ccic regs\n"); + goto out_disable; + } + mcam->regs_size = pci_resource_len(pdev, 0); + ret = request_irq(pdev->irq, cafe_irq, IRQF_SHARED, "cafe-ccic", cam); + if (ret) + goto out_iounmap; + + /* + * Initialize the controller. + */ + cafe_ctlr_init(mcam); + + /* + * Set up I2C/SMBUS communications. We have to drop the mutex here + * because the sensor could attach in this call chain, leading to + * unsightly deadlocks. + */ + ret = cafe_smbus_setup(cam); + if (ret) + goto out_pdown; + + v4l2_async_nf_init(&mcam->notifier); + + asd = v4l2_async_nf_add_i2c(&mcam->notifier, + i2c_adapter_id(cam->i2c_adapter), + ov7670_info.addr, struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out_smbus_shutdown; + } + + ret = mccic_register(mcam); + if (ret) + goto out_smbus_shutdown; + + clkdev_create(mcam->mclk, "xclk", "%d-%04x", + i2c_adapter_id(cam->i2c_adapter), ov7670_info.addr); + + i2c_dev = i2c_new_client_device(cam->i2c_adapter, &ov7670_info); + if (IS_ERR(i2c_dev)) { + ret = PTR_ERR(i2c_dev); + goto out_mccic_shutdown; + } + + cam->registered = 1; + return 0; + +out_mccic_shutdown: + mccic_shutdown(mcam); +out_smbus_shutdown: + cafe_smbus_shutdown(cam); +out_pdown: + cafe_ctlr_power_down(mcam); + free_irq(pdev->irq, cam); +out_iounmap: + pci_iounmap(pdev, mcam->regs); +out_disable: + pci_disable_device(pdev); +out_free: + kfree(cam); +out: + return ret; +} + + +/* + * Shut down an initialized device + */ +static void cafe_shutdown(struct cafe_camera *cam) +{ + mccic_shutdown(&cam->mcam); + cafe_smbus_shutdown(cam); + free_irq(cam->pdev->irq, cam); + pci_iounmap(cam->pdev, cam->mcam.regs); +} + + +static void cafe_pci_remove(struct pci_dev *pdev) +{ + struct cafe_camera *cam = pci_get_drvdata(pdev); + + if (cam == NULL) { + printk(KERN_WARNING "pci_remove on unknown pdev %p\n", pdev); + return; + } + cafe_shutdown(cam); + kfree(cam); +} + + +/* + * Basic power management. + */ +static int __maybe_unused cafe_pci_suspend(struct device *dev) +{ + struct cafe_camera *cam = dev_get_drvdata(dev); + + mccic_suspend(&cam->mcam); + return 0; +} + + +static int __maybe_unused cafe_pci_resume(struct device *dev) +{ + struct cafe_camera *cam = dev_get_drvdata(dev); + + cafe_ctlr_init(&cam->mcam); + return mccic_resume(&cam->mcam); +} + +static const struct pci_device_id cafe_ids[] = { + { PCI_DEVICE(PCI_VENDOR_ID_MARVELL, + PCI_DEVICE_ID_MARVELL_88ALP01_CCIC) }, + { 0, } +}; + +MODULE_DEVICE_TABLE(pci, cafe_ids); + +static SIMPLE_DEV_PM_OPS(cafe_pci_pm_ops, cafe_pci_suspend, cafe_pci_resume); + +static struct pci_driver cafe_pci_driver = { + .name = "cafe1000-ccic", + .id_table = cafe_ids, + .probe = cafe_pci_probe, + .remove = cafe_pci_remove, + .driver.pm = &cafe_pci_pm_ops, +}; + + + + +static int __init cafe_init(void) +{ + int ret; + + printk(KERN_NOTICE "Marvell M88ALP01 'CAFE' Camera Controller version %d\n", + CAFE_VERSION); + ret = pci_register_driver(&cafe_pci_driver); + if (ret) { + printk(KERN_ERR "Unable to register cafe_ccic driver\n"); + goto out; + } + ret = 0; + +out: + return ret; +} + + +static void __exit cafe_exit(void) +{ + pci_unregister_driver(&cafe_pci_driver); +} + +module_init(cafe_init); +module_exit(cafe_exit); diff --git a/drivers/media/platform/marvell/mcam-core.c b/drivers/media/platform/marvell/mcam-core.c new file mode 100644 index 000000000..ad4a7922d --- /dev/null +++ b/drivers/media/platform/marvell/mcam-core.c @@ -0,0 +1,1994 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * The Marvell camera core. This device appears in a number of settings, + * so it needs platform-specific support outside of the core. + * + * Copyright 2011 Jonathan Corbet corbet@lwn.net + * Copyright 2018 Lubomir Rintel + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mcam-core.h" + +#ifdef MCAM_MODE_VMALLOC +/* + * Internal DMA buffer management. Since the controller cannot do S/G I/O, + * we must have physically contiguous buffers to bring frames into. + * These parameters control how many buffers we use, whether we + * allocate them at load time (better chance of success, but nails down + * memory) or when somebody tries to use the camera (riskier), and, + * for load-time allocation, how big they should be. + * + * The controller can cycle through three buffers. We could use + * more by flipping pointers around, but it probably makes little + * sense. + */ + +static bool alloc_bufs_at_read; +module_param(alloc_bufs_at_read, bool, 0444); +MODULE_PARM_DESC(alloc_bufs_at_read, + "Non-zero value causes DMA buffers to be allocated when the video capture device is read, rather than at module load time. This saves memory, but decreases the chances of successfully getting those buffers. This parameter is only used in the vmalloc buffer mode"); + +static int n_dma_bufs = 3; +module_param(n_dma_bufs, uint, 0644); +MODULE_PARM_DESC(n_dma_bufs, + "The number of DMA buffers to allocate. Can be either two (saves memory, makes timing tighter) or three."); + +static int dma_buf_size = VGA_WIDTH * VGA_HEIGHT * 2; /* Worst case */ +module_param(dma_buf_size, uint, 0444); +MODULE_PARM_DESC(dma_buf_size, + "The size of the allocated DMA buffers. If actual operating parameters require larger buffers, an attempt to reallocate will be made."); +#else /* MCAM_MODE_VMALLOC */ +static const bool alloc_bufs_at_read; +static const int n_dma_bufs = 3; /* Used by S/G_PARM */ +#endif /* MCAM_MODE_VMALLOC */ + +static bool flip; +module_param(flip, bool, 0444); +MODULE_PARM_DESC(flip, + "If set, the sensor will be instructed to flip the image vertically."); + +static int buffer_mode = -1; +module_param(buffer_mode, int, 0444); +MODULE_PARM_DESC(buffer_mode, + "Set the buffer mode to be used; default is to go with what the platform driver asks for. Set to 0 for vmalloc, 1 for DMA contiguous."); + +/* + * Status flags. Always manipulated with bit operations. + */ +#define CF_BUF0_VALID 0 /* Buffers valid - first three */ +#define CF_BUF1_VALID 1 +#define CF_BUF2_VALID 2 +#define CF_DMA_ACTIVE 3 /* A frame is incoming */ +#define CF_CONFIG_NEEDED 4 /* Must configure hardware */ +#define CF_SINGLE_BUFFER 5 /* Running with a single buffer */ +#define CF_SG_RESTART 6 /* SG restart needed */ +#define CF_FRAME_SOF0 7 /* Frame 0 started */ +#define CF_FRAME_SOF1 8 +#define CF_FRAME_SOF2 9 + +#define sensor_call(cam, o, f, args...) \ + v4l2_subdev_call(cam->sensor, o, f, ##args) + +#define notifier_to_mcam(notifier) \ + container_of(notifier, struct mcam_camera, notifier) + +static struct mcam_format_struct { + __u32 pixelformat; + int bpp; /* Bytes per pixel */ + bool planar; + u32 mbus_code; +} mcam_formats[] = { + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 2, + .planar = false, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 2, + .planar = false, + }, + { + .pixelformat = V4L2_PIX_FMT_YUV420, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 1, + .planar = true, + }, + { + .pixelformat = V4L2_PIX_FMT_YVU420, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 1, + .planar = true, + }, + { + .pixelformat = V4L2_PIX_FMT_XRGB444, + .mbus_code = MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE, + .bpp = 2, + .planar = false, + }, + { + .pixelformat = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .bpp = 2, + .planar = false, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .bpp = 1, + .planar = false, + }, +}; +#define N_MCAM_FMTS ARRAY_SIZE(mcam_formats) + +static struct mcam_format_struct *mcam_find_format(u32 pixelformat) +{ + unsigned i; + + for (i = 0; i < N_MCAM_FMTS; i++) + if (mcam_formats[i].pixelformat == pixelformat) + return mcam_formats + i; + /* Not found? Then return the first format. */ + return mcam_formats; +} + +/* + * The default format we use until somebody says otherwise. + */ +static const struct v4l2_pix_format mcam_def_pix_format = { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .pixelformat = V4L2_PIX_FMT_YUYV, + .field = V4L2_FIELD_NONE, + .bytesperline = VGA_WIDTH*2, + .sizeimage = VGA_WIDTH*VGA_HEIGHT*2, + .colorspace = V4L2_COLORSPACE_SRGB, +}; + +static const u32 mcam_def_mbus_code = MEDIA_BUS_FMT_YUYV8_2X8; + + +/* + * The two-word DMA descriptor format used by the Armada 610 and like. There + * Is a three-word format as well (set C1_DESC_3WORD) where the third + * word is a pointer to the next descriptor, but we don't use it. Two-word + * descriptors have to be contiguous in memory. + */ +struct mcam_dma_desc { + u32 dma_addr; + u32 segment_len; +}; + +/* + * Our buffer type for working with videobuf2. Note that the vb2 + * developers have decreed that struct vb2_v4l2_buffer must be at the + * beginning of this structure. + */ +struct mcam_vb_buffer { + struct vb2_v4l2_buffer vb_buf; + struct list_head queue; + struct mcam_dma_desc *dma_desc; /* Descriptor virtual address */ + dma_addr_t dma_desc_pa; /* Descriptor physical address */ +}; + +static inline struct mcam_vb_buffer *vb_to_mvb(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct mcam_vb_buffer, vb_buf); +} + +/* + * Hand a completed buffer back to user space. + */ +static void mcam_buffer_done(struct mcam_camera *cam, int frame, + struct vb2_v4l2_buffer *vbuf) +{ + vbuf->vb2_buf.planes[0].bytesused = cam->pix_format.sizeimage; + vbuf->sequence = cam->buf_seq[frame]; + vbuf->field = V4L2_FIELD_NONE; + vbuf->vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&vbuf->vb2_buf, 0, cam->pix_format.sizeimage); + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); +} + + + +/* + * Debugging and related. + */ +#define cam_err(cam, fmt, arg...) \ + dev_err((cam)->dev, fmt, ##arg); +#define cam_warn(cam, fmt, arg...) \ + dev_warn((cam)->dev, fmt, ##arg); +#define cam_dbg(cam, fmt, arg...) \ + dev_dbg((cam)->dev, fmt, ##arg); + + +/* + * Flag manipulation helpers + */ +static void mcam_reset_buffers(struct mcam_camera *cam) +{ + int i; + + cam->next_buf = -1; + for (i = 0; i < cam->nbufs; i++) { + clear_bit(i, &cam->flags); + clear_bit(CF_FRAME_SOF0 + i, &cam->flags); + } +} + +static inline int mcam_needs_config(struct mcam_camera *cam) +{ + return test_bit(CF_CONFIG_NEEDED, &cam->flags); +} + +static void mcam_set_config_needed(struct mcam_camera *cam, int needed) +{ + if (needed) + set_bit(CF_CONFIG_NEEDED, &cam->flags); + else + clear_bit(CF_CONFIG_NEEDED, &cam->flags); +} + +/* ------------------------------------------------------------------- */ +/* + * Make the controller start grabbing images. Everything must + * be set up before doing this. + */ +static void mcam_ctlr_start(struct mcam_camera *cam) +{ + /* set_bit performs a read, so no other barrier should be + needed here */ + mcam_reg_set_bit(cam, REG_CTRL0, C0_ENABLE); +} + +static void mcam_ctlr_stop(struct mcam_camera *cam) +{ + mcam_reg_clear_bit(cam, REG_CTRL0, C0_ENABLE); +} + +static void mcam_enable_mipi(struct mcam_camera *mcam) +{ + /* Using MIPI mode and enable MIPI */ + if (mcam->calc_dphy) + mcam->calc_dphy(mcam); + cam_dbg(mcam, "camera: DPHY3=0x%x, DPHY5=0x%x, DPHY6=0x%x\n", + mcam->dphy[0], mcam->dphy[1], mcam->dphy[2]); + mcam_reg_write(mcam, REG_CSI2_DPHY3, mcam->dphy[0]); + mcam_reg_write(mcam, REG_CSI2_DPHY5, mcam->dphy[1]); + mcam_reg_write(mcam, REG_CSI2_DPHY6, mcam->dphy[2]); + + if (!mcam->mipi_enabled) { + if (mcam->lane > 4 || mcam->lane <= 0) { + cam_warn(mcam, "lane number error\n"); + mcam->lane = 1; /* set the default value */ + } + /* + * 0x41 actives 1 lane + * 0x43 actives 2 lanes + * 0x45 actives 3 lanes (never happen) + * 0x47 actives 4 lanes + */ + mcam_reg_write(mcam, REG_CSI2_CTRL0, + CSI2_C0_MIPI_EN | CSI2_C0_ACT_LANE(mcam->lane)); + mcam->mipi_enabled = true; + } +} + +static void mcam_disable_mipi(struct mcam_camera *mcam) +{ + /* Using Parallel mode or disable MIPI */ + mcam_reg_write(mcam, REG_CSI2_CTRL0, 0x0); + mcam_reg_write(mcam, REG_CSI2_DPHY3, 0x0); + mcam_reg_write(mcam, REG_CSI2_DPHY5, 0x0); + mcam_reg_write(mcam, REG_CSI2_DPHY6, 0x0); + mcam->mipi_enabled = false; +} + +static bool mcam_fmt_is_planar(__u32 pfmt) +{ + struct mcam_format_struct *f; + + f = mcam_find_format(pfmt); + return f->planar; +} + +static void mcam_write_yuv_bases(struct mcam_camera *cam, + unsigned frame, dma_addr_t base) +{ + struct v4l2_pix_format *fmt = &cam->pix_format; + u32 pixel_count = fmt->width * fmt->height; + dma_addr_t y, u = 0, v = 0; + + y = base; + + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_YUV420: + u = y + pixel_count; + v = u + pixel_count / 4; + break; + case V4L2_PIX_FMT_YVU420: + v = y + pixel_count; + u = v + pixel_count / 4; + break; + default: + break; + } + + mcam_reg_write(cam, REG_Y0BAR + frame * 4, y); + if (mcam_fmt_is_planar(fmt->pixelformat)) { + mcam_reg_write(cam, REG_U0BAR + frame * 4, u); + mcam_reg_write(cam, REG_V0BAR + frame * 4, v); + } +} + +/* ------------------------------------------------------------------- */ + +#ifdef MCAM_MODE_VMALLOC +/* + * Code specific to the vmalloc buffer mode. + */ + +/* + * Allocate in-kernel DMA buffers for vmalloc mode. + */ +static int mcam_alloc_dma_bufs(struct mcam_camera *cam, int loadtime) +{ + int i; + + mcam_set_config_needed(cam, 1); + if (loadtime) + cam->dma_buf_size = dma_buf_size; + else + cam->dma_buf_size = cam->pix_format.sizeimage; + if (n_dma_bufs > 3) + n_dma_bufs = 3; + + cam->nbufs = 0; + for (i = 0; i < n_dma_bufs; i++) { + cam->dma_bufs[i] = dma_alloc_coherent(cam->dev, + cam->dma_buf_size, cam->dma_handles + i, + GFP_KERNEL); + if (cam->dma_bufs[i] == NULL) { + cam_warn(cam, "Failed to allocate DMA buffer\n"); + break; + } + (cam->nbufs)++; + } + + switch (cam->nbufs) { + case 1: + dma_free_coherent(cam->dev, cam->dma_buf_size, + cam->dma_bufs[0], cam->dma_handles[0]); + cam->nbufs = 0; + fallthrough; + case 0: + cam_err(cam, "Insufficient DMA buffers, cannot operate\n"); + return -ENOMEM; + + case 2: + if (n_dma_bufs > 2) + cam_warn(cam, "Will limp along with only 2 buffers\n"); + break; + } + return 0; +} + +static void mcam_free_dma_bufs(struct mcam_camera *cam) +{ + int i; + + for (i = 0; i < cam->nbufs; i++) { + dma_free_coherent(cam->dev, cam->dma_buf_size, + cam->dma_bufs[i], cam->dma_handles[i]); + cam->dma_bufs[i] = NULL; + } + cam->nbufs = 0; +} + + +/* + * Set up DMA buffers when operating in vmalloc mode + */ +static void mcam_ctlr_dma_vmalloc(struct mcam_camera *cam) +{ + /* + * Store the first two YUV buffers. Then either + * set the third if it exists, or tell the controller + * to just use two. + */ + mcam_write_yuv_bases(cam, 0, cam->dma_handles[0]); + mcam_write_yuv_bases(cam, 1, cam->dma_handles[1]); + if (cam->nbufs > 2) { + mcam_write_yuv_bases(cam, 2, cam->dma_handles[2]); + mcam_reg_clear_bit(cam, REG_CTRL1, C1_TWOBUFS); + } else + mcam_reg_set_bit(cam, REG_CTRL1, C1_TWOBUFS); + if (cam->chip_id == MCAM_CAFE) + mcam_reg_write(cam, REG_UBAR, 0); /* 32 bits only */ +} + +/* + * Copy data out to user space in the vmalloc case + */ +static void mcam_frame_tasklet(struct tasklet_struct *t) +{ + struct mcam_camera *cam = from_tasklet(cam, t, s_tasklet); + int i; + unsigned long flags; + struct mcam_vb_buffer *buf; + + spin_lock_irqsave(&cam->dev_lock, flags); + for (i = 0; i < cam->nbufs; i++) { + int bufno = cam->next_buf; + + if (cam->state != S_STREAMING || bufno < 0) + break; /* I/O got stopped */ + if (++(cam->next_buf) >= cam->nbufs) + cam->next_buf = 0; + if (!test_bit(bufno, &cam->flags)) + continue; + if (list_empty(&cam->buffers)) { + cam->frame_state.singles++; + break; /* Leave it valid, hope for better later */ + } + cam->frame_state.delivered++; + clear_bit(bufno, &cam->flags); + buf = list_first_entry(&cam->buffers, struct mcam_vb_buffer, + queue); + list_del_init(&buf->queue); + /* + * Drop the lock during the big copy. This *should* be safe... + */ + spin_unlock_irqrestore(&cam->dev_lock, flags); + memcpy(vb2_plane_vaddr(&buf->vb_buf.vb2_buf, 0), + cam->dma_bufs[bufno], + cam->pix_format.sizeimage); + mcam_buffer_done(cam, bufno, &buf->vb_buf); + spin_lock_irqsave(&cam->dev_lock, flags); + } + spin_unlock_irqrestore(&cam->dev_lock, flags); +} + + +/* + * Make sure our allocated buffers are up to the task. + */ +static int mcam_check_dma_buffers(struct mcam_camera *cam) +{ + if (cam->nbufs > 0 && cam->dma_buf_size < cam->pix_format.sizeimage) + mcam_free_dma_bufs(cam); + if (cam->nbufs == 0) + return mcam_alloc_dma_bufs(cam, 0); + return 0; +} + +static void mcam_vmalloc_done(struct mcam_camera *cam, int frame) +{ + tasklet_schedule(&cam->s_tasklet); +} + +#else /* MCAM_MODE_VMALLOC */ + +static inline int mcam_alloc_dma_bufs(struct mcam_camera *cam, int loadtime) +{ + return 0; +} + +static inline void mcam_free_dma_bufs(struct mcam_camera *cam) +{ + return; +} + +static inline int mcam_check_dma_buffers(struct mcam_camera *cam) +{ + return 0; +} + + + +#endif /* MCAM_MODE_VMALLOC */ + + +#ifdef MCAM_MODE_DMA_CONTIG +/* ---------------------------------------------------------------------- */ +/* + * DMA-contiguous code. + */ + +/* + * Set up a contiguous buffer for the given frame. Here also is where + * the underrun strategy is set: if there is no buffer available, reuse + * the buffer from the other BAR and set the CF_SINGLE_BUFFER flag to + * keep the interrupt handler from giving that buffer back to user + * space. In this way, we always have a buffer to DMA to and don't + * have to try to play games stopping and restarting the controller. + */ +static void mcam_set_contig_buffer(struct mcam_camera *cam, int frame) +{ + struct mcam_vb_buffer *buf; + dma_addr_t dma_handle; + struct vb2_v4l2_buffer *vb; + + /* + * If there are no available buffers, go into single mode + */ + if (list_empty(&cam->buffers)) { + buf = cam->vb_bufs[frame ^ 0x1]; + set_bit(CF_SINGLE_BUFFER, &cam->flags); + cam->frame_state.singles++; + } else { + /* + * OK, we have a buffer we can use. + */ + buf = list_first_entry(&cam->buffers, struct mcam_vb_buffer, + queue); + list_del_init(&buf->queue); + clear_bit(CF_SINGLE_BUFFER, &cam->flags); + } + + cam->vb_bufs[frame] = buf; + vb = &buf->vb_buf; + + dma_handle = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0); + mcam_write_yuv_bases(cam, frame, dma_handle); +} + +/* + * Initial B_DMA_contig setup. + */ +static void mcam_ctlr_dma_contig(struct mcam_camera *cam) +{ + mcam_reg_set_bit(cam, REG_CTRL1, C1_TWOBUFS); + cam->nbufs = 2; + mcam_set_contig_buffer(cam, 0); + mcam_set_contig_buffer(cam, 1); +} + +/* + * Frame completion handling. + */ +static void mcam_dma_contig_done(struct mcam_camera *cam, int frame) +{ + struct mcam_vb_buffer *buf = cam->vb_bufs[frame]; + + if (!test_bit(CF_SINGLE_BUFFER, &cam->flags)) { + cam->frame_state.delivered++; + cam->vb_bufs[frame] = NULL; + mcam_buffer_done(cam, frame, &buf->vb_buf); + } + mcam_set_contig_buffer(cam, frame); +} + +#endif /* MCAM_MODE_DMA_CONTIG */ + +#ifdef MCAM_MODE_DMA_SG +/* ---------------------------------------------------------------------- */ +/* + * Scatter/gather-specific code. + */ + +/* + * Set up the next buffer for S/G I/O; caller should be sure that + * the controller is stopped and a buffer is available. + */ +static void mcam_sg_next_buffer(struct mcam_camera *cam) +{ + struct mcam_vb_buffer *buf; + struct sg_table *sg_table; + + buf = list_first_entry(&cam->buffers, struct mcam_vb_buffer, queue); + list_del_init(&buf->queue); + sg_table = vb2_dma_sg_plane_desc(&buf->vb_buf.vb2_buf, 0); + /* + * Very Bad Not Good Things happen if you don't clear + * C1_DESC_ENA before making any descriptor changes. + */ + mcam_reg_clear_bit(cam, REG_CTRL1, C1_DESC_ENA); + mcam_reg_write(cam, REG_DMA_DESC_Y, buf->dma_desc_pa); + mcam_reg_write(cam, REG_DESC_LEN_Y, + sg_table->nents * sizeof(struct mcam_dma_desc)); + mcam_reg_write(cam, REG_DESC_LEN_U, 0); + mcam_reg_write(cam, REG_DESC_LEN_V, 0); + mcam_reg_set_bit(cam, REG_CTRL1, C1_DESC_ENA); + cam->vb_bufs[0] = buf; +} + +/* + * Initial B_DMA_sg setup + */ +static void mcam_ctlr_dma_sg(struct mcam_camera *cam) +{ + /* + * The list-empty condition can hit us at resume time + * if the buffer list was empty when the system was suspended. + */ + if (list_empty(&cam->buffers)) { + set_bit(CF_SG_RESTART, &cam->flags); + return; + } + + mcam_reg_clear_bit(cam, REG_CTRL1, C1_DESC_3WORD); + mcam_sg_next_buffer(cam); + cam->nbufs = 3; +} + + +/* + * Frame completion with S/G is trickier. We can't muck with + * a descriptor chain on the fly, since the controller buffers it + * internally. So we have to actually stop and restart; Marvell + * says this is the way to do it. + * + * Of course, stopping is easier said than done; experience shows + * that the controller can start a frame *after* C0_ENABLE has been + * cleared. So when running in S/G mode, the controller is "stopped" + * on receipt of the start-of-frame interrupt. That means we can + * safely change the DMA descriptor array here and restart things + * (assuming there's another buffer waiting to go). + */ +static void mcam_dma_sg_done(struct mcam_camera *cam, int frame) +{ + struct mcam_vb_buffer *buf = cam->vb_bufs[0]; + + /* + * If we're no longer supposed to be streaming, don't do anything. + */ + if (cam->state != S_STREAMING) + return; + /* + * If we have another buffer available, put it in and + * restart the engine. + */ + if (!list_empty(&cam->buffers)) { + mcam_sg_next_buffer(cam); + mcam_ctlr_start(cam); + /* + * Otherwise set CF_SG_RESTART and the controller will + * be restarted once another buffer shows up. + */ + } else { + set_bit(CF_SG_RESTART, &cam->flags); + cam->frame_state.singles++; + cam->vb_bufs[0] = NULL; + } + /* + * Now we can give the completed frame back to user space. + */ + cam->frame_state.delivered++; + mcam_buffer_done(cam, frame, &buf->vb_buf); +} + + +/* + * Scatter/gather mode requires stopping the controller between + * frames so we can put in a new DMA descriptor array. If no new + * buffer exists at frame completion, the controller is left stopped; + * this function is charged with getting things going again. + */ +static void mcam_sg_restart(struct mcam_camera *cam) +{ + mcam_ctlr_dma_sg(cam); + mcam_ctlr_start(cam); + clear_bit(CF_SG_RESTART, &cam->flags); +} + +#else /* MCAM_MODE_DMA_SG */ + +static inline void mcam_sg_restart(struct mcam_camera *cam) +{ + return; +} + +#endif /* MCAM_MODE_DMA_SG */ + +/* ---------------------------------------------------------------------- */ +/* + * Buffer-mode-independent controller code. + */ + +/* + * Image format setup + */ +static void mcam_ctlr_image(struct mcam_camera *cam) +{ + struct v4l2_pix_format *fmt = &cam->pix_format; + u32 widthy = 0, widthuv = 0, imgsz_h, imgsz_w; + + cam_dbg(cam, "camera: bytesperline = %d; height = %d\n", + fmt->bytesperline, fmt->sizeimage / fmt->bytesperline); + imgsz_h = (fmt->height << IMGSZ_V_SHIFT) & IMGSZ_V_MASK; + imgsz_w = (fmt->width * 2) & IMGSZ_H_MASK; + + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + widthy = fmt->width * 2; + widthuv = 0; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + widthy = fmt->width; + widthuv = fmt->width / 2; + break; + default: + widthy = fmt->bytesperline; + widthuv = 0; + break; + } + + mcam_reg_write_mask(cam, REG_IMGPITCH, widthuv << 16 | widthy, + IMGP_YP_MASK | IMGP_UVP_MASK); + mcam_reg_write(cam, REG_IMGSIZE, imgsz_h | imgsz_w); + mcam_reg_write(cam, REG_IMGOFFSET, 0x0); + + /* + * Tell the controller about the image format we are using. + */ + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_YUV | C0_YUV_420PL | C0_YUVE_VYUY, C0_DF_MASK); + break; + case V4L2_PIX_FMT_YUYV: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_YUV | C0_YUV_PACKED | C0_YUVE_NOSWAP, C0_DF_MASK); + break; + case V4L2_PIX_FMT_YVYU: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_YUV | C0_YUV_PACKED | C0_YUVE_SWAP24, C0_DF_MASK); + break; + case V4L2_PIX_FMT_XRGB444: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_RGB | C0_RGBF_444 | C0_RGB4_XBGR, C0_DF_MASK); + break; + case V4L2_PIX_FMT_RGB565: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_RGB | C0_RGBF_565 | C0_RGB5_BGGR, C0_DF_MASK); + break; + case V4L2_PIX_FMT_SBGGR8: + mcam_reg_write_mask(cam, REG_CTRL0, + C0_DF_RGB | C0_RGB5_GRBG, C0_DF_MASK); + break; + default: + cam_err(cam, "camera: unknown format: %#x\n", fmt->pixelformat); + break; + } + + /* + * Make sure it knows we want to use hsync/vsync. + */ + mcam_reg_write_mask(cam, REG_CTRL0, C0_SIF_HVSYNC, C0_SIFM_MASK); +} + + +/* + * Configure the controller for operation; caller holds the + * device mutex. + */ +static int mcam_ctlr_configure(struct mcam_camera *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->dev_lock, flags); + clear_bit(CF_SG_RESTART, &cam->flags); + cam->dma_setup(cam); + mcam_ctlr_image(cam); + mcam_set_config_needed(cam, 0); + spin_unlock_irqrestore(&cam->dev_lock, flags); + return 0; +} + +static void mcam_ctlr_irq_enable(struct mcam_camera *cam) +{ + /* + * Clear any pending interrupts, since we do not + * expect to have I/O active prior to enabling. + */ + mcam_reg_write(cam, REG_IRQSTAT, FRAMEIRQS); + mcam_reg_set_bit(cam, REG_IRQMASK, FRAMEIRQS); +} + +static void mcam_ctlr_irq_disable(struct mcam_camera *cam) +{ + mcam_reg_clear_bit(cam, REG_IRQMASK, FRAMEIRQS); +} + +/* + * Stop the controller, and don't return until we're really sure that no + * further DMA is going on. + */ +static void mcam_ctlr_stop_dma(struct mcam_camera *cam) +{ + unsigned long flags; + + /* + * Theory: stop the camera controller (whether it is operating + * or not). Delay briefly just in case we race with the SOF + * interrupt, then wait until no DMA is active. + */ + spin_lock_irqsave(&cam->dev_lock, flags); + clear_bit(CF_SG_RESTART, &cam->flags); + mcam_ctlr_stop(cam); + cam->state = S_IDLE; + spin_unlock_irqrestore(&cam->dev_lock, flags); + /* + * This is a brutally long sleep, but experience shows that + * it can take the controller a while to get the message that + * it needs to stop grabbing frames. In particular, we can + * sometimes (on mmp) get a frame at the end WITHOUT the + * start-of-frame indication. + */ + msleep(150); + if (test_bit(CF_DMA_ACTIVE, &cam->flags)) + cam_err(cam, "Timeout waiting for DMA to end\n"); + /* This would be bad news - what now? */ + spin_lock_irqsave(&cam->dev_lock, flags); + mcam_ctlr_irq_disable(cam); + spin_unlock_irqrestore(&cam->dev_lock, flags); +} + +/* + * Power up and down. + */ +static int mcam_ctlr_power_up(struct mcam_camera *cam) +{ + unsigned long flags; + int ret; + + spin_lock_irqsave(&cam->dev_lock, flags); + if (cam->plat_power_up) { + ret = cam->plat_power_up(cam); + if (ret) { + spin_unlock_irqrestore(&cam->dev_lock, flags); + return ret; + } + } + mcam_reg_clear_bit(cam, REG_CTRL1, C1_PWRDWN); + spin_unlock_irqrestore(&cam->dev_lock, flags); + return 0; +} + +static void mcam_ctlr_power_down(struct mcam_camera *cam) +{ + unsigned long flags; + + spin_lock_irqsave(&cam->dev_lock, flags); + /* + * School of hard knocks department: be sure we do any register + * twiddling on the controller *before* calling the platform + * power down routine. + */ + mcam_reg_set_bit(cam, REG_CTRL1, C1_PWRDWN); + if (cam->plat_power_down) + cam->plat_power_down(cam); + spin_unlock_irqrestore(&cam->dev_lock, flags); +} + +/* ---------------------------------------------------------------------- */ +/* + * Master sensor clock. + */ +static int mclk_prepare(struct clk_hw *hw) +{ + struct mcam_camera *cam = container_of(hw, struct mcam_camera, mclk_hw); + + clk_prepare(cam->clk[0]); + return 0; +} + +static void mclk_unprepare(struct clk_hw *hw) +{ + struct mcam_camera *cam = container_of(hw, struct mcam_camera, mclk_hw); + + clk_unprepare(cam->clk[0]); +} + +static int mclk_enable(struct clk_hw *hw) +{ + struct mcam_camera *cam = container_of(hw, struct mcam_camera, mclk_hw); + int mclk_src; + int mclk_div; + int ret; + + /* + * Clock the sensor appropriately. Controller clock should + * be 48MHz, sensor "typical" value is half that. + */ + if (cam->bus_type == V4L2_MBUS_CSI2_DPHY) { + mclk_src = cam->mclk_src; + mclk_div = cam->mclk_div; + } else { + mclk_src = 3; + mclk_div = 2; + } + + ret = pm_runtime_resume_and_get(cam->dev); + if (ret < 0) + return ret; + clk_enable(cam->clk[0]); + mcam_reg_write(cam, REG_CLKCTRL, (mclk_src << 29) | mclk_div); + mcam_ctlr_power_up(cam); + + return 0; +} + +static void mclk_disable(struct clk_hw *hw) +{ + struct mcam_camera *cam = container_of(hw, struct mcam_camera, mclk_hw); + + mcam_ctlr_power_down(cam); + clk_disable(cam->clk[0]); + pm_runtime_put(cam->dev); +} + +static unsigned long mclk_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + return 48000000; +} + +static const struct clk_ops mclk_ops = { + .prepare = mclk_prepare, + .unprepare = mclk_unprepare, + .enable = mclk_enable, + .disable = mclk_disable, + .recalc_rate = mclk_recalc_rate, +}; + +/* -------------------------------------------------------------------- */ +/* + * Communications with the sensor. + */ + +static int __mcam_cam_reset(struct mcam_camera *cam) +{ + return sensor_call(cam, core, reset, 0); +} + +/* + * We have found the sensor on the i2c. Let's try to have a + * conversation. + */ +static int mcam_cam_init(struct mcam_camera *cam) +{ + int ret; + + if (cam->state != S_NOTREADY) + cam_warn(cam, "Cam init with device in funky state %d", + cam->state); + ret = __mcam_cam_reset(cam); + /* Get/set parameters? */ + cam->state = S_IDLE; + return ret; +} + +/* + * Configure the sensor to match the parameters we have. Caller should + * hold s_mutex + */ +static int mcam_cam_set_flip(struct mcam_camera *cam) +{ + struct v4l2_control ctrl; + + memset(&ctrl, 0, sizeof(ctrl)); + ctrl.id = V4L2_CID_VFLIP; + ctrl.value = flip; + return v4l2_s_ctrl(NULL, cam->sensor->ctrl_handler, &ctrl); +} + + +static int mcam_cam_configure(struct mcam_camera *cam) +{ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + v4l2_fill_mbus_format(&format.format, &cam->pix_format, cam->mbus_code); + ret = sensor_call(cam, core, init, 0); + if (ret == 0) + ret = sensor_call(cam, pad, set_fmt, NULL, &format); + /* + * OV7670 does weird things if flip is set *before* format... + */ + ret += mcam_cam_set_flip(cam); + return ret; +} + +/* + * Get everything ready, and start grabbing frames. + */ +static int mcam_read_setup(struct mcam_camera *cam) +{ + int ret; + unsigned long flags; + + /* + * Configuration. If we still don't have DMA buffers, + * make one last, desperate attempt. + */ + if (cam->buffer_mode == B_vmalloc && cam->nbufs == 0 && + mcam_alloc_dma_bufs(cam, 0)) + return -ENOMEM; + + if (mcam_needs_config(cam)) { + mcam_cam_configure(cam); + ret = mcam_ctlr_configure(cam); + if (ret) + return ret; + } + + /* + * Turn it loose. + */ + spin_lock_irqsave(&cam->dev_lock, flags); + clear_bit(CF_DMA_ACTIVE, &cam->flags); + mcam_reset_buffers(cam); + if (cam->bus_type == V4L2_MBUS_CSI2_DPHY) + mcam_enable_mipi(cam); + else + mcam_disable_mipi(cam); + mcam_ctlr_irq_enable(cam); + cam->state = S_STREAMING; + if (!test_bit(CF_SG_RESTART, &cam->flags)) + mcam_ctlr_start(cam); + spin_unlock_irqrestore(&cam->dev_lock, flags); + return 0; +} + +/* ----------------------------------------------------------------------- */ +/* + * Videobuf2 interface code. + */ + +static int mcam_vb_queue_setup(struct vb2_queue *vq, + unsigned int *nbufs, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mcam_camera *cam = vb2_get_drv_priv(vq); + int minbufs = (cam->buffer_mode == B_DMA_contig) ? 3 : 2; + unsigned size = cam->pix_format.sizeimage; + + if (*nbufs < minbufs) + *nbufs = minbufs; + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + sizes[0] = size; + *num_planes = 1; /* Someday we have to support planar formats... */ + return 0; +} + + +static void mcam_vb_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mcam_vb_buffer *mvb = vb_to_mvb(vbuf); + struct mcam_camera *cam = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + int start; + + spin_lock_irqsave(&cam->dev_lock, flags); + start = (cam->state == S_BUFWAIT) && !list_empty(&cam->buffers); + list_add(&mvb->queue, &cam->buffers); + if (cam->state == S_STREAMING && test_bit(CF_SG_RESTART, &cam->flags)) + mcam_sg_restart(cam); + spin_unlock_irqrestore(&cam->dev_lock, flags); + if (start) + mcam_read_setup(cam); +} + +static void mcam_vb_requeue_bufs(struct vb2_queue *vq, + enum vb2_buffer_state state) +{ + struct mcam_camera *cam = vb2_get_drv_priv(vq); + struct mcam_vb_buffer *buf, *node; + unsigned long flags; + unsigned i; + + spin_lock_irqsave(&cam->dev_lock, flags); + list_for_each_entry_safe(buf, node, &cam->buffers, queue) { + vb2_buffer_done(&buf->vb_buf.vb2_buf, state); + list_del(&buf->queue); + } + for (i = 0; i < MAX_DMA_BUFS; i++) { + buf = cam->vb_bufs[i]; + + if (buf) { + vb2_buffer_done(&buf->vb_buf.vb2_buf, state); + cam->vb_bufs[i] = NULL; + } + } + spin_unlock_irqrestore(&cam->dev_lock, flags); +} + +/* + * These need to be called with the mutex held from vb2 + */ +static int mcam_vb_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct mcam_camera *cam = vb2_get_drv_priv(vq); + unsigned int frame; + int ret; + + if (cam->state != S_IDLE) { + mcam_vb_requeue_bufs(vq, VB2_BUF_STATE_QUEUED); + return -EINVAL; + } + cam->frame_state.frames = 0; + cam->frame_state.singles = 0; + cam->frame_state.delivered = 0; + cam->sequence = 0; + /* + * Videobuf2 sneakily hoards all the buffers and won't + * give them to us until *after* streaming starts. But + * we can't actually start streaming until we have a + * destination. So go into a wait state and hope they + * give us buffers soon. + */ + if (cam->buffer_mode != B_vmalloc && list_empty(&cam->buffers)) { + cam->state = S_BUFWAIT; + return 0; + } + + /* + * Ensure clear the left over frame flags + * before every really start streaming + */ + for (frame = 0; frame < cam->nbufs; frame++) + clear_bit(CF_FRAME_SOF0 + frame, &cam->flags); + + ret = mcam_read_setup(cam); + if (ret) + mcam_vb_requeue_bufs(vq, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void mcam_vb_stop_streaming(struct vb2_queue *vq) +{ + struct mcam_camera *cam = vb2_get_drv_priv(vq); + + cam_dbg(cam, "stop_streaming: %d frames, %d singles, %d delivered\n", + cam->frame_state.frames, cam->frame_state.singles, + cam->frame_state.delivered); + if (cam->state == S_BUFWAIT) { + /* They never gave us buffers */ + cam->state = S_IDLE; + return; + } + if (cam->state != S_STREAMING) + return; + mcam_ctlr_stop_dma(cam); + /* + * VB2 reclaims the buffers, so we need to forget + * about them. + */ + mcam_vb_requeue_bufs(vq, VB2_BUF_STATE_ERROR); +} + + +static const struct vb2_ops mcam_vb2_ops = { + .queue_setup = mcam_vb_queue_setup, + .buf_queue = mcam_vb_buf_queue, + .start_streaming = mcam_vb_start_streaming, + .stop_streaming = mcam_vb_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + + +#ifdef MCAM_MODE_DMA_SG +/* + * Scatter/gather mode uses all of the above functions plus a + * few extras to deal with DMA mapping. + */ +static int mcam_vb_sg_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mcam_vb_buffer *mvb = vb_to_mvb(vbuf); + struct mcam_camera *cam = vb2_get_drv_priv(vb->vb2_queue); + int ndesc = cam->pix_format.sizeimage/PAGE_SIZE + 1; + + mvb->dma_desc = dma_alloc_coherent(cam->dev, + ndesc * sizeof(struct mcam_dma_desc), + &mvb->dma_desc_pa, GFP_KERNEL); + if (mvb->dma_desc == NULL) { + cam_err(cam, "Unable to get DMA descriptor array\n"); + return -ENOMEM; + } + return 0; +} + +static int mcam_vb_sg_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mcam_vb_buffer *mvb = vb_to_mvb(vbuf); + struct sg_table *sg_table = vb2_dma_sg_plane_desc(vb, 0); + struct mcam_dma_desc *desc = mvb->dma_desc; + struct scatterlist *sg; + int i; + + for_each_sg(sg_table->sgl, sg, sg_table->nents, i) { + desc->dma_addr = sg_dma_address(sg); + desc->segment_len = sg_dma_len(sg); + desc++; + } + return 0; +} + +static void mcam_vb_sg_buf_cleanup(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mcam_camera *cam = vb2_get_drv_priv(vb->vb2_queue); + struct mcam_vb_buffer *mvb = vb_to_mvb(vbuf); + int ndesc = cam->pix_format.sizeimage/PAGE_SIZE + 1; + + dma_free_coherent(cam->dev, ndesc * sizeof(struct mcam_dma_desc), + mvb->dma_desc, mvb->dma_desc_pa); +} + + +static const struct vb2_ops mcam_vb2_sg_ops = { + .queue_setup = mcam_vb_queue_setup, + .buf_init = mcam_vb_sg_buf_init, + .buf_prepare = mcam_vb_sg_buf_prepare, + .buf_queue = mcam_vb_buf_queue, + .buf_cleanup = mcam_vb_sg_buf_cleanup, + .start_streaming = mcam_vb_start_streaming, + .stop_streaming = mcam_vb_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +#endif /* MCAM_MODE_DMA_SG */ + +static int mcam_setup_vb2(struct mcam_camera *cam) +{ + struct vb2_queue *vq = &cam->vb_queue; + + memset(vq, 0, sizeof(*vq)); + vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vq->drv_priv = cam; + vq->lock = &cam->s_mutex; + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ; + vq->buf_struct_size = sizeof(struct mcam_vb_buffer); + vq->dev = cam->dev; + INIT_LIST_HEAD(&cam->buffers); + switch (cam->buffer_mode) { + case B_DMA_contig: +#ifdef MCAM_MODE_DMA_CONTIG + vq->ops = &mcam_vb2_ops; + vq->mem_ops = &vb2_dma_contig_memops; + cam->dma_setup = mcam_ctlr_dma_contig; + cam->frame_complete = mcam_dma_contig_done; +#endif + break; + case B_DMA_sg: +#ifdef MCAM_MODE_DMA_SG + vq->ops = &mcam_vb2_sg_ops; + vq->mem_ops = &vb2_dma_sg_memops; + cam->dma_setup = mcam_ctlr_dma_sg; + cam->frame_complete = mcam_dma_sg_done; +#endif + break; + case B_vmalloc: +#ifdef MCAM_MODE_VMALLOC + tasklet_setup(&cam->s_tasklet, mcam_frame_tasklet); + vq->ops = &mcam_vb2_ops; + vq->mem_ops = &vb2_vmalloc_memops; + cam->dma_setup = mcam_ctlr_dma_vmalloc; + cam->frame_complete = mcam_vmalloc_done; +#endif + break; + } + return vb2_queue_init(vq); +} + + +/* ---------------------------------------------------------------------- */ +/* + * The long list of V4L2 ioctl() operations. + */ + +static int mcam_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mcam_camera *cam = video_drvdata(file); + + strscpy(cap->driver, "marvell_ccic", sizeof(cap->driver)); + strscpy(cap->card, "marvell_ccic", sizeof(cap->card)); + strscpy(cap->bus_info, cam->bus_info, sizeof(cap->bus_info)); + return 0; +} + + +static int mcam_vidioc_enum_fmt_vid_cap(struct file *filp, + void *priv, struct v4l2_fmtdesc *fmt) +{ + if (fmt->index >= N_MCAM_FMTS) + return -EINVAL; + fmt->pixelformat = mcam_formats[fmt->index].pixelformat; + return 0; +} + +static int mcam_vidioc_try_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *fmt) +{ + struct mcam_camera *cam = video_drvdata(filp); + struct mcam_format_struct *f; + struct v4l2_pix_format *pix = &fmt->fmt.pix; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_state pad_state = { + .pads = &pad_cfg + }; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + int ret; + + f = mcam_find_format(pix->pixelformat); + pix->pixelformat = f->pixelformat; + v4l2_fill_mbus_format(&format.format, pix, f->mbus_code); + ret = sensor_call(cam, pad, set_fmt, &pad_state, &format); + v4l2_fill_pix_format(pix, &format.format); + pix->bytesperline = pix->width * f->bpp; + switch (f->pixelformat) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + pix->sizeimage = pix->height * pix->bytesperline * 3 / 2; + break; + default: + pix->sizeimage = pix->height * pix->bytesperline; + break; + } + pix->colorspace = V4L2_COLORSPACE_SRGB; + return ret; +} + +static int mcam_vidioc_s_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *fmt) +{ + struct mcam_camera *cam = video_drvdata(filp); + struct mcam_format_struct *f; + int ret; + + /* + * Can't do anything if the device is not idle + * Also can't if there are streaming buffers in place. + */ + if (cam->state != S_IDLE || vb2_is_busy(&cam->vb_queue)) + return -EBUSY; + + f = mcam_find_format(fmt->fmt.pix.pixelformat); + + /* + * See if the formatting works in principle. + */ + ret = mcam_vidioc_try_fmt_vid_cap(filp, priv, fmt); + if (ret) + return ret; + /* + * Now we start to change things for real, so let's do it + * under lock. + */ + cam->pix_format = fmt->fmt.pix; + cam->mbus_code = f->mbus_code; + + /* + * Make sure we have appropriate DMA buffers. + */ + if (cam->buffer_mode == B_vmalloc) { + ret = mcam_check_dma_buffers(cam); + if (ret) + goto out; + } + mcam_set_config_needed(cam, 1); +out: + return ret; +} + +/* + * Return our stored notion of how the camera is/should be configured. + * The V4l2 spec wants us to be smarter, and actually get this from + * the camera (and not mess with it at open time). Someday. + */ +static int mcam_vidioc_g_fmt_vid_cap(struct file *filp, void *priv, + struct v4l2_format *f) +{ + struct mcam_camera *cam = video_drvdata(filp); + + f->fmt.pix = cam->pix_format; + return 0; +} + +/* + * We only have one input - the sensor - so minimize the nonsense here. + */ +static int mcam_vidioc_enum_input(struct file *filp, void *priv, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, "Camera", sizeof(input->name)); + return 0; +} + +static int mcam_vidioc_g_input(struct file *filp, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int mcam_vidioc_s_input(struct file *filp, void *priv, unsigned int i) +{ + if (i != 0) + return -EINVAL; + return 0; +} + +/* + * G/S_PARM. Most of this is done by the sensor, but we are + * the level which controls the number of read buffers. + */ +static int mcam_vidioc_g_parm(struct file *filp, void *priv, + struct v4l2_streamparm *a) +{ + struct mcam_camera *cam = video_drvdata(filp); + int ret; + + ret = v4l2_g_parm_cap(video_devdata(filp), cam->sensor, a); + a->parm.capture.readbuffers = n_dma_bufs; + return ret; +} + +static int mcam_vidioc_s_parm(struct file *filp, void *priv, + struct v4l2_streamparm *a) +{ + struct mcam_camera *cam = video_drvdata(filp); + int ret; + + ret = v4l2_s_parm_cap(video_devdata(filp), cam->sensor, a); + a->parm.capture.readbuffers = n_dma_bufs; + return ret; +} + +static int mcam_vidioc_enum_framesizes(struct file *filp, void *priv, + struct v4l2_frmsizeenum *sizes) +{ + struct mcam_camera *cam = video_drvdata(filp); + struct mcam_format_struct *f; + struct v4l2_subdev_frame_size_enum fse = { + .index = sizes->index, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + f = mcam_find_format(sizes->pixel_format); + if (f->pixelformat != sizes->pixel_format) + return -EINVAL; + fse.code = f->mbus_code; + ret = sensor_call(cam, pad, enum_frame_size, NULL, &fse); + if (ret) + return ret; + if (fse.min_width == fse.max_width && + fse.min_height == fse.max_height) { + sizes->type = V4L2_FRMSIZE_TYPE_DISCRETE; + sizes->discrete.width = fse.min_width; + sizes->discrete.height = fse.min_height; + return 0; + } + sizes->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + sizes->stepwise.min_width = fse.min_width; + sizes->stepwise.max_width = fse.max_width; + sizes->stepwise.min_height = fse.min_height; + sizes->stepwise.max_height = fse.max_height; + sizes->stepwise.step_width = 1; + sizes->stepwise.step_height = 1; + return 0; +} + +static int mcam_vidioc_enum_frameintervals(struct file *filp, void *priv, + struct v4l2_frmivalenum *interval) +{ + struct mcam_camera *cam = video_drvdata(filp); + struct mcam_format_struct *f; + struct v4l2_subdev_frame_interval_enum fie = { + .index = interval->index, + .width = interval->width, + .height = interval->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + f = mcam_find_format(interval->pixel_format); + if (f->pixelformat != interval->pixel_format) + return -EINVAL; + fie.code = f->mbus_code; + ret = sensor_call(cam, pad, enum_frame_interval, NULL, &fie); + if (ret) + return ret; + interval->type = V4L2_FRMIVAL_TYPE_DISCRETE; + interval->discrete = fie.interval; + return 0; +} + +#ifdef CONFIG_VIDEO_ADV_DEBUG +static int mcam_vidioc_g_register(struct file *file, void *priv, + struct v4l2_dbg_register *reg) +{ + struct mcam_camera *cam = video_drvdata(file); + + if (reg->reg > cam->regs_size - 4) + return -EINVAL; + reg->val = mcam_reg_read(cam, reg->reg); + reg->size = 4; + return 0; +} + +static int mcam_vidioc_s_register(struct file *file, void *priv, + const struct v4l2_dbg_register *reg) +{ + struct mcam_camera *cam = video_drvdata(file); + + if (reg->reg > cam->regs_size - 4) + return -EINVAL; + mcam_reg_write(cam, reg->reg, reg->val); + return 0; +} +#endif + +static const struct v4l2_ioctl_ops mcam_v4l_ioctl_ops = { + .vidioc_querycap = mcam_vidioc_querycap, + .vidioc_enum_fmt_vid_cap = mcam_vidioc_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = mcam_vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = mcam_vidioc_s_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = mcam_vidioc_g_fmt_vid_cap, + .vidioc_enum_input = mcam_vidioc_enum_input, + .vidioc_g_input = mcam_vidioc_g_input, + .vidioc_s_input = mcam_vidioc_s_input, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_g_parm = mcam_vidioc_g_parm, + .vidioc_s_parm = mcam_vidioc_s_parm, + .vidioc_enum_framesizes = mcam_vidioc_enum_framesizes, + .vidioc_enum_frameintervals = mcam_vidioc_enum_frameintervals, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +#ifdef CONFIG_VIDEO_ADV_DEBUG + .vidioc_g_register = mcam_vidioc_g_register, + .vidioc_s_register = mcam_vidioc_s_register, +#endif +}; + +/* ---------------------------------------------------------------------- */ +/* + * Our various file operations. + */ +static int mcam_v4l_open(struct file *filp) +{ + struct mcam_camera *cam = video_drvdata(filp); + int ret; + + mutex_lock(&cam->s_mutex); + ret = v4l2_fh_open(filp); + if (ret) + goto out; + if (v4l2_fh_is_singular_file(filp)) { + ret = sensor_call(cam, core, s_power, 1); + if (ret) + goto out; + ret = pm_runtime_resume_and_get(cam->dev); + if (ret < 0) + goto out; + __mcam_cam_reset(cam); + mcam_set_config_needed(cam, 1); + } +out: + mutex_unlock(&cam->s_mutex); + if (ret) + v4l2_fh_release(filp); + return ret; +} + + +static int mcam_v4l_release(struct file *filp) +{ + struct mcam_camera *cam = video_drvdata(filp); + bool last_open; + + mutex_lock(&cam->s_mutex); + last_open = v4l2_fh_is_singular_file(filp); + _vb2_fop_release(filp, NULL); + if (last_open) { + mcam_disable_mipi(cam); + sensor_call(cam, core, s_power, 0); + pm_runtime_put(cam->dev); + if (cam->buffer_mode == B_vmalloc && alloc_bufs_at_read) + mcam_free_dma_bufs(cam); + } + + mutex_unlock(&cam->s_mutex); + return 0; +} + +static const struct v4l2_file_operations mcam_v4l_fops = { + .owner = THIS_MODULE, + .open = mcam_v4l_open, + .release = mcam_v4l_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + + +/* + * This template device holds all of those v4l2 methods; we + * clone it for specific real devices. + */ +static const struct video_device mcam_v4l_template = { + .name = "mcam", + .fops = &mcam_v4l_fops, + .ioctl_ops = &mcam_v4l_ioctl_ops, + .release = video_device_release_empty, + .device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, +}; + +/* ---------------------------------------------------------------------- */ +/* + * Interrupt handler stuff + */ +static void mcam_frame_complete(struct mcam_camera *cam, int frame) +{ + /* + * Basic frame housekeeping. + */ + set_bit(frame, &cam->flags); + clear_bit(CF_DMA_ACTIVE, &cam->flags); + cam->next_buf = frame; + cam->buf_seq[frame] = cam->sequence++; + cam->frame_state.frames++; + /* + * "This should never happen" + */ + if (cam->state != S_STREAMING) + return; + /* + * Process the frame and set up the next one. + */ + cam->frame_complete(cam, frame); +} + + +/* + * The interrupt handler; this needs to be called from the + * platform irq handler with the lock held. + */ +int mccic_irq(struct mcam_camera *cam, unsigned int irqs) +{ + unsigned int frame, handled = 0; + + mcam_reg_write(cam, REG_IRQSTAT, FRAMEIRQS); /* Clear'em all */ + /* + * Handle any frame completions. There really should + * not be more than one of these, or we have fallen + * far behind. + * + * When running in S/G mode, the frame number lacks any + * real meaning - there's only one descriptor array - but + * the controller still picks a different one to signal + * each time. + */ + for (frame = 0; frame < cam->nbufs; frame++) + if (irqs & (IRQ_EOF0 << frame) && + test_bit(CF_FRAME_SOF0 + frame, &cam->flags)) { + mcam_frame_complete(cam, frame); + handled = 1; + clear_bit(CF_FRAME_SOF0 + frame, &cam->flags); + if (cam->buffer_mode == B_DMA_sg) + break; + } + /* + * If a frame starts, note that we have DMA active. This + * code assumes that we won't get multiple frame interrupts + * at once; may want to rethink that. + */ + for (frame = 0; frame < cam->nbufs; frame++) { + if (irqs & (IRQ_SOF0 << frame)) { + set_bit(CF_FRAME_SOF0 + frame, &cam->flags); + handled = IRQ_HANDLED; + } + } + + if (handled == IRQ_HANDLED) { + set_bit(CF_DMA_ACTIVE, &cam->flags); + if (cam->buffer_mode == B_DMA_sg) + mcam_ctlr_stop(cam); + } + return handled; +} +EXPORT_SYMBOL_GPL(mccic_irq); + +/* ---------------------------------------------------------------------- */ +/* + * Registration and such. + */ + +static int mccic_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd) +{ + struct mcam_camera *cam = notifier_to_mcam(notifier); + int ret; + + mutex_lock(&cam->s_mutex); + if (cam->sensor) { + cam_err(cam, "sensor already bound\n"); + ret = -EBUSY; + goto out; + } + + v4l2_set_subdev_hostdata(subdev, cam); + cam->sensor = subdev; + + ret = mcam_cam_init(cam); + if (ret) { + cam->sensor = NULL; + goto out; + } + + ret = mcam_setup_vb2(cam); + if (ret) { + cam->sensor = NULL; + goto out; + } + + cam->vdev = mcam_v4l_template; + cam->vdev.v4l2_dev = &cam->v4l2_dev; + cam->vdev.lock = &cam->s_mutex; + cam->vdev.queue = &cam->vb_queue; + video_set_drvdata(&cam->vdev, cam); + ret = video_register_device(&cam->vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + cam->sensor = NULL; + goto out; + } + + cam_dbg(cam, "sensor %s bound\n", subdev->name); +out: + mutex_unlock(&cam->s_mutex); + return ret; +} + +static void mccic_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, struct v4l2_async_subdev *asd) +{ + struct mcam_camera *cam = notifier_to_mcam(notifier); + + mutex_lock(&cam->s_mutex); + if (cam->sensor != subdev) { + cam_err(cam, "sensor %s not bound\n", subdev->name); + goto out; + } + + video_unregister_device(&cam->vdev); + cam->sensor = NULL; + cam_dbg(cam, "sensor %s unbound\n", subdev->name); + +out: + mutex_unlock(&cam->s_mutex); +} + +static int mccic_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct mcam_camera *cam = notifier_to_mcam(notifier); + int ret; + + /* + * Get the v4l2 setup done. + */ + ret = v4l2_ctrl_handler_init(&cam->ctrl_handler, 10); + if (!ret) + cam->v4l2_dev.ctrl_handler = &cam->ctrl_handler; + + return ret; +} + +static const struct v4l2_async_notifier_operations mccic_notify_ops = { + .bound = mccic_notify_bound, + .unbind = mccic_notify_unbind, + .complete = mccic_notify_complete, +}; + +int mccic_register(struct mcam_camera *cam) +{ + struct clk_init_data mclk_init = { }; + int ret; + + /* + * Validate the requested buffer mode. + */ + if (buffer_mode >= 0) + cam->buffer_mode = buffer_mode; + if (cam->buffer_mode == B_DMA_sg && + cam->chip_id == MCAM_CAFE) { + printk(KERN_ERR "marvell-cam: Cafe can't do S/G I/O, attempting vmalloc mode instead\n"); + cam->buffer_mode = B_vmalloc; + } + + if (!mcam_buffer_mode_supported(cam->buffer_mode)) { + printk(KERN_ERR "marvell-cam: buffer mode %d unsupported\n", + cam->buffer_mode); + ret = -EINVAL; + goto out; + } + + /* + * Register with V4L + */ + ret = v4l2_device_register(cam->dev, &cam->v4l2_dev); + if (ret) + goto out; + + mutex_init(&cam->s_mutex); + cam->state = S_NOTREADY; + mcam_set_config_needed(cam, 1); + cam->pix_format = mcam_def_pix_format; + cam->mbus_code = mcam_def_mbus_code; + + cam->notifier.ops = &mccic_notify_ops; + ret = v4l2_async_nf_register(&cam->v4l2_dev, &cam->notifier); + if (ret < 0) { + cam_warn(cam, "failed to register a sensor notifier"); + goto out; + } + + /* + * Register sensor master clock. + */ + mclk_init.parent_names = NULL; + mclk_init.num_parents = 0; + mclk_init.ops = &mclk_ops; + mclk_init.name = "mclk"; + + of_property_read_string(cam->dev->of_node, "clock-output-names", + &mclk_init.name); + + cam->mclk_hw.init = &mclk_init; + + cam->mclk = devm_clk_register(cam->dev, &cam->mclk_hw); + if (IS_ERR(cam->mclk)) { + ret = PTR_ERR(cam->mclk); + dev_err(cam->dev, "can't register clock\n"); + goto out; + } + + /* + * If so requested, try to get our DMA buffers now. + */ + if (cam->buffer_mode == B_vmalloc && !alloc_bufs_at_read) { + if (mcam_alloc_dma_bufs(cam, 1)) + cam_warn(cam, "Unable to alloc DMA buffers at load will try again later."); + } + + return 0; + +out: + v4l2_async_nf_unregister(&cam->notifier); + v4l2_device_unregister(&cam->v4l2_dev); + v4l2_async_nf_cleanup(&cam->notifier); + return ret; +} +EXPORT_SYMBOL_GPL(mccic_register); + +void mccic_shutdown(struct mcam_camera *cam) +{ + /* + * If we have no users (and we really, really should have no + * users) the device will already be powered down. Trying to + * take it down again will wedge the machine, which is frowned + * upon. + */ + if (!list_empty(&cam->vdev.fh_list)) { + cam_warn(cam, "Removing a device with users!\n"); + sensor_call(cam, core, s_power, 0); + } + if (cam->buffer_mode == B_vmalloc) + mcam_free_dma_bufs(cam); + v4l2_ctrl_handler_free(&cam->ctrl_handler); + v4l2_async_nf_unregister(&cam->notifier); + v4l2_device_unregister(&cam->v4l2_dev); + v4l2_async_nf_cleanup(&cam->notifier); +} +EXPORT_SYMBOL_GPL(mccic_shutdown); + +/* + * Power management + */ +void mccic_suspend(struct mcam_camera *cam) +{ + mutex_lock(&cam->s_mutex); + if (!list_empty(&cam->vdev.fh_list)) { + enum mcam_state cstate = cam->state; + + mcam_ctlr_stop_dma(cam); + sensor_call(cam, core, s_power, 0); + cam->state = cstate; + } + mutex_unlock(&cam->s_mutex); +} +EXPORT_SYMBOL_GPL(mccic_suspend); + +int mccic_resume(struct mcam_camera *cam) +{ + int ret = 0; + + mutex_lock(&cam->s_mutex); + if (!list_empty(&cam->vdev.fh_list)) { + ret = sensor_call(cam, core, s_power, 1); + if (ret) { + mutex_unlock(&cam->s_mutex); + return ret; + } + __mcam_cam_reset(cam); + } else { + sensor_call(cam, core, s_power, 0); + } + mutex_unlock(&cam->s_mutex); + + set_bit(CF_CONFIG_NEEDED, &cam->flags); + if (cam->state == S_STREAMING) { + /* + * If there was a buffer in the DMA engine at suspend + * time, put it back on the queue or we'll forget about it. + */ + if (cam->buffer_mode == B_DMA_sg && cam->vb_bufs[0]) + list_add(&cam->vb_bufs[0]->queue, &cam->buffers); + ret = mcam_read_setup(cam); + } + return ret; +} +EXPORT_SYMBOL_GPL(mccic_resume); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jonathan Corbet "); diff --git a/drivers/media/platform/marvell/mcam-core.h b/drivers/media/platform/marvell/mcam-core.h new file mode 100644 index 000000000..51e66db45 --- /dev/null +++ b/drivers/media/platform/marvell/mcam-core.h @@ -0,0 +1,378 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Marvell camera core structures. + * + * Copyright 2011 Jonathan Corbet corbet@lwn.net + */ +#ifndef _MCAM_CORE_H +#define _MCAM_CORE_H + +#include +#include +#include +#include +#include +#include + +/* + * Create our own symbols for the supported buffer modes, but, for now, + * base them entirely on which videobuf2 options have been selected. + */ +#if IS_ENABLED(CONFIG_VIDEOBUF2_VMALLOC) +#define MCAM_MODE_VMALLOC 1 +#endif + +#if IS_ENABLED(CONFIG_VIDEOBUF2_DMA_CONTIG) +#define MCAM_MODE_DMA_CONTIG 1 +#endif + +#if IS_ENABLED(CONFIG_VIDEOBUF2_DMA_SG) +#define MCAM_MODE_DMA_SG 1 +#endif + +#if !defined(MCAM_MODE_VMALLOC) && !defined(MCAM_MODE_DMA_CONTIG) && \ + !defined(MCAM_MODE_DMA_SG) +#error One of the vb2 buffer modes must be selected in the config +#endif + + +enum mcam_state { + S_NOTREADY, /* Not yet initialized */ + S_IDLE, /* Just hanging around */ + S_FLAKED, /* Some sort of problem */ + S_STREAMING, /* Streaming data */ + S_BUFWAIT /* streaming requested but no buffers yet */ +}; +#define MAX_DMA_BUFS 3 + +/* + * Different platforms work best with different buffer modes, so we + * let the platform pick. + */ +enum mcam_buffer_mode { + B_vmalloc = 0, + B_DMA_contig = 1, + B_DMA_sg = 2 +}; + +enum mcam_chip_id { + MCAM_CAFE, + MCAM_ARMADA610, +}; + +/* + * Is a given buffer mode supported by the current kernel configuration? + */ +static inline int mcam_buffer_mode_supported(enum mcam_buffer_mode mode) +{ + switch (mode) { +#ifdef MCAM_MODE_VMALLOC + case B_vmalloc: +#endif +#ifdef MCAM_MODE_DMA_CONTIG + case B_DMA_contig: +#endif +#ifdef MCAM_MODE_DMA_SG + case B_DMA_sg: +#endif + return 1; + default: + return 0; + } +} + +/* + * Basic frame states + */ +struct mcam_frame_state { + unsigned int frames; + unsigned int singles; + unsigned int delivered; +}; + +#define NR_MCAM_CLK 3 + +/* + * A description of one of our devices. + * Locking: controlled by s_mutex. Certain fields, however, require + * the dev_lock spinlock; they are marked as such by comments. + * dev_lock is also required for access to device registers. + */ +struct mcam_camera { + /* + * These fields should be set by the platform code prior to + * calling mcam_register(). + */ + unsigned char __iomem *regs; + unsigned regs_size; /* size in bytes of the register space */ + spinlock_t dev_lock; + struct device *dev; /* For messages, dma alloc */ + enum mcam_chip_id chip_id; + enum mcam_buffer_mode buffer_mode; + + int mclk_src; /* which clock source the mclk derives from */ + int mclk_div; /* Clock Divider Value for MCLK */ + + enum v4l2_mbus_type bus_type; + /* MIPI support */ + /* The dphy config value, allocated in board file + * dphy[0]: DPHY3 + * dphy[1]: DPHY5 + * dphy[2]: DPHY6 + */ + int *dphy; + bool mipi_enabled; /* flag whether mipi is enabled already */ + int lane; /* lane number */ + + /* clock tree support */ + struct clk *clk[NR_MCAM_CLK]; + struct clk_hw mclk_hw; + struct clk *mclk; + + /* + * Callbacks from the core to the platform code. + */ + int (*plat_power_up) (struct mcam_camera *cam); + void (*plat_power_down) (struct mcam_camera *cam); + void (*calc_dphy) (struct mcam_camera *cam); + + /* + * Everything below here is private to the mcam core and + * should not be touched by the platform code. + */ + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; + enum mcam_state state; + unsigned long flags; /* Buffer status, mainly (dev_lock) */ + + struct mcam_frame_state frame_state; /* Frame state counter */ + /* + * Subsystem structures. + */ + struct video_device vdev; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *sensor; + + /* Videobuf2 stuff */ + struct vb2_queue vb_queue; + struct list_head buffers; /* Available frames */ + + unsigned int nbufs; /* How many are alloc'd */ + int next_buf; /* Next to consume (dev_lock) */ + + char bus_info[32]; /* querycap bus_info */ + + /* DMA buffers - vmalloc mode */ +#ifdef MCAM_MODE_VMALLOC + unsigned int dma_buf_size; /* allocated size */ + void *dma_bufs[MAX_DMA_BUFS]; /* Internal buffer addresses */ + dma_addr_t dma_handles[MAX_DMA_BUFS]; /* Buffer bus addresses */ + struct tasklet_struct s_tasklet; +#endif + unsigned int sequence; /* Frame sequence number */ + unsigned int buf_seq[MAX_DMA_BUFS]; /* Sequence for individual bufs */ + + /* DMA buffers - DMA modes */ + struct mcam_vb_buffer *vb_bufs[MAX_DMA_BUFS]; + + /* Mode-specific ops, set at open time */ + void (*dma_setup)(struct mcam_camera *cam); + void (*frame_complete)(struct mcam_camera *cam, int frame); + + /* Current operating parameters */ + struct v4l2_pix_format pix_format; + u32 mbus_code; + + /* Locks */ + struct mutex s_mutex; /* Access to this structure */ +}; + + +/* + * Register I/O functions. These are here because the platform code + * may legitimately need to mess with the register space. + */ +/* + * Device register I/O + */ +static inline void mcam_reg_write(struct mcam_camera *cam, unsigned int reg, + unsigned int val) +{ + iowrite32(val, cam->regs + reg); +} + +static inline unsigned int mcam_reg_read(struct mcam_camera *cam, + unsigned int reg) +{ + return ioread32(cam->regs + reg); +} + + +static inline void mcam_reg_write_mask(struct mcam_camera *cam, unsigned int reg, + unsigned int val, unsigned int mask) +{ + unsigned int v = mcam_reg_read(cam, reg); + + v = (v & ~mask) | (val & mask); + mcam_reg_write(cam, reg, v); +} + +static inline void mcam_reg_clear_bit(struct mcam_camera *cam, + unsigned int reg, unsigned int val) +{ + mcam_reg_write_mask(cam, reg, 0, val); +} + +static inline void mcam_reg_set_bit(struct mcam_camera *cam, + unsigned int reg, unsigned int val) +{ + mcam_reg_write_mask(cam, reg, val, val); +} + +/* + * Functions for use by platform code. + */ +int mccic_register(struct mcam_camera *cam); +int mccic_irq(struct mcam_camera *cam, unsigned int irqs); +void mccic_shutdown(struct mcam_camera *cam); +void mccic_suspend(struct mcam_camera *cam); +int mccic_resume(struct mcam_camera *cam); + +/* + * Register definitions for the m88alp01 camera interface. Offsets in bytes + * as given in the spec. + */ +#define REG_Y0BAR 0x00 +#define REG_Y1BAR 0x04 +#define REG_Y2BAR 0x08 +#define REG_U0BAR 0x0c +#define REG_U1BAR 0x10 +#define REG_U2BAR 0x14 +#define REG_V0BAR 0x18 +#define REG_V1BAR 0x1C +#define REG_V2BAR 0x20 + +/* + * register definitions for MIPI support + */ +#define REG_CSI2_CTRL0 0x100 +#define CSI2_C0_MIPI_EN (0x1 << 0) +#define CSI2_C0_ACT_LANE(n) ((n-1) << 1) +#define REG_CSI2_DPHY3 0x12c +#define REG_CSI2_DPHY5 0x134 +#define REG_CSI2_DPHY6 0x138 + +/* ... */ + +#define REG_IMGPITCH 0x24 /* Image pitch register */ +#define IMGP_YP_SHFT 2 /* Y pitch params */ +#define IMGP_YP_MASK 0x00003ffc /* Y pitch field */ +#define IMGP_UVP_SHFT 18 /* UV pitch (planar) */ +#define IMGP_UVP_MASK 0x3ffc0000 +#define REG_IRQSTATRAW 0x28 /* RAW IRQ Status */ +#define IRQ_EOF0 0x00000001 /* End of frame 0 */ +#define IRQ_EOF1 0x00000002 /* End of frame 1 */ +#define IRQ_EOF2 0x00000004 /* End of frame 2 */ +#define IRQ_SOF0 0x00000008 /* Start of frame 0 */ +#define IRQ_SOF1 0x00000010 /* Start of frame 1 */ +#define IRQ_SOF2 0x00000020 /* Start of frame 2 */ +#define IRQ_OVERFLOW 0x00000040 /* FIFO overflow */ +#define IRQ_TWSIW 0x00010000 /* TWSI (smbus) write */ +#define IRQ_TWSIR 0x00020000 /* TWSI read */ +#define IRQ_TWSIE 0x00040000 /* TWSI error */ +#define TWSIIRQS (IRQ_TWSIW|IRQ_TWSIR|IRQ_TWSIE) +#define FRAMEIRQS (IRQ_EOF0|IRQ_EOF1|IRQ_EOF2|IRQ_SOF0|IRQ_SOF1|IRQ_SOF2) +#define ALLIRQS (TWSIIRQS|FRAMEIRQS|IRQ_OVERFLOW) +#define REG_IRQMASK 0x2c /* IRQ mask - same bits as IRQSTAT */ +#define REG_IRQSTAT 0x30 /* IRQ status / clear */ + +#define REG_IMGSIZE 0x34 /* Image size */ +#define IMGSZ_V_MASK 0x1fff0000 +#define IMGSZ_V_SHIFT 16 +#define IMGSZ_H_MASK 0x00003fff +#define REG_IMGOFFSET 0x38 /* IMage offset */ + +#define REG_CTRL0 0x3c /* Control 0 */ +#define C0_ENABLE 0x00000001 /* Makes the whole thing go */ + +/* Mask for all the format bits */ +#define C0_DF_MASK 0x00fffffc /* Bits 2-23 */ + +/* RGB ordering */ +#define C0_RGB4_RGBX 0x00000000 +#define C0_RGB4_XRGB 0x00000004 +#define C0_RGB4_BGRX 0x00000008 +#define C0_RGB4_XBGR 0x0000000c +#define C0_RGB5_RGGB 0x00000000 +#define C0_RGB5_GRBG 0x00000004 +#define C0_RGB5_GBRG 0x00000008 +#define C0_RGB5_BGGR 0x0000000c + +/* Spec has two fields for DIN and DOUT, but they must match, so + combine them here. */ +#define C0_DF_YUV 0x00000000 /* Data is YUV */ +#define C0_DF_RGB 0x000000a0 /* ... RGB */ +#define C0_DF_BAYER 0x00000140 /* ... Bayer */ +/* 8-8-8 must be missing from the below - ask */ +#define C0_RGBF_565 0x00000000 +#define C0_RGBF_444 0x00000800 +#define C0_RGB_BGR 0x00001000 /* Blue comes first */ +#define C0_YUV_PLANAR 0x00000000 /* YUV 422 planar format */ +#define C0_YUV_PACKED 0x00008000 /* YUV 422 packed */ +#define C0_YUV_420PL 0x0000a000 /* YUV 420 planar */ +/* Think that 420 packed must be 111 - ask */ +#define C0_YUVE_YUYV 0x00000000 /* Y1CbY0Cr */ +#define C0_YUVE_YVYU 0x00010000 /* Y1CrY0Cb */ +#define C0_YUVE_VYUY 0x00020000 /* CrY1CbY0 */ +#define C0_YUVE_UYVY 0x00030000 /* CbY1CrY0 */ +#define C0_YUVE_NOSWAP 0x00000000 /* no bytes swapping */ +#define C0_YUVE_SWAP13 0x00010000 /* swap byte 1 and 3 */ +#define C0_YUVE_SWAP24 0x00020000 /* swap byte 2 and 4 */ +#define C0_YUVE_SWAP1324 0x00030000 /* swap bytes 1&3 and 2&4 */ +/* Bayer bits 18,19 if needed */ +#define C0_EOF_VSYNC 0x00400000 /* Generate EOF by VSYNC */ +#define C0_VEDGE_CTRL 0x00800000 /* Detect falling edge of VSYNC */ +#define C0_HPOL_LOW 0x01000000 /* HSYNC polarity active low */ +#define C0_VPOL_LOW 0x02000000 /* VSYNC polarity active low */ +#define C0_VCLK_LOW 0x04000000 /* VCLK on falling edge */ +#define C0_DOWNSCALE 0x08000000 /* Enable downscaler */ +/* SIFMODE */ +#define C0_SIF_HVSYNC 0x00000000 /* Use H/VSYNC */ +#define C0_SOF_NOSYNC 0x40000000 /* Use inband active signaling */ +#define C0_SIFM_MASK 0xc0000000 /* SIF mode bits */ + +/* Bits below C1_444ALPHA are not present in Cafe */ +#define REG_CTRL1 0x40 /* Control 1 */ +#define C1_CLKGATE 0x00000001 /* Sensor clock gate */ +#define C1_DESC_ENA 0x00000100 /* DMA descriptor enable */ +#define C1_DESC_3WORD 0x00000200 /* Three-word descriptors used */ +#define C1_444ALPHA 0x00f00000 /* Alpha field in RGB444 */ +#define C1_ALPHA_SHFT 20 +#define C1_DMAB32 0x00000000 /* 32-byte DMA burst */ +#define C1_DMAB16 0x02000000 /* 16-byte DMA burst */ +#define C1_DMAB64 0x04000000 /* 64-byte DMA burst */ +#define C1_DMAB_MASK 0x06000000 +#define C1_TWOBUFS 0x08000000 /* Use only two DMA buffers */ +#define C1_PWRDWN 0x10000000 /* Power down */ + +#define REG_CLKCTRL 0x88 /* Clock control */ +#define CLK_DIV_MASK 0x0000ffff /* Upper bits RW "reserved" */ + +/* This appears to be a Cafe-only register */ +#define REG_UBAR 0xc4 /* Upper base address register */ + +/* Armada 610 DMA descriptor registers */ +#define REG_DMA_DESC_Y 0x200 +#define REG_DMA_DESC_U 0x204 +#define REG_DMA_DESC_V 0x208 +#define REG_DESC_LEN_Y 0x20c /* Lengths are in bytes */ +#define REG_DESC_LEN_U 0x210 +#define REG_DESC_LEN_V 0x214 + +/* + * Useful stuff that probably belongs somewhere global. + */ +#define VGA_WIDTH 640 +#define VGA_HEIGHT 480 + +#endif /* _MCAM_CORE_H */ diff --git a/drivers/media/platform/marvell/mmp-driver.c b/drivers/media/platform/marvell/mmp-driver.c new file mode 100644 index 000000000..df16899ab --- /dev/null +++ b/drivers/media/platform/marvell/mmp-driver.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Support for the camera device found on Marvell MMP processors; known + * to work with the Armada 610 as used in the OLPC 1.75 system. + * + * Copyright 2011 Jonathan Corbet + * Copyright 2018 Lubomir Rintel + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mcam-core.h" + +MODULE_ALIAS("platform:mmp-camera"); +MODULE_AUTHOR("Jonathan Corbet "); +MODULE_LICENSE("GPL"); + +static char *mcam_clks[] = {"axi", "func", "phy"}; + +struct mmp_camera { + struct platform_device *pdev; + struct mcam_camera mcam; + struct list_head devlist; + struct clk *mipi_clk; + int irq; +}; + +static inline struct mmp_camera *mcam_to_cam(struct mcam_camera *mcam) +{ + return container_of(mcam, struct mmp_camera, mcam); +} + +/* + * calc the dphy register values + * There are three dphy registers being used. + * dphy[0] - CSI2_DPHY3 + * dphy[1] - CSI2_DPHY5 + * dphy[2] - CSI2_DPHY6 + * CSI2_DPHY3 and CSI2_DPHY6 can be set with a default value + * or be calculated dynamically + */ +static void mmpcam_calc_dphy(struct mcam_camera *mcam) +{ + struct mmp_camera *cam = mcam_to_cam(mcam); + struct mmp_camera_platform_data *pdata = cam->pdev->dev.platform_data; + struct device *dev = &cam->pdev->dev; + unsigned long tx_clk_esc; + + /* + * If CSI2_DPHY3 is calculated dynamically, + * pdata->lane_clk should be already set + * either in the board driver statically + * or in the sensor driver dynamically. + */ + /* + * dphy[0] - CSI2_DPHY3: + * bit 0 ~ bit 7: HS Term Enable. + * defines the time that the DPHY + * wait before enabling the data + * lane termination after detecting + * that the sensor has driven the data + * lanes to the LP00 bridge state. + * The value is calculated by: + * (Max T(D_TERM_EN)/Period(DDR)) - 1 + * bit 8 ~ bit 15: HS_SETTLE + * Time interval during which the HS + * receiver shall ignore any Data Lane + * HS transitions. + * The value has been calibrated on + * different boards. It seems to work well. + * + * More detail please refer + * MIPI Alliance Spectification for D-PHY + * document for explanation of HS-SETTLE + * and D-TERM-EN. + */ + switch (pdata->dphy3_algo) { + case DPHY3_ALGO_PXA910: + /* + * Calculate CSI2_DPHY3 algo for PXA910 + */ + pdata->dphy[0] = + (((1 + (pdata->lane_clk * 80) / 1000) & 0xff) << 8) + | (1 + pdata->lane_clk * 35 / 1000); + break; + case DPHY3_ALGO_PXA2128: + /* + * Calculate CSI2_DPHY3 algo for PXA2128 + */ + pdata->dphy[0] = + (((2 + (pdata->lane_clk * 110) / 1000) & 0xff) << 8) + | (1 + pdata->lane_clk * 35 / 1000); + break; + default: + /* + * Use default CSI2_DPHY3 value for PXA688/PXA988 + */ + dev_dbg(dev, "camera: use the default CSI2_DPHY3 value\n"); + } + + /* + * mipi_clk will never be changed, it is a fixed value on MMP + */ + if (IS_ERR(cam->mipi_clk)) + return; + + /* get the escape clk, this is hard coded */ + clk_prepare_enable(cam->mipi_clk); + tx_clk_esc = (clk_get_rate(cam->mipi_clk) / 1000000) / 12; + clk_disable_unprepare(cam->mipi_clk); + /* + * dphy[2] - CSI2_DPHY6: + * bit 0 ~ bit 7: CK Term Enable + * Time for the Clock Lane receiver to enable the HS line + * termination. The value is calculated similarly with + * HS Term Enable + * bit 8 ~ bit 15: CK Settle + * Time interval during which the HS receiver shall ignore + * any Clock Lane HS transitions. + * The value is calibrated on the boards. + */ + pdata->dphy[2] = + ((((534 * tx_clk_esc) / 2000 - 1) & 0xff) << 8) + | (((38 * tx_clk_esc) / 1000 - 1) & 0xff); + + dev_dbg(dev, "camera: DPHY sets: dphy3=0x%x, dphy5=0x%x, dphy6=0x%x\n", + pdata->dphy[0], pdata->dphy[1], pdata->dphy[2]); +} + +static irqreturn_t mmpcam_irq(int irq, void *data) +{ + struct mcam_camera *mcam = data; + unsigned int irqs, handled; + + spin_lock(&mcam->dev_lock); + irqs = mcam_reg_read(mcam, REG_IRQSTAT); + handled = mccic_irq(mcam, irqs); + spin_unlock(&mcam->dev_lock); + return IRQ_RETVAL(handled); +} + +static void mcam_init_clk(struct mcam_camera *mcam) +{ + unsigned int i; + + for (i = 0; i < NR_MCAM_CLK; i++) { + if (mcam_clks[i] != NULL) { + /* Some clks are not necessary on some boards + * We still try to run even it fails getting clk + */ + mcam->clk[i] = devm_clk_get(mcam->dev, mcam_clks[i]); + if (IS_ERR(mcam->clk[i])) + dev_warn(mcam->dev, "Could not get clk: %s\n", + mcam_clks[i]); + } + } +} + +static int mmpcam_probe(struct platform_device *pdev) +{ + struct mmp_camera *cam; + struct mcam_camera *mcam; + struct resource *res; + struct fwnode_handle *ep; + struct mmp_camera_platform_data *pdata; + struct v4l2_async_subdev *asd; + int ret; + + cam = devm_kzalloc(&pdev->dev, sizeof(*cam), GFP_KERNEL); + if (cam == NULL) + return -ENOMEM; + platform_set_drvdata(pdev, cam); + cam->pdev = pdev; + INIT_LIST_HEAD(&cam->devlist); + + mcam = &cam->mcam; + mcam->calc_dphy = mmpcam_calc_dphy; + mcam->dev = &pdev->dev; + pdata = pdev->dev.platform_data; + if (pdata) { + mcam->mclk_src = pdata->mclk_src; + mcam->mclk_div = pdata->mclk_div; + mcam->bus_type = pdata->bus_type; + mcam->dphy = pdata->dphy; + mcam->lane = pdata->lane; + } else { + /* + * These are values that used to be hardcoded in mcam-core and + * work well on a OLPC XO 1.75 with a parallel bus sensor. + * If it turns out other setups make sense, the values should + * be obtained from the device tree. + */ + mcam->mclk_src = 3; + mcam->mclk_div = 2; + } + if (mcam->bus_type == V4L2_MBUS_CSI2_DPHY) { + cam->mipi_clk = devm_clk_get(mcam->dev, "mipi"); + if ((IS_ERR(cam->mipi_clk) && mcam->dphy[2] == 0)) + return PTR_ERR(cam->mipi_clk); + } + mcam->mipi_enabled = false; + mcam->chip_id = MCAM_ARMADA610; + mcam->buffer_mode = B_DMA_sg; + strscpy(mcam->bus_info, "platform:mmp-camera", sizeof(mcam->bus_info)); + spin_lock_init(&mcam->dev_lock); + /* + * Get our I/O memory. + */ + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + mcam->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(mcam->regs)) + return PTR_ERR(mcam->regs); + mcam->regs_size = resource_size(res); + + mcam_init_clk(mcam); + + /* + * Create a match of the sensor against its OF node. + */ + ep = fwnode_graph_get_next_endpoint(of_fwnode_handle(pdev->dev.of_node), + NULL); + if (!ep) + return -ENODEV; + + v4l2_async_nf_init(&mcam->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&mcam->notifier, ep, + struct v4l2_async_subdev); + fwnode_handle_put(ep); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out; + } + + /* + * Register the device with the core. + */ + ret = mccic_register(mcam); + if (ret) + return ret; + + /* + * Add OF clock provider. + */ + ret = of_clk_add_provider(pdev->dev.of_node, of_clk_src_simple_get, + mcam->mclk); + if (ret) { + dev_err(&pdev->dev, "can't add DT clock provider\n"); + goto out; + } + + /* + * Finally, set up our IRQ now that the core is ready to + * deal with it. + */ + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto out; + cam->irq = ret; + ret = devm_request_irq(&pdev->dev, cam->irq, mmpcam_irq, IRQF_SHARED, + "mmp-camera", mcam); + if (ret) + goto out; + + pm_runtime_enable(&pdev->dev); + return 0; +out: + mccic_shutdown(mcam); + + return ret; +} + + +static int mmpcam_remove(struct mmp_camera *cam) +{ + struct mcam_camera *mcam = &cam->mcam; + + mccic_shutdown(mcam); + pm_runtime_force_suspend(mcam->dev); + return 0; +} + +static int mmpcam_platform_remove(struct platform_device *pdev) +{ + struct mmp_camera *cam = platform_get_drvdata(pdev); + + if (cam == NULL) + return -ENODEV; + return mmpcam_remove(cam); +} + +/* + * Suspend/resume support. + */ + +static int __maybe_unused mmpcam_runtime_resume(struct device *dev) +{ + struct mmp_camera *cam = dev_get_drvdata(dev); + struct mcam_camera *mcam = &cam->mcam; + unsigned int i; + + for (i = 0; i < NR_MCAM_CLK; i++) { + if (!IS_ERR(mcam->clk[i])) + clk_prepare_enable(mcam->clk[i]); + } + + return 0; +} + +static int __maybe_unused mmpcam_runtime_suspend(struct device *dev) +{ + struct mmp_camera *cam = dev_get_drvdata(dev); + struct mcam_camera *mcam = &cam->mcam; + int i; + + for (i = NR_MCAM_CLK - 1; i >= 0; i--) { + if (!IS_ERR(mcam->clk[i])) + clk_disable_unprepare(mcam->clk[i]); + } + + return 0; +} + +static int __maybe_unused mmpcam_suspend(struct device *dev) +{ + struct mmp_camera *cam = dev_get_drvdata(dev); + + if (!pm_runtime_suspended(dev)) + mccic_suspend(&cam->mcam); + return 0; +} + +static int __maybe_unused mmpcam_resume(struct device *dev) +{ + struct mmp_camera *cam = dev_get_drvdata(dev); + + if (!pm_runtime_suspended(dev)) + return mccic_resume(&cam->mcam); + return 0; +} + +static const struct dev_pm_ops mmpcam_pm_ops = { + SET_RUNTIME_PM_OPS(mmpcam_runtime_suspend, mmpcam_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(mmpcam_suspend, mmpcam_resume) +}; + +static const struct of_device_id mmpcam_of_match[] = { + { .compatible = "marvell,mmp2-ccic", }, + {}, +}; +MODULE_DEVICE_TABLE(of, mmpcam_of_match); + +static struct platform_driver mmpcam_driver = { + .probe = mmpcam_probe, + .remove = mmpcam_platform_remove, + .driver = { + .name = "mmp-camera", + .of_match_table = of_match_ptr(mmpcam_of_match), + .pm = &mmpcam_pm_ops, + } +}; + +module_platform_driver(mmpcam_driver); diff --git a/drivers/media/platform/mediatek/Kconfig b/drivers/media/platform/mediatek/Kconfig new file mode 100644 index 000000000..84104e2cd --- /dev/null +++ b/drivers/media/platform/mediatek/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Mediatek media platform drivers" + +source "drivers/media/platform/mediatek/jpeg/Kconfig" +source "drivers/media/platform/mediatek/mdp/Kconfig" +source "drivers/media/platform/mediatek/vcodec/Kconfig" +source "drivers/media/platform/mediatek/vpu/Kconfig" +source "drivers/media/platform/mediatek/mdp3/Kconfig" diff --git a/drivers/media/platform/mediatek/Makefile b/drivers/media/platform/mediatek/Makefile new file mode 100644 index 000000000..38e6ba917 --- /dev/null +++ b/drivers/media/platform/mediatek/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += jpeg/ +obj-y += mdp/ +obj-y += vcodec/ +obj-y += vpu/ +obj-y += mdp3/ diff --git a/drivers/media/platform/mediatek/jpeg/Kconfig b/drivers/media/platform/mediatek/jpeg/Kconfig new file mode 100644 index 000000000..39c4d1bc6 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_MEDIATEK_JPEG + tristate "Mediatek JPEG Codec driver" + depends on V4L_MEM2MEM_DRIVERS + depends on MTK_IOMMU_V1 || MTK_IOMMU || COMPILE_TEST + depends on VIDEO_DEV + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on MTK_SMI || (COMPILE_TEST && MTK_SMI=n) + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + Mediatek jpeg codec driver provides HW capability to decode + JPEG format + + To compile this driver as a module, choose M here: the + module will be called mtk-jpeg diff --git a/drivers/media/platform/mediatek/jpeg/Makefile b/drivers/media/platform/mediatek/jpeg/Makefile new file mode 100644 index 000000000..76c33aad0 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +mtk_jpeg-objs := mtk_jpeg_core.o \ + mtk_jpeg_dec_hw.o \ + mtk_jpeg_dec_parse.o \ + mtk_jpeg_enc_hw.o +obj-$(CONFIG_VIDEO_MEDIATEK_JPEG) += mtk_jpeg.o diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c new file mode 100644 index 000000000..f911d3c7d --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.c @@ -0,0 +1,1528 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + * Rick Chang + * Xia Jiang + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_jpeg_enc_hw.h" +#include "mtk_jpeg_dec_hw.h" +#include "mtk_jpeg_core.h" +#include "mtk_jpeg_dec_parse.h" + +static struct mtk_jpeg_fmt mtk_jpeg_enc_formats[] = { + { + .fourcc = V4L2_PIX_FMT_JPEG, + .colplanes = 1, + .flags = MTK_JPEG_FMT_FLAG_CAPTURE, + }, + { + .fourcc = V4L2_PIX_FMT_NV12M, + .hw_format = JPEG_ENC_YUV_FORMAT_NV12, + .h_sample = {4, 4}, + .v_sample = {4, 2}, + .colplanes = 2, + .h_align = 4, + .v_align = 4, + .flags = MTK_JPEG_FMT_FLAG_OUTPUT, + }, + { + .fourcc = V4L2_PIX_FMT_NV21M, + .hw_format = JEPG_ENC_YUV_FORMAT_NV21, + .h_sample = {4, 4}, + .v_sample = {4, 2}, + .colplanes = 2, + .h_align = 4, + .v_align = 4, + .flags = MTK_JPEG_FMT_FLAG_OUTPUT, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .hw_format = JPEG_ENC_YUV_FORMAT_YUYV, + .h_sample = {8}, + .v_sample = {4}, + .colplanes = 1, + .h_align = 5, + .v_align = 3, + .flags = MTK_JPEG_FMT_FLAG_OUTPUT, + }, + { + .fourcc = V4L2_PIX_FMT_YVYU, + .hw_format = JPEG_ENC_YUV_FORMAT_YVYU, + .h_sample = {8}, + .v_sample = {4}, + .colplanes = 1, + .h_align = 5, + .v_align = 3, + .flags = MTK_JPEG_FMT_FLAG_OUTPUT, + }, +}; + +static struct mtk_jpeg_fmt mtk_jpeg_dec_formats[] = { + { + .fourcc = V4L2_PIX_FMT_JPEG, + .colplanes = 1, + .flags = MTK_JPEG_FMT_FLAG_OUTPUT, + }, + { + .fourcc = V4L2_PIX_FMT_YUV420M, + .h_sample = {4, 2, 2}, + .v_sample = {4, 2, 2}, + .colplanes = 3, + .h_align = 5, + .v_align = 4, + .flags = MTK_JPEG_FMT_FLAG_CAPTURE, + }, + { + .fourcc = V4L2_PIX_FMT_YUV422M, + .h_sample = {4, 2, 2}, + .v_sample = {4, 4, 4}, + .colplanes = 3, + .h_align = 5, + .v_align = 3, + .flags = MTK_JPEG_FMT_FLAG_CAPTURE, + }, +}; + +#define MTK_JPEG_ENC_NUM_FORMATS ARRAY_SIZE(mtk_jpeg_enc_formats) +#define MTK_JPEG_DEC_NUM_FORMATS ARRAY_SIZE(mtk_jpeg_dec_formats) + +struct mtk_jpeg_src_buf { + struct vb2_v4l2_buffer b; + struct list_head list; + struct mtk_jpeg_dec_param dec_param; +}; + +static int debug; +module_param(debug, int, 0644); + +static inline struct mtk_jpeg_ctx *ctrl_to_ctx(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mtk_jpeg_ctx, ctrl_hdl); +} + +static inline struct mtk_jpeg_ctx *mtk_jpeg_fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct mtk_jpeg_ctx, fh); +} + +static inline struct mtk_jpeg_src_buf *mtk_jpeg_vb2_to_srcbuf( + struct vb2_buffer *vb) +{ + return container_of(to_vb2_v4l2_buffer(vb), struct mtk_jpeg_src_buf, b); +} + +static int mtk_jpeg_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mtk_jpeg_dev *jpeg = video_drvdata(file); + + strscpy(cap->driver, jpeg->variant->dev_name, sizeof(cap->driver)); + strscpy(cap->card, jpeg->variant->dev_name, sizeof(cap->card)); + + return 0; +} + +static int vidioc_jpeg_enc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mtk_jpeg_ctx *ctx = ctrl_to_ctx(ctrl); + + switch (ctrl->id) { + case V4L2_CID_JPEG_RESTART_INTERVAL: + ctx->restart_interval = ctrl->val; + break; + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + ctx->enc_quality = ctrl->val; + break; + case V4L2_CID_JPEG_ACTIVE_MARKER: + ctx->enable_exif = ctrl->val & V4L2_JPEG_ACTIVE_MARKER_APP1; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops mtk_jpeg_enc_ctrl_ops = { + .s_ctrl = vidioc_jpeg_enc_s_ctrl, +}; + +static int mtk_jpeg_enc_ctrls_setup(struct mtk_jpeg_ctx *ctx) +{ + const struct v4l2_ctrl_ops *ops = &mtk_jpeg_enc_ctrl_ops; + struct v4l2_ctrl_handler *handler = &ctx->ctrl_hdl; + + v4l2_ctrl_handler_init(handler, 3); + + v4l2_ctrl_new_std(handler, ops, V4L2_CID_JPEG_RESTART_INTERVAL, 0, 100, + 1, 0); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_JPEG_COMPRESSION_QUALITY, 48, + 100, 1, 90); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_JPEG_ACTIVE_MARKER, 0, + V4L2_JPEG_ACTIVE_MARKER_APP1, 0, 0); + + if (handler->error) { + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + return handler->error; + } + + v4l2_ctrl_handler_setup(&ctx->ctrl_hdl); + + return 0; +} + +static int mtk_jpeg_enum_fmt(struct mtk_jpeg_fmt *mtk_jpeg_formats, int n, + struct v4l2_fmtdesc *f, u32 type) +{ + int i, num = 0; + + for (i = 0; i < n; ++i) { + if (mtk_jpeg_formats[i].flags & type) { + if (num == f->index) + break; + ++num; + } + } + + if (i >= n) + return -EINVAL; + + f->pixelformat = mtk_jpeg_formats[i].fourcc; + + return 0; +} + +static int mtk_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + return mtk_jpeg_enum_fmt(jpeg->variant->formats, + jpeg->variant->num_formats, f, + MTK_JPEG_FMT_FLAG_CAPTURE); +} + +static int mtk_jpeg_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + return mtk_jpeg_enum_fmt(jpeg->variant->formats, + jpeg->variant->num_formats, f, + MTK_JPEG_FMT_FLAG_OUTPUT); +} + +static struct mtk_jpeg_q_data *mtk_jpeg_get_q_data(struct mtk_jpeg_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->out_q; + return &ctx->cap_q; +} + +static struct mtk_jpeg_fmt * +mtk_jpeg_find_format(struct mtk_jpeg_fmt *mtk_jpeg_formats, int num_formats, + u32 pixelformat, unsigned int fmt_type) +{ + unsigned int k; + struct mtk_jpeg_fmt *fmt; + + for (k = 0; k < num_formats; k++) { + fmt = &mtk_jpeg_formats[k]; + + if (fmt->fourcc == pixelformat && fmt->flags & fmt_type) + return fmt; + } + + return NULL; +} + +static int mtk_jpeg_try_fmt_mplane(struct v4l2_pix_format_mplane *pix_mp, + struct mtk_jpeg_fmt *fmt) +{ + int i; + + pix_mp->field = V4L2_FIELD_NONE; + + pix_mp->num_planes = fmt->colplanes; + pix_mp->pixelformat = fmt->fourcc; + + if (fmt->fourcc == V4L2_PIX_FMT_JPEG) { + struct v4l2_plane_pix_format *pfmt = &pix_mp->plane_fmt[0]; + + pix_mp->height = clamp(pix_mp->height, MTK_JPEG_MIN_HEIGHT, + MTK_JPEG_MAX_HEIGHT); + pix_mp->width = clamp(pix_mp->width, MTK_JPEG_MIN_WIDTH, + MTK_JPEG_MAX_WIDTH); + + pfmt->bytesperline = 0; + /* Source size must be aligned to 128 */ + pfmt->sizeimage = round_up(pfmt->sizeimage, 128); + if (pfmt->sizeimage == 0) + pfmt->sizeimage = MTK_JPEG_DEFAULT_SIZEIMAGE; + return 0; + } + + /* other fourcc */ + pix_mp->height = clamp(round_up(pix_mp->height, fmt->v_align), + MTK_JPEG_MIN_HEIGHT, MTK_JPEG_MAX_HEIGHT); + pix_mp->width = clamp(round_up(pix_mp->width, fmt->h_align), + MTK_JPEG_MIN_WIDTH, MTK_JPEG_MAX_WIDTH); + + for (i = 0; i < fmt->colplanes; i++) { + struct v4l2_plane_pix_format *pfmt = &pix_mp->plane_fmt[i]; + u32 stride = pix_mp->width * fmt->h_sample[i] / 4; + u32 h = pix_mp->height * fmt->v_sample[i] / 4; + + pfmt->bytesperline = stride; + pfmt->sizeimage = stride * h; + } + return 0; +} + +static int mtk_jpeg_g_fmt_vid_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct mtk_jpeg_q_data *q_data = NULL; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + int i; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = mtk_jpeg_get_q_data(ctx, f->type); + + pix_mp->width = q_data->pix_mp.width; + pix_mp->height = q_data->pix_mp.height; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->pixelformat = q_data->fmt->fourcc; + pix_mp->num_planes = q_data->fmt->colplanes; + pix_mp->colorspace = q_data->pix_mp.colorspace; + pix_mp->ycbcr_enc = q_data->pix_mp.ycbcr_enc; + pix_mp->xfer_func = q_data->pix_mp.xfer_func; + pix_mp->quantization = q_data->pix_mp.quantization; + + v4l2_dbg(1, debug, &jpeg->v4l2_dev, "(%d) g_fmt:%c%c%c%c wxh:%ux%u\n", + f->type, + (pix_mp->pixelformat & 0xff), + (pix_mp->pixelformat >> 8 & 0xff), + (pix_mp->pixelformat >> 16 & 0xff), + (pix_mp->pixelformat >> 24 & 0xff), + pix_mp->width, pix_mp->height); + + for (i = 0; i < pix_mp->num_planes; i++) { + struct v4l2_plane_pix_format *pfmt = &pix_mp->plane_fmt[i]; + + pfmt->bytesperline = q_data->pix_mp.plane_fmt[i].bytesperline; + pfmt->sizeimage = q_data->pix_mp.plane_fmt[i].sizeimage; + + v4l2_dbg(1, debug, &jpeg->v4l2_dev, + "plane[%d] bpl=%u, size=%u\n", + i, + pfmt->bytesperline, + pfmt->sizeimage); + } + return 0; +} + +static int mtk_jpeg_try_fmt_vid_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + struct mtk_jpeg_fmt *fmt; + + fmt = mtk_jpeg_find_format(jpeg->variant->formats, + jpeg->variant->num_formats, + f->fmt.pix_mp.pixelformat, + MTK_JPEG_FMT_FLAG_CAPTURE); + if (!fmt) + fmt = ctx->cap_q.fmt; + + v4l2_dbg(2, debug, &ctx->jpeg->v4l2_dev, "(%d) try_fmt:%c%c%c%c\n", + f->type, + (fmt->fourcc & 0xff), + (fmt->fourcc >> 8 & 0xff), + (fmt->fourcc >> 16 & 0xff), + (fmt->fourcc >> 24 & 0xff)); + + if (ctx->state != MTK_JPEG_INIT) { + mtk_jpeg_g_fmt_vid_mplane(file, priv, f); + return 0; + } + + return mtk_jpeg_try_fmt_mplane(&f->fmt.pix_mp, fmt); +} + +static int mtk_jpeg_try_fmt_vid_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + struct mtk_jpeg_fmt *fmt; + + fmt = mtk_jpeg_find_format(jpeg->variant->formats, + jpeg->variant->num_formats, + f->fmt.pix_mp.pixelformat, + MTK_JPEG_FMT_FLAG_OUTPUT); + if (!fmt) + fmt = ctx->out_q.fmt; + + v4l2_dbg(2, debug, &ctx->jpeg->v4l2_dev, "(%d) try_fmt:%c%c%c%c\n", + f->type, + (fmt->fourcc & 0xff), + (fmt->fourcc >> 8 & 0xff), + (fmt->fourcc >> 16 & 0xff), + (fmt->fourcc >> 24 & 0xff)); + + if (ctx->state != MTK_JPEG_INIT) { + mtk_jpeg_g_fmt_vid_mplane(file, priv, f); + return 0; + } + + return mtk_jpeg_try_fmt_mplane(&f->fmt.pix_mp, fmt); +} + +static int mtk_jpeg_s_fmt_mplane(struct mtk_jpeg_ctx *ctx, + struct v4l2_format *f, unsigned int fmt_type) +{ + struct vb2_queue *vq; + struct mtk_jpeg_q_data *q_data = NULL; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + int i; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = mtk_jpeg_get_q_data(ctx, f->type); + + if (vb2_is_busy(vq)) { + v4l2_err(&jpeg->v4l2_dev, "queue busy\n"); + return -EBUSY; + } + + q_data->fmt = mtk_jpeg_find_format(jpeg->variant->formats, + jpeg->variant->num_formats, + pix_mp->pixelformat, fmt_type); + q_data->pix_mp.width = pix_mp->width; + q_data->pix_mp.height = pix_mp->height; + q_data->enc_crop_rect.width = pix_mp->width; + q_data->enc_crop_rect.height = pix_mp->height; + q_data->pix_mp.colorspace = V4L2_COLORSPACE_SRGB; + q_data->pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_601; + q_data->pix_mp.xfer_func = V4L2_XFER_FUNC_SRGB; + q_data->pix_mp.quantization = V4L2_QUANTIZATION_FULL_RANGE; + + v4l2_dbg(1, debug, &jpeg->v4l2_dev, "(%d) s_fmt:%c%c%c%c wxh:%ux%u\n", + f->type, + (q_data->fmt->fourcc & 0xff), + (q_data->fmt->fourcc >> 8 & 0xff), + (q_data->fmt->fourcc >> 16 & 0xff), + (q_data->fmt->fourcc >> 24 & 0xff), + q_data->pix_mp.width, q_data->pix_mp.height); + + for (i = 0; i < q_data->fmt->colplanes; i++) { + q_data->pix_mp.plane_fmt[i].bytesperline = + pix_mp->plane_fmt[i].bytesperline; + q_data->pix_mp.plane_fmt[i].sizeimage = + pix_mp->plane_fmt[i].sizeimage; + + v4l2_dbg(1, debug, &jpeg->v4l2_dev, + "plane[%d] bpl=%u, size=%u\n", + i, q_data->pix_mp.plane_fmt[i].bytesperline, + q_data->pix_mp.plane_fmt[i].sizeimage); + } + + return 0; +} + +static int mtk_jpeg_s_fmt_vid_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = mtk_jpeg_try_fmt_vid_out_mplane(file, priv, f); + if (ret) + return ret; + + return mtk_jpeg_s_fmt_mplane(mtk_jpeg_fh_to_ctx(priv), f, + MTK_JPEG_FMT_FLAG_OUTPUT); +} + +static int mtk_jpeg_s_fmt_vid_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = mtk_jpeg_try_fmt_vid_cap_mplane(file, priv, f); + if (ret) + return ret; + + return mtk_jpeg_s_fmt_mplane(mtk_jpeg_fh_to_ctx(priv), f, + MTK_JPEG_FMT_FLAG_CAPTURE); +} + +static void mtk_jpeg_queue_src_chg_event(struct mtk_jpeg_ctx *ctx) +{ + static const struct v4l2_event ev_src_ch = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = + V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + v4l2_event_queue_fh(&ctx->fh, &ev_src_ch); +} + +static int mtk_jpeg_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + } + + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static int mtk_jpeg_enc_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + s->r = ctx->out_q.enc_crop_rect; + break; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.width = ctx->out_q.pix_mp.width; + s->r.height = ctx->out_q.pix_mp.height; + s->r.left = 0; + s->r.top = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static int mtk_jpeg_dec_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r.width = ctx->out_q.pix_mp.width; + s->r.height = ctx->out_q.pix_mp.height; + s->r.left = 0; + s->r.top = 0; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_PADDED: + s->r.width = ctx->cap_q.pix_mp.width; + s->r.height = ctx->cap_q.pix_mp.height; + s->r.left = 0; + s->r.top = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static int mtk_jpeg_enc_s_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(priv); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + s->r.left = 0; + s->r.top = 0; + s->r.width = min(s->r.width, ctx->out_q.pix_mp.width); + s->r.height = min(s->r.height, ctx->out_q.pix_mp.height); + ctx->out_q.enc_crop_rect = s->r; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ioctl_ops mtk_jpeg_enc_ioctl_ops = { + .vidioc_querycap = mtk_jpeg_querycap, + .vidioc_enum_fmt_vid_cap = mtk_jpeg_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = mtk_jpeg_enum_fmt_vid_out, + .vidioc_try_fmt_vid_cap_mplane = mtk_jpeg_try_fmt_vid_cap_mplane, + .vidioc_try_fmt_vid_out_mplane = mtk_jpeg_try_fmt_vid_out_mplane, + .vidioc_g_fmt_vid_cap_mplane = mtk_jpeg_g_fmt_vid_mplane, + .vidioc_g_fmt_vid_out_mplane = mtk_jpeg_g_fmt_vid_mplane, + .vidioc_s_fmt_vid_cap_mplane = mtk_jpeg_s_fmt_vid_cap_mplane, + .vidioc_s_fmt_vid_out_mplane = mtk_jpeg_s_fmt_vid_out_mplane, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_subscribe_event = mtk_jpeg_subscribe_event, + .vidioc_g_selection = mtk_jpeg_enc_g_selection, + .vidioc_s_selection = mtk_jpeg_enc_s_selection, + + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_ioctl_ops mtk_jpeg_dec_ioctl_ops = { + .vidioc_querycap = mtk_jpeg_querycap, + .vidioc_enum_fmt_vid_cap = mtk_jpeg_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = mtk_jpeg_enum_fmt_vid_out, + .vidioc_try_fmt_vid_cap_mplane = mtk_jpeg_try_fmt_vid_cap_mplane, + .vidioc_try_fmt_vid_out_mplane = mtk_jpeg_try_fmt_vid_out_mplane, + .vidioc_g_fmt_vid_cap_mplane = mtk_jpeg_g_fmt_vid_mplane, + .vidioc_g_fmt_vid_out_mplane = mtk_jpeg_g_fmt_vid_mplane, + .vidioc_s_fmt_vid_cap_mplane = mtk_jpeg_s_fmt_vid_cap_mplane, + .vidioc_s_fmt_vid_out_mplane = mtk_jpeg_s_fmt_vid_out_mplane, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_subscribe_event = mtk_jpeg_subscribe_event, + .vidioc_g_selection = mtk_jpeg_dec_g_selection, + + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int mtk_jpeg_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_ctxs[]) +{ + struct mtk_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct mtk_jpeg_q_data *q_data = NULL; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + int i; + + v4l2_dbg(1, debug, &jpeg->v4l2_dev, "(%d) buf_req count=%u\n", + q->type, *num_buffers); + + q_data = mtk_jpeg_get_q_data(ctx, q->type); + if (!q_data) + return -EINVAL; + + if (*num_planes) { + for (i = 0; i < *num_planes; i++) + if (sizes[i] < q_data->pix_mp.plane_fmt[i].sizeimage) + return -EINVAL; + return 0; + } + + *num_planes = q_data->fmt->colplanes; + for (i = 0; i < q_data->fmt->colplanes; i++) { + sizes[i] = q_data->pix_mp.plane_fmt[i].sizeimage; + v4l2_dbg(1, debug, &jpeg->v4l2_dev, "sizeimage[%d]=%u\n", + i, sizes[i]); + } + + return 0; +} + +static int mtk_jpeg_buf_prepare(struct vb2_buffer *vb) +{ + struct mtk_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mtk_jpeg_q_data *q_data = NULL; + struct v4l2_plane_pix_format plane_fmt; + int i; + + q_data = mtk_jpeg_get_q_data(ctx, vb->vb2_queue->type); + if (!q_data) + return -EINVAL; + + for (i = 0; i < q_data->fmt->colplanes; i++) { + plane_fmt = q_data->pix_mp.plane_fmt[i]; + if (ctx->enable_exif && + q_data->fmt->fourcc == V4L2_PIX_FMT_JPEG) + vb2_set_plane_payload(vb, i, plane_fmt.sizeimage + + MTK_JPEG_MAX_EXIF_SIZE); + else + vb2_set_plane_payload(vb, i, plane_fmt.sizeimage); + } + + return 0; +} + +static bool mtk_jpeg_check_resolution_change(struct mtk_jpeg_ctx *ctx, + struct mtk_jpeg_dec_param *param) +{ + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + struct mtk_jpeg_q_data *q_data; + + q_data = &ctx->out_q; + if (q_data->pix_mp.width != param->pic_w || + q_data->pix_mp.height != param->pic_h) { + v4l2_dbg(1, debug, &jpeg->v4l2_dev, "Picture size change\n"); + return true; + } + + q_data = &ctx->cap_q; + if (q_data->fmt != + mtk_jpeg_find_format(jpeg->variant->formats, + jpeg->variant->num_formats, param->dst_fourcc, + MTK_JPEG_FMT_FLAG_CAPTURE)) { + v4l2_dbg(1, debug, &jpeg->v4l2_dev, "format change\n"); + return true; + } + return false; +} + +static void mtk_jpeg_set_queue_data(struct mtk_jpeg_ctx *ctx, + struct mtk_jpeg_dec_param *param) +{ + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + struct mtk_jpeg_q_data *q_data; + int i; + + q_data = &ctx->out_q; + q_data->pix_mp.width = param->pic_w; + q_data->pix_mp.height = param->pic_h; + + q_data = &ctx->cap_q; + q_data->pix_mp.width = param->dec_w; + q_data->pix_mp.height = param->dec_h; + q_data->fmt = mtk_jpeg_find_format(jpeg->variant->formats, + jpeg->variant->num_formats, + param->dst_fourcc, + MTK_JPEG_FMT_FLAG_CAPTURE); + + for (i = 0; i < q_data->fmt->colplanes; i++) { + q_data->pix_mp.plane_fmt[i].bytesperline = param->mem_stride[i]; + q_data->pix_mp.plane_fmt[i].sizeimage = param->comp_size[i]; + } + + v4l2_dbg(1, debug, &jpeg->v4l2_dev, + "set_parse cap:%c%c%c%c pic(%u, %u), buf(%u, %u)\n", + (param->dst_fourcc & 0xff), + (param->dst_fourcc >> 8 & 0xff), + (param->dst_fourcc >> 16 & 0xff), + (param->dst_fourcc >> 24 & 0xff), + param->pic_w, param->pic_h, + param->dec_w, param->dec_h); +} + +static void mtk_jpeg_enc_buf_queue(struct vb2_buffer *vb) +{ + struct mtk_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + v4l2_dbg(2, debug, &jpeg->v4l2_dev, "(%d) buf_q id=%d, vb=%p\n", + vb->vb2_queue->type, vb->index, vb); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, to_vb2_v4l2_buffer(vb)); +} + +static void mtk_jpeg_dec_buf_queue(struct vb2_buffer *vb) +{ + struct mtk_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mtk_jpeg_dec_param *param; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + struct mtk_jpeg_src_buf *jpeg_src_buf; + bool header_valid; + + v4l2_dbg(2, debug, &jpeg->v4l2_dev, "(%d) buf_q id=%d, vb=%p\n", + vb->vb2_queue->type, vb->index, vb); + + if (vb->vb2_queue->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + goto end; + + jpeg_src_buf = mtk_jpeg_vb2_to_srcbuf(vb); + param = &jpeg_src_buf->dec_param; + memset(param, 0, sizeof(*param)); + + header_valid = mtk_jpeg_parse(param, (u8 *)vb2_plane_vaddr(vb, 0), + vb2_get_plane_payload(vb, 0)); + if (!header_valid) { + v4l2_err(&jpeg->v4l2_dev, "Header invalid.\n"); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + return; + } + + if (ctx->state == MTK_JPEG_INIT) { + struct vb2_queue *dst_vq = v4l2_m2m_get_vq( + ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + mtk_jpeg_queue_src_chg_event(ctx); + mtk_jpeg_set_queue_data(ctx, param); + ctx->state = vb2_is_streaming(dst_vq) ? + MTK_JPEG_SOURCE_CHANGE : MTK_JPEG_RUNNING; + } +end: + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, to_vb2_v4l2_buffer(vb)); +} + +static struct vb2_v4l2_buffer *mtk_jpeg_buf_remove(struct mtk_jpeg_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + return v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); +} + +static void mtk_jpeg_enc_stop_streaming(struct vb2_queue *q) +{ + struct mtk_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vb; + + while ((vb = mtk_jpeg_buf_remove(ctx, q->type))) + v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR); +} + +static void mtk_jpeg_dec_stop_streaming(struct vb2_queue *q) +{ + struct mtk_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vb; + + /* + * STREAMOFF is an acknowledgment for source change event. + * Before STREAMOFF, we still have to return the old resolution and + * subsampling. Update capture queue when the stream is off. + */ + if (ctx->state == MTK_JPEG_SOURCE_CHANGE && + V4L2_TYPE_IS_CAPTURE(q->type)) { + struct mtk_jpeg_src_buf *src_buf; + + vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + src_buf = mtk_jpeg_vb2_to_srcbuf(&vb->vb2_buf); + mtk_jpeg_set_queue_data(ctx, &src_buf->dec_param); + ctx->state = MTK_JPEG_RUNNING; + } else if (V4L2_TYPE_IS_OUTPUT(q->type)) { + ctx->state = MTK_JPEG_INIT; + } + + while ((vb = mtk_jpeg_buf_remove(ctx, q->type))) + v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops mtk_jpeg_dec_qops = { + .queue_setup = mtk_jpeg_queue_setup, + .buf_prepare = mtk_jpeg_buf_prepare, + .buf_queue = mtk_jpeg_dec_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = mtk_jpeg_dec_stop_streaming, +}; + +static const struct vb2_ops mtk_jpeg_enc_qops = { + .queue_setup = mtk_jpeg_queue_setup, + .buf_prepare = mtk_jpeg_buf_prepare, + .buf_queue = mtk_jpeg_enc_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = mtk_jpeg_enc_stop_streaming, +}; + +static void mtk_jpeg_set_dec_src(struct mtk_jpeg_ctx *ctx, + struct vb2_buffer *src_buf, + struct mtk_jpeg_bs *bs) +{ + bs->str_addr = vb2_dma_contig_plane_dma_addr(src_buf, 0); + bs->end_addr = bs->str_addr + + round_up(vb2_get_plane_payload(src_buf, 0), 16); + bs->size = round_up(vb2_plane_size(src_buf, 0), 128); +} + +static int mtk_jpeg_set_dec_dst(struct mtk_jpeg_ctx *ctx, + struct mtk_jpeg_dec_param *param, + struct vb2_buffer *dst_buf, + struct mtk_jpeg_fb *fb) +{ + int i; + + if (param->comp_num != dst_buf->num_planes) { + dev_err(ctx->jpeg->dev, "plane number mismatch (%u != %u)\n", + param->comp_num, dst_buf->num_planes); + return -EINVAL; + } + + for (i = 0; i < dst_buf->num_planes; i++) { + if (vb2_plane_size(dst_buf, i) < param->comp_size[i]) { + dev_err(ctx->jpeg->dev, + "buffer size is underflow (%lu < %u)\n", + vb2_plane_size(dst_buf, 0), + param->comp_size[i]); + return -EINVAL; + } + fb->plane_addr[i] = vb2_dma_contig_plane_dma_addr(dst_buf, i); + } + + return 0; +} + +static void mtk_jpeg_enc_device_run(void *priv) +{ + struct mtk_jpeg_ctx *ctx = priv; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + unsigned long flags; + int ret; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + ret = pm_runtime_resume_and_get(jpeg->dev); + if (ret < 0) + goto enc_end; + + schedule_delayed_work(&jpeg->job_timeout_work, + msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); + + spin_lock_irqsave(&jpeg->hw_lock, flags); + + /* + * Resetting the hardware every frame is to ensure that all the + * registers are cleared. This is a hardware requirement. + */ + mtk_jpeg_enc_reset(jpeg->reg_base); + + mtk_jpeg_set_enc_src(ctx, jpeg->reg_base, &src_buf->vb2_buf); + mtk_jpeg_set_enc_dst(ctx, jpeg->reg_base, &dst_buf->vb2_buf); + mtk_jpeg_set_enc_params(ctx, jpeg->reg_base); + mtk_jpeg_enc_start(jpeg->reg_base); + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + return; + +enc_end: + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); +} + +static void mtk_jpeg_dec_device_run(void *priv) +{ + struct mtk_jpeg_ctx *ctx = priv; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + unsigned long flags; + struct mtk_jpeg_src_buf *jpeg_src_buf; + struct mtk_jpeg_bs bs; + struct mtk_jpeg_fb fb; + int ret; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + jpeg_src_buf = mtk_jpeg_vb2_to_srcbuf(&src_buf->vb2_buf); + + if (mtk_jpeg_check_resolution_change(ctx, &jpeg_src_buf->dec_param)) { + mtk_jpeg_queue_src_chg_event(ctx); + ctx->state = MTK_JPEG_SOURCE_CHANGE; + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return; + } + + ret = pm_runtime_resume_and_get(jpeg->dev); + if (ret < 0) + goto dec_end; + + mtk_jpeg_set_dec_src(ctx, &src_buf->vb2_buf, &bs); + if (mtk_jpeg_set_dec_dst(ctx, &jpeg_src_buf->dec_param, &dst_buf->vb2_buf, &fb)) + goto dec_end; + + schedule_delayed_work(&jpeg->job_timeout_work, + msecs_to_jiffies(MTK_JPEG_HW_TIMEOUT_MSEC)); + + spin_lock_irqsave(&jpeg->hw_lock, flags); + mtk_jpeg_dec_reset(jpeg->reg_base); + mtk_jpeg_dec_set_config(jpeg->reg_base, + &jpeg_src_buf->dec_param, &bs, &fb); + + mtk_jpeg_dec_start(jpeg->reg_base); + spin_unlock_irqrestore(&jpeg->hw_lock, flags); + return; + +dec_end: + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); +} + +static int mtk_jpeg_dec_job_ready(void *priv) +{ + struct mtk_jpeg_ctx *ctx = priv; + + return (ctx->state == MTK_JPEG_RUNNING) ? 1 : 0; +} + +static const struct v4l2_m2m_ops mtk_jpeg_enc_m2m_ops = { + .device_run = mtk_jpeg_enc_device_run, +}; + +static const struct v4l2_m2m_ops mtk_jpeg_dec_m2m_ops = { + .device_run = mtk_jpeg_dec_device_run, + .job_ready = mtk_jpeg_dec_job_ready, +}; + +static int mtk_jpeg_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mtk_jpeg_ctx *ctx = priv; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_DMABUF | VB2_MMAP; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct mtk_jpeg_src_buf); + src_vq->ops = jpeg->variant->qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->jpeg->lock; + src_vq->dev = ctx->jpeg->dev; + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_DMABUF | VB2_MMAP; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = jpeg->variant->qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->jpeg->lock; + dst_vq->dev = ctx->jpeg->dev; + ret = vb2_queue_init(dst_vq); + + return ret; +} + +static void mtk_jpeg_clk_on(struct mtk_jpeg_dev *jpeg) +{ + int ret; + + ret = clk_bulk_prepare_enable(jpeg->variant->num_clks, + jpeg->variant->clks); + if (ret) + dev_err(jpeg->dev, "Failed to open jpeg clk: %d\n", ret); +} + +static void mtk_jpeg_clk_off(struct mtk_jpeg_dev *jpeg) +{ + clk_bulk_disable_unprepare(jpeg->variant->num_clks, + jpeg->variant->clks); +} + +static irqreturn_t mtk_jpeg_enc_done(struct mtk_jpeg_dev *jpeg) +{ + struct mtk_jpeg_ctx *ctx; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + u32 result_size; + + ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + if (!ctx) { + v4l2_err(&jpeg->v4l2_dev, "Context is NULL\n"); + return IRQ_HANDLED; + } + + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + result_size = mtk_jpeg_enc_get_file_size(jpeg->reg_base); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, result_size); + + buf_state = VB2_BUF_STATE_DONE; + + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + pm_runtime_put(ctx->jpeg->dev); + return IRQ_HANDLED; +} + +static irqreturn_t mtk_jpeg_enc_irq(int irq, void *priv) +{ + struct mtk_jpeg_dev *jpeg = priv; + u32 irq_status; + irqreturn_t ret = IRQ_NONE; + + cancel_delayed_work(&jpeg->job_timeout_work); + + irq_status = readl(jpeg->reg_base + JPEG_ENC_INT_STS) & + JPEG_ENC_INT_STATUS_MASK_ALLIRQ; + if (irq_status) + writel(0, jpeg->reg_base + JPEG_ENC_INT_STS); + + if (!(irq_status & JPEG_ENC_INT_STATUS_DONE)) + return ret; + + ret = mtk_jpeg_enc_done(jpeg); + return ret; +} + +static irqreturn_t mtk_jpeg_dec_irq(int irq, void *priv) +{ + struct mtk_jpeg_dev *jpeg = priv; + struct mtk_jpeg_ctx *ctx; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mtk_jpeg_src_buf *jpeg_src_buf; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + u32 dec_irq_ret; + u32 dec_ret; + int i; + + cancel_delayed_work(&jpeg->job_timeout_work); + + dec_ret = mtk_jpeg_dec_get_int_status(jpeg->reg_base); + dec_irq_ret = mtk_jpeg_dec_enum_result(dec_ret); + ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + if (!ctx) { + v4l2_err(&jpeg->v4l2_dev, "Context is NULL\n"); + return IRQ_HANDLED; + } + + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + jpeg_src_buf = mtk_jpeg_vb2_to_srcbuf(&src_buf->vb2_buf); + + if (dec_irq_ret >= MTK_JPEG_DEC_RESULT_UNDERFLOW) + mtk_jpeg_dec_reset(jpeg->reg_base); + + if (dec_irq_ret != MTK_JPEG_DEC_RESULT_EOF_DONE) { + dev_err(jpeg->dev, "decode failed\n"); + goto dec_end; + } + + for (i = 0; i < dst_buf->vb2_buf.num_planes; i++) + vb2_set_plane_payload(&dst_buf->vb2_buf, i, + jpeg_src_buf->dec_param.comp_size[i]); + + buf_state = VB2_BUF_STATE_DONE; + +dec_end: + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + pm_runtime_put(ctx->jpeg->dev); + return IRQ_HANDLED; +} + +static void mtk_jpeg_set_default_params(struct mtk_jpeg_ctx *ctx) +{ + struct mtk_jpeg_q_data *q = &ctx->out_q; + struct mtk_jpeg_dev *jpeg = ctx->jpeg; + + ctx->fh.ctrl_handler = &ctx->ctrl_hdl; + q->pix_mp.colorspace = V4L2_COLORSPACE_SRGB; + q->pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_601; + q->pix_mp.quantization = V4L2_QUANTIZATION_FULL_RANGE; + q->pix_mp.xfer_func = V4L2_XFER_FUNC_SRGB; + + q->fmt = mtk_jpeg_find_format(jpeg->variant->formats, + jpeg->variant->num_formats, + jpeg->variant->out_q_default_fourcc, + MTK_JPEG_FMT_FLAG_OUTPUT); + q->pix_mp.width = MTK_JPEG_MIN_WIDTH; + q->pix_mp.height = MTK_JPEG_MIN_HEIGHT; + mtk_jpeg_try_fmt_mplane(&q->pix_mp, q->fmt); + + q = &ctx->cap_q; + q->fmt = mtk_jpeg_find_format(jpeg->variant->formats, + jpeg->variant->num_formats, + jpeg->variant->cap_q_default_fourcc, + MTK_JPEG_FMT_FLAG_CAPTURE); + q->pix_mp.colorspace = V4L2_COLORSPACE_SRGB; + q->pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_601; + q->pix_mp.quantization = V4L2_QUANTIZATION_FULL_RANGE; + q->pix_mp.xfer_func = V4L2_XFER_FUNC_SRGB; + q->pix_mp.width = MTK_JPEG_MIN_WIDTH; + q->pix_mp.height = MTK_JPEG_MIN_HEIGHT; + + mtk_jpeg_try_fmt_mplane(&q->pix_mp, q->fmt); +} + +static int mtk_jpeg_open(struct file *file) +{ + struct mtk_jpeg_dev *jpeg = video_drvdata(file); + struct video_device *vfd = video_devdata(file); + struct mtk_jpeg_ctx *ctx; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (mutex_lock_interruptible(&jpeg->lock)) { + ret = -ERESTARTSYS; + goto free; + } + + v4l2_fh_init(&ctx->fh, vfd); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->jpeg = jpeg; + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(jpeg->m2m_dev, ctx, + mtk_jpeg_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto error; + } + + if (jpeg->variant->cap_q_default_fourcc == V4L2_PIX_FMT_JPEG) { + ret = mtk_jpeg_enc_ctrls_setup(ctx); + if (ret) { + v4l2_err(&jpeg->v4l2_dev, "Failed to setup jpeg enc controls\n"); + goto error; + } + } else { + v4l2_ctrl_handler_init(&ctx->ctrl_hdl, 0); + } + mtk_jpeg_set_default_params(ctx); + mutex_unlock(&jpeg->lock); + return 0; + +error: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + mutex_unlock(&jpeg->lock); +free: + kfree(ctx); + return ret; +} + +static int mtk_jpeg_release(struct file *file) +{ + struct mtk_jpeg_dev *jpeg = video_drvdata(file); + struct mtk_jpeg_ctx *ctx = mtk_jpeg_fh_to_ctx(file->private_data); + + mutex_lock(&jpeg->lock); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&jpeg->lock); + return 0; +} + +static const struct v4l2_file_operations mtk_jpeg_fops = { + .owner = THIS_MODULE, + .open = mtk_jpeg_open, + .release = mtk_jpeg_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static struct clk_bulk_data mt8173_jpeg_dec_clocks[] = { + { .id = "jpgdec-smi" }, + { .id = "jpgdec" }, +}; + +static struct clk_bulk_data mtk_jpeg_clocks[] = { + { .id = "jpgenc" }, +}; + +static void mtk_jpeg_job_timeout_work(struct work_struct *work) +{ + struct mtk_jpeg_dev *jpeg = container_of(work, struct mtk_jpeg_dev, + job_timeout_work.work); + struct mtk_jpeg_ctx *ctx; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + jpeg->variant->hw_reset(jpeg->reg_base); + + pm_runtime_put(jpeg->dev); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); +} + +static int mtk_jpeg_probe(struct platform_device *pdev) +{ + struct mtk_jpeg_dev *jpeg; + int jpeg_irq; + int ret; + + jpeg = devm_kzalloc(&pdev->dev, sizeof(*jpeg), GFP_KERNEL); + if (!jpeg) + return -ENOMEM; + + mutex_init(&jpeg->lock); + spin_lock_init(&jpeg->hw_lock); + jpeg->dev = &pdev->dev; + jpeg->variant = of_device_get_match_data(jpeg->dev); + INIT_DELAYED_WORK(&jpeg->job_timeout_work, mtk_jpeg_job_timeout_work); + + jpeg->reg_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(jpeg->reg_base)) { + ret = PTR_ERR(jpeg->reg_base); + return ret; + } + + jpeg_irq = platform_get_irq(pdev, 0); + if (jpeg_irq < 0) + return jpeg_irq; + + ret = devm_request_irq(&pdev->dev, jpeg_irq, + jpeg->variant->irq_handler, 0, pdev->name, jpeg); + if (ret) { + dev_err(&pdev->dev, "Failed to request jpeg_irq %d (%d)\n", + jpeg_irq, ret); + goto err_req_irq; + } + + ret = devm_clk_bulk_get(jpeg->dev, jpeg->variant->num_clks, + jpeg->variant->clks); + if (ret) { + dev_err(&pdev->dev, "Failed to init clk, err %d\n", ret); + goto err_clk_init; + } + + ret = v4l2_device_register(&pdev->dev, &jpeg->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register v4l2 device\n"); + ret = -EINVAL; + goto err_dev_register; + } + + jpeg->m2m_dev = v4l2_m2m_init(jpeg->variant->m2m_ops); + + if (IS_ERR(jpeg->m2m_dev)) { + v4l2_err(&jpeg->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(jpeg->m2m_dev); + goto err_m2m_init; + } + + jpeg->vdev = video_device_alloc(); + if (!jpeg->vdev) { + ret = -ENOMEM; + goto err_vfd_jpeg_alloc; + } + snprintf(jpeg->vdev->name, sizeof(jpeg->vdev->name), + "%s", jpeg->variant->dev_name); + jpeg->vdev->fops = &mtk_jpeg_fops; + jpeg->vdev->ioctl_ops = jpeg->variant->ioctl_ops; + jpeg->vdev->minor = -1; + jpeg->vdev->release = video_device_release; + jpeg->vdev->lock = &jpeg->lock; + jpeg->vdev->v4l2_dev = &jpeg->v4l2_dev; + jpeg->vdev->vfl_dir = VFL_DIR_M2M; + jpeg->vdev->device_caps = V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_M2M_MPLANE; + + if (of_get_property(pdev->dev.of_node, "dma-ranges", NULL)) + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34)); + + ret = video_register_device(jpeg->vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&jpeg->v4l2_dev, "Failed to register video device\n"); + goto err_vfd_jpeg_register; + } + + video_set_drvdata(jpeg->vdev, jpeg); + v4l2_info(&jpeg->v4l2_dev, + "%s device registered as /dev/video%d (%d,%d)\n", + jpeg->variant->dev_name, jpeg->vdev->num, + VIDEO_MAJOR, jpeg->vdev->minor); + + platform_set_drvdata(pdev, jpeg); + + pm_runtime_enable(&pdev->dev); + + return 0; + +err_vfd_jpeg_register: + video_device_release(jpeg->vdev); + +err_vfd_jpeg_alloc: + v4l2_m2m_release(jpeg->m2m_dev); + +err_m2m_init: + v4l2_device_unregister(&jpeg->v4l2_dev); + +err_dev_register: + +err_clk_init: + +err_req_irq: + + return ret; +} + +static int mtk_jpeg_remove(struct platform_device *pdev) +{ + struct mtk_jpeg_dev *jpeg = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + video_unregister_device(jpeg->vdev); + v4l2_m2m_release(jpeg->m2m_dev); + v4l2_device_unregister(&jpeg->v4l2_dev); + + return 0; +} + +static __maybe_unused int mtk_jpeg_pm_suspend(struct device *dev) +{ + struct mtk_jpeg_dev *jpeg = dev_get_drvdata(dev); + + mtk_jpeg_clk_off(jpeg); + + return 0; +} + +static __maybe_unused int mtk_jpeg_pm_resume(struct device *dev) +{ + struct mtk_jpeg_dev *jpeg = dev_get_drvdata(dev); + + mtk_jpeg_clk_on(jpeg); + + return 0; +} + +static __maybe_unused int mtk_jpeg_suspend(struct device *dev) +{ + struct mtk_jpeg_dev *jpeg = dev_get_drvdata(dev); + + v4l2_m2m_suspend(jpeg->m2m_dev); + return pm_runtime_force_suspend(dev); +} + +static __maybe_unused int mtk_jpeg_resume(struct device *dev) +{ + struct mtk_jpeg_dev *jpeg = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret < 0) + return ret; + + v4l2_m2m_resume(jpeg->m2m_dev); + return ret; +} + +static const struct dev_pm_ops mtk_jpeg_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mtk_jpeg_suspend, mtk_jpeg_resume) + SET_RUNTIME_PM_OPS(mtk_jpeg_pm_suspend, mtk_jpeg_pm_resume, NULL) +}; + +static const struct mtk_jpeg_variant mt8173_jpeg_drvdata = { + .clks = mt8173_jpeg_dec_clocks, + .num_clks = ARRAY_SIZE(mt8173_jpeg_dec_clocks), + .formats = mtk_jpeg_dec_formats, + .num_formats = MTK_JPEG_DEC_NUM_FORMATS, + .qops = &mtk_jpeg_dec_qops, + .irq_handler = mtk_jpeg_dec_irq, + .hw_reset = mtk_jpeg_dec_reset, + .m2m_ops = &mtk_jpeg_dec_m2m_ops, + .dev_name = "mtk-jpeg-dec", + .ioctl_ops = &mtk_jpeg_dec_ioctl_ops, + .out_q_default_fourcc = V4L2_PIX_FMT_JPEG, + .cap_q_default_fourcc = V4L2_PIX_FMT_YUV420M, +}; + +static const struct mtk_jpeg_variant mtk_jpeg_drvdata = { + .clks = mtk_jpeg_clocks, + .num_clks = ARRAY_SIZE(mtk_jpeg_clocks), + .formats = mtk_jpeg_enc_formats, + .num_formats = MTK_JPEG_ENC_NUM_FORMATS, + .qops = &mtk_jpeg_enc_qops, + .irq_handler = mtk_jpeg_enc_irq, + .hw_reset = mtk_jpeg_enc_reset, + .m2m_ops = &mtk_jpeg_enc_m2m_ops, + .dev_name = "mtk-jpeg-enc", + .ioctl_ops = &mtk_jpeg_enc_ioctl_ops, + .out_q_default_fourcc = V4L2_PIX_FMT_YUYV, + .cap_q_default_fourcc = V4L2_PIX_FMT_JPEG, +}; + +static const struct of_device_id mtk_jpeg_match[] = { + { + .compatible = "mediatek,mt8173-jpgdec", + .data = &mt8173_jpeg_drvdata, + }, + { + .compatible = "mediatek,mt2701-jpgdec", + .data = &mt8173_jpeg_drvdata, + }, + { + .compatible = "mediatek,mtk-jpgenc", + .data = &mtk_jpeg_drvdata, + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, mtk_jpeg_match); + +static struct platform_driver mtk_jpeg_driver = { + .probe = mtk_jpeg_probe, + .remove = mtk_jpeg_remove, + .driver = { + .name = MTK_JPEG_NAME, + .of_match_table = mtk_jpeg_match, + .pm = &mtk_jpeg_pm_ops, + }, +}; + +module_platform_driver(mtk_jpeg_driver); + +MODULE_DESCRIPTION("MediaTek JPEG codec driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h new file mode 100644 index 000000000..3e4811a41 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_core.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + * Rick Chang + * Xia Jiang + */ + +#ifndef _MTK_JPEG_CORE_H +#define _MTK_JPEG_CORE_H + +#include +#include +#include +#include + +#define MTK_JPEG_NAME "mtk-jpeg" + +#define MTK_JPEG_COMP_MAX 3 + +#define MTK_JPEG_FMT_FLAG_OUTPUT BIT(0) +#define MTK_JPEG_FMT_FLAG_CAPTURE BIT(1) + +#define MTK_JPEG_MIN_WIDTH 32U +#define MTK_JPEG_MIN_HEIGHT 32U +#define MTK_JPEG_MAX_WIDTH 65535U +#define MTK_JPEG_MAX_HEIGHT 65535U + +#define MTK_JPEG_DEFAULT_SIZEIMAGE (1 * 1024 * 1024) + +#define MTK_JPEG_HW_TIMEOUT_MSEC 1000 + +#define MTK_JPEG_MAX_EXIF_SIZE (64 * 1024) + +/** + * enum mtk_jpeg_ctx_state - states of the context state machine + * @MTK_JPEG_INIT: current state is initialized + * @MTK_JPEG_RUNNING: current state is running + * @MTK_JPEG_SOURCE_CHANGE: current state is source resolution change + */ +enum mtk_jpeg_ctx_state { + MTK_JPEG_INIT = 0, + MTK_JPEG_RUNNING, + MTK_JPEG_SOURCE_CHANGE, +}; + +/** + * struct mtk_jpeg_variant - mtk jpeg driver variant + * @clks: clock names + * @num_clks: numbers of clock + * @formats: jpeg driver's internal color format + * @num_formats: number of formats + * @qops: the callback of jpeg vb2_ops + * @irq_handler: jpeg irq handler callback + * @hw_reset: jpeg hardware reset callback + * @m2m_ops: the callback of jpeg v4l2_m2m_ops + * @dev_name: jpeg device name + * @ioctl_ops: the callback of jpeg v4l2_ioctl_ops + * @out_q_default_fourcc: output queue default fourcc + * @cap_q_default_fourcc: capture queue default fourcc + */ +struct mtk_jpeg_variant { + struct clk_bulk_data *clks; + int num_clks; + struct mtk_jpeg_fmt *formats; + int num_formats; + const struct vb2_ops *qops; + irqreturn_t (*irq_handler)(int irq, void *priv); + void (*hw_reset)(void __iomem *base); + const struct v4l2_m2m_ops *m2m_ops; + const char *dev_name; + const struct v4l2_ioctl_ops *ioctl_ops; + u32 out_q_default_fourcc; + u32 cap_q_default_fourcc; +}; + +/** + * struct mtk_jpeg_dev - JPEG IP abstraction + * @lock: the mutex protecting this structure + * @hw_lock: spinlock protecting the hw device resource + * @workqueue: decode work queue + * @dev: JPEG device + * @v4l2_dev: v4l2 device for mem2mem mode + * @m2m_dev: v4l2 mem2mem device data + * @alloc_ctx: videobuf2 memory allocator's context + * @vdev: video device node for jpeg mem2mem mode + * @reg_base: JPEG registers mapping + * @job_timeout_work: IRQ timeout structure + * @variant: driver variant to be used + */ +struct mtk_jpeg_dev { + struct mutex lock; + spinlock_t hw_lock; + struct workqueue_struct *workqueue; + struct device *dev; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + void *alloc_ctx; + struct video_device *vdev; + void __iomem *reg_base; + struct delayed_work job_timeout_work; + const struct mtk_jpeg_variant *variant; +}; + +/** + * struct mtk_jpeg_fmt - driver's internal color format data + * @fourcc: the fourcc code, 0 if not applicable + * @hw_format: hardware format value + * @h_sample: horizontal sample count of plane in 4 * 4 pixel image + * @v_sample: vertical sample count of plane in 4 * 4 pixel image + * @colplanes: number of color planes (1 for packed formats) + * @h_align: horizontal alignment order (align to 2^h_align) + * @v_align: vertical alignment order (align to 2^v_align) + * @flags: flags describing format applicability + */ +struct mtk_jpeg_fmt { + u32 fourcc; + u32 hw_format; + int h_sample[VIDEO_MAX_PLANES]; + int v_sample[VIDEO_MAX_PLANES]; + int colplanes; + int h_align; + int v_align; + u32 flags; +}; + +/** + * struct mtk_jpeg_q_data - parameters of one queue + * @fmt: driver-specific format of this queue + * @pix_mp: multiplanar format + * @enc_crop_rect: jpeg encoder crop information + */ +struct mtk_jpeg_q_data { + struct mtk_jpeg_fmt *fmt; + struct v4l2_pix_format_mplane pix_mp; + struct v4l2_rect enc_crop_rect; +}; + +/** + * struct mtk_jpeg_ctx - the device context data + * @jpeg: JPEG IP device for this context + * @out_q: source (output) queue information + * @cap_q: destination (capture) queue queue information + * @fh: V4L2 file handle + * @state: state of the context + * @enable_exif: enable exif mode of jpeg encoder + * @enc_quality: jpeg encoder quality + * @restart_interval: jpeg encoder restart interval + * @ctrl_hdl: controls handler + */ +struct mtk_jpeg_ctx { + struct mtk_jpeg_dev *jpeg; + struct mtk_jpeg_q_data out_q; + struct mtk_jpeg_q_data cap_q; + struct v4l2_fh fh; + enum mtk_jpeg_ctx_state state; + bool enable_exif; + u8 enc_quality; + u8 restart_interval; + struct v4l2_ctrl_handler ctrl_hdl; +}; + +#endif /* _MTK_JPEG_CORE_H */ diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c new file mode 100644 index 000000000..6d200e237 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + * Rick Chang + */ + +#include +#include +#include + +#include "mtk_jpeg_dec_hw.h" + +#define MTK_JPEG_DUNUM_MASK(val) (((val) - 1) & 0x3) + +enum mtk_jpeg_color { + MTK_JPEG_COLOR_420 = 0x00221111, + MTK_JPEG_COLOR_422 = 0x00211111, + MTK_JPEG_COLOR_444 = 0x00111111, + MTK_JPEG_COLOR_422V = 0x00121111, + MTK_JPEG_COLOR_422X2 = 0x00412121, + MTK_JPEG_COLOR_422VX2 = 0x00222121, + MTK_JPEG_COLOR_400 = 0x00110000 +}; + +static inline int mtk_jpeg_verify_align(u32 val, int align, u32 reg) +{ + if (val & (align - 1)) { + pr_err("mtk-jpeg: write reg %x without %d align\n", reg, align); + return -1; + } + + return 0; +} + +static int mtk_jpeg_decide_format(struct mtk_jpeg_dec_param *param) +{ + param->src_color = (param->sampling_w[0] << 20) | + (param->sampling_h[0] << 16) | + (param->sampling_w[1] << 12) | + (param->sampling_h[1] << 8) | + (param->sampling_w[2] << 4) | + (param->sampling_h[2]); + + param->uv_brz_w = 0; + switch (param->src_color) { + case MTK_JPEG_COLOR_444: + param->uv_brz_w = 1; + param->dst_fourcc = V4L2_PIX_FMT_YUV422M; + break; + case MTK_JPEG_COLOR_422X2: + case MTK_JPEG_COLOR_422: + param->dst_fourcc = V4L2_PIX_FMT_YUV422M; + break; + case MTK_JPEG_COLOR_422V: + case MTK_JPEG_COLOR_422VX2: + param->uv_brz_w = 1; + param->dst_fourcc = V4L2_PIX_FMT_YUV420M; + break; + case MTK_JPEG_COLOR_420: + param->dst_fourcc = V4L2_PIX_FMT_YUV420M; + break; + case MTK_JPEG_COLOR_400: + param->dst_fourcc = V4L2_PIX_FMT_GREY; + break; + default: + param->dst_fourcc = 0; + return -1; + } + + return 0; +} + +static void mtk_jpeg_calc_mcu(struct mtk_jpeg_dec_param *param) +{ + u32 factor_w, factor_h; + u32 i, comp, blk; + + factor_w = 2 + param->sampling_w[0]; + factor_h = 2 + param->sampling_h[0]; + param->mcu_w = (param->pic_w + (1 << factor_w) - 1) >> factor_w; + param->mcu_h = (param->pic_h + (1 << factor_h) - 1) >> factor_h; + param->total_mcu = param->mcu_w * param->mcu_h; + param->unit_num = ((param->pic_w + 7) >> 3) * ((param->pic_h + 7) >> 3); + param->blk_num = 0; + for (i = 0; i < MTK_JPEG_COMP_MAX; i++) { + param->blk_comp[i] = 0; + if (i >= param->comp_num) + continue; + param->blk_comp[i] = param->sampling_w[i] * + param->sampling_h[i]; + param->blk_num += param->blk_comp[i]; + } + + param->membership = 0; + for (i = 0, blk = 0, comp = 0; i < MTK_JPEG_BLOCK_MAX; i++) { + if (i < param->blk_num && comp < param->comp_num) { + u32 tmp; + + tmp = (0x04 + (comp & 0x3)); + param->membership |= tmp << (i * 3); + if (++blk == param->blk_comp[comp]) { + comp++; + blk = 0; + } + } else { + param->membership |= 7 << (i * 3); + } + } +} + +static void mtk_jpeg_calc_dma_group(struct mtk_jpeg_dec_param *param) +{ + u32 factor_mcu = 3; + + if (param->src_color == MTK_JPEG_COLOR_444 && + param->dst_fourcc == V4L2_PIX_FMT_YUV422M) + factor_mcu = 4; + else if (param->src_color == MTK_JPEG_COLOR_422V && + param->dst_fourcc == V4L2_PIX_FMT_YUV420M) + factor_mcu = 4; + else if (param->src_color == MTK_JPEG_COLOR_422X2 && + param->dst_fourcc == V4L2_PIX_FMT_YUV422M) + factor_mcu = 2; + else if (param->src_color == MTK_JPEG_COLOR_400 || + (param->src_color & 0x0FFFF) == 0) + factor_mcu = 4; + + param->dma_mcu = 1 << factor_mcu; + param->dma_group = param->mcu_w / param->dma_mcu; + param->dma_last_mcu = param->mcu_w % param->dma_mcu; + if (param->dma_last_mcu) + param->dma_group++; + else + param->dma_last_mcu = param->dma_mcu; +} + +static int mtk_jpeg_calc_dst_size(struct mtk_jpeg_dec_param *param) +{ + u32 i, padding_w; + u32 ds_row_h[3]; + u32 brz_w[3]; + + brz_w[0] = 0; + brz_w[1] = param->uv_brz_w; + brz_w[2] = brz_w[1]; + + for (i = 0; i < param->comp_num; i++) { + if (brz_w[i] > 3) + return -1; + + padding_w = param->mcu_w * MTK_JPEG_DCTSIZE * + param->sampling_w[i]; + /* output format is 420/422 */ + param->comp_w[i] = padding_w >> brz_w[i]; + param->comp_w[i] = round_up(param->comp_w[i], + MTK_JPEG_DCTSIZE); + param->img_stride[i] = i ? round_up(param->comp_w[i], 16) + : round_up(param->comp_w[i], 32); + ds_row_h[i] = (MTK_JPEG_DCTSIZE * param->sampling_h[i]); + } + param->dec_w = param->img_stride[0]; + param->dec_h = ds_row_h[0] * param->mcu_h; + + for (i = 0; i < MTK_JPEG_COMP_MAX; i++) { + /* They must be equal in frame mode. */ + param->mem_stride[i] = param->img_stride[i]; + param->comp_size[i] = param->mem_stride[i] * ds_row_h[i] * + param->mcu_h; + } + + param->y_size = param->comp_size[0]; + param->uv_size = param->comp_size[1]; + param->dec_size = param->y_size + (param->uv_size << 1); + + return 0; +} + +int mtk_jpeg_dec_fill_param(struct mtk_jpeg_dec_param *param) +{ + if (mtk_jpeg_decide_format(param)) + return -1; + + mtk_jpeg_calc_mcu(param); + mtk_jpeg_calc_dma_group(param); + if (mtk_jpeg_calc_dst_size(param)) + return -2; + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_fill_param); + +u32 mtk_jpeg_dec_get_int_status(void __iomem *base) +{ + u32 ret; + + ret = readl(base + JPGDEC_REG_INTERRUPT_STATUS) & BIT_INQST_MASK_ALLIRQ; + if (ret) + writel(ret, base + JPGDEC_REG_INTERRUPT_STATUS); + + return ret; +} +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_get_int_status); + +u32 mtk_jpeg_dec_enum_result(u32 irq_result) +{ + if (irq_result & BIT_INQST_MASK_EOF) + return MTK_JPEG_DEC_RESULT_EOF_DONE; + if (irq_result & BIT_INQST_MASK_PAUSE) + return MTK_JPEG_DEC_RESULT_PAUSE; + if (irq_result & BIT_INQST_MASK_UNDERFLOW) + return MTK_JPEG_DEC_RESULT_UNDERFLOW; + if (irq_result & BIT_INQST_MASK_OVERFLOW) + return MTK_JPEG_DEC_RESULT_OVERFLOW; + if (irq_result & BIT_INQST_MASK_ERROR_BS) + return MTK_JPEG_DEC_RESULT_ERROR_BS; + + return MTK_JPEG_DEC_RESULT_ERROR_UNKNOWN; +} +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_enum_result); + +void mtk_jpeg_dec_start(void __iomem *base) +{ + writel(0, base + JPGDEC_REG_TRIG); +} +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_start); + +static void mtk_jpeg_dec_soft_reset(void __iomem *base) +{ + writel(0x0000FFFF, base + JPGDEC_REG_INTERRUPT_STATUS); + writel(0x00, base + JPGDEC_REG_RESET); + writel(0x01, base + JPGDEC_REG_RESET); +} + +static void mtk_jpeg_dec_hard_reset(void __iomem *base) +{ + writel(0x00, base + JPGDEC_REG_RESET); + writel(0x10, base + JPGDEC_REG_RESET); +} + +void mtk_jpeg_dec_reset(void __iomem *base) +{ + mtk_jpeg_dec_soft_reset(base); + mtk_jpeg_dec_hard_reset(base); +} +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_reset); + +static void mtk_jpeg_dec_set_brz_factor(void __iomem *base, u8 yscale_w, + u8 yscale_h, u8 uvscale_w, u8 uvscale_h) +{ + u32 val; + + val = (uvscale_h << 12) | (uvscale_w << 8) | + (yscale_h << 4) | yscale_w; + writel(val, base + JPGDEC_REG_BRZ_FACTOR); +} + +static void mtk_jpeg_dec_set_dst_bank0(void __iomem *base, u32 addr_y, + u32 addr_u, u32 addr_v) +{ + mtk_jpeg_verify_align(addr_y, 16, JPGDEC_REG_DEST_ADDR0_Y); + writel(addr_y, base + JPGDEC_REG_DEST_ADDR0_Y); + mtk_jpeg_verify_align(addr_u, 16, JPGDEC_REG_DEST_ADDR0_U); + writel(addr_u, base + JPGDEC_REG_DEST_ADDR0_U); + mtk_jpeg_verify_align(addr_v, 16, JPGDEC_REG_DEST_ADDR0_V); + writel(addr_v, base + JPGDEC_REG_DEST_ADDR0_V); +} + +static void mtk_jpeg_dec_set_dst_bank1(void __iomem *base, u32 addr_y, + u32 addr_u, u32 addr_v) +{ + writel(addr_y, base + JPGDEC_REG_DEST_ADDR1_Y); + writel(addr_u, base + JPGDEC_REG_DEST_ADDR1_U); + writel(addr_v, base + JPGDEC_REG_DEST_ADDR1_V); +} + +static void mtk_jpeg_dec_set_mem_stride(void __iomem *base, u32 stride_y, + u32 stride_uv) +{ + writel((stride_y & 0xFFFF), base + JPGDEC_REG_STRIDE_Y); + writel((stride_uv & 0xFFFF), base + JPGDEC_REG_STRIDE_UV); +} + +static void mtk_jpeg_dec_set_img_stride(void __iomem *base, u32 stride_y, + u32 stride_uv) +{ + writel((stride_y & 0xFFFF), base + JPGDEC_REG_IMG_STRIDE_Y); + writel((stride_uv & 0xFFFF), base + JPGDEC_REG_IMG_STRIDE_UV); +} + +static void mtk_jpeg_dec_set_pause_mcu_idx(void __iomem *base, u32 idx) +{ + writel(idx & 0x0003FFFFFF, base + JPGDEC_REG_PAUSE_MCU_NUM); +} + +static void mtk_jpeg_dec_set_dec_mode(void __iomem *base, u32 mode) +{ + writel(mode & 0x03, base + JPGDEC_REG_OPERATION_MODE); +} + +static void mtk_jpeg_dec_set_bs_write_ptr(void __iomem *base, u32 ptr) +{ + mtk_jpeg_verify_align(ptr, 16, JPGDEC_REG_FILE_BRP); + writel(ptr, base + JPGDEC_REG_FILE_BRP); +} + +static void mtk_jpeg_dec_set_bs_info(void __iomem *base, u32 addr, u32 size) +{ + mtk_jpeg_verify_align(addr, 16, JPGDEC_REG_FILE_ADDR); + mtk_jpeg_verify_align(size, 128, JPGDEC_REG_FILE_TOTAL_SIZE); + writel(addr, base + JPGDEC_REG_FILE_ADDR); + writel(size, base + JPGDEC_REG_FILE_TOTAL_SIZE); +} + +static void mtk_jpeg_dec_set_comp_id(void __iomem *base, u32 id_y, u32 id_u, + u32 id_v) +{ + u32 val; + + val = ((id_y & 0x00FF) << 24) | ((id_u & 0x00FF) << 16) | + ((id_v & 0x00FF) << 8); + writel(val, base + JPGDEC_REG_COMP_ID); +} + +static void mtk_jpeg_dec_set_total_mcu(void __iomem *base, u32 num) +{ + writel(num - 1, base + JPGDEC_REG_TOTAL_MCU_NUM); +} + +static void mtk_jpeg_dec_set_comp0_du(void __iomem *base, u32 num) +{ + writel(num - 1, base + JPGDEC_REG_COMP0_DATA_UNIT_NUM); +} + +static void mtk_jpeg_dec_set_du_membership(void __iomem *base, u32 member, + u32 gmc, u32 isgray) +{ + if (isgray) + member = 0x3FFFFFFC; + member |= (isgray << 31) | (gmc << 30); + writel(member, base + JPGDEC_REG_DU_CTRL); +} + +static void mtk_jpeg_dec_set_q_table(void __iomem *base, u32 id0, u32 id1, + u32 id2) +{ + u32 val; + + val = ((id0 & 0x0f) << 8) | ((id1 & 0x0f) << 4) | ((id2 & 0x0f) << 0); + writel(val, base + JPGDEC_REG_QT_ID); +} + +static void mtk_jpeg_dec_set_dma_group(void __iomem *base, u32 mcu_group, + u32 group_num, u32 last_mcu) +{ + u32 val; + + val = (((mcu_group - 1) & 0x00FF) << 16) | + (((group_num - 1) & 0x007F) << 8) | + ((last_mcu - 1) & 0x00FF); + writel(val, base + JPGDEC_REG_WDMA_CTRL); +} + +static void mtk_jpeg_dec_set_sampling_factor(void __iomem *base, u32 comp_num, + u32 y_w, u32 y_h, u32 u_w, + u32 u_h, u32 v_w, u32 v_h) +{ + u32 val; + u32 y_wh = (MTK_JPEG_DUNUM_MASK(y_w) << 2) | MTK_JPEG_DUNUM_MASK(y_h); + u32 u_wh = (MTK_JPEG_DUNUM_MASK(u_w) << 2) | MTK_JPEG_DUNUM_MASK(u_h); + u32 v_wh = (MTK_JPEG_DUNUM_MASK(v_w) << 2) | MTK_JPEG_DUNUM_MASK(v_h); + + if (comp_num == 1) + val = 0; + else + val = (y_wh << 8) | (u_wh << 4) | v_wh; + writel(val, base + JPGDEC_REG_DU_NUM); +} + +void mtk_jpeg_dec_set_config(void __iomem *base, + struct mtk_jpeg_dec_param *config, + struct mtk_jpeg_bs *bs, + struct mtk_jpeg_fb *fb) +{ + mtk_jpeg_dec_set_brz_factor(base, 0, 0, config->uv_brz_w, 0); + mtk_jpeg_dec_set_dec_mode(base, 0); + mtk_jpeg_dec_set_comp0_du(base, config->unit_num); + mtk_jpeg_dec_set_total_mcu(base, config->total_mcu); + mtk_jpeg_dec_set_bs_info(base, bs->str_addr, bs->size); + mtk_jpeg_dec_set_bs_write_ptr(base, bs->end_addr); + mtk_jpeg_dec_set_du_membership(base, config->membership, 1, + (config->comp_num == 1) ? 1 : 0); + mtk_jpeg_dec_set_comp_id(base, config->comp_id[0], config->comp_id[1], + config->comp_id[2]); + mtk_jpeg_dec_set_q_table(base, config->qtbl_num[0], + config->qtbl_num[1], config->qtbl_num[2]); + mtk_jpeg_dec_set_sampling_factor(base, config->comp_num, + config->sampling_w[0], + config->sampling_h[0], + config->sampling_w[1], + config->sampling_h[1], + config->sampling_w[2], + config->sampling_h[2]); + mtk_jpeg_dec_set_mem_stride(base, config->mem_stride[0], + config->mem_stride[1]); + mtk_jpeg_dec_set_img_stride(base, config->img_stride[0], + config->img_stride[1]); + mtk_jpeg_dec_set_dst_bank0(base, fb->plane_addr[0], + fb->plane_addr[1], fb->plane_addr[2]); + mtk_jpeg_dec_set_dst_bank1(base, 0, 0, 0); + mtk_jpeg_dec_set_dma_group(base, config->dma_mcu, config->dma_group, + config->dma_last_mcu); + mtk_jpeg_dec_set_pause_mcu_idx(base, config->total_mcu); +} +EXPORT_SYMBOL_GPL(mtk_jpeg_dec_set_config); diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h new file mode 100644 index 000000000..fa0d45fd7 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_hw.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + * Rick Chang + * Xia Jiang + */ + +#ifndef _MTK_JPEG_DEC_HW_H +#define _MTK_JPEG_DEC_HW_H + +#include + +#include "mtk_jpeg_core.h" +#include "mtk_jpeg_dec_reg.h" + +enum { + MTK_JPEG_DEC_RESULT_EOF_DONE = 0, + MTK_JPEG_DEC_RESULT_PAUSE = 1, + MTK_JPEG_DEC_RESULT_UNDERFLOW = 2, + MTK_JPEG_DEC_RESULT_OVERFLOW = 3, + MTK_JPEG_DEC_RESULT_ERROR_BS = 4, + MTK_JPEG_DEC_RESULT_ERROR_UNKNOWN = 6 +}; + +struct mtk_jpeg_dec_param { + u32 pic_w; + u32 pic_h; + u32 dec_w; + u32 dec_h; + u32 src_color; + u32 dst_fourcc; + u32 mcu_w; + u32 mcu_h; + u32 total_mcu; + u32 unit_num; + u32 comp_num; + u32 comp_id[MTK_JPEG_COMP_MAX]; + u32 sampling_w[MTK_JPEG_COMP_MAX]; + u32 sampling_h[MTK_JPEG_COMP_MAX]; + u32 qtbl_num[MTK_JPEG_COMP_MAX]; + u32 blk_num; + u32 blk_comp[MTK_JPEG_COMP_MAX]; + u32 membership; + u32 dma_mcu; + u32 dma_group; + u32 dma_last_mcu; + u32 img_stride[MTK_JPEG_COMP_MAX]; + u32 mem_stride[MTK_JPEG_COMP_MAX]; + u32 comp_w[MTK_JPEG_COMP_MAX]; + u32 comp_size[MTK_JPEG_COMP_MAX]; + u32 y_size; + u32 uv_size; + u32 dec_size; + u8 uv_brz_w; +}; + +struct mtk_jpeg_bs { + dma_addr_t str_addr; + dma_addr_t end_addr; + size_t size; +}; + +struct mtk_jpeg_fb { + dma_addr_t plane_addr[MTK_JPEG_COMP_MAX]; + size_t size; +}; + +int mtk_jpeg_dec_fill_param(struct mtk_jpeg_dec_param *param); +u32 mtk_jpeg_dec_get_int_status(void __iomem *dec_reg_base); +u32 mtk_jpeg_dec_enum_result(u32 irq_result); +void mtk_jpeg_dec_set_config(void __iomem *base, + struct mtk_jpeg_dec_param *config, + struct mtk_jpeg_bs *bs, + struct mtk_jpeg_fb *fb); +void mtk_jpeg_dec_reset(void __iomem *dec_reg_base); +void mtk_jpeg_dec_start(void __iomem *dec_reg_base); + +#endif /* _MTK_JPEG_HW_H */ diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_parse.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_parse.c new file mode 100644 index 000000000..b95c45791 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_parse.c @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + * Rick Chang + */ + +#include +#include + +#include "mtk_jpeg_dec_parse.h" + +#define TEM 0x01 +#define SOF0 0xc0 +#define RST 0xd0 +#define SOI 0xd8 +#define EOI 0xd9 + +struct mtk_jpeg_stream { + u8 *addr; + u32 size; + u32 curr; +}; + +static int read_byte(struct mtk_jpeg_stream *stream) +{ + if (stream->curr >= stream->size) + return -1; + return stream->addr[stream->curr++]; +} + +static int read_word_be(struct mtk_jpeg_stream *stream, u32 *word) +{ + u32 temp; + int byte; + + byte = read_byte(stream); + if (byte == -1) + return -1; + temp = byte << 8; + byte = read_byte(stream); + if (byte == -1) + return -1; + *word = (u32)byte | temp; + + return 0; +} + +static void read_skip(struct mtk_jpeg_stream *stream, long len) +{ + if (len <= 0) + return; + while (len--) + read_byte(stream); +} + +static bool mtk_jpeg_do_parse(struct mtk_jpeg_dec_param *param, u8 *src_addr_va, + u32 src_size) +{ + bool notfound = true; + struct mtk_jpeg_stream stream; + + stream.addr = src_addr_va; + stream.size = src_size; + stream.curr = 0; + + while (notfound) { + int i, length, byte; + u32 word; + + byte = read_byte(&stream); + if (byte == -1) + return false; + if (byte != 0xff) + continue; + do + byte = read_byte(&stream); + while (byte == 0xff); + if (byte == -1) + return false; + if (byte == 0) + continue; + + length = 0; + switch (byte) { + case SOF0: + /* length */ + if (read_word_be(&stream, &word)) + break; + + /* precision */ + if (read_byte(&stream) == -1) + break; + + if (read_word_be(&stream, &word)) + break; + param->pic_h = word; + + if (read_word_be(&stream, &word)) + break; + param->pic_w = word; + + param->comp_num = read_byte(&stream); + if (param->comp_num != 1 && param->comp_num != 3) + break; + + for (i = 0; i < param->comp_num; i++) { + param->comp_id[i] = read_byte(&stream); + if (param->comp_id[i] == -1) + break; + + /* sampling */ + byte = read_byte(&stream); + if (byte == -1) + break; + param->sampling_w[i] = (byte >> 4) & 0x0F; + param->sampling_h[i] = byte & 0x0F; + + param->qtbl_num[i] = read_byte(&stream); + if (param->qtbl_num[i] == -1) + break; + } + + notfound = !(i == param->comp_num); + break; + case RST ... RST + 7: + case SOI: + case EOI: + case TEM: + break; + default: + if (read_word_be(&stream, &word)) + break; + length = (long)word - 2; + read_skip(&stream, length); + break; + } + } + + return !notfound; +} + +bool mtk_jpeg_parse(struct mtk_jpeg_dec_param *param, u8 *src_addr_va, + u32 src_size) +{ + if (!mtk_jpeg_do_parse(param, src_addr_va, src_size)) + return false; + if (mtk_jpeg_dec_fill_param(param)) + return false; + + return true; +} diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_parse.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_parse.h new file mode 100644 index 000000000..2918f1581 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_parse.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + * Rick Chang + */ + +#ifndef _MTK_JPEG_PARSE_H +#define _MTK_JPEG_PARSE_H + +#include "mtk_jpeg_dec_hw.h" + +bool mtk_jpeg_parse(struct mtk_jpeg_dec_param *param, u8 *src_addr_va, + u32 src_size); + +#endif /* _MTK_JPEG_PARSE_H */ + diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h new file mode 100644 index 000000000..21ec8f967 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_dec_reg.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + * Rick Chang + */ + +#ifndef _MTK_JPEG_REG_H +#define _MTK_JPEG_REG_H + +#define MTK_JPEG_BLOCK_MAX 10 +#define MTK_JPEG_DCTSIZE 8 + +#define BIT_INQST_MASK_ERROR_BS 0x20 +#define BIT_INQST_MASK_PAUSE 0x10 +#define BIT_INQST_MASK_OVERFLOW 0x04 +#define BIT_INQST_MASK_UNDERFLOW 0x02 +#define BIT_INQST_MASK_EOF 0x01 +#define BIT_INQST_MASK_ALLIRQ 0x37 + +#define JPGDEC_REG_RESET 0x0090 +#define JPGDEC_REG_BRZ_FACTOR 0x00f8 +#define JPGDEC_REG_DU_NUM 0x00fc +#define JPGDEC_REG_DEST_ADDR0_Y 0x0140 +#define JPGDEC_REG_DEST_ADDR0_U 0x0144 +#define JPGDEC_REG_DEST_ADDR0_V 0x0148 +#define JPGDEC_REG_DEST_ADDR1_Y 0x014c +#define JPGDEC_REG_DEST_ADDR1_U 0x0150 +#define JPGDEC_REG_DEST_ADDR1_V 0x0154 +#define JPGDEC_REG_STRIDE_Y 0x0158 +#define JPGDEC_REG_STRIDE_UV 0x015c +#define JPGDEC_REG_IMG_STRIDE_Y 0x0160 +#define JPGDEC_REG_IMG_STRIDE_UV 0x0164 +#define JPGDEC_REG_WDMA_CTRL 0x016c +#define JPGDEC_REG_PAUSE_MCU_NUM 0x0170 +#define JPGDEC_REG_OPERATION_MODE 0x017c +#define JPGDEC_REG_FILE_ADDR 0x0200 +#define JPGDEC_REG_COMP_ID 0x020c +#define JPGDEC_REG_TOTAL_MCU_NUM 0x0210 +#define JPGDEC_REG_COMP0_DATA_UNIT_NUM 0x0224 +#define JPGDEC_REG_DU_CTRL 0x023c +#define JPGDEC_REG_TRIG 0x0240 +#define JPGDEC_REG_FILE_BRP 0x0248 +#define JPGDEC_REG_FILE_TOTAL_SIZE 0x024c +#define JPGDEC_REG_QT_ID 0x0270 +#define JPGDEC_REG_INTERRUPT_STATUS 0x0274 +#define JPGDEC_REG_STATUS 0x0278 + +#endif /* _MTK_JPEG_REG_H */ diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c new file mode 100644 index 000000000..8c271c38c --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019 MediaTek Inc. + * Author: Xia Jiang + * + */ + +#include +#include +#include +#include + +#include "mtk_jpeg_enc_hw.h" + +static const struct mtk_jpeg_enc_qlt mtk_jpeg_enc_quality[] = { + {.quality_param = 34, .hardware_value = JPEG_ENC_QUALITY_Q34}, + {.quality_param = 39, .hardware_value = JPEG_ENC_QUALITY_Q39}, + {.quality_param = 48, .hardware_value = JPEG_ENC_QUALITY_Q48}, + {.quality_param = 60, .hardware_value = JPEG_ENC_QUALITY_Q60}, + {.quality_param = 64, .hardware_value = JPEG_ENC_QUALITY_Q64}, + {.quality_param = 68, .hardware_value = JPEG_ENC_QUALITY_Q68}, + {.quality_param = 74, .hardware_value = JPEG_ENC_QUALITY_Q74}, + {.quality_param = 80, .hardware_value = JPEG_ENC_QUALITY_Q80}, + {.quality_param = 82, .hardware_value = JPEG_ENC_QUALITY_Q82}, + {.quality_param = 84, .hardware_value = JPEG_ENC_QUALITY_Q84}, + {.quality_param = 87, .hardware_value = JPEG_ENC_QUALITY_Q87}, + {.quality_param = 90, .hardware_value = JPEG_ENC_QUALITY_Q90}, + {.quality_param = 92, .hardware_value = JPEG_ENC_QUALITY_Q92}, + {.quality_param = 95, .hardware_value = JPEG_ENC_QUALITY_Q95}, + {.quality_param = 97, .hardware_value = JPEG_ENC_QUALITY_Q97}, +}; + +void mtk_jpeg_enc_reset(void __iomem *base) +{ + writel(0, base + JPEG_ENC_RSTB); + writel(JPEG_ENC_RESET_BIT, base + JPEG_ENC_RSTB); + writel(0, base + JPEG_ENC_CODEC_SEL); +} + +u32 mtk_jpeg_enc_get_file_size(void __iomem *base) +{ + return readl(base + JPEG_ENC_DMA_ADDR0) - + readl(base + JPEG_ENC_DST_ADDR0); +} + +void mtk_jpeg_enc_start(void __iomem *base) +{ + u32 value; + + value = readl(base + JPEG_ENC_CTRL); + value |= JPEG_ENC_CTRL_INT_EN_BIT | JPEG_ENC_CTRL_ENABLE_BIT; + writel(value, base + JPEG_ENC_CTRL); +} + +void mtk_jpeg_set_enc_src(struct mtk_jpeg_ctx *ctx, void __iomem *base, + struct vb2_buffer *src_buf) +{ + int i; + dma_addr_t dma_addr; + + for (i = 0; i < src_buf->num_planes; i++) { + dma_addr = vb2_dma_contig_plane_dma_addr(src_buf, i) + + src_buf->planes[i].data_offset; + if (!i) + writel(dma_addr, base + JPEG_ENC_SRC_LUMA_ADDR); + else + writel(dma_addr, base + JPEG_ENC_SRC_CHROMA_ADDR); + } +} + +void mtk_jpeg_set_enc_dst(struct mtk_jpeg_ctx *ctx, void __iomem *base, + struct vb2_buffer *dst_buf) +{ + dma_addr_t dma_addr; + size_t size; + u32 dma_addr_offset; + u32 dma_addr_offsetmask; + + dma_addr = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + dma_addr_offset = ctx->enable_exif ? MTK_JPEG_MAX_EXIF_SIZE : 0; + dma_addr_offsetmask = dma_addr & JPEG_ENC_DST_ADDR_OFFSET_MASK; + size = vb2_plane_size(dst_buf, 0); + + writel(dma_addr_offset & ~0xf, base + JPEG_ENC_OFFSET_ADDR); + writel(dma_addr_offsetmask & 0xf, base + JPEG_ENC_BYTE_OFFSET_MASK); + writel(dma_addr & ~0xf, base + JPEG_ENC_DST_ADDR0); + writel((dma_addr + size) & ~0xf, base + JPEG_ENC_STALL_ADDR0); +} + +void mtk_jpeg_set_enc_params(struct mtk_jpeg_ctx *ctx, void __iomem *base) +{ + u32 value; + u32 width = ctx->out_q.enc_crop_rect.width; + u32 height = ctx->out_q.enc_crop_rect.height; + u32 enc_format = ctx->out_q.fmt->fourcc; + u32 bytesperline = ctx->out_q.pix_mp.plane_fmt[0].bytesperline; + u32 blk_num; + u32 img_stride; + u32 mem_stride; + u32 i, enc_quality; + u32 nr_enc_quality = ARRAY_SIZE(mtk_jpeg_enc_quality); + + value = width << 16 | height; + writel(value, base + JPEG_ENC_IMG_SIZE); + + if (enc_format == V4L2_PIX_FMT_NV12M || + enc_format == V4L2_PIX_FMT_NV21M) + /* + * Total 8 x 8 block number of luma and chroma. + * The number of blocks is counted from 0. + */ + blk_num = DIV_ROUND_UP(width, 16) * + DIV_ROUND_UP(height, 16) * 6 - 1; + else + blk_num = DIV_ROUND_UP(width, 16) * + DIV_ROUND_UP(height, 8) * 4 - 1; + writel(blk_num, base + JPEG_ENC_BLK_NUM); + + if (enc_format == V4L2_PIX_FMT_NV12M || + enc_format == V4L2_PIX_FMT_NV21M) { + /* 4:2:0 */ + img_stride = round_up(width, 16); + mem_stride = bytesperline; + } else { + /* 4:2:2 */ + img_stride = round_up(width * 2, 32); + mem_stride = img_stride; + } + writel(img_stride, base + JPEG_ENC_IMG_STRIDE); + writel(mem_stride, base + JPEG_ENC_STRIDE); + + enc_quality = mtk_jpeg_enc_quality[nr_enc_quality - 1].hardware_value; + for (i = 0; i < nr_enc_quality; i++) { + if (ctx->enc_quality <= mtk_jpeg_enc_quality[i].quality_param) { + enc_quality = mtk_jpeg_enc_quality[i].hardware_value; + break; + } + } + writel(enc_quality, base + JPEG_ENC_QUALITY); + + value = readl(base + JPEG_ENC_CTRL); + value &= ~JPEG_ENC_CTRL_YUV_FORMAT_MASK; + value |= (ctx->out_q.fmt->hw_format & 3) << 3; + if (ctx->enable_exif) + value |= JPEG_ENC_CTRL_FILE_FORMAT_BIT; + else + value &= ~JPEG_ENC_CTRL_FILE_FORMAT_BIT; + if (ctx->restart_interval) + value |= JPEG_ENC_CTRL_RESTART_EN_BIT; + else + value &= ~JPEG_ENC_CTRL_RESTART_EN_BIT; + writel(value, base + JPEG_ENC_CTRL); + + writel(ctx->restart_interval, base + JPEG_ENC_RST_MCU_NUM); +} diff --git a/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.h b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.h new file mode 100644 index 000000000..61c60e4e5 --- /dev/null +++ b/drivers/media/platform/mediatek/jpeg/mtk_jpeg_enc_hw.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2019 MediaTek Inc. + * Author: Xia Jiang + * + */ + +#ifndef _MTK_JPEG_ENC_HW_H +#define _MTK_JPEG_ENC_HW_H + +#include + +#include "mtk_jpeg_core.h" + +#define JPEG_ENC_INT_STATUS_DONE BIT(0) +#define JPEG_ENC_INT_STATUS_MASK_ALLIRQ 0x13 + +#define JPEG_ENC_DST_ADDR_OFFSET_MASK GENMASK(3, 0) + +#define JPEG_ENC_CTRL_YUV_FORMAT_MASK 0x18 +#define JPEG_ENC_CTRL_RESTART_EN_BIT BIT(10) +#define JPEG_ENC_CTRL_FILE_FORMAT_BIT BIT(5) +#define JPEG_ENC_CTRL_INT_EN_BIT BIT(2) +#define JPEG_ENC_CTRL_ENABLE_BIT BIT(0) +#define JPEG_ENC_RESET_BIT BIT(0) + +#define JPEG_ENC_YUV_FORMAT_YUYV 0 +#define JPEG_ENC_YUV_FORMAT_YVYU 1 +#define JPEG_ENC_YUV_FORMAT_NV12 2 +#define JEPG_ENC_YUV_FORMAT_NV21 3 + +#define JPEG_ENC_QUALITY_Q60 0x0 +#define JPEG_ENC_QUALITY_Q80 0x1 +#define JPEG_ENC_QUALITY_Q90 0x2 +#define JPEG_ENC_QUALITY_Q95 0x3 +#define JPEG_ENC_QUALITY_Q39 0x4 +#define JPEG_ENC_QUALITY_Q68 0x5 +#define JPEG_ENC_QUALITY_Q84 0x6 +#define JPEG_ENC_QUALITY_Q92 0x7 +#define JPEG_ENC_QUALITY_Q48 0x8 +#define JPEG_ENC_QUALITY_Q74 0xa +#define JPEG_ENC_QUALITY_Q87 0xb +#define JPEG_ENC_QUALITY_Q34 0xc +#define JPEG_ENC_QUALITY_Q64 0xe +#define JPEG_ENC_QUALITY_Q82 0xf +#define JPEG_ENC_QUALITY_Q97 0x10 + +#define JPEG_ENC_RSTB 0x100 +#define JPEG_ENC_CTRL 0x104 +#define JPEG_ENC_QUALITY 0x108 +#define JPEG_ENC_BLK_NUM 0x10C +#define JPEG_ENC_BLK_CNT 0x110 +#define JPEG_ENC_INT_STS 0x11c +#define JPEG_ENC_DST_ADDR0 0x120 +#define JPEG_ENC_DMA_ADDR0 0x124 +#define JPEG_ENC_STALL_ADDR0 0x128 +#define JPEG_ENC_OFFSET_ADDR 0x138 +#define JPEG_ENC_RST_MCU_NUM 0x150 +#define JPEG_ENC_IMG_SIZE 0x154 +#define JPEG_ENC_DEBUG_INFO0 0x160 +#define JPEG_ENC_DEBUG_INFO1 0x164 +#define JPEG_ENC_TOTAL_CYCLE 0x168 +#define JPEG_ENC_BYTE_OFFSET_MASK 0x16c +#define JPEG_ENC_SRC_LUMA_ADDR 0x170 +#define JPEG_ENC_SRC_CHROMA_ADDR 0x174 +#define JPEG_ENC_STRIDE 0x178 +#define JPEG_ENC_IMG_STRIDE 0x17c +#define JPEG_ENC_DCM_CTRL 0x300 +#define JPEG_ENC_CODEC_SEL 0x314 +#define JPEG_ENC_ULTRA_THRES 0x318 + +/** + * struct mtk_jpeg_enc_qlt - JPEG encoder quality data + * @quality_param: quality value + * @hardware_value: hardware value of quality + */ +struct mtk_jpeg_enc_qlt { + u8 quality_param; + u8 hardware_value; +}; + +void mtk_jpeg_enc_reset(void __iomem *base); +u32 mtk_jpeg_enc_get_file_size(void __iomem *base); +void mtk_jpeg_enc_start(void __iomem *enc_reg_base); +void mtk_jpeg_set_enc_src(struct mtk_jpeg_ctx *ctx, void __iomem *base, + struct vb2_buffer *src_buf); +void mtk_jpeg_set_enc_dst(struct mtk_jpeg_ctx *ctx, void __iomem *base, + struct vb2_buffer *dst_buf); +void mtk_jpeg_set_enc_params(struct mtk_jpeg_ctx *ctx, void __iomem *base); + +#endif /* _MTK_JPEG_ENC_HW_H */ diff --git a/drivers/media/platform/mediatek/mdp/Kconfig b/drivers/media/platform/mediatek/mdp/Kconfig new file mode 100644 index 000000000..9f13a4289 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_MEDIATEK_MDP + tristate "Mediatek MDP driver" + depends on V4L_MEM2MEM_DRIVERS + depends on MTK_IOMMU || COMPILE_TEST + depends on VIDEO_DEV + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on MTK_SMI || (COMPILE_TEST && MTK_SMI=n) + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select VIDEO_MEDIATEK_VPU + help + It is a v4l2 driver and present in Mediatek MT8173 SoCs. + The driver supports for scaling and color space conversion. + + To compile this driver as a module, choose M here: the + module will be called mtk-mdp. diff --git a/drivers/media/platform/mediatek/mdp/Makefile b/drivers/media/platform/mediatek/mdp/Makefile new file mode 100644 index 000000000..b7c16ebec --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +mtk-mdp-y += mtk_mdp_core.o +mtk-mdp-y += mtk_mdp_comp.o +mtk-mdp-y += mtk_mdp_m2m.o +mtk-mdp-y += mtk_mdp_regs.o +mtk-mdp-y += mtk_mdp_vpu.o + +obj-$(CONFIG_VIDEO_MEDIATEK_MDP) += mtk-mdp.o + +ccflags-y += -I$(srctree)/drivers/media/platform/mediatek/vpu diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c new file mode 100644 index 000000000..1e3833f1c --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.c @@ -0,0 +1,77 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + */ + +#include +#include +#include +#include +#include + +#include "mtk_mdp_comp.h" + + +void mtk_mdp_comp_clock_on(struct device *dev, struct mtk_mdp_comp *comp) +{ + int i, err; + + for (i = 0; i < ARRAY_SIZE(comp->clk); i++) { + if (IS_ERR(comp->clk[i])) + continue; + err = clk_prepare_enable(comp->clk[i]); + if (err) + dev_err(dev, + "failed to enable clock, err %d. type:%d i:%d\n", + err, comp->type, i); + } +} + +void mtk_mdp_comp_clock_off(struct device *dev, struct mtk_mdp_comp *comp) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(comp->clk); i++) { + if (IS_ERR(comp->clk[i])) + continue; + clk_disable_unprepare(comp->clk[i]); + } +} + +int mtk_mdp_comp_init(struct device *dev, struct device_node *node, + struct mtk_mdp_comp *comp, + enum mtk_mdp_comp_type comp_type) +{ + int ret; + int i; + + comp->dev_node = of_node_get(node); + comp->type = comp_type; + + for (i = 0; i < ARRAY_SIZE(comp->clk); i++) { + comp->clk[i] = of_clk_get(node, i); + if (IS_ERR(comp->clk[i])) { + if (PTR_ERR(comp->clk[i]) != -EPROBE_DEFER) + dev_err(dev, "Failed to get clock\n"); + ret = PTR_ERR(comp->clk[i]); + goto put_dev; + } + + /* Only RDMA needs two clocks */ + if (comp->type != MTK_MDP_RDMA) + break; + } + + return 0; + +put_dev: + of_node_put(comp->dev_node); + + return ret; +} + +void mtk_mdp_comp_deinit(struct device *dev, struct mtk_mdp_comp *comp) +{ + of_node_put(comp->dev_node); +} diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.h b/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.h new file mode 100644 index 000000000..ae41dd3cd --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_comp.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + */ + +#ifndef __MTK_MDP_COMP_H__ +#define __MTK_MDP_COMP_H__ + +/** + * enum mtk_mdp_comp_type - the MDP component + * @MTK_MDP_RDMA: Read DMA + * @MTK_MDP_RSZ: Riszer + * @MTK_MDP_WDMA: Write DMA + * @MTK_MDP_WROT: Write DMA with rotation + */ +enum mtk_mdp_comp_type { + MTK_MDP_RDMA, + MTK_MDP_RSZ, + MTK_MDP_WDMA, + MTK_MDP_WROT, +}; + +/** + * struct mtk_mdp_comp - the MDP's function component data + * @node: list node to track sibing MDP components + * @dev_node: component device node + * @clk: clocks required for component + * @type: component type + */ +struct mtk_mdp_comp { + struct list_head node; + struct device_node *dev_node; + struct clk *clk[2]; + enum mtk_mdp_comp_type type; +}; + +int mtk_mdp_comp_init(struct device *dev, struct device_node *node, + struct mtk_mdp_comp *comp, + enum mtk_mdp_comp_type comp_type); +void mtk_mdp_comp_deinit(struct device *dev, struct mtk_mdp_comp *comp); +void mtk_mdp_comp_clock_on(struct device *dev, struct mtk_mdp_comp *comp); +void mtk_mdp_comp_clock_off(struct device *dev, struct mtk_mdp_comp *comp); + + +#endif /* __MTK_MDP_COMP_H__ */ diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c new file mode 100644 index 000000000..d83c4964e --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_core.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2016 MediaTek Inc. + * Author: Houlong Wei + * Ming Hsiu Tsai + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_mdp_core.h" +#include "mtk_mdp_m2m.h" +#include "mtk_vpu.h" + +/* MDP debug log level (0-3). 3 shows all the logs. */ +int mtk_mdp_dbg_level; +EXPORT_SYMBOL(mtk_mdp_dbg_level); + +module_param(mtk_mdp_dbg_level, int, 0644); + +static const struct of_device_id mtk_mdp_comp_dt_ids[] = { + { + .compatible = "mediatek,mt8173-mdp-rdma", + .data = (void *)MTK_MDP_RDMA + }, { + .compatible = "mediatek,mt8173-mdp-rsz", + .data = (void *)MTK_MDP_RSZ + }, { + .compatible = "mediatek,mt8173-mdp-wdma", + .data = (void *)MTK_MDP_WDMA + }, { + .compatible = "mediatek,mt8173-mdp-wrot", + .data = (void *)MTK_MDP_WROT + }, + { }, +}; + +static const struct of_device_id mtk_mdp_of_ids[] = { + { .compatible = "mediatek,mt8173-mdp", }, + { }, +}; +MODULE_DEVICE_TABLE(of, mtk_mdp_of_ids); + +static void mtk_mdp_clock_on(struct mtk_mdp_dev *mdp) +{ + struct device *dev = &mdp->pdev->dev; + struct mtk_mdp_comp *comp_node; + + list_for_each_entry(comp_node, &mdp->comp_list, node) + mtk_mdp_comp_clock_on(dev, comp_node); +} + +static void mtk_mdp_clock_off(struct mtk_mdp_dev *mdp) +{ + struct device *dev = &mdp->pdev->dev; + struct mtk_mdp_comp *comp_node; + + list_for_each_entry(comp_node, &mdp->comp_list, node) + mtk_mdp_comp_clock_off(dev, comp_node); +} + +static void mtk_mdp_wdt_worker(struct work_struct *work) +{ + struct mtk_mdp_dev *mdp = + container_of(work, struct mtk_mdp_dev, wdt_work); + struct mtk_mdp_ctx *ctx; + + mtk_mdp_err("Watchdog timeout"); + + list_for_each_entry(ctx, &mdp->ctx_list, list) { + mtk_mdp_dbg(0, "[%d] Change as state error", ctx->id); + mtk_mdp_ctx_state_lock_set(ctx, MTK_MDP_CTX_ERROR); + } +} + +static void mtk_mdp_reset_handler(void *priv) +{ + struct mtk_mdp_dev *mdp = priv; + + queue_work(mdp->wdt_wq, &mdp->wdt_work); +} + +void mtk_mdp_register_component(struct mtk_mdp_dev *mdp, + struct mtk_mdp_comp *comp) +{ + list_add(&comp->node, &mdp->comp_list); +} + +void mtk_mdp_unregister_component(struct mtk_mdp_dev *mdp, + struct mtk_mdp_comp *comp) +{ + list_del(&comp->node); +} + +static int mtk_mdp_probe(struct platform_device *pdev) +{ + struct mtk_mdp_dev *mdp; + struct device *dev = &pdev->dev; + struct device_node *node, *parent; + struct mtk_mdp_comp *comp, *comp_temp; + int ret = 0; + + mdp = devm_kzalloc(dev, sizeof(*mdp), GFP_KERNEL); + if (!mdp) + return -ENOMEM; + + mdp->id = pdev->id; + mdp->pdev = pdev; + INIT_LIST_HEAD(&mdp->comp_list); + INIT_LIST_HEAD(&mdp->ctx_list); + + mutex_init(&mdp->lock); + mutex_init(&mdp->vpulock); + + /* Old dts had the components as child nodes */ + node = of_get_next_child(dev->of_node, NULL); + if (node) { + of_node_put(node); + parent = dev->of_node; + dev_warn(dev, "device tree is out of date\n"); + } else { + parent = dev->of_node->parent; + } + + /* Iterate over sibling MDP function blocks */ + for_each_child_of_node(parent, node) { + const struct of_device_id *of_id; + enum mtk_mdp_comp_type comp_type; + + of_id = of_match_node(mtk_mdp_comp_dt_ids, node); + if (!of_id) + continue; + + if (!of_device_is_available(node)) { + dev_err(dev, "Skipping disabled component %pOF\n", + node); + continue; + } + + comp_type = (uintptr_t)of_id->data; + + comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL); + if (!comp) { + ret = -ENOMEM; + of_node_put(node); + goto err_comp; + } + + ret = mtk_mdp_comp_init(dev, node, comp, comp_type); + if (ret) { + of_node_put(node); + goto err_comp; + } + + mtk_mdp_register_component(mdp, comp); + } + + mdp->job_wq = create_singlethread_workqueue(MTK_MDP_MODULE_NAME); + if (!mdp->job_wq) { + dev_err(&pdev->dev, "unable to alloc job workqueue\n"); + ret = -ENOMEM; + goto err_alloc_job_wq; + } + + mdp->wdt_wq = create_singlethread_workqueue("mdp_wdt_wq"); + if (!mdp->wdt_wq) { + dev_err(&pdev->dev, "unable to alloc wdt workqueue\n"); + ret = -ENOMEM; + goto err_alloc_wdt_wq; + } + INIT_WORK(&mdp->wdt_work, mtk_mdp_wdt_worker); + + ret = v4l2_device_register(dev, &mdp->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register v4l2 device\n"); + ret = -EINVAL; + goto err_dev_register; + } + + ret = mtk_mdp_register_m2m_device(mdp); + if (ret) { + v4l2_err(&mdp->v4l2_dev, "Failed to init mem2mem device\n"); + goto err_m2m_register; + } + + mdp->vpu_dev = vpu_get_plat_device(pdev); + ret = vpu_wdt_reg_handler(mdp->vpu_dev, mtk_mdp_reset_handler, mdp, + VPU_RST_MDP); + if (ret) { + dev_err(&pdev->dev, "Failed to register reset handler\n"); + goto err_m2m_register; + } + + platform_set_drvdata(pdev, mdp); + + ret = vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "Failed to set vb2 dma mag seg size\n"); + goto err_m2m_register; + } + + pm_runtime_enable(dev); + dev_dbg(dev, "mdp-%d registered successfully\n", mdp->id); + + return 0; + +err_m2m_register: + v4l2_device_unregister(&mdp->v4l2_dev); + +err_dev_register: + destroy_workqueue(mdp->wdt_wq); + +err_alloc_wdt_wq: + destroy_workqueue(mdp->job_wq); + +err_alloc_job_wq: + +err_comp: + list_for_each_entry_safe(comp, comp_temp, &mdp->comp_list, node) { + mtk_mdp_unregister_component(mdp, comp); + mtk_mdp_comp_deinit(dev, comp); + } + + dev_dbg(dev, "err %d\n", ret); + return ret; +} + +static int mtk_mdp_remove(struct platform_device *pdev) +{ + struct mtk_mdp_dev *mdp = platform_get_drvdata(pdev); + struct mtk_mdp_comp *comp, *comp_temp; + + pm_runtime_disable(&pdev->dev); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); + mtk_mdp_unregister_m2m_device(mdp); + v4l2_device_unregister(&mdp->v4l2_dev); + + destroy_workqueue(mdp->wdt_wq); + + destroy_workqueue(mdp->job_wq); + + list_for_each_entry_safe(comp, comp_temp, &mdp->comp_list, node) { + mtk_mdp_unregister_component(mdp, comp); + mtk_mdp_comp_deinit(&pdev->dev, comp); + } + + dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name); + return 0; +} + +static int __maybe_unused mtk_mdp_pm_suspend(struct device *dev) +{ + struct mtk_mdp_dev *mdp = dev_get_drvdata(dev); + + mtk_mdp_clock_off(mdp); + + return 0; +} + +static int __maybe_unused mtk_mdp_pm_resume(struct device *dev) +{ + struct mtk_mdp_dev *mdp = dev_get_drvdata(dev); + + mtk_mdp_clock_on(mdp); + + return 0; +} + +static int __maybe_unused mtk_mdp_suspend(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return mtk_mdp_pm_suspend(dev); +} + +static int __maybe_unused mtk_mdp_resume(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return mtk_mdp_pm_resume(dev); +} + +static const struct dev_pm_ops mtk_mdp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mtk_mdp_suspend, mtk_mdp_resume) + SET_RUNTIME_PM_OPS(mtk_mdp_pm_suspend, mtk_mdp_pm_resume, NULL) +}; + +static struct platform_driver mtk_mdp_driver = { + .probe = mtk_mdp_probe, + .remove = mtk_mdp_remove, + .driver = { + .name = MTK_MDP_MODULE_NAME, + .pm = &mtk_mdp_pm_ops, + .of_match_table = mtk_mdp_of_ids, + } +}; + +module_platform_driver(mtk_mdp_driver); + +MODULE_AUTHOR("Houlong Wei "); +MODULE_DESCRIPTION("Mediatek image processor driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_core.h b/drivers/media/platform/mediatek/mdp/mtk_mdp_core.h new file mode 100644 index 000000000..a6e6dc363 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_core.h @@ -0,0 +1,256 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2016 MediaTek Inc. + * Author: Houlong Wei + * Ming Hsiu Tsai + */ + +#ifndef __MTK_MDP_CORE_H__ +#define __MTK_MDP_CORE_H__ + +#include +#include +#include +#include +#include +#include + +#include "mtk_mdp_vpu.h" +#include "mtk_mdp_comp.h" + + +#define MTK_MDP_MODULE_NAME "mtk-mdp" + +#define MTK_MDP_SHUTDOWN_TIMEOUT ((100*HZ)/1000) /* 100ms */ +#define MTK_MDP_MAX_CTRL_NUM 10 + +#define MTK_MDP_FMT_FLAG_OUTPUT BIT(0) +#define MTK_MDP_FMT_FLAG_CAPTURE BIT(1) + +#define MTK_MDP_VPU_INIT BIT(0) +#define MTK_MDP_CTX_ERROR BIT(5) + +/** + * struct mtk_mdp_pix_align - alignment of image + * @org_w: source alignment of width + * @org_h: source alignment of height + * @target_w: dst alignment of width + * @target_h: dst alignment of height + */ +struct mtk_mdp_pix_align { + u16 org_w; + u16 org_h; + u16 target_w; + u16 target_h; +}; + +/** + * struct mtk_mdp_fmt - the driver's internal color format data + * @pixelformat: the fourcc code for this format, 0 if not applicable + * @num_planes: number of physically non-contiguous data planes + * @num_comp: number of logical data planes + * @depth: per plane driver's private 'number of bits per pixel' + * @row_depth: per plane driver's private 'number of bits per pixel per row' + * @flags: flags indicating which operation mode format applies to + * MTK_MDP_FMT_FLAG_OUTPUT is used in OUTPUT stream + * MTK_MDP_FMT_FLAG_CAPTURE is used in CAPTURE stream + * @align: pointer to a pixel alignment struct, NULL if using default value + */ +struct mtk_mdp_fmt { + u32 pixelformat; + u16 num_planes; + u16 num_comp; + u8 depth[VIDEO_MAX_PLANES]; + u8 row_depth[VIDEO_MAX_PLANES]; + u32 flags; + struct mtk_mdp_pix_align *align; +}; + +/** + * struct mtk_mdp_addr - the image processor physical address set + * @addr: address of planes + */ +struct mtk_mdp_addr { + dma_addr_t addr[MTK_MDP_MAX_NUM_PLANE]; +}; + +/* struct mtk_mdp_ctrls - the image processor control set + * @rotate: rotation degree + * @hflip: horizontal flip + * @vflip: vertical flip + * @global_alpha: the alpha value of current frame + */ +struct mtk_mdp_ctrls { + struct v4l2_ctrl *rotate; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *global_alpha; +}; + +/** + * struct mtk_mdp_frame - source/target frame properties + * @width: SRC : SRCIMG_WIDTH, DST : OUTPUTDMA_WHOLE_IMG_WIDTH + * @height: SRC : SRCIMG_HEIGHT, DST : OUTPUTDMA_WHOLE_IMG_HEIGHT + * @crop: cropped(source)/scaled(destination) size + * @payload: image size in bytes (w x h x bpp) + * @pitch: bytes per line of image in memory + * @addr: image frame buffer physical addresses + * @fmt: color format pointer + * @alpha: frame's alpha value + */ +struct mtk_mdp_frame { + u32 width; + u32 height; + struct v4l2_rect crop; + unsigned long payload[VIDEO_MAX_PLANES]; + unsigned int pitch[VIDEO_MAX_PLANES]; + struct mtk_mdp_addr addr; + const struct mtk_mdp_fmt *fmt; + u8 alpha; +}; + +/** + * struct mtk_mdp_variant - image processor variant information + * @pix_max: maximum limit of image size + * @pix_min: minimum limit of image size + * @pix_align: alignment of image + * @h_scale_up_max: maximum scale-up in horizontal + * @v_scale_up_max: maximum scale-up in vertical + * @h_scale_down_max: maximum scale-down in horizontal + * @v_scale_down_max: maximum scale-down in vertical + */ +struct mtk_mdp_variant { + struct mtk_mdp_pix_limit *pix_max; + struct mtk_mdp_pix_limit *pix_min; + struct mtk_mdp_pix_align *pix_align; + u16 h_scale_up_max; + u16 v_scale_up_max; + u16 h_scale_down_max; + u16 v_scale_down_max; +}; + +/** + * struct mtk_mdp_dev - abstraction for image processor entity + * @lock: the mutex protecting this data structure + * @vpulock: the mutex protecting the communication with VPU + * @pdev: pointer to the image processor platform device + * @variant: the IP variant information + * @id: image processor device index (0..MTK_MDP_MAX_DEVS) + * @comp_list: list of MDP function components + * @m2m_dev: v4l2 memory-to-memory device data + * @ctx_list: list of struct mtk_mdp_ctx + * @vdev: video device for image processor driver + * @v4l2_dev: V4L2 device to register video devices for. + * @job_wq: processor work queue + * @vpu_dev: VPU platform device + * @ctx_num: counter of active MTK MDP context + * @id_counter: An integer id given to the next opened context + * @wdt_wq: work queue for VPU watchdog + * @wdt_work: worker for VPU watchdog + */ +struct mtk_mdp_dev { + struct mutex lock; + struct mutex vpulock; + struct platform_device *pdev; + struct mtk_mdp_variant *variant; + u16 id; + struct list_head comp_list; + struct v4l2_m2m_dev *m2m_dev; + struct list_head ctx_list; + struct video_device *vdev; + struct v4l2_device v4l2_dev; + struct workqueue_struct *job_wq; + struct platform_device *vpu_dev; + int ctx_num; + unsigned long id_counter; + struct workqueue_struct *wdt_wq; + struct work_struct wdt_work; +}; + +/** + * struct mtk_mdp_ctx - the device context data + * @list: link to ctx_list of mtk_mdp_dev + * @s_frame: source frame properties + * @d_frame: destination frame properties + * @id: index of the context that this structure describes + * @flags: additional flags for image conversion + * @state: flags to keep track of user configuration + * Protected by slock + * @rotation: rotates the image by specified angle + * @hflip: mirror the picture horizontally + * @vflip: mirror the picture vertically + * @mdp_dev: the image processor device this context applies to + * @m2m_ctx: memory-to-memory device context + * @fh: v4l2 file handle + * @ctrl_handler: v4l2 controls handler + * @ctrls: image processor control set + * @ctrls_rdy: true if the control handler is initialized + * @colorspace: enum v4l2_colorspace; supplemental to pixelformat + * @ycbcr_enc: enum v4l2_ycbcr_encoding, Y'CbCr encoding + * @xfer_func: enum v4l2_xfer_func, colorspace transfer function + * @quant: enum v4l2_quantization, colorspace quantization + * @vpu: VPU instance + * @slock: the mutex protecting mtp_mdp_ctx.state + * @work: worker for image processing + */ +struct mtk_mdp_ctx { + struct list_head list; + struct mtk_mdp_frame s_frame; + struct mtk_mdp_frame d_frame; + u32 flags; + u32 state; + int id; + int rotation; + u32 hflip:1; + u32 vflip:1; + struct mtk_mdp_dev *mdp_dev; + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; + struct mtk_mdp_ctrls ctrls; + bool ctrls_rdy; + enum v4l2_colorspace colorspace; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_xfer_func xfer_func; + enum v4l2_quantization quant; + + struct mtk_mdp_vpu vpu; + struct mutex slock; + struct work_struct work; +}; + +extern int mtk_mdp_dbg_level; + +void mtk_mdp_register_component(struct mtk_mdp_dev *mdp, + struct mtk_mdp_comp *comp); + +void mtk_mdp_unregister_component(struct mtk_mdp_dev *mdp, + struct mtk_mdp_comp *comp); + +#if defined(DEBUG) + +#define mtk_mdp_dbg(level, fmt, args...) \ + do { \ + if (mtk_mdp_dbg_level >= level) \ + pr_info("[MTK_MDP] level=%d %s(),%d: " fmt "\n", \ + level, __func__, __LINE__, ##args); \ + } while (0) + +#define mtk_mdp_err(fmt, args...) \ + pr_err("[MTK_MDP][ERROR] %s:%d: " fmt "\n", __func__, __LINE__, \ + ##args) + + +#define mtk_mdp_dbg_enter() mtk_mdp_dbg(3, "+") +#define mtk_mdp_dbg_leave() mtk_mdp_dbg(3, "-") + +#else + +#define mtk_mdp_dbg(level, fmt, args...) {} +#define mtk_mdp_err(fmt, args...) +#define mtk_mdp_dbg_enter() +#define mtk_mdp_dbg_leave() + +#endif + +#endif /* __MTK_MDP_CORE_H__ */ diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h b/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h new file mode 100644 index 000000000..b810c9669 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_ipi.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2016 MediaTek Inc. + * Author: Houlong Wei + * Ming Hsiu Tsai + */ + +#ifndef __MTK_MDP_IPI_H__ +#define __MTK_MDP_IPI_H__ + +#define MTK_MDP_MAX_NUM_PLANE 3 + +enum mdp_ipi_msgid { + AP_MDP_INIT = 0xd000, + AP_MDP_DEINIT = 0xd001, + AP_MDP_PROCESS = 0xd002, + + VPU_MDP_INIT_ACK = 0xe000, + VPU_MDP_DEINIT_ACK = 0xe001, + VPU_MDP_PROCESS_ACK = 0xe002 +}; + +#pragma pack(push, 4) + +/** + * struct mdp_ipi_init - for AP_MDP_INIT + * @msg_id : AP_MDP_INIT + * @ipi_id : IPI_MDP + * @ap_inst : AP mtk_mdp_vpu address + */ +struct mdp_ipi_init { + uint32_t msg_id; + uint32_t ipi_id; + uint64_t ap_inst; +}; + +/** + * struct mdp_ipi_comm - for AP_MDP_PROCESS, AP_MDP_DEINIT + * @msg_id : AP_MDP_PROCESS, AP_MDP_DEINIT + * @ipi_id : IPI_MDP + * @ap_inst : AP mtk_mdp_vpu address + * @vpu_inst_addr : VPU MDP instance address + * @padding : Alignment padding + */ +struct mdp_ipi_comm { + uint32_t msg_id; + uint32_t ipi_id; + uint64_t ap_inst; + uint32_t vpu_inst_addr; + uint32_t padding; +}; + +/** + * struct mdp_ipi_comm_ack - for VPU_MDP_DEINIT_ACK, VPU_MDP_PROCESS_ACK + * @msg_id : VPU_MDP_DEINIT_ACK, VPU_MDP_PROCESS_ACK + * @ipi_id : IPI_MDP + * @ap_inst : AP mtk_mdp_vpu address + * @vpu_inst_addr : VPU MDP instance address + * @status : VPU exeuction result + */ +struct mdp_ipi_comm_ack { + uint32_t msg_id; + uint32_t ipi_id; + uint64_t ap_inst; + uint32_t vpu_inst_addr; + int32_t status; +}; + +/** + * struct mdp_config - configured for source/destination image + * @x : left + * @y : top + * @w : width + * @h : height + * @w_stride : bytes in horizontal + * @h_stride : bytes in vertical + * @crop_x : cropped left + * @crop_y : cropped top + * @crop_w : cropped width + * @crop_h : cropped height + * @format : color format + */ +struct mdp_config { + int32_t x; + int32_t y; + int32_t w; + int32_t h; + int32_t w_stride; + int32_t h_stride; + int32_t crop_x; + int32_t crop_y; + int32_t crop_w; + int32_t crop_h; + int32_t format; +}; + +struct mdp_buffer { + uint64_t addr_mva[MTK_MDP_MAX_NUM_PLANE]; + int32_t plane_size[MTK_MDP_MAX_NUM_PLANE]; + int32_t plane_num; +}; + +struct mdp_config_misc { + int32_t orientation; /* 0, 90, 180, 270 */ + int32_t hflip; /* 1 will enable the flip */ + int32_t vflip; /* 1 will enable the flip */ + int32_t alpha; /* global alpha */ +}; + +struct mdp_process_vsi { + struct mdp_config src_config; + struct mdp_buffer src_buffer; + struct mdp_config dst_config; + struct mdp_buffer dst_buffer; + struct mdp_config_misc misc; +}; + +#pragma pack(pop) + +#endif /* __MTK_MDP_IPI_H__ */ diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.c new file mode 100644 index 000000000..f14779e75 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.c @@ -0,0 +1,1229 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2016 MediaTek Inc. + * Author: Houlong Wei + * Ming Hsiu Tsai + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_mdp_core.h" +#include "mtk_mdp_m2m.h" +#include "mtk_mdp_regs.h" +#include "mtk_vpu.h" + + +/** + * struct mtk_mdp_pix_limit - image pixel size limits + * @org_w: source pixel width + * @org_h: source pixel height + * @target_rot_dis_w: pixel dst scaled width with the rotator is off + * @target_rot_dis_h: pixel dst scaled height with the rotator is off + * @target_rot_en_w: pixel dst scaled width with the rotator is on + * @target_rot_en_h: pixel dst scaled height with the rotator is on + */ +struct mtk_mdp_pix_limit { + u16 org_w; + u16 org_h; + u16 target_rot_dis_w; + u16 target_rot_dis_h; + u16 target_rot_en_w; + u16 target_rot_en_h; +}; + +static struct mtk_mdp_pix_align mtk_mdp_size_align = { + .org_w = 16, + .org_h = 16, + .target_w = 2, + .target_h = 2, +}; + +static const struct mtk_mdp_fmt mtk_mdp_formats[] = { + { + .pixelformat = V4L2_PIX_FMT_MT21C, + .depth = { 8, 4 }, + .row_depth = { 8, 8 }, + .num_planes = 2, + .num_comp = 2, + .align = &mtk_mdp_size_align, + .flags = MTK_MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_NV12M, + .depth = { 8, 4 }, + .row_depth = { 8, 8 }, + .num_planes = 2, + .num_comp = 2, + .flags = MTK_MDP_FMT_FLAG_OUTPUT | + MTK_MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_YUV420M, + .depth = { 8, 2, 2 }, + .row_depth = { 8, 4, 4 }, + .num_planes = 3, + .num_comp = 3, + .flags = MTK_MDP_FMT_FLAG_OUTPUT | + MTK_MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_YVU420, + .depth = { 12 }, + .row_depth = { 8 }, + .num_planes = 1, + .num_comp = 3, + .flags = MTK_MDP_FMT_FLAG_OUTPUT | + MTK_MDP_FMT_FLAG_CAPTURE, + } +}; + +static struct mtk_mdp_pix_limit mtk_mdp_size_max = { + .target_rot_dis_w = 4096, + .target_rot_dis_h = 4096, + .target_rot_en_w = 4096, + .target_rot_en_h = 4096, +}; + +static struct mtk_mdp_pix_limit mtk_mdp_size_min = { + .org_w = 16, + .org_h = 16, + .target_rot_dis_w = 16, + .target_rot_dis_h = 16, + .target_rot_en_w = 16, + .target_rot_en_h = 16, +}; + +/* align size for normal raster scan pixel format */ +static struct mtk_mdp_pix_align mtk_mdp_rs_align = { + .org_w = 2, + .org_h = 2, + .target_w = 2, + .target_h = 2, +}; + +static struct mtk_mdp_variant mtk_mdp_default_variant = { + .pix_max = &mtk_mdp_size_max, + .pix_min = &mtk_mdp_size_min, + .pix_align = &mtk_mdp_rs_align, + .h_scale_up_max = 32, + .v_scale_up_max = 32, + .h_scale_down_max = 32, + .v_scale_down_max = 128, +}; + +static const struct mtk_mdp_fmt *mtk_mdp_find_fmt(u32 pixelformat, u32 type) +{ + u32 i, flag; + + flag = V4L2_TYPE_IS_OUTPUT(type) ? MTK_MDP_FMT_FLAG_OUTPUT : + MTK_MDP_FMT_FLAG_CAPTURE; + + for (i = 0; i < ARRAY_SIZE(mtk_mdp_formats); ++i) { + if (!(mtk_mdp_formats[i].flags & flag)) + continue; + if (mtk_mdp_formats[i].pixelformat == pixelformat) + return &mtk_mdp_formats[i]; + } + return NULL; +} + +static const struct mtk_mdp_fmt *mtk_mdp_find_fmt_by_index(u32 index, u32 type) +{ + u32 i, flag, num = 0; + + flag = V4L2_TYPE_IS_OUTPUT(type) ? MTK_MDP_FMT_FLAG_OUTPUT : + MTK_MDP_FMT_FLAG_CAPTURE; + + for (i = 0; i < ARRAY_SIZE(mtk_mdp_formats); ++i) { + if (!(mtk_mdp_formats[i].flags & flag)) + continue; + if (index == num) + return &mtk_mdp_formats[i]; + num++; + } + return NULL; +} + +static void mtk_mdp_bound_align_image(u32 *w, unsigned int wmin, + unsigned int wmax, unsigned int align_w, + u32 *h, unsigned int hmin, + unsigned int hmax, unsigned int align_h) +{ + int org_w, org_h, step_w, step_h; + int walign, halign; + + org_w = *w; + org_h = *h; + walign = ffs(align_w) - 1; + halign = ffs(align_h) - 1; + v4l_bound_align_image(w, wmin, wmax, walign, h, hmin, hmax, halign, 0); + + step_w = 1 << walign; + step_h = 1 << halign; + if (*w < org_w && (*w + step_w) <= wmax) + *w += step_w; + if (*h < org_h && (*h + step_h) <= hmax) + *h += step_h; +} + +static const struct mtk_mdp_fmt *mtk_mdp_try_fmt_mplane(struct mtk_mdp_ctx *ctx, + struct v4l2_format *f) +{ + struct mtk_mdp_dev *mdp = ctx->mdp_dev; + struct mtk_mdp_variant *variant = mdp->variant; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct mtk_mdp_fmt *fmt; + u32 max_w, max_h, align_w, align_h; + u32 min_w, min_h, org_w, org_h; + int i; + + fmt = mtk_mdp_find_fmt(pix_mp->pixelformat, f->type); + if (!fmt) + fmt = mtk_mdp_find_fmt_by_index(0, f->type); + if (!fmt) { + dev_dbg(&ctx->mdp_dev->pdev->dev, + "pixelformat format 0x%X invalid\n", + pix_mp->pixelformat); + return NULL; + } + + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->pixelformat = fmt->pixelformat; + if (V4L2_TYPE_IS_CAPTURE(f->type)) { + pix_mp->colorspace = ctx->colorspace; + pix_mp->xfer_func = ctx->xfer_func; + pix_mp->ycbcr_enc = ctx->ycbcr_enc; + pix_mp->quantization = ctx->quant; + } + + max_w = variant->pix_max->target_rot_dis_w; + max_h = variant->pix_max->target_rot_dis_h; + + if (fmt->align == NULL) { + /* use default alignment */ + align_w = variant->pix_align->org_w; + align_h = variant->pix_align->org_h; + } else { + align_w = fmt->align->org_w; + align_h = fmt->align->org_h; + } + + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + min_w = variant->pix_min->org_w; + min_h = variant->pix_min->org_h; + } else { + min_w = variant->pix_min->target_rot_dis_w; + min_h = variant->pix_min->target_rot_dis_h; + } + + mtk_mdp_dbg(2, "[%d] type:%d, wxh:%ux%u, align:%ux%u, max:%ux%u", + ctx->id, f->type, pix_mp->width, pix_mp->height, + align_w, align_h, max_w, max_h); + /* + * To check if image size is modified to adjust parameter against + * hardware abilities + */ + org_w = pix_mp->width; + org_h = pix_mp->height; + + mtk_mdp_bound_align_image(&pix_mp->width, min_w, max_w, align_w, + &pix_mp->height, min_h, max_h, align_h); + + if (org_w != pix_mp->width || org_h != pix_mp->height) + mtk_mdp_dbg(1, "[%d] size change:%ux%u to %ux%u", ctx->id, + org_w, org_h, pix_mp->width, pix_mp->height); + pix_mp->num_planes = fmt->num_planes; + + for (i = 0; i < pix_mp->num_planes; ++i) { + int bpl = (pix_mp->width * fmt->row_depth[i]) / 8; + int sizeimage = (pix_mp->width * pix_mp->height * + fmt->depth[i]) / 8; + + pix_mp->plane_fmt[i].bytesperline = bpl; + if (pix_mp->plane_fmt[i].sizeimage < sizeimage) + pix_mp->plane_fmt[i].sizeimage = sizeimage; + mtk_mdp_dbg(2, "[%d] p%d, bpl:%d, sizeimage:%u (%u)", ctx->id, + i, bpl, pix_mp->plane_fmt[i].sizeimage, sizeimage); + } + + return fmt; +} + +static struct mtk_mdp_frame *mtk_mdp_ctx_get_frame(struct mtk_mdp_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->s_frame; + return &ctx->d_frame; +} + +static void mtk_mdp_check_crop_change(u32 new_w, u32 new_h, u32 *w, u32 *h) +{ + if (new_w != *w || new_h != *h) { + mtk_mdp_dbg(1, "size change:%dx%d to %dx%d", + *w, *h, new_w, new_h); + + *w = new_w; + *h = new_h; + } +} + +static int mtk_mdp_try_crop(struct mtk_mdp_ctx *ctx, u32 type, + struct v4l2_rect *r) +{ + struct mtk_mdp_frame *frame; + struct mtk_mdp_dev *mdp = ctx->mdp_dev; + struct mtk_mdp_variant *variant = mdp->variant; + u32 align_w, align_h, new_w, new_h; + u32 min_w, min_h, max_w, max_h; + + if (r->top < 0 || r->left < 0) { + dev_err(&ctx->mdp_dev->pdev->dev, + "doesn't support negative values for top & left\n"); + return -EINVAL; + } + + mtk_mdp_dbg(2, "[%d] type:%d, set wxh:%dx%d", ctx->id, type, + r->width, r->height); + + frame = mtk_mdp_ctx_get_frame(ctx, type); + max_w = frame->width; + max_h = frame->height; + new_w = r->width; + new_h = r->height; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + align_w = 1; + align_h = 1; + min_w = 64; + min_h = 32; + } else { + align_w = variant->pix_align->target_w; + align_h = variant->pix_align->target_h; + if (ctx->ctrls.rotate->val == 90 || + ctx->ctrls.rotate->val == 270) { + max_w = frame->height; + max_h = frame->width; + min_w = variant->pix_min->target_rot_en_w; + min_h = variant->pix_min->target_rot_en_h; + new_w = r->height; + new_h = r->width; + } else { + min_w = variant->pix_min->target_rot_dis_w; + min_h = variant->pix_min->target_rot_dis_h; + } + } + + mtk_mdp_dbg(2, "[%d] align:%dx%d, min:%dx%d, new:%dx%d", ctx->id, + align_w, align_h, min_w, min_h, new_w, new_h); + + mtk_mdp_bound_align_image(&new_w, min_w, max_w, align_w, + &new_h, min_h, max_h, align_h); + + if (V4L2_TYPE_IS_CAPTURE(type) && + (ctx->ctrls.rotate->val == 90 || ctx->ctrls.rotate->val == 270)) + mtk_mdp_check_crop_change(new_h, new_w, + &r->width, &r->height); + else + mtk_mdp_check_crop_change(new_w, new_h, + &r->width, &r->height); + + /* adjust left/top if cropping rectangle is out of bounds */ + /* Need to add code to algin left value with 2's multiple */ + if (r->left + new_w > max_w) + r->left = max_w - new_w; + if (r->top + new_h > max_h) + r->top = max_h - new_h; + + if (r->left & 1) + r->left -= 1; + + mtk_mdp_dbg(2, "[%d] crop l,t,w,h:%d,%d,%d,%d, max:%dx%d", ctx->id, + r->left, r->top, r->width, + r->height, max_w, max_h); + return 0; +} + +static inline struct mtk_mdp_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct mtk_mdp_ctx, fh); +} + +static inline struct mtk_mdp_ctx *ctrl_to_ctx(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mtk_mdp_ctx, ctrl_handler); +} + +void mtk_mdp_ctx_state_lock_set(struct mtk_mdp_ctx *ctx, u32 state) +{ + mutex_lock(&ctx->slock); + ctx->state |= state; + mutex_unlock(&ctx->slock); +} + +static bool mtk_mdp_ctx_state_is_set(struct mtk_mdp_ctx *ctx, u32 mask) +{ + bool ret; + + mutex_lock(&ctx->slock); + ret = (ctx->state & mask) == mask; + mutex_unlock(&ctx->slock); + return ret; +} + +static void mtk_mdp_set_frame_size(struct mtk_mdp_frame *frame, int width, + int height) +{ + frame->width = width; + frame->height = height; + frame->crop.width = width; + frame->crop.height = height; + frame->crop.left = 0; + frame->crop.top = 0; +} + +static int mtk_mdp_m2m_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mtk_mdp_ctx *ctx = q->drv_priv; + int ret; + + ret = pm_runtime_resume_and_get(&ctx->mdp_dev->pdev->dev); + if (ret < 0) + mtk_mdp_dbg(1, "[%d] pm_runtime_resume_and_get failed:%d", + ctx->id, ret); + + return ret; +} + +static void *mtk_mdp_m2m_buf_remove(struct mtk_mdp_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + else + return v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); +} + +static void mtk_mdp_m2m_stop_streaming(struct vb2_queue *q) +{ + struct mtk_mdp_ctx *ctx = q->drv_priv; + struct vb2_buffer *vb; + + vb = mtk_mdp_m2m_buf_remove(ctx, q->type); + while (vb != NULL) { + v4l2_m2m_buf_done(to_vb2_v4l2_buffer(vb), VB2_BUF_STATE_ERROR); + vb = mtk_mdp_m2m_buf_remove(ctx, q->type); + } + + pm_runtime_put(&ctx->mdp_dev->pdev->dev); +} + +/* The color format (num_planes) must be already configured. */ +static void mtk_mdp_prepare_addr(struct mtk_mdp_ctx *ctx, + struct vb2_buffer *vb, + struct mtk_mdp_frame *frame, + struct mtk_mdp_addr *addr) +{ + u32 pix_size, planes, i; + + pix_size = frame->width * frame->height; + planes = min_t(u32, frame->fmt->num_planes, ARRAY_SIZE(addr->addr)); + for (i = 0; i < planes; i++) + addr->addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + + if (planes == 1) { + if (frame->fmt->pixelformat == V4L2_PIX_FMT_YVU420) { + addr->addr[1] = (dma_addr_t)(addr->addr[0] + pix_size); + addr->addr[2] = (dma_addr_t)(addr->addr[1] + + (pix_size >> 2)); + } else { + dev_err(&ctx->mdp_dev->pdev->dev, + "Invalid pixelformat:0x%x\n", + frame->fmt->pixelformat); + } + } + mtk_mdp_dbg(3, "[%d] planes:%d, size:%d, addr:%p,%p,%p", + ctx->id, planes, pix_size, (void *)addr->addr[0], + (void *)addr->addr[1], (void *)addr->addr[2]); +} + +static void mtk_mdp_m2m_get_bufs(struct mtk_mdp_ctx *ctx) +{ + struct mtk_mdp_frame *s_frame, *d_frame; + struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf; + + s_frame = &ctx->s_frame; + d_frame = &ctx->d_frame; + + src_vbuf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + mtk_mdp_prepare_addr(ctx, &src_vbuf->vb2_buf, s_frame, &s_frame->addr); + + dst_vbuf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + mtk_mdp_prepare_addr(ctx, &dst_vbuf->vb2_buf, d_frame, &d_frame->addr); + + dst_vbuf->vb2_buf.timestamp = src_vbuf->vb2_buf.timestamp; +} + +static void mtk_mdp_process_done(void *priv, int vb_state) +{ + struct mtk_mdp_dev *mdp = priv; + struct mtk_mdp_ctx *ctx; + struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf; + + ctx = v4l2_m2m_get_curr_priv(mdp->m2m_dev); + if (!ctx) + return; + + src_vbuf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + dst_vbuf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + + dst_vbuf->vb2_buf.timestamp = src_vbuf->vb2_buf.timestamp; + dst_vbuf->timecode = src_vbuf->timecode; + dst_vbuf->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vbuf->flags |= src_vbuf->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + + v4l2_m2m_buf_done(src_vbuf, vb_state); + v4l2_m2m_buf_done(dst_vbuf, vb_state); + v4l2_m2m_job_finish(ctx->mdp_dev->m2m_dev, ctx->m2m_ctx); +} + +static void mtk_mdp_m2m_worker(struct work_struct *work) +{ + struct mtk_mdp_ctx *ctx = + container_of(work, struct mtk_mdp_ctx, work); + struct mtk_mdp_dev *mdp = ctx->mdp_dev; + enum vb2_buffer_state buf_state = VB2_BUF_STATE_ERROR; + int ret; + + if (mtk_mdp_ctx_state_is_set(ctx, MTK_MDP_CTX_ERROR)) { + dev_err(&mdp->pdev->dev, "ctx is in error state"); + goto worker_end; + } + + mtk_mdp_m2m_get_bufs(ctx); + + mtk_mdp_hw_set_input_addr(ctx, &ctx->s_frame.addr); + mtk_mdp_hw_set_output_addr(ctx, &ctx->d_frame.addr); + + mtk_mdp_hw_set_in_size(ctx); + mtk_mdp_hw_set_in_image_format(ctx); + + mtk_mdp_hw_set_out_size(ctx); + mtk_mdp_hw_set_out_image_format(ctx); + + mtk_mdp_hw_set_rotation(ctx); + mtk_mdp_hw_set_global_alpha(ctx); + + ret = mtk_mdp_vpu_process(&ctx->vpu); + if (ret) { + dev_err(&mdp->pdev->dev, "processing failed: %d", ret); + goto worker_end; + } + + buf_state = VB2_BUF_STATE_DONE; + +worker_end: + mtk_mdp_process_done(mdp, buf_state); +} + +static void mtk_mdp_m2m_device_run(void *priv) +{ + struct mtk_mdp_ctx *ctx = priv; + + queue_work(ctx->mdp_dev->job_wq, &ctx->work); +} + +static int mtk_mdp_m2m_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct mtk_mdp_ctx *ctx = vb2_get_drv_priv(vq); + struct mtk_mdp_frame *frame; + int i; + + frame = mtk_mdp_ctx_get_frame(ctx, vq->type); + *num_planes = frame->fmt->num_planes; + for (i = 0; i < frame->fmt->num_planes; i++) + sizes[i] = frame->payload[i]; + mtk_mdp_dbg(2, "[%d] type:%d, planes:%d, buffers:%d, size:%u,%u", + ctx->id, vq->type, *num_planes, *num_buffers, + sizes[0], sizes[1]); + return 0; +} + +static int mtk_mdp_m2m_buf_prepare(struct vb2_buffer *vb) +{ + struct mtk_mdp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mtk_mdp_frame *frame; + int i; + + frame = mtk_mdp_ctx_get_frame(ctx, vb->vb2_queue->type); + + if (!V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + for (i = 0; i < frame->fmt->num_planes; i++) + vb2_set_plane_payload(vb, i, frame->payload[i]); + } + + return 0; +} + +static void mtk_mdp_m2m_buf_queue(struct vb2_buffer *vb) +{ + struct mtk_mdp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->m2m_ctx, to_vb2_v4l2_buffer(vb)); +} + +static const struct vb2_ops mtk_mdp_m2m_qops = { + .queue_setup = mtk_mdp_m2m_queue_setup, + .buf_prepare = mtk_mdp_m2m_buf_prepare, + .buf_queue = mtk_mdp_m2m_buf_queue, + .stop_streaming = mtk_mdp_m2m_stop_streaming, + .start_streaming = mtk_mdp_m2m_start_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int mtk_mdp_m2m_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct mtk_mdp_ctx *ctx = fh_to_ctx(fh); + struct mtk_mdp_dev *mdp = ctx->mdp_dev; + + strscpy(cap->driver, MTK_MDP_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, mdp->pdev->name, sizeof(cap->card)); + strscpy(cap->bus_info, "platform:mt8173", sizeof(cap->bus_info)); + + return 0; +} + +static int mtk_mdp_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + const struct mtk_mdp_fmt *fmt; + + fmt = mtk_mdp_find_fmt_by_index(f->index, type); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixelformat; + + return 0; +} + +static int mtk_mdp_m2m_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return mtk_mdp_enum_fmt(f, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); +} + +static int mtk_mdp_m2m_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return mtk_mdp_enum_fmt(f, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); +} + +static int mtk_mdp_m2m_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mtk_mdp_ctx *ctx = fh_to_ctx(fh); + struct mtk_mdp_frame *frame; + struct v4l2_pix_format_mplane *pix_mp; + int i; + + mtk_mdp_dbg(2, "[%d] type:%d", ctx->id, f->type); + + frame = mtk_mdp_ctx_get_frame(ctx, f->type); + pix_mp = &f->fmt.pix_mp; + + pix_mp->width = frame->width; + pix_mp->height = frame->height; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->pixelformat = frame->fmt->pixelformat; + pix_mp->num_planes = frame->fmt->num_planes; + pix_mp->colorspace = ctx->colorspace; + pix_mp->xfer_func = ctx->xfer_func; + pix_mp->ycbcr_enc = ctx->ycbcr_enc; + pix_mp->quantization = ctx->quant; + mtk_mdp_dbg(2, "[%d] wxh:%dx%d", ctx->id, + pix_mp->width, pix_mp->height); + + for (i = 0; i < pix_mp->num_planes; ++i) { + pix_mp->plane_fmt[i].bytesperline = (frame->width * + frame->fmt->row_depth[i]) / 8; + pix_mp->plane_fmt[i].sizeimage = (frame->width * + frame->height * frame->fmt->depth[i]) / 8; + + mtk_mdp_dbg(2, "[%d] p%d, bpl:%d, sizeimage:%d", ctx->id, i, + pix_mp->plane_fmt[i].bytesperline, + pix_mp->plane_fmt[i].sizeimage); + } + + return 0; +} + +static int mtk_mdp_m2m_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mtk_mdp_ctx *ctx = fh_to_ctx(fh); + + if (!mtk_mdp_try_fmt_mplane(ctx, f)) + return -EINVAL; + return 0; +} + +static int mtk_mdp_m2m_s_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mtk_mdp_ctx *ctx = fh_to_ctx(fh); + struct vb2_queue *vq; + struct mtk_mdp_frame *frame; + struct v4l2_pix_format_mplane *pix_mp; + const struct mtk_mdp_fmt *fmt; + int i; + + mtk_mdp_dbg(2, "[%d] type:%d", ctx->id, f->type); + + frame = mtk_mdp_ctx_get_frame(ctx, f->type); + fmt = mtk_mdp_try_fmt_mplane(ctx, f); + if (!fmt) { + mtk_mdp_err("[%d] try_fmt failed, type:%d", ctx->id, f->type); + return -EINVAL; + } + frame->fmt = fmt; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (vb2_is_streaming(vq)) { + dev_info(&ctx->mdp_dev->pdev->dev, "queue %d busy", f->type); + return -EBUSY; + } + + pix_mp = &f->fmt.pix_mp; + for (i = 0; i < frame->fmt->num_planes; i++) { + frame->payload[i] = pix_mp->plane_fmt[i].sizeimage; + frame->pitch[i] = pix_mp->plane_fmt[i].bytesperline; + } + + mtk_mdp_set_frame_size(frame, pix_mp->width, pix_mp->height); + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + ctx->colorspace = pix_mp->colorspace; + ctx->xfer_func = pix_mp->xfer_func; + ctx->ycbcr_enc = pix_mp->ycbcr_enc; + ctx->quant = pix_mp->quantization; + } + + mtk_mdp_dbg(2, "[%d] type:%d, frame:%dx%d", ctx->id, f->type, + frame->width, frame->height); + + return 0; +} + +static int mtk_mdp_m2m_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *reqbufs) +{ + struct mtk_mdp_ctx *ctx = fh_to_ctx(fh); + + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int mtk_mdp_m2m_streamon(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct mtk_mdp_ctx *ctx = fh_to_ctx(fh); + int ret; + + if (!mtk_mdp_ctx_state_is_set(ctx, MTK_MDP_VPU_INIT)) { + ret = mtk_mdp_vpu_init(&ctx->vpu); + if (ret < 0) { + dev_err(&ctx->mdp_dev->pdev->dev, + "vpu init failed %d\n", + ret); + return -EINVAL; + } + mtk_mdp_ctx_state_lock_set(ctx, MTK_MDP_VPU_INIT); + } + + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static inline bool mtk_mdp_is_target_compose(u32 target) +{ + if (target == V4L2_SEL_TGT_COMPOSE_DEFAULT + || target == V4L2_SEL_TGT_COMPOSE_BOUNDS + || target == V4L2_SEL_TGT_COMPOSE) + return true; + return false; +} + +static inline bool mtk_mdp_is_target_crop(u32 target) +{ + if (target == V4L2_SEL_TGT_CROP_DEFAULT + || target == V4L2_SEL_TGT_CROP_BOUNDS + || target == V4L2_SEL_TGT_CROP) + return true; + return false; +} + +static int mtk_mdp_m2m_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct mtk_mdp_frame *frame; + struct mtk_mdp_ctx *ctx = fh_to_ctx(fh); + bool valid = false; + + if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + if (mtk_mdp_is_target_compose(s->target)) + valid = true; + } else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (mtk_mdp_is_target_crop(s->target)) + valid = true; + } + if (!valid) { + mtk_mdp_dbg(1, "[%d] invalid type:%d,%u", ctx->id, s->type, + s->target); + return -EINVAL; + } + + frame = mtk_mdp_ctx_get_frame(ctx, s->type); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->width; + s->r.height = frame->height; + return 0; + + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_CROP: + s->r.left = frame->crop.left; + s->r.top = frame->crop.top; + s->r.width = frame->crop.width; + s->r.height = frame->crop.height; + return 0; + } + + return -EINVAL; +} + +static int mtk_mdp_check_scaler_ratio(struct mtk_mdp_variant *var, int src_w, + int src_h, int dst_w, int dst_h, int rot) +{ + int tmp_w, tmp_h; + + if (rot == 90 || rot == 270) { + tmp_w = dst_h; + tmp_h = dst_w; + } else { + tmp_w = dst_w; + tmp_h = dst_h; + } + + if ((src_w / tmp_w) > var->h_scale_down_max || + (src_h / tmp_h) > var->v_scale_down_max || + (tmp_w / src_w) > var->h_scale_up_max || + (tmp_h / src_h) > var->v_scale_up_max) + return -EINVAL; + + return 0; +} + +static int mtk_mdp_m2m_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct mtk_mdp_frame *frame; + struct mtk_mdp_ctx *ctx = fh_to_ctx(fh); + struct v4l2_rect new_r; + struct mtk_mdp_variant *variant = ctx->mdp_dev->variant; + int ret; + bool valid = false; + + if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + if (s->target == V4L2_SEL_TGT_COMPOSE) + valid = true; + } else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (s->target == V4L2_SEL_TGT_CROP) + valid = true; + } + if (!valid) { + mtk_mdp_dbg(1, "[%d] invalid type:%d,%u", ctx->id, s->type, + s->target); + return -EINVAL; + } + + new_r = s->r; + ret = mtk_mdp_try_crop(ctx, s->type, &new_r); + if (ret) + return ret; + + if (mtk_mdp_is_target_crop(s->target)) + frame = &ctx->s_frame; + else + frame = &ctx->d_frame; + + /* Check to see if scaling ratio is within supported range */ + if (V4L2_TYPE_IS_OUTPUT(s->type)) + ret = mtk_mdp_check_scaler_ratio(variant, new_r.width, + new_r.height, ctx->d_frame.crop.width, + ctx->d_frame.crop.height, + ctx->ctrls.rotate->val); + else + ret = mtk_mdp_check_scaler_ratio(variant, + ctx->s_frame.crop.width, + ctx->s_frame.crop.height, new_r.width, + new_r.height, ctx->ctrls.rotate->val); + + if (ret) { + dev_info(&ctx->mdp_dev->pdev->dev, + "Out of scaler range"); + return -EINVAL; + } + + s->r = new_r; + frame->crop = new_r; + + return 0; +} + +static const struct v4l2_ioctl_ops mtk_mdp_m2m_ioctl_ops = { + .vidioc_querycap = mtk_mdp_m2m_querycap, + .vidioc_enum_fmt_vid_cap = mtk_mdp_m2m_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = mtk_mdp_m2m_enum_fmt_vid_out, + .vidioc_g_fmt_vid_cap_mplane = mtk_mdp_m2m_g_fmt_mplane, + .vidioc_g_fmt_vid_out_mplane = mtk_mdp_m2m_g_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = mtk_mdp_m2m_try_fmt_mplane, + .vidioc_try_fmt_vid_out_mplane = mtk_mdp_m2m_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = mtk_mdp_m2m_s_fmt_mplane, + .vidioc_s_fmt_vid_out_mplane = mtk_mdp_m2m_s_fmt_mplane, + .vidioc_reqbufs = mtk_mdp_m2m_reqbufs, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = mtk_mdp_m2m_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_g_selection = mtk_mdp_m2m_g_selection, + .vidioc_s_selection = mtk_mdp_m2m_s_selection +}; + +static int mtk_mdp_m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mtk_mdp_ctx *ctx = priv; + int ret; + + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->ops = &mtk_mdp_m2m_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->dev = &ctx->mdp_dev->pdev->dev; + src_vq->lock = &ctx->mdp_dev->lock; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->ops = &mtk_mdp_m2m_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->dev = &ctx->mdp_dev->pdev->dev; + dst_vq->lock = &ctx->mdp_dev->lock; + + return vb2_queue_init(dst_vq); +} + +static int mtk_mdp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mtk_mdp_ctx *ctx = ctrl_to_ctx(ctrl); + struct mtk_mdp_dev *mdp = ctx->mdp_dev; + struct mtk_mdp_variant *variant = mdp->variant; + int ret = 0; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->hflip = ctrl->val; + break; + case V4L2_CID_VFLIP: + ctx->vflip = ctrl->val; + break; + case V4L2_CID_ROTATE: + ret = mtk_mdp_check_scaler_ratio(variant, + ctx->s_frame.crop.width, + ctx->s_frame.crop.height, + ctx->d_frame.crop.width, + ctx->d_frame.crop.height, + ctx->ctrls.rotate->val); + + if (ret) + return -EINVAL; + + ctx->rotation = ctrl->val; + break; + case V4L2_CID_ALPHA_COMPONENT: + ctx->d_frame.alpha = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops mtk_mdp_ctrl_ops = { + .s_ctrl = mtk_mdp_s_ctrl, +}; + +static int mtk_mdp_ctrls_create(struct mtk_mdp_ctx *ctx) +{ + v4l2_ctrl_handler_init(&ctx->ctrl_handler, MTK_MDP_MAX_CTRL_NUM); + + ctx->ctrls.rotate = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &mtk_mdp_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0); + ctx->ctrls.hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &mtk_mdp_ctrl_ops, + V4L2_CID_HFLIP, + 0, 1, 1, 0); + ctx->ctrls.vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &mtk_mdp_ctrl_ops, + V4L2_CID_VFLIP, + 0, 1, 1, 0); + ctx->ctrls.global_alpha = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &mtk_mdp_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, + 0, 255, 1, 0); + ctx->ctrls_rdy = ctx->ctrl_handler.error == 0; + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + dev_err(&ctx->mdp_dev->pdev->dev, + "Failed to create control handlers\n"); + return err; + } + + return 0; +} + +static void mtk_mdp_set_default_params(struct mtk_mdp_ctx *ctx) +{ + struct mtk_mdp_dev *mdp = ctx->mdp_dev; + struct mtk_mdp_frame *frame; + + frame = mtk_mdp_ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + frame->fmt = mtk_mdp_find_fmt_by_index(0, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + frame->width = mdp->variant->pix_min->org_w; + frame->height = mdp->variant->pix_min->org_h; + frame->payload[0] = frame->width * frame->height; + frame->payload[1] = frame->payload[0] / 2; + + frame = mtk_mdp_ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + frame->fmt = mtk_mdp_find_fmt_by_index(0, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + frame->width = mdp->variant->pix_min->target_rot_dis_w; + frame->height = mdp->variant->pix_min->target_rot_dis_h; + frame->payload[0] = frame->width * frame->height; + frame->payload[1] = frame->payload[0] / 2; + +} + +static int mtk_mdp_m2m_open(struct file *file) +{ + struct mtk_mdp_dev *mdp = video_drvdata(file); + struct video_device *vfd = video_devdata(file); + struct mtk_mdp_ctx *ctx = NULL; + int ret; + struct v4l2_format default_format; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (mutex_lock_interruptible(&mdp->lock)) { + ret = -ERESTARTSYS; + goto err_lock; + } + + mutex_init(&ctx->slock); + ctx->id = mdp->id_counter++; + v4l2_fh_init(&ctx->fh, vfd); + file->private_data = &ctx->fh; + ret = mtk_mdp_ctrls_create(ctx); + if (ret) + goto error_ctrls; + + /* Use separate control handler per file handle */ + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + v4l2_fh_add(&ctx->fh); + INIT_LIST_HEAD(&ctx->list); + + ctx->mdp_dev = mdp; + mtk_mdp_set_default_params(ctx); + + INIT_WORK(&ctx->work, mtk_mdp_m2m_worker); + ctx->m2m_ctx = v4l2_m2m_ctx_init(mdp->m2m_dev, ctx, + mtk_mdp_m2m_queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + dev_err(&mdp->pdev->dev, "Failed to initialize m2m context"); + ret = PTR_ERR(ctx->m2m_ctx); + goto error_m2m_ctx; + } + ctx->fh.m2m_ctx = ctx->m2m_ctx; + if (mdp->ctx_num++ == 0) { + ret = vpu_load_firmware(mdp->vpu_dev); + if (ret < 0) { + dev_err(&mdp->pdev->dev, + "vpu_load_firmware failed %d\n", ret); + goto err_load_vpu; + } + + ret = mtk_mdp_vpu_register(mdp->pdev); + if (ret < 0) { + dev_err(&mdp->pdev->dev, + "mdp_vpu register failed %d\n", ret); + goto err_load_vpu; + } + } + + list_add(&ctx->list, &mdp->ctx_list); + mutex_unlock(&mdp->lock); + + /* Default format */ + memset(&default_format, 0, sizeof(default_format)); + default_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + default_format.fmt.pix_mp.width = 32; + default_format.fmt.pix_mp.height = 32; + default_format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420M; + mtk_mdp_m2m_s_fmt_mplane(file, &ctx->fh, &default_format); + default_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + mtk_mdp_m2m_s_fmt_mplane(file, &ctx->fh, &default_format); + + mtk_mdp_dbg(0, "%s [%d]", dev_name(&mdp->pdev->dev), ctx->id); + + return 0; + +err_load_vpu: + mdp->ctx_num--; + v4l2_m2m_ctx_release(ctx->m2m_ctx); +error_m2m_ctx: + v4l2_ctrl_handler_free(&ctx->ctrl_handler); +error_ctrls: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + mutex_unlock(&mdp->lock); +err_lock: + kfree(ctx); + + return ret; +} + +static int mtk_mdp_m2m_release(struct file *file) +{ + struct mtk_mdp_ctx *ctx = fh_to_ctx(file->private_data); + struct mtk_mdp_dev *mdp = ctx->mdp_dev; + + flush_workqueue(mdp->job_wq); + mutex_lock(&mdp->lock); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + mtk_mdp_vpu_deinit(&ctx->vpu); + mdp->ctx_num--; + list_del_init(&ctx->list); + + mtk_mdp_dbg(0, "%s [%d]", dev_name(&mdp->pdev->dev), ctx->id); + + mutex_unlock(&mdp->lock); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations mtk_mdp_m2m_fops = { + .owner = THIS_MODULE, + .open = mtk_mdp_m2m_open, + .release = mtk_mdp_m2m_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct v4l2_m2m_ops mtk_mdp_m2m_ops = { + .device_run = mtk_mdp_m2m_device_run, +}; + +int mtk_mdp_register_m2m_device(struct mtk_mdp_dev *mdp) +{ + struct device *dev = &mdp->pdev->dev; + int ret; + + mdp->variant = &mtk_mdp_default_variant; + mdp->vdev = video_device_alloc(); + if (!mdp->vdev) { + dev_err(dev, "failed to allocate video device\n"); + ret = -ENOMEM; + goto err_video_alloc; + } + mdp->vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + mdp->vdev->fops = &mtk_mdp_m2m_fops; + mdp->vdev->ioctl_ops = &mtk_mdp_m2m_ioctl_ops; + mdp->vdev->release = video_device_release; + mdp->vdev->lock = &mdp->lock; + mdp->vdev->vfl_dir = VFL_DIR_M2M; + mdp->vdev->v4l2_dev = &mdp->v4l2_dev; + snprintf(mdp->vdev->name, sizeof(mdp->vdev->name), "%s:m2m", + MTK_MDP_MODULE_NAME); + video_set_drvdata(mdp->vdev, mdp); + + mdp->m2m_dev = v4l2_m2m_init(&mtk_mdp_m2m_ops); + if (IS_ERR(mdp->m2m_dev)) { + dev_err(dev, "failed to initialize v4l2-m2m device\n"); + ret = PTR_ERR(mdp->m2m_dev); + goto err_m2m_init; + } + + ret = video_register_device(mdp->vdev, VFL_TYPE_VIDEO, 2); + if (ret) { + dev_err(dev, "failed to register video device\n"); + goto err_vdev_register; + } + + v4l2_info(&mdp->v4l2_dev, "driver registered as /dev/video%d", + mdp->vdev->num); + return 0; + +err_vdev_register: + v4l2_m2m_release(mdp->m2m_dev); +err_m2m_init: + video_device_release(mdp->vdev); +err_video_alloc: + + return ret; +} + +void mtk_mdp_unregister_m2m_device(struct mtk_mdp_dev *mdp) +{ + video_unregister_device(mdp->vdev); + v4l2_m2m_release(mdp->m2m_dev); +} diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.h b/drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.h new file mode 100644 index 000000000..485dbdbbf --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_m2m.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + */ + +#ifndef __MTK_MDP_M2M_H__ +#define __MTK_MDP_M2M_H__ + +void mtk_mdp_ctx_state_lock_set(struct mtk_mdp_ctx *ctx, u32 state); +int mtk_mdp_register_m2m_device(struct mtk_mdp_dev *mdp); +void mtk_mdp_unregister_m2m_device(struct mtk_mdp_dev *mdp); + +#endif /* __MTK_MDP_M2M_H__ */ diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_regs.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_regs.c new file mode 100644 index 000000000..ba476d50a --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_regs.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2016 MediaTek Inc. + * Author: Houlong Wei + * Ming Hsiu Tsai + */ + +#include + +#include "mtk_mdp_core.h" +#include "mtk_mdp_regs.h" + + +#define MDP_COLORFMT_PACK(VIDEO, PLANE, COPLANE, HF, VF, BITS, GROUP, SWAP, ID)\ + (((VIDEO) << 27) | ((PLANE) << 24) | ((COPLANE) << 22) |\ + ((HF) << 20) | ((VF) << 18) | ((BITS) << 8) | ((GROUP) << 6) |\ + ((SWAP) << 5) | ((ID) << 0)) + +enum MDP_COLOR_ENUM { + MDP_COLOR_UNKNOWN = 0, + MDP_COLOR_NV12 = MDP_COLORFMT_PACK(0, 2, 1, 1, 1, 8, 1, 0, 12), + MDP_COLOR_I420 = MDP_COLORFMT_PACK(0, 3, 0, 1, 1, 8, 1, 0, 8), + MDP_COLOR_YV12 = MDP_COLORFMT_PACK(0, 3, 0, 1, 1, 8, 1, 1, 8), + /* Mediatek proprietary format */ + MDP_COLOR_420_MT21 = MDP_COLORFMT_PACK(5, 2, 1, 1, 1, 256, 1, 0, 12), +}; + +static int32_t mtk_mdp_map_color_format(int v4l2_format) +{ + switch (v4l2_format) { + case V4L2_PIX_FMT_NV12M: + case V4L2_PIX_FMT_NV12: + return MDP_COLOR_NV12; + case V4L2_PIX_FMT_MT21C: + return MDP_COLOR_420_MT21; + case V4L2_PIX_FMT_YUV420M: + case V4L2_PIX_FMT_YUV420: + return MDP_COLOR_I420; + case V4L2_PIX_FMT_YVU420: + return MDP_COLOR_YV12; + } + + mtk_mdp_err("Unknown format 0x%x", v4l2_format); + + return MDP_COLOR_UNKNOWN; +} + +void mtk_mdp_hw_set_input_addr(struct mtk_mdp_ctx *ctx, + struct mtk_mdp_addr *addr) +{ + struct mdp_buffer *src_buf = &ctx->vpu.vsi->src_buffer; + int i; + + for (i = 0; i < ARRAY_SIZE(addr->addr); i++) + src_buf->addr_mva[i] = (uint64_t)addr->addr[i]; +} + +void mtk_mdp_hw_set_output_addr(struct mtk_mdp_ctx *ctx, + struct mtk_mdp_addr *addr) +{ + struct mdp_buffer *dst_buf = &ctx->vpu.vsi->dst_buffer; + int i; + + for (i = 0; i < ARRAY_SIZE(addr->addr); i++) + dst_buf->addr_mva[i] = (uint64_t)addr->addr[i]; +} + +void mtk_mdp_hw_set_in_size(struct mtk_mdp_ctx *ctx) +{ + struct mtk_mdp_frame *frame = &ctx->s_frame; + struct mdp_config *config = &ctx->vpu.vsi->src_config; + + /* Set input pixel offset */ + config->crop_x = frame->crop.left; + config->crop_y = frame->crop.top; + + /* Set input cropped size */ + config->crop_w = frame->crop.width; + config->crop_h = frame->crop.height; + + /* Set input original size */ + config->x = 0; + config->y = 0; + config->w = frame->width; + config->h = frame->height; +} + +void mtk_mdp_hw_set_in_image_format(struct mtk_mdp_ctx *ctx) +{ + unsigned int i; + struct mtk_mdp_frame *frame = &ctx->s_frame; + struct mdp_config *config = &ctx->vpu.vsi->src_config; + struct mdp_buffer *src_buf = &ctx->vpu.vsi->src_buffer; + + src_buf->plane_num = frame->fmt->num_comp; + config->format = mtk_mdp_map_color_format(frame->fmt->pixelformat); + config->w_stride = 0; /* MDP will calculate it by color format. */ + config->h_stride = 0; /* MDP will calculate it by color format. */ + + for (i = 0; i < src_buf->plane_num; i++) + src_buf->plane_size[i] = frame->payload[i]; +} + +void mtk_mdp_hw_set_out_size(struct mtk_mdp_ctx *ctx) +{ + struct mtk_mdp_frame *frame = &ctx->d_frame; + struct mdp_config *config = &ctx->vpu.vsi->dst_config; + + config->crop_x = frame->crop.left; + config->crop_y = frame->crop.top; + config->crop_w = frame->crop.width; + config->crop_h = frame->crop.height; + config->x = 0; + config->y = 0; + config->w = frame->width; + config->h = frame->height; +} + +void mtk_mdp_hw_set_out_image_format(struct mtk_mdp_ctx *ctx) +{ + unsigned int i; + struct mtk_mdp_frame *frame = &ctx->d_frame; + struct mdp_config *config = &ctx->vpu.vsi->dst_config; + struct mdp_buffer *dst_buf = &ctx->vpu.vsi->dst_buffer; + + dst_buf->plane_num = frame->fmt->num_comp; + config->format = mtk_mdp_map_color_format(frame->fmt->pixelformat); + config->w_stride = 0; /* MDP will calculate it by color format. */ + config->h_stride = 0; /* MDP will calculate it by color format. */ + for (i = 0; i < dst_buf->plane_num; i++) + dst_buf->plane_size[i] = frame->payload[i]; +} + +void mtk_mdp_hw_set_rotation(struct mtk_mdp_ctx *ctx) +{ + struct mdp_config_misc *misc = &ctx->vpu.vsi->misc; + + misc->orientation = ctx->ctrls.rotate->val; + misc->hflip = ctx->ctrls.hflip->val; + misc->vflip = ctx->ctrls.vflip->val; +} + +void mtk_mdp_hw_set_global_alpha(struct mtk_mdp_ctx *ctx) +{ + struct mdp_config_misc *misc = &ctx->vpu.vsi->misc; + + misc->alpha = ctx->ctrls.global_alpha->val; +} diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_regs.h b/drivers/media/platform/mediatek/mdp/mtk_mdp_regs.h new file mode 100644 index 000000000..32cf202f2 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_regs.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Ming Hsiu Tsai + */ + +#ifndef __MTK_MDP_REGS_H__ +#define __MTK_MDP_REGS_H__ + + +void mtk_mdp_hw_set_input_addr(struct mtk_mdp_ctx *ctx, + struct mtk_mdp_addr *addr); +void mtk_mdp_hw_set_output_addr(struct mtk_mdp_ctx *ctx, + struct mtk_mdp_addr *addr); +void mtk_mdp_hw_set_in_size(struct mtk_mdp_ctx *ctx); +void mtk_mdp_hw_set_in_image_format(struct mtk_mdp_ctx *ctx); +void mtk_mdp_hw_set_out_size(struct mtk_mdp_ctx *ctx); +void mtk_mdp_hw_set_out_image_format(struct mtk_mdp_ctx *ctx); +void mtk_mdp_hw_set_rotation(struct mtk_mdp_ctx *ctx); +void mtk_mdp_hw_set_global_alpha(struct mtk_mdp_ctx *ctx); + + +#endif /* __MTK_MDP_REGS_H__ */ diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.c b/drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.c new file mode 100644 index 000000000..b065ccd06 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2016 MediaTek Inc. + * Author: Houlong Wei + * Ming Hsiu Tsai + */ + +#include "mtk_mdp_core.h" +#include "mtk_mdp_vpu.h" +#include "mtk_vpu.h" + + +static inline struct mtk_mdp_ctx *vpu_to_ctx(struct mtk_mdp_vpu *vpu) +{ + return container_of(vpu, struct mtk_mdp_ctx, vpu); +} + +static void mtk_mdp_vpu_handle_init_ack(const struct mdp_ipi_comm_ack *msg) +{ + struct mtk_mdp_vpu *vpu = (struct mtk_mdp_vpu *) + (unsigned long)msg->ap_inst; + + /* mapping VPU address to kernel virtual address */ + vpu->vsi = (struct mdp_process_vsi *) + vpu_mapping_dm_addr(vpu->pdev, msg->vpu_inst_addr); + vpu->inst_addr = msg->vpu_inst_addr; +} + +static void mtk_mdp_vpu_ipi_handler(const void *data, unsigned int len, + void *priv) +{ + const struct mdp_ipi_comm_ack *msg = data; + unsigned int msg_id = msg->msg_id; + struct mtk_mdp_vpu *vpu = (struct mtk_mdp_vpu *) + (unsigned long)msg->ap_inst; + struct mtk_mdp_ctx *ctx; + + vpu->failure = msg->status; + if (!vpu->failure) { + switch (msg_id) { + case VPU_MDP_INIT_ACK: + mtk_mdp_vpu_handle_init_ack(data); + break; + case VPU_MDP_DEINIT_ACK: + case VPU_MDP_PROCESS_ACK: + break; + default: + ctx = vpu_to_ctx(vpu); + dev_err(&ctx->mdp_dev->pdev->dev, + "handle unknown ipi msg:0x%x\n", + msg_id); + break; + } + } else { + ctx = vpu_to_ctx(vpu); + mtk_mdp_dbg(0, "[%d]:msg 0x%x, failure:%d", ctx->id, + msg_id, vpu->failure); + } +} + +int mtk_mdp_vpu_register(struct platform_device *pdev) +{ + struct mtk_mdp_dev *mdp = platform_get_drvdata(pdev); + int err; + + err = vpu_ipi_register(mdp->vpu_dev, IPI_MDP, + mtk_mdp_vpu_ipi_handler, "mdp_vpu", NULL); + if (err) + dev_err(&mdp->pdev->dev, + "vpu_ipi_registration fail status=%d\n", err); + + return err; +} + +static int mtk_mdp_vpu_send_msg(void *msg, int len, struct mtk_mdp_vpu *vpu, + int id) +{ + struct mtk_mdp_ctx *ctx = vpu_to_ctx(vpu); + int err; + + if (!vpu->pdev) { + mtk_mdp_dbg(1, "[%d]:vpu pdev is NULL", ctx->id); + return -EINVAL; + } + + mutex_lock(&ctx->mdp_dev->vpulock); + err = vpu_ipi_send(vpu->pdev, (enum ipi_id)id, msg, len); + if (err) + dev_err(&ctx->mdp_dev->pdev->dev, + "vpu_ipi_send fail status %d\n", err); + mutex_unlock(&ctx->mdp_dev->vpulock); + + return err; +} + +static int mtk_mdp_vpu_send_ap_ipi(struct mtk_mdp_vpu *vpu, uint32_t msg_id) +{ + int err; + struct mdp_ipi_comm msg; + + msg.msg_id = msg_id; + msg.ipi_id = IPI_MDP; + msg.vpu_inst_addr = vpu->inst_addr; + msg.ap_inst = (unsigned long)vpu; + err = mtk_mdp_vpu_send_msg((void *)&msg, sizeof(msg), vpu, IPI_MDP); + if (!err && vpu->failure) + err = -EINVAL; + + return err; +} + +int mtk_mdp_vpu_init(struct mtk_mdp_vpu *vpu) +{ + int err; + struct mdp_ipi_init msg; + struct mtk_mdp_ctx *ctx = vpu_to_ctx(vpu); + + vpu->pdev = ctx->mdp_dev->vpu_dev; + + msg.msg_id = AP_MDP_INIT; + msg.ipi_id = IPI_MDP; + msg.ap_inst = (unsigned long)vpu; + err = mtk_mdp_vpu_send_msg((void *)&msg, sizeof(msg), vpu, IPI_MDP); + if (!err && vpu->failure) + err = -EINVAL; + + return err; +} + +int mtk_mdp_vpu_deinit(struct mtk_mdp_vpu *vpu) +{ + return mtk_mdp_vpu_send_ap_ipi(vpu, AP_MDP_DEINIT); +} + +int mtk_mdp_vpu_process(struct mtk_mdp_vpu *vpu) +{ + return mtk_mdp_vpu_send_ap_ipi(vpu, AP_MDP_PROCESS); +} diff --git a/drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.h b/drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.h new file mode 100644 index 000000000..5a1020508 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp/mtk_mdp_vpu.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2016 MediaTek Inc. + * Author: Houlong Wei + * Ming Hsiu Tsai + */ + +#ifndef __MTK_MDP_VPU_H__ +#define __MTK_MDP_VPU_H__ + +#include "mtk_mdp_ipi.h" + + +/** + * struct mtk_mdp_vpu - VPU instance for MDP + * @pdev : pointer to the VPU platform device + * @inst_addr : VPU MDP instance address + * @failure : VPU execution result status + * @vsi : VPU shared information + */ +struct mtk_mdp_vpu { + struct platform_device *pdev; + uint32_t inst_addr; + int32_t failure; + struct mdp_process_vsi *vsi; +}; + +int mtk_mdp_vpu_register(struct platform_device *pdev); +int mtk_mdp_vpu_init(struct mtk_mdp_vpu *vpu); +int mtk_mdp_vpu_deinit(struct mtk_mdp_vpu *vpu); +int mtk_mdp_vpu_process(struct mtk_mdp_vpu *vpu); + +#endif /* __MTK_MDP_VPU_H__ */ diff --git a/drivers/media/platform/mediatek/mdp3/Kconfig b/drivers/media/platform/mediatek/mdp3/Kconfig new file mode 100644 index 000000000..602329c44 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_MEDIATEK_MDP3 + tristate "MediaTek MDP v3 driver" + depends on MTK_IOMMU || COMPILE_TEST + depends on VIDEO_DEV + depends on HAS_DMA + depends on REMOTEPROC + depends on MTK_MMSYS + depends on MTK_CMDQ + depends on MTK_SCP + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + default n + help + It is a v4l2 driver and present in MediaTek MT8183 SoC. + The driver supports scaling and color space conversion. + + To compile this driver as a module, choose M here: the + module will be called mtk-mdp3. diff --git a/drivers/media/platform/mediatek/mdp3/Makefile b/drivers/media/platform/mediatek/mdp3/Makefile new file mode 100644 index 000000000..63e6c87e4 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +mtk-mdp3-y += mtk-mdp3-core.o mtk-mdp3-vpu.o mtk-mdp3-regs.o +mtk-mdp3-y += mtk-mdp3-m2m.o +mtk-mdp3-y += mtk-mdp3-comp.o mtk-mdp3-cmdq.o + +obj-$(CONFIG_VIDEO_MEDIATEK_MDP3) += mtk-mdp3.o diff --git a/drivers/media/platform/mediatek/mdp3/mdp_reg_ccorr.h b/drivers/media/platform/mediatek/mdp3/mdp_reg_ccorr.h new file mode 100644 index 000000000..3b2c6531c --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mdp_reg_ccorr.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MDP_REG_CCORR_H__ +#define __MDP_REG_CCORR_H__ + +#define MDP_CCORR_EN 0x000 +#define MDP_CCORR_CFG 0x020 +#define MDP_CCORR_SIZE 0x030 + +/* MASK */ +#define MDP_CCORR_EN_MASK 0x00000001 +#define MDP_CCORR_CFG_MASK 0x70001317 +#define MDP_CCORR_SIZE_MASK 0x1fff1fff + +#endif // __MDP_REG_CCORR_H__ diff --git a/drivers/media/platform/mediatek/mdp3/mdp_reg_rdma.h b/drivers/media/platform/mediatek/mdp3/mdp_reg_rdma.h new file mode 100644 index 000000000..be4065e25 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mdp_reg_rdma.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MDP_REG_RDMA_H__ +#define __MDP_REG_RDMA_H__ + +#define MDP_RDMA_EN 0x000 +#define MDP_RDMA_RESET 0x008 +#define MDP_RDMA_CON 0x020 +#define MDP_RDMA_GMCIF_CON 0x028 +#define MDP_RDMA_SRC_CON 0x030 +#define MDP_RDMA_MF_BKGD_SIZE_IN_BYTE 0x060 +#define MDP_RDMA_MF_BKGD_SIZE_IN_PXL 0x068 +#define MDP_RDMA_MF_SRC_SIZE 0x070 +#define MDP_RDMA_MF_CLIP_SIZE 0x078 +#define MDP_RDMA_MF_OFFSET_1 0x080 +#define MDP_RDMA_SF_BKGD_SIZE_IN_BYTE 0x090 +#define MDP_RDMA_SRC_END_0 0x100 +#define MDP_RDMA_SRC_END_1 0x108 +#define MDP_RDMA_SRC_END_2 0x110 +#define MDP_RDMA_SRC_OFFSET_0 0x118 +#define MDP_RDMA_SRC_OFFSET_1 0x120 +#define MDP_RDMA_SRC_OFFSET_2 0x128 +#define MDP_RDMA_SRC_OFFSET_0_P 0x148 +#define MDP_RDMA_TRANSFORM_0 0x200 +#define MDP_RDMA_RESV_DUMMY_0 0x2a0 +#define MDP_RDMA_MON_STA_1 0x408 +#define MDP_RDMA_SRC_BASE_0 0xf00 +#define MDP_RDMA_SRC_BASE_1 0xf08 +#define MDP_RDMA_SRC_BASE_2 0xf10 +#define MDP_RDMA_UFO_DEC_LENGTH_BASE_Y 0xf20 +#define MDP_RDMA_UFO_DEC_LENGTH_BASE_C 0xf28 + +/* MASK */ +#define MDP_RDMA_EN_MASK 0x00000001 +#define MDP_RDMA_RESET_MASK 0x00000001 +#define MDP_RDMA_CON_MASK 0x00001110 +#define MDP_RDMA_GMCIF_CON_MASK 0xfffb3771 +#define MDP_RDMA_SRC_CON_MASK 0xf3ffffff +#define MDP_RDMA_MF_BKGD_SIZE_IN_BYTE_MASK 0x001fffff +#define MDP_RDMA_MF_BKGD_SIZE_IN_PXL_MASK 0x001fffff +#define MDP_RDMA_MF_SRC_SIZE_MASK 0x1fff1fff +#define MDP_RDMA_MF_CLIP_SIZE_MASK 0x1fff1fff +#define MDP_RDMA_MF_OFFSET_1_MASK 0x003f001f +#define MDP_RDMA_SF_BKGD_SIZE_IN_BYTE_MASK 0x001fffff +#define MDP_RDMA_SRC_END_0_MASK 0xffffffff +#define MDP_RDMA_SRC_END_1_MASK 0xffffffff +#define MDP_RDMA_SRC_END_2_MASK 0xffffffff +#define MDP_RDMA_SRC_OFFSET_0_MASK 0xffffffff +#define MDP_RDMA_SRC_OFFSET_1_MASK 0xffffffff +#define MDP_RDMA_SRC_OFFSET_2_MASK 0xffffffff +#define MDP_RDMA_SRC_OFFSET_0_P_MASK 0xffffffff +#define MDP_RDMA_TRANSFORM_0_MASK 0xff110777 +#define MDP_RDMA_RESV_DUMMY_0_MASK 0xffffffff +#define MDP_RDMA_MON_STA_1_MASK 0xffffffff +#define MDP_RDMA_SRC_BASE_0_MASK 0xffffffff +#define MDP_RDMA_SRC_BASE_1_MASK 0xffffffff +#define MDP_RDMA_SRC_BASE_2_MASK 0xffffffff +#define MDP_RDMA_UFO_DEC_LENGTH_BASE_Y_MASK 0xffffffff +#define MDP_RDMA_UFO_DEC_LENGTH_BASE_C_MASK 0xffffffff + +#endif // __MDP_REG_RDMA_H__ diff --git a/drivers/media/platform/mediatek/mdp3/mdp_reg_rsz.h b/drivers/media/platform/mediatek/mdp3/mdp_reg_rsz.h new file mode 100644 index 000000000..484f6d606 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mdp_reg_rsz.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MDP_REG_RSZ_H__ +#define __MDP_REG_RSZ_H__ + +#define PRZ_ENABLE 0x000 +#define PRZ_CONTROL_1 0x004 +#define PRZ_CONTROL_2 0x008 +#define PRZ_INPUT_IMAGE 0x010 +#define PRZ_OUTPUT_IMAGE 0x014 +#define PRZ_HORIZONTAL_COEFF_STEP 0x018 +#define PRZ_VERTICAL_COEFF_STEP 0x01c +#define PRZ_LUMA_HORIZONTAL_INTEGER_OFFSET 0x020 +#define PRZ_LUMA_HORIZONTAL_SUBPIXEL_OFFSET 0x024 +#define PRZ_LUMA_VERTICAL_INTEGER_OFFSET 0x028 +#define PRZ_LUMA_VERTICAL_SUBPIXEL_OFFSET 0x02c +#define PRZ_CHROMA_HORIZONTAL_INTEGER_OFFSET 0x030 +#define PRZ_CHROMA_HORIZONTAL_SUBPIXEL_OFFSET 0x034 + +/* MASK */ +#define PRZ_ENABLE_MASK 0x00010001 +#define PRZ_CONTROL_1_MASK 0xfffffff3 +#define PRZ_CONTROL_2_MASK 0x0ffffaff +#define PRZ_INPUT_IMAGE_MASK 0xffffffff +#define PRZ_OUTPUT_IMAGE_MASK 0xffffffff +#define PRZ_HORIZONTAL_COEFF_STEP_MASK 0x007fffff +#define PRZ_VERTICAL_COEFF_STEP_MASK 0x007fffff +#define PRZ_LUMA_HORIZONTAL_INTEGER_OFFSET_MASK 0x0000ffff +#define PRZ_LUMA_HORIZONTAL_SUBPIXEL_OFFSET_MASK 0x001fffff +#define PRZ_LUMA_VERTICAL_INTEGER_OFFSET_MASK 0x0000ffff +#define PRZ_LUMA_VERTICAL_SUBPIXEL_OFFSET_MASK 0x001fffff +#define PRZ_CHROMA_HORIZONTAL_INTEGER_OFFSET_MASK 0x0000ffff +#define PRZ_CHROMA_HORIZONTAL_SUBPIXEL_OFFSET_MASK 0x001fffff + +#endif // __MDP_REG_RSZ_H__ diff --git a/drivers/media/platform/mediatek/mdp3/mdp_reg_wdma.h b/drivers/media/platform/mediatek/mdp3/mdp_reg_wdma.h new file mode 100644 index 000000000..0280e91c0 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mdp_reg_wdma.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MDP_REG_WDMA_H__ +#define __MDP_REG_WDMA_H__ + +#define WDMA_EN 0x008 +#define WDMA_RST 0x00c +#define WDMA_CFG 0x014 +#define WDMA_SRC_SIZE 0x018 +#define WDMA_CLIP_SIZE 0x01c +#define WDMA_CLIP_COORD 0x020 +#define WDMA_DST_W_IN_BYTE 0x028 +#define WDMA_ALPHA 0x02c +#define WDMA_BUF_CON2 0x03c +#define WDMA_DST_UV_PITCH 0x078 +#define WDMA_DST_ADDR_OFFSET 0x080 +#define WDMA_DST_U_ADDR_OFFSET 0x084 +#define WDMA_DST_V_ADDR_OFFSET 0x088 +#define WDMA_FLOW_CTRL_DBG 0x0a0 +#define WDMA_DST_ADDR 0xf00 +#define WDMA_DST_U_ADDR 0xf04 +#define WDMA_DST_V_ADDR 0xf08 + +/* MASK */ +#define WDMA_EN_MASK 0x00000001 +#define WDMA_RST_MASK 0x00000001 +#define WDMA_CFG_MASK 0xff03bff0 +#define WDMA_SRC_SIZE_MASK 0x3fff3fff +#define WDMA_CLIP_SIZE_MASK 0x3fff3fff +#define WDMA_CLIP_COORD_MASK 0x3fff3fff +#define WDMA_DST_W_IN_BYTE_MASK 0x0000ffff +#define WDMA_ALPHA_MASK 0x800000ff +#define WDMA_BUF_CON2_MASK 0xffffffff +#define WDMA_DST_UV_PITCH_MASK 0x0000ffff +#define WDMA_DST_ADDR_OFFSET_MASK 0x0fffffff +#define WDMA_DST_U_ADDR_OFFSET_MASK 0x0fffffff +#define WDMA_DST_V_ADDR_OFFSET_MASK 0x0fffffff +#define WDMA_FLOW_CTRL_DBG_MASK 0x0000f3ff +#define WDMA_DST_ADDR_MASK 0xffffffff +#define WDMA_DST_U_ADDR_MASK 0xffffffff +#define WDMA_DST_V_ADDR_MASK 0xffffffff + +#endif // __MDP_REG_WDMA_H__ diff --git a/drivers/media/platform/mediatek/mdp3/mdp_reg_wrot.h b/drivers/media/platform/mediatek/mdp3/mdp_reg_wrot.h new file mode 100644 index 000000000..6d3ff0e2b --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mdp_reg_wrot.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MDP_REG_WROT_H__ +#define __MDP_REG_WROT_H__ + +#define VIDO_CTRL 0x000 +#define VIDO_MAIN_BUF_SIZE 0x008 +#define VIDO_SOFT_RST 0x010 +#define VIDO_SOFT_RST_STAT 0x014 +#define VIDO_CROP_OFST 0x020 +#define VIDO_TAR_SIZE 0x024 +#define VIDO_OFST_ADDR 0x02c +#define VIDO_STRIDE 0x030 +#define VIDO_OFST_ADDR_C 0x038 +#define VIDO_STRIDE_C 0x03c +#define VIDO_DITHER 0x054 +#define VIDO_STRIDE_V 0x06c +#define VIDO_OFST_ADDR_V 0x068 +#define VIDO_RSV_1 0x070 +#define VIDO_IN_SIZE 0x078 +#define VIDO_ROT_EN 0x07c +#define VIDO_FIFO_TEST 0x080 +#define VIDO_MAT_CTRL 0x084 +#define VIDO_BASE_ADDR 0xf00 +#define VIDO_BASE_ADDR_C 0xf04 +#define VIDO_BASE_ADDR_V 0xf08 + +/* MASK */ +#define VIDO_CTRL_MASK 0xf530711f +#define VIDO_MAIN_BUF_SIZE_MASK 0x1fff7f77 +#define VIDO_SOFT_RST_MASK 0x00000001 +#define VIDO_SOFT_RST_STAT_MASK 0x00000001 +#define VIDO_TAR_SIZE_MASK 0x1fff1fff +#define VIDO_CROP_OFST_MASK 0x1fff1fff +#define VIDO_OFST_ADDR_MASK 0x0fffffff +#define VIDO_STRIDE_MASK 0x0000ffff +#define VIDO_OFST_ADDR_C_MASK 0x0fffffff +#define VIDO_STRIDE_C_MASK 0x0000ffff +#define VIDO_DITHER_MASK 0xff000001 +#define VIDO_STRIDE_V_MASK 0x0000ffff +#define VIDO_OFST_ADDR_V_MASK 0x0fffffff +#define VIDO_RSV_1_MASK 0xffffffff +#define VIDO_IN_SIZE_MASK 0x1fff1fff +#define VIDO_ROT_EN_MASK 0x00000001 +#define VIDO_FIFO_TEST_MASK 0x00000fff +#define VIDO_MAT_CTRL_MASK 0x000000f3 +#define VIDO_BASE_ADDR_MASK 0xffffffff +#define VIDO_BASE_ADDR_C_MASK 0xffffffff +#define VIDO_BASE_ADDR_V_MASK 0xffffffff + +#endif // __MDP_REG_WROT_H__ diff --git a/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h b/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h new file mode 100644 index 000000000..3e66ebaee --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-img-ipi.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Holmes Chiou + * Ping-Hsun Wu + */ + +#ifndef __MTK_IMG_IPI_H__ +#define __MTK_IMG_IPI_H__ + +#include + +/* + * ISP-MDP generic input information + * MD5 of the target SCP blob: + * 6da52bdcf4bf76a0983b313e1d4745d6 + */ + +#define IMG_MAX_HW_INPUTS 3 + +#define IMG_MAX_HW_OUTPUTS 4 + +#define IMG_MAX_PLANES 3 + +#define IMG_IPI_INIT 1 +#define IMG_IPI_DEINIT 2 +#define IMG_IPI_FRAME 3 +#define IMG_IPI_DEBUG 4 + +struct img_timeval { + u32 tv_sec; + u32 tv_usec; +} __packed; + +struct img_addr { + u64 va; /* Used for Linux OS access */ + u32 pa; /* Used for CM4 access */ + u32 iova; /* Used for IOMMU HW access */ +} __packed; + +struct tuning_addr { + u64 present; + u32 pa; /* Used for CM4 access */ + u32 iova; /* Used for IOMMU HW access */ +} __packed; + +struct img_sw_addr { + u64 va; /* Used for APMCU access */ + u32 pa; /* Used for CM4 access */ +} __packed; + +struct img_plane_format { + u32 size; + u16 stride; +} __packed; + +struct img_pix_format { + u16 width; + u16 height; + u32 colorformat; /* enum mdp_color */ + u16 ycbcr_prof; /* enum mdp_ycbcr_profile */ + struct img_plane_format plane_fmt[IMG_MAX_PLANES]; +} __packed; + +struct img_image_buffer { + struct img_pix_format format; + u32 iova[IMG_MAX_PLANES]; + /* enum mdp_buffer_usage, FD or advanced ISP usages */ + u32 usage; +} __packed; + +#define IMG_SUBPIXEL_SHIFT 20 + +struct img_crop { + s16 left; + s16 top; + u16 width; + u16 height; + u32 left_subpix; + u32 top_subpix; + u32 width_subpix; + u32 height_subpix; +} __packed; + +#define IMG_CTRL_FLAG_HFLIP BIT(0) +#define IMG_CTRL_FLAG_DITHER BIT(1) +#define IMG_CTRL_FLAG_SHARPNESS BIT(4) +#define IMG_CTRL_FLAG_HDR BIT(5) +#define IMG_CTRL_FLAG_DRE BIT(6) + +struct img_input { + struct img_image_buffer buffer; + u16 flags; /* HDR, DRE, dither */ +} __packed; + +struct img_output { + struct img_image_buffer buffer; + struct img_crop crop; + s16 rotation; + u16 flags; /* H-flip, sharpness, dither */ +} __packed; + +struct img_ipi_frameparam { + u32 index; + u32 frame_no; + struct img_timeval timestamp; + u8 type; /* enum mdp_stream_type */ + u8 state; + u8 num_inputs; + u8 num_outputs; + u64 drv_data; + struct img_input inputs[IMG_MAX_HW_INPUTS]; + struct img_output outputs[IMG_MAX_HW_OUTPUTS]; + struct tuning_addr tuning_data; + struct img_addr subfrm_data; + struct img_sw_addr config_data; + struct img_sw_addr self_data; +} __packed; + +struct img_sw_buffer { + u64 handle; /* Used for APMCU access */ + u32 scp_addr; /* Used for CM4 access */ +} __packed; + +struct img_ipi_param { + u8 usage; + struct img_sw_buffer frm_param; +} __packed; + +struct img_frameparam { + struct list_head list_entry; + struct img_ipi_frameparam frameparam; +}; + +/* ISP-MDP generic output information */ + +struct img_comp_frame { + u32 output_disable:1; + u32 bypass:1; + u16 in_width; + u16 in_height; + u16 out_width; + u16 out_height; + struct img_crop crop; + u16 in_total_width; + u16 out_total_width; +} __packed; + +struct img_region { + s16 left; + s16 right; + s16 top; + s16 bottom; +} __packed; + +struct img_offset { + s16 left; + s16 top; + u32 left_subpix; + u32 top_subpix; +} __packed; + +struct img_comp_subfrm { + u32 tile_disable:1; + struct img_region in; + struct img_region out; + struct img_offset luma; + struct img_offset chroma; + s16 out_vertical; /* Output vertical index */ + s16 out_horizontal; /* Output horizontal index */ +} __packed; + +#define IMG_MAX_SUBFRAMES 14 + +struct mdp_rdma_subfrm { + u32 offset[IMG_MAX_PLANES]; + u32 offset_0_p; + u32 src; + u32 clip; + u32 clip_ofst; +} __packed; + +struct mdp_rdma_data { + u32 src_ctrl; + u32 control; + u32 iova[IMG_MAX_PLANES]; + u32 iova_end[IMG_MAX_PLANES]; + u32 mf_bkgd; + u32 mf_bkgd_in_pxl; + u32 sf_bkgd; + u32 ufo_dec_y; + u32 ufo_dec_c; + u32 transform; + struct mdp_rdma_subfrm subfrms[IMG_MAX_SUBFRAMES]; +} __packed; + +struct mdp_rsz_subfrm { + u32 control2; + u32 src; + u32 clip; +} __packed; + +struct mdp_rsz_data { + u32 coeff_step_x; + u32 coeff_step_y; + u32 control1; + u32 control2; + struct mdp_rsz_subfrm subfrms[IMG_MAX_SUBFRAMES]; +} __packed; + +struct mdp_wrot_subfrm { + u32 offset[IMG_MAX_PLANES]; + u32 src; + u32 clip; + u32 clip_ofst; + u32 main_buf; +} __packed; + +struct mdp_wrot_data { + u32 iova[IMG_MAX_PLANES]; + u32 control; + u32 stride[IMG_MAX_PLANES]; + u32 mat_ctrl; + u32 fifo_test; + u32 filter; + struct mdp_wrot_subfrm subfrms[IMG_MAX_SUBFRAMES]; +} __packed; + +struct mdp_wdma_subfrm { + u32 offset[IMG_MAX_PLANES]; + u32 src; + u32 clip; + u32 clip_ofst; +} __packed; + +struct mdp_wdma_data { + u32 wdma_cfg; + u32 iova[IMG_MAX_PLANES]; + u32 w_in_byte; + u32 uv_stride; + struct mdp_wdma_subfrm subfrms[IMG_MAX_SUBFRAMES]; +} __packed; + +struct isp_data { + u64 dl_flags; /* 1 << (enum mdp_comp_type) */ + u32 smxi_iova[4]; + u32 cq_idx; + u32 cq_iova; + u32 tpipe_iova[IMG_MAX_SUBFRAMES]; +} __packed; + +struct img_compparam { + u16 type; /* enum mdp_comp_type */ + u16 id; /* enum mtk_mdp_comp_id */ + u32 input; + u32 outputs[IMG_MAX_HW_OUTPUTS]; + u32 num_outputs; + struct img_comp_frame frame; + struct img_comp_subfrm subfrms[IMG_MAX_SUBFRAMES]; + u32 num_subfrms; + union { + struct mdp_rdma_data rdma; + struct mdp_rsz_data rsz; + struct mdp_wrot_data wrot; + struct mdp_wdma_data wdma; + struct isp_data isp; + }; +} __packed; + +#define IMG_MAX_COMPONENTS 20 + +struct img_mux { + u32 reg; + u32 value; + u32 subsys_id; +}; + +struct img_mmsys_ctrl { + struct img_mux sets[IMG_MAX_COMPONENTS * 2]; + u32 num_sets; +}; + +struct img_config { + struct img_compparam components[IMG_MAX_COMPONENTS]; + u32 num_components; + struct img_mmsys_ctrl ctrls[IMG_MAX_SUBFRAMES]; + u32 num_subfrms; +} __packed; + +#endif /* __MTK_IMG_IPI_H__ */ diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c new file mode 100644 index 000000000..124c1b96e --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.c @@ -0,0 +1,469 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#include +#include +#include "mtk-mdp3-cmdq.h" +#include "mtk-mdp3-comp.h" +#include "mtk-mdp3-core.h" +#include "mtk-mdp3-m2m.h" + +#define MDP_PATH_MAX_COMPS IMG_MAX_COMPONENTS + +struct mdp_path { + struct mdp_dev *mdp_dev; + struct mdp_comp_ctx comps[MDP_PATH_MAX_COMPS]; + u32 num_comps; + const struct img_config *config; + const struct img_ipi_frameparam *param; + const struct v4l2_rect *composes[IMG_MAX_HW_OUTPUTS]; + struct v4l2_rect bounds[IMG_MAX_HW_OUTPUTS]; +}; + +#define has_op(ctx, op) \ + ((ctx)->comp->ops && (ctx)->comp->ops->op) + #define call_op(ctx, op, ...) \ + (has_op(ctx, op) ? (ctx)->comp->ops->op(ctx, ##__VA_ARGS__) : 0) + +static bool is_output_disabled(const struct img_compparam *param, u32 count) +{ + return (count < param->num_subfrms) ? + (param->frame.output_disable || + param->subfrms[count].tile_disable) : + true; +} + +static int mdp_path_subfrm_require(const struct mdp_path *path, + struct mdp_cmdq_cmd *cmd, + s32 *mutex_id, u32 count) +{ + const struct img_config *config = path->config; + const struct mdp_comp_ctx *ctx; + const struct mtk_mdp_driver_data *data = path->mdp_dev->mdp_data; + struct device *dev = &path->mdp_dev->pdev->dev; + struct mtk_mutex **mutex = path->mdp_dev->mdp_mutex; + int id, index; + + /* Decide which mutex to use based on the current pipeline */ + switch (path->comps[0].comp->id) { + case MDP_COMP_RDMA0: + *mutex_id = MDP_PIPE_RDMA0; + break; + case MDP_COMP_ISP_IMGI: + *mutex_id = MDP_PIPE_IMGI; + break; + case MDP_COMP_WPEI: + *mutex_id = MDP_PIPE_WPEI; + break; + case MDP_COMP_WPEI2: + *mutex_id = MDP_PIPE_WPEI2; + break; + default: + dev_err(dev, "Unknown pipeline and no mutex is assigned"); + return -EINVAL; + } + + /* Set mutex mod */ + for (index = 0; index < config->num_components; index++) { + ctx = &path->comps[index]; + if (is_output_disabled(ctx->param, count)) + continue; + id = ctx->comp->id; + mtk_mutex_write_mod(mutex[*mutex_id], + data->mdp_mutex_table_idx[id], false); + } + + mtk_mutex_write_sof(mutex[*mutex_id], + MUTEX_SOF_IDX_SINGLE_MODE); + + return 0; +} + +static int mdp_path_subfrm_run(const struct mdp_path *path, + struct mdp_cmdq_cmd *cmd, + s32 *mutex_id, u32 count) +{ + const struct img_config *config = path->config; + const struct mdp_comp_ctx *ctx; + struct device *dev = &path->mdp_dev->pdev->dev; + struct mtk_mutex **mutex = path->mdp_dev->mdp_mutex; + int index; + s32 event; + + if (-1 == *mutex_id) { + dev_err(dev, "Incorrect mutex id"); + return -EINVAL; + } + + /* Wait WROT SRAM shared to DISP RDMA */ + /* Clear SOF event for each engine */ + for (index = 0; index < config->num_components; index++) { + ctx = &path->comps[index]; + if (is_output_disabled(ctx->param, count)) + continue; + event = ctx->comp->gce_event[MDP_GCE_EVENT_SOF]; + if (event != MDP_GCE_NO_EVENT) + MM_REG_CLEAR(cmd, event); + } + + /* Enable the mutex */ + mtk_mutex_enable_by_cmdq(mutex[*mutex_id], (void *)&cmd->pkt); + + /* Wait SOF events and clear mutex modules (optional) */ + for (index = 0; index < config->num_components; index++) { + ctx = &path->comps[index]; + if (is_output_disabled(ctx->param, count)) + continue; + event = ctx->comp->gce_event[MDP_GCE_EVENT_SOF]; + if (event != MDP_GCE_NO_EVENT) + MM_REG_WAIT(cmd, event); + } + + return 0; +} + +static int mdp_path_ctx_init(struct mdp_dev *mdp, struct mdp_path *path) +{ + const struct img_config *config = path->config; + int index, ret; + + if (config->num_components < 1) + return -EINVAL; + + for (index = 0; index < config->num_components; index++) { + ret = mdp_comp_ctx_config(mdp, &path->comps[index], + &config->components[index], + path->param); + if (ret) + return ret; + } + + return 0; +} + +static int mdp_path_config_subfrm(struct mdp_cmdq_cmd *cmd, + struct mdp_path *path, u32 count) +{ + const struct img_config *config = path->config; + const struct img_mmsys_ctrl *ctrl = &config->ctrls[count]; + const struct img_mux *set; + struct mdp_comp_ctx *ctx; + s32 mutex_id; + int index, ret; + + /* Acquire components */ + ret = mdp_path_subfrm_require(path, cmd, &mutex_id, count); + if (ret) + return ret; + /* Enable mux settings */ + for (index = 0; index < ctrl->num_sets; index++) { + set = &ctrl->sets[index]; + cmdq_pkt_write_mask(&cmd->pkt, set->subsys_id, set->reg, + set->value, 0xFFFFFFFF); + } + /* Config sub-frame information */ + for (index = (config->num_components - 1); index >= 0; index--) { + ctx = &path->comps[index]; + if (is_output_disabled(ctx->param, count)) + continue; + ret = call_op(ctx, config_subfrm, cmd, count); + if (ret) + return ret; + } + /* Run components */ + ret = mdp_path_subfrm_run(path, cmd, &mutex_id, count); + if (ret) + return ret; + /* Wait components done */ + for (index = 0; index < config->num_components; index++) { + ctx = &path->comps[index]; + if (is_output_disabled(ctx->param, count)) + continue; + ret = call_op(ctx, wait_comp_event, cmd); + if (ret) + return ret; + } + /* Advance to the next sub-frame */ + for (index = 0; index < config->num_components; index++) { + ctx = &path->comps[index]; + ret = call_op(ctx, advance_subfrm, cmd, count); + if (ret) + return ret; + } + /* Disable mux settings */ + for (index = 0; index < ctrl->num_sets; index++) { + set = &ctrl->sets[index]; + cmdq_pkt_write_mask(&cmd->pkt, set->subsys_id, set->reg, + 0, 0xFFFFFFFF); + } + + return 0; +} + +static int mdp_path_config(struct mdp_dev *mdp, struct mdp_cmdq_cmd *cmd, + struct mdp_path *path) +{ + const struct img_config *config = path->config; + struct mdp_comp_ctx *ctx; + int index, count, ret; + + /* Config path frame */ + /* Reset components */ + for (index = 0; index < config->num_components; index++) { + ctx = &path->comps[index]; + ret = call_op(ctx, init_comp, cmd); + if (ret) + return ret; + } + /* Config frame mode */ + for (index = 0; index < config->num_components; index++) { + const struct v4l2_rect *compose = + path->composes[ctx->param->outputs[0]]; + + ctx = &path->comps[index]; + ret = call_op(ctx, config_frame, cmd, compose); + if (ret) + return ret; + } + + /* Config path sub-frames */ + for (count = 0; count < config->num_subfrms; count++) { + ret = mdp_path_config_subfrm(cmd, path, count); + if (ret) + return ret; + } + /* Post processing information */ + for (index = 0; index < config->num_components; index++) { + ctx = &path->comps[index]; + ret = call_op(ctx, post_process, cmd); + if (ret) + return ret; + } + return 0; +} + +static int mdp_cmdq_pkt_create(struct cmdq_client *client, struct cmdq_pkt *pkt, + size_t size) +{ + struct device *dev; + dma_addr_t dma_addr; + + pkt->va_base = kzalloc(size, GFP_KERNEL); + if (!pkt->va_base) + return -ENOMEM; + + pkt->buf_size = size; + pkt->cl = (void *)client; + + dev = client->chan->mbox->dev; + dma_addr = dma_map_single(dev, pkt->va_base, pkt->buf_size, + DMA_TO_DEVICE); + if (dma_mapping_error(dev, dma_addr)) { + dev_err(dev, "dma map failed, size=%u\n", (u32)(u64)size); + kfree(pkt->va_base); + return -ENOMEM; + } + + pkt->pa_base = dma_addr; + + return 0; +} + +static void mdp_cmdq_pkt_destroy(struct cmdq_pkt *pkt) +{ + struct cmdq_client *client = (struct cmdq_client *)pkt->cl; + + dma_unmap_single(client->chan->mbox->dev, pkt->pa_base, pkt->buf_size, + DMA_TO_DEVICE); + kfree(pkt->va_base); + pkt->va_base = NULL; +} + +static void mdp_auto_release_work(struct work_struct *work) +{ + struct mdp_cmdq_cmd *cmd; + struct mdp_dev *mdp; + + cmd = container_of(work, struct mdp_cmdq_cmd, auto_release_work); + mdp = cmd->mdp; + + mtk_mutex_unprepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); + mdp_comp_clocks_off(&mdp->pdev->dev, cmd->comps, + cmd->num_comps); + + atomic_dec(&mdp->job_count); + wake_up(&mdp->callback_wq); + + mdp_cmdq_pkt_destroy(&cmd->pkt); + kfree(cmd->comps); + cmd->comps = NULL; + kfree(cmd); + cmd = NULL; +} + +static void mdp_handle_cmdq_callback(struct mbox_client *cl, void *mssg) +{ + struct mdp_cmdq_cmd *cmd; + struct cmdq_cb_data *data; + struct mdp_dev *mdp; + struct device *dev; + + if (!mssg) { + pr_info("%s:no callback data\n", __func__); + return; + } + + data = (struct cmdq_cb_data *)mssg; + cmd = container_of(data->pkt, struct mdp_cmdq_cmd, pkt); + mdp = cmd->mdp; + dev = &mdp->pdev->dev; + + if (cmd->mdp_ctx) + mdp_m2m_job_finish(cmd->mdp_ctx); + + if (cmd->user_cmdq_cb) { + struct cmdq_cb_data user_cb_data; + + user_cb_data.sta = data->sta; + user_cb_data.pkt = data->pkt; + cmd->user_cmdq_cb(user_cb_data); + } + + INIT_WORK(&cmd->auto_release_work, mdp_auto_release_work); + if (!queue_work(mdp->clock_wq, &cmd->auto_release_work)) { + dev_err(dev, "%s:queue_work fail!\n", __func__); + mtk_mutex_unprepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); + mdp_comp_clocks_off(&mdp->pdev->dev, cmd->comps, + cmd->num_comps); + + atomic_dec(&mdp->job_count); + wake_up(&mdp->callback_wq); + + mdp_cmdq_pkt_destroy(&cmd->pkt); + kfree(cmd->comps); + cmd->comps = NULL; + kfree(cmd); + cmd = NULL; + } +} + +int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param) +{ + struct mdp_path *path = NULL; + struct mdp_cmdq_cmd *cmd = NULL; + struct mdp_comp *comps = NULL; + struct device *dev = &mdp->pdev->dev; + int i, ret; + + atomic_inc(&mdp->job_count); + if (atomic_read(&mdp->suspended)) { + atomic_dec(&mdp->job_count); + return -ECANCELED; + } + + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) { + ret = -ENOMEM; + goto err_cancel_job; + } + + ret = mdp_cmdq_pkt_create(mdp->cmdq_clt, &cmd->pkt, SZ_16K); + if (ret) + goto err_free_cmd; + + comps = kcalloc(param->config->num_components, sizeof(*comps), + GFP_KERNEL); + if (!comps) { + ret = -ENOMEM; + goto err_destroy_pkt; + } + + path = kzalloc(sizeof(*path), GFP_KERNEL); + if (!path) { + ret = -ENOMEM; + goto err_free_comps; + } + + ret = mtk_mutex_prepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); + if (ret) { + dev_err(dev, "Fail to enable mutex clk\n"); + goto err_free_path; + } + + path->mdp_dev = mdp; + path->config = param->config; + path->param = param->param; + for (i = 0; i < param->param->num_outputs; i++) { + path->bounds[i].left = 0; + path->bounds[i].top = 0; + path->bounds[i].width = + param->param->outputs[i].buffer.format.width; + path->bounds[i].height = + param->param->outputs[i].buffer.format.height; + path->composes[i] = param->composes[i] ? + param->composes[i] : &path->bounds[i]; + } + + ret = mdp_path_ctx_init(mdp, path); + if (ret) { + dev_err(dev, "mdp_path_ctx_init error\n"); + goto err_free_path; + } + + ret = mdp_path_config(mdp, cmd, path); + if (ret) { + dev_err(dev, "mdp_path_config error\n"); + goto err_free_path; + } + cmdq_pkt_finalize(&cmd->pkt); + + for (i = 0; i < param->config->num_components; i++) + memcpy(&comps[i], path->comps[i].comp, + sizeof(struct mdp_comp)); + + mdp->cmdq_clt->client.rx_callback = mdp_handle_cmdq_callback; + cmd->mdp = mdp; + cmd->user_cmdq_cb = param->cmdq_cb; + cmd->user_cb_data = param->cb_data; + cmd->comps = comps; + cmd->num_comps = param->config->num_components; + cmd->mdp_ctx = param->mdp_ctx; + + ret = mdp_comp_clocks_on(&mdp->pdev->dev, cmd->comps, cmd->num_comps); + if (ret) + goto err_free_path; + + dma_sync_single_for_device(mdp->cmdq_clt->chan->mbox->dev, + cmd->pkt.pa_base, cmd->pkt.cmd_buf_size, + DMA_TO_DEVICE); + ret = mbox_send_message(mdp->cmdq_clt->chan, &cmd->pkt); + if (ret < 0) { + dev_err(dev, "mbox send message fail %d!\n", ret); + goto err_clock_off; + } + mbox_client_txdone(mdp->cmdq_clt->chan, 0); + + kfree(path); + return 0; + +err_clock_off: + mdp_comp_clocks_off(&mdp->pdev->dev, cmd->comps, + cmd->num_comps); +err_free_path: + mtk_mutex_unprepare(mdp->mdp_mutex[MDP_PIPE_RDMA0]); + kfree(path); +err_free_comps: + kfree(comps); +err_destroy_pkt: + mdp_cmdq_pkt_destroy(&cmd->pkt); +err_free_cmd: + kfree(cmd); +err_cancel_job: + atomic_dec(&mdp->job_count); + + return ret; +} +EXPORT_SYMBOL_GPL(mdp_cmdq_send); diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.h b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.h new file mode 100644 index 000000000..43475b862 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-cmdq.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MTK_MDP3_CMDQ_H__ +#define __MTK_MDP3_CMDQ_H__ + +#include +#include +#include +#include "mtk-img-ipi.h" + +struct platform_device *mdp_get_plat_device(struct platform_device *pdev); + +struct mdp_cmdq_param { + struct img_config *config; + struct img_ipi_frameparam *param; + const struct v4l2_rect *composes[IMG_MAX_HW_OUTPUTS]; + + void (*cmdq_cb)(struct cmdq_cb_data data); + void *cb_data; + void *mdp_ctx; +}; + +struct mdp_cmdq_cmd { + struct work_struct auto_release_work; + struct cmdq_pkt pkt; + s32 *event; + struct mdp_dev *mdp; + void (*user_cmdq_cb)(struct cmdq_cb_data data); + void *user_cb_data; + struct mdp_comp *comps; + void *mdp_ctx; + u8 num_comps; +}; + +struct mdp_dev; + +int mdp_cmdq_send(struct mdp_dev *mdp, struct mdp_cmdq_param *param); + +#endif /* __MTK_MDP3_CMDQ_H__ */ diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c new file mode 100644 index 000000000..9a3f46c1f --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.c @@ -0,0 +1,1048 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#include +#include +#include +#include +#include "mtk-mdp3-comp.h" +#include "mtk-mdp3-core.h" +#include "mtk-mdp3-regs.h" + +#include "mdp_reg_rdma.h" +#include "mdp_reg_ccorr.h" +#include "mdp_reg_rsz.h" +#include "mdp_reg_wrot.h" +#include "mdp_reg_wdma.h" + +static u32 mdp_comp_alias_id[MDP_COMP_TYPE_COUNT]; + +static inline const struct mdp_platform_config * +__get_plat_cfg(const struct mdp_comp_ctx *ctx) +{ + if (!ctx) + return NULL; + + return ctx->comp->mdp_dev->mdp_data->mdp_cfg; +} + +static s64 get_comp_flag(const struct mdp_comp_ctx *ctx) +{ + const struct mdp_platform_config *mdp_cfg = __get_plat_cfg(ctx); + + if (mdp_cfg && mdp_cfg->rdma_rsz1_sram_sharing) + if (ctx->comp->id == MDP_COMP_RDMA0) + return BIT(MDP_COMP_RDMA0) | BIT(MDP_COMP_RSZ1); + + return BIT(ctx->comp->id); +} + +static int init_rdma(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd) +{ + const struct mdp_platform_config *mdp_cfg = __get_plat_cfg(ctx); + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + if (mdp_cfg && mdp_cfg->rdma_support_10bit) { + struct mdp_comp *prz1 = ctx->comp->mdp_dev->comp[MDP_COMP_RSZ1]; + + /* Disable RSZ1 */ + if (ctx->comp->id == MDP_COMP_RDMA0 && prz1) + MM_REG_WRITE(cmd, subsys_id, prz1->reg_base, PRZ_ENABLE, + 0x0, BIT(0)); + } + + /* Reset RDMA */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_RESET, BIT(0), BIT(0)); + MM_REG_POLL(cmd, subsys_id, base, MDP_RDMA_MON_STA_1, BIT(8), BIT(8)); + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_RESET, 0x0, BIT(0)); + return 0; +} + +static int config_rdma_frame(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, + const struct v4l2_rect *compose) +{ + const struct mdp_rdma_data *rdma = &ctx->param->rdma; + const struct mdp_platform_config *mdp_cfg = __get_plat_cfg(ctx); + u32 colorformat = ctx->input->buffer.format.colorformat; + bool block10bit = MDP_COLOR_IS_10BIT_PACKED(colorformat); + bool en_ufo = MDP_COLOR_IS_UFP(colorformat); + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + if (mdp_cfg && mdp_cfg->rdma_support_10bit) { + if (block10bit) + MM_REG_WRITE(cmd, subsys_id, base, + MDP_RDMA_RESV_DUMMY_0, 0x7, 0x7); + else + MM_REG_WRITE(cmd, subsys_id, base, + MDP_RDMA_RESV_DUMMY_0, 0x0, 0x7); + } + + /* Setup smi control */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_GMCIF_CON, + (7 << 4) + //burst type to 8 + (1 << 16), //enable pre-ultra + 0x00030071); + + /* Setup source frame info */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_CON, rdma->src_ctrl, + 0x03C8FE0F); + + if (mdp_cfg) + if (mdp_cfg->rdma_support_10bit && en_ufo) { + /* Setup source buffer base */ + MM_REG_WRITE(cmd, subsys_id, + base, MDP_RDMA_UFO_DEC_LENGTH_BASE_Y, + rdma->ufo_dec_y, 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, + base, MDP_RDMA_UFO_DEC_LENGTH_BASE_C, + rdma->ufo_dec_c, 0xFFFFFFFF); + /* Set 10bit source frame pitch */ + if (block10bit) + MM_REG_WRITE(cmd, subsys_id, + base, MDP_RDMA_MF_BKGD_SIZE_IN_PXL, + rdma->mf_bkgd_in_pxl, 0x001FFFFF); + } + + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_CON, rdma->control, + 0x1110); + /* Setup source buffer base */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_BASE_0, rdma->iova[0], + 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_BASE_1, rdma->iova[1], + 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_BASE_2, rdma->iova[2], + 0xFFFFFFFF); + /* Setup source buffer end */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_END_0, + rdma->iova_end[0], 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_END_1, + rdma->iova_end[1], 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_END_2, + rdma->iova_end[2], 0xFFFFFFFF); + /* Setup source frame pitch */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_MF_BKGD_SIZE_IN_BYTE, + rdma->mf_bkgd, 0x001FFFFF); + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SF_BKGD_SIZE_IN_BYTE, + rdma->sf_bkgd, 0x001FFFFF); + /* Setup color transform */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_TRANSFORM_0, + rdma->transform, 0x0F110000); + + return 0; +} + +static int config_rdma_subfrm(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, u32 index) +{ + const struct mdp_rdma_subfrm *subfrm = &ctx->param->rdma.subfrms[index]; + const struct img_comp_subfrm *csf = &ctx->param->subfrms[index]; + const struct mdp_platform_config *mdp_cfg = __get_plat_cfg(ctx); + u32 colorformat = ctx->input->buffer.format.colorformat; + bool block10bit = MDP_COLOR_IS_10BIT_PACKED(colorformat); + bool en_ufo = MDP_COLOR_IS_UFP(colorformat); + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + /* Enable RDMA */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_EN, BIT(0), BIT(0)); + + /* Set Y pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_OFFSET_0, + subfrm->offset[0], 0xFFFFFFFF); + + /* Set 10bit UFO mode */ + if (mdp_cfg) + if (mdp_cfg->rdma_support_10bit && block10bit && en_ufo) + MM_REG_WRITE(cmd, subsys_id, base, + MDP_RDMA_SRC_OFFSET_0_P, + subfrm->offset_0_p, 0xFFFFFFFF); + + /* Set U pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_OFFSET_1, + subfrm->offset[1], 0xFFFFFFFF); + /* Set V pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_SRC_OFFSET_2, + subfrm->offset[2], 0xFFFFFFFF); + /* Set source size */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_MF_SRC_SIZE, subfrm->src, + 0x1FFF1FFF); + /* Set target size */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_MF_CLIP_SIZE, + subfrm->clip, 0x1FFF1FFF); + /* Set crop offset */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_MF_OFFSET_1, + subfrm->clip_ofst, 0x003F001F); + + if (mdp_cfg && mdp_cfg->rdma_upsample_repeat_only) + if ((csf->in.right - csf->in.left + 1) > 320) + MM_REG_WRITE(cmd, subsys_id, base, + MDP_RDMA_RESV_DUMMY_0, BIT(2), BIT(2)); + + return 0; +} + +static int wait_rdma_event(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd) +{ + struct device *dev = &ctx->comp->mdp_dev->pdev->dev; + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + if (ctx->comp->alias_id == 0) + MM_REG_WAIT(cmd, ctx->comp->gce_event[MDP_GCE_EVENT_EOF]); + else + dev_err(dev, "Do not support RDMA1_DONE event\n"); + + /* Disable RDMA */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_RDMA_EN, 0x0, BIT(0)); + return 0; +} + +static const struct mdp_comp_ops rdma_ops = { + .get_comp_flag = get_comp_flag, + .init_comp = init_rdma, + .config_frame = config_rdma_frame, + .config_subfrm = config_rdma_subfrm, + .wait_comp_event = wait_rdma_event, +}; + +static int init_rsz(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd) +{ + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + /* Reset RSZ */ + MM_REG_WRITE(cmd, subsys_id, base, PRZ_ENABLE, 0x10000, BIT(16)); + MM_REG_WRITE(cmd, subsys_id, base, PRZ_ENABLE, 0x0, BIT(16)); + /* Enable RSZ */ + MM_REG_WRITE(cmd, subsys_id, base, PRZ_ENABLE, BIT(0), BIT(0)); + return 0; +} + +static int config_rsz_frame(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, + const struct v4l2_rect *compose) +{ + const struct mdp_rsz_data *rsz = &ctx->param->rsz; + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + if (ctx->param->frame.bypass) { + /* Disable RSZ */ + MM_REG_WRITE(cmd, subsys_id, base, PRZ_ENABLE, 0x0, BIT(0)); + return 0; + } + + MM_REG_WRITE(cmd, subsys_id, base, PRZ_CONTROL_1, rsz->control1, + 0x03FFFDF3); + MM_REG_WRITE(cmd, subsys_id, base, PRZ_CONTROL_2, rsz->control2, + 0x0FFFC290); + MM_REG_WRITE(cmd, subsys_id, base, PRZ_HORIZONTAL_COEFF_STEP, + rsz->coeff_step_x, 0x007FFFFF); + MM_REG_WRITE(cmd, subsys_id, base, PRZ_VERTICAL_COEFF_STEP, + rsz->coeff_step_y, 0x007FFFFF); + return 0; +} + +static int config_rsz_subfrm(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, u32 index) +{ + const struct mdp_rsz_subfrm *subfrm = &ctx->param->rsz.subfrms[index]; + const struct img_comp_subfrm *csf = &ctx->param->subfrms[index]; + const struct mdp_platform_config *mdp_cfg = __get_plat_cfg(ctx); + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + MM_REG_WRITE(cmd, subsys_id, base, PRZ_CONTROL_2, subfrm->control2, + 0x00003800); + MM_REG_WRITE(cmd, subsys_id, base, PRZ_INPUT_IMAGE, subfrm->src, + 0xFFFFFFFF); + + if (mdp_cfg && mdp_cfg->rsz_disable_dcm_small_sample) + if ((csf->in.right - csf->in.left + 1) <= 16) + MM_REG_WRITE(cmd, subsys_id, base, PRZ_CONTROL_1, + BIT(27), BIT(27)); + + MM_REG_WRITE(cmd, subsys_id, base, PRZ_LUMA_HORIZONTAL_INTEGER_OFFSET, + csf->luma.left, 0xFFFF); + MM_REG_WRITE(cmd, subsys_id, + base, PRZ_LUMA_HORIZONTAL_SUBPIXEL_OFFSET, + csf->luma.left_subpix, 0x1FFFFF); + MM_REG_WRITE(cmd, subsys_id, base, PRZ_LUMA_VERTICAL_INTEGER_OFFSET, + csf->luma.top, 0xFFFF); + MM_REG_WRITE(cmd, subsys_id, base, PRZ_LUMA_VERTICAL_SUBPIXEL_OFFSET, + csf->luma.top_subpix, 0x1FFFFF); + MM_REG_WRITE(cmd, subsys_id, + base, PRZ_CHROMA_HORIZONTAL_INTEGER_OFFSET, + csf->chroma.left, 0xFFFF); + MM_REG_WRITE(cmd, subsys_id, + base, PRZ_CHROMA_HORIZONTAL_SUBPIXEL_OFFSET, + csf->chroma.left_subpix, 0x1FFFFF); + + MM_REG_WRITE(cmd, subsys_id, base, PRZ_OUTPUT_IMAGE, subfrm->clip, + 0xFFFFFFFF); + + return 0; +} + +static int advance_rsz_subfrm(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, u32 index) +{ + const struct mdp_platform_config *mdp_cfg = __get_plat_cfg(ctx); + + if (mdp_cfg && mdp_cfg->rsz_disable_dcm_small_sample) { + const struct img_comp_subfrm *csf = &ctx->param->subfrms[index]; + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + if ((csf->in.right - csf->in.left + 1) <= 16) + MM_REG_WRITE(cmd, subsys_id, base, PRZ_CONTROL_1, 0x0, + BIT(27)); + } + + return 0; +} + +static const struct mdp_comp_ops rsz_ops = { + .get_comp_flag = get_comp_flag, + .init_comp = init_rsz, + .config_frame = config_rsz_frame, + .config_subfrm = config_rsz_subfrm, + .advance_subfrm = advance_rsz_subfrm, +}; + +static int init_wrot(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd) +{ + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + /* Reset WROT */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_SOFT_RST, BIT(0), BIT(0)); + MM_REG_POLL(cmd, subsys_id, base, VIDO_SOFT_RST_STAT, BIT(0), BIT(0)); + MM_REG_WRITE(cmd, subsys_id, base, VIDO_SOFT_RST, 0x0, BIT(0)); + MM_REG_POLL(cmd, subsys_id, base, VIDO_SOFT_RST_STAT, 0x0, BIT(0)); + return 0; +} + +static int config_wrot_frame(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, + const struct v4l2_rect *compose) +{ + const struct mdp_wrot_data *wrot = &ctx->param->wrot; + const struct mdp_platform_config *mdp_cfg = __get_plat_cfg(ctx); + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + /* Write frame base address */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_BASE_ADDR, wrot->iova[0], + 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, base, VIDO_BASE_ADDR_C, wrot->iova[1], + 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, base, VIDO_BASE_ADDR_V, wrot->iova[2], + 0xFFFFFFFF); + /* Write frame related registers */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_CTRL, wrot->control, + 0xF131510F); + /* Write frame Y pitch */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_STRIDE, wrot->stride[0], + 0x0000FFFF); + /* Write frame UV pitch */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_STRIDE_C, wrot->stride[1], + 0xFFFF); + MM_REG_WRITE(cmd, subsys_id, base, VIDO_STRIDE_V, wrot->stride[2], + 0xFFFF); + /* Write matrix control */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_MAT_CTRL, wrot->mat_ctrl, 0xF3); + + /* Set the fixed ALPHA as 0xFF */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_DITHER, 0xFF000000, + 0xFF000000); + /* Set VIDO_EOL_SEL */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_RSV_1, BIT(31), BIT(31)); + /* Set VIDO_FIFO_TEST */ + if (wrot->fifo_test != 0) + MM_REG_WRITE(cmd, subsys_id, base, VIDO_FIFO_TEST, + wrot->fifo_test, 0xFFF); + /* Filter enable */ + if (mdp_cfg && mdp_cfg->wrot_filter_constraint) + MM_REG_WRITE(cmd, subsys_id, base, VIDO_MAIN_BUF_SIZE, + wrot->filter, 0x77); + + return 0; +} + +static int config_wrot_subfrm(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, u32 index) +{ + const struct mdp_wrot_subfrm *subfrm = &ctx->param->wrot.subfrms[index]; + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + /* Write Y pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_OFST_ADDR, + subfrm->offset[0], 0x0FFFFFFF); + /* Write U pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_OFST_ADDR_C, + subfrm->offset[1], 0x0FFFFFFF); + /* Write V pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_OFST_ADDR_V, + subfrm->offset[2], 0x0FFFFFFF); + /* Write source size */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_IN_SIZE, subfrm->src, + 0x1FFF1FFF); + /* Write target size */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_TAR_SIZE, subfrm->clip, + 0x1FFF1FFF); + MM_REG_WRITE(cmd, subsys_id, base, VIDO_CROP_OFST, subfrm->clip_ofst, + 0x1FFF1FFF); + + MM_REG_WRITE(cmd, subsys_id, base, VIDO_MAIN_BUF_SIZE, + subfrm->main_buf, 0x1FFF7F00); + + /* Enable WROT */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_ROT_EN, BIT(0), BIT(0)); + + return 0; +} + +static int wait_wrot_event(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd) +{ + const struct mdp_platform_config *mdp_cfg = __get_plat_cfg(ctx); + struct device *dev = &ctx->comp->mdp_dev->pdev->dev; + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + if (ctx->comp->alias_id == 0) + MM_REG_WAIT(cmd, ctx->comp->gce_event[MDP_GCE_EVENT_EOF]); + else + dev_err(dev, "Do not support WROT1_DONE event\n"); + + if (mdp_cfg && mdp_cfg->wrot_filter_constraint) + MM_REG_WRITE(cmd, subsys_id, base, VIDO_MAIN_BUF_SIZE, 0x0, + 0x77); + + /* Disable WROT */ + MM_REG_WRITE(cmd, subsys_id, base, VIDO_ROT_EN, 0x0, BIT(0)); + + return 0; +} + +static const struct mdp_comp_ops wrot_ops = { + .get_comp_flag = get_comp_flag, + .init_comp = init_wrot, + .config_frame = config_wrot_frame, + .config_subfrm = config_wrot_subfrm, + .wait_comp_event = wait_wrot_event, +}; + +static int init_wdma(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd) +{ + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + /* Reset WDMA */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_RST, BIT(0), BIT(0)); + MM_REG_POLL(cmd, subsys_id, base, WDMA_FLOW_CTRL_DBG, BIT(0), BIT(0)); + MM_REG_WRITE(cmd, subsys_id, base, WDMA_RST, 0x0, BIT(0)); + return 0; +} + +static int config_wdma_frame(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, + const struct v4l2_rect *compose) +{ + const struct mdp_wdma_data *wdma = &ctx->param->wdma; + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + MM_REG_WRITE(cmd, subsys_id, base, WDMA_BUF_CON2, 0x10101050, + 0xFFFFFFFF); + + /* Setup frame information */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_CFG, wdma->wdma_cfg, + 0x0F01B8F0); + /* Setup frame base address */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_DST_ADDR, wdma->iova[0], + 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, base, WDMA_DST_U_ADDR, wdma->iova[1], + 0xFFFFFFFF); + MM_REG_WRITE(cmd, subsys_id, base, WDMA_DST_V_ADDR, wdma->iova[2], + 0xFFFFFFFF); + /* Setup Y pitch */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_DST_W_IN_BYTE, + wdma->w_in_byte, 0x0000FFFF); + /* Setup UV pitch */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_DST_UV_PITCH, + wdma->uv_stride, 0x0000FFFF); + /* Set the fixed ALPHA as 0xFF */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_ALPHA, 0x800000FF, + 0x800000FF); + + return 0; +} + +static int config_wdma_subfrm(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, u32 index) +{ + const struct mdp_wdma_subfrm *subfrm = &ctx->param->wdma.subfrms[index]; + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + /* Write Y pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_DST_ADDR_OFFSET, + subfrm->offset[0], 0x0FFFFFFF); + /* Write U pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_DST_U_ADDR_OFFSET, + subfrm->offset[1], 0x0FFFFFFF); + /* Write V pixel offset */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_DST_V_ADDR_OFFSET, + subfrm->offset[2], 0x0FFFFFFF); + /* Write source size */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_SRC_SIZE, subfrm->src, + 0x3FFF3FFF); + /* Write target size */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_CLIP_SIZE, subfrm->clip, + 0x3FFF3FFF); + /* Write clip offset */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_CLIP_COORD, subfrm->clip_ofst, + 0x3FFF3FFF); + + /* Enable WDMA */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_EN, BIT(0), BIT(0)); + + return 0; +} + +static int wait_wdma_event(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd) +{ + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + MM_REG_WAIT(cmd, ctx->comp->gce_event[MDP_GCE_EVENT_EOF]); + /* Disable WDMA */ + MM_REG_WRITE(cmd, subsys_id, base, WDMA_EN, 0x0, BIT(0)); + return 0; +} + +static const struct mdp_comp_ops wdma_ops = { + .get_comp_flag = get_comp_flag, + .init_comp = init_wdma, + .config_frame = config_wdma_frame, + .config_subfrm = config_wdma_subfrm, + .wait_comp_event = wait_wdma_event, +}; + +static int init_ccorr(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd) +{ + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + + /* CCORR enable */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_CCORR_EN, BIT(0), BIT(0)); + /* Relay mode */ + MM_REG_WRITE(cmd, subsys_id, base, MDP_CCORR_CFG, BIT(0), BIT(0)); + return 0; +} + +static int config_ccorr_subfrm(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, u32 index) +{ + const struct img_comp_subfrm *csf = &ctx->param->subfrms[index]; + phys_addr_t base = ctx->comp->reg_base; + u8 subsys_id = ctx->comp->subsys_id; + u32 hsize, vsize; + + hsize = csf->in.right - csf->in.left + 1; + vsize = csf->in.bottom - csf->in.top + 1; + MM_REG_WRITE(cmd, subsys_id, base, MDP_CCORR_SIZE, + (hsize << 16) + (vsize << 0), 0x1FFF1FFF); + return 0; +} + +static const struct mdp_comp_ops ccorr_ops = { + .get_comp_flag = get_comp_flag, + .init_comp = init_ccorr, + .config_subfrm = config_ccorr_subfrm, +}; + +static const struct mdp_comp_ops *mdp_comp_ops[MDP_COMP_TYPE_COUNT] = { + [MDP_COMP_TYPE_RDMA] = &rdma_ops, + [MDP_COMP_TYPE_RSZ] = &rsz_ops, + [MDP_COMP_TYPE_WROT] = &wrot_ops, + [MDP_COMP_TYPE_WDMA] = &wdma_ops, + [MDP_COMP_TYPE_CCORR] = &ccorr_ops, +}; + +struct mdp_comp_match { + enum mdp_comp_type type; + u32 alias_id; +}; + +static const struct mdp_comp_match mdp_comp_matches[MDP_MAX_COMP_COUNT] = { + [MDP_COMP_WPEI] = { MDP_COMP_TYPE_WPEI, 0 }, + [MDP_COMP_WPEO] = { MDP_COMP_TYPE_EXTO, 2 }, + [MDP_COMP_WPEI2] = { MDP_COMP_TYPE_WPEI, 1 }, + [MDP_COMP_WPEO2] = { MDP_COMP_TYPE_EXTO, 3 }, + [MDP_COMP_ISP_IMGI] = { MDP_COMP_TYPE_IMGI, 0 }, + [MDP_COMP_ISP_IMGO] = { MDP_COMP_TYPE_EXTO, 0 }, + [MDP_COMP_ISP_IMG2O] = { MDP_COMP_TYPE_EXTO, 1 }, + + [MDP_COMP_CAMIN] = { MDP_COMP_TYPE_DL_PATH, 0 }, + [MDP_COMP_CAMIN2] = { MDP_COMP_TYPE_DL_PATH, 1 }, + [MDP_COMP_RDMA0] = { MDP_COMP_TYPE_RDMA, 0 }, + [MDP_COMP_CCORR0] = { MDP_COMP_TYPE_CCORR, 0 }, + [MDP_COMP_RSZ0] = { MDP_COMP_TYPE_RSZ, 0 }, + [MDP_COMP_RSZ1] = { MDP_COMP_TYPE_RSZ, 1 }, + [MDP_COMP_PATH0_SOUT] = { MDP_COMP_TYPE_PATH, 0 }, + [MDP_COMP_PATH1_SOUT] = { MDP_COMP_TYPE_PATH, 1 }, + [MDP_COMP_WROT0] = { MDP_COMP_TYPE_WROT, 0 }, + [MDP_COMP_WDMA] = { MDP_COMP_TYPE_WDMA, 0 }, +}; + +static const struct of_device_id mdp_comp_dt_ids[] = { + { + .compatible = "mediatek,mt8183-mdp3-rdma", + .data = (void *)MDP_COMP_TYPE_RDMA, + }, { + .compatible = "mediatek,mt8183-mdp3-ccorr", + .data = (void *)MDP_COMP_TYPE_CCORR, + }, { + .compatible = "mediatek,mt8183-mdp3-rsz", + .data = (void *)MDP_COMP_TYPE_RSZ, + }, { + .compatible = "mediatek,mt8183-mdp3-wrot", + .data = (void *)MDP_COMP_TYPE_WROT, + }, { + .compatible = "mediatek,mt8183-mdp3-wdma", + .data = (void *)MDP_COMP_TYPE_WDMA, + }, + {} +}; + +static const struct of_device_id mdp_sub_comp_dt_ids[] = { + { + .compatible = "mediatek,mt8183-mdp3-wdma", + .data = (void *)MDP_COMP_TYPE_PATH, + }, { + .compatible = "mediatek,mt8183-mdp3-wrot", + .data = (void *)MDP_COMP_TYPE_PATH, + }, + {} +}; + +/* Used to describe the item order in MDP property */ +struct mdp_comp_info { + u32 clk_num; + u32 clk_ofst; + u32 dts_reg_ofst; +}; + +static const struct mdp_comp_info mdp_comp_dt_info[MDP_MAX_COMP_COUNT] = { + [MDP_COMP_RDMA0] = {2, 0, 0}, + [MDP_COMP_RSZ0] = {1, 0, 0}, + [MDP_COMP_WROT0] = {1, 0, 0}, + [MDP_COMP_WDMA] = {1, 0, 0}, + [MDP_COMP_CCORR0] = {1, 0, 0}, +}; + +static inline bool is_dma_capable(const enum mdp_comp_type type) +{ + return (type == MDP_COMP_TYPE_RDMA || + type == MDP_COMP_TYPE_WROT || + type == MDP_COMP_TYPE_WDMA); +} + +static inline bool is_bypass_gce_event(const enum mdp_comp_type type) +{ + /* + * Subcomponent PATH is only used for the direction of data flow and + * dose not need to wait for GCE event. + */ + return (type == MDP_COMP_TYPE_PATH); +} + +static int mdp_comp_get_id(enum mdp_comp_type type, int alias_id) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mdp_comp_matches); i++) + if (mdp_comp_matches[i].type == type && + mdp_comp_matches[i].alias_id == alias_id) + return i; + return -ENODEV; +} + +int mdp_comp_clock_on(struct device *dev, struct mdp_comp *comp) +{ + int i, ret; + + if (comp->comp_dev) { + ret = pm_runtime_resume_and_get(comp->comp_dev); + if (ret < 0) { + dev_err(dev, + "Failed to get power, err %d. type:%d id:%d\n", + ret, comp->type, comp->id); + return ret; + } + } + + for (i = 0; i < ARRAY_SIZE(comp->clks); i++) { + if (IS_ERR_OR_NULL(comp->clks[i])) + continue; + ret = clk_prepare_enable(comp->clks[i]); + if (ret) { + dev_err(dev, + "Failed to enable clk %d. type:%d id:%d\n", + i, comp->type, comp->id); + goto err_revert; + } + } + + return 0; + +err_revert: + while (--i >= 0) { + if (IS_ERR_OR_NULL(comp->clks[i])) + continue; + clk_disable_unprepare(comp->clks[i]); + } + if (comp->comp_dev) + pm_runtime_put_sync(comp->comp_dev); + + return ret; +} + +void mdp_comp_clock_off(struct device *dev, struct mdp_comp *comp) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(comp->clks); i++) { + if (IS_ERR_OR_NULL(comp->clks[i])) + continue; + clk_disable_unprepare(comp->clks[i]); + } + + if (comp->comp_dev) + pm_runtime_put(comp->comp_dev); +} + +int mdp_comp_clocks_on(struct device *dev, struct mdp_comp *comps, int num) +{ + int i, ret; + + for (i = 0; i < num; i++) { + ret = mdp_comp_clock_on(dev, &comps[i]); + if (ret) + return ret; + } + + return 0; +} + +void mdp_comp_clocks_off(struct device *dev, struct mdp_comp *comps, int num) +{ + int i; + + for (i = 0; i < num; i++) + mdp_comp_clock_off(dev, &comps[i]); +} + +static int mdp_get_subsys_id(struct device *dev, struct device_node *node, + struct mdp_comp *comp) +{ + struct platform_device *comp_pdev; + struct cmdq_client_reg cmdq_reg; + int ret = 0; + int index = 0; + + if (!dev || !node || !comp) + return -EINVAL; + + comp_pdev = of_find_device_by_node(node); + + if (!comp_pdev) { + dev_err(dev, "get comp_pdev fail! comp id=%d type=%d\n", + comp->id, comp->type); + return -ENODEV; + } + + index = mdp_comp_dt_info[comp->id].dts_reg_ofst; + ret = cmdq_dev_get_client_reg(&comp_pdev->dev, &cmdq_reg, index); + if (ret != 0) { + dev_err(&comp_pdev->dev, "cmdq_dev_get_subsys fail!\n"); + put_device(&comp_pdev->dev); + return -EINVAL; + } + + comp->subsys_id = cmdq_reg.subsys; + dev_dbg(&comp_pdev->dev, "subsys id=%d\n", cmdq_reg.subsys); + put_device(&comp_pdev->dev); + + return 0; +} + +static void __mdp_comp_init(struct mdp_dev *mdp, struct device_node *node, + struct mdp_comp *comp) +{ + struct resource res; + phys_addr_t base; + int index = mdp_comp_dt_info[comp->id].dts_reg_ofst; + + if (of_address_to_resource(node, index, &res) < 0) + base = 0L; + else + base = res.start; + + comp->mdp_dev = mdp; + comp->regs = of_iomap(node, 0); + comp->reg_base = base; +} + +static int mdp_comp_init(struct mdp_dev *mdp, struct device_node *node, + struct mdp_comp *comp, enum mtk_mdp_comp_id id) +{ + struct device *dev = &mdp->pdev->dev; + int clk_num; + int clk_ofst; + int i; + s32 event; + + if (id < 0 || id >= MDP_MAX_COMP_COUNT) { + dev_err(dev, "Invalid component id %d\n", id); + return -EINVAL; + } + + comp->id = id; + comp->type = mdp_comp_matches[id].type; + comp->alias_id = mdp_comp_matches[id].alias_id; + comp->ops = mdp_comp_ops[comp->type]; + __mdp_comp_init(mdp, node, comp); + + clk_num = mdp_comp_dt_info[id].clk_num; + clk_ofst = mdp_comp_dt_info[id].clk_ofst; + + for (i = 0; i < clk_num; i++) { + comp->clks[i] = of_clk_get(node, i + clk_ofst); + if (IS_ERR(comp->clks[i])) + break; + } + + mdp_get_subsys_id(dev, node, comp); + + /* Set GCE SOF event */ + if (is_bypass_gce_event(comp->type) || + of_property_read_u32_index(node, "mediatek,gce-events", + MDP_GCE_EVENT_SOF, &event)) + event = MDP_GCE_NO_EVENT; + + comp->gce_event[MDP_GCE_EVENT_SOF] = event; + + /* Set GCE EOF event */ + if (is_dma_capable(comp->type)) { + if (of_property_read_u32_index(node, "mediatek,gce-events", + MDP_GCE_EVENT_EOF, &event)) { + dev_err(dev, "Component id %d has no EOF\n", id); + return -EINVAL; + } + } else { + event = MDP_GCE_NO_EVENT; + } + + comp->gce_event[MDP_GCE_EVENT_EOF] = event; + + return 0; +} + +static void mdp_comp_deinit(struct mdp_comp *comp) +{ + if (!comp) + return; + + if (comp->regs) + iounmap(comp->regs); +} + +static struct mdp_comp *mdp_comp_create(struct mdp_dev *mdp, + struct device_node *node, + enum mtk_mdp_comp_id id) +{ + struct device *dev = &mdp->pdev->dev; + struct mdp_comp *comp; + int ret; + + if (mdp->comp[id]) + return ERR_PTR(-EEXIST); + + comp = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL); + if (!comp) + return ERR_PTR(-ENOMEM); + + ret = mdp_comp_init(mdp, node, comp, id); + if (ret) { + devm_kfree(dev, comp); + return ERR_PTR(ret); + } + mdp->comp[id] = comp; + mdp->comp[id]->mdp_dev = mdp; + + dev_dbg(dev, "%s type:%d alias:%d id:%d base:%#x regs:%p\n", + dev->of_node->name, comp->type, comp->alias_id, id, + (u32)comp->reg_base, comp->regs); + return comp; +} + +static int mdp_comp_sub_create(struct mdp_dev *mdp) +{ + struct device *dev = &mdp->pdev->dev; + struct device_node *node, *parent; + + parent = dev->of_node->parent; + + for_each_child_of_node(parent, node) { + const struct of_device_id *of_id; + enum mdp_comp_type type; + int id, alias_id; + struct mdp_comp *comp; + + of_id = of_match_node(mdp_sub_comp_dt_ids, node); + if (!of_id) + continue; + if (!of_device_is_available(node)) { + dev_dbg(dev, "Skipping disabled sub comp. %pOF\n", + node); + continue; + } + + type = (enum mdp_comp_type)(uintptr_t)of_id->data; + alias_id = mdp_comp_alias_id[type]; + id = mdp_comp_get_id(type, alias_id); + if (id < 0) { + dev_err(dev, + "Fail to get sub comp. id: type %d alias %d\n", + type, alias_id); + return -EINVAL; + } + mdp_comp_alias_id[type]++; + + comp = mdp_comp_create(mdp, node, id); + if (IS_ERR(comp)) + return PTR_ERR(comp); + } + + return 0; +} + +void mdp_comp_destroy(struct mdp_dev *mdp) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(mdp->comp); i++) { + if (mdp->comp[i]) { + pm_runtime_disable(mdp->comp[i]->comp_dev); + mdp_comp_deinit(mdp->comp[i]); + devm_kfree(mdp->comp[i]->comp_dev, mdp->comp[i]); + mdp->comp[i] = NULL; + } + } +} + +int mdp_comp_config(struct mdp_dev *mdp) +{ + struct device *dev = &mdp->pdev->dev; + struct device_node *node, *parent; + struct platform_device *pdev; + int ret; + + memset(mdp_comp_alias_id, 0, sizeof(mdp_comp_alias_id)); + + parent = dev->of_node->parent; + /* Iterate over sibling MDP function blocks */ + for_each_child_of_node(parent, node) { + const struct of_device_id *of_id; + enum mdp_comp_type type; + int id, alias_id; + struct mdp_comp *comp; + + of_id = of_match_node(mdp_comp_dt_ids, node); + if (!of_id) + continue; + + if (!of_device_is_available(node)) { + dev_dbg(dev, "Skipping disabled component %pOF\n", + node); + continue; + } + + type = (enum mdp_comp_type)(uintptr_t)of_id->data; + alias_id = mdp_comp_alias_id[type]; + id = mdp_comp_get_id(type, alias_id); + if (id < 0) { + dev_err(dev, + "Fail to get component id: type %d alias %d\n", + type, alias_id); + continue; + } + mdp_comp_alias_id[type]++; + + comp = mdp_comp_create(mdp, node, id); + if (IS_ERR(comp)) { + ret = PTR_ERR(comp); + goto err_init_comps; + } + + /* Only DMA capable components need the pm control */ + comp->comp_dev = NULL; + if (!is_dma_capable(comp->type)) + continue; + + pdev = of_find_device_by_node(node); + if (!pdev) { + dev_warn(dev, "can't find platform device of node:%s\n", + node->name); + return -ENODEV; + } + + comp->comp_dev = &pdev->dev; + pm_runtime_enable(comp->comp_dev); + } + + ret = mdp_comp_sub_create(mdp); + if (ret) + goto err_init_comps; + + return 0; + +err_init_comps: + mdp_comp_destroy(mdp); + return ret; +} + +int mdp_comp_ctx_config(struct mdp_dev *mdp, struct mdp_comp_ctx *ctx, + const struct img_compparam *param, + const struct img_ipi_frameparam *frame) +{ + struct device *dev = &mdp->pdev->dev; + int i; + + if (param->type < 0 || param->type >= MDP_MAX_COMP_COUNT) { + dev_err(dev, "Invalid component id %d", param->type); + return -EINVAL; + } + + ctx->comp = mdp->comp[param->type]; + if (!ctx->comp) { + dev_err(dev, "Uninit component id %d", param->type); + return -EINVAL; + } + + ctx->param = param; + ctx->input = &frame->inputs[param->input]; + for (i = 0; i < param->num_outputs; i++) + ctx->outputs[i] = &frame->outputs[param->outputs[i]]; + return 0; +} diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.h b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.h new file mode 100644 index 000000000..dc48f55ac --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-comp.h @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MTK_MDP3_COMP_H__ +#define __MTK_MDP3_COMP_H__ + +#include "mtk-mdp3-cmdq.h" + +#define MM_REG_WRITE_MASK(cmd, id, base, ofst, val, mask, ...) \ + cmdq_pkt_write_mask(&((cmd)->pkt), id, \ + (base) + (ofst), (val), (mask), ##__VA_ARGS__) + +#define MM_REG_WRITE(cmd, id, base, ofst, val, mask, ...) \ +do { \ + typeof(mask) (m) = (mask); \ + MM_REG_WRITE_MASK(cmd, id, base, ofst, val, \ + (((m) & (ofst##_MASK)) == (ofst##_MASK)) ? \ + (0xffffffff) : (m), ##__VA_ARGS__); \ +} while (0) + +#define MM_REG_WAIT(cmd, evt) \ +do { \ + typeof(cmd) (c) = (cmd); \ + typeof(evt) (e) = (evt); \ + cmdq_pkt_wfe(&((c)->pkt), (e), true); \ +} while (0) + +#define MM_REG_WAIT_NO_CLEAR(cmd, evt) \ +do { \ + typeof(cmd) (c) = (cmd); \ + typeof(evt) (e) = (evt); \ + cmdq_pkt_wfe(&((c)->pkt), (e), false); \ +} while (0) + +#define MM_REG_CLEAR(cmd, evt) \ +do { \ + typeof(cmd) (c) = (cmd); \ + typeof(evt) (e) = (evt); \ + cmdq_pkt_clear_event(&((c)->pkt), (e)); \ +} while (0) + +#define MM_REG_SET_EVENT(cmd, evt) \ +do { \ + typeof(cmd) (c) = (cmd); \ + typeof(evt) (e) = (evt); \ + cmdq_pkt_set_event(&((c)->pkt), (e)); \ +} while (0) + +#define MM_REG_POLL_MASK(cmd, id, base, ofst, val, _mask, ...) \ +do { \ + typeof(_mask) (_m) = (_mask); \ + cmdq_pkt_poll_mask(&((cmd)->pkt), id, \ + (base) + (ofst), (val), (_m), ##__VA_ARGS__); \ +} while (0) + +#define MM_REG_POLL(cmd, id, base, ofst, val, mask, ...) \ +do { \ + typeof(mask) (m) = (mask); \ + MM_REG_POLL_MASK((cmd), id, base, ofst, val, \ + (((m) & (ofst##_MASK)) == (ofst##_MASK)) ? \ + (0xffffffff) : (m), ##__VA_ARGS__); \ +} while (0) + +enum mtk_mdp_comp_id { + MDP_COMP_NONE = -1, /* Invalid engine */ + + /* ISP */ + MDP_COMP_WPEI = 0, + MDP_COMP_WPEO, /* 1 */ + MDP_COMP_WPEI2, /* 2 */ + MDP_COMP_WPEO2, /* 3 */ + MDP_COMP_ISP_IMGI, /* 4 */ + MDP_COMP_ISP_IMGO, /* 5 */ + MDP_COMP_ISP_IMG2O, /* 6 */ + + /* IPU */ + MDP_COMP_IPUI, /* 7 */ + MDP_COMP_IPUO, /* 8 */ + + /* MDP */ + MDP_COMP_CAMIN, /* 9 */ + MDP_COMP_CAMIN2, /* 10 */ + MDP_COMP_RDMA0, /* 11 */ + MDP_COMP_AAL0, /* 12 */ + MDP_COMP_CCORR0, /* 13 */ + MDP_COMP_RSZ0, /* 14 */ + MDP_COMP_RSZ1, /* 15 */ + MDP_COMP_TDSHP0, /* 16 */ + MDP_COMP_COLOR0, /* 17 */ + MDP_COMP_PATH0_SOUT, /* 18 */ + MDP_COMP_PATH1_SOUT, /* 19 */ + MDP_COMP_WROT0, /* 20 */ + MDP_COMP_WDMA, /* 21 */ + + /* Dummy Engine */ + MDP_COMP_RDMA1, /* 22 */ + MDP_COMP_RSZ2, /* 23 */ + MDP_COMP_TDSHP1, /* 24 */ + MDP_COMP_WROT1, /* 25 */ + + MDP_MAX_COMP_COUNT /* ALWAYS keep at the end */ +}; + +enum mdp_comp_type { + MDP_COMP_TYPE_INVALID = 0, + + MDP_COMP_TYPE_RDMA, + MDP_COMP_TYPE_RSZ, + MDP_COMP_TYPE_WROT, + MDP_COMP_TYPE_WDMA, + MDP_COMP_TYPE_PATH, + + MDP_COMP_TYPE_TDSHP, + MDP_COMP_TYPE_COLOR, + MDP_COMP_TYPE_DRE, + MDP_COMP_TYPE_CCORR, + MDP_COMP_TYPE_HDR, + + MDP_COMP_TYPE_IMGI, + MDP_COMP_TYPE_WPEI, + MDP_COMP_TYPE_EXTO, /* External path */ + MDP_COMP_TYPE_DL_PATH, /* Direct-link path */ + + MDP_COMP_TYPE_COUNT /* ALWAYS keep at the end */ +}; + +#define MDP_GCE_NO_EVENT (-1) +enum { + MDP_GCE_EVENT_SOF = 0, + MDP_GCE_EVENT_EOF = 1, + MDP_GCE_EVENT_MAX, +}; + +struct mdp_comp_ops; + +struct mdp_comp { + struct mdp_dev *mdp_dev; + void __iomem *regs; + phys_addr_t reg_base; + u8 subsys_id; + struct clk *clks[6]; + struct device *comp_dev; + enum mdp_comp_type type; + enum mtk_mdp_comp_id id; + u32 alias_id; + s32 gce_event[MDP_GCE_EVENT_MAX]; + const struct mdp_comp_ops *ops; +}; + +struct mdp_comp_ctx { + struct mdp_comp *comp; + const struct img_compparam *param; + const struct img_input *input; + const struct img_output *outputs[IMG_MAX_HW_OUTPUTS]; +}; + +struct mdp_comp_ops { + s64 (*get_comp_flag)(const struct mdp_comp_ctx *ctx); + int (*init_comp)(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd); + int (*config_frame)(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd, + const struct v4l2_rect *compose); + int (*config_subfrm)(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, u32 index); + int (*wait_comp_event)(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd); + int (*advance_subfrm)(struct mdp_comp_ctx *ctx, + struct mdp_cmdq_cmd *cmd, u32 index); + int (*post_process)(struct mdp_comp_ctx *ctx, struct mdp_cmdq_cmd *cmd); +}; + +struct mdp_dev; + +int mdp_comp_config(struct mdp_dev *mdp); +void mdp_comp_destroy(struct mdp_dev *mdp); +int mdp_comp_clock_on(struct device *dev, struct mdp_comp *comp); +void mdp_comp_clock_off(struct device *dev, struct mdp_comp *comp); +int mdp_comp_clocks_on(struct device *dev, struct mdp_comp *comps, int num); +void mdp_comp_clocks_off(struct device *dev, struct mdp_comp *comps, int num); +int mdp_comp_ctx_config(struct mdp_dev *mdp, struct mdp_comp_ctx *ctx, + const struct img_compparam *param, + const struct img_ipi_frameparam *frame); + +#endif /* __MTK_MDP3_COMP_H__ */ diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c new file mode 100644 index 000000000..97edcd9d1 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.c @@ -0,0 +1,360 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "mtk-mdp3-core.h" +#include "mtk-mdp3-m2m.h" + +static const struct mdp_platform_config mt8183_plat_cfg = { + .rdma_support_10bit = true, + .rdma_rsz1_sram_sharing = true, + .rdma_upsample_repeat_only = true, + .rsz_disable_dcm_small_sample = false, + .wrot_filter_constraint = false, +}; + +static const struct of_device_id mt8183_mdp_probe_infra[MDP_INFRA_MAX] = { + [MDP_INFRA_MMSYS] = { .compatible = "mediatek,mt8183-mmsys" }, + [MDP_INFRA_MUTEX] = { .compatible = "mediatek,mt8183-disp-mutex" }, + [MDP_INFRA_SCP] = { .compatible = "mediatek,mt8183-scp" } +}; + +static const u32 mt8183_mutex_idx[MDP_MAX_COMP_COUNT] = { + [MDP_COMP_RDMA0] = MUTEX_MOD_IDX_MDP_RDMA0, + [MDP_COMP_RSZ0] = MUTEX_MOD_IDX_MDP_RSZ0, + [MDP_COMP_RSZ1] = MUTEX_MOD_IDX_MDP_RSZ1, + [MDP_COMP_TDSHP0] = MUTEX_MOD_IDX_MDP_TDSHP0, + [MDP_COMP_WROT0] = MUTEX_MOD_IDX_MDP_WROT0, + [MDP_COMP_WDMA] = MUTEX_MOD_IDX_MDP_WDMA, + [MDP_COMP_AAL0] = MUTEX_MOD_IDX_MDP_AAL0, + [MDP_COMP_CCORR0] = MUTEX_MOD_IDX_MDP_CCORR0, +}; + +static const struct mtk_mdp_driver_data mt8183_mdp_driver_data = { + .mdp_probe_infra = mt8183_mdp_probe_infra, + .mdp_cfg = &mt8183_plat_cfg, + .mdp_mutex_table_idx = mt8183_mutex_idx, +}; + +static const struct of_device_id mdp_of_ids[] = { + { .compatible = "mediatek,mt8183-mdp3-rdma", + .data = &mt8183_mdp_driver_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mdp_of_ids); + +static struct platform_device *__get_pdev_by_id(struct platform_device *pdev, + enum mdp_infra_id id) +{ + struct device_node *node; + struct platform_device *mdp_pdev = NULL; + const struct mtk_mdp_driver_data *mdp_data; + const char *compat; + + if (!pdev) + return NULL; + + if (id < MDP_INFRA_MMSYS || id >= MDP_INFRA_MAX) { + dev_err(&pdev->dev, "Illegal infra id %d\n", id); + return NULL; + } + + mdp_data = of_device_get_match_data(&pdev->dev); + if (!mdp_data) { + dev_err(&pdev->dev, "have no driver data to find node\n"); + return NULL; + } + compat = mdp_data->mdp_probe_infra[id].compatible; + + node = of_find_compatible_node(NULL, NULL, compat); + if (WARN_ON(!node)) { + dev_err(&pdev->dev, "find node from id %d failed\n", id); + return NULL; + } + + mdp_pdev = of_find_device_by_node(node); + of_node_put(node); + if (WARN_ON(!mdp_pdev)) { + dev_err(&pdev->dev, "find pdev from id %d failed\n", id); + return NULL; + } + + return mdp_pdev; +} + +struct platform_device *mdp_get_plat_device(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *mdp_node; + struct platform_device *mdp_pdev; + + mdp_node = of_parse_phandle(dev->of_node, MDP_PHANDLE_NAME, 0); + if (!mdp_node) { + dev_err(dev, "can't get node %s\n", MDP_PHANDLE_NAME); + return NULL; + } + + mdp_pdev = of_find_device_by_node(mdp_node); + of_node_put(mdp_node); + + return mdp_pdev; +} +EXPORT_SYMBOL_GPL(mdp_get_plat_device); + +int mdp_vpu_get_locked(struct mdp_dev *mdp) +{ + int ret = 0; + + if (mdp->vpu_count++ == 0) { + ret = rproc_boot(mdp->rproc_handle); + if (ret) { + dev_err(&mdp->pdev->dev, + "vpu_load_firmware failed %d\n", ret); + goto err_load_vpu; + } + ret = mdp_vpu_register(mdp); + if (ret) { + dev_err(&mdp->pdev->dev, + "mdp_vpu register failed %d\n", ret); + goto err_reg_vpu; + } + ret = mdp_vpu_dev_init(&mdp->vpu, mdp->scp, &mdp->vpu_lock); + if (ret) { + dev_err(&mdp->pdev->dev, + "mdp_vpu device init failed %d\n", ret); + goto err_init_vpu; + } + } + return 0; + +err_init_vpu: + mdp_vpu_unregister(mdp); +err_reg_vpu: +err_load_vpu: + mdp->vpu_count--; + return ret; +} + +void mdp_vpu_put_locked(struct mdp_dev *mdp) +{ + if (--mdp->vpu_count == 0) { + mdp_vpu_dev_deinit(&mdp->vpu); + mdp_vpu_unregister(mdp); + } +} + +void mdp_video_device_release(struct video_device *vdev) +{ + struct mdp_dev *mdp = (struct mdp_dev *)video_get_drvdata(vdev); + int i; + + scp_put(mdp->scp); + + destroy_workqueue(mdp->job_wq); + destroy_workqueue(mdp->clock_wq); + + pm_runtime_disable(&mdp->pdev->dev); + + vb2_dma_contig_clear_max_seg_size(&mdp->pdev->dev); + + mdp_comp_destroy(mdp); + for (i = 0; i < MDP_PIPE_MAX; i++) + mtk_mutex_put(mdp->mdp_mutex[i]); + + mdp_vpu_shared_mem_free(&mdp->vpu); + v4l2_m2m_release(mdp->m2m_dev); + kfree(mdp); +} + +static int mdp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mdp_dev *mdp; + struct platform_device *mm_pdev; + int ret, i; + + mdp = kzalloc(sizeof(*mdp), GFP_KERNEL); + if (!mdp) { + ret = -ENOMEM; + goto err_return; + } + + mdp->pdev = pdev; + mdp->mdp_data = of_device_get_match_data(&pdev->dev); + + mm_pdev = __get_pdev_by_id(pdev, MDP_INFRA_MMSYS); + if (!mm_pdev) { + ret = -ENODEV; + goto err_destroy_device; + } + mdp->mdp_mmsys = &mm_pdev->dev; + + mm_pdev = __get_pdev_by_id(pdev, MDP_INFRA_MUTEX); + if (WARN_ON(!mm_pdev)) { + ret = -ENODEV; + goto err_destroy_device; + } + for (i = 0; i < MDP_PIPE_MAX; i++) { + mdp->mdp_mutex[i] = mtk_mutex_get(&mm_pdev->dev); + if (IS_ERR(mdp->mdp_mutex[i])) { + ret = PTR_ERR(mdp->mdp_mutex[i]); + goto err_free_mutex; + } + } + + ret = mdp_comp_config(mdp); + if (ret) { + dev_err(dev, "Failed to config mdp components\n"); + goto err_free_mutex; + } + + mdp->job_wq = alloc_workqueue(MDP_MODULE_NAME, WQ_FREEZABLE, 0); + if (!mdp->job_wq) { + dev_err(dev, "Unable to create job workqueue\n"); + ret = -ENOMEM; + goto err_deinit_comp; + } + + mdp->clock_wq = alloc_workqueue(MDP_MODULE_NAME "-clock", WQ_FREEZABLE, + 0); + if (!mdp->clock_wq) { + dev_err(dev, "Unable to create clock workqueue\n"); + ret = -ENOMEM; + goto err_destroy_job_wq; + } + + mm_pdev = __get_pdev_by_id(pdev, MDP_INFRA_SCP); + if (WARN_ON(!mm_pdev)) { + dev_err(&pdev->dev, "Could not get scp device\n"); + ret = -ENODEV; + goto err_destroy_clock_wq; + } + mdp->scp = platform_get_drvdata(mm_pdev); + mdp->rproc_handle = scp_get_rproc(mdp->scp); + dev_dbg(&pdev->dev, "MDP rproc_handle: %pK", mdp->rproc_handle); + + mutex_init(&mdp->vpu_lock); + mutex_init(&mdp->m2m_lock); + + mdp->cmdq_clt = cmdq_mbox_create(dev, 0); + if (IS_ERR(mdp->cmdq_clt)) { + ret = PTR_ERR(mdp->cmdq_clt); + goto err_put_scp; + } + + init_waitqueue_head(&mdp->callback_wq); + ida_init(&mdp->mdp_ida); + platform_set_drvdata(pdev, mdp); + + vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); + + ret = v4l2_device_register(dev, &mdp->v4l2_dev); + if (ret) { + dev_err(dev, "Failed to register v4l2 device\n"); + ret = -EINVAL; + goto err_mbox_destroy; + } + + ret = mdp_m2m_device_register(mdp); + if (ret) { + v4l2_err(&mdp->v4l2_dev, "Failed to register m2m device\n"); + goto err_unregister_device; + } + + dev_dbg(dev, "mdp-%d registered successfully\n", pdev->id); + return 0; + +err_unregister_device: + v4l2_device_unregister(&mdp->v4l2_dev); +err_mbox_destroy: + cmdq_mbox_destroy(mdp->cmdq_clt); +err_put_scp: + scp_put(mdp->scp); +err_destroy_clock_wq: + destroy_workqueue(mdp->clock_wq); +err_destroy_job_wq: + destroy_workqueue(mdp->job_wq); +err_deinit_comp: + mdp_comp_destroy(mdp); +err_free_mutex: + for (i = 0; i < MDP_PIPE_MAX; i++) + if (!IS_ERR_OR_NULL(mdp->mdp_mutex[i])) + mtk_mutex_put(mdp->mdp_mutex[i]); +err_destroy_device: + kfree(mdp); +err_return: + dev_dbg(dev, "Errno %d\n", ret); + return ret; +} + +static int mdp_remove(struct platform_device *pdev) +{ + struct mdp_dev *mdp = platform_get_drvdata(pdev); + + v4l2_device_unregister(&mdp->v4l2_dev); + + dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name); + return 0; +} + +static int __maybe_unused mdp_suspend(struct device *dev) +{ + struct mdp_dev *mdp = dev_get_drvdata(dev); + int ret; + + atomic_set(&mdp->suspended, 1); + + if (atomic_read(&mdp->job_count)) { + ret = wait_event_timeout(mdp->callback_wq, + !atomic_read(&mdp->job_count), + 2 * HZ); + if (ret == 0) { + dev_err(dev, + "%s:flushed cmdq task incomplete, count=%d\n", + __func__, atomic_read(&mdp->job_count)); + return -EBUSY; + } + } + + return 0; +} + +static int __maybe_unused mdp_resume(struct device *dev) +{ + struct mdp_dev *mdp = dev_get_drvdata(dev); + + atomic_set(&mdp->suspended, 0); + + return 0; +} + +static const struct dev_pm_ops mdp_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(mdp_suspend, mdp_resume) +}; + +static struct platform_driver mdp_driver = { + .probe = mdp_probe, + .remove = mdp_remove, + .driver = { + .name = MDP_MODULE_NAME, + .pm = &mdp_pm_ops, + .of_match_table = of_match_ptr(mdp_of_ids), + }, +}; + +module_platform_driver(mdp_driver); + +MODULE_AUTHOR("Ping-Hsun Wu "); +MODULE_DESCRIPTION("MediaTek image processor 3 driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.h b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.h new file mode 100644 index 000000000..2ef5fbc4f --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-core.h @@ -0,0 +1,94 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MTK_MDP3_CORE_H__ +#define __MTK_MDP3_CORE_H__ + +#include +#include +#include +#include +#include "mtk-mdp3-comp.h" +#include "mtk-mdp3-vpu.h" + +#define MDP_MODULE_NAME "mtk-mdp3" +#define MDP_DEVICE_NAME "MediaTek MDP3" +#define MDP_PHANDLE_NAME "mediatek,mdp3" + +enum mdp_infra_id { + MDP_INFRA_MMSYS, + MDP_INFRA_MUTEX, + MDP_INFRA_SCP, + MDP_INFRA_MAX +}; + +enum mdp_buffer_usage { + MDP_BUFFER_USAGE_HW_READ, + MDP_BUFFER_USAGE_MDP, + MDP_BUFFER_USAGE_MDP2, + MDP_BUFFER_USAGE_ISP, + MDP_BUFFER_USAGE_WPE, +}; + +struct mdp_platform_config { + bool rdma_support_10bit; + bool rdma_rsz1_sram_sharing; + bool rdma_upsample_repeat_only; + bool rsz_disable_dcm_small_sample; + bool wrot_filter_constraint; +}; + +/* indicate which mutex is used by each pipepline */ +enum mdp_pipe_id { + MDP_PIPE_RDMA0, + MDP_PIPE_IMGI, + MDP_PIPE_WPEI, + MDP_PIPE_WPEI2, + MDP_PIPE_MAX +}; + +struct mtk_mdp_driver_data { + const struct of_device_id *mdp_probe_infra; + const struct mdp_platform_config *mdp_cfg; + const u32 *mdp_mutex_table_idx; +}; + +struct mdp_dev { + struct platform_device *pdev; + struct device *mdp_mmsys; + struct mtk_mutex *mdp_mutex[MDP_PIPE_MAX]; + struct mdp_comp *comp[MDP_MAX_COMP_COUNT]; + const struct mtk_mdp_driver_data *mdp_data; + + struct workqueue_struct *job_wq; + struct workqueue_struct *clock_wq; + struct mdp_vpu_dev vpu; + struct mtk_scp *scp; + struct rproc *rproc_handle; + /* synchronization protect for accessing vpu working buffer info */ + struct mutex vpu_lock; + s32 vpu_count; + u32 id_count; + struct ida mdp_ida; + struct cmdq_client *cmdq_clt; + wait_queue_head_t callback_wq; + + struct v4l2_device v4l2_dev; + struct video_device *m2m_vdev; + struct v4l2_m2m_dev *m2m_dev; + /* synchronization protect for m2m device operation */ + struct mutex m2m_lock; + atomic_t suspended; + atomic_t job_count; +}; + +int mdp_vpu_get_locked(struct mdp_dev *mdp); +void mdp_vpu_put_locked(struct mdp_dev *mdp); +int mdp_vpu_register(struct mdp_dev *mdp); +void mdp_vpu_unregister(struct mdp_dev *mdp); +void mdp_video_device_release(struct video_device *vdev); + +#endif /* __MTK_MDP3_CORE_H__ */ diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.c new file mode 100644 index 000000000..8612a48bd --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.c @@ -0,0 +1,730 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#include +#include +#include +#include +#include "mtk-mdp3-m2m.h" + +static inline struct mdp_m2m_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct mdp_m2m_ctx, fh); +} + +static inline struct mdp_m2m_ctx *ctrl_to_ctx(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mdp_m2m_ctx, ctrl_handler); +} + +static inline struct mdp_frame *ctx_get_frame(struct mdp_m2m_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->curr_param.output; + else + return &ctx->curr_param.captures[0]; +} + +static inline void mdp_m2m_ctx_set_state(struct mdp_m2m_ctx *ctx, u32 state) +{ + atomic_or(state, &ctx->curr_param.state); +} + +static inline bool mdp_m2m_ctx_is_state_set(struct mdp_m2m_ctx *ctx, u32 mask) +{ + return ((atomic_read(&ctx->curr_param.state) & mask) == mask); +} + +static void mdp_m2m_process_done(void *priv, int vb_state) +{ + struct mdp_m2m_ctx *ctx = priv; + struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf; + + src_vbuf = (struct vb2_v4l2_buffer *) + v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + dst_vbuf = (struct vb2_v4l2_buffer *) + v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + ctx->curr_param.frame_no = ctx->frame_count[MDP_M2M_SRC]; + src_vbuf->sequence = ctx->frame_count[MDP_M2M_SRC]++; + dst_vbuf->sequence = ctx->frame_count[MDP_M2M_DST]++; + v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf, true); + + v4l2_m2m_buf_done(src_vbuf, vb_state); + v4l2_m2m_buf_done(dst_vbuf, vb_state); + v4l2_m2m_job_finish(ctx->mdp_dev->m2m_dev, ctx->m2m_ctx); +} + +static void mdp_m2m_device_run(void *priv) +{ + struct mdp_m2m_ctx *ctx = priv; + struct mdp_frame *frame; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + struct img_ipi_frameparam param = {}; + struct mdp_cmdq_param task = {}; + enum vb2_buffer_state vb_state = VB2_BUF_STATE_ERROR; + int ret; + + if (mdp_m2m_ctx_is_state_set(ctx, MDP_M2M_CTX_ERROR)) { + dev_err(&ctx->mdp_dev->pdev->dev, + "mdp_m2m_ctx is in error state\n"); + goto worker_end; + } + + param.frame_no = ctx->curr_param.frame_no; + param.type = ctx->curr_param.type; + param.num_inputs = 1; + param.num_outputs = 1; + + frame = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + src_vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + mdp_set_src_config(¶m.inputs[0], frame, &src_vb->vb2_buf); + + frame = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + dst_vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + mdp_set_dst_config(¶m.outputs[0], frame, &dst_vb->vb2_buf); + + ret = mdp_vpu_process(&ctx->vpu, ¶m); + if (ret) { + dev_err(&ctx->mdp_dev->pdev->dev, + "VPU MDP process failed: %d\n", ret); + goto worker_end; + } + + task.config = ctx->vpu.config; + task.param = ¶m; + task.composes[0] = &frame->compose; + task.cmdq_cb = NULL; + task.cb_data = NULL; + task.mdp_ctx = ctx; + + ret = mdp_cmdq_send(ctx->mdp_dev, &task); + if (ret) { + dev_err(&ctx->mdp_dev->pdev->dev, + "CMDQ sendtask failed: %d\n", ret); + goto worker_end; + } + + return; + +worker_end: + mdp_m2m_process_done(ctx, vb_state); +} + +static int mdp_m2m_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(q); + struct mdp_frame *capture; + struct vb2_queue *vq; + int ret; + bool out_streaming, cap_streaming; + + if (V4L2_TYPE_IS_OUTPUT(q->type)) + ctx->frame_count[MDP_M2M_SRC] = 0; + + if (V4L2_TYPE_IS_CAPTURE(q->type)) + ctx->frame_count[MDP_M2M_DST] = 0; + + capture = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + vq = v4l2_m2m_get_src_vq(ctx->m2m_ctx); + out_streaming = vb2_is_streaming(vq); + vq = v4l2_m2m_get_dst_vq(ctx->m2m_ctx); + cap_streaming = vb2_is_streaming(vq); + + /* Check to see if scaling ratio is within supported range */ + if ((V4L2_TYPE_IS_OUTPUT(q->type) && cap_streaming) || + (V4L2_TYPE_IS_CAPTURE(q->type) && out_streaming)) { + ret = mdp_check_scaling_ratio(&capture->crop.c, + &capture->compose, + capture->rotation, + ctx->curr_param.limit); + if (ret) { + dev_err(&ctx->mdp_dev->pdev->dev, + "Out of scaling range\n"); + return ret; + } + } + + if (!mdp_m2m_ctx_is_state_set(ctx, MDP_VPU_INIT)) { + ret = mdp_vpu_get_locked(ctx->mdp_dev); + if (ret) + return ret; + + ret = mdp_vpu_ctx_init(&ctx->vpu, &ctx->mdp_dev->vpu, + MDP_DEV_M2M); + if (ret) { + dev_err(&ctx->mdp_dev->pdev->dev, + "VPU init failed %d\n", ret); + return -EINVAL; + } + mdp_m2m_ctx_set_state(ctx, MDP_VPU_INIT); + } + + return 0; +} + +static struct vb2_v4l2_buffer *mdp_m2m_buf_remove(struct mdp_m2m_ctx *ctx, + unsigned int type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return (struct vb2_v4l2_buffer *) + v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + else + return (struct vb2_v4l2_buffer *) + v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); +} + +static void mdp_m2m_stop_streaming(struct vb2_queue *q) +{ + struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vb; + + vb = mdp_m2m_buf_remove(ctx, q->type); + while (vb) { + v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR); + vb = mdp_m2m_buf_remove(ctx, q->type); + } +} + +static int mdp_m2m_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(q); + struct v4l2_pix_format_mplane *pix_mp; + u32 i; + + pix_mp = &ctx_get_frame(ctx, q->type)->format.fmt.pix_mp; + + /* from VIDIOC_CREATE_BUFS */ + if (*num_planes) { + if (*num_planes != pix_mp->num_planes) + return -EINVAL; + for (i = 0; i < pix_mp->num_planes; ++i) + if (sizes[i] < pix_mp->plane_fmt[i].sizeimage) + return -EINVAL; + } else {/* from VIDIOC_REQBUFS */ + *num_planes = pix_mp->num_planes; + for (i = 0; i < pix_mp->num_planes; ++i) + sizes[i] = pix_mp->plane_fmt[i].sizeimage; + } + + return 0; +} + +static int mdp_m2m_buf_prepare(struct vb2_buffer *vb) +{ + struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_pix_format_mplane *pix_mp; + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + u32 i; + + v4l2_buf->field = V4L2_FIELD_NONE; + + if (V4L2_TYPE_IS_CAPTURE(vb->type)) { + pix_mp = &ctx_get_frame(ctx, vb->type)->format.fmt.pix_mp; + for (i = 0; i < pix_mp->num_planes; ++i) { + vb2_set_plane_payload(vb, i, + pix_mp->plane_fmt[i].sizeimage); + } + } + return 0; +} + +static int mdp_m2m_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + + v4l2_buf->field = V4L2_FIELD_NONE; + + return 0; +} + +static void mdp_m2m_buf_queue(struct vb2_buffer *vb) +{ + struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb); + + v4l2_buf->field = V4L2_FIELD_NONE; + + v4l2_m2m_buf_queue(ctx->m2m_ctx, to_vb2_v4l2_buffer(vb)); +} + +static const struct vb2_ops mdp_m2m_qops = { + .queue_setup = mdp_m2m_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = mdp_m2m_buf_prepare, + .start_streaming = mdp_m2m_start_streaming, + .stop_streaming = mdp_m2m_stop_streaming, + .buf_queue = mdp_m2m_buf_queue, + .buf_out_validate = mdp_m2m_buf_out_validate, +}; + +static int mdp_m2m_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MDP_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, MDP_DEVICE_NAME, sizeof(cap->card)); + + return 0; +} + +static int mdp_m2m_enum_fmt_mplane(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + return mdp_enum_fmt_mplane(f); +} + +static int mdp_m2m_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mdp_m2m_ctx *ctx = fh_to_ctx(fh); + struct mdp_frame *frame; + struct v4l2_pix_format_mplane *pix_mp; + + frame = ctx_get_frame(ctx, f->type); + *f = frame->format; + pix_mp = &f->fmt.pix_mp; + pix_mp->colorspace = ctx->curr_param.colorspace; + pix_mp->xfer_func = ctx->curr_param.xfer_func; + pix_mp->ycbcr_enc = ctx->curr_param.ycbcr_enc; + pix_mp->quantization = ctx->curr_param.quant; + + return 0; +} + +static int mdp_m2m_s_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mdp_m2m_ctx *ctx = fh_to_ctx(fh); + struct mdp_frame *frame = ctx_get_frame(ctx, f->type); + struct mdp_frame *capture; + const struct mdp_format *fmt; + struct vb2_queue *vq; + + fmt = mdp_try_fmt_mplane(f, &ctx->curr_param, ctx->id); + if (!fmt) + return -EINVAL; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (vb2_is_busy(vq)) + return -EBUSY; + + frame->format = *f; + frame->mdp_fmt = fmt; + frame->ycbcr_prof = mdp_map_ycbcr_prof_mplane(f, fmt->mdp_color); + frame->usage = V4L2_TYPE_IS_OUTPUT(f->type) ? + MDP_BUFFER_USAGE_HW_READ : MDP_BUFFER_USAGE_MDP; + + capture = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + capture->crop.c.left = 0; + capture->crop.c.top = 0; + capture->crop.c.width = f->fmt.pix_mp.width; + capture->crop.c.height = f->fmt.pix_mp.height; + ctx->curr_param.colorspace = f->fmt.pix_mp.colorspace; + ctx->curr_param.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; + ctx->curr_param.quant = f->fmt.pix_mp.quantization; + ctx->curr_param.xfer_func = f->fmt.pix_mp.xfer_func; + } else { + capture->compose.left = 0; + capture->compose.top = 0; + capture->compose.width = f->fmt.pix_mp.width; + capture->compose.height = f->fmt.pix_mp.height; + } + + return 0; +} + +static int mdp_m2m_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct mdp_m2m_ctx *ctx = fh_to_ctx(fh); + + if (!mdp_try_fmt_mplane(f, &ctx->curr_param, ctx->id)) + return -EINVAL; + + return 0; +} + +static int mdp_m2m_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct mdp_m2m_ctx *ctx = fh_to_ctx(fh); + struct mdp_frame *frame; + bool valid = false; + + if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + valid = mdp_target_is_crop(s->target); + else if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + valid = mdp_target_is_compose(s->target); + + if (!valid) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + frame = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + s->r = frame->crop.c; + return 0; + case V4L2_SEL_TGT_COMPOSE: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + frame = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + s->r = frame->compose; + return 0; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + frame = ctx_get_frame(ctx, s->type); + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->format.fmt.pix_mp.width; + s->r.height = frame->format.fmt.pix_mp.height; + return 0; + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + frame = ctx_get_frame(ctx, s->type); + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->format.fmt.pix_mp.width; + s->r.height = frame->format.fmt.pix_mp.height; + return 0; + } + return -EINVAL; +} + +static int mdp_m2m_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct mdp_m2m_ctx *ctx = fh_to_ctx(fh); + struct mdp_frame *frame = ctx_get_frame(ctx, s->type); + struct mdp_frame *capture; + struct v4l2_rect r; + struct device *dev = &ctx->mdp_dev->pdev->dev; + bool valid = false; + int ret; + + if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + valid = (s->target == V4L2_SEL_TGT_CROP); + else if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + valid = (s->target == V4L2_SEL_TGT_COMPOSE); + + if (!valid) { + dev_dbg(dev, "[%s:%d] invalid type:%u target:%u", __func__, + ctx->id, s->type, s->target); + return -EINVAL; + } + + ret = mdp_try_crop(ctx, &r, s, frame); + if (ret) + return ret; + capture = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + if (mdp_target_is_crop(s->target)) + capture->crop.c = r; + else + capture->compose = r; + + s->r = r; + + return 0; +} + +static const struct v4l2_ioctl_ops mdp_m2m_ioctl_ops = { + .vidioc_querycap = mdp_m2m_querycap, + .vidioc_enum_fmt_vid_cap = mdp_m2m_enum_fmt_mplane, + .vidioc_enum_fmt_vid_out = mdp_m2m_enum_fmt_mplane, + .vidioc_g_fmt_vid_cap_mplane = mdp_m2m_g_fmt_mplane, + .vidioc_g_fmt_vid_out_mplane = mdp_m2m_g_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = mdp_m2m_s_fmt_mplane, + .vidioc_s_fmt_vid_out_mplane = mdp_m2m_s_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = mdp_m2m_try_fmt_mplane, + .vidioc_try_fmt_vid_out_mplane = mdp_m2m_try_fmt_mplane, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_g_selection = mdp_m2m_g_selection, + .vidioc_s_selection = mdp_m2m_s_selection, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int mdp_m2m_queue_init(void *priv, + struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mdp_m2m_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->ops = &mdp_m2m_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->dev = &ctx->mdp_dev->pdev->dev; + src_vq->lock = &ctx->ctx_lock; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->ops = &mdp_m2m_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->dev = &ctx->mdp_dev->pdev->dev; + dst_vq->lock = &ctx->ctx_lock; + + return vb2_queue_init(dst_vq); +} + +static int mdp_m2m_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mdp_m2m_ctx *ctx = ctrl_to_ctx(ctrl); + struct mdp_frame *capture; + + capture = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + switch (ctrl->id) { + case V4L2_CID_HFLIP: + capture->hflip = ctrl->val; + break; + case V4L2_CID_VFLIP: + capture->vflip = ctrl->val; + break; + case V4L2_CID_ROTATE: + capture->rotation = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops mdp_m2m_ctrl_ops = { + .s_ctrl = mdp_m2m_s_ctrl, +}; + +static int mdp_m2m_ctrls_create(struct mdp_m2m_ctx *ctx) +{ + v4l2_ctrl_handler_init(&ctx->ctrl_handler, MDP_MAX_CTRLS); + ctx->ctrls.hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &mdp_m2m_ctrl_ops, V4L2_CID_HFLIP, + 0, 1, 1, 0); + ctx->ctrls.vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &mdp_m2m_ctrl_ops, V4L2_CID_VFLIP, + 0, 1, 1, 0); + ctx->ctrls.rotate = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &mdp_m2m_ctrl_ops, + V4L2_CID_ROTATE, 0, 270, 90, 0); + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + dev_err(&ctx->mdp_dev->pdev->dev, + "Failed to register controls\n"); + return err; + } + return 0; +} + +static int mdp_m2m_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct mdp_dev *mdp = video_get_drvdata(vdev); + struct mdp_m2m_ctx *ctx; + struct device *dev = &mdp->pdev->dev; + int ret; + struct v4l2_format default_format = {}; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (mutex_lock_interruptible(&mdp->m2m_lock)) { + ret = -ERESTARTSYS; + goto err_free_ctx; + } + + ret = ida_alloc(&mdp->mdp_ida, GFP_KERNEL); + if (ret < 0) + goto err_unlock_mutex; + ctx->id = ret; + + ctx->mdp_dev = mdp; + + v4l2_fh_init(&ctx->fh, vdev); + file->private_data = &ctx->fh; + ret = mdp_m2m_ctrls_create(ctx); + if (ret) + goto err_exit_fh; + + /* Use separate control handler per file handle */ + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + v4l2_fh_add(&ctx->fh); + + mutex_init(&ctx->ctx_lock); + ctx->m2m_ctx = v4l2_m2m_ctx_init(mdp->m2m_dev, ctx, mdp_m2m_queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + dev_err(dev, "Failed to initialize m2m context\n"); + ret = PTR_ERR(ctx->m2m_ctx); + goto err_release_handler; + } + ctx->fh.m2m_ctx = ctx->m2m_ctx; + + ctx->curr_param.ctx = ctx; + ret = mdp_frameparam_init(&ctx->curr_param); + if (ret) { + dev_err(dev, "Failed to initialize mdp parameter\n"); + goto err_release_m2m_ctx; + } + + mutex_unlock(&mdp->m2m_lock); + + /* Default format */ + default_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + default_format.fmt.pix_mp.width = 32; + default_format.fmt.pix_mp.height = 32; + default_format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420M; + mdp_m2m_s_fmt_mplane(file, &ctx->fh, &default_format); + default_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + mdp_m2m_s_fmt_mplane(file, &ctx->fh, &default_format); + + dev_dbg(dev, "%s:[%d]", __func__, ctx->id); + + return 0; + +err_release_m2m_ctx: + v4l2_m2m_ctx_release(ctx->m2m_ctx); +err_release_handler: + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); +err_exit_fh: + v4l2_fh_exit(&ctx->fh); + ida_free(&mdp->mdp_ida, ctx->id); +err_unlock_mutex: + mutex_unlock(&mdp->m2m_lock); +err_free_ctx: + kfree(ctx); + + return ret; +} + +static int mdp_m2m_release(struct file *file) +{ + struct mdp_m2m_ctx *ctx = fh_to_ctx(file->private_data); + struct mdp_dev *mdp = video_drvdata(file); + struct device *dev = &mdp->pdev->dev; + + mutex_lock(&mdp->m2m_lock); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + if (mdp_m2m_ctx_is_state_set(ctx, MDP_VPU_INIT)) { + mdp_vpu_ctx_deinit(&ctx->vpu); + mdp_vpu_put_locked(mdp); + } + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + ida_free(&mdp->mdp_ida, ctx->id); + mutex_unlock(&mdp->m2m_lock); + + dev_dbg(dev, "%s:[%d]", __func__, ctx->id); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations mdp_m2m_fops = { + .owner = THIS_MODULE, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, + .open = mdp_m2m_open, + .release = mdp_m2m_release, +}; + +static const struct v4l2_m2m_ops mdp_m2m_ops = { + .device_run = mdp_m2m_device_run, +}; + +int mdp_m2m_device_register(struct mdp_dev *mdp) +{ + struct device *dev = &mdp->pdev->dev; + int ret = 0; + + mdp->m2m_vdev = video_device_alloc(); + if (!mdp->m2m_vdev) { + dev_err(dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto err_video_alloc; + } + mdp->m2m_vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | + V4L2_CAP_STREAMING; + mdp->m2m_vdev->fops = &mdp_m2m_fops; + mdp->m2m_vdev->ioctl_ops = &mdp_m2m_ioctl_ops; + mdp->m2m_vdev->release = mdp_video_device_release; + mdp->m2m_vdev->lock = &mdp->m2m_lock; + mdp->m2m_vdev->vfl_dir = VFL_DIR_M2M; + mdp->m2m_vdev->v4l2_dev = &mdp->v4l2_dev; + snprintf(mdp->m2m_vdev->name, sizeof(mdp->m2m_vdev->name), "%s:m2m", + MDP_MODULE_NAME); + video_set_drvdata(mdp->m2m_vdev, mdp); + + mdp->m2m_dev = v4l2_m2m_init(&mdp_m2m_ops); + if (IS_ERR(mdp->m2m_dev)) { + dev_err(dev, "Failed to initialize v4l2-m2m device\n"); + ret = PTR_ERR(mdp->m2m_dev); + goto err_m2m_init; + } + + ret = video_register_device(mdp->m2m_vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(dev, "Failed to register video device\n"); + goto err_video_register; + } + + v4l2_info(&mdp->v4l2_dev, "Driver registered as /dev/video%d", + mdp->m2m_vdev->num); + return 0; + +err_video_register: + v4l2_m2m_release(mdp->m2m_dev); +err_m2m_init: + video_device_release(mdp->m2m_vdev); +err_video_alloc: + + return ret; +} + +void mdp_m2m_device_unregister(struct mdp_dev *mdp) +{ + video_unregister_device(mdp->m2m_vdev); +} + +void mdp_m2m_job_finish(struct mdp_m2m_ctx *ctx) +{ + enum vb2_buffer_state vb_state = VB2_BUF_STATE_DONE; + + mdp_m2m_process_done(ctx, vb_state); +} diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.h b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.h new file mode 100644 index 000000000..61ddbaf1b --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MTK_MDP3_M2M_H__ +#define __MTK_MDP3_M2M_H__ + +#include +#include "mtk-mdp3-core.h" +#include "mtk-mdp3-vpu.h" +#include "mtk-mdp3-regs.h" + +#define MDP_MAX_CTRLS 10 + +enum { + MDP_M2M_SRC = 0, + MDP_M2M_DST = 1, + MDP_M2M_MAX, +}; + +struct mdp_m2m_ctrls { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *rotate; +}; + +struct mdp_m2m_ctx { + u32 id; + struct mdp_dev *mdp_dev; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; + struct mdp_m2m_ctrls ctrls; + struct v4l2_m2m_ctx *m2m_ctx; + struct mdp_vpu_ctx vpu; + u32 frame_count[MDP_M2M_MAX]; + + struct mdp_frameparam curr_param; + /* synchronization protect for mdp m2m context */ + struct mutex ctx_lock; +}; + +int mdp_m2m_device_register(struct mdp_dev *mdp); +void mdp_m2m_device_unregister(struct mdp_dev *mdp); +void mdp_m2m_job_finish(struct mdp_m2m_ctx *ctx); + +#endif /* __MTK_MDP3_M2M_H__ */ diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-regs.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-regs.c new file mode 100644 index 000000000..36336d169 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-regs.c @@ -0,0 +1,737 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#include +#include +#include +#include +#include "mtk-mdp3-core.h" +#include "mtk-mdp3-regs.h" +#include "mtk-mdp3-m2m.h" + +/* + * All 10-bit related formats are not added in the basic format list, + * please add the corresponding format settings before use. + */ +static const struct mdp_format mdp_formats[] = { + { + .pixelformat = V4L2_PIX_FMT_GREY, + .mdp_color = MDP_COLOR_GREY, + .depth = { 8 }, + .row_depth = { 8 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_RGB565X, + .mdp_color = MDP_COLOR_BGR565, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_RGB565, + .mdp_color = MDP_COLOR_RGB565, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_RGB24, + .mdp_color = MDP_COLOR_RGB888, + .depth = { 24 }, + .row_depth = { 24 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_BGR24, + .mdp_color = MDP_COLOR_BGR888, + .depth = { 24 }, + .row_depth = { 24 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_ABGR32, + .mdp_color = MDP_COLOR_BGRA8888, + .depth = { 32 }, + .row_depth = { 32 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_ARGB32, + .mdp_color = MDP_COLOR_ARGB8888, + .depth = { 32 }, + .row_depth = { 32 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_UYVY, + .mdp_color = MDP_COLOR_UYVY, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + .walign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_VYUY, + .mdp_color = MDP_COLOR_VYUY, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + .walign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_YUYV, + .mdp_color = MDP_COLOR_YUYV, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + .walign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_YVYU, + .mdp_color = MDP_COLOR_YVYU, + .depth = { 16 }, + .row_depth = { 16 }, + .num_planes = 1, + .walign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_YUV420, + .mdp_color = MDP_COLOR_I420, + .depth = { 12 }, + .row_depth = { 8 }, + .num_planes = 1, + .walign = 1, + .halign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_YVU420, + .mdp_color = MDP_COLOR_YV12, + .depth = { 12 }, + .row_depth = { 8 }, + .num_planes = 1, + .walign = 1, + .halign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_NV12, + .mdp_color = MDP_COLOR_NV12, + .depth = { 12 }, + .row_depth = { 8 }, + .num_planes = 1, + .walign = 1, + .halign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_NV21, + .mdp_color = MDP_COLOR_NV21, + .depth = { 12 }, + .row_depth = { 8 }, + .num_planes = 1, + .walign = 1, + .halign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_NV16, + .mdp_color = MDP_COLOR_NV16, + .depth = { 16 }, + .row_depth = { 8 }, + .num_planes = 1, + .walign = 1, + .flags = MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_NV61, + .mdp_color = MDP_COLOR_NV61, + .depth = { 16 }, + .row_depth = { 8 }, + .num_planes = 1, + .walign = 1, + .flags = MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_NV24, + .mdp_color = MDP_COLOR_NV24, + .depth = { 24 }, + .row_depth = { 8 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_NV42, + .mdp_color = MDP_COLOR_NV42, + .depth = { 24 }, + .row_depth = { 8 }, + .num_planes = 1, + .flags = MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_MT21C, + .mdp_color = MDP_COLOR_420_BLK_UFO, + .depth = { 8, 4 }, + .row_depth = { 8, 8 }, + .num_planes = 2, + .walign = 4, + .halign = 5, + .flags = MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_MM21, + .mdp_color = MDP_COLOR_420_BLK, + .depth = { 8, 4 }, + .row_depth = { 8, 8 }, + .num_planes = 2, + .walign = 4, + .halign = 5, + .flags = MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_NV12M, + .mdp_color = MDP_COLOR_NV12, + .depth = { 8, 4 }, + .row_depth = { 8, 8 }, + .num_planes = 2, + .walign = 1, + .halign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_NV21M, + .mdp_color = MDP_COLOR_NV21, + .depth = { 8, 4 }, + .row_depth = { 8, 8 }, + .num_planes = 2, + .walign = 1, + .halign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_NV16M, + .mdp_color = MDP_COLOR_NV16, + .depth = { 8, 8 }, + .row_depth = { 8, 8 }, + .num_planes = 2, + .walign = 1, + .flags = MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_NV61M, + .mdp_color = MDP_COLOR_NV61, + .depth = { 8, 8 }, + .row_depth = { 8, 8 }, + .num_planes = 2, + .walign = 1, + .flags = MDP_FMT_FLAG_OUTPUT, + }, { + .pixelformat = V4L2_PIX_FMT_YUV420M, + .mdp_color = MDP_COLOR_I420, + .depth = { 8, 2, 2 }, + .row_depth = { 8, 4, 4 }, + .num_planes = 3, + .walign = 1, + .halign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + }, { + .pixelformat = V4L2_PIX_FMT_YVU420M, + .mdp_color = MDP_COLOR_YV12, + .depth = { 8, 2, 2 }, + .row_depth = { 8, 4, 4 }, + .num_planes = 3, + .walign = 1, + .halign = 1, + .flags = MDP_FMT_FLAG_OUTPUT | MDP_FMT_FLAG_CAPTURE, + } +}; + +static const struct mdp_limit mdp_def_limit = { + .out_limit = { + .wmin = 16, + .hmin = 16, + .wmax = 8176, + .hmax = 8176, + }, + .cap_limit = { + .wmin = 2, + .hmin = 2, + .wmax = 8176, + .hmax = 8176, + }, + .h_scale_up_max = 32, + .v_scale_up_max = 32, + .h_scale_down_max = 20, + .v_scale_down_max = 128, +}; + +static const struct mdp_format *mdp_find_fmt(u32 pixelformat, u32 type) +{ + u32 i, flag; + + flag = V4L2_TYPE_IS_OUTPUT(type) ? MDP_FMT_FLAG_OUTPUT : + MDP_FMT_FLAG_CAPTURE; + for (i = 0; i < ARRAY_SIZE(mdp_formats); ++i) { + if (!(mdp_formats[i].flags & flag)) + continue; + if (mdp_formats[i].pixelformat == pixelformat) + return &mdp_formats[i]; + } + return NULL; +} + +static const struct mdp_format *mdp_find_fmt_by_index(u32 index, u32 type) +{ + u32 i, flag, num = 0; + + flag = V4L2_TYPE_IS_OUTPUT(type) ? MDP_FMT_FLAG_OUTPUT : + MDP_FMT_FLAG_CAPTURE; + for (i = 0; i < ARRAY_SIZE(mdp_formats); ++i) { + if (!(mdp_formats[i].flags & flag)) + continue; + if (index == num) + return &mdp_formats[i]; + num++; + } + return NULL; +} + +enum mdp_ycbcr_profile mdp_map_ycbcr_prof_mplane(struct v4l2_format *f, + u32 mdp_color) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + + if (MDP_COLOR_IS_RGB(mdp_color)) + return MDP_YCBCR_PROFILE_FULL_BT601; + + switch (pix_mp->colorspace) { + case V4L2_COLORSPACE_JPEG: + return MDP_YCBCR_PROFILE_JPEG; + case V4L2_COLORSPACE_REC709: + case V4L2_COLORSPACE_DCI_P3: + if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE) + return MDP_YCBCR_PROFILE_FULL_BT709; + return MDP_YCBCR_PROFILE_BT709; + case V4L2_COLORSPACE_BT2020: + if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE) + return MDP_YCBCR_PROFILE_FULL_BT2020; + return MDP_YCBCR_PROFILE_BT2020; + default: + if (pix_mp->quantization == V4L2_QUANTIZATION_FULL_RANGE) + return MDP_YCBCR_PROFILE_FULL_BT601; + return MDP_YCBCR_PROFILE_BT601; + } +} + +static void mdp_bound_align_image(u32 *w, u32 *h, + struct v4l2_frmsize_stepwise *s, + unsigned int salign) +{ + unsigned int org_w, org_h; + + org_w = *w; + org_h = *h; + v4l_bound_align_image(w, s->min_width, s->max_width, s->step_width, + h, s->min_height, s->max_height, s->step_height, + salign); + + s->min_width = org_w; + s->min_height = org_h; + v4l2_apply_frmsize_constraints(w, h, s); +} + +static int mdp_clamp_align(s32 *x, int min, int max, unsigned int align) +{ + unsigned int mask; + + if (min < 0 || max < 0) + return -ERANGE; + + /* Bits that must be zero to be aligned */ + mask = ~((1 << align) - 1); + + min = 0 ? 0 : ((min + ~mask) & mask); + max = max & mask; + if ((unsigned int)min > (unsigned int)max) + return -ERANGE; + + /* Clamp to aligned min and max */ + *x = clamp(*x, min, max); + + /* Round to nearest aligned value */ + if (align) + *x = (*x + (1 << (align - 1))) & mask; + return 0; +} + +int mdp_enum_fmt_mplane(struct v4l2_fmtdesc *f) +{ + const struct mdp_format *fmt; + + fmt = mdp_find_fmt_by_index(f->index, f->type); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixelformat; + return 0; +} + +const struct mdp_format *mdp_try_fmt_mplane(struct v4l2_format *f, + struct mdp_frameparam *param, + u32 ctx_id) +{ + struct device *dev = ¶m->ctx->mdp_dev->pdev->dev; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct mdp_format *fmt; + const struct mdp_pix_limit *pix_limit; + struct v4l2_frmsize_stepwise s; + u32 org_w, org_h; + unsigned int i; + + fmt = mdp_find_fmt(pix_mp->pixelformat, f->type); + if (!fmt) { + fmt = mdp_find_fmt_by_index(0, f->type); + if (!fmt) { + dev_dbg(dev, "%d: pixelformat %c%c%c%c invalid", ctx_id, + (pix_mp->pixelformat & 0xff), + (pix_mp->pixelformat >> 8) & 0xff, + (pix_mp->pixelformat >> 16) & 0xff, + (pix_mp->pixelformat >> 24) & 0xff); + return NULL; + } + } + + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->flags = 0; + pix_mp->pixelformat = fmt->pixelformat; + if (V4L2_TYPE_IS_CAPTURE(f->type)) { + pix_mp->colorspace = param->colorspace; + pix_mp->xfer_func = param->xfer_func; + pix_mp->ycbcr_enc = param->ycbcr_enc; + pix_mp->quantization = param->quant; + } + + pix_limit = V4L2_TYPE_IS_OUTPUT(f->type) ? ¶m->limit->out_limit : + ¶m->limit->cap_limit; + s.min_width = pix_limit->wmin; + s.max_width = pix_limit->wmax; + s.step_width = fmt->walign; + s.min_height = pix_limit->hmin; + s.max_height = pix_limit->hmax; + s.step_height = fmt->halign; + org_w = pix_mp->width; + org_h = pix_mp->height; + + mdp_bound_align_image(&pix_mp->width, &pix_mp->height, &s, fmt->salign); + if (org_w != pix_mp->width || org_h != pix_mp->height) + dev_dbg(dev, "%d: size change: %ux%u to %ux%u", ctx_id, + org_w, org_h, pix_mp->width, pix_mp->height); + + if (pix_mp->num_planes && pix_mp->num_planes != fmt->num_planes) + dev_dbg(dev, "%d num of planes change: %u to %u", ctx_id, + pix_mp->num_planes, fmt->num_planes); + pix_mp->num_planes = fmt->num_planes; + + for (i = 0; i < pix_mp->num_planes; ++i) { + u32 min_bpl = (pix_mp->width * fmt->row_depth[i]) >> 3; + u32 max_bpl = (pix_limit->wmax * fmt->row_depth[i]) >> 3; + u32 bpl = pix_mp->plane_fmt[i].bytesperline; + u32 min_si, max_si; + u32 si = pix_mp->plane_fmt[i].sizeimage; + u64 di; + + bpl = clamp(bpl, min_bpl, max_bpl); + pix_mp->plane_fmt[i].bytesperline = bpl; + + di = (u64)bpl * pix_mp->height * fmt->depth[i]; + min_si = (u32)div_u64(di, fmt->row_depth[i]); + di = (u64)bpl * s.max_height * fmt->depth[i]; + max_si = (u32)div_u64(di, fmt->row_depth[i]); + + si = clamp(si, min_si, max_si); + pix_mp->plane_fmt[i].sizeimage = si; + + dev_dbg(dev, "%d: p%u, bpl:%u [%u, %u], sizeimage:%u [%u, %u]", + ctx_id, i, bpl, min_bpl, max_bpl, si, min_si, max_si); + } + + return fmt; +} + +static int mdp_clamp_start(s32 *x, int min, int max, unsigned int align, + u32 flags) +{ + if (flags & V4L2_SEL_FLAG_GE) + max = *x; + if (flags & V4L2_SEL_FLAG_LE) + min = *x; + return mdp_clamp_align(x, min, max, align); +} + +static int mdp_clamp_end(s32 *x, int min, int max, unsigned int align, + u32 flags) +{ + if (flags & V4L2_SEL_FLAG_GE) + min = *x; + if (flags & V4L2_SEL_FLAG_LE) + max = *x; + return mdp_clamp_align(x, min, max, align); +} + +int mdp_try_crop(struct mdp_m2m_ctx *ctx, struct v4l2_rect *r, + const struct v4l2_selection *s, struct mdp_frame *frame) +{ + struct device *dev = &ctx->mdp_dev->pdev->dev; + s32 left, top, right, bottom; + u32 framew, frameh, walign, halign; + int ret; + + dev_dbg(dev, "%d target:%d, set:(%d,%d) %ux%u", ctx->id, + s->target, s->r.left, s->r.top, s->r.width, s->r.height); + + left = s->r.left; + top = s->r.top; + right = s->r.left + s->r.width; + bottom = s->r.top + s->r.height; + framew = frame->format.fmt.pix_mp.width; + frameh = frame->format.fmt.pix_mp.height; + + if (mdp_target_is_crop(s->target)) { + walign = 1; + halign = 1; + } else { + walign = frame->mdp_fmt->walign; + halign = frame->mdp_fmt->halign; + } + + dev_dbg(dev, "%d align:%u,%u, bound:%ux%u", ctx->id, + walign, halign, framew, frameh); + + ret = mdp_clamp_start(&left, 0, right, walign, s->flags); + if (ret) + return ret; + ret = mdp_clamp_start(&top, 0, bottom, halign, s->flags); + if (ret) + return ret; + ret = mdp_clamp_end(&right, left, framew, walign, s->flags); + if (ret) + return ret; + ret = mdp_clamp_end(&bottom, top, frameh, halign, s->flags); + if (ret) + return ret; + + r->left = left; + r->top = top; + r->width = right - left; + r->height = bottom - top; + + dev_dbg(dev, "%d crop:(%d,%d) %ux%u", ctx->id, + r->left, r->top, r->width, r->height); + return 0; +} + +int mdp_check_scaling_ratio(const struct v4l2_rect *crop, + const struct v4l2_rect *compose, s32 rotation, + const struct mdp_limit *limit) +{ + u32 crop_w, crop_h, comp_w, comp_h; + + crop_w = crop->width; + crop_h = crop->height; + if (90 == rotation || 270 == rotation) { + comp_w = compose->height; + comp_h = compose->width; + } else { + comp_w = compose->width; + comp_h = compose->height; + } + + if ((crop_w / comp_w) > limit->h_scale_down_max || + (crop_h / comp_h) > limit->v_scale_down_max || + (comp_w / crop_w) > limit->h_scale_up_max || + (comp_h / crop_h) > limit->v_scale_up_max) + return -ERANGE; + return 0; +} + +/* Stride that is accepted by MDP HW */ +static u32 mdp_fmt_get_stride(const struct mdp_format *fmt, + u32 bytesperline, unsigned int plane) +{ + enum mdp_color c = fmt->mdp_color; + u32 stride; + + stride = (bytesperline * MDP_COLOR_BITS_PER_PIXEL(c)) + / fmt->row_depth[0]; + if (plane == 0) + return stride; + if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) { + if (MDP_COLOR_IS_BLOCK_MODE(c)) + stride = stride / 2; + return stride; + } + return 0; +} + +/* Stride that is accepted by MDP HW of format with contiguous planes */ +static u32 mdp_fmt_get_stride_contig(const struct mdp_format *fmt, + u32 pix_stride, unsigned int plane) +{ + enum mdp_color c = fmt->mdp_color; + u32 stride = pix_stride; + + if (plane == 0) + return stride; + if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) { + stride = stride >> MDP_COLOR_GET_H_SUBSAMPLE(c); + if (MDP_COLOR_IS_UV_COPLANE(c) && !MDP_COLOR_IS_BLOCK_MODE(c)) + stride = stride * 2; + return stride; + } + return 0; +} + +/* Plane size that is accepted by MDP HW */ +static u32 mdp_fmt_get_plane_size(const struct mdp_format *fmt, + u32 stride, u32 height, unsigned int plane) +{ + enum mdp_color c = fmt->mdp_color; + u32 bytesperline; + + bytesperline = (stride * fmt->row_depth[0]) + / MDP_COLOR_BITS_PER_PIXEL(c); + if (plane == 0) + return bytesperline * height; + if (plane < MDP_COLOR_GET_PLANE_COUNT(c)) { + height = height >> MDP_COLOR_GET_V_SUBSAMPLE(c); + if (MDP_COLOR_IS_BLOCK_MODE(c)) + bytesperline = bytesperline * 2; + return bytesperline * height; + } + return 0; +} + +static void mdp_prepare_buffer(struct img_image_buffer *b, + struct mdp_frame *frame, struct vb2_buffer *vb) +{ + struct v4l2_pix_format_mplane *pix_mp = &frame->format.fmt.pix_mp; + unsigned int i; + + b->format.colorformat = frame->mdp_fmt->mdp_color; + b->format.ycbcr_prof = frame->ycbcr_prof; + for (i = 0; i < pix_mp->num_planes; ++i) { + u32 stride = mdp_fmt_get_stride(frame->mdp_fmt, + pix_mp->plane_fmt[i].bytesperline, i); + + b->format.plane_fmt[i].stride = stride; + b->format.plane_fmt[i].size = + mdp_fmt_get_plane_size(frame->mdp_fmt, stride, + pix_mp->height, i); + b->iova[i] = vb2_dma_contig_plane_dma_addr(vb, i); + } + for (; i < MDP_COLOR_GET_PLANE_COUNT(b->format.colorformat); ++i) { + u32 stride = mdp_fmt_get_stride_contig(frame->mdp_fmt, + b->format.plane_fmt[0].stride, i); + + b->format.plane_fmt[i].stride = stride; + b->format.plane_fmt[i].size = + mdp_fmt_get_plane_size(frame->mdp_fmt, stride, + pix_mp->height, i); + b->iova[i] = b->iova[i - 1] + b->format.plane_fmt[i - 1].size; + } + b->usage = frame->usage; +} + +void mdp_set_src_config(struct img_input *in, + struct mdp_frame *frame, struct vb2_buffer *vb) +{ + in->buffer.format.width = frame->format.fmt.pix_mp.width; + in->buffer.format.height = frame->format.fmt.pix_mp.height; + mdp_prepare_buffer(&in->buffer, frame, vb); +} + +static u32 mdp_to_fixed(u32 *r, struct v4l2_fract *f) +{ + u32 q; + + if (f->denominator == 0) { + *r = 0; + return 0; + } + + q = f->numerator / f->denominator; + *r = div_u64(((u64)f->numerator - q * f->denominator) << + IMG_SUBPIXEL_SHIFT, f->denominator); + return q; +} + +static void mdp_set_src_crop(struct img_crop *c, struct mdp_crop *crop) +{ + c->left = crop->c.left + + mdp_to_fixed(&c->left_subpix, &crop->left_subpix); + c->top = crop->c.top + + mdp_to_fixed(&c->top_subpix, &crop->top_subpix); + c->width = crop->c.width + + mdp_to_fixed(&c->width_subpix, &crop->width_subpix); + c->height = crop->c.height + + mdp_to_fixed(&c->height_subpix, &crop->height_subpix); +} + +static void mdp_set_orientation(struct img_output *out, + s32 rotation, bool hflip, bool vflip) +{ + u8 flip = 0; + + if (hflip) + flip ^= 1; + if (vflip) { + /* + * A vertical flip is equivalent to + * a 180-degree rotation with a horizontal flip + */ + rotation += 180; + flip ^= 1; + } + + out->rotation = rotation % 360; + if (flip != 0) + out->flags |= IMG_CTRL_FLAG_HFLIP; + else + out->flags &= ~IMG_CTRL_FLAG_HFLIP; +} + +void mdp_set_dst_config(struct img_output *out, + struct mdp_frame *frame, struct vb2_buffer *vb) +{ + out->buffer.format.width = frame->compose.width; + out->buffer.format.height = frame->compose.height; + mdp_prepare_buffer(&out->buffer, frame, vb); + mdp_set_src_crop(&out->crop, &frame->crop); + mdp_set_orientation(out, frame->rotation, frame->hflip, frame->vflip); +} + +int mdp_frameparam_init(struct mdp_frameparam *param) +{ + struct mdp_frame *frame; + + if (!param) + return -EINVAL; + + INIT_LIST_HEAD(¶m->list); + param->limit = &mdp_def_limit; + param->type = MDP_STREAM_TYPE_BITBLT; + + frame = ¶m->output; + frame->format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + frame->mdp_fmt = mdp_try_fmt_mplane(&frame->format, param, 0); + frame->ycbcr_prof = + mdp_map_ycbcr_prof_mplane(&frame->format, + frame->mdp_fmt->mdp_color); + frame->usage = MDP_BUFFER_USAGE_HW_READ; + + param->num_captures = 1; + frame = ¶m->captures[0]; + frame->format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + frame->mdp_fmt = mdp_try_fmt_mplane(&frame->format, param, 0); + frame->ycbcr_prof = + mdp_map_ycbcr_prof_mplane(&frame->format, + frame->mdp_fmt->mdp_color); + frame->usage = MDP_BUFFER_USAGE_MDP; + frame->crop.c.width = param->output.format.fmt.pix_mp.width; + frame->crop.c.height = param->output.format.fmt.pix_mp.height; + frame->compose.width = frame->format.fmt.pix_mp.width; + frame->compose.height = frame->format.fmt.pix_mp.height; + + return 0; +} diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-regs.h b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-regs.h new file mode 100644 index 000000000..f995e536d --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-regs.h @@ -0,0 +1,373 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MTK_MDP3_REGS_H__ +#define __MTK_MDP3_REGS_H__ + +#include +#include +#include "mtk-img-ipi.h" + +/* + * MDP native color code + * Plane count: 1, 2, 3 + * H-subsample: 0, 1, 2 + * V-subsample: 0, 1 + * Color group: 0-RGB, 1-YUV, 2-raw + */ +#define MDP_COLOR(PACKED, LOOSE, VIDEO, PLANE, HF, VF, BITS, GROUP, SWAP, ID)\ + (((PACKED) << 27) | ((LOOSE) << 26) | ((VIDEO) << 23) |\ + ((PLANE) << 21) | ((HF) << 19) | ((VF) << 18) | ((BITS) << 8) |\ + ((GROUP) << 6) | ((SWAP) << 5) | ((ID) << 0)) + +#define MDP_COLOR_IS_10BIT_PACKED(c) ((0x08000000 & (c)) >> 27) +#define MDP_COLOR_IS_10BIT_LOOSE(c) (((0x0c000000 & (c)) >> 26) == 1) +#define MDP_COLOR_IS_10BIT_TILE(c) (((0x0c000000 & (c)) >> 26) == 3) +#define MDP_COLOR_IS_UFP(c) ((0x02000000 & (c)) >> 25) +#define MDP_COLOR_IS_INTERLACED(c) ((0x01000000 & (c)) >> 24) +#define MDP_COLOR_IS_BLOCK_MODE(c) ((0x00800000 & (c)) >> 23) +#define MDP_COLOR_GET_PLANE_COUNT(c) ((0x00600000 & (c)) >> 21) +#define MDP_COLOR_GET_H_SUBSAMPLE(c) ((0x00180000 & (c)) >> 19) +#define MDP_COLOR_GET_V_SUBSAMPLE(c) ((0x00040000 & (c)) >> 18) +#define MDP_COLOR_BITS_PER_PIXEL(c) ((0x0003ff00 & (c)) >> 8) +#define MDP_COLOR_GET_GROUP(c) ((0x000000c0 & (c)) >> 6) +#define MDP_COLOR_IS_SWAPPED(c) ((0x00000020 & (c)) >> 5) +#define MDP_COLOR_GET_UNIQUE_ID(c) ((0x0000001f & (c)) >> 0) +#define MDP_COLOR_GET_HW_FORMAT(c) ((0x0000001f & (c)) >> 0) + +#define MDP_COLOR_IS_RGB(c) (MDP_COLOR_GET_GROUP(c) == 0) +#define MDP_COLOR_IS_YUV(c) (MDP_COLOR_GET_GROUP(c) == 1) + +enum mdp_color { + MDP_COLOR_UNKNOWN = 0, + + //MDP_COLOR_FULLG8, + MDP_COLOR_FULLG8_RGGB = MDP_COLOR(0, 0, 0, 1, 0, 0, 8, 2, 0, 21), + MDP_COLOR_FULLG8_GRBG = MDP_COLOR(0, 0, 0, 1, 0, 1, 8, 2, 0, 21), + MDP_COLOR_FULLG8_GBRG = MDP_COLOR(0, 0, 0, 1, 1, 0, 8, 2, 0, 21), + MDP_COLOR_FULLG8_BGGR = MDP_COLOR(0, 0, 0, 1, 1, 1, 8, 2, 0, 21), + MDP_COLOR_FULLG8 = MDP_COLOR_FULLG8_BGGR, + + //MDP_COLOR_FULLG10, + MDP_COLOR_FULLG10_RGGB = MDP_COLOR(0, 0, 0, 1, 0, 0, 10, 2, 0, 21), + MDP_COLOR_FULLG10_GRBG = MDP_COLOR(0, 0, 0, 1, 0, 1, 10, 2, 0, 21), + MDP_COLOR_FULLG10_GBRG = MDP_COLOR(0, 0, 0, 1, 1, 0, 10, 2, 0, 21), + MDP_COLOR_FULLG10_BGGR = MDP_COLOR(0, 0, 0, 1, 1, 1, 10, 2, 0, 21), + MDP_COLOR_FULLG10 = MDP_COLOR_FULLG10_BGGR, + + //MDP_COLOR_FULLG12, + MDP_COLOR_FULLG12_RGGB = MDP_COLOR(0, 0, 0, 1, 0, 0, 12, 2, 0, 21), + MDP_COLOR_FULLG12_GRBG = MDP_COLOR(0, 0, 0, 1, 0, 1, 12, 2, 0, 21), + MDP_COLOR_FULLG12_GBRG = MDP_COLOR(0, 0, 0, 1, 1, 0, 12, 2, 0, 21), + MDP_COLOR_FULLG12_BGGR = MDP_COLOR(0, 0, 0, 1, 1, 1, 12, 2, 0, 21), + MDP_COLOR_FULLG12 = MDP_COLOR_FULLG12_BGGR, + + //MDP_COLOR_FULLG14, + MDP_COLOR_FULLG14_RGGB = MDP_COLOR(0, 0, 0, 1, 0, 0, 14, 2, 0, 21), + MDP_COLOR_FULLG14_GRBG = MDP_COLOR(0, 0, 0, 1, 0, 1, 14, 2, 0, 21), + MDP_COLOR_FULLG14_GBRG = MDP_COLOR(0, 0, 0, 1, 1, 0, 14, 2, 0, 21), + MDP_COLOR_FULLG14_BGGR = MDP_COLOR(0, 0, 0, 1, 1, 1, 14, 2, 0, 21), + MDP_COLOR_FULLG14 = MDP_COLOR_FULLG14_BGGR, + + MDP_COLOR_UFO10 = MDP_COLOR(0, 0, 0, 1, 0, 0, 10, 2, 0, 24), + + //MDP_COLOR_BAYER8, + MDP_COLOR_BAYER8_RGGB = MDP_COLOR(0, 0, 0, 1, 0, 0, 8, 2, 0, 20), + MDP_COLOR_BAYER8_GRBG = MDP_COLOR(0, 0, 0, 1, 0, 1, 8, 2, 0, 20), + MDP_COLOR_BAYER8_GBRG = MDP_COLOR(0, 0, 0, 1, 1, 0, 8, 2, 0, 20), + MDP_COLOR_BAYER8_BGGR = MDP_COLOR(0, 0, 0, 1, 1, 1, 8, 2, 0, 20), + MDP_COLOR_BAYER8 = MDP_COLOR_BAYER8_BGGR, + + //MDP_COLOR_BAYER10, + MDP_COLOR_BAYER10_RGGB = MDP_COLOR(0, 0, 0, 1, 0, 0, 10, 2, 0, 20), + MDP_COLOR_BAYER10_GRBG = MDP_COLOR(0, 0, 0, 1, 0, 1, 10, 2, 0, 20), + MDP_COLOR_BAYER10_GBRG = MDP_COLOR(0, 0, 0, 1, 1, 0, 10, 2, 0, 20), + MDP_COLOR_BAYER10_BGGR = MDP_COLOR(0, 0, 0, 1, 1, 1, 10, 2, 0, 20), + MDP_COLOR_BAYER10 = MDP_COLOR_BAYER10_BGGR, + + //MDP_COLOR_BAYER12, + MDP_COLOR_BAYER12_RGGB = MDP_COLOR(0, 0, 0, 1, 0, 0, 12, 2, 0, 20), + MDP_COLOR_BAYER12_GRBG = MDP_COLOR(0, 0, 0, 1, 0, 1, 12, 2, 0, 20), + MDP_COLOR_BAYER12_GBRG = MDP_COLOR(0, 0, 0, 1, 1, 0, 12, 2, 0, 20), + MDP_COLOR_BAYER12_BGGR = MDP_COLOR(0, 0, 0, 1, 1, 1, 12, 2, 0, 20), + MDP_COLOR_BAYER12 = MDP_COLOR_BAYER12_BGGR, + + //MDP_COLOR_BAYER14, + MDP_COLOR_BAYER14_RGGB = MDP_COLOR(0, 0, 0, 1, 0, 0, 14, 2, 0, 20), + MDP_COLOR_BAYER14_GRBG = MDP_COLOR(0, 0, 0, 1, 0, 1, 14, 2, 0, 20), + MDP_COLOR_BAYER14_GBRG = MDP_COLOR(0, 0, 0, 1, 1, 0, 14, 2, 0, 20), + MDP_COLOR_BAYER14_BGGR = MDP_COLOR(0, 0, 0, 1, 1, 1, 14, 2, 0, 20), + MDP_COLOR_BAYER14 = MDP_COLOR_BAYER14_BGGR, + + MDP_COLOR_RGB48 = MDP_COLOR(0, 0, 0, 1, 0, 0, 48, 0, 0, 23), + /* For bayer+mono raw-16 */ + MDP_COLOR_RGB565_RAW = MDP_COLOR(0, 0, 0, 1, 0, 0, 16, 2, 0, 0), + + MDP_COLOR_BAYER8_UNPAK = MDP_COLOR(0, 0, 0, 1, 0, 0, 8, 2, 0, 22), + MDP_COLOR_BAYER10_UNPAK = MDP_COLOR(0, 0, 0, 1, 0, 0, 10, 2, 0, 22), + MDP_COLOR_BAYER12_UNPAK = MDP_COLOR(0, 0, 0, 1, 0, 0, 12, 2, 0, 22), + MDP_COLOR_BAYER14_UNPAK = MDP_COLOR(0, 0, 0, 1, 0, 0, 14, 2, 0, 22), + + /* Unified formats */ + MDP_COLOR_GREY = MDP_COLOR(0, 0, 0, 1, 0, 0, 8, 1, 0, 7), + + MDP_COLOR_RGB565 = MDP_COLOR(0, 0, 0, 1, 0, 0, 16, 0, 0, 0), + MDP_COLOR_BGR565 = MDP_COLOR(0, 0, 0, 1, 0, 0, 16, 0, 1, 0), + MDP_COLOR_RGB888 = MDP_COLOR(0, 0, 0, 1, 0, 0, 24, 0, 1, 1), + MDP_COLOR_BGR888 = MDP_COLOR(0, 0, 0, 1, 0, 0, 24, 0, 0, 1), + MDP_COLOR_RGBA8888 = MDP_COLOR(0, 0, 0, 1, 0, 0, 32, 0, 1, 2), + MDP_COLOR_BGRA8888 = MDP_COLOR(0, 0, 0, 1, 0, 0, 32, 0, 0, 2), + MDP_COLOR_ARGB8888 = MDP_COLOR(0, 0, 0, 1, 0, 0, 32, 0, 1, 3), + MDP_COLOR_ABGR8888 = MDP_COLOR(0, 0, 0, 1, 0, 0, 32, 0, 0, 3), + + MDP_COLOR_UYVY = MDP_COLOR(0, 0, 0, 1, 1, 0, 16, 1, 0, 4), + MDP_COLOR_VYUY = MDP_COLOR(0, 0, 0, 1, 1, 0, 16, 1, 1, 4), + MDP_COLOR_YUYV = MDP_COLOR(0, 0, 0, 1, 1, 0, 16, 1, 0, 5), + MDP_COLOR_YVYU = MDP_COLOR(0, 0, 0, 1, 1, 0, 16, 1, 1, 5), + + MDP_COLOR_I420 = MDP_COLOR(0, 0, 0, 3, 1, 1, 8, 1, 0, 8), + MDP_COLOR_YV12 = MDP_COLOR(0, 0, 0, 3, 1, 1, 8, 1, 1, 8), + MDP_COLOR_I422 = MDP_COLOR(0, 0, 0, 3, 1, 0, 8, 1, 0, 9), + MDP_COLOR_YV16 = MDP_COLOR(0, 0, 0, 3, 1, 0, 8, 1, 1, 9), + MDP_COLOR_I444 = MDP_COLOR(0, 0, 0, 3, 0, 0, 8, 1, 0, 10), + MDP_COLOR_YV24 = MDP_COLOR(0, 0, 0, 3, 0, 0, 8, 1, 1, 10), + + MDP_COLOR_NV12 = MDP_COLOR(0, 0, 0, 2, 1, 1, 8, 1, 0, 12), + MDP_COLOR_NV21 = MDP_COLOR(0, 0, 0, 2, 1, 1, 8, 1, 1, 12), + MDP_COLOR_NV16 = MDP_COLOR(0, 0, 0, 2, 1, 0, 8, 1, 0, 13), + MDP_COLOR_NV61 = MDP_COLOR(0, 0, 0, 2, 1, 0, 8, 1, 1, 13), + MDP_COLOR_NV24 = MDP_COLOR(0, 0, 0, 2, 0, 0, 8, 1, 0, 14), + MDP_COLOR_NV42 = MDP_COLOR(0, 0, 0, 2, 0, 0, 8, 1, 1, 14), + + /* MediaTek proprietary formats */ + /* UFO encoded block mode */ + MDP_COLOR_420_BLK_UFO = MDP_COLOR(0, 0, 5, 2, 1, 1, 256, 1, 0, 12), + /* Block mode */ + MDP_COLOR_420_BLK = MDP_COLOR(0, 0, 1, 2, 1, 1, 256, 1, 0, 12), + /* Block mode + field mode */ + MDP_COLOR_420_BLKI = MDP_COLOR(0, 0, 3, 2, 1, 1, 256, 1, 0, 12), + /* Block mode */ + MDP_COLOR_422_BLK = MDP_COLOR(0, 0, 1, 1, 1, 0, 512, 1, 0, 4), + + MDP_COLOR_IYU2 = MDP_COLOR(0, 0, 0, 1, 0, 0, 24, 1, 0, 25), + MDP_COLOR_YUV444 = MDP_COLOR(0, 0, 0, 1, 0, 0, 24, 1, 0, 30), + + /* Packed 10-bit formats */ + MDP_COLOR_RGBA1010102 = MDP_COLOR(1, 0, 0, 1, 0, 0, 32, 0, 1, 2), + MDP_COLOR_BGRA1010102 = MDP_COLOR(1, 0, 0, 1, 0, 0, 32, 0, 0, 2), + /* Packed 10-bit UYVY */ + MDP_COLOR_UYVY_10P = MDP_COLOR(1, 0, 0, 1, 1, 0, 20, 1, 0, 4), + /* Packed 10-bit NV21 */ + MDP_COLOR_NV21_10P = MDP_COLOR(1, 0, 0, 2, 1, 1, 10, 1, 1, 12), + /* 10-bit block mode */ + MDP_COLOR_420_BLK_10_H = MDP_COLOR(1, 0, 1, 2, 1, 1, 320, 1, 0, 12), + /* 10-bit HEVC tile mode */ + MDP_COLOR_420_BLK_10_V = MDP_COLOR(1, 1, 1, 2, 1, 1, 320, 1, 0, 12), + /* UFO encoded 10-bit block mode */ + MDP_COLOR_420_BLK_U10_H = MDP_COLOR(1, 0, 5, 2, 1, 1, 320, 1, 0, 12), + /* UFO encoded 10-bit HEVC tile mode */ + MDP_COLOR_420_BLK_U10_V = MDP_COLOR(1, 1, 5, 2, 1, 1, 320, 1, 0, 12), + + /* Loose 10-bit formats */ + MDP_COLOR_UYVY_10L = MDP_COLOR(0, 1, 0, 1, 1, 0, 20, 1, 0, 4), + MDP_COLOR_VYUY_10L = MDP_COLOR(0, 1, 0, 1, 1, 0, 20, 1, 1, 4), + MDP_COLOR_YUYV_10L = MDP_COLOR(0, 1, 0, 1, 1, 0, 20, 1, 0, 5), + MDP_COLOR_YVYU_10L = MDP_COLOR(0, 1, 0, 1, 1, 0, 20, 1, 1, 5), + MDP_COLOR_NV12_10L = MDP_COLOR(0, 1, 0, 2, 1, 1, 10, 1, 0, 12), + MDP_COLOR_NV21_10L = MDP_COLOR(0, 1, 0, 2, 1, 1, 10, 1, 1, 12), + MDP_COLOR_NV16_10L = MDP_COLOR(0, 1, 0, 2, 1, 0, 10, 1, 0, 13), + MDP_COLOR_NV61_10L = MDP_COLOR(0, 1, 0, 2, 1, 0, 10, 1, 1, 13), + MDP_COLOR_YV12_10L = MDP_COLOR(0, 1, 0, 3, 1, 1, 10, 1, 1, 8), + MDP_COLOR_I420_10L = MDP_COLOR(0, 1, 0, 3, 1, 1, 10, 1, 0, 8), +}; + +static inline bool MDP_COLOR_IS_UV_COPLANE(enum mdp_color c) +{ + return (MDP_COLOR_GET_PLANE_COUNT(c) == 2 && MDP_COLOR_IS_YUV(c)); +} + +/* Minimum Y stride that is accepted by MDP HW */ +static inline u32 mdp_color_get_min_y_stride(enum mdp_color c, u32 width) +{ + return ((MDP_COLOR_BITS_PER_PIXEL(c) * width) + 4) >> 3; +} + +/* Minimum UV stride that is accepted by MDP HW */ +static inline u32 mdp_color_get_min_uv_stride(enum mdp_color c, u32 width) +{ + u32 min_stride; + + if (MDP_COLOR_GET_PLANE_COUNT(c) == 1) + return 0; + min_stride = mdp_color_get_min_y_stride(c, width) + >> MDP_COLOR_GET_H_SUBSAMPLE(c); + if (MDP_COLOR_IS_UV_COPLANE(c) && !MDP_COLOR_IS_BLOCK_MODE(c)) + min_stride = min_stride * 2; + return min_stride; +} + +/* Minimum Y plane size that is necessary in buffer */ +static inline u32 mdp_color_get_min_y_size(enum mdp_color c, + u32 width, u32 height) +{ + if (MDP_COLOR_IS_BLOCK_MODE(c)) + return ((MDP_COLOR_BITS_PER_PIXEL(c) * width) >> 8) * height; + return mdp_color_get_min_y_stride(c, width) * height; +} + +/* Minimum UV plane size that is necessary in buffer */ +static inline u32 mdp_color_get_min_uv_size(enum mdp_color c, + u32 width, u32 height) +{ + height = height >> MDP_COLOR_GET_V_SUBSAMPLE(c); + if (MDP_COLOR_IS_BLOCK_MODE(c) && (MDP_COLOR_GET_PLANE_COUNT(c) > 1)) + return ((MDP_COLOR_BITS_PER_PIXEL(c) * width) >> 8) * height; + return mdp_color_get_min_uv_stride(c, width) * height; +} + +/* Combine colorspace, xfer_func, ycbcr_encoding, and quantization */ +enum mdp_ycbcr_profile { + /* V4L2_YCBCR_ENC_601 and V4L2_QUANTIZATION_LIM_RANGE */ + MDP_YCBCR_PROFILE_BT601, + /* V4L2_YCBCR_ENC_709 and V4L2_QUANTIZATION_LIM_RANGE */ + MDP_YCBCR_PROFILE_BT709, + /* V4L2_YCBCR_ENC_601 and V4L2_QUANTIZATION_FULL_RANGE */ + MDP_YCBCR_PROFILE_JPEG, + MDP_YCBCR_PROFILE_FULL_BT601 = MDP_YCBCR_PROFILE_JPEG, + + /* Colorspaces not support for capture */ + /* V4L2_YCBCR_ENC_BT2020 and V4L2_QUANTIZATION_LIM_RANGE */ + MDP_YCBCR_PROFILE_BT2020, + /* V4L2_YCBCR_ENC_709 and V4L2_QUANTIZATION_FULL_RANGE */ + MDP_YCBCR_PROFILE_FULL_BT709, + /* V4L2_YCBCR_ENC_BT2020 and V4L2_QUANTIZATION_FULL_RANGE */ + MDP_YCBCR_PROFILE_FULL_BT2020, +}; + +#define MDP_FMT_FLAG_OUTPUT BIT(0) +#define MDP_FMT_FLAG_CAPTURE BIT(1) + +struct mdp_format { + u32 pixelformat; + u32 mdp_color; + u8 depth[VIDEO_MAX_PLANES]; + u8 row_depth[VIDEO_MAX_PLANES]; + u8 num_planes; + u8 walign; + u8 halign; + u8 salign; + u32 flags; +}; + +struct mdp_pix_limit { + u32 wmin; + u32 hmin; + u32 wmax; + u32 hmax; +}; + +struct mdp_limit { + struct mdp_pix_limit out_limit; + struct mdp_pix_limit cap_limit; + u32 h_scale_up_max; + u32 v_scale_up_max; + u32 h_scale_down_max; + u32 v_scale_down_max; +}; + +enum mdp_stream_type { + MDP_STREAM_TYPE_UNKNOWN, + MDP_STREAM_TYPE_BITBLT, + MDP_STREAM_TYPE_GPU_BITBLT, + MDP_STREAM_TYPE_DUAL_BITBLT, + MDP_STREAM_TYPE_2ND_BITBLT, + MDP_STREAM_TYPE_ISP_IC, + MDP_STREAM_TYPE_ISP_VR, + MDP_STREAM_TYPE_ISP_ZSD, + MDP_STREAM_TYPE_ISP_IP, + MDP_STREAM_TYPE_ISP_VSS, + MDP_STREAM_TYPE_ISP_ZSD_SLOW, + MDP_STREAM_TYPE_WPE, + MDP_STREAM_TYPE_WPE2, +}; + +struct mdp_crop { + struct v4l2_rect c; + struct v4l2_fract left_subpix; + struct v4l2_fract top_subpix; + struct v4l2_fract width_subpix; + struct v4l2_fract height_subpix; +}; + +struct mdp_frame { + struct v4l2_format format; + const struct mdp_format *mdp_fmt; + u32 ycbcr_prof; /* enum mdp_ycbcr_profile */ + u32 usage; /* enum mdp_buffer_usage */ + struct mdp_crop crop; + struct v4l2_rect compose; + s32 rotation; + u32 hflip:1; + u32 vflip:1; + u32 hdr:1; + u32 dre:1; + u32 sharpness:1; + u32 dither:1; +}; + +static inline bool mdp_target_is_crop(u32 target) +{ + return (target == V4L2_SEL_TGT_CROP) || + (target == V4L2_SEL_TGT_CROP_DEFAULT) || + (target == V4L2_SEL_TGT_CROP_BOUNDS); +} + +static inline bool mdp_target_is_compose(u32 target) +{ + return (target == V4L2_SEL_TGT_COMPOSE) || + (target == V4L2_SEL_TGT_COMPOSE_DEFAULT) || + (target == V4L2_SEL_TGT_COMPOSE_BOUNDS); +} + +#define MDP_MAX_CAPTURES IMG_MAX_HW_OUTPUTS + +#define MDP_VPU_INIT BIT(0) +#define MDP_M2M_CTX_ERROR BIT(1) + +struct mdp_frameparam { + struct list_head list; + struct mdp_m2m_ctx *ctx; + atomic_t state; + const struct mdp_limit *limit; + u32 type; /* enum mdp_stream_type */ + u32 frame_no; + struct mdp_frame output; + struct mdp_frame captures[MDP_MAX_CAPTURES]; + u32 num_captures; + enum v4l2_colorspace colorspace; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_xfer_func xfer_func; + enum v4l2_quantization quant; +}; + +int mdp_enum_fmt_mplane(struct v4l2_fmtdesc *f); +const struct mdp_format *mdp_try_fmt_mplane(struct v4l2_format *f, + struct mdp_frameparam *param, + u32 ctx_id); +enum mdp_ycbcr_profile mdp_map_ycbcr_prof_mplane(struct v4l2_format *f, + u32 mdp_color); +int mdp_try_crop(struct mdp_m2m_ctx *ctx, struct v4l2_rect *r, + const struct v4l2_selection *s, struct mdp_frame *frame); +int mdp_check_scaling_ratio(const struct v4l2_rect *crop, + const struct v4l2_rect *compose, s32 rotation, + const struct mdp_limit *limit); +void mdp_set_src_config(struct img_input *in, + struct mdp_frame *frame, struct vb2_buffer *vb); +void mdp_set_dst_config(struct img_output *out, + struct mdp_frame *frame, struct vb2_buffer *vb); +int mdp_frameparam_init(struct mdp_frameparam *param); + +#endif /* __MTK_MDP3_REGS_H__ */ diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-vpu.c b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-vpu.c new file mode 100644 index 000000000..a72bed927 --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-vpu.c @@ -0,0 +1,314 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#include +#include +#include "mtk-mdp3-vpu.h" +#include "mtk-mdp3-core.h" + +#define MDP_VPU_MESSAGE_TIMEOUT 500U +#define vpu_alloc_size 0x600000 + +static inline struct mdp_dev *vpu_to_mdp(struct mdp_vpu_dev *vpu) +{ + return container_of(vpu, struct mdp_dev, vpu); +} + +static int mdp_vpu_shared_mem_alloc(struct mdp_vpu_dev *vpu) +{ + if (vpu->work && vpu->work_addr) + return 0; + + vpu->work = dma_alloc_coherent(scp_get_device(vpu->scp), vpu_alloc_size, + &vpu->work_addr, GFP_KERNEL); + + if (!vpu->work) + return -ENOMEM; + else + return 0; +} + +void mdp_vpu_shared_mem_free(struct mdp_vpu_dev *vpu) +{ + if (vpu->work && vpu->work_addr) + dma_free_coherent(scp_get_device(vpu->scp), vpu_alloc_size, + vpu->work, vpu->work_addr); +} + +static void mdp_vpu_ipi_handle_init_ack(void *data, unsigned int len, + void *priv) +{ + struct mdp_ipi_init_msg *msg = (struct mdp_ipi_init_msg *)data; + struct mdp_vpu_dev *vpu = + (struct mdp_vpu_dev *)(unsigned long)msg->drv_data; + + if (!vpu->work_size) + vpu->work_size = msg->work_size; + + vpu->status = msg->status; + complete(&vpu->ipi_acked); +} + +static void mdp_vpu_ipi_handle_deinit_ack(void *data, unsigned int len, + void *priv) +{ + struct mdp_ipi_deinit_msg *msg = (struct mdp_ipi_deinit_msg *)data; + struct mdp_vpu_dev *vpu = + (struct mdp_vpu_dev *)(unsigned long)msg->drv_data; + + vpu->status = msg->status; + complete(&vpu->ipi_acked); +} + +static void mdp_vpu_ipi_handle_frame_ack(void *data, unsigned int len, + void *priv) +{ + struct img_sw_addr *addr = (struct img_sw_addr *)data; + struct img_ipi_frameparam *param = + (struct img_ipi_frameparam *)(unsigned long)addr->va; + struct mdp_vpu_ctx *ctx = + (struct mdp_vpu_ctx *)(unsigned long)param->drv_data; + + if (param->state) { + struct mdp_dev *mdp = vpu_to_mdp(ctx->vpu_dev); + + dev_err(&mdp->pdev->dev, "VPU MDP failure:%d\n", param->state); + } + ctx->vpu_dev->status = param->state; + complete(&ctx->vpu_dev->ipi_acked); +} + +int mdp_vpu_register(struct mdp_dev *mdp) +{ + int err; + struct mtk_scp *scp = mdp->scp; + struct device *dev = &mdp->pdev->dev; + + err = scp_ipi_register(scp, SCP_IPI_MDP_INIT, + mdp_vpu_ipi_handle_init_ack, NULL); + if (err) { + dev_err(dev, "scp_ipi_register failed %d\n", err); + goto err_ipi_init; + } + err = scp_ipi_register(scp, SCP_IPI_MDP_DEINIT, + mdp_vpu_ipi_handle_deinit_ack, NULL); + if (err) { + dev_err(dev, "scp_ipi_register failed %d\n", err); + goto err_ipi_deinit; + } + err = scp_ipi_register(scp, SCP_IPI_MDP_FRAME, + mdp_vpu_ipi_handle_frame_ack, NULL); + if (err) { + dev_err(dev, "scp_ipi_register failed %d\n", err); + goto err_ipi_frame; + } + return 0; + +err_ipi_frame: + scp_ipi_unregister(scp, SCP_IPI_MDP_DEINIT); +err_ipi_deinit: + scp_ipi_unregister(scp, SCP_IPI_MDP_INIT); +err_ipi_init: + + return err; +} + +void mdp_vpu_unregister(struct mdp_dev *mdp) +{ + scp_ipi_unregister(mdp->scp, SCP_IPI_MDP_INIT); + scp_ipi_unregister(mdp->scp, SCP_IPI_MDP_DEINIT); + scp_ipi_unregister(mdp->scp, SCP_IPI_MDP_FRAME); +} + +static int mdp_vpu_sendmsg(struct mdp_vpu_dev *vpu, enum scp_ipi_id id, + void *buf, unsigned int len) +{ + struct mdp_dev *mdp = vpu_to_mdp(vpu); + unsigned int t = MDP_VPU_MESSAGE_TIMEOUT; + int ret; + + if (!vpu->scp) { + dev_dbg(&mdp->pdev->dev, "vpu scp is NULL"); + return -EINVAL; + } + ret = scp_ipi_send(vpu->scp, id, buf, len, 2000); + + if (ret) { + dev_err(&mdp->pdev->dev, "scp_ipi_send failed %d\n", ret); + return -EPERM; + } + ret = wait_for_completion_timeout(&vpu->ipi_acked, + msecs_to_jiffies(t)); + if (!ret) + ret = -ETIME; + else if (vpu->status) + ret = -EINVAL; + else + ret = 0; + return ret; +} + +int mdp_vpu_dev_init(struct mdp_vpu_dev *vpu, struct mtk_scp *scp, + struct mutex *lock) +{ + struct mdp_ipi_init_msg msg = { + .drv_data = (unsigned long)vpu, + }; + size_t mem_size; + phys_addr_t pool; + const size_t pool_size = sizeof(struct mdp_config_pool); + struct mdp_dev *mdp = vpu_to_mdp(vpu); + int err; + + init_completion(&vpu->ipi_acked); + vpu->scp = scp; + vpu->lock = lock; + vpu->work_size = 0; + err = mdp_vpu_sendmsg(vpu, SCP_IPI_MDP_INIT, &msg, sizeof(msg)); + if (err) + goto err_work_size; + /* vpu work_size was set in mdp_vpu_ipi_handle_init_ack */ + + mem_size = vpu_alloc_size; + err = mdp_vpu_shared_mem_alloc(vpu); + if (err) { + dev_err(&mdp->pdev->dev, "VPU memory alloc fail!"); + goto err_mem_alloc; + } + + pool = ALIGN((uintptr_t)vpu->work + vpu->work_size, 8); + if (pool + pool_size - (uintptr_t)vpu->work > mem_size) { + dev_err(&mdp->pdev->dev, + "VPU memory insufficient: %zx + %zx > %zx", + vpu->work_size, pool_size, mem_size); + err = -ENOMEM; + goto err_mem_size; + } + + dev_dbg(&mdp->pdev->dev, + "VPU work:%pK pa:%pad sz:%zx pool:%pa sz:%zx (mem sz:%zx)", + vpu->work, &vpu->work_addr, vpu->work_size, + &pool, pool_size, mem_size); + vpu->pool = (struct mdp_config_pool *)(uintptr_t)pool; + msg.work_addr = vpu->work_addr; + msg.work_size = vpu->work_size; + err = mdp_vpu_sendmsg(vpu, SCP_IPI_MDP_INIT, &msg, sizeof(msg)); + if (err) + goto err_work_size; + + memset(vpu->pool, 0, sizeof(*vpu->pool)); + return 0; + +err_work_size: + switch (vpu->status) { + case -MDP_IPI_EBUSY: + err = -EBUSY; + break; + case -MDP_IPI_ENOMEM: + err = -ENOSPC; /* -ENOMEM */ + break; + } + return err; +err_mem_size: +err_mem_alloc: + return err; +} + +int mdp_vpu_dev_deinit(struct mdp_vpu_dev *vpu) +{ + struct mdp_ipi_deinit_msg msg = { + .drv_data = (unsigned long)vpu, + .work_addr = vpu->work_addr, + }; + + return mdp_vpu_sendmsg(vpu, SCP_IPI_MDP_DEINIT, &msg, sizeof(msg)); +} + +static struct img_config *mdp_config_get(struct mdp_vpu_dev *vpu, + enum mdp_config_id id, uint32_t *addr) +{ + struct img_config *config; + + if (id < 0 || id >= MDP_CONFIG_POOL_SIZE) + return ERR_PTR(-EINVAL); + + mutex_lock(vpu->lock); + vpu->pool->cfg_count[id]++; + config = &vpu->pool->configs[id]; + *addr = vpu->work_addr + ((uintptr_t)config - (uintptr_t)vpu->work); + mutex_unlock(vpu->lock); + + return config; +} + +static int mdp_config_put(struct mdp_vpu_dev *vpu, + enum mdp_config_id id, + const struct img_config *config) +{ + int err = 0; + + if (id < 0 || id >= MDP_CONFIG_POOL_SIZE) + return -EINVAL; + if (vpu->lock) + mutex_lock(vpu->lock); + if (!vpu->pool->cfg_count[id] || config != &vpu->pool->configs[id]) + err = -EINVAL; + else + vpu->pool->cfg_count[id]--; + if (vpu->lock) + mutex_unlock(vpu->lock); + return err; +} + +int mdp_vpu_ctx_init(struct mdp_vpu_ctx *ctx, struct mdp_vpu_dev *vpu, + enum mdp_config_id id) +{ + ctx->config = mdp_config_get(vpu, id, &ctx->inst_addr); + if (IS_ERR(ctx->config)) { + int err = PTR_ERR(ctx->config); + + ctx->config = NULL; + return err; + } + ctx->config_id = id; + ctx->vpu_dev = vpu; + return 0; +} + +int mdp_vpu_ctx_deinit(struct mdp_vpu_ctx *ctx) +{ + int err = mdp_config_put(ctx->vpu_dev, ctx->config_id, ctx->config); + + ctx->config_id = 0; + ctx->config = NULL; + ctx->inst_addr = 0; + return err; +} + +int mdp_vpu_process(struct mdp_vpu_ctx *ctx, struct img_ipi_frameparam *param) +{ + struct mdp_vpu_dev *vpu = ctx->vpu_dev; + struct mdp_dev *mdp = vpu_to_mdp(vpu); + struct img_sw_addr addr; + + if (!ctx->vpu_dev->work || !ctx->vpu_dev->work_addr) { + if (mdp_vpu_shared_mem_alloc(vpu)) { + dev_err(&mdp->pdev->dev, "VPU memory alloc fail!"); + return -ENOMEM; + } + } + memset((void *)ctx->vpu_dev->work, 0, ctx->vpu_dev->work_size); + memset(ctx->config, 0, sizeof(*ctx->config)); + param->config_data.va = (unsigned long)ctx->config; + param->config_data.pa = ctx->inst_addr; + param->drv_data = (unsigned long)ctx; + + memcpy((void *)ctx->vpu_dev->work, param, sizeof(*param)); + addr.pa = ctx->vpu_dev->work_addr; + addr.va = (uintptr_t)ctx->vpu_dev->work; + return mdp_vpu_sendmsg(ctx->vpu_dev, SCP_IPI_MDP_FRAME, + &addr, sizeof(addr)); +} diff --git a/drivers/media/platform/mediatek/mdp3/mtk-mdp3-vpu.h b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-vpu.h new file mode 100644 index 000000000..244b3a32d --- /dev/null +++ b/drivers/media/platform/mediatek/mdp3/mtk-mdp3-vpu.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Ping-Hsun Wu + */ + +#ifndef __MTK_MDP3_VPU_H__ +#define __MTK_MDP3_VPU_H__ + +#include +#include "mtk-img-ipi.h" + +enum mdp_ipi_result { + MDP_IPI_SUCCESS = 0, + MDP_IPI_ENOMEM = 12, + MDP_IPI_EBUSY = 16, + MDP_IPI_EINVAL = 22, + MDP_IPI_EMINST = 24, + MDP_IPI_ERANGE = 34, + MDP_IPI_NR_ERRNO, + + MDP_IPI_EOTHER = MDP_IPI_NR_ERRNO, + MDP_IPI_PATH_CANT_MERGE, + MDP_IPI_OP_FAIL, +}; + +struct mdp_ipi_init_msg { + u32 status; + u64 drv_data; + u32 work_addr; /* [in] working buffer address */ + u32 work_size; /* [in] working buffer size */ +} __packed; + +struct mdp_ipi_deinit_msg { + u32 status; + u64 drv_data; + u32 work_addr; +} __packed; + +enum mdp_config_id { + MDP_DEV_M2M = 0, + MDP_CONFIG_POOL_SIZE /* ALWAYS keep at the end */ +}; + +struct mdp_config_pool { + u64 cfg_count[MDP_CONFIG_POOL_SIZE]; + struct img_config configs[MDP_CONFIG_POOL_SIZE]; +}; + +struct mdp_vpu_dev { + /* synchronization protect for accessing vpu working buffer info */ + struct mutex *lock; + struct mtk_scp *scp; + struct completion ipi_acked; + void *work; + dma_addr_t work_addr; + size_t work_size; + struct mdp_config_pool *pool; + u32 status; +}; + +struct mdp_vpu_ctx { + struct mdp_vpu_dev *vpu_dev; + u32 config_id; + struct img_config *config; + u32 inst_addr; +}; + +void mdp_vpu_shared_mem_free(struct mdp_vpu_dev *vpu); +int mdp_vpu_dev_init(struct mdp_vpu_dev *vpu, struct mtk_scp *scp, + struct mutex *lock /* for sync */); +int mdp_vpu_dev_deinit(struct mdp_vpu_dev *vpu); +int mdp_vpu_ctx_init(struct mdp_vpu_ctx *ctx, struct mdp_vpu_dev *vpu, + enum mdp_config_id id); +int mdp_vpu_ctx_deinit(struct mdp_vpu_ctx *ctx); +int mdp_vpu_process(struct mdp_vpu_ctx *vpu, struct img_ipi_frameparam *param); + +#endif /* __MTK_MDP3_VPU_H__ */ diff --git a/drivers/media/platform/mediatek/vcodec/Kconfig b/drivers/media/platform/mediatek/vcodec/Kconfig new file mode 100644 index 000000000..74b00eb1b --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/Kconfig @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_MEDIATEK_VCODEC_SCP + bool + +config VIDEO_MEDIATEK_VCODEC_VPU + bool + +config VIDEO_MEDIATEK_VCODEC + tristate "Mediatek Video Codec driver" + depends on V4L_MEM2MEM_DRIVERS + depends on MTK_IOMMU || COMPILE_TEST + depends on VIDEO_DEV + depends on ARCH_MEDIATEK || COMPILE_TEST + depends on VIDEO_MEDIATEK_VPU || MTK_SCP + # The two following lines ensure we have the same state ("m" or "y") as + # our dependencies, to avoid missing symbols during link. + depends on VIDEO_MEDIATEK_VPU || !VIDEO_MEDIATEK_VPU + depends on MTK_SCP || !MTK_SCP + depends on MTK_SMI || (COMPILE_TEST && MTK_SMI=n) + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select VIDEO_MEDIATEK_VCODEC_VPU if VIDEO_MEDIATEK_VPU + select VIDEO_MEDIATEK_VCODEC_SCP if MTK_SCP + select V4L2_H264 + select V4L2_VP9 + select MEDIA_CONTROLLER + select MEDIA_CONTROLLER_REQUEST_API + help + Mediatek video codec driver provides HW capability to + encode and decode in a range of video formats on MT8173 + and MT8183. + + Note that support for MT8173 requires VIDEO_MEDIATEK_VPU to + also be selected. Support for MT8183 depends on MTK_SCP. + + To compile this driver as modules, choose M here: the + modules will be called mtk-vcodec-dec and mtk-vcodec-enc. diff --git a/drivers/media/platform/mediatek/vcodec/Makefile b/drivers/media/platform/mediatek/vcodec/Makefile new file mode 100644 index 000000000..93e7a343b --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-$(CONFIG_VIDEO_MEDIATEK_VCODEC) += mtk-vcodec-dec.o \ + mtk-vcodec-enc.o \ + mtk-vcodec-common.o \ + mtk-vcodec-dec-hw.o + +mtk-vcodec-dec-y := vdec/vdec_h264_if.o \ + vdec/vdec_vp8_if.o \ + vdec/vdec_vp8_req_if.o \ + vdec/vdec_vp9_if.o \ + vdec/vdec_vp9_req_lat_if.o \ + vdec/vdec_h264_req_if.o \ + vdec/vdec_h264_req_common.o \ + vdec/vdec_h264_req_multi_if.o \ + mtk_vcodec_dec_drv.o \ + vdec_drv_if.o \ + vdec_vpu_if.o \ + vdec_msg_queue.o \ + mtk_vcodec_dec.o \ + mtk_vcodec_dec_stateful.o \ + mtk_vcodec_dec_stateless.o \ + mtk_vcodec_dec_pm.o \ + +mtk-vcodec-dec-hw-y := mtk_vcodec_dec_hw.o + +mtk-vcodec-enc-y := venc/venc_vp8_if.o \ + venc/venc_h264_if.o \ + mtk_vcodec_enc.o \ + mtk_vcodec_enc_drv.o \ + mtk_vcodec_enc_pm.o \ + venc_drv_if.o \ + venc_vpu_if.o \ + + +mtk-vcodec-common-y := mtk_vcodec_intr.o \ + mtk_vcodec_util.o \ + mtk_vcodec_fw.o \ + +ifneq ($(CONFIG_VIDEO_MEDIATEK_VCODEC_VPU),) +mtk-vcodec-common-y += mtk_vcodec_fw_vpu.o +endif + +ifneq ($(CONFIG_VIDEO_MEDIATEK_VCODEC_SCP),) +mtk-vcodec-common-y += mtk_vcodec_fw_scp.o +endif diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec.c new file mode 100644 index 000000000..93fcea821 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec.c @@ -0,0 +1,1010 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + * Tiffany Lin + */ + +#include +#include +#include + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_dec.h" +#include "mtk_vcodec_intr.h" +#include "mtk_vcodec_util.h" +#include "vdec_drv_if.h" +#include "mtk_vcodec_dec_pm.h" + +#define DFT_CFG_WIDTH MTK_VDEC_MIN_W +#define DFT_CFG_HEIGHT MTK_VDEC_MIN_H + +static const struct mtk_video_fmt * +mtk_vdec_find_format(struct v4l2_format *f, + const struct mtk_vcodec_dec_pdata *dec_pdata) +{ + const struct mtk_video_fmt *fmt; + unsigned int k; + + for (k = 0; k < *dec_pdata->num_formats; k++) { + fmt = &dec_pdata->vdec_formats[k]; + if (fmt->fourcc == f->fmt.pix_mp.pixelformat) + return fmt; + } + + return NULL; +} + +static bool mtk_vdec_get_cap_fmt(struct mtk_vcodec_ctx *ctx, int format_index) +{ + const struct mtk_vcodec_dec_pdata *dec_pdata = ctx->dev->vdec_pdata; + const struct mtk_video_fmt *fmt; + int num_frame_count = 0, i; + + fmt = &dec_pdata->vdec_formats[format_index]; + for (i = 0; i < *dec_pdata->num_formats; i++) { + if (dec_pdata->vdec_formats[i].type != MTK_FMT_FRAME) + continue; + + num_frame_count++; + } + + if (num_frame_count == 1 || fmt->fourcc == V4L2_PIX_FMT_MM21) + return true; + + return false; +} + +static struct mtk_q_data *mtk_vdec_get_q_data(struct mtk_vcodec_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->q_data[MTK_Q_DATA_SRC]; + + return &ctx->q_data[MTK_Q_DATA_DST]; +} + +static int vidioc_try_decoder_cmd(struct file *file, void *priv, + struct v4l2_decoder_cmd *cmd) +{ + return v4l2_m2m_ioctl_try_decoder_cmd(file, priv, cmd); +} + + +static int vidioc_decoder_cmd(struct file *file, void *priv, + struct v4l2_decoder_cmd *cmd) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct vb2_queue *src_vq, *dst_vq; + int ret; + + ret = vidioc_try_decoder_cmd(file, priv, cmd); + if (ret) + return ret; + + mtk_v4l2_debug(1, "decoder cmd=%u", cmd->cmd); + dst_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + switch (cmd->cmd) { + case V4L2_DEC_CMD_STOP: + src_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (!vb2_is_streaming(src_vq)) { + mtk_v4l2_debug(1, "Output stream is off. No need to flush."); + return 0; + } + if (!vb2_is_streaming(dst_vq)) { + mtk_v4l2_debug(1, "Capture stream is off. No need to flush."); + return 0; + } + v4l2_m2m_buf_queue(ctx->m2m_ctx, &ctx->empty_flush_buf.vb); + v4l2_m2m_try_schedule(ctx->m2m_ctx); + break; + + case V4L2_DEC_CMD_START: + vb2_clear_last_buffer_dequeued(dst_vq); + break; + + default: + return -EINVAL; + } + + return 0; +} + +void mtk_vdec_unlock(struct mtk_vcodec_ctx *ctx) +{ + mutex_unlock(&ctx->dev->dec_mutex[ctx->hw_id]); +} + +void mtk_vdec_lock(struct mtk_vcodec_ctx *ctx) +{ + mutex_lock(&ctx->dev->dec_mutex[ctx->hw_id]); +} + +void mtk_vcodec_dec_release(struct mtk_vcodec_ctx *ctx) +{ + vdec_if_deinit(ctx); + ctx->state = MTK_STATE_FREE; +} + +void mtk_vcodec_dec_set_default_params(struct mtk_vcodec_ctx *ctx) +{ + struct mtk_q_data *q_data; + + ctx->m2m_ctx->q_lock = &ctx->dev->dev_mutex; + ctx->fh.m2m_ctx = ctx->m2m_ctx; + ctx->fh.ctrl_handler = &ctx->ctrl_hdl; + INIT_WORK(&ctx->decode_work, ctx->dev->vdec_pdata->worker); + ctx->colorspace = V4L2_COLORSPACE_REC709; + ctx->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + ctx->quantization = V4L2_QUANTIZATION_DEFAULT; + ctx->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + q_data = &ctx->q_data[MTK_Q_DATA_SRC]; + memset(q_data, 0, sizeof(struct mtk_q_data)); + q_data->visible_width = DFT_CFG_WIDTH; + q_data->visible_height = DFT_CFG_HEIGHT; + q_data->fmt = ctx->dev->vdec_pdata->default_out_fmt; + q_data->field = V4L2_FIELD_NONE; + + q_data->sizeimage[0] = DFT_CFG_WIDTH * DFT_CFG_HEIGHT; + q_data->bytesperline[0] = 0; + + q_data = &ctx->q_data[MTK_Q_DATA_DST]; + memset(q_data, 0, sizeof(struct mtk_q_data)); + q_data->visible_width = DFT_CFG_WIDTH; + q_data->visible_height = DFT_CFG_HEIGHT; + q_data->coded_width = DFT_CFG_WIDTH; + q_data->coded_height = DFT_CFG_HEIGHT; + q_data->fmt = ctx->dev->vdec_pdata->default_cap_fmt; + q_data->field = V4L2_FIELD_NONE; + + q_data->sizeimage[0] = q_data->coded_width * q_data->coded_height; + q_data->bytesperline[0] = q_data->coded_width; + q_data->sizeimage[1] = q_data->sizeimage[0] / 2; + q_data->bytesperline[1] = q_data->coded_width; +} + +static int vidioc_vdec_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + + if (ctx->state == MTK_STATE_ABORT) { + mtk_v4l2_err("[%d] Call on QBUF after unrecoverable error", + ctx->id); + return -EIO; + } + + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_vdec_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + + if (ctx->state == MTK_STATE_ABORT) { + mtk_v4l2_err("[%d] Call on DQBUF after unrecoverable error", + ctx->id); + return -EIO; + } + + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int mtk_vcodec_dec_get_chip_name(void *priv) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct device *dev = &ctx->dev->plat_dev->dev; + + if (of_device_is_compatible(dev->of_node, "mediatek,mt8173-vcodec-dec")) + return 8173; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8183-vcodec-dec")) + return 8183; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8192-vcodec-dec")) + return 8192; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8195-vcodec-dec")) + return 8195; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8186-vcodec-dec")) + return 8186; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8188-vcodec-dec")) + return 8188; + else + return 8173; +} + +static int vidioc_vdec_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct device *dev = &ctx->dev->plat_dev->dev; + int platform_name = mtk_vcodec_dec_get_chip_name(priv); + + strscpy(cap->driver, dev->driver->name, sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), "MT%d video decoder", platform_name); + + return 0; +} + +static int vidioc_vdec_subscribe_evt(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(fh); + + if (ctx->dev->vdec_pdata->uses_stateless_api) + return v4l2_ctrl_subscribe_event(fh, sub); + + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + default: + return v4l2_ctrl_subscribe_event(fh, sub); + } +} + +static int vidioc_try_fmt(struct mtk_vcodec_ctx *ctx, struct v4l2_format *f, + const struct mtk_video_fmt *fmt) +{ + struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp; + const struct v4l2_frmsize_stepwise *frmsize; + + pix_fmt_mp->field = V4L2_FIELD_NONE; + + /* Always apply frame size constraints from the coded side */ + if (V4L2_TYPE_IS_OUTPUT(f->type)) + frmsize = &fmt->frmsize; + else + frmsize = &ctx->q_data[MTK_Q_DATA_SRC].fmt->frmsize; + + pix_fmt_mp->width = clamp(pix_fmt_mp->width, MTK_VDEC_MIN_W, frmsize->max_width); + pix_fmt_mp->height = clamp(pix_fmt_mp->height, MTK_VDEC_MIN_H, frmsize->max_height); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pix_fmt_mp->num_planes = 1; + pix_fmt_mp->plane_fmt[0].bytesperline = 0; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + int tmp_w, tmp_h; + + /* + * Find next closer width align 64, heign align 64, size align + * 64 rectangle + * Note: This only get default value, the real HW needed value + * only available when ctx in MTK_STATE_HEADER state + */ + tmp_w = pix_fmt_mp->width; + tmp_h = pix_fmt_mp->height; + v4l_bound_align_image(&pix_fmt_mp->width, MTK_VDEC_MIN_W, frmsize->max_width, 6, + &pix_fmt_mp->height, MTK_VDEC_MIN_H, frmsize->max_height, 6, + 9); + + if (pix_fmt_mp->width < tmp_w && + (pix_fmt_mp->width + 64) <= frmsize->max_width) + pix_fmt_mp->width += 64; + if (pix_fmt_mp->height < tmp_h && + (pix_fmt_mp->height + 64) <= frmsize->max_height) + pix_fmt_mp->height += 64; + + mtk_v4l2_debug(0, + "before resize width=%d, height=%d, after resize width=%d, height=%d, sizeimage=%d", + tmp_w, tmp_h, pix_fmt_mp->width, + pix_fmt_mp->height, + pix_fmt_mp->width * pix_fmt_mp->height); + + pix_fmt_mp->num_planes = fmt->num_planes; + pix_fmt_mp->plane_fmt[0].sizeimage = + pix_fmt_mp->width * pix_fmt_mp->height; + pix_fmt_mp->plane_fmt[0].bytesperline = pix_fmt_mp->width; + + if (pix_fmt_mp->num_planes == 2) { + pix_fmt_mp->plane_fmt[1].sizeimage = + (pix_fmt_mp->width * pix_fmt_mp->height) / 2; + pix_fmt_mp->plane_fmt[1].bytesperline = + pix_fmt_mp->width; + } + } + + pix_fmt_mp->flags = 0; + return 0; +} + +static int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + const struct mtk_video_fmt *fmt; + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + const struct mtk_vcodec_dec_pdata *dec_pdata = ctx->dev->vdec_pdata; + + fmt = mtk_vdec_find_format(f, dec_pdata); + if (!fmt) { + f->fmt.pix.pixelformat = + ctx->q_data[MTK_Q_DATA_DST].fmt->fourcc; + fmt = mtk_vdec_find_format(f, dec_pdata); + } + + return vidioc_try_fmt(ctx, f, fmt); +} + +static int vidioc_try_fmt_vid_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp; + const struct mtk_video_fmt *fmt; + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + const struct mtk_vcodec_dec_pdata *dec_pdata = ctx->dev->vdec_pdata; + + fmt = mtk_vdec_find_format(f, dec_pdata); + if (!fmt) { + f->fmt.pix.pixelformat = + ctx->q_data[MTK_Q_DATA_SRC].fmt->fourcc; + fmt = mtk_vdec_find_format(f, dec_pdata); + } + + if (pix_fmt_mp->plane_fmt[0].sizeimage == 0) { + mtk_v4l2_err("sizeimage of output format must be given"); + return -EINVAL; + } + + return vidioc_try_fmt(ctx, f, fmt); +} + +static int vidioc_vdec_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct mtk_q_data *q_data; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + q_data = &ctx->q_data[MTK_Q_DATA_DST]; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = ctx->picinfo.pic_w; + s->r.height = ctx->picinfo.pic_h; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = ctx->picinfo.buf_w; + s->r.height = ctx->picinfo.buf_h; + break; + case V4L2_SEL_TGT_COMPOSE: + if (vdec_if_get_param(ctx, GET_PARAM_CROP_INFO, &(s->r))) { + /* set to default value if header info not ready yet*/ + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data->visible_width; + s->r.height = q_data->visible_height; + } + break; + default: + return -EINVAL; + } + + if (ctx->state < MTK_STATE_HEADER) { + /* set to default value if header info not ready yet*/ + s->r.left = 0; + s->r.top = 0; + s->r.width = q_data->visible_width; + s->r.height = q_data->visible_height; + return 0; + } + + return 0; +} + +static int vidioc_vdec_s_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + s->r.left = 0; + s->r.top = 0; + s->r.width = ctx->picinfo.pic_w; + s->r.height = ctx->picinfo.pic_h; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vidioc_vdec_s_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format_mplane *pix_mp; + struct mtk_q_data *q_data; + int ret = 0; + const struct mtk_video_fmt *fmt; + const struct mtk_vcodec_dec_pdata *dec_pdata = ctx->dev->vdec_pdata; + + mtk_v4l2_debug(3, "[%d]", ctx->id); + + q_data = mtk_vdec_get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + pix_mp = &f->fmt.pix_mp; + /* + * Setting OUTPUT format after OUTPUT buffers are allocated is invalid + * if using the stateful API. + */ + if (!dec_pdata->uses_stateless_api && + f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + vb2_is_busy(&ctx->m2m_ctx->out_q_ctx.q)) { + mtk_v4l2_err("out_q_ctx buffers already requested"); + ret = -EBUSY; + } + + /* + * Setting CAPTURE format after CAPTURE buffers are allocated is + * invalid. + */ + if ((f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && + vb2_is_busy(&ctx->m2m_ctx->cap_q_ctx.q)) { + mtk_v4l2_err("cap_q_ctx buffers already requested"); + ret = -EBUSY; + } + + fmt = mtk_vdec_find_format(f, dec_pdata); + if (fmt == NULL) { + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + f->fmt.pix.pixelformat = + dec_pdata->default_out_fmt->fourcc; + fmt = mtk_vdec_find_format(f, dec_pdata); + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + f->fmt.pix.pixelformat = + dec_pdata->default_cap_fmt->fourcc; + fmt = mtk_vdec_find_format(f, dec_pdata); + } + } + if (fmt == NULL) + return -EINVAL; + + q_data->fmt = fmt; + vidioc_try_fmt(ctx, f, q_data->fmt); + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + q_data->sizeimage[0] = pix_mp->plane_fmt[0].sizeimage; + q_data->coded_width = pix_mp->width; + q_data->coded_height = pix_mp->height; + + ctx->colorspace = pix_mp->colorspace; + ctx->ycbcr_enc = pix_mp->ycbcr_enc; + ctx->quantization = pix_mp->quantization; + ctx->xfer_func = pix_mp->xfer_func; + + ctx->current_codec = fmt->fourcc; + if (ctx->state == MTK_STATE_FREE) { + ret = vdec_if_init(ctx, q_data->fmt->fourcc); + if (ret) { + mtk_v4l2_err("[%d]: vdec_if_init() fail ret=%d", + ctx->id, ret); + return -EINVAL; + } + ctx->state = MTK_STATE_INIT; + } + } else { + ctx->capture_fourcc = fmt->fourcc; + } + + /* + * If using the stateless API, S_FMT should have the effect of setting + * the CAPTURE queue resolution no matter which queue it was called on. + */ + if (dec_pdata->uses_stateless_api) { + ctx->picinfo.pic_w = pix_mp->width; + ctx->picinfo.pic_h = pix_mp->height; + + /* + * If get pic info fail, need to use the default pic info params, or + * v4l2-compliance will fail + */ + ret = vdec_if_get_param(ctx, GET_PARAM_PIC_INFO, &ctx->picinfo); + if (ret) { + mtk_v4l2_err("[%d]Error!! Get GET_PARAM_PICTURE_INFO Fail", + ctx->id); + } + + ctx->last_decoded_picinfo = ctx->picinfo; + + if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 1) { + ctx->q_data[MTK_Q_DATA_DST].sizeimage[0] = + ctx->picinfo.fb_sz[0] + + ctx->picinfo.fb_sz[1]; + ctx->q_data[MTK_Q_DATA_DST].bytesperline[0] = + ctx->picinfo.buf_w; + } else { + ctx->q_data[MTK_Q_DATA_DST].sizeimage[0] = + ctx->picinfo.fb_sz[0]; + ctx->q_data[MTK_Q_DATA_DST].bytesperline[0] = + ctx->picinfo.buf_w; + ctx->q_data[MTK_Q_DATA_DST].sizeimage[1] = + ctx->picinfo.fb_sz[1]; + ctx->q_data[MTK_Q_DATA_DST].bytesperline[1] = + ctx->picinfo.buf_w; + } + + ctx->q_data[MTK_Q_DATA_DST].coded_width = ctx->picinfo.buf_w; + ctx->q_data[MTK_Q_DATA_DST].coded_height = ctx->picinfo.buf_h; + mtk_v4l2_debug(2, "[%d] vdec_if_init() num_plane = %d wxh=%dx%d pic wxh=%dx%d sz[0]=0x%x sz[1]=0x%x", + ctx->id, pix_mp->num_planes, ctx->picinfo.buf_w, ctx->picinfo.buf_h, + ctx->picinfo.pic_w, ctx->picinfo.pic_h, + ctx->q_data[MTK_Q_DATA_DST].sizeimage[0], + ctx->q_data[MTK_Q_DATA_DST].sizeimage[1]); + } + return 0; +} + +static int vidioc_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + int i = 0; + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + const struct mtk_vcodec_dec_pdata *dec_pdata = ctx->dev->vdec_pdata; + + if (fsize->index != 0) + return -EINVAL; + + for (i = 0; i < *dec_pdata->num_formats; i++) { + if (fsize->pixel_format != dec_pdata->vdec_formats[i].fourcc) + continue; + + /* Only coded formats have frame sizes set */ + if (!dec_pdata->vdec_formats[i].frmsize.max_width) + return -ENOTTY; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise = dec_pdata->vdec_formats[i].frmsize; + + mtk_v4l2_debug(1, "%x, %d %d %d %d %d %d", + ctx->dev->dec_capability, + fsize->stepwise.min_width, + fsize->stepwise.max_width, + fsize->stepwise.step_width, + fsize->stepwise.min_height, + fsize->stepwise.max_height, + fsize->stepwise.step_height); + + return 0; + } + + return -EINVAL; +} + +static int vidioc_enum_fmt(struct v4l2_fmtdesc *f, void *priv, + bool output_queue) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + const struct mtk_vcodec_dec_pdata *dec_pdata = ctx->dev->vdec_pdata; + const struct mtk_video_fmt *fmt; + int i, j = 0; + + for (i = 0; i < *dec_pdata->num_formats; i++) { + if (output_queue && + dec_pdata->vdec_formats[i].type != MTK_FMT_DEC) + continue; + if (!output_queue && + dec_pdata->vdec_formats[i].type != MTK_FMT_FRAME) + continue; + + if (!output_queue && !mtk_vdec_get_cap_fmt(ctx, i)) + continue; + + if (j == f->index) + break; + ++j; + } + + if (i == *dec_pdata->num_formats) + return -EINVAL; + + fmt = &dec_pdata->vdec_formats[i]; + f->pixelformat = fmt->fourcc; + f->flags = fmt->flags; + + return 0; +} + +static int vidioc_vdec_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt(f, priv, false); +} + +static int vidioc_vdec_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt(f, priv, true); +} + +static int vidioc_vdec_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct vb2_queue *vq; + struct mtk_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) { + mtk_v4l2_err("no vb2 queue for type=%d", f->type); + return -EINVAL; + } + + q_data = mtk_vdec_get_q_data(ctx, f->type); + + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->colorspace = ctx->colorspace; + pix_mp->ycbcr_enc = ctx->ycbcr_enc; + pix_mp->quantization = ctx->quantization; + pix_mp->xfer_func = ctx->xfer_func; + + if ((f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) && + (ctx->state >= MTK_STATE_HEADER)) { + /* Until STREAMOFF is called on the CAPTURE queue + * (acknowledging the event), the driver operates as if + * the resolution hasn't changed yet. + * So we just return picinfo yet, and update picinfo in + * stop_streaming hook function + */ + q_data->sizeimage[0] = ctx->picinfo.fb_sz[0]; + q_data->sizeimage[1] = ctx->picinfo.fb_sz[1]; + q_data->bytesperline[0] = ctx->last_decoded_picinfo.buf_w; + q_data->bytesperline[1] = ctx->last_decoded_picinfo.buf_w; + q_data->coded_width = ctx->picinfo.buf_w; + q_data->coded_height = ctx->picinfo.buf_h; + ctx->last_decoded_picinfo.cap_fourcc = q_data->fmt->fourcc; + + /* + * Width and height are set to the dimensions + * of the movie, the buffer is bigger and + * further processing stages should crop to this + * rectangle. + */ + pix_mp->width = q_data->coded_width; + pix_mp->height = q_data->coded_height; + + /* + * Set pixelformat to the format in which mt vcodec + * outputs the decoded frame + */ + pix_mp->num_planes = q_data->fmt->num_planes; + pix_mp->pixelformat = q_data->fmt->fourcc; + pix_mp->plane_fmt[0].bytesperline = q_data->bytesperline[0]; + pix_mp->plane_fmt[0].sizeimage = q_data->sizeimage[0]; + pix_mp->plane_fmt[1].bytesperline = q_data->bytesperline[1]; + pix_mp->plane_fmt[1].sizeimage = q_data->sizeimage[1]; + + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + /* + * This is run on OUTPUT + * The buffer contains compressed image + * so width and height have no meaning. + * Assign value here to pass v4l2-compliance test + */ + pix_mp->width = q_data->visible_width; + pix_mp->height = q_data->visible_height; + pix_mp->plane_fmt[0].bytesperline = q_data->bytesperline[0]; + pix_mp->plane_fmt[0].sizeimage = q_data->sizeimage[0]; + pix_mp->pixelformat = q_data->fmt->fourcc; + pix_mp->num_planes = q_data->fmt->num_planes; + } else { + pix_mp->width = q_data->coded_width; + pix_mp->height = q_data->coded_height; + pix_mp->num_planes = q_data->fmt->num_planes; + pix_mp->pixelformat = q_data->fmt->fourcc; + pix_mp->plane_fmt[0].bytesperline = q_data->bytesperline[0]; + pix_mp->plane_fmt[0].sizeimage = q_data->sizeimage[0]; + pix_mp->plane_fmt[1].bytesperline = q_data->bytesperline[1]; + pix_mp->plane_fmt[1].sizeimage = q_data->sizeimage[1]; + + mtk_v4l2_debug(1, "[%d] type=%d state=%d Format information could not be read, not ready yet!", + ctx->id, f->type, ctx->state); + } + + return 0; +} + +int vb2ops_vdec_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vq); + struct mtk_q_data *q_data; + unsigned int i; + + q_data = mtk_vdec_get_q_data(ctx, vq->type); + + if (q_data == NULL) { + mtk_v4l2_err("vq->type=%d err\n", vq->type); + return -EINVAL; + } + + if (*nplanes) { + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (*nplanes != q_data->fmt->num_planes) + return -EINVAL; + } else { + if (*nplanes != 1) + return -EINVAL; + } + for (i = 0; i < *nplanes; i++) { + if (sizes[i] < q_data->sizeimage[i]) + return -EINVAL; + } + } else { + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + *nplanes = q_data->fmt->num_planes; + else + *nplanes = 1; + + for (i = 0; i < *nplanes; i++) + sizes[i] = q_data->sizeimage[i]; + } + + mtk_v4l2_debug(1, + "[%d]\t type = %d, get %d plane(s), %d buffer(s) of size 0x%x 0x%x ", + ctx->id, vq->type, *nplanes, *nbuffers, + sizes[0], sizes[1]); + + return 0; +} + +int vb2ops_vdec_buf_prepare(struct vb2_buffer *vb) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mtk_q_data *q_data; + int i; + + mtk_v4l2_debug(3, "[%d] (%d) id=%d", + ctx->id, vb->vb2_queue->type, vb->index); + + q_data = mtk_vdec_get_q_data(ctx, vb->vb2_queue->type); + + for (i = 0; i < q_data->fmt->num_planes; i++) { + if (vb2_plane_size(vb, i) < q_data->sizeimage[i]) { + mtk_v4l2_err("data will not fit into plane %d (%lu < %d)", + i, vb2_plane_size(vb, i), + q_data->sizeimage[i]); + return -EINVAL; + } + if (!V4L2_TYPE_IS_OUTPUT(vb->type)) + vb2_set_plane_payload(vb, i, q_data->sizeimage[i]); + } + + return 0; +} + +void vb2ops_vdec_buf_finish(struct vb2_buffer *vb) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vb2_v4l2; + struct mtk_video_dec_buf *buf; + bool buf_error; + + vb2_v4l2 = container_of(vb, struct vb2_v4l2_buffer, vb2_buf); + buf = container_of(vb2_v4l2, struct mtk_video_dec_buf, m2m_buf.vb); + mutex_lock(&ctx->lock); + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + buf->queued_in_v4l2 = false; + buf->queued_in_vb2 = false; + } + buf_error = buf->error; + mutex_unlock(&ctx->lock); + + if (buf_error) { + mtk_v4l2_err("Unrecoverable error on buffer."); + ctx->state = MTK_STATE_ABORT; + } +} + +int vb2ops_vdec_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vb2_v4l2 = container_of(vb, + struct vb2_v4l2_buffer, vb2_buf); + struct mtk_video_dec_buf *buf = container_of(vb2_v4l2, + struct mtk_video_dec_buf, m2m_buf.vb); + + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + buf->used = false; + buf->queued_in_v4l2 = false; + } + + return 0; +} + +int vb2ops_vdec_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(q); + + if (ctx->state == MTK_STATE_FLUSH) + ctx->state = MTK_STATE_HEADER; + + return 0; +} + +void vb2ops_vdec_stop_streaming(struct vb2_queue *q) +{ + struct vb2_v4l2_buffer *src_buf = NULL, *dst_buf = NULL; + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(q); + int ret; + + mtk_v4l2_debug(3, "[%d] (%d) state=(%x) ctx->decoded_frame_cnt=%d", + ctx->id, q->type, ctx->state, ctx->decoded_frame_cnt); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx))) { + if (src_buf != &ctx->empty_flush_buf.vb) { + struct media_request *req = + src_buf->vb2_buf.req_obj.req; + v4l2_m2m_buf_done(src_buf, + VB2_BUF_STATE_ERROR); + if (req) + v4l2_ctrl_request_complete(req, &ctx->ctrl_hdl); + } + } + return; + } + + if (ctx->state >= MTK_STATE_HEADER) { + + /* Until STREAMOFF is called on the CAPTURE queue + * (acknowledging the event), the driver operates + * as if the resolution hasn't changed yet, i.e. + * VIDIOC_G_FMT< etc. return previous resolution. + * So we update picinfo here + */ + ctx->picinfo = ctx->last_decoded_picinfo; + + mtk_v4l2_debug(2, + "[%d]-> new(%d,%d), old(%d,%d), real(%d,%d)", + ctx->id, ctx->last_decoded_picinfo.pic_w, + ctx->last_decoded_picinfo.pic_h, + ctx->picinfo.pic_w, ctx->picinfo.pic_h, + ctx->last_decoded_picinfo.buf_w, + ctx->last_decoded_picinfo.buf_h); + + ret = ctx->dev->vdec_pdata->flush_decoder(ctx); + if (ret) + mtk_v4l2_err("DecodeFinal failed, ret=%d", ret); + } + ctx->state = MTK_STATE_FLUSH; + + while ((dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx))) { + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0); + if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 2) + vb2_set_plane_payload(&dst_buf->vb2_buf, 1, 0); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + } + +} + +static void m2mops_vdec_device_run(void *priv) +{ + struct mtk_vcodec_ctx *ctx = priv; + struct mtk_vcodec_dev *dev = ctx->dev; + + queue_work(dev->decode_workqueue, &ctx->decode_work); +} + +static int m2mops_vdec_job_ready(void *m2m_priv) +{ + struct mtk_vcodec_ctx *ctx = m2m_priv; + + mtk_v4l2_debug(3, "[%d]", ctx->id); + + if (ctx->state == MTK_STATE_ABORT) + return 0; + + if ((ctx->last_decoded_picinfo.pic_w != ctx->picinfo.pic_w) || + (ctx->last_decoded_picinfo.pic_h != ctx->picinfo.pic_h)) + return 0; + + if (ctx->state != MTK_STATE_HEADER) + return 0; + + return 1; +} + +static void m2mops_vdec_job_abort(void *priv) +{ + struct mtk_vcodec_ctx *ctx = priv; + + ctx->state = MTK_STATE_ABORT; +} + +const struct v4l2_m2m_ops mtk_vdec_m2m_ops = { + .device_run = m2mops_vdec_device_run, + .job_ready = m2mops_vdec_job_ready, + .job_abort = m2mops_vdec_job_abort, +}; + +const struct v4l2_ioctl_ops mtk_vdec_ioctl_ops = { + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_qbuf = vidioc_vdec_qbuf, + .vidioc_dqbuf = vidioc_vdec_dqbuf, + + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_vid_out_mplane, + + .vidioc_s_fmt_vid_cap_mplane = vidioc_vdec_s_fmt, + .vidioc_s_fmt_vid_out_mplane = vidioc_vdec_s_fmt, + .vidioc_g_fmt_vid_cap_mplane = vidioc_vdec_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vidioc_vdec_g_fmt, + + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + + .vidioc_enum_fmt_vid_cap = vidioc_vdec_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = vidioc_vdec_enum_fmt_vid_out, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + + .vidioc_querycap = vidioc_vdec_querycap, + .vidioc_subscribe_event = vidioc_vdec_subscribe_evt, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_g_selection = vidioc_vdec_g_selection, + .vidioc_s_selection = vidioc_vdec_s_selection, + + .vidioc_decoder_cmd = vidioc_decoder_cmd, + .vidioc_try_decoder_cmd = vidioc_try_decoder_cmd, +}; + +int mtk_vcodec_dec_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mtk_vcodec_ctx *ctx = priv; + int ret = 0; + + mtk_v4l2_debug(3, "[%d]", ctx->id); + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_DMABUF | VB2_MMAP; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct mtk_video_dec_buf); + src_vq->ops = ctx->dev->vdec_pdata->vdec_vb2_ops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->dev_mutex; + src_vq->dev = &ctx->dev->plat_dev->dev; + src_vq->allow_cache_hints = 1; + + ret = vb2_queue_init(src_vq); + if (ret) { + mtk_v4l2_err("Failed to initialize videobuf2 queue(output)"); + return ret; + } + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_DMABUF | VB2_MMAP; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct mtk_video_dec_buf); + dst_vq->ops = ctx->dev->vdec_pdata->vdec_vb2_ops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->dev_mutex; + dst_vq->dev = &ctx->dev->plat_dev->dev; + dst_vq->allow_cache_hints = 1; + + ret = vb2_queue_init(dst_vq); + if (ret) + mtk_v4l2_err("Failed to initialize videobuf2 queue(capture)"); + + return ret; +} diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec.h new file mode 100644 index 000000000..4572f9282 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + * Tiffany Lin + */ + +#ifndef _MTK_VCODEC_DEC_H_ +#define _MTK_VCODEC_DEC_H_ + +#include +#include + +#define VCODEC_DEC_ALIGNED_64 64 +#define VCODEC_CAPABILITY_4K_DISABLED 0x10 +#define VCODEC_DEC_4K_CODED_WIDTH 4096U +#define VCODEC_DEC_4K_CODED_HEIGHT 2304U +#define MTK_VDEC_MAX_W 2048U +#define MTK_VDEC_MAX_H 1088U +#define MTK_VDEC_MIN_W 64U +#define MTK_VDEC_MIN_H 64U + +#define MTK_VDEC_IRQ_STATUS_DEC_SUCCESS 0x10000 + +/** + * struct vdec_fb - decoder frame buffer + * @base_y : Y plane memory info + * @base_c : C plane memory info + * @status : frame buffer status (vdec_fb_status) + */ +struct vdec_fb { + struct mtk_vcodec_mem base_y; + struct mtk_vcodec_mem base_c; + unsigned int status; +}; + +/** + * struct mtk_video_dec_buf - Private data related to each VB2 buffer. + * @m2m_buf: M2M buffer + * @list: link list + * @used: Capture buffer contain decoded frame data and keep in + * codec data structure + * @queued_in_vb2: Capture buffer is queue in vb2 + * @queued_in_v4l2: Capture buffer is in v4l2 driver, but not in vb2 + * queue yet + * @error: An unrecoverable error occurs on this buffer. + * @frame_buffer: Decode status, and buffer information of Capture buffer + * @bs_buffer: Output buffer info + * + * Note : These status information help us track and debug buffer state + */ +struct mtk_video_dec_buf { + struct v4l2_m2m_buffer m2m_buf; + + bool used; + bool queued_in_vb2; + bool queued_in_v4l2; + bool error; + + union { + struct vdec_fb frame_buffer; + struct mtk_vcodec_mem bs_buffer; + }; +}; + +extern const struct v4l2_ioctl_ops mtk_vdec_ioctl_ops; +extern const struct v4l2_m2m_ops mtk_vdec_m2m_ops; +extern const struct media_device_ops mtk_vcodec_media_ops; +extern const struct mtk_vcodec_dec_pdata mtk_vdec_8173_pdata; +extern const struct mtk_vcodec_dec_pdata mtk_vdec_8183_pdata; +extern const struct mtk_vcodec_dec_pdata mtk_lat_sig_core_pdata; +extern const struct mtk_vcodec_dec_pdata mtk_vdec_single_core_pdata; + + +/* + * mtk_vdec_lock/mtk_vdec_unlock are for ctx instance to + * get/release lock before/after access decoder hw. + * mtk_vdec_lock get decoder hw lock and set curr_ctx + * to ctx instance that get lock + */ +void mtk_vdec_unlock(struct mtk_vcodec_ctx *ctx); +void mtk_vdec_lock(struct mtk_vcodec_ctx *ctx); +int mtk_vcodec_dec_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); +void mtk_vcodec_dec_set_default_params(struct mtk_vcodec_ctx *ctx); +void mtk_vcodec_dec_release(struct mtk_vcodec_ctx *ctx); + +/* + * VB2 ops + */ +int vb2ops_vdec_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]); +int vb2ops_vdec_buf_prepare(struct vb2_buffer *vb); +void vb2ops_vdec_buf_finish(struct vb2_buffer *vb); +int vb2ops_vdec_buf_init(struct vb2_buffer *vb); +int vb2ops_vdec_start_streaming(struct vb2_queue *q, unsigned int count); +void vb2ops_vdec_stop_streaming(struct vb2_queue *q); + + +#endif /* _MTK_VCODEC_DEC_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_drv.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_drv.c new file mode 100644 index 000000000..42df901e8 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_drv.c @@ -0,0 +1,528 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + * Tiffany Lin + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_dec.h" +#include "mtk_vcodec_dec_hw.h" +#include "mtk_vcodec_dec_pm.h" +#include "mtk_vcodec_intr.h" +#include "mtk_vcodec_util.h" +#include "mtk_vcodec_fw.h" + +static int mtk_vcodec_get_hw_count(struct mtk_vcodec_dev *dev) +{ + switch (dev->vdec_pdata->hw_arch) { + case MTK_VDEC_PURE_SINGLE_CORE: + return MTK_VDEC_ONE_CORE; + case MTK_VDEC_LAT_SINGLE_CORE: + return MTK_VDEC_ONE_LAT_ONE_CORE; + default: + mtk_v4l2_err("hw arch %d not supported", dev->vdec_pdata->hw_arch); + return MTK_VDEC_NO_HW; + } +} + +static irqreturn_t mtk_vcodec_dec_irq_handler(int irq, void *priv) +{ + struct mtk_vcodec_dev *dev = priv; + struct mtk_vcodec_ctx *ctx; + u32 cg_status = 0; + unsigned int dec_done_status = 0; + void __iomem *vdec_misc_addr = dev->reg_base[VDEC_MISC] + + VDEC_IRQ_CFG_REG; + + ctx = mtk_vcodec_get_curr_ctx(dev, MTK_VDEC_CORE); + + /* check if HW active or not */ + cg_status = readl(dev->reg_base[0]); + if ((cg_status & VDEC_HW_ACTIVE) != 0) { + mtk_v4l2_err("DEC ISR, VDEC active is not 0x0 (0x%08x)", + cg_status); + return IRQ_HANDLED; + } + + dec_done_status = readl(vdec_misc_addr); + ctx->irq_status = dec_done_status; + if ((dec_done_status & MTK_VDEC_IRQ_STATUS_DEC_SUCCESS) != + MTK_VDEC_IRQ_STATUS_DEC_SUCCESS) + return IRQ_HANDLED; + + /* clear interrupt */ + writel((readl(vdec_misc_addr) | VDEC_IRQ_CFG), + dev->reg_base[VDEC_MISC] + VDEC_IRQ_CFG_REG); + writel((readl(vdec_misc_addr) & ~VDEC_IRQ_CLR), + dev->reg_base[VDEC_MISC] + VDEC_IRQ_CFG_REG); + + wake_up_ctx(ctx, MTK_INST_IRQ_RECEIVED, 0); + + mtk_v4l2_debug(3, + "mtk_vcodec_dec_irq_handler :wake up ctx %d, dec_done_status=%x", + ctx->id, dec_done_status); + + return IRQ_HANDLED; +} + +static int mtk_vcodec_get_reg_bases(struct mtk_vcodec_dev *dev) +{ + struct platform_device *pdev = dev->plat_dev; + int reg_num, i; + + /* Sizeof(u32) * 4 bytes for each register base. */ + reg_num = of_property_count_elems_of_size(pdev->dev.of_node, "reg", + sizeof(u32) * 4); + if (reg_num <= 0 || reg_num > NUM_MAX_VDEC_REG_BASE) { + dev_err(&pdev->dev, "Invalid register property size: %d\n", reg_num); + return -EINVAL; + } + + for (i = 0; i < reg_num; i++) { + dev->reg_base[i] = devm_platform_ioremap_resource(pdev, i); + if (IS_ERR(dev->reg_base[i])) + return PTR_ERR(dev->reg_base[i]); + + mtk_v4l2_debug(2, "reg[%d] base=%p", i, dev->reg_base[i]); + } + + return 0; +} + +static int mtk_vcodec_init_dec_resources(struct mtk_vcodec_dev *dev) +{ + struct platform_device *pdev = dev->plat_dev; + int ret; + + ret = mtk_vcodec_get_reg_bases(dev); + if (ret) + return ret; + + if (dev->vdec_pdata->is_subdev_supported) + return 0; + + dev->dec_irq = platform_get_irq(pdev, 0); + if (dev->dec_irq < 0) + return dev->dec_irq; + + irq_set_status_flags(dev->dec_irq, IRQ_NOAUTOEN); + ret = devm_request_irq(&pdev->dev, dev->dec_irq, + mtk_vcodec_dec_irq_handler, 0, pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "failed to install dev->dec_irq %d (%d)", + dev->dec_irq, ret); + return ret; + } + + ret = mtk_vcodec_init_dec_clk(pdev, &dev->pm); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get mt vcodec clock source"); + return ret; + } + + pm_runtime_enable(&pdev->dev); + return 0; +} + +static int fops_vcodec_open(struct file *file) +{ + struct mtk_vcodec_dev *dev = video_drvdata(file); + struct mtk_vcodec_ctx *ctx = NULL; + int ret = 0, i, hw_count; + struct vb2_queue *src_vq; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_lock(&dev->dev_mutex); + ctx->id = dev->id_counter++; + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + INIT_LIST_HEAD(&ctx->list); + ctx->dev = dev; + if (ctx->dev->vdec_pdata->is_subdev_supported) { + hw_count = mtk_vcodec_get_hw_count(dev); + if (!hw_count || !dev->subdev_prob_done) { + ret = -EINVAL; + goto err_ctrls_setup; + } + + ret = dev->subdev_prob_done(dev); + if (ret) + goto err_ctrls_setup; + + for (i = 0; i < hw_count; i++) + init_waitqueue_head(&ctx->queue[i]); + } else { + init_waitqueue_head(&ctx->queue[0]); + } + mutex_init(&ctx->lock); + + ctx->type = MTK_INST_DECODER; + ret = dev->vdec_pdata->ctrls_setup(ctx); + if (ret) { + mtk_v4l2_err("Failed to setup mt vcodec controls"); + goto err_ctrls_setup; + } + ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev_dec, ctx, + &mtk_vcodec_dec_queue_init); + if (IS_ERR((__force void *)ctx->m2m_ctx)) { + ret = PTR_ERR((__force void *)ctx->m2m_ctx); + mtk_v4l2_err("Failed to v4l2_m2m_ctx_init() (%d)", + ret); + goto err_m2m_ctx_init; + } + src_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + ctx->empty_flush_buf.vb.vb2_buf.vb2_queue = src_vq; + mtk_vcodec_dec_set_default_params(ctx); + + if (v4l2_fh_is_singular(&ctx->fh)) { + /* + * Does nothing if firmware was already loaded. + */ + ret = mtk_vcodec_fw_load_firmware(dev->fw_handler); + if (ret < 0) { + /* + * Return 0 if downloading firmware successfully, + * otherwise it is failed + */ + mtk_v4l2_err("failed to load firmware!"); + goto err_load_fw; + } + + dev->dec_capability = + mtk_vcodec_fw_get_vdec_capa(dev->fw_handler); + + mtk_v4l2_debug(0, "decoder capability %x", dev->dec_capability); + } + + ctx->dev->vdec_pdata->init_vdec_params(ctx); + + list_add(&ctx->list, &dev->ctx_list); + + mutex_unlock(&dev->dev_mutex); + mtk_v4l2_debug(0, "%s decoder [%d]", dev_name(&dev->plat_dev->dev), + ctx->id); + return ret; + + /* Deinit when failure occurred */ +err_load_fw: + v4l2_m2m_ctx_release(ctx->m2m_ctx); +err_m2m_ctx_init: + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); +err_ctrls_setup: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&dev->dev_mutex); + + return ret; +} + +static int fops_vcodec_release(struct file *file) +{ + struct mtk_vcodec_dev *dev = video_drvdata(file); + struct mtk_vcodec_ctx *ctx = fh_to_ctx(file->private_data); + + mtk_v4l2_debug(0, "[%d] decoder", ctx->id); + mutex_lock(&dev->dev_mutex); + + /* + * Call v4l2_m2m_ctx_release before mtk_vcodec_dec_release. First, it + * makes sure the worker thread is not running after vdec_if_deinit. + * Second, the decoder will be flushed and all the buffers will be + * returned in stop_streaming. + */ + v4l2_m2m_ctx_release(ctx->m2m_ctx); + mtk_vcodec_dec_release(ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + + list_del_init(&ctx->list); + kfree(ctx); + mutex_unlock(&dev->dev_mutex); + return 0; +} + +static const struct v4l2_file_operations mtk_vcodec_fops = { + .owner = THIS_MODULE, + .open = fops_vcodec_open, + .release = fops_vcodec_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int mtk_vcodec_probe(struct platform_device *pdev) +{ + struct mtk_vcodec_dev *dev; + struct video_device *vfd_dec; + phandle rproc_phandle; + enum mtk_vcodec_fw_type fw_type; + int i, ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + INIT_LIST_HEAD(&dev->ctx_list); + dev->plat_dev = pdev; + + dev->vdec_pdata = of_device_get_match_data(&pdev->dev); + if (!of_property_read_u32(pdev->dev.of_node, "mediatek,vpu", + &rproc_phandle)) { + fw_type = VPU; + } else if (!of_property_read_u32(pdev->dev.of_node, "mediatek,scp", + &rproc_phandle)) { + fw_type = SCP; + } else { + mtk_v4l2_err("Could not get vdec IPI device"); + return -ENODEV; + } + dma_set_max_seg_size(&pdev->dev, UINT_MAX); + + dev->fw_handler = mtk_vcodec_fw_select(dev, fw_type, DECODER); + if (IS_ERR(dev->fw_handler)) + return PTR_ERR(dev->fw_handler); + + ret = mtk_vcodec_init_dec_resources(dev); + if (ret) { + dev_err(&pdev->dev, "Failed to init dec resources"); + goto err_dec_pm; + } + + if (IS_VDEC_LAT_ARCH(dev->vdec_pdata->hw_arch)) { + vdec_msg_queue_init_ctx(&dev->msg_queue_core_ctx, MTK_VDEC_CORE); + dev->core_workqueue = + alloc_ordered_workqueue("core-decoder", + WQ_MEM_RECLAIM | WQ_FREEZABLE); + if (!dev->core_workqueue) { + mtk_v4l2_err("Failed to create core workqueue"); + ret = -EINVAL; + goto err_res; + } + } + + if (of_get_property(pdev->dev.of_node, "dma-ranges", NULL)) { + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34)); + if (ret) { + mtk_v4l2_err("Failed to set mask"); + goto err_core_workq; + } + } + + for (i = 0; i < MTK_VDEC_HW_MAX; i++) + mutex_init(&dev->dec_mutex[i]); + mutex_init(&dev->dev_mutex); + spin_lock_init(&dev->irqlock); + + snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), "%s", + "[/MTK_V4L2_VDEC]"); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) { + mtk_v4l2_err("v4l2_device_register err=%d", ret); + goto err_core_workq; + } + + init_waitqueue_head(&dev->queue); + + vfd_dec = video_device_alloc(); + if (!vfd_dec) { + mtk_v4l2_err("Failed to allocate video device"); + ret = -ENOMEM; + goto err_dec_alloc; + } + vfd_dec->fops = &mtk_vcodec_fops; + vfd_dec->ioctl_ops = &mtk_vdec_ioctl_ops; + vfd_dec->release = video_device_release; + vfd_dec->lock = &dev->dev_mutex; + vfd_dec->v4l2_dev = &dev->v4l2_dev; + vfd_dec->vfl_dir = VFL_DIR_M2M; + vfd_dec->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | + V4L2_CAP_STREAMING; + + snprintf(vfd_dec->name, sizeof(vfd_dec->name), "%s", + MTK_VCODEC_DEC_NAME); + video_set_drvdata(vfd_dec, dev); + dev->vfd_dec = vfd_dec; + platform_set_drvdata(pdev, dev); + + dev->m2m_dev_dec = v4l2_m2m_init(&mtk_vdec_m2m_ops); + if (IS_ERR((__force void *)dev->m2m_dev_dec)) { + mtk_v4l2_err("Failed to init mem2mem dec device"); + ret = PTR_ERR((__force void *)dev->m2m_dev_dec); + goto err_dec_alloc; + } + + dev->decode_workqueue = + alloc_ordered_workqueue(MTK_VCODEC_DEC_NAME, + WQ_MEM_RECLAIM | WQ_FREEZABLE); + if (!dev->decode_workqueue) { + mtk_v4l2_err("Failed to create decode workqueue"); + ret = -EINVAL; + goto err_event_workq; + } + + if (dev->vdec_pdata->is_subdev_supported) { + ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, + &pdev->dev); + if (ret) { + mtk_v4l2_err("Main device of_platform_populate failed."); + goto err_reg_cont; + } + } else { + set_bit(MTK_VDEC_CORE, dev->subdev_bitmap); + } + + atomic_set(&dev->dec_active_cnt, 0); + memset(dev->vdec_racing_info, 0, sizeof(dev->vdec_racing_info)); + mutex_init(&dev->dec_racing_info_mutex); + + ret = video_register_device(vfd_dec, VFL_TYPE_VIDEO, -1); + if (ret) { + mtk_v4l2_err("Failed to register video device"); + goto err_reg_cont; + } + + if (dev->vdec_pdata->uses_stateless_api) { + v4l2_disable_ioctl(vfd_dec, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(vfd_dec, VIDIOC_TRY_DECODER_CMD); + + dev->mdev_dec.dev = &pdev->dev; + strscpy(dev->mdev_dec.model, MTK_VCODEC_DEC_NAME, + sizeof(dev->mdev_dec.model)); + + media_device_init(&dev->mdev_dec); + dev->mdev_dec.ops = &mtk_vcodec_media_ops; + dev->v4l2_dev.mdev = &dev->mdev_dec; + + ret = v4l2_m2m_register_media_controller(dev->m2m_dev_dec, dev->vfd_dec, + MEDIA_ENT_F_PROC_VIDEO_DECODER); + if (ret) { + mtk_v4l2_err("Failed to register media controller"); + goto err_dec_mem_init; + } + + ret = media_device_register(&dev->mdev_dec); + if (ret) { + mtk_v4l2_err("Failed to register media device"); + goto err_media_reg; + } + + mtk_v4l2_debug(0, "media registered as /dev/media%d", vfd_dec->minor); + } + + mtk_v4l2_debug(0, "decoder registered as /dev/video%d", vfd_dec->minor); + + return 0; + +err_media_reg: + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec); +err_dec_mem_init: + video_unregister_device(vfd_dec); +err_reg_cont: + if (dev->vdec_pdata->uses_stateless_api) + media_device_cleanup(&dev->mdev_dec); + destroy_workqueue(dev->decode_workqueue); +err_event_workq: + v4l2_m2m_release(dev->m2m_dev_dec); +err_dec_alloc: + v4l2_device_unregister(&dev->v4l2_dev); +err_core_workq: + if (IS_VDEC_LAT_ARCH(dev->vdec_pdata->hw_arch)) + destroy_workqueue(dev->core_workqueue); +err_res: + if (!dev->vdec_pdata->is_subdev_supported) + pm_runtime_disable(dev->pm.dev); +err_dec_pm: + mtk_vcodec_fw_release(dev->fw_handler); + return ret; +} + +static const struct of_device_id mtk_vcodec_match[] = { + { + .compatible = "mediatek,mt8173-vcodec-dec", + .data = &mtk_vdec_8173_pdata, + }, + { + .compatible = "mediatek,mt8183-vcodec-dec", + .data = &mtk_vdec_8183_pdata, + }, + { + .compatible = "mediatek,mt8192-vcodec-dec", + .data = &mtk_lat_sig_core_pdata, + }, + { + .compatible = "mediatek,mt8186-vcodec-dec", + .data = &mtk_vdec_single_core_pdata, + }, + { + .compatible = "mediatek,mt8195-vcodec-dec", + .data = &mtk_lat_sig_core_pdata, + }, + { + .compatible = "mediatek,mt8188-vcodec-dec", + .data = &mtk_lat_sig_core_pdata, + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, mtk_vcodec_match); + +static int mtk_vcodec_dec_remove(struct platform_device *pdev) +{ + struct mtk_vcodec_dev *dev = platform_get_drvdata(pdev); + + destroy_workqueue(dev->decode_workqueue); + + if (media_devnode_is_registered(dev->mdev_dec.devnode)) { + media_device_unregister(&dev->mdev_dec); + v4l2_m2m_unregister_media_controller(dev->m2m_dev_dec); + media_device_cleanup(&dev->mdev_dec); + } + + if (dev->m2m_dev_dec) + v4l2_m2m_release(dev->m2m_dev_dec); + + if (dev->vfd_dec) + video_unregister_device(dev->vfd_dec); + + v4l2_device_unregister(&dev->v4l2_dev); + if (!dev->vdec_pdata->is_subdev_supported) + pm_runtime_disable(dev->pm.dev); + mtk_vcodec_fw_release(dev->fw_handler); + return 0; +} + +static struct platform_driver mtk_vcodec_dec_driver = { + .probe = mtk_vcodec_probe, + .remove = mtk_vcodec_dec_remove, + .driver = { + .name = MTK_VCODEC_DEC_NAME, + .of_match_table = mtk_vcodec_match, + }, +}; + +module_platform_driver(mtk_vcodec_dec_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Mediatek video codec V4L2 decoder driver"); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_hw.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_hw.c new file mode 100644 index 000000000..b753bf54e --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_hw.c @@ -0,0 +1,214 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + * Author: Yunfei Dong + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_dec.h" +#include "mtk_vcodec_dec_hw.h" +#include "mtk_vcodec_dec_pm.h" +#include "mtk_vcodec_intr.h" +#include "mtk_vcodec_util.h" + +static const struct of_device_id mtk_vdec_hw_match[] = { + { + .compatible = "mediatek,mtk-vcodec-lat", + .data = (void *)MTK_VDEC_LAT0, + }, + { + .compatible = "mediatek,mtk-vcodec-core", + .data = (void *)MTK_VDEC_CORE, + }, + { + .compatible = "mediatek,mtk-vcodec-lat-soc", + .data = (void *)MTK_VDEC_LAT_SOC, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_vdec_hw_match); + +static int mtk_vdec_hw_prob_done(struct mtk_vcodec_dev *vdec_dev) +{ + struct platform_device *pdev = vdec_dev->plat_dev; + struct device_node *subdev_node; + enum mtk_vdec_hw_id hw_idx; + const struct of_device_id *of_id; + int i; + + for (i = 0; i < ARRAY_SIZE(mtk_vdec_hw_match); i++) { + of_id = &mtk_vdec_hw_match[i]; + subdev_node = of_find_compatible_node(NULL, NULL, + of_id->compatible); + if (!subdev_node) + continue; + + of_node_put(subdev_node); + + hw_idx = (enum mtk_vdec_hw_id)(uintptr_t)of_id->data; + if (!test_bit(hw_idx, vdec_dev->subdev_bitmap)) { + dev_err(&pdev->dev, "vdec %d is not ready", hw_idx); + return -EAGAIN; + } + } + + return 0; +} + +static irqreturn_t mtk_vdec_hw_irq_handler(int irq, void *priv) +{ + struct mtk_vdec_hw_dev *dev = priv; + struct mtk_vcodec_ctx *ctx; + u32 cg_status; + unsigned int dec_done_status; + void __iomem *vdec_misc_addr = dev->reg_base[VDEC_HW_MISC] + + VDEC_IRQ_CFG_REG; + + ctx = mtk_vcodec_get_curr_ctx(dev->main_dev, dev->hw_idx); + + /* check if HW active or not */ + cg_status = readl(dev->reg_base[VDEC_HW_SYS]); + if (cg_status & VDEC_HW_ACTIVE) { + mtk_v4l2_err("vdec active is not 0x0 (0x%08x)", + cg_status); + return IRQ_HANDLED; + } + + dec_done_status = readl(vdec_misc_addr); + if ((dec_done_status & MTK_VDEC_IRQ_STATUS_DEC_SUCCESS) != + MTK_VDEC_IRQ_STATUS_DEC_SUCCESS) + return IRQ_HANDLED; + + /* clear interrupt */ + writel(dec_done_status | VDEC_IRQ_CFG, vdec_misc_addr); + writel(dec_done_status & ~VDEC_IRQ_CLR, vdec_misc_addr); + + wake_up_ctx(ctx, MTK_INST_IRQ_RECEIVED, dev->hw_idx); + + mtk_v4l2_debug(3, "wake up ctx %d, dec_done_status=%x", + ctx->id, dec_done_status); + + return IRQ_HANDLED; +} + +static int mtk_vdec_hw_init_irq(struct mtk_vdec_hw_dev *dev) +{ + struct platform_device *pdev = dev->plat_dev; + int ret; + + dev->dec_irq = platform_get_irq(pdev, 0); + if (dev->dec_irq < 0) + return dev->dec_irq; + + irq_set_status_flags(dev->dec_irq, IRQ_NOAUTOEN); + ret = devm_request_irq(&pdev->dev, dev->dec_irq, + mtk_vdec_hw_irq_handler, 0, pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "Failed to install dev->dec_irq %d (%d)", + dev->dec_irq, ret); + return ret; + } + + return 0; +} + +static int mtk_vdec_hw_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mtk_vdec_hw_dev *subdev_dev; + struct mtk_vcodec_dev *main_dev; + const struct of_device_id *of_id; + int hw_idx; + int ret; + + if (!dev->parent) { + dev_err(dev, "no parent for hardware devices.\n"); + return -ENODEV; + } + + main_dev = dev_get_drvdata(dev->parent); + if (!main_dev) { + dev_err(dev, "failed to get parent driver data"); + return -EINVAL; + } + + subdev_dev = devm_kzalloc(dev, sizeof(*subdev_dev), GFP_KERNEL); + if (!subdev_dev) + return -ENOMEM; + + subdev_dev->plat_dev = pdev; + ret = mtk_vcodec_init_dec_clk(pdev, &subdev_dev->pm); + if (ret) + return ret; + pm_runtime_enable(&pdev->dev); + + of_id = of_match_device(mtk_vdec_hw_match, dev); + if (!of_id) { + dev_err(dev, "Can't get vdec subdev id.\n"); + ret = -EINVAL; + goto err; + } + + hw_idx = (enum mtk_vdec_hw_id)(uintptr_t)of_id->data; + if (hw_idx >= MTK_VDEC_HW_MAX) { + dev_err(dev, "Hardware index %d not correct.\n", hw_idx); + ret = -EINVAL; + goto err; + } + + main_dev->subdev_dev[hw_idx] = subdev_dev; + subdev_dev->hw_idx = hw_idx; + subdev_dev->main_dev = main_dev; + subdev_dev->reg_base[VDEC_HW_SYS] = main_dev->reg_base[VDEC_HW_SYS]; + set_bit(subdev_dev->hw_idx, main_dev->subdev_bitmap); + + if (IS_SUPPORT_VDEC_HW_IRQ(hw_idx)) { + ret = mtk_vdec_hw_init_irq(subdev_dev); + if (ret) + goto err; + } + + subdev_dev->reg_base[VDEC_HW_MISC] = + devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR((__force void *)subdev_dev->reg_base[VDEC_HW_MISC])) { + ret = PTR_ERR((__force void *)subdev_dev->reg_base[VDEC_HW_MISC]); + goto err; + } + + if (!main_dev->subdev_prob_done) + main_dev->subdev_prob_done = mtk_vdec_hw_prob_done; + + platform_set_drvdata(pdev, subdev_dev); + return 0; +err: + pm_runtime_disable(subdev_dev->pm.dev); + return ret; +} + +static int mtk_vdec_hw_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static struct platform_driver mtk_vdec_driver = { + .probe = mtk_vdec_hw_probe, + .remove = mtk_vdec_hw_remove, + .driver = { + .name = "mtk-vdec-comp", + .of_match_table = mtk_vdec_hw_match, + }, +}; +module_platform_driver(mtk_vdec_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Mediatek video decoder hardware driver"); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_hw.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_hw.h new file mode 100644 index 000000000..36faa8d9d --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_hw.h @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 MediaTek Inc. + * Author: Yunfei Dong + */ + +#ifndef _MTK_VCODEC_DEC_HW_H_ +#define _MTK_VCODEC_DEC_HW_H_ + +#include +#include + +#include "mtk_vcodec_drv.h" + +#define VDEC_HW_ACTIVE 0x10 +#define VDEC_IRQ_CFG 0x11 +#define VDEC_IRQ_CLR 0x10 +#define VDEC_IRQ_CFG_REG 0xa4 + +#define IS_SUPPORT_VDEC_HW_IRQ(hw_idx) ((hw_idx) != MTK_VDEC_LAT_SOC) + +/** + * enum mtk_vdec_hw_reg_idx - subdev hardware register base index + * @VDEC_HW_SYS : vdec soc register index + * @VDEC_HW_MISC: vdec misc register index + * @VDEC_HW_MAX : vdec supported max register index + */ +enum mtk_vdec_hw_reg_idx { + VDEC_HW_SYS, + VDEC_HW_MISC, + VDEC_HW_MAX +}; + +/** + * struct mtk_vdec_hw_dev - vdec hardware driver data + * @plat_dev: platform device + * @main_dev: main device + * @reg_base: mapped address of MTK Vcodec registers. + * + * @curr_ctx: the context that is waiting for codec hardware + * + * @dec_irq : decoder irq resource + * @pm : power management control + * @hw_idx : each hardware index + */ +struct mtk_vdec_hw_dev { + struct platform_device *plat_dev; + struct mtk_vcodec_dev *main_dev; + void __iomem *reg_base[VDEC_HW_MAX]; + + struct mtk_vcodec_ctx *curr_ctx; + + int dec_irq; + struct mtk_vcodec_pm pm; + int hw_idx; +}; + +#endif /* _MTK_VCODEC_DEC_HW_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.c new file mode 100644 index 000000000..4305e4eb9 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Tiffany Lin + */ + +#include +#include +#include +#include +#include + +#include "mtk_vcodec_dec_hw.h" +#include "mtk_vcodec_dec_pm.h" +#include "mtk_vcodec_util.h" + +int mtk_vcodec_init_dec_clk(struct platform_device *pdev, struct mtk_vcodec_pm *pm) +{ + struct mtk_vcodec_clk *dec_clk; + struct mtk_vcodec_clk_info *clk_info; + int i = 0, ret; + + dec_clk = &pm->vdec_clk; + pm->dev = &pdev->dev; + + dec_clk->clk_num = + of_property_count_strings(pdev->dev.of_node, "clock-names"); + if (dec_clk->clk_num > 0) { + dec_clk->clk_info = devm_kcalloc(&pdev->dev, + dec_clk->clk_num, sizeof(*clk_info), + GFP_KERNEL); + if (!dec_clk->clk_info) + return -ENOMEM; + } else { + mtk_v4l2_err("Failed to get vdec clock count"); + return -EINVAL; + } + + for (i = 0; i < dec_clk->clk_num; i++) { + clk_info = &dec_clk->clk_info[i]; + ret = of_property_read_string_index(pdev->dev.of_node, + "clock-names", i, &clk_info->clk_name); + if (ret) { + mtk_v4l2_err("Failed to get clock name id = %d", i); + return ret; + } + clk_info->vcodec_clk = devm_clk_get(&pdev->dev, + clk_info->clk_name); + if (IS_ERR(clk_info->vcodec_clk)) { + mtk_v4l2_err("devm_clk_get (%d)%s fail", i, + clk_info->clk_name); + return PTR_ERR(clk_info->vcodec_clk); + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(mtk_vcodec_init_dec_clk); + +static int mtk_vcodec_dec_pw_on(struct mtk_vcodec_pm *pm) +{ + int ret; + + ret = pm_runtime_resume_and_get(pm->dev); + if (ret) + mtk_v4l2_err("pm_runtime_resume_and_get fail %d", ret); + + return ret; +} + +static void mtk_vcodec_dec_pw_off(struct mtk_vcodec_pm *pm) +{ + int ret; + + ret = pm_runtime_put_sync(pm->dev); + if (ret) + mtk_v4l2_err("pm_runtime_put_sync fail %d", ret); +} + +static void mtk_vcodec_dec_clock_on(struct mtk_vcodec_pm *pm) +{ + struct mtk_vcodec_clk *dec_clk; + int ret, i; + + dec_clk = &pm->vdec_clk; + for (i = 0; i < dec_clk->clk_num; i++) { + ret = clk_prepare_enable(dec_clk->clk_info[i].vcodec_clk); + if (ret) { + mtk_v4l2_err("clk_prepare_enable %d %s fail %d", i, + dec_clk->clk_info[i].clk_name, ret); + goto error; + } + } + + return; +error: + for (i -= 1; i >= 0; i--) + clk_disable_unprepare(dec_clk->clk_info[i].vcodec_clk); +} + +static void mtk_vcodec_dec_clock_off(struct mtk_vcodec_pm *pm) +{ + struct mtk_vcodec_clk *dec_clk; + int i; + + dec_clk = &pm->vdec_clk; + for (i = dec_clk->clk_num - 1; i >= 0; i--) + clk_disable_unprepare(dec_clk->clk_info[i].vcodec_clk); +} + +static void mtk_vcodec_dec_enable_irq(struct mtk_vcodec_dev *vdec_dev, int hw_idx) +{ + struct mtk_vdec_hw_dev *subdev_dev; + + if (!test_bit(hw_idx, vdec_dev->subdev_bitmap)) + return; + + if (vdec_dev->vdec_pdata->is_subdev_supported) { + subdev_dev = mtk_vcodec_get_hw_dev(vdec_dev, hw_idx); + if (subdev_dev) + enable_irq(subdev_dev->dec_irq); + else + mtk_v4l2_err("Failed to get hw dev\n"); + } else { + enable_irq(vdec_dev->dec_irq); + } +} + +static void mtk_vcodec_dec_disable_irq(struct mtk_vcodec_dev *vdec_dev, int hw_idx) +{ + struct mtk_vdec_hw_dev *subdev_dev; + + if (!test_bit(hw_idx, vdec_dev->subdev_bitmap)) + return; + + if (vdec_dev->vdec_pdata->is_subdev_supported) { + subdev_dev = mtk_vcodec_get_hw_dev(vdec_dev, hw_idx); + if (subdev_dev) + disable_irq(subdev_dev->dec_irq); + else + mtk_v4l2_err("Failed to get hw dev\n"); + } else { + disable_irq(vdec_dev->dec_irq); + } +} + +static void mtk_vcodec_load_racing_info(struct mtk_vcodec_ctx *ctx) +{ + void __iomem *vdec_racing_addr; + int j; + + mutex_lock(&ctx->dev->dec_racing_info_mutex); + if (atomic_inc_return(&ctx->dev->dec_active_cnt) == 1) { + vdec_racing_addr = ctx->dev->reg_base[VDEC_MISC] + 0x100; + for (j = 0; j < 132; j++) + writel(ctx->dev->vdec_racing_info[j], vdec_racing_addr + j * 4); + } + mutex_unlock(&ctx->dev->dec_racing_info_mutex); +} + +static void mtk_vcodec_record_racing_info(struct mtk_vcodec_ctx *ctx) +{ + void __iomem *vdec_racing_addr; + int j; + + mutex_lock(&ctx->dev->dec_racing_info_mutex); + if (atomic_dec_and_test(&ctx->dev->dec_active_cnt)) { + vdec_racing_addr = ctx->dev->reg_base[VDEC_MISC] + 0x100; + for (j = 0; j < 132; j++) + ctx->dev->vdec_racing_info[j] = readl(vdec_racing_addr + j * 4); + } + mutex_unlock(&ctx->dev->dec_racing_info_mutex); +} + +static struct mtk_vcodec_pm *mtk_vcodec_dec_get_pm(struct mtk_vcodec_dev *vdec_dev, + int hw_idx) +{ + struct mtk_vdec_hw_dev *subdev_dev; + + if (!test_bit(hw_idx, vdec_dev->subdev_bitmap)) + return NULL; + + if (vdec_dev->vdec_pdata->is_subdev_supported) { + subdev_dev = mtk_vcodec_get_hw_dev(vdec_dev, hw_idx); + if (subdev_dev) + return &subdev_dev->pm; + + mtk_v4l2_err("Failed to get hw dev\n"); + return NULL; + } + + return &vdec_dev->pm; +} + +static void mtk_vcodec_dec_child_dev_on(struct mtk_vcodec_dev *vdec_dev, + int hw_idx) +{ + struct mtk_vcodec_pm *pm; + + pm = mtk_vcodec_dec_get_pm(vdec_dev, hw_idx); + if (pm) { + mtk_vcodec_dec_pw_on(pm); + mtk_vcodec_dec_clock_on(pm); + } + + if (hw_idx == MTK_VDEC_LAT0) { + pm = mtk_vcodec_dec_get_pm(vdec_dev, MTK_VDEC_LAT_SOC); + if (pm) { + mtk_vcodec_dec_pw_on(pm); + mtk_vcodec_dec_clock_on(pm); + } + } +} + +static void mtk_vcodec_dec_child_dev_off(struct mtk_vcodec_dev *vdec_dev, + int hw_idx) +{ + struct mtk_vcodec_pm *pm; + + pm = mtk_vcodec_dec_get_pm(vdec_dev, hw_idx); + if (pm) { + mtk_vcodec_dec_clock_off(pm); + mtk_vcodec_dec_pw_off(pm); + } + + if (hw_idx == MTK_VDEC_LAT0) { + pm = mtk_vcodec_dec_get_pm(vdec_dev, MTK_VDEC_LAT_SOC); + if (pm) { + mtk_vcodec_dec_clock_off(pm); + mtk_vcodec_dec_pw_off(pm); + } + } +} + +void mtk_vcodec_dec_enable_hardware(struct mtk_vcodec_ctx *ctx, int hw_idx) +{ + mutex_lock(&ctx->dev->dec_mutex[hw_idx]); + + if (IS_VDEC_LAT_ARCH(ctx->dev->vdec_pdata->hw_arch) && + hw_idx == MTK_VDEC_CORE) + mtk_vcodec_dec_child_dev_on(ctx->dev, MTK_VDEC_LAT0); + mtk_vcodec_dec_child_dev_on(ctx->dev, hw_idx); + + mtk_vcodec_dec_enable_irq(ctx->dev, hw_idx); + + if (IS_VDEC_INNER_RACING(ctx->dev->dec_capability)) + mtk_vcodec_load_racing_info(ctx); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_dec_enable_hardware); + +void mtk_vcodec_dec_disable_hardware(struct mtk_vcodec_ctx *ctx, int hw_idx) +{ + if (IS_VDEC_INNER_RACING(ctx->dev->dec_capability)) + mtk_vcodec_record_racing_info(ctx); + + mtk_vcodec_dec_disable_irq(ctx->dev, hw_idx); + + mtk_vcodec_dec_child_dev_off(ctx->dev, hw_idx); + if (IS_VDEC_LAT_ARCH(ctx->dev->vdec_pdata->hw_arch) && + hw_idx == MTK_VDEC_CORE) + mtk_vcodec_dec_child_dev_off(ctx->dev, MTK_VDEC_LAT0); + + mutex_unlock(&ctx->dev->dec_mutex[hw_idx]); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_dec_disable_hardware); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.h new file mode 100644 index 000000000..dbcf3cabe --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_pm.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Tiffany Lin + */ + +#ifndef _MTK_VCODEC_DEC_PM_H_ +#define _MTK_VCODEC_DEC_PM_H_ + +#include "mtk_vcodec_drv.h" + +int mtk_vcodec_init_dec_clk(struct platform_device *pdev, struct mtk_vcodec_pm *pm); + +void mtk_vcodec_dec_enable_hardware(struct mtk_vcodec_ctx *ctx, int hw_idx); +void mtk_vcodec_dec_disable_hardware(struct mtk_vcodec_ctx *ctx, int hw_idx); + +#endif /* _MTK_VCODEC_DEC_PM_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateful.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateful.c new file mode 100644 index 000000000..0fbd03002 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateful.c @@ -0,0 +1,623 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_dec.h" +#include "mtk_vcodec_intr.h" +#include "mtk_vcodec_util.h" +#include "mtk_vcodec_dec_pm.h" +#include "vdec_drv_if.h" + +static struct mtk_video_fmt mtk_video_formats[] = { + { + .fourcc = V4L2_PIX_FMT_H264, + .type = MTK_FMT_DEC, + .num_planes = 1, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + .frmsize = { MTK_VDEC_MIN_W, MTK_VDEC_MAX_W, 16, + MTK_VDEC_MIN_H, MTK_VDEC_MAX_H, 16 }, + }, + { + .fourcc = V4L2_PIX_FMT_VP8, + .type = MTK_FMT_DEC, + .num_planes = 1, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + .frmsize = { MTK_VDEC_MIN_W, MTK_VDEC_MAX_W, 16, + MTK_VDEC_MIN_H, MTK_VDEC_MAX_H, 16 }, + }, + { + .fourcc = V4L2_PIX_FMT_VP9, + .type = MTK_FMT_DEC, + .num_planes = 1, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + .frmsize = { MTK_VDEC_MIN_W, MTK_VDEC_MAX_W, 16, + MTK_VDEC_MIN_H, MTK_VDEC_MAX_H, 16 }, + }, + { + .fourcc = V4L2_PIX_FMT_MT21C, + .type = MTK_FMT_FRAME, + .num_planes = 2, + }, +}; + +static const unsigned int num_supported_formats = + ARRAY_SIZE(mtk_video_formats); + +#define DEFAULT_OUT_FMT_IDX 0 +#define DEFAULT_CAP_FMT_IDX 3 + +/* + * This function tries to clean all display buffers, the buffers will return + * in display order. + * Note the buffers returned from codec driver may still be in driver's + * reference list. + */ +static struct vb2_buffer *get_display_buffer(struct mtk_vcodec_ctx *ctx) +{ + struct vdec_fb *disp_frame_buffer = NULL; + struct mtk_video_dec_buf *dstbuf; + struct vb2_v4l2_buffer *vb; + + mtk_v4l2_debug(3, "[%d]", ctx->id); + if (vdec_if_get_param(ctx, GET_PARAM_DISP_FRAME_BUFFER, + &disp_frame_buffer)) { + mtk_v4l2_err("[%d]Cannot get param : GET_PARAM_DISP_FRAME_BUFFER", ctx->id); + return NULL; + } + + if (!disp_frame_buffer) { + mtk_v4l2_debug(3, "No display frame buffer"); + return NULL; + } + + dstbuf = container_of(disp_frame_buffer, struct mtk_video_dec_buf, + frame_buffer); + vb = &dstbuf->m2m_buf.vb; + mutex_lock(&ctx->lock); + if (dstbuf->used) { + mtk_v4l2_debug(2, "[%d]status=%x queue id=%d to done_list %d", + ctx->id, disp_frame_buffer->status, + vb->vb2_buf.index, dstbuf->queued_in_vb2); + + v4l2_m2m_buf_done(vb, VB2_BUF_STATE_DONE); + ctx->decoded_frame_cnt++; + } + mutex_unlock(&ctx->lock); + return &vb->vb2_buf; +} + +/* + * This function tries to clean all capture buffers that are not used as + * reference buffers by codec driver any more + * In this case, we need re-queue buffer to vb2 buffer if user space + * already returns this buffer to v4l2 or this buffer is just the output of + * previous sps/pps/resolution change decode, or do nothing if user + * space still owns this buffer + */ +static struct vb2_buffer *get_free_buffer(struct mtk_vcodec_ctx *ctx) +{ + struct mtk_video_dec_buf *dstbuf; + struct vdec_fb *free_frame_buffer = NULL; + struct vb2_v4l2_buffer *vb; + + if (vdec_if_get_param(ctx, GET_PARAM_FREE_FRAME_BUFFER, + &free_frame_buffer)) { + mtk_v4l2_err("[%d] Error!! Cannot get param", ctx->id); + return NULL; + } + if (!free_frame_buffer) { + mtk_v4l2_debug(3, " No free frame buffer"); + return NULL; + } + + mtk_v4l2_debug(3, "[%d] tmp_frame_addr = 0x%p", ctx->id, + free_frame_buffer); + + dstbuf = container_of(free_frame_buffer, struct mtk_video_dec_buf, + frame_buffer); + vb = &dstbuf->m2m_buf.vb; + + mutex_lock(&ctx->lock); + if (dstbuf->used) { + if (dstbuf->queued_in_vb2 && dstbuf->queued_in_v4l2 && + free_frame_buffer->status == FB_ST_FREE) { + /* + * After decode sps/pps or non-display buffer, we don't + * need to return capture buffer to user space, but + * just re-queue this capture buffer to vb2 queue. + * This reduce overheads that dq/q unused capture + * buffer. In this case, queued_in_vb2 = true. + */ + mtk_v4l2_debug(2, "[%d]status=%x queue id=%d to rdy_queue %d", + ctx->id, free_frame_buffer->status, + vb->vb2_buf.index, dstbuf->queued_in_vb2); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); + } else if (!dstbuf->queued_in_vb2 && dstbuf->queued_in_v4l2) { + /* + * If buffer in v4l2 driver but not in vb2 queue yet, + * and we get this buffer from free_list, it means + * that codec driver do not use this buffer as + * reference buffer anymore. We should q buffer to vb2 + * queue, so later work thread could get this buffer + * for decode. In this case, queued_in_vb2 = false + * means this buffer is not from previous decode + * output. + */ + mtk_v4l2_debug(2, + "[%d]status=%x queue id=%d to rdy_queue", + ctx->id, free_frame_buffer->status, + vb->vb2_buf.index); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); + dstbuf->queued_in_vb2 = true; + } else { + /* + * Codec driver do not need to reference this capture + * buffer and this buffer is not in v4l2 driver. + * Then we don't need to do any thing, just add log when + * we need to debug buffer flow. + * When this buffer q from user space, it could + * directly q to vb2 buffer + */ + mtk_v4l2_debug(3, "[%d]status=%x err queue id=%d %d %d", + ctx->id, free_frame_buffer->status, + vb->vb2_buf.index, dstbuf->queued_in_vb2, + dstbuf->queued_in_v4l2); + } + dstbuf->used = false; + } + mutex_unlock(&ctx->lock); + return &vb->vb2_buf; +} + +static void clean_display_buffer(struct mtk_vcodec_ctx *ctx) +{ + while (get_display_buffer(ctx)) + ; +} + +static void clean_free_buffer(struct mtk_vcodec_ctx *ctx) +{ + while (get_free_buffer(ctx)) + ; +} + +static void mtk_vdec_queue_res_chg_event(struct mtk_vcodec_ctx *ctx) +{ + static const struct v4l2_event ev_src_ch = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + mtk_v4l2_debug(1, "[%d]", ctx->id); + v4l2_event_queue_fh(&ctx->fh, &ev_src_ch); +} + +static int mtk_vdec_flush_decoder(struct mtk_vcodec_ctx *ctx) +{ + bool res_chg; + int ret; + + ret = vdec_if_decode(ctx, NULL, NULL, &res_chg); + if (ret) + mtk_v4l2_err("DecodeFinal failed, ret=%d", ret); + + clean_display_buffer(ctx); + clean_free_buffer(ctx); + + return 0; +} + +static void mtk_vdec_update_fmt(struct mtk_vcodec_ctx *ctx, + unsigned int pixelformat) +{ + const struct mtk_video_fmt *fmt; + struct mtk_q_data *dst_q_data; + unsigned int k; + + dst_q_data = &ctx->q_data[MTK_Q_DATA_DST]; + for (k = 0; k < num_supported_formats; k++) { + fmt = &mtk_video_formats[k]; + if (fmt->fourcc == pixelformat) { + mtk_v4l2_debug(1, "Update cap fourcc(%d -> %d)", + dst_q_data->fmt->fourcc, pixelformat); + dst_q_data->fmt = fmt; + return; + } + } + + mtk_v4l2_err("Cannot get fourcc(%d), using init value", pixelformat); +} + +static int mtk_vdec_pic_info_update(struct mtk_vcodec_ctx *ctx) +{ + unsigned int dpbsize = 0; + int ret; + + if (vdec_if_get_param(ctx, GET_PARAM_PIC_INFO, + &ctx->last_decoded_picinfo)) { + mtk_v4l2_err("[%d]Error!! Cannot get param : GET_PARAM_PICTURE_INFO ERR", ctx->id); + return -EINVAL; + } + + if (ctx->last_decoded_picinfo.pic_w == 0 || + ctx->last_decoded_picinfo.pic_h == 0 || + ctx->last_decoded_picinfo.buf_w == 0 || + ctx->last_decoded_picinfo.buf_h == 0) { + mtk_v4l2_err("Cannot get correct pic info"); + return -EINVAL; + } + + if (ctx->last_decoded_picinfo.cap_fourcc != ctx->picinfo.cap_fourcc && + ctx->picinfo.cap_fourcc != 0) + mtk_vdec_update_fmt(ctx, ctx->picinfo.cap_fourcc); + + if (ctx->last_decoded_picinfo.pic_w == ctx->picinfo.pic_w || + ctx->last_decoded_picinfo.pic_h == ctx->picinfo.pic_h) + return 0; + + mtk_v4l2_debug(1, "[%d]-> new(%d,%d), old(%d,%d), real(%d,%d)", ctx->id, + ctx->last_decoded_picinfo.pic_w, + ctx->last_decoded_picinfo.pic_h, ctx->picinfo.pic_w, + ctx->picinfo.pic_h, ctx->last_decoded_picinfo.buf_w, + ctx->last_decoded_picinfo.buf_h); + + ret = vdec_if_get_param(ctx, GET_PARAM_DPB_SIZE, &dpbsize); + if (dpbsize == 0) + mtk_v4l2_err("Incorrect dpb size, ret=%d", ret); + + ctx->dpb_size = dpbsize; + + return ret; +} + +static void mtk_vdec_worker(struct work_struct *work) +{ + struct mtk_vcodec_ctx *ctx = + container_of(work, struct mtk_vcodec_ctx, decode_work); + struct mtk_vcodec_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mtk_vcodec_mem buf; + struct vdec_fb *pfb; + bool res_chg = false; + int ret; + struct mtk_video_dec_buf *dst_buf_info, *src_buf_info; + + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + if (!src_buf) { + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); + mtk_v4l2_debug(1, "[%d] src_buf empty!!", ctx->id); + return; + } + + dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + if (!dst_buf) { + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); + mtk_v4l2_debug(1, "[%d] dst_buf empty!!", ctx->id); + return; + } + + dst_buf_info = + container_of(dst_buf, struct mtk_video_dec_buf, m2m_buf.vb); + + pfb = &dst_buf_info->frame_buffer; + pfb->base_y.va = vb2_plane_vaddr(&dst_buf->vb2_buf, 0); + pfb->base_y.dma_addr = + vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + pfb->base_y.size = ctx->picinfo.fb_sz[0]; + + pfb->base_c.va = vb2_plane_vaddr(&dst_buf->vb2_buf, 1); + pfb->base_c.dma_addr = + vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 1); + pfb->base_c.size = ctx->picinfo.fb_sz[1]; + pfb->status = 0; + mtk_v4l2_debug(3, "===>[%d] vdec_if_decode() ===>", ctx->id); + + mtk_v4l2_debug(3, + "id=%d Framebuf pfb=%p VA=%p Y_DMA=%pad C_DMA=%pad Size=%zx", + dst_buf->vb2_buf.index, pfb, pfb->base_y.va, + &pfb->base_y.dma_addr, &pfb->base_c.dma_addr, pfb->base_y.size); + + if (src_buf == &ctx->empty_flush_buf.vb) { + mtk_v4l2_debug(1, "Got empty flush input buffer."); + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + + /* update dst buf status */ + dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + mutex_lock(&ctx->lock); + dst_buf_info->used = false; + mutex_unlock(&ctx->lock); + + vdec_if_decode(ctx, NULL, NULL, &res_chg); + clean_display_buffer(ctx); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0); + if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 2) + vb2_set_plane_payload(&dst_buf->vb2_buf, 1, 0); + dst_buf->flags |= V4L2_BUF_FLAG_LAST; + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + clean_free_buffer(ctx); + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); + return; + } + + src_buf_info = + container_of(src_buf, struct mtk_video_dec_buf, m2m_buf.vb); + + buf.va = vb2_plane_vaddr(&src_buf->vb2_buf, 0); + buf.dma_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + buf.size = (size_t)src_buf->vb2_buf.planes[0].bytesused; + if (!buf.va) { + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); + mtk_v4l2_err("[%d] id=%d src_addr is NULL!!", ctx->id, + src_buf->vb2_buf.index); + return; + } + mtk_v4l2_debug(3, "[%d] Bitstream VA=%p DMA=%pad Size=%zx vb=%p", + ctx->id, buf.va, &buf.dma_addr, buf.size, src_buf); + dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp; + dst_buf->timecode = src_buf->timecode; + mutex_lock(&ctx->lock); + dst_buf_info->used = true; + mutex_unlock(&ctx->lock); + src_buf_info->used = true; + + ret = vdec_if_decode(ctx, &buf, pfb, &res_chg); + + if (ret) { + mtk_v4l2_err(" <===[%d], src_buf[%d] sz=0x%zx pts=%llu dst_buf[%d] vdec_if_decode() ret=%d res_chg=%d===>", + ctx->id, src_buf->vb2_buf.index, buf.size, + src_buf->vb2_buf.timestamp, dst_buf->vb2_buf.index, ret, res_chg); + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + if (ret == -EIO) { + mutex_lock(&ctx->lock); + src_buf_info->error = true; + mutex_unlock(&ctx->lock); + } + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + } else if (!res_chg) { + /* + * we only return src buffer with VB2_BUF_STATE_DONE + * when decode success without resolution change + */ + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + } + + dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + clean_display_buffer(ctx); + clean_free_buffer(ctx); + + if (!ret && res_chg) { + mtk_vdec_pic_info_update(ctx); + /* + * On encountering a resolution change in the stream. + * The driver must first process and decode all + * remaining buffers from before the resolution change + * point, so call flush decode here + */ + mtk_vdec_flush_decoder(ctx); + /* + * After all buffers containing decoded frames from + * before the resolution change point ready to be + * dequeued on the CAPTURE queue, the driver sends a + * V4L2_EVENT_SOURCE_CHANGE event for source change + * type V4L2_EVENT_SRC_CH_RESOLUTION + */ + mtk_vdec_queue_res_chg_event(ctx); + } + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); +} + +static void vb2ops_vdec_stateful_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *src_buf; + struct mtk_vcodec_mem src_mem; + bool res_chg = false; + int ret; + unsigned int dpbsize = 1, i; + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vb2_v4l2; + struct mtk_q_data *dst_q_data; + + mtk_v4l2_debug(3, "[%d] (%d) id=%d, vb=%p", ctx->id, + vb->vb2_queue->type, vb->index, vb); + /* + * check if this buffer is ready to be used after decode + */ + if (vb->vb2_queue->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + struct mtk_video_dec_buf *buf; + + vb2_v4l2 = to_vb2_v4l2_buffer(vb); + buf = container_of(vb2_v4l2, struct mtk_video_dec_buf, + m2m_buf.vb); + mutex_lock(&ctx->lock); + if (!buf->used) { + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb2_v4l2); + buf->queued_in_vb2 = true; + buf->queued_in_v4l2 = true; + } else { + buf->queued_in_vb2 = false; + buf->queued_in_v4l2 = true; + } + mutex_unlock(&ctx->lock); + return; + } + + v4l2_m2m_buf_queue(ctx->m2m_ctx, to_vb2_v4l2_buffer(vb)); + + if (ctx->state != MTK_STATE_INIT) { + mtk_v4l2_debug(3, "[%d] already init driver %d", ctx->id, + ctx->state); + return; + } + + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + if (!src_buf) { + mtk_v4l2_err("No src buffer"); + return; + } + + if (src_buf == &ctx->empty_flush_buf.vb) { + /* This shouldn't happen. Just in case. */ + mtk_v4l2_err("Invalid flush buffer."); + v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + return; + } + + src_mem.va = vb2_plane_vaddr(&src_buf->vb2_buf, 0); + src_mem.dma_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + src_mem.size = (size_t)src_buf->vb2_buf.planes[0].bytesused; + mtk_v4l2_debug(2, "[%d] buf id=%d va=%p dma=%pad size=%zx", ctx->id, + src_buf->vb2_buf.index, src_mem.va, &src_mem.dma_addr, + src_mem.size); + + ret = vdec_if_decode(ctx, &src_mem, NULL, &res_chg); + if (ret || !res_chg) { + /* + * fb == NULL means to parse SPS/PPS header or + * resolution info in src_mem. Decode can fail + * if there is no SPS header or picture info + * in bs + */ + + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + if (ret == -EIO) { + mtk_v4l2_err("[%d] Unrecoverable error in vdec_if_decode.", ctx->id); + ctx->state = MTK_STATE_ABORT; + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + } else { + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + } + mtk_v4l2_debug(ret ? 0 : 1, + "[%d] vdec_if_decode() src_buf=%d, size=%zu, fail=%d, res_chg=%d", + ctx->id, src_buf->vb2_buf.index, src_mem.size, ret, res_chg); + return; + } + + if (vdec_if_get_param(ctx, GET_PARAM_PIC_INFO, &ctx->picinfo)) { + mtk_v4l2_err("[%d]Error!! Cannot get param : GET_PARAM_PICTURE_INFO ERR", ctx->id); + return; + } + + ctx->last_decoded_picinfo = ctx->picinfo; + dst_q_data = &ctx->q_data[MTK_Q_DATA_DST]; + for (i = 0; i < dst_q_data->fmt->num_planes; i++) { + dst_q_data->sizeimage[i] = ctx->picinfo.fb_sz[i]; + dst_q_data->bytesperline[i] = ctx->picinfo.buf_w; + } + + mtk_v4l2_debug(2, "[%d] vdec_if_init() OK wxh=%dx%d pic wxh=%dx%d sz[0]=0x%x sz[1]=0x%x", + ctx->id, ctx->picinfo.buf_w, ctx->picinfo.buf_h, ctx->picinfo.pic_w, + ctx->picinfo.pic_h, dst_q_data->sizeimage[0], dst_q_data->sizeimage[1]); + + ret = vdec_if_get_param(ctx, GET_PARAM_DPB_SIZE, &dpbsize); + if (dpbsize == 0) + mtk_v4l2_err("[%d] GET_PARAM_DPB_SIZE fail=%d", ctx->id, ret); + + ctx->dpb_size = dpbsize; + ctx->state = MTK_STATE_HEADER; + mtk_v4l2_debug(1, "[%d] dpbsize=%d", ctx->id, ctx->dpb_size); + + mtk_vdec_queue_res_chg_event(ctx); +} + +static int mtk_vdec_g_v_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mtk_vcodec_ctx *ctx = ctrl_to_ctx(ctrl); + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + if (ctx->state >= MTK_STATE_HEADER) { + ctrl->val = ctx->dpb_size; + } else { + mtk_v4l2_debug(0, "Seqinfo not ready"); + ctrl->val = 0; + } + break; + default: + ret = -EINVAL; + } + return ret; +} + +static const struct v4l2_ctrl_ops mtk_vcodec_dec_ctrl_ops = { + .g_volatile_ctrl = mtk_vdec_g_v_ctrl, +}; + +static int mtk_vcodec_dec_ctrls_setup(struct mtk_vcodec_ctx *ctx) +{ + struct v4l2_ctrl *ctrl; + + v4l2_ctrl_handler_init(&ctx->ctrl_hdl, 1); + + ctrl = v4l2_ctrl_new_std(&ctx->ctrl_hdl, &mtk_vcodec_dec_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 0, 32, 1, 1); + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + v4l2_ctrl_new_std_menu(&ctx->ctrl_hdl, &mtk_vcodec_dec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VP9_PROFILE, + V4L2_MPEG_VIDEO_VP9_PROFILE_0, 0, + V4L2_MPEG_VIDEO_VP9_PROFILE_0); + /* + * H264. Baseline / Extended decoding is not supported. + */ + v4l2_ctrl_new_std_menu(&ctx->ctrl_hdl, &mtk_vcodec_dec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED), + V4L2_MPEG_VIDEO_H264_PROFILE_MAIN); + + if (ctx->ctrl_hdl.error) { + mtk_v4l2_err("Adding control failed %d", ctx->ctrl_hdl.error); + return ctx->ctrl_hdl.error; + } + + v4l2_ctrl_handler_setup(&ctx->ctrl_hdl); + return 0; +} + +static void mtk_init_vdec_params(struct mtk_vcodec_ctx *ctx) +{ + unsigned int i; + + if (!(ctx->dev->dec_capability & VCODEC_CAPABILITY_4K_DISABLED)) { + for (i = 0; i < num_supported_formats; i++) { + if (mtk_video_formats[i].type != MTK_FMT_DEC) + continue; + + mtk_video_formats[i].frmsize.max_width = + VCODEC_DEC_4K_CODED_WIDTH; + mtk_video_formats[i].frmsize.max_height = + VCODEC_DEC_4K_CODED_HEIGHT; + } + } +} + +static struct vb2_ops mtk_vdec_frame_vb2_ops = { + .queue_setup = vb2ops_vdec_queue_setup, + .buf_prepare = vb2ops_vdec_buf_prepare, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = vb2ops_vdec_start_streaming, + + .buf_queue = vb2ops_vdec_stateful_buf_queue, + .buf_init = vb2ops_vdec_buf_init, + .buf_finish = vb2ops_vdec_buf_finish, + .stop_streaming = vb2ops_vdec_stop_streaming, +}; + +const struct mtk_vcodec_dec_pdata mtk_vdec_8173_pdata = { + .init_vdec_params = mtk_init_vdec_params, + .ctrls_setup = mtk_vcodec_dec_ctrls_setup, + .vdec_vb2_ops = &mtk_vdec_frame_vb2_ops, + .vdec_formats = mtk_video_formats, + .num_formats = &num_supported_formats, + .default_out_fmt = &mtk_video_formats[DEFAULT_OUT_FMT_IDX], + .default_cap_fmt = &mtk_video_formats[DEFAULT_CAP_FMT_IDX], + .worker = mtk_vdec_worker, + .flush_decoder = mtk_vdec_flush_decoder, + .is_subdev_supported = false, + .hw_arch = MTK_VDEC_PURE_SINGLE_CORE, +}; diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c new file mode 100644 index 000000000..3000db975 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_dec_stateless.c @@ -0,0 +1,513 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_dec.h" +#include "mtk_vcodec_intr.h" +#include "mtk_vcodec_util.h" +#include "mtk_vcodec_dec_pm.h" +#include "vdec_drv_if.h" + +/** + * struct mtk_stateless_control - CID control type + * @cfg: control configuration + * @codec_type: codec type (V4L2 pixel format) for CID control type + */ +struct mtk_stateless_control { + struct v4l2_ctrl_config cfg; + int codec_type; +}; + +static const struct mtk_stateless_control mtk_stateless_controls[] = { + { + .cfg = { + .id = V4L2_CID_STATELESS_H264_SPS, + }, + .codec_type = V4L2_PIX_FMT_H264_SLICE, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_H264_PPS, + }, + .codec_type = V4L2_PIX_FMT_H264_SLICE, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_H264_SCALING_MATRIX, + }, + .codec_type = V4L2_PIX_FMT_H264_SLICE, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, + }, + .codec_type = V4L2_PIX_FMT_H264_SLICE, + }, + { + .cfg = { + .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, + .max = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + .menu_skip_mask = + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED), + }, + .codec_type = V4L2_PIX_FMT_H264_SLICE, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_H264_DECODE_MODE, + .min = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .def = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .max = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + }, + .codec_type = V4L2_PIX_FMT_H264_SLICE, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_H264_START_CODE, + .min = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .def = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .max = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + }, + .codec_type = V4L2_PIX_FMT_H264_SLICE, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_VP8_FRAME, + }, + .codec_type = V4L2_PIX_FMT_VP8_FRAME, + }, + { + .cfg = { + .id = V4L2_CID_MPEG_VIDEO_VP8_PROFILE, + .min = V4L2_MPEG_VIDEO_VP8_PROFILE_0, + .def = V4L2_MPEG_VIDEO_VP8_PROFILE_0, + .max = V4L2_MPEG_VIDEO_VP8_PROFILE_3, + }, + .codec_type = V4L2_PIX_FMT_VP8_FRAME, + }, + { + .cfg = { + .id = V4L2_CID_STATELESS_VP9_FRAME, + }, + .codec_type = V4L2_PIX_FMT_VP9_FRAME, + }, + { + .cfg = { + .id = V4L2_CID_MPEG_VIDEO_VP9_PROFILE, + .min = V4L2_MPEG_VIDEO_VP9_PROFILE_0, + .def = V4L2_MPEG_VIDEO_VP9_PROFILE_0, + .max = V4L2_MPEG_VIDEO_VP9_PROFILE_3, + }, + .codec_type = V4L2_PIX_FMT_VP9_FRAME, + }, +}; + +#define NUM_CTRLS ARRAY_SIZE(mtk_stateless_controls) + +static struct mtk_video_fmt mtk_video_formats[5]; + +static struct mtk_video_fmt default_out_format; +static struct mtk_video_fmt default_cap_format; +static unsigned int num_formats; + +static const struct v4l2_frmsize_stepwise stepwise_fhd = { + .min_width = MTK_VDEC_MIN_W, + .max_width = MTK_VDEC_MAX_W, + .step_width = 16, + .min_height = MTK_VDEC_MIN_H, + .max_height = MTK_VDEC_MAX_H, + .step_height = 16 +}; + +static void mtk_vdec_stateless_cap_to_disp(struct mtk_vcodec_ctx *ctx, int error, + struct media_request *src_buf_req) +{ + struct vb2_v4l2_buffer *vb2_dst; + enum vb2_buffer_state state; + + if (error) + state = VB2_BUF_STATE_ERROR; + else + state = VB2_BUF_STATE_DONE; + + vb2_dst = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + if (vb2_dst) { + v4l2_m2m_buf_done(vb2_dst, state); + mtk_v4l2_debug(2, "free frame buffer id:%d to done list", + vb2_dst->vb2_buf.index); + } else { + mtk_v4l2_err("dst buffer is NULL"); + } + + if (src_buf_req) + v4l2_ctrl_request_complete(src_buf_req, &ctx->ctrl_hdl); +} + +static struct vdec_fb *vdec_get_cap_buffer(struct mtk_vcodec_ctx *ctx) +{ + struct mtk_video_dec_buf *framebuf; + struct vb2_v4l2_buffer *vb2_v4l2; + struct vb2_buffer *dst_buf; + struct vdec_fb *pfb; + + vb2_v4l2 = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + if (!vb2_v4l2) { + mtk_v4l2_debug(1, "[%d] dst_buf empty!!", ctx->id); + return NULL; + } + + dst_buf = &vb2_v4l2->vb2_buf; + framebuf = container_of(vb2_v4l2, struct mtk_video_dec_buf, m2m_buf.vb); + + pfb = &framebuf->frame_buffer; + pfb->base_y.va = vb2_plane_vaddr(dst_buf, 0); + pfb->base_y.dma_addr = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + pfb->base_y.size = ctx->q_data[MTK_Q_DATA_DST].sizeimage[0]; + + if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 2) { + pfb->base_c.va = vb2_plane_vaddr(dst_buf, 1); + pfb->base_c.dma_addr = + vb2_dma_contig_plane_dma_addr(dst_buf, 1); + pfb->base_c.size = ctx->q_data[MTK_Q_DATA_DST].sizeimage[1]; + } + mtk_v4l2_debug(1, "id=%d Framebuf pfb=%p VA=%p Y_DMA=%pad C_DMA=%pad Size=%zx frame_count = %d", + dst_buf->index, pfb, pfb->base_y.va, &pfb->base_y.dma_addr, + &pfb->base_c.dma_addr, pfb->base_y.size, ctx->decoded_frame_cnt); + + return pfb; +} + +static void vb2ops_vdec_buf_request_complete(struct vb2_buffer *vb) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->ctrl_hdl); +} + +static void mtk_vdec_worker(struct work_struct *work) +{ + struct mtk_vcodec_ctx *ctx = + container_of(work, struct mtk_vcodec_ctx, decode_work); + struct mtk_vcodec_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *vb2_v4l2_src; + struct vb2_buffer *vb2_src; + struct mtk_vcodec_mem *bs_src; + struct mtk_video_dec_buf *dec_buf_src; + struct media_request *src_buf_req; + enum vb2_buffer_state state; + bool res_chg = false; + int ret; + + vb2_v4l2_src = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + if (!vb2_v4l2_src) { + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); + mtk_v4l2_debug(1, "[%d] no available source buffer", ctx->id); + return; + } + + vb2_src = &vb2_v4l2_src->vb2_buf; + dec_buf_src = container_of(vb2_v4l2_src, struct mtk_video_dec_buf, + m2m_buf.vb); + bs_src = &dec_buf_src->bs_buffer; + + mtk_v4l2_debug(3, "[%d] (%d) id=%d, vb=%p", ctx->id, + vb2_src->vb2_queue->type, vb2_src->index, vb2_src); + + bs_src->va = vb2_plane_vaddr(vb2_src, 0); + bs_src->dma_addr = vb2_dma_contig_plane_dma_addr(vb2_src, 0); + bs_src->size = (size_t)vb2_src->planes[0].bytesused; + if (!bs_src->va) { + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); + mtk_v4l2_err("[%d] id=%d source buffer is NULL", ctx->id, + vb2_src->index); + return; + } + + mtk_v4l2_debug(3, "[%d] Bitstream VA=%p DMA=%pad Size=%zx vb=%p", + ctx->id, bs_src->va, &bs_src->dma_addr, bs_src->size, vb2_src); + /* Apply request controls. */ + src_buf_req = vb2_src->req_obj.req; + if (src_buf_req) + v4l2_ctrl_request_setup(src_buf_req, &ctx->ctrl_hdl); + else + mtk_v4l2_err("vb2 buffer media request is NULL"); + + ret = vdec_if_decode(ctx, bs_src, NULL, &res_chg); + if (ret) { + mtk_v4l2_err(" <===[%d], src_buf[%d] sz=0x%zx pts=%llu vdec_if_decode() ret=%d res_chg=%d===>", + ctx->id, vb2_src->index, bs_src->size, + vb2_src->timestamp, ret, res_chg); + if (ret == -EIO) { + mutex_lock(&ctx->lock); + dec_buf_src->error = true; + mutex_unlock(&ctx->lock); + } + } + + state = ret ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE; + if (!IS_VDEC_LAT_ARCH(dev->vdec_pdata->hw_arch) || + ctx->current_codec == V4L2_PIX_FMT_VP8_FRAME) { + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx, state); + if (src_buf_req) + v4l2_ctrl_request_complete(src_buf_req, &ctx->ctrl_hdl); + } else { + if (ret != -EAGAIN) { + v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + v4l2_m2m_buf_done(vb2_v4l2_src, state); + } + v4l2_m2m_job_finish(dev->m2m_dev_dec, ctx->m2m_ctx); + } +} + +static void vb2ops_vdec_stateless_buf_queue(struct vb2_buffer *vb) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vb2_v4l2 = to_vb2_v4l2_buffer(vb); + + mtk_v4l2_debug(3, "[%d] (%d) id=%d, vb=%p", ctx->id, vb->vb2_queue->type, vb->index, vb); + + mutex_lock(&ctx->lock); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb2_v4l2); + mutex_unlock(&ctx->lock); + if (vb->vb2_queue->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return; + + /* If an OUTPUT buffer, we may need to update the state */ + if (ctx->state == MTK_STATE_INIT) { + ctx->state = MTK_STATE_HEADER; + mtk_v4l2_debug(1, "Init driver from init to header."); + } else { + mtk_v4l2_debug(3, "[%d] already init driver %d", ctx->id, ctx->state); + } +} + +static int mtk_vdec_flush_decoder(struct mtk_vcodec_ctx *ctx) +{ + bool res_chg; + + return vdec_if_decode(ctx, NULL, NULL, &res_chg); +} + +static int mtk_vcodec_dec_ctrls_setup(struct mtk_vcodec_ctx *ctx) +{ + unsigned int i; + + v4l2_ctrl_handler_init(&ctx->ctrl_hdl, NUM_CTRLS); + if (ctx->ctrl_hdl.error) { + mtk_v4l2_err("v4l2_ctrl_handler_init failed\n"); + return ctx->ctrl_hdl.error; + } + + for (i = 0; i < NUM_CTRLS; i++) { + struct v4l2_ctrl_config cfg = mtk_stateless_controls[i].cfg; + + v4l2_ctrl_new_custom(&ctx->ctrl_hdl, &cfg, NULL); + if (ctx->ctrl_hdl.error) { + mtk_v4l2_err("Adding control %d failed %d", i, ctx->ctrl_hdl.error); + return ctx->ctrl_hdl.error; + } + } + + v4l2_ctrl_handler_setup(&ctx->ctrl_hdl); + + return 0; +} + +static int fops_media_request_validate(struct media_request *mreq) +{ + const unsigned int buffer_cnt = vb2_request_buffer_cnt(mreq); + + switch (buffer_cnt) { + case 1: + /* We expect exactly one buffer with the request */ + break; + case 0: + mtk_v4l2_debug(1, "No buffer provided with the request"); + return -ENOENT; + default: + mtk_v4l2_debug(1, "Too many buffers (%d) provided with the request", + buffer_cnt); + return -EINVAL; + } + + return vb2_request_validate(mreq); +} + +const struct media_device_ops mtk_vcodec_media_ops = { + .req_validate = fops_media_request_validate, + .req_queue = v4l2_m2m_request_queue, +}; + +static void mtk_vcodec_add_formats(unsigned int fourcc, + struct mtk_vcodec_ctx *ctx) +{ + struct mtk_vcodec_dev *dev = ctx->dev; + const struct mtk_vcodec_dec_pdata *pdata = dev->vdec_pdata; + int count_formats = *pdata->num_formats; + + switch (fourcc) { + case V4L2_PIX_FMT_H264_SLICE: + case V4L2_PIX_FMT_VP8_FRAME: + case V4L2_PIX_FMT_VP9_FRAME: + mtk_video_formats[count_formats].fourcc = fourcc; + mtk_video_formats[count_formats].type = MTK_FMT_DEC; + mtk_video_formats[count_formats].num_planes = 1; + mtk_video_formats[count_formats].frmsize = stepwise_fhd; + + if (!(ctx->dev->dec_capability & VCODEC_CAPABILITY_4K_DISABLED) && + fourcc != V4L2_PIX_FMT_VP8_FRAME) { + mtk_video_formats[count_formats].frmsize.max_width = + VCODEC_DEC_4K_CODED_WIDTH; + mtk_video_formats[count_formats].frmsize.max_height = + VCODEC_DEC_4K_CODED_HEIGHT; + } + break; + case V4L2_PIX_FMT_MM21: + case V4L2_PIX_FMT_MT21C: + mtk_video_formats[count_formats].fourcc = fourcc; + mtk_video_formats[count_formats].type = MTK_FMT_FRAME; + mtk_video_formats[count_formats].num_planes = 2; + break; + default: + mtk_v4l2_err("Can not add unsupported format type"); + return; + } + + num_formats++; + mtk_v4l2_debug(3, "num_formats: %d dec_capability: 0x%x", + count_formats, ctx->dev->dec_capability); +} + +static void mtk_vcodec_get_supported_formats(struct mtk_vcodec_ctx *ctx) +{ + int cap_format_count = 0, out_format_count = 0; + + if (num_formats) + return; + + if (ctx->dev->dec_capability & MTK_VDEC_FORMAT_MT21C) { + mtk_vcodec_add_formats(V4L2_PIX_FMT_MT21C, ctx); + cap_format_count++; + } + if (ctx->dev->dec_capability & MTK_VDEC_FORMAT_MM21) { + mtk_vcodec_add_formats(V4L2_PIX_FMT_MM21, ctx); + cap_format_count++; + } + if (ctx->dev->dec_capability & MTK_VDEC_FORMAT_H264_SLICE) { + mtk_vcodec_add_formats(V4L2_PIX_FMT_H264_SLICE, ctx); + out_format_count++; + } + if (ctx->dev->dec_capability & MTK_VDEC_FORMAT_VP8_FRAME) { + mtk_vcodec_add_formats(V4L2_PIX_FMT_VP8_FRAME, ctx); + out_format_count++; + } + if (ctx->dev->dec_capability & MTK_VDEC_FORMAT_VP9_FRAME) { + mtk_vcodec_add_formats(V4L2_PIX_FMT_VP9_FRAME, ctx); + out_format_count++; + } + + if (cap_format_count) + default_cap_format = mtk_video_formats[cap_format_count - 1]; + if (out_format_count) + default_out_format = + mtk_video_formats[cap_format_count + out_format_count - 1]; +} + +static void mtk_init_vdec_params(struct mtk_vcodec_ctx *ctx) +{ + struct vb2_queue *src_vq; + + src_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + if (!ctx->dev->vdec_pdata->is_subdev_supported) + ctx->dev->dec_capability |= + MTK_VDEC_FORMAT_H264_SLICE | MTK_VDEC_FORMAT_MM21; + mtk_vcodec_get_supported_formats(ctx); + + /* Support request api for output plane */ + src_vq->supports_requests = true; + src_vq->requires_requests = true; +} + +static int vb2ops_vdec_out_buf_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static struct vb2_ops mtk_vdec_request_vb2_ops = { + .queue_setup = vb2ops_vdec_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = vb2ops_vdec_start_streaming, + .stop_streaming = vb2ops_vdec_stop_streaming, + + .buf_queue = vb2ops_vdec_stateless_buf_queue, + .buf_out_validate = vb2ops_vdec_out_buf_validate, + .buf_init = vb2ops_vdec_buf_init, + .buf_prepare = vb2ops_vdec_buf_prepare, + .buf_finish = vb2ops_vdec_buf_finish, + .buf_request_complete = vb2ops_vdec_buf_request_complete, +}; + +const struct mtk_vcodec_dec_pdata mtk_vdec_8183_pdata = { + .init_vdec_params = mtk_init_vdec_params, + .ctrls_setup = mtk_vcodec_dec_ctrls_setup, + .vdec_vb2_ops = &mtk_vdec_request_vb2_ops, + .vdec_formats = mtk_video_formats, + .num_formats = &num_formats, + .default_out_fmt = &default_out_format, + .default_cap_fmt = &default_cap_format, + .uses_stateless_api = true, + .worker = mtk_vdec_worker, + .flush_decoder = mtk_vdec_flush_decoder, + .cap_to_disp = mtk_vdec_stateless_cap_to_disp, + .get_cap_buffer = vdec_get_cap_buffer, + .is_subdev_supported = false, + .hw_arch = MTK_VDEC_PURE_SINGLE_CORE, +}; + +/* This platform data is used for one lat and one core architecture. */ +const struct mtk_vcodec_dec_pdata mtk_lat_sig_core_pdata = { + .init_vdec_params = mtk_init_vdec_params, + .ctrls_setup = mtk_vcodec_dec_ctrls_setup, + .vdec_vb2_ops = &mtk_vdec_request_vb2_ops, + .vdec_formats = mtk_video_formats, + .num_formats = &num_formats, + .default_out_fmt = &default_out_format, + .default_cap_fmt = &default_cap_format, + .uses_stateless_api = true, + .worker = mtk_vdec_worker, + .flush_decoder = mtk_vdec_flush_decoder, + .cap_to_disp = mtk_vdec_stateless_cap_to_disp, + .get_cap_buffer = vdec_get_cap_buffer, + .is_subdev_supported = true, + .hw_arch = MTK_VDEC_LAT_SINGLE_CORE, +}; + +const struct mtk_vcodec_dec_pdata mtk_vdec_single_core_pdata = { + .init_vdec_params = mtk_init_vdec_params, + .ctrls_setup = mtk_vcodec_dec_ctrls_setup, + .vdec_vb2_ops = &mtk_vdec_request_vb2_ops, + .vdec_formats = mtk_video_formats, + .num_formats = &num_formats, + .default_out_fmt = &default_out_format, + .default_cap_fmt = &default_cap_format, + .uses_stateless_api = true, + .worker = mtk_vdec_worker, + .flush_decoder = mtk_vdec_flush_decoder, + .cap_to_disp = mtk_vdec_stateless_cap_to_disp, + .get_cap_buffer = vdec_get_cap_buffer, + .is_subdev_supported = true, + .hw_arch = MTK_VDEC_PURE_SINGLE_CORE, +}; diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_drv.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_drv.h new file mode 100644 index 000000000..9acab54fd --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_drv.h @@ -0,0 +1,544 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: PC Chen +* Tiffany Lin +*/ + +#ifndef _MTK_VCODEC_DRV_H_ +#define _MTK_VCODEC_DRV_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_vcodec_util.h" +#include "vdec_msg_queue.h" + +#define MTK_VCODEC_DEC_NAME "mtk-vcodec-dec" +#define MTK_VCODEC_ENC_NAME "mtk-vcodec-enc" + +#define MTK_VCODEC_MAX_PLANES 3 +#define MTK_V4L2_BENCHMARK 0 +#define WAIT_INTR_TIMEOUT_MS 1000 +#define IS_VDEC_LAT_ARCH(hw_arch) ((hw_arch) >= MTK_VDEC_LAT_SINGLE_CORE) +#define IS_VDEC_INNER_RACING(capability) ((capability) & MTK_VCODEC_INNER_RACING) + +/* + * enum mtk_hw_reg_idx - MTK hw register base index + */ +enum mtk_hw_reg_idx { + VDEC_SYS, + VDEC_MISC, + VDEC_LD, + VDEC_TOP, + VDEC_CM, + VDEC_AD, + VDEC_AV, + VDEC_PP, + VDEC_HWD, + VDEC_HWQ, + VDEC_HWB, + VDEC_HWG, + NUM_MAX_VDEC_REG_BASE, + /* h264 encoder */ + VENC_SYS = NUM_MAX_VDEC_REG_BASE, + /* vp8 encoder */ + VENC_LT_SYS, + NUM_MAX_VCODEC_REG_BASE +}; + +/* + * enum mtk_instance_type - The type of an MTK Vcodec instance. + */ +enum mtk_instance_type { + MTK_INST_DECODER = 0, + MTK_INST_ENCODER = 1, +}; + +/** + * enum mtk_instance_state - The state of an MTK Vcodec instance. + * @MTK_STATE_FREE: default state when instance is created + * @MTK_STATE_INIT: vcodec instance is initialized + * @MTK_STATE_HEADER: vdec had sps/pps header parsed or venc + * had sps/pps header encoded + * @MTK_STATE_FLUSH: vdec is flushing. Only used by decoder + * @MTK_STATE_ABORT: vcodec should be aborted + */ +enum mtk_instance_state { + MTK_STATE_FREE = 0, + MTK_STATE_INIT = 1, + MTK_STATE_HEADER = 2, + MTK_STATE_FLUSH = 3, + MTK_STATE_ABORT = 4, +}; + +/* + * enum mtk_encode_param - General encoding parameters type + */ +enum mtk_encode_param { + MTK_ENCODE_PARAM_NONE = 0, + MTK_ENCODE_PARAM_BITRATE = (1 << 0), + MTK_ENCODE_PARAM_FRAMERATE = (1 << 1), + MTK_ENCODE_PARAM_INTRA_PERIOD = (1 << 2), + MTK_ENCODE_PARAM_FORCE_INTRA = (1 << 3), + MTK_ENCODE_PARAM_GOP_SIZE = (1 << 4), +}; + +enum mtk_fmt_type { + MTK_FMT_DEC = 0, + MTK_FMT_ENC = 1, + MTK_FMT_FRAME = 2, +}; + +/* + * enum mtk_vdec_hw_id - Hardware index used to separate + * different hardware + */ +enum mtk_vdec_hw_id { + MTK_VDEC_CORE, + MTK_VDEC_LAT0, + MTK_VDEC_LAT1, + MTK_VDEC_LAT_SOC, + MTK_VDEC_HW_MAX, +}; + +/* + * enum mtk_vdec_hw_count - Supported hardware count + */ +enum mtk_vdec_hw_count { + MTK_VDEC_NO_HW = 0, + MTK_VDEC_ONE_CORE, + MTK_VDEC_ONE_LAT_ONE_CORE, + MTK_VDEC_MAX_HW_COUNT, +}; + +/* + * struct mtk_video_fmt - Structure used to store information about pixelformats + */ +struct mtk_video_fmt { + u32 fourcc; + enum mtk_fmt_type type; + u32 num_planes; + u32 flags; + struct v4l2_frmsize_stepwise frmsize; +}; + +/* + * enum mtk_q_type - Type of queue + */ +enum mtk_q_type { + MTK_Q_DATA_SRC = 0, + MTK_Q_DATA_DST = 1, +}; + +/* + * struct mtk_q_data - Structure used to store information about queue + */ +struct mtk_q_data { + unsigned int visible_width; + unsigned int visible_height; + unsigned int coded_width; + unsigned int coded_height; + enum v4l2_field field; + unsigned int bytesperline[MTK_VCODEC_MAX_PLANES]; + unsigned int sizeimage[MTK_VCODEC_MAX_PLANES]; + const struct mtk_video_fmt *fmt; +}; + +/** + * struct mtk_enc_params - General encoding parameters + * @bitrate: target bitrate in bits per second + * @num_b_frame: number of b frames between p-frame + * @rc_frame: frame based rate control + * @rc_mb: macroblock based rate control + * @seq_hdr_mode: H.264 sequence header is encoded separately or joined + * with the first frame + * @intra_period: I frame period + * @gop_size: group of picture size, it's used as the intra frame period + * @framerate_num: frame rate numerator. ex: framerate_num=30 and + * framerate_denom=1 means FPS is 30 + * @framerate_denom: frame rate denominator. ex: framerate_num=30 and + * framerate_denom=1 means FPS is 30 + * @h264_max_qp: Max value for H.264 quantization parameter + * @h264_profile: V4L2 defined H.264 profile + * @h264_level: V4L2 defined H.264 level + * @force_intra: force/insert intra frame + */ +struct mtk_enc_params { + unsigned int bitrate; + unsigned int num_b_frame; + unsigned int rc_frame; + unsigned int rc_mb; + unsigned int seq_hdr_mode; + unsigned int intra_period; + unsigned int gop_size; + unsigned int framerate_num; + unsigned int framerate_denom; + unsigned int h264_max_qp; + unsigned int h264_profile; + unsigned int h264_level; + unsigned int force_intra; +}; + +/* + * struct mtk_vcodec_clk_info - Structure used to store clock name + */ +struct mtk_vcodec_clk_info { + const char *clk_name; + struct clk *vcodec_clk; +}; + +/* + * struct mtk_vcodec_clk - Structure used to store vcodec clock information + */ +struct mtk_vcodec_clk { + struct mtk_vcodec_clk_info *clk_info; + int clk_num; +}; + +/* + * struct mtk_vcodec_pm - Power management data structure + */ +struct mtk_vcodec_pm { + struct mtk_vcodec_clk vdec_clk; + struct mtk_vcodec_clk venc_clk; + struct device *dev; +}; + +/** + * struct vdec_pic_info - picture size information + * @pic_w: picture width + * @pic_h: picture height + * @buf_w: picture buffer width (64 aligned up from pic_w) + * @buf_h: picture buffer heiht (64 aligned up from pic_h) + * @fb_sz: bitstream size of each plane + * E.g. suppose picture size is 176x144, + * buffer size will be aligned to 176x160. + * @cap_fourcc: fourcc number(may changed when resolution change) + * @reserved: align struct to 64-bit in order to adjust 32-bit and 64-bit os. + */ +struct vdec_pic_info { + unsigned int pic_w; + unsigned int pic_h; + unsigned int buf_w; + unsigned int buf_h; + unsigned int fb_sz[VIDEO_MAX_PLANES]; + unsigned int cap_fourcc; + unsigned int reserved; +}; + +/** + * struct mtk_vcodec_ctx - Context (instance) private data. + * + * @type: type of the instance - decoder or encoder + * @dev: pointer to the mtk_vcodec_dev of the device + * @list: link to ctx_list of mtk_vcodec_dev + * @fh: struct v4l2_fh + * @m2m_ctx: pointer to the v4l2_m2m_ctx of the context + * @q_data: store information of input and output queue + * of the context + * @id: index of the context that this structure describes + * @state: state of the context + * @param_change: indicate encode parameter type + * @enc_params: encoding parameters + * @dec_if: hooked decoder driver interface + * @enc_if: hooked encoder driver interface + * @drv_handle: driver handle for specific decode/encode instance + * + * @picinfo: store picture info after header parsing + * @dpb_size: store dpb count after header parsing + * @int_cond: variable used by the waitqueue + * @int_type: type of the last interrupt + * @queue: waitqueue that can be used to wait for this context to + * finish + * @irq_status: irq status + * + * @ctrl_hdl: handler for v4l2 framework + * @decode_work: worker for the decoding + * @encode_work: worker for the encoding + * @last_decoded_picinfo: pic information get from latest decode + * @empty_flush_buf: a fake size-0 capture buffer that indicates flush. Only + * to be used with encoder and stateful decoder. + * @is_flushing: set to true if flushing is in progress. + * @current_codec: current set input codec, in V4L2 pixel format + * @capture_fourcc: capture queue type in V4L2 pixel format + * + * @colorspace: enum v4l2_colorspace; supplemental to pixelformat + * @ycbcr_enc: enum v4l2_ycbcr_encoding, Y'CbCr encoding + * @quantization: enum v4l2_quantization, colorspace quantization + * @xfer_func: enum v4l2_xfer_func, colorspace transfer function + * @decoded_frame_cnt: number of decoded frames + * @lock: protect variables accessed by V4L2 threads and worker thread such as + * mtk_video_dec_buf. + * @hw_id: hardware index used to identify different hardware. + * + * @msg_queue: msg queue used to store lat buffer information. + * @q_mutex: vb2_queue mutex. + */ +struct mtk_vcodec_ctx { + enum mtk_instance_type type; + struct mtk_vcodec_dev *dev; + struct list_head list; + + struct v4l2_fh fh; + struct v4l2_m2m_ctx *m2m_ctx; + struct mtk_q_data q_data[2]; + int id; + enum mtk_instance_state state; + enum mtk_encode_param param_change; + struct mtk_enc_params enc_params; + + const struct vdec_common_if *dec_if; + const struct venc_common_if *enc_if; + void *drv_handle; + + struct vdec_pic_info picinfo; + int dpb_size; + + int int_cond[MTK_VDEC_HW_MAX]; + int int_type[MTK_VDEC_HW_MAX]; + wait_queue_head_t queue[MTK_VDEC_HW_MAX]; + unsigned int irq_status; + + struct v4l2_ctrl_handler ctrl_hdl; + struct work_struct decode_work; + struct work_struct encode_work; + struct vdec_pic_info last_decoded_picinfo; + struct v4l2_m2m_buffer empty_flush_buf; + bool is_flushing; + + u32 current_codec; + u32 capture_fourcc; + + enum v4l2_colorspace colorspace; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + enum v4l2_xfer_func xfer_func; + + int decoded_frame_cnt; + struct mutex lock; + int hw_id; + + struct vdec_msg_queue msg_queue; + + struct mutex q_mutex; +}; + +/* + * enum mtk_vdec_hw_arch - Used to separate different hardware architecture + */ +enum mtk_vdec_hw_arch { + MTK_VDEC_PURE_SINGLE_CORE, + MTK_VDEC_LAT_SINGLE_CORE, +}; + +/* + * struct mtk_vdec_format_types - Structure used to get supported + * format types according to decoder capability + */ +enum mtk_vdec_format_types { + MTK_VDEC_FORMAT_MM21 = 0x20, + MTK_VDEC_FORMAT_MT21C = 0x40, + MTK_VDEC_FORMAT_H264_SLICE = 0x100, + MTK_VDEC_FORMAT_VP8_FRAME = 0x200, + MTK_VDEC_FORMAT_VP9_FRAME = 0x400, + MTK_VCODEC_INNER_RACING = 0x20000, +}; + +/** + * struct mtk_vcodec_dec_pdata - compatible data for each IC + * @init_vdec_params: init vdec params + * @ctrls_setup: init vcodec dec ctrls + * @worker: worker to start a decode job + * @flush_decoder: function that flushes the decoder + * @get_cap_buffer: get capture buffer from capture queue + * @cap_to_disp: put capture buffer to disp list for lat and core arch + * @vdec_vb2_ops: struct vb2_ops + * + * @vdec_formats: supported video decoder formats + * @num_formats: count of video decoder formats + * @default_out_fmt: default output buffer format + * @default_cap_fmt: default capture buffer format + * + * @hw_arch: hardware arch is used to separate pure_sin_core and lat_sin_core + * + * @is_subdev_supported: whether support parent-node architecture(subdev) + * @uses_stateless_api: whether the decoder uses the stateless API with requests + */ + +struct mtk_vcodec_dec_pdata { + void (*init_vdec_params)(struct mtk_vcodec_ctx *ctx); + int (*ctrls_setup)(struct mtk_vcodec_ctx *ctx); + void (*worker)(struct work_struct *work); + int (*flush_decoder)(struct mtk_vcodec_ctx *ctx); + struct vdec_fb *(*get_cap_buffer)(struct mtk_vcodec_ctx *ctx); + void (*cap_to_disp)(struct mtk_vcodec_ctx *ctx, int error, + struct media_request *src_buf_req); + + struct vb2_ops *vdec_vb2_ops; + + const struct mtk_video_fmt *vdec_formats; + const int *num_formats; + const struct mtk_video_fmt *default_out_fmt; + const struct mtk_video_fmt *default_cap_fmt; + + enum mtk_vdec_hw_arch hw_arch; + + bool is_subdev_supported; + bool uses_stateless_api; +}; + +/** + * struct mtk_vcodec_enc_pdata - compatible data for each IC + * + * @uses_ext: whether the encoder uses the extended firmware messaging format + * @min_bitrate: minimum supported encoding bitrate + * @max_bitrate: maximum supported encoding bitrate + * @capture_formats: array of supported capture formats + * @num_capture_formats: number of entries in capture_formats + * @output_formats: array of supported output formats + * @num_output_formats: number of entries in output_formats + * @core_id: stand for h264 or vp8 encode index + * @uses_34bit: whether the encoder uses 34-bit iova + */ +struct mtk_vcodec_enc_pdata { + bool uses_ext; + unsigned long min_bitrate; + unsigned long max_bitrate; + const struct mtk_video_fmt *capture_formats; + size_t num_capture_formats; + const struct mtk_video_fmt *output_formats; + size_t num_output_formats; + int core_id; + bool uses_34bit; +}; + +#define MTK_ENC_CTX_IS_EXT(ctx) ((ctx)->dev->venc_pdata->uses_ext) +#define MTK_ENC_IOVA_IS_34BIT(ctx) ((ctx)->dev->venc_pdata->uses_34bit) + +/** + * struct mtk_vcodec_dev - driver data + * @v4l2_dev: V4L2 device to register video devices for. + * @vfd_dec: Video device for decoder + * @mdev_dec: Media device for decoder + * @vfd_enc: Video device for encoder. + * + * @m2m_dev_dec: m2m device for decoder + * @m2m_dev_enc: m2m device for encoder. + * @plat_dev: platform device + * @ctx_list: list of struct mtk_vcodec_ctx + * @irqlock: protect data access by irq handler and work thread + * @curr_ctx: The context that is waiting for codec hardware + * + * @reg_base: Mapped address of MTK Vcodec registers. + * @vdec_pdata: decoder IC-specific data + * @venc_pdata: encoder IC-specific data + * + * @fw_handler: used to communicate with the firmware. + * @id_counter: used to identify current opened instance + * + * @decode_workqueue: decode work queue + * @encode_workqueue: encode work queue + * + * @int_cond: used to identify interrupt condition happen + * @int_type: used to identify what kind of interrupt condition happen + * @dev_mutex: video_device lock + * @queue: waitqueue for waiting for completion of device commands + * + * @dec_irq: decoder irq resource + * @enc_irq: h264 encoder irq resource + * + * @dec_mutex: decoder hardware lock + * @enc_mutex: encoder hardware lock. + * + * @pm: power management control + * @dec_capability: used to identify decode capability, ex: 4k + * @enc_capability: used to identify encode capability + * + * @core_workqueue: queue used for core hardware decode + * @msg_queue_core_ctx: msg queue context used for core workqueue + * + * @subdev_dev: subdev hardware device + * @subdev_prob_done: check whether all used hw device is prob done + * @subdev_bitmap: used to record hardware is ready or not + * + * @dec_active_cnt: used to mark whether need to record register value + * @vdec_racing_info: record register value + * @dec_racing_info_mutex: mutex lock used for inner racing mode + */ +struct mtk_vcodec_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd_dec; + struct media_device mdev_dec; + struct video_device *vfd_enc; + + struct v4l2_m2m_dev *m2m_dev_dec; + struct v4l2_m2m_dev *m2m_dev_enc; + struct platform_device *plat_dev; + struct list_head ctx_list; + spinlock_t irqlock; + struct mtk_vcodec_ctx *curr_ctx; + void __iomem *reg_base[NUM_MAX_VCODEC_REG_BASE]; + const struct mtk_vcodec_dec_pdata *vdec_pdata; + const struct mtk_vcodec_enc_pdata *venc_pdata; + + struct mtk_vcodec_fw *fw_handler; + + unsigned long id_counter; + + struct workqueue_struct *decode_workqueue; + struct workqueue_struct *encode_workqueue; + int int_cond; + int int_type; + struct mutex dev_mutex; + wait_queue_head_t queue; + + int dec_irq; + int enc_irq; + + /* decoder hardware mutex lock */ + struct mutex dec_mutex[MTK_VDEC_HW_MAX]; + struct mutex enc_mutex; + + struct mtk_vcodec_pm pm; + unsigned int dec_capability; + unsigned int enc_capability; + + struct workqueue_struct *core_workqueue; + struct vdec_msg_queue_ctx msg_queue_core_ctx; + + void *subdev_dev[MTK_VDEC_HW_MAX]; + int (*subdev_prob_done)(struct mtk_vcodec_dev *vdec_dev); + DECLARE_BITMAP(subdev_bitmap, MTK_VDEC_HW_MAX); + + atomic_t dec_active_cnt; + u32 vdec_racing_info[132]; + /* Protects access to vdec_racing_info data */ + struct mutex dec_racing_info_mutex; +}; + +static inline struct mtk_vcodec_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct mtk_vcodec_ctx, fh); +} + +static inline struct mtk_vcodec_ctx *ctrl_to_ctx(struct v4l2_ctrl *ctrl) +{ + return container_of(ctrl->handler, struct mtk_vcodec_ctx, ctrl_hdl); +} + +/* Wake up context wait_queue */ +static inline void +wake_up_ctx(struct mtk_vcodec_ctx *ctx, unsigned int reason, unsigned int hw_id) +{ + ctx->int_cond[hw_id] = 1; + ctx->int_type[hw_id] = reason; + wake_up_interruptible(&ctx->queue[hw_id]); +} + +#endif /* _MTK_VCODEC_DRV_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c new file mode 100644 index 000000000..31e3c3766 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.c @@ -0,0 +1,1487 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: PC Chen +* Tiffany Lin +*/ + +#include +#include +#include +#include + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_enc.h" +#include "mtk_vcodec_intr.h" +#include "mtk_vcodec_util.h" +#include "venc_drv_if.h" + +#define MTK_VENC_MIN_W 160U +#define MTK_VENC_MIN_H 128U +#define MTK_VENC_HD_MAX_W 1920U +#define MTK_VENC_HD_MAX_H 1088U +#define MTK_VENC_4K_MAX_W 3840U +#define MTK_VENC_4K_MAX_H 2176U + +#define DFT_CFG_WIDTH MTK_VENC_MIN_W +#define DFT_CFG_HEIGHT MTK_VENC_MIN_H +#define MTK_MAX_CTRLS_HINT 20 + +#define MTK_DEFAULT_FRAMERATE_NUM 1001 +#define MTK_DEFAULT_FRAMERATE_DENOM 30000 +#define MTK_VENC_4K_CAPABILITY_ENABLE BIT(0) + +static void mtk_venc_worker(struct work_struct *work); + +static const struct v4l2_frmsize_stepwise mtk_venc_hd_framesizes = { + MTK_VENC_MIN_W, MTK_VENC_HD_MAX_W, 16, + MTK_VENC_MIN_H, MTK_VENC_HD_MAX_H, 16, +}; + +static const struct v4l2_frmsize_stepwise mtk_venc_4k_framesizes = { + MTK_VENC_MIN_W, MTK_VENC_4K_MAX_W, 16, + MTK_VENC_MIN_H, MTK_VENC_4K_MAX_H, 16, +}; + +static int vidioc_venc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mtk_vcodec_ctx *ctx = ctrl_to_ctx(ctrl); + struct mtk_enc_params *p = &ctx->enc_params; + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_BITRATE_MODE val= %d", + ctrl->val); + if (ctrl->val != V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) { + mtk_v4l2_err("Unsupported bitrate mode =%d", ctrl->val); + ret = -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_BITRATE val = %d", + ctrl->val); + p->bitrate = ctrl->val; + ctx->param_change |= MTK_ENCODE_PARAM_BITRATE; + break; + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_B_FRAMES val = %d", + ctrl->val); + p->num_b_frame = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE val = %d", + ctrl->val); + p->rc_frame = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_H264_MAX_QP val = %d", + ctrl->val); + p->h264_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_HEADER_MODE val = %d", + ctrl->val); + p->seq_hdr_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE val = %d", + ctrl->val); + p->rc_mb = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_H264_PROFILE val = %d", + ctrl->val); + p->h264_profile = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_H264_LEVEL val = %d", + ctrl->val); + p->h264_level = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_H264_I_PERIOD val = %d", + ctrl->val); + p->intra_period = ctrl->val; + ctx->param_change |= MTK_ENCODE_PARAM_INTRA_PERIOD; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_GOP_SIZE val = %d", + ctrl->val); + p->gop_size = ctrl->val; + ctx->param_change |= MTK_ENCODE_PARAM_GOP_SIZE; + break; + case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: + /* + * FIXME - what vp8 profiles are actually supported? + * The ctrl is added (with only profile 0 supported) for now. + */ + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_VP8_PROFILE val = %d", ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: + mtk_v4l2_debug(2, "V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME"); + p->force_intra = 1; + ctx->param_change |= MTK_ENCODE_PARAM_FORCE_INTRA; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops mtk_vcodec_enc_ctrl_ops = { + .s_ctrl = vidioc_venc_s_ctrl, +}; + +static int vidioc_enum_fmt(struct v4l2_fmtdesc *f, + const struct mtk_video_fmt *formats, + size_t num_formats) +{ + if (f->index >= num_formats) + return -EINVAL; + + f->pixelformat = formats[f->index].fourcc; + + return 0; +} + +static const struct mtk_video_fmt * +mtk_venc_find_format(u32 fourcc, const struct mtk_vcodec_enc_pdata *pdata) +{ + const struct mtk_video_fmt *fmt; + unsigned int k; + + for (k = 0; k < pdata->num_capture_formats; k++) { + fmt = &pdata->capture_formats[k]; + if (fmt->fourcc == fourcc) + return fmt; + } + + for (k = 0; k < pdata->num_output_formats; k++) { + fmt = &pdata->output_formats[k]; + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +static int vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + const struct mtk_video_fmt *fmt; + struct mtk_vcodec_ctx *ctx = fh_to_ctx(fh); + + if (fsize->index != 0) + return -EINVAL; + + fmt = mtk_venc_find_format(fsize->pixel_format, + ctx->dev->venc_pdata); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + if (ctx->dev->enc_capability & MTK_VENC_4K_CAPABILITY_ENABLE) + fsize->stepwise = mtk_venc_4k_framesizes; + else + fsize->stepwise = mtk_venc_hd_framesizes; + + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct mtk_vcodec_enc_pdata *pdata = + fh_to_ctx(priv)->dev->venc_pdata; + + return vidioc_enum_fmt(f, pdata->capture_formats, + pdata->num_capture_formats); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct mtk_vcodec_enc_pdata *pdata = + fh_to_ctx(priv)->dev->venc_pdata; + + return vidioc_enum_fmt(f, pdata->output_formats, + pdata->num_output_formats); +} + +static int mtk_vcodec_enc_get_chip_name(void *priv) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct device *dev = &ctx->dev->plat_dev->dev; + + if (of_device_is_compatible(dev->of_node, "mediatek,mt8173-vcodec-enc")) + return 8173; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8183-vcodec-enc")) + return 8183; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8192-vcodec-enc")) + return 8192; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8195-vcodec-enc")) + return 8195; + else if (of_device_is_compatible(dev->of_node, "mediatek,mt8188-vcodec-enc")) + return 8188; + else + return 8173; +} + +static int vidioc_venc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct device *dev = &ctx->dev->plat_dev->dev; + int platform_name = mtk_vcodec_enc_get_chip_name(priv); + + strscpy(cap->driver, dev->driver->name, sizeof(cap->driver)); + snprintf(cap->card, sizeof(cap->card), "MT%d video encoder", platform_name); + + return 0; +} + +static int vidioc_venc_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *a) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct v4l2_fract *timeperframe = &a->parm.output.timeperframe; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + if (timeperframe->numerator == 0 || timeperframe->denominator == 0) { + timeperframe->numerator = MTK_DEFAULT_FRAMERATE_NUM; + timeperframe->denominator = MTK_DEFAULT_FRAMERATE_DENOM; + } + + ctx->enc_params.framerate_num = timeperframe->denominator; + ctx->enc_params.framerate_denom = timeperframe->numerator; + ctx->param_change |= MTK_ENCODE_PARAM_FRAMERATE; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + + return 0; +} + +static int vidioc_venc_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *a) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + a->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + a->parm.output.timeperframe.denominator = + ctx->enc_params.framerate_num; + a->parm.output.timeperframe.numerator = + ctx->enc_params.framerate_denom; + + return 0; +} + +static struct mtk_q_data *mtk_venc_get_q_data(struct mtk_vcodec_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->q_data[MTK_Q_DATA_SRC]; + + return &ctx->q_data[MTK_Q_DATA_DST]; +} + +static void vidioc_try_fmt_cap(struct v4l2_format *f) +{ + f->fmt.pix_mp.field = V4L2_FIELD_NONE; + f->fmt.pix_mp.num_planes = 1; + f->fmt.pix_mp.plane_fmt[0].bytesperline = 0; + f->fmt.pix_mp.flags = 0; +} + +/* V4L2 specification suggests the driver corrects the format struct if any of + * the dimensions is unsupported + */ +static int vidioc_try_fmt_out(struct mtk_vcodec_ctx *ctx, struct v4l2_format *f, + const struct mtk_video_fmt *fmt) +{ + struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp; + int tmp_w, tmp_h; + unsigned int max_width, max_height; + + pix_fmt_mp->field = V4L2_FIELD_NONE; + + if (ctx->dev->enc_capability & MTK_VENC_4K_CAPABILITY_ENABLE) { + max_width = MTK_VENC_4K_MAX_W; + max_height = MTK_VENC_4K_MAX_H; + } else { + max_width = MTK_VENC_HD_MAX_W; + max_height = MTK_VENC_HD_MAX_H; + } + + pix_fmt_mp->height = clamp(pix_fmt_mp->height, MTK_VENC_MIN_H, max_height); + pix_fmt_mp->width = clamp(pix_fmt_mp->width, MTK_VENC_MIN_W, max_width); + + /* find next closer width align 16, heign align 32, size align + * 64 rectangle + */ + tmp_w = pix_fmt_mp->width; + tmp_h = pix_fmt_mp->height; + v4l_bound_align_image(&pix_fmt_mp->width, + MTK_VENC_MIN_W, + max_width, 4, + &pix_fmt_mp->height, + MTK_VENC_MIN_H, + max_height, 5, 6); + + if (pix_fmt_mp->width < tmp_w && (pix_fmt_mp->width + 16) <= max_width) + pix_fmt_mp->width += 16; + if (pix_fmt_mp->height < tmp_h && (pix_fmt_mp->height + 32) <= max_height) + pix_fmt_mp->height += 32; + + mtk_v4l2_debug(0, "before resize w=%d, h=%d, after resize w=%d, h=%d, sizeimage=%d %d", + tmp_w, tmp_h, pix_fmt_mp->width, + pix_fmt_mp->height, + pix_fmt_mp->plane_fmt[0].sizeimage, + pix_fmt_mp->plane_fmt[1].sizeimage); + + pix_fmt_mp->num_planes = fmt->num_planes; + pix_fmt_mp->plane_fmt[0].sizeimage = + pix_fmt_mp->width * pix_fmt_mp->height + + ((ALIGN(pix_fmt_mp->width, 16) * 2) * 16); + pix_fmt_mp->plane_fmt[0].bytesperline = pix_fmt_mp->width; + + if (pix_fmt_mp->num_planes == 2) { + pix_fmt_mp->plane_fmt[1].sizeimage = + (pix_fmt_mp->width * pix_fmt_mp->height) / 2 + + (ALIGN(pix_fmt_mp->width, 16) * 16); + pix_fmt_mp->plane_fmt[2].sizeimage = 0; + pix_fmt_mp->plane_fmt[1].bytesperline = + pix_fmt_mp->width; + pix_fmt_mp->plane_fmt[2].bytesperline = 0; + } else if (pix_fmt_mp->num_planes == 3) { + pix_fmt_mp->plane_fmt[1].sizeimage = + pix_fmt_mp->plane_fmt[2].sizeimage = + (pix_fmt_mp->width * pix_fmt_mp->height) / 4 + + ((ALIGN(pix_fmt_mp->width, 16) / 2) * 16); + pix_fmt_mp->plane_fmt[1].bytesperline = + pix_fmt_mp->plane_fmt[2].bytesperline = + pix_fmt_mp->width / 2; + } + + pix_fmt_mp->flags = 0; + + return 0; +} + +static void mtk_venc_set_param(struct mtk_vcodec_ctx *ctx, + struct venc_enc_param *param) +{ + struct mtk_q_data *q_data_src = &ctx->q_data[MTK_Q_DATA_SRC]; + struct mtk_enc_params *enc_params = &ctx->enc_params; + + switch (q_data_src->fmt->fourcc) { + case V4L2_PIX_FMT_YUV420M: + param->input_yuv_fmt = VENC_YUV_FORMAT_I420; + break; + case V4L2_PIX_FMT_YVU420M: + param->input_yuv_fmt = VENC_YUV_FORMAT_YV12; + break; + case V4L2_PIX_FMT_NV12M: + param->input_yuv_fmt = VENC_YUV_FORMAT_NV12; + break; + case V4L2_PIX_FMT_NV21M: + param->input_yuv_fmt = VENC_YUV_FORMAT_NV21; + break; + default: + mtk_v4l2_err("Unsupported fourcc =%d", q_data_src->fmt->fourcc); + break; + } + param->h264_profile = enc_params->h264_profile; + param->h264_level = enc_params->h264_level; + + /* Config visible resolution */ + param->width = q_data_src->visible_width; + param->height = q_data_src->visible_height; + /* Config coded resolution */ + param->buf_width = q_data_src->coded_width; + param->buf_height = q_data_src->coded_height; + param->frm_rate = enc_params->framerate_num / + enc_params->framerate_denom; + param->intra_period = enc_params->intra_period; + param->gop_size = enc_params->gop_size; + param->bitrate = enc_params->bitrate; + + mtk_v4l2_debug(0, + "fmt 0x%x, P/L %d/%d, w/h %d/%d, buf %d/%d, fps/bps %d/%d, gop %d, i_period %d", + param->input_yuv_fmt, param->h264_profile, + param->h264_level, param->width, param->height, + param->buf_width, param->buf_height, + param->frm_rate, param->bitrate, + param->gop_size, param->intra_period); +} + +static int vidioc_venc_s_fmt_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + const struct mtk_vcodec_enc_pdata *pdata = ctx->dev->venc_pdata; + struct vb2_queue *vq; + struct mtk_q_data *q_data = mtk_venc_get_q_data(ctx, f->type); + int i, ret; + const struct mtk_video_fmt *fmt; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) { + mtk_v4l2_err("fail to get vq"); + return -EINVAL; + } + + if (vb2_is_busy(vq)) { + mtk_v4l2_err("queue busy"); + return -EBUSY; + } + + fmt = mtk_venc_find_format(f->fmt.pix.pixelformat, pdata); + if (!fmt) { + fmt = &ctx->dev->venc_pdata->capture_formats[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + } + + q_data->fmt = fmt; + vidioc_try_fmt_cap(f); + + q_data->coded_width = f->fmt.pix_mp.width; + q_data->coded_height = f->fmt.pix_mp.height; + q_data->field = f->fmt.pix_mp.field; + + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) { + struct v4l2_plane_pix_format *plane_fmt; + + plane_fmt = &f->fmt.pix_mp.plane_fmt[i]; + q_data->bytesperline[i] = plane_fmt->bytesperline; + q_data->sizeimage[i] = plane_fmt->sizeimage; + } + + if (ctx->state == MTK_STATE_FREE) { + ret = venc_if_init(ctx, q_data->fmt->fourcc); + if (ret) { + mtk_v4l2_err("venc_if_init failed=%d, codec type=%x", + ret, q_data->fmt->fourcc); + return -EBUSY; + } + ctx->state = MTK_STATE_INIT; + } + + return 0; +} + +static int vidioc_venc_s_fmt_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + const struct mtk_vcodec_enc_pdata *pdata = ctx->dev->venc_pdata; + struct vb2_queue *vq; + struct mtk_q_data *q_data = mtk_venc_get_q_data(ctx, f->type); + int ret, i; + const struct mtk_video_fmt *fmt; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) { + mtk_v4l2_err("fail to get vq"); + return -EINVAL; + } + + if (vb2_is_busy(vq)) { + mtk_v4l2_err("queue busy"); + return -EBUSY; + } + + fmt = mtk_venc_find_format(f->fmt.pix.pixelformat, pdata); + if (!fmt) { + fmt = &ctx->dev->venc_pdata->output_formats[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + } + + q_data->visible_width = f->fmt.pix_mp.width; + q_data->visible_height = f->fmt.pix_mp.height; + q_data->fmt = fmt; + ret = vidioc_try_fmt_out(ctx, f, q_data->fmt); + if (ret) + return ret; + + q_data->coded_width = f->fmt.pix_mp.width; + q_data->coded_height = f->fmt.pix_mp.height; + + q_data->field = f->fmt.pix_mp.field; + ctx->colorspace = f->fmt.pix_mp.colorspace; + ctx->ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; + ctx->quantization = f->fmt.pix_mp.quantization; + ctx->xfer_func = f->fmt.pix_mp.xfer_func; + + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) { + struct v4l2_plane_pix_format *plane_fmt; + + plane_fmt = &f->fmt.pix_mp.plane_fmt[i]; + q_data->bytesperline[i] = plane_fmt->bytesperline; + q_data->sizeimage[i] = plane_fmt->sizeimage; + } + + return 0; +} + +static int vidioc_venc_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct vb2_queue *vq; + struct mtk_q_data *q_data = mtk_venc_get_q_data(ctx, f->type); + int i; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + + pix->width = q_data->coded_width; + pix->height = q_data->coded_height; + pix->pixelformat = q_data->fmt->fourcc; + pix->field = q_data->field; + pix->num_planes = q_data->fmt->num_planes; + for (i = 0; i < pix->num_planes; i++) { + pix->plane_fmt[i].bytesperline = q_data->bytesperline[i]; + pix->plane_fmt[i].sizeimage = q_data->sizeimage[i]; + } + + pix->flags = 0; + pix->colorspace = ctx->colorspace; + pix->ycbcr_enc = ctx->ycbcr_enc; + pix->quantization = ctx->quantization; + pix->xfer_func = ctx->xfer_func; + + return 0; +} + +static int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + const struct mtk_video_fmt *fmt; + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + const struct mtk_vcodec_enc_pdata *pdata = ctx->dev->venc_pdata; + + fmt = mtk_venc_find_format(f->fmt.pix.pixelformat, pdata); + if (!fmt) { + fmt = &ctx->dev->venc_pdata->capture_formats[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + } + f->fmt.pix_mp.colorspace = ctx->colorspace; + f->fmt.pix_mp.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix_mp.quantization = ctx->quantization; + f->fmt.pix_mp.xfer_func = ctx->xfer_func; + + vidioc_try_fmt_cap(f); + + return 0; +} + +static int vidioc_try_fmt_vid_out_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + const struct mtk_video_fmt *fmt; + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + const struct mtk_vcodec_enc_pdata *pdata = ctx->dev->venc_pdata; + + fmt = mtk_venc_find_format(f->fmt.pix.pixelformat, pdata); + if (!fmt) { + fmt = &ctx->dev->venc_pdata->output_formats[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + } + if (!f->fmt.pix_mp.colorspace) { + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; + f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_DEFAULT; + f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_DEFAULT; + } + + return vidioc_try_fmt_out(ctx, f, fmt); +} + +static int vidioc_venc_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct mtk_q_data *q_data = mtk_venc_get_q_data(ctx, s->type); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.top = 0; + s->r.left = 0; + s->r.width = q_data->coded_width; + s->r.height = q_data->coded_height; + break; + case V4L2_SEL_TGT_CROP: + s->r.top = 0; + s->r.left = 0; + s->r.width = q_data->visible_width; + s->r.height = q_data->visible_height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vidioc_venc_s_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct mtk_q_data *q_data = mtk_venc_get_q_data(ctx, s->type); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + /* Only support crop from (0,0) */ + s->r.top = 0; + s->r.left = 0; + s->r.width = min(s->r.width, q_data->coded_width); + s->r.height = min(s->r.height, q_data->coded_height); + q_data->visible_width = s->r.width; + q_data->visible_height = s->r.height; + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_venc_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + + if (ctx->state == MTK_STATE_ABORT) { + mtk_v4l2_err("[%d] Call on QBUF after unrecoverable error", + ctx->id); + return -EIO; + } + + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int vidioc_venc_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + int ret; + + if (ctx->state == MTK_STATE_ABORT) { + mtk_v4l2_err("[%d] Call on QBUF after unrecoverable error", + ctx->id); + return -EIO; + } + + ret = v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); + if (ret) + return ret; + + /* + * Complete flush if the user dequeued the 0-payload LAST buffer. + * We check the payload because a buffer with the LAST flag can also + * be seen during resolution changes. If we happen to be flushing at + * that time, the last buffer before the resolution changes could be + * misinterpreted for the buffer generated by the flush and terminate + * it earlier than we want. + */ + if (!V4L2_TYPE_IS_OUTPUT(buf->type) && + buf->flags & V4L2_BUF_FLAG_LAST && + buf->m.planes[0].bytesused == 0 && + ctx->is_flushing) { + /* + * Last CAPTURE buffer is dequeued, we can allow another flush + * to take place. + */ + ctx->is_flushing = false; + } + + return 0; +} + +static int vidioc_encoder_cmd(struct file *file, void *priv, + struct v4l2_encoder_cmd *cmd) +{ + struct mtk_vcodec_ctx *ctx = fh_to_ctx(priv); + struct vb2_queue *src_vq, *dst_vq; + int ret; + + if (ctx->state == MTK_STATE_ABORT) { + mtk_v4l2_err("[%d] Call to CMD after unrecoverable error", + ctx->id); + return -EIO; + } + + ret = v4l2_m2m_ioctl_try_encoder_cmd(file, priv, cmd); + if (ret) + return ret; + + /* Calling START or STOP is invalid if a flush is in progress */ + if (ctx->is_flushing) + return -EBUSY; + + mtk_v4l2_debug(1, "encoder cmd=%u", cmd->cmd); + + dst_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + switch (cmd->cmd) { + case V4L2_ENC_CMD_STOP: + src_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (!vb2_is_streaming(src_vq)) { + mtk_v4l2_debug(1, "Output stream is off. No need to flush."); + return 0; + } + if (!vb2_is_streaming(dst_vq)) { + mtk_v4l2_debug(1, "Capture stream is off. No need to flush."); + return 0; + } + ctx->is_flushing = true; + v4l2_m2m_buf_queue(ctx->m2m_ctx, &ctx->empty_flush_buf.vb); + v4l2_m2m_try_schedule(ctx->m2m_ctx); + break; + + case V4L2_ENC_CMD_START: + vb2_clear_last_buffer_dequeued(dst_vq); + break; + + default: + return -EINVAL; + } + + return 0; +} + +const struct v4l2_ioctl_ops mtk_venc_ioctl_ops = { + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = vidioc_venc_qbuf, + .vidioc_dqbuf = vidioc_venc_dqbuf, + + .vidioc_querycap = vidioc_venc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_enum_framesizes = vidioc_enum_framesizes, + + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_vid_out_mplane, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_s_parm = vidioc_venc_s_parm, + .vidioc_g_parm = vidioc_venc_g_parm, + .vidioc_s_fmt_vid_cap_mplane = vidioc_venc_s_fmt_cap, + .vidioc_s_fmt_vid_out_mplane = vidioc_venc_s_fmt_out, + + .vidioc_g_fmt_vid_cap_mplane = vidioc_venc_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vidioc_venc_g_fmt, + + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + + .vidioc_g_selection = vidioc_venc_g_selection, + .vidioc_s_selection = vidioc_venc_s_selection, + + .vidioc_encoder_cmd = vidioc_encoder_cmd, + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, +}; + +static int vb2ops_venc_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vq); + struct mtk_q_data *q_data = mtk_venc_get_q_data(ctx, vq->type); + unsigned int i; + + if (q_data == NULL) + return -EINVAL; + + if (*nplanes) { + if (*nplanes != q_data->fmt->num_planes) + return -EINVAL; + for (i = 0; i < *nplanes; i++) + if (sizes[i] < q_data->sizeimage[i]) + return -EINVAL; + } else { + *nplanes = q_data->fmt->num_planes; + for (i = 0; i < *nplanes; i++) + sizes[i] = q_data->sizeimage[i]; + } + + return 0; +} + +static int vb2ops_venc_buf_prepare(struct vb2_buffer *vb) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mtk_q_data *q_data = mtk_venc_get_q_data(ctx, vb->vb2_queue->type); + int i; + + for (i = 0; i < q_data->fmt->num_planes; i++) { + if (vb2_plane_size(vb, i) < q_data->sizeimage[i]) { + mtk_v4l2_err("data will not fit into plane %d (%lu < %d)", + i, vb2_plane_size(vb, i), + q_data->sizeimage[i]); + return -EINVAL; + } + } + + return 0; +} + +static void vb2ops_venc_buf_queue(struct vb2_buffer *vb) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vb2_v4l2 = + container_of(vb, struct vb2_v4l2_buffer, vb2_buf); + + struct mtk_video_enc_buf *mtk_buf = + container_of(vb2_v4l2, struct mtk_video_enc_buf, + m2m_buf.vb); + + if ((vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) && + (ctx->param_change != MTK_ENCODE_PARAM_NONE)) { + mtk_v4l2_debug(1, "[%d] Before id=%d encode parameter change %x", + ctx->id, + vb2_v4l2->vb2_buf.index, + ctx->param_change); + mtk_buf->param_change = ctx->param_change; + mtk_buf->enc_params = ctx->enc_params; + ctx->param_change = MTK_ENCODE_PARAM_NONE; + } + + v4l2_m2m_buf_queue(ctx->m2m_ctx, to_vb2_v4l2_buffer(vb)); +} + +static int vb2ops_venc_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(q); + struct venc_enc_param param; + int ret, pm_ret; + int i; + + /* Once state turn into MTK_STATE_ABORT, we need stop_streaming + * to clear it + */ + if ((ctx->state == MTK_STATE_ABORT) || (ctx->state == MTK_STATE_FREE)) { + ret = -EIO; + goto err_start_stream; + } + + /* Do the initialization when both start_streaming have been called */ + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + if (!vb2_start_streaming_called(&ctx->m2m_ctx->cap_q_ctx.q)) + return 0; + } else { + if (!vb2_start_streaming_called(&ctx->m2m_ctx->out_q_ctx.q)) + return 0; + } + + ret = pm_runtime_resume_and_get(&ctx->dev->plat_dev->dev); + if (ret < 0) { + mtk_v4l2_err("pm_runtime_resume_and_get fail %d", ret); + goto err_start_stream; + } + + mtk_venc_set_param(ctx, ¶m); + ret = venc_if_set_param(ctx, VENC_SET_PARAM_ENC, ¶m); + if (ret) { + mtk_v4l2_err("venc_if_set_param failed=%d", ret); + ctx->state = MTK_STATE_ABORT; + goto err_set_param; + } + ctx->param_change = MTK_ENCODE_PARAM_NONE; + + if ((ctx->q_data[MTK_Q_DATA_DST].fmt->fourcc == V4L2_PIX_FMT_H264) && + (ctx->enc_params.seq_hdr_mode != + V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE)) { + ret = venc_if_set_param(ctx, + VENC_SET_PARAM_PREPEND_HEADER, + NULL); + if (ret) { + mtk_v4l2_err("venc_if_set_param failed=%d", ret); + ctx->state = MTK_STATE_ABORT; + goto err_set_param; + } + ctx->state = MTK_STATE_HEADER; + } + + return 0; + +err_set_param: + pm_ret = pm_runtime_put(&ctx->dev->plat_dev->dev); + if (pm_ret < 0) + mtk_v4l2_err("pm_runtime_put fail %d", pm_ret); + +err_start_stream: + for (i = 0; i < q->num_buffers; ++i) { + struct vb2_buffer *buf = vb2_get_buffer(q, i); + + /* + * FIXME: This check is not needed as only active buffers + * can be marked as done. + */ + if (buf->state == VB2_BUF_STATE_ACTIVE) { + mtk_v4l2_debug(0, "[%d] id=%d, type=%d, %d -> VB2_BUF_STATE_QUEUED", + ctx->id, i, q->type, + (int)buf->state); + v4l2_m2m_buf_done(to_vb2_v4l2_buffer(buf), + VB2_BUF_STATE_QUEUED); + } + } + + return ret; +} + +static void vb2ops_venc_stop_streaming(struct vb2_queue *q) +{ + struct mtk_vcodec_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *src_buf, *dst_buf; + int ret; + + mtk_v4l2_debug(2, "[%d]-> type=%d", ctx->id, q->type); + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + while ((dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx))) { + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + } + /* STREAMOFF on the CAPTURE queue completes any ongoing flush */ + if (ctx->is_flushing) { + struct v4l2_m2m_buffer *b, *n; + + mtk_v4l2_debug(1, "STREAMOFF called while flushing"); + /* + * STREAMOFF could be called before the flush buffer is + * dequeued. Check whether empty flush buf is still in + * queue before removing it. + */ + v4l2_m2m_for_each_src_buf_safe(ctx->m2m_ctx, b, n) { + if (b == &ctx->empty_flush_buf) { + v4l2_m2m_src_buf_remove_by_buf(ctx->m2m_ctx, &b->vb); + break; + } + } + ctx->is_flushing = false; + } + } else { + while ((src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx))) { + if (src_buf != &ctx->empty_flush_buf.vb) + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + } + if (ctx->is_flushing) { + /* + * If we are in the middle of a flush, put the flush + * buffer back into the queue so the next CAPTURE + * buffer gets returned with the LAST flag set. + */ + v4l2_m2m_buf_queue(ctx->m2m_ctx, + &ctx->empty_flush_buf.vb); + } + } + + if ((q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + vb2_is_streaming(&ctx->m2m_ctx->out_q_ctx.q)) || + (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + vb2_is_streaming(&ctx->m2m_ctx->cap_q_ctx.q))) { + mtk_v4l2_debug(1, "[%d]-> q type %d out=%d cap=%d", + ctx->id, q->type, + vb2_is_streaming(&ctx->m2m_ctx->out_q_ctx.q), + vb2_is_streaming(&ctx->m2m_ctx->cap_q_ctx.q)); + return; + } + + /* Release the encoder if both streams are stopped. */ + ret = venc_if_deinit(ctx); + if (ret) + mtk_v4l2_err("venc_if_deinit failed=%d", ret); + + ret = pm_runtime_put(&ctx->dev->plat_dev->dev); + if (ret < 0) + mtk_v4l2_err("pm_runtime_put fail %d", ret); + + ctx->state = MTK_STATE_FREE; +} + +static int vb2ops_venc_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static const struct vb2_ops mtk_venc_vb2_ops = { + .queue_setup = vb2ops_venc_queue_setup, + .buf_out_validate = vb2ops_venc_buf_out_validate, + .buf_prepare = vb2ops_venc_buf_prepare, + .buf_queue = vb2ops_venc_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = vb2ops_venc_start_streaming, + .stop_streaming = vb2ops_venc_stop_streaming, +}; + +static int mtk_venc_encode_header(void *priv) +{ + struct mtk_vcodec_ctx *ctx = priv; + int ret; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mtk_vcodec_mem bs_buf; + struct venc_done_result enc_result; + + dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + if (!dst_buf) { + mtk_v4l2_debug(1, "No dst buffer"); + return -EINVAL; + } + + bs_buf.va = vb2_plane_vaddr(&dst_buf->vb2_buf, 0); + bs_buf.dma_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + bs_buf.size = (size_t)dst_buf->vb2_buf.planes[0].length; + + mtk_v4l2_debug(1, + "[%d] buf id=%d va=0x%p dma_addr=0x%llx size=%zu", + ctx->id, + dst_buf->vb2_buf.index, bs_buf.va, + (u64)bs_buf.dma_addr, + bs_buf.size); + + ret = venc_if_encode(ctx, + VENC_START_OPT_ENCODE_SEQUENCE_HEADER, + NULL, &bs_buf, &enc_result); + + if (ret) { + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0); + ctx->state = MTK_STATE_ABORT; + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + mtk_v4l2_err("venc_if_encode failed=%d", ret); + return -EINVAL; + } + src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + if (src_buf) { + dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp; + dst_buf->timecode = src_buf->timecode; + } else { + mtk_v4l2_err("No timestamp for the header buffer."); + } + + ctx->state = MTK_STATE_HEADER; + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, enc_result.bs_size); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + + return 0; +} + +static int mtk_venc_param_change(struct mtk_vcodec_ctx *ctx) +{ + struct venc_enc_param enc_prm; + struct vb2_v4l2_buffer *vb2_v4l2 = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + struct mtk_video_enc_buf *mtk_buf; + int ret = 0; + + /* Don't upcast the empty flush buffer */ + if (vb2_v4l2 == &ctx->empty_flush_buf.vb) + return 0; + + mtk_buf = container_of(vb2_v4l2, struct mtk_video_enc_buf, m2m_buf.vb); + + memset(&enc_prm, 0, sizeof(enc_prm)); + if (mtk_buf->param_change == MTK_ENCODE_PARAM_NONE) + return 0; + + if (mtk_buf->param_change & MTK_ENCODE_PARAM_BITRATE) { + enc_prm.bitrate = mtk_buf->enc_params.bitrate; + mtk_v4l2_debug(1, "[%d] id=%d, change param br=%d", + ctx->id, + vb2_v4l2->vb2_buf.index, + enc_prm.bitrate); + ret |= venc_if_set_param(ctx, + VENC_SET_PARAM_ADJUST_BITRATE, + &enc_prm); + } + if (!ret && mtk_buf->param_change & MTK_ENCODE_PARAM_FRAMERATE) { + enc_prm.frm_rate = mtk_buf->enc_params.framerate_num / + mtk_buf->enc_params.framerate_denom; + mtk_v4l2_debug(1, "[%d] id=%d, change param fr=%d", + ctx->id, + vb2_v4l2->vb2_buf.index, + enc_prm.frm_rate); + ret |= venc_if_set_param(ctx, + VENC_SET_PARAM_ADJUST_FRAMERATE, + &enc_prm); + } + if (!ret && mtk_buf->param_change & MTK_ENCODE_PARAM_GOP_SIZE) { + enc_prm.gop_size = mtk_buf->enc_params.gop_size; + mtk_v4l2_debug(1, "change param intra period=%d", + enc_prm.gop_size); + ret |= venc_if_set_param(ctx, + VENC_SET_PARAM_GOP_SIZE, + &enc_prm); + } + if (!ret && mtk_buf->param_change & MTK_ENCODE_PARAM_FORCE_INTRA) { + mtk_v4l2_debug(1, "[%d] id=%d, change param force I=%d", + ctx->id, + vb2_v4l2->vb2_buf.index, + mtk_buf->enc_params.force_intra); + if (mtk_buf->enc_params.force_intra) + ret |= venc_if_set_param(ctx, + VENC_SET_PARAM_FORCE_INTRA, + NULL); + } + + mtk_buf->param_change = MTK_ENCODE_PARAM_NONE; + + if (ret) { + ctx->state = MTK_STATE_ABORT; + mtk_v4l2_err("venc_if_set_param %d failed=%d", + mtk_buf->param_change, ret); + return -1; + } + + return 0; +} + +/* + * v4l2_m2m_streamoff() holds dev_mutex and waits mtk_venc_worker() + * to call v4l2_m2m_job_finish(). + * If mtk_venc_worker() tries to acquire dev_mutex, it will deadlock. + * So this function must not try to acquire dev->dev_mutex. + * This means v4l2 ioctls and mtk_venc_worker() can run at the same time. + * mtk_venc_worker() should be carefully implemented to avoid bugs. + */ +static void mtk_venc_worker(struct work_struct *work) +{ + struct mtk_vcodec_ctx *ctx = container_of(work, struct mtk_vcodec_ctx, + encode_work); + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct venc_frm_buf frm_buf; + struct mtk_vcodec_mem bs_buf; + struct venc_done_result enc_result; + int ret, i; + + /* check dst_buf, dst_buf may be removed in device_run + * to stored encdoe header so we need check dst_buf and + * call job_finish here to prevent recursion + */ + dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + if (!dst_buf) { + v4l2_m2m_job_finish(ctx->dev->m2m_dev_enc, ctx->m2m_ctx); + return; + } + + src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + + /* + * If we see the flush buffer, send an empty buffer with the LAST flag + * to the client. is_flushing will be reset at the time the buffer + * is dequeued. + */ + if (src_buf == &ctx->empty_flush_buf.vb) { + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0); + dst_buf->flags |= V4L2_BUF_FLAG_LAST; + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + v4l2_m2m_job_finish(ctx->dev->m2m_dev_enc, ctx->m2m_ctx); + return; + } + + memset(&frm_buf, 0, sizeof(frm_buf)); + for (i = 0; i < src_buf->vb2_buf.num_planes ; i++) { + frm_buf.fb_addr[i].dma_addr = + vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, i); + frm_buf.fb_addr[i].size = + (size_t)src_buf->vb2_buf.planes[i].length; + } + bs_buf.va = vb2_plane_vaddr(&dst_buf->vb2_buf, 0); + bs_buf.dma_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + bs_buf.size = (size_t)dst_buf->vb2_buf.planes[0].length; + + mtk_v4l2_debug(2, + "Framebuf PA=%llx Size=0x%zx;PA=0x%llx Size=0x%zx;PA=0x%llx Size=%zu", + (u64)frm_buf.fb_addr[0].dma_addr, + frm_buf.fb_addr[0].size, + (u64)frm_buf.fb_addr[1].dma_addr, + frm_buf.fb_addr[1].size, + (u64)frm_buf.fb_addr[2].dma_addr, + frm_buf.fb_addr[2].size); + + ret = venc_if_encode(ctx, VENC_START_OPT_ENCODE_FRAME, + &frm_buf, &bs_buf, &enc_result); + + dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp; + dst_buf->timecode = src_buf->timecode; + + if (enc_result.is_key_frm) + dst_buf->flags |= V4L2_BUF_FLAG_KEYFRAME; + + if (ret) { + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, 0); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + mtk_v4l2_err("venc_if_encode failed=%d", ret); + } else { + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, enc_result.bs_size); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + mtk_v4l2_debug(2, "venc_if_encode bs size=%d", + enc_result.bs_size); + } + + v4l2_m2m_job_finish(ctx->dev->m2m_dev_enc, ctx->m2m_ctx); + + mtk_v4l2_debug(1, "<=== src_buf[%d] dst_buf[%d] venc_if_encode ret=%d Size=%u===>", + src_buf->vb2_buf.index, dst_buf->vb2_buf.index, ret, + enc_result.bs_size); +} + +static void m2mops_venc_device_run(void *priv) +{ + struct mtk_vcodec_ctx *ctx = priv; + + if ((ctx->q_data[MTK_Q_DATA_DST].fmt->fourcc == V4L2_PIX_FMT_H264) && + (ctx->state != MTK_STATE_HEADER)) { + /* encode h264 sps/pps header */ + mtk_venc_encode_header(ctx); + queue_work(ctx->dev->encode_workqueue, &ctx->encode_work); + return; + } + + mtk_venc_param_change(ctx); + queue_work(ctx->dev->encode_workqueue, &ctx->encode_work); +} + +static int m2mops_venc_job_ready(void *m2m_priv) +{ + struct mtk_vcodec_ctx *ctx = m2m_priv; + + if (ctx->state == MTK_STATE_ABORT || ctx->state == MTK_STATE_FREE) { + mtk_v4l2_debug(3, "[%d]Not ready: state=0x%x.", + ctx->id, ctx->state); + return 0; + } + + return 1; +} + +static void m2mops_venc_job_abort(void *priv) +{ + struct mtk_vcodec_ctx *ctx = priv; + + ctx->state = MTK_STATE_ABORT; +} + +const struct v4l2_m2m_ops mtk_venc_m2m_ops = { + .device_run = m2mops_venc_device_run, + .job_ready = m2mops_venc_job_ready, + .job_abort = m2mops_venc_job_abort, +}; + +void mtk_vcodec_enc_set_default_params(struct mtk_vcodec_ctx *ctx) +{ + struct mtk_q_data *q_data; + + ctx->m2m_ctx->q_lock = &ctx->q_mutex; + ctx->fh.m2m_ctx = ctx->m2m_ctx; + ctx->fh.ctrl_handler = &ctx->ctrl_hdl; + INIT_WORK(&ctx->encode_work, mtk_venc_worker); + + ctx->colorspace = V4L2_COLORSPACE_REC709; + ctx->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + ctx->quantization = V4L2_QUANTIZATION_DEFAULT; + ctx->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + q_data = &ctx->q_data[MTK_Q_DATA_SRC]; + memset(q_data, 0, sizeof(struct mtk_q_data)); + q_data->visible_width = DFT_CFG_WIDTH; + q_data->visible_height = DFT_CFG_HEIGHT; + q_data->coded_width = DFT_CFG_WIDTH; + q_data->coded_height = DFT_CFG_HEIGHT; + q_data->field = V4L2_FIELD_NONE; + + q_data->fmt = &ctx->dev->venc_pdata->output_formats[0]; + + v4l_bound_align_image(&q_data->coded_width, + MTK_VENC_MIN_W, + MTK_VENC_HD_MAX_W, 4, + &q_data->coded_height, + MTK_VENC_MIN_H, + MTK_VENC_HD_MAX_H, 5, 6); + + if (q_data->coded_width < DFT_CFG_WIDTH && + (q_data->coded_width + 16) <= MTK_VENC_HD_MAX_W) + q_data->coded_width += 16; + if (q_data->coded_height < DFT_CFG_HEIGHT && + (q_data->coded_height + 32) <= MTK_VENC_HD_MAX_H) + q_data->coded_height += 32; + + q_data->sizeimage[0] = + q_data->coded_width * q_data->coded_height+ + ((ALIGN(q_data->coded_width, 16) * 2) * 16); + q_data->bytesperline[0] = q_data->coded_width; + q_data->sizeimage[1] = + (q_data->coded_width * q_data->coded_height) / 2 + + (ALIGN(q_data->coded_width, 16) * 16); + q_data->bytesperline[1] = q_data->coded_width; + + q_data = &ctx->q_data[MTK_Q_DATA_DST]; + memset(q_data, 0, sizeof(struct mtk_q_data)); + q_data->coded_width = DFT_CFG_WIDTH; + q_data->coded_height = DFT_CFG_HEIGHT; + q_data->fmt = &ctx->dev->venc_pdata->capture_formats[0]; + q_data->field = V4L2_FIELD_NONE; + ctx->q_data[MTK_Q_DATA_DST].sizeimage[0] = + DFT_CFG_WIDTH * DFT_CFG_HEIGHT; + ctx->q_data[MTK_Q_DATA_DST].bytesperline[0] = 0; + + ctx->enc_params.framerate_num = MTK_DEFAULT_FRAMERATE_NUM; + ctx->enc_params.framerate_denom = MTK_DEFAULT_FRAMERATE_DENOM; +} + +int mtk_vcodec_enc_ctrls_setup(struct mtk_vcodec_ctx *ctx) +{ + const struct v4l2_ctrl_ops *ops = &mtk_vcodec_enc_ctrl_ops; + struct v4l2_ctrl_handler *handler = &ctx->ctrl_hdl; + u8 h264_max_level; + + if (ctx->dev->enc_capability & MTK_VENC_4K_CAPABILITY_ENABLE) + h264_max_level = V4L2_MPEG_VIDEO_H264_LEVEL_5_1; + else + h264_max_level = V4L2_MPEG_VIDEO_H264_LEVEL_4_2; + + v4l2_ctrl_handler_init(handler, MTK_MAX_CTRLS_HINT); + + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, + 1, 1, 1, 1); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MPEG_VIDEO_BITRATE, + ctx->dev->venc_pdata->min_bitrate, + ctx->dev->venc_pdata->max_bitrate, 1, 4000000); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MPEG_VIDEO_B_FRAMES, + 0, 2, 1, 0); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, + 0, 1, 1, 1); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MPEG_VIDEO_H264_MAX_QP, + 0, 51, 1, 51); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, + 0, 65535, 1, 0); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 0, 65535, 1, 0); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE, + 0, 1, 1, 0); + v4l2_ctrl_new_std(handler, ops, V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, + 0, 0, 0, 0); + v4l2_ctrl_new_std_menu(handler, ops, + V4L2_CID_MPEG_VIDEO_HEADER_MODE, + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + 0, V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE); + v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, + 0, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_MPEG_VIDEO_H264_LEVEL, + h264_max_level, + 0, V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_MPEG_VIDEO_VP8_PROFILE, + V4L2_MPEG_VIDEO_VP8_PROFILE_0, 0, V4L2_MPEG_VIDEO_VP8_PROFILE_0); + v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, + ~(1 << V4L2_MPEG_VIDEO_BITRATE_MODE_CBR), + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR); + + + if (handler->error) { + mtk_v4l2_err("Init control handler fail %d", + handler->error); + return handler->error; + } + + v4l2_ctrl_handler_setup(&ctx->ctrl_hdl); + + return 0; +} + +int mtk_vcodec_enc_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mtk_vcodec_ctx *ctx = priv; + int ret; + + /* Note: VB2_USERPTR works with dma-contig because mt8173 + * support iommu + * https://patchwork.kernel.org/patch/8335461/ + * https://patchwork.kernel.org/patch/7596181/ + */ + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_DMABUF | VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct mtk_video_enc_buf); + src_vq->ops = &mtk_venc_vb2_ops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->q_mutex; + src_vq->dev = &ctx->dev->plat_dev->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_DMABUF | VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &mtk_venc_vb2_ops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->q_mutex; + dst_vq->dev = &ctx->dev->plat_dev->dev; + + return vb2_queue_init(dst_vq); +} + +int mtk_venc_unlock(struct mtk_vcodec_ctx *ctx) +{ + struct mtk_vcodec_dev *dev = ctx->dev; + + mutex_unlock(&dev->enc_mutex); + return 0; +} + +int mtk_venc_lock(struct mtk_vcodec_ctx *ctx) +{ + struct mtk_vcodec_dev *dev = ctx->dev; + + mutex_lock(&dev->enc_mutex); + return 0; +} + +void mtk_vcodec_enc_release(struct mtk_vcodec_ctx *ctx) +{ + int ret = venc_if_deinit(ctx); + + if (ret) + mtk_v4l2_err("venc_if_deinit failed=%d", ret); + + ctx->state = MTK_STATE_FREE; +} diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.h new file mode 100644 index 000000000..513ee7993 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc.h @@ -0,0 +1,50 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: PC Chen +* Tiffany Lin +*/ + +#ifndef _MTK_VCODEC_ENC_H_ +#define _MTK_VCODEC_ENC_H_ + +#include +#include + +#define MTK_VENC_IRQ_STATUS_SPS 0x1 +#define MTK_VENC_IRQ_STATUS_PPS 0x2 +#define MTK_VENC_IRQ_STATUS_FRM 0x4 +#define MTK_VENC_IRQ_STATUS_DRAM 0x8 +#define MTK_VENC_IRQ_STATUS_PAUSE 0x10 +#define MTK_VENC_IRQ_STATUS_SWITCH 0x20 + +#define MTK_VENC_IRQ_STATUS_OFFSET 0x05C +#define MTK_VENC_IRQ_ACK_OFFSET 0x060 + +/** + * struct mtk_video_enc_buf - Private data related to each VB2 buffer. + * @m2m_buf: M2M buffer + * @list: list that buffer link to + * @param_change: Types of encode parameter change before encoding this + * buffer + * @enc_params: Encode parameters changed before encode this buffer + */ +struct mtk_video_enc_buf { + struct v4l2_m2m_buffer m2m_buf; + + u32 param_change; + struct mtk_enc_params enc_params; +}; + +extern const struct v4l2_ioctl_ops mtk_venc_ioctl_ops; +extern const struct v4l2_m2m_ops mtk_venc_m2m_ops; + +int mtk_venc_unlock(struct mtk_vcodec_ctx *ctx); +int mtk_venc_lock(struct mtk_vcodec_ctx *ctx); +int mtk_vcodec_enc_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); +void mtk_vcodec_enc_release(struct mtk_vcodec_ctx *ctx); +int mtk_vcodec_enc_ctrls_setup(struct mtk_vcodec_ctx *ctx); +void mtk_vcodec_enc_set_default_params(struct mtk_vcodec_ctx *ctx); + +#endif /* _MTK_VCODEC_ENC_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_drv.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_drv.c new file mode 100644 index 000000000..9095186d5 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_drv.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: PC Chen +* Tiffany Lin +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_enc.h" +#include "mtk_vcodec_enc_pm.h" +#include "mtk_vcodec_intr.h" +#include "mtk_vcodec_util.h" +#include "mtk_vcodec_fw.h" + +static const struct mtk_video_fmt mtk_video_formats_output[] = { + { + .fourcc = V4L2_PIX_FMT_NV12M, + .type = MTK_FMT_FRAME, + .num_planes = 2, + }, + { + .fourcc = V4L2_PIX_FMT_NV21M, + .type = MTK_FMT_FRAME, + .num_planes = 2, + }, + { + .fourcc = V4L2_PIX_FMT_YUV420M, + .type = MTK_FMT_FRAME, + .num_planes = 3, + }, + { + .fourcc = V4L2_PIX_FMT_YVU420M, + .type = MTK_FMT_FRAME, + .num_planes = 3, + }, +}; + +static const struct mtk_video_fmt mtk_video_formats_capture_h264[] = { + { + .fourcc = V4L2_PIX_FMT_H264, + .type = MTK_FMT_ENC, + .num_planes = 1, + }, +}; + +static const struct mtk_video_fmt mtk_video_formats_capture_vp8[] = { + { + .fourcc = V4L2_PIX_FMT_VP8, + .type = MTK_FMT_ENC, + .num_planes = 1, + }, +}; + +static void clean_irq_status(unsigned int irq_status, void __iomem *addr) +{ + if (irq_status & MTK_VENC_IRQ_STATUS_PAUSE) + writel(MTK_VENC_IRQ_STATUS_PAUSE, addr); + + if (irq_status & MTK_VENC_IRQ_STATUS_SWITCH) + writel(MTK_VENC_IRQ_STATUS_SWITCH, addr); + + if (irq_status & MTK_VENC_IRQ_STATUS_DRAM) + writel(MTK_VENC_IRQ_STATUS_DRAM, addr); + + if (irq_status & MTK_VENC_IRQ_STATUS_SPS) + writel(MTK_VENC_IRQ_STATUS_SPS, addr); + + if (irq_status & MTK_VENC_IRQ_STATUS_PPS) + writel(MTK_VENC_IRQ_STATUS_PPS, addr); + + if (irq_status & MTK_VENC_IRQ_STATUS_FRM) + writel(MTK_VENC_IRQ_STATUS_FRM, addr); + +} +static irqreturn_t mtk_vcodec_enc_irq_handler(int irq, void *priv) +{ + struct mtk_vcodec_dev *dev = priv; + struct mtk_vcodec_ctx *ctx; + unsigned long flags; + void __iomem *addr; + + spin_lock_irqsave(&dev->irqlock, flags); + ctx = dev->curr_ctx; + spin_unlock_irqrestore(&dev->irqlock, flags); + + mtk_v4l2_debug(1, "id=%d coreid:%d", ctx->id, dev->venc_pdata->core_id); + addr = dev->reg_base[dev->venc_pdata->core_id] + + MTK_VENC_IRQ_ACK_OFFSET; + + ctx->irq_status = readl(dev->reg_base[dev->venc_pdata->core_id] + + (MTK_VENC_IRQ_STATUS_OFFSET)); + + clean_irq_status(ctx->irq_status, addr); + + wake_up_ctx(ctx, MTK_INST_IRQ_RECEIVED, 0); + return IRQ_HANDLED; +} + +static int fops_vcodec_open(struct file *file) +{ + struct mtk_vcodec_dev *dev = video_drvdata(file); + struct mtk_vcodec_ctx *ctx = NULL; + int ret = 0; + struct vb2_queue *src_vq; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_lock(&dev->dev_mutex); + /* + * Use simple counter to uniquely identify this context. Only + * used for logging. + */ + ctx->id = dev->id_counter++; + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + INIT_LIST_HEAD(&ctx->list); + ctx->dev = dev; + init_waitqueue_head(&ctx->queue[0]); + mutex_init(&ctx->q_mutex); + + ctx->type = MTK_INST_ENCODER; + ret = mtk_vcodec_enc_ctrls_setup(ctx); + if (ret) { + mtk_v4l2_err("Failed to setup controls() (%d)", + ret); + goto err_ctrls_setup; + } + ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev_enc, ctx, + &mtk_vcodec_enc_queue_init); + if (IS_ERR((__force void *)ctx->m2m_ctx)) { + ret = PTR_ERR((__force void *)ctx->m2m_ctx); + mtk_v4l2_err("Failed to v4l2_m2m_ctx_init() (%d)", + ret); + goto err_m2m_ctx_init; + } + src_vq = v4l2_m2m_get_vq(ctx->m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + ctx->empty_flush_buf.vb.vb2_buf.vb2_queue = src_vq; + mtk_vcodec_enc_set_default_params(ctx); + + if (v4l2_fh_is_singular(&ctx->fh)) { + /* + * load fireware to checks if it was loaded already and + * does nothing in that case + */ + ret = mtk_vcodec_fw_load_firmware(dev->fw_handler); + if (ret < 0) { + /* + * Return 0 if downloading firmware successfully, + * otherwise it is failed + */ + mtk_v4l2_err("vpu_load_firmware failed!"); + goto err_load_fw; + } + + dev->enc_capability = + mtk_vcodec_fw_get_venc_capa(dev->fw_handler); + mtk_v4l2_debug(0, "encoder capability %x", dev->enc_capability); + } + + mtk_v4l2_debug(2, "Create instance [%d]@%p m2m_ctx=%p ", + ctx->id, ctx, ctx->m2m_ctx); + + list_add(&ctx->list, &dev->ctx_list); + + mutex_unlock(&dev->dev_mutex); + mtk_v4l2_debug(0, "%s encoder [%d]", dev_name(&dev->plat_dev->dev), + ctx->id); + return ret; + + /* Deinit when failure occurred */ +err_load_fw: + v4l2_m2m_ctx_release(ctx->m2m_ctx); +err_m2m_ctx_init: + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); +err_ctrls_setup: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&dev->dev_mutex); + + return ret; +} + +static int fops_vcodec_release(struct file *file) +{ + struct mtk_vcodec_dev *dev = video_drvdata(file); + struct mtk_vcodec_ctx *ctx = fh_to_ctx(file->private_data); + + mtk_v4l2_debug(1, "[%d] encoder", ctx->id); + mutex_lock(&dev->dev_mutex); + + v4l2_m2m_ctx_release(ctx->m2m_ctx); + mtk_vcodec_enc_release(ctx); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + + list_del_init(&ctx->list); + kfree(ctx); + mutex_unlock(&dev->dev_mutex); + return 0; +} + +static const struct v4l2_file_operations mtk_vcodec_fops = { + .owner = THIS_MODULE, + .open = fops_vcodec_open, + .release = fops_vcodec_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int mtk_vcodec_probe(struct platform_device *pdev) +{ + struct mtk_vcodec_dev *dev; + struct video_device *vfd_enc; + phandle rproc_phandle; + enum mtk_vcodec_fw_type fw_type; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + INIT_LIST_HEAD(&dev->ctx_list); + dev->plat_dev = pdev; + + if (!of_property_read_u32(pdev->dev.of_node, "mediatek,vpu", + &rproc_phandle)) { + fw_type = VPU; + } else if (!of_property_read_u32(pdev->dev.of_node, "mediatek,scp", + &rproc_phandle)) { + fw_type = SCP; + } else { + mtk_v4l2_err("Could not get venc IPI device"); + return -ENODEV; + } + dma_set_max_seg_size(&pdev->dev, UINT_MAX); + + dev->fw_handler = mtk_vcodec_fw_select(dev, fw_type, ENCODER); + if (IS_ERR(dev->fw_handler)) + return PTR_ERR(dev->fw_handler); + + dev->venc_pdata = of_device_get_match_data(&pdev->dev); + ret = mtk_vcodec_init_enc_clk(dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to get mtk vcodec clock source!"); + goto err_enc_pm; + } + + pm_runtime_enable(&pdev->dev); + + dev->reg_base[dev->venc_pdata->core_id] = + devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->reg_base[dev->venc_pdata->core_id])) { + ret = PTR_ERR(dev->reg_base[dev->venc_pdata->core_id]); + goto err_res; + } + + dev->enc_irq = platform_get_irq(pdev, 0); + if (dev->enc_irq < 0) { + ret = dev->enc_irq; + goto err_res; + } + + irq_set_status_flags(dev->enc_irq, IRQ_NOAUTOEN); + ret = devm_request_irq(&pdev->dev, dev->enc_irq, + mtk_vcodec_enc_irq_handler, + 0, pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, + "Failed to install dev->enc_irq %d (%d) core_id (%d)", + dev->enc_irq, ret, dev->venc_pdata->core_id); + ret = -EINVAL; + goto err_res; + } + + mutex_init(&dev->enc_mutex); + mutex_init(&dev->dev_mutex); + spin_lock_init(&dev->irqlock); + + snprintf(dev->v4l2_dev.name, sizeof(dev->v4l2_dev.name), "%s", + "[MTK_V4L2_VENC]"); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) { + mtk_v4l2_err("v4l2_device_register err=%d", ret); + goto err_res; + } + + init_waitqueue_head(&dev->queue); + + /* allocate video device for encoder and register it */ + vfd_enc = video_device_alloc(); + if (!vfd_enc) { + mtk_v4l2_err("Failed to allocate video device"); + ret = -ENOMEM; + goto err_enc_alloc; + } + vfd_enc->fops = &mtk_vcodec_fops; + vfd_enc->ioctl_ops = &mtk_venc_ioctl_ops; + vfd_enc->release = video_device_release; + vfd_enc->lock = &dev->dev_mutex; + vfd_enc->v4l2_dev = &dev->v4l2_dev; + vfd_enc->vfl_dir = VFL_DIR_M2M; + vfd_enc->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | + V4L2_CAP_STREAMING; + + snprintf(vfd_enc->name, sizeof(vfd_enc->name), "%s", + MTK_VCODEC_ENC_NAME); + video_set_drvdata(vfd_enc, dev); + dev->vfd_enc = vfd_enc; + platform_set_drvdata(pdev, dev); + + dev->m2m_dev_enc = v4l2_m2m_init(&mtk_venc_m2m_ops); + if (IS_ERR((__force void *)dev->m2m_dev_enc)) { + mtk_v4l2_err("Failed to init mem2mem enc device"); + ret = PTR_ERR((__force void *)dev->m2m_dev_enc); + goto err_enc_mem_init; + } + + dev->encode_workqueue = + alloc_ordered_workqueue(MTK_VCODEC_ENC_NAME, + WQ_MEM_RECLAIM | + WQ_FREEZABLE); + if (!dev->encode_workqueue) { + mtk_v4l2_err("Failed to create encode workqueue"); + ret = -EINVAL; + goto err_event_workq; + } + + if (of_get_property(pdev->dev.of_node, "dma-ranges", NULL)) + dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(34)); + + ret = video_register_device(vfd_enc, VFL_TYPE_VIDEO, -1); + if (ret) { + mtk_v4l2_err("Failed to register video device"); + goto err_enc_reg; + } + + mtk_v4l2_debug(0, "encoder %d registered as /dev/video%d", + dev->venc_pdata->core_id, vfd_enc->num); + + return 0; + +err_enc_reg: + destroy_workqueue(dev->encode_workqueue); +err_event_workq: + v4l2_m2m_release(dev->m2m_dev_enc); +err_enc_mem_init: + video_unregister_device(vfd_enc); +err_enc_alloc: + v4l2_device_unregister(&dev->v4l2_dev); +err_res: + pm_runtime_disable(dev->pm.dev); +err_enc_pm: + mtk_vcodec_fw_release(dev->fw_handler); + return ret; +} + +static const struct mtk_vcodec_enc_pdata mt8173_avc_pdata = { + .capture_formats = mtk_video_formats_capture_h264, + .num_capture_formats = ARRAY_SIZE(mtk_video_formats_capture_h264), + .output_formats = mtk_video_formats_output, + .num_output_formats = ARRAY_SIZE(mtk_video_formats_output), + .min_bitrate = 64, + .max_bitrate = 60000000, + .core_id = VENC_SYS, +}; + +static const struct mtk_vcodec_enc_pdata mt8173_vp8_pdata = { + .capture_formats = mtk_video_formats_capture_vp8, + .num_capture_formats = ARRAY_SIZE(mtk_video_formats_capture_vp8), + .output_formats = mtk_video_formats_output, + .num_output_formats = ARRAY_SIZE(mtk_video_formats_output), + .min_bitrate = 64, + .max_bitrate = 9000000, + .core_id = VENC_LT_SYS, +}; + +static const struct mtk_vcodec_enc_pdata mt8183_pdata = { + .uses_ext = true, + .capture_formats = mtk_video_formats_capture_h264, + .num_capture_formats = ARRAY_SIZE(mtk_video_formats_capture_h264), + .output_formats = mtk_video_formats_output, + .num_output_formats = ARRAY_SIZE(mtk_video_formats_output), + .min_bitrate = 64, + .max_bitrate = 40000000, + .core_id = VENC_SYS, +}; + +static const struct mtk_vcodec_enc_pdata mt8188_pdata = { + .uses_ext = true, + .capture_formats = mtk_video_formats_capture_h264, + .num_capture_formats = ARRAY_SIZE(mtk_video_formats_capture_h264), + .output_formats = mtk_video_formats_output, + .num_output_formats = ARRAY_SIZE(mtk_video_formats_output), + .min_bitrate = 64, + .max_bitrate = 50000000, + .core_id = VENC_SYS, + .uses_34bit = true, +}; + +static const struct mtk_vcodec_enc_pdata mt8192_pdata = { + .uses_ext = true, + .capture_formats = mtk_video_formats_capture_h264, + .num_capture_formats = ARRAY_SIZE(mtk_video_formats_capture_h264), + .output_formats = mtk_video_formats_output, + .num_output_formats = ARRAY_SIZE(mtk_video_formats_output), + .min_bitrate = 64, + .max_bitrate = 100000000, + .core_id = VENC_SYS, +}; + +static const struct mtk_vcodec_enc_pdata mt8195_pdata = { + .uses_ext = true, + .capture_formats = mtk_video_formats_capture_h264, + .num_capture_formats = ARRAY_SIZE(mtk_video_formats_capture_h264), + .output_formats = mtk_video_formats_output, + .num_output_formats = ARRAY_SIZE(mtk_video_formats_output), + .min_bitrate = 64, + .max_bitrate = 100000000, + .core_id = VENC_SYS, +}; + +static const struct of_device_id mtk_vcodec_enc_match[] = { + {.compatible = "mediatek,mt8173-vcodec-enc", + .data = &mt8173_avc_pdata}, + {.compatible = "mediatek,mt8173-vcodec-enc-vp8", + .data = &mt8173_vp8_pdata}, + {.compatible = "mediatek,mt8183-vcodec-enc", .data = &mt8183_pdata}, + {.compatible = "mediatek,mt8188-vcodec-enc", .data = &mt8188_pdata}, + {.compatible = "mediatek,mt8192-vcodec-enc", .data = &mt8192_pdata}, + {.compatible = "mediatek,mt8195-vcodec-enc", .data = &mt8195_pdata}, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_vcodec_enc_match); + +static int mtk_vcodec_enc_remove(struct platform_device *pdev) +{ + struct mtk_vcodec_dev *dev = platform_get_drvdata(pdev); + + mtk_v4l2_debug_enter(); + destroy_workqueue(dev->encode_workqueue); + if (dev->m2m_dev_enc) + v4l2_m2m_release(dev->m2m_dev_enc); + + if (dev->vfd_enc) + video_unregister_device(dev->vfd_enc); + + v4l2_device_unregister(&dev->v4l2_dev); + pm_runtime_disable(dev->pm.dev); + mtk_vcodec_fw_release(dev->fw_handler); + return 0; +} + +static struct platform_driver mtk_vcodec_enc_driver = { + .probe = mtk_vcodec_probe, + .remove = mtk_vcodec_enc_remove, + .driver = { + .name = MTK_VCODEC_ENC_NAME, + .of_match_table = mtk_vcodec_enc_match, + }, +}; + +module_platform_driver(mtk_vcodec_enc_driver); + + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Mediatek video codec V4L2 encoder driver"); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_pm.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_pm.c new file mode 100644 index 000000000..7055954eb --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_pm.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: Tiffany Lin +*/ + +#include +#include +#include +#include + +#include "mtk_vcodec_enc_pm.h" +#include "mtk_vcodec_util.h" + +int mtk_vcodec_init_enc_clk(struct mtk_vcodec_dev *mtkdev) +{ + struct platform_device *pdev; + struct mtk_vcodec_pm *pm; + struct mtk_vcodec_clk *enc_clk; + struct mtk_vcodec_clk_info *clk_info; + int ret, i; + + pdev = mtkdev->plat_dev; + pm = &mtkdev->pm; + memset(pm, 0, sizeof(struct mtk_vcodec_pm)); + pm->dev = &pdev->dev; + enc_clk = &pm->venc_clk; + + enc_clk->clk_num = of_property_count_strings(pdev->dev.of_node, + "clock-names"); + if (enc_clk->clk_num > 0) { + enc_clk->clk_info = devm_kcalloc(&pdev->dev, + enc_clk->clk_num, sizeof(*clk_info), + GFP_KERNEL); + if (!enc_clk->clk_info) + return -ENOMEM; + } else { + mtk_v4l2_err("Failed to get venc clock count"); + return -EINVAL; + } + + for (i = 0; i < enc_clk->clk_num; i++) { + clk_info = &enc_clk->clk_info[i]; + ret = of_property_read_string_index(pdev->dev.of_node, + "clock-names", i, &clk_info->clk_name); + if (ret) { + mtk_v4l2_err("venc failed to get clk name %d", i); + return ret; + } + clk_info->vcodec_clk = devm_clk_get(&pdev->dev, + clk_info->clk_name); + if (IS_ERR(clk_info->vcodec_clk)) { + mtk_v4l2_err("venc devm_clk_get (%d)%s fail", i, + clk_info->clk_name); + return PTR_ERR(clk_info->vcodec_clk); + } + } + + return 0; +} + +void mtk_vcodec_enc_clock_on(struct mtk_vcodec_pm *pm) +{ + struct mtk_vcodec_clk *enc_clk = &pm->venc_clk; + int ret, i = 0; + + for (i = 0; i < enc_clk->clk_num; i++) { + ret = clk_prepare_enable(enc_clk->clk_info[i].vcodec_clk); + if (ret) { + mtk_v4l2_err("venc clk_prepare_enable %d %s fail %d", i, + enc_clk->clk_info[i].clk_name, ret); + goto clkerr; + } + } + + return; + +clkerr: + for (i -= 1; i >= 0; i--) + clk_disable_unprepare(enc_clk->clk_info[i].vcodec_clk); +} + +void mtk_vcodec_enc_clock_off(struct mtk_vcodec_pm *pm) +{ + struct mtk_vcodec_clk *enc_clk = &pm->venc_clk; + int i = 0; + + for (i = enc_clk->clk_num - 1; i >= 0; i--) + clk_disable_unprepare(enc_clk->clk_info[i].vcodec_clk); +} diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_pm.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_pm.h new file mode 100644 index 000000000..bc455cefc --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_enc_pm.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: Tiffany Lin +*/ + +#ifndef _MTK_VCODEC_ENC_PM_H_ +#define _MTK_VCODEC_ENC_PM_H_ + +#include "mtk_vcodec_drv.h" + +int mtk_vcodec_init_enc_clk(struct mtk_vcodec_dev *dev); + +void mtk_vcodec_enc_clock_on(struct mtk_vcodec_pm *pm); +void mtk_vcodec_enc_clock_off(struct mtk_vcodec_pm *pm); + +#endif /* _MTK_VCODEC_ENC_PM_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw.c new file mode 100644 index 000000000..556e54aad --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "mtk_vcodec_fw.h" +#include "mtk_vcodec_fw_priv.h" +#include "mtk_vcodec_util.h" +#include "mtk_vcodec_drv.h" + +struct mtk_vcodec_fw *mtk_vcodec_fw_select(struct mtk_vcodec_dev *dev, + enum mtk_vcodec_fw_type type, + enum mtk_vcodec_fw_use fw_use) +{ + switch (type) { + case VPU: + return mtk_vcodec_fw_vpu_init(dev, fw_use); + case SCP: + return mtk_vcodec_fw_scp_init(dev); + default: + mtk_v4l2_err("invalid vcodec fw type"); + return ERR_PTR(-EINVAL); + } +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_select); + +void mtk_vcodec_fw_release(struct mtk_vcodec_fw *fw) +{ + fw->ops->release(fw); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_release); + +int mtk_vcodec_fw_load_firmware(struct mtk_vcodec_fw *fw) +{ + return fw->ops->load_firmware(fw); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_load_firmware); + +unsigned int mtk_vcodec_fw_get_vdec_capa(struct mtk_vcodec_fw *fw) +{ + return fw->ops->get_vdec_capa(fw); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_get_vdec_capa); + +unsigned int mtk_vcodec_fw_get_venc_capa(struct mtk_vcodec_fw *fw) +{ + return fw->ops->get_venc_capa(fw); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_get_venc_capa); + +void *mtk_vcodec_fw_map_dm_addr(struct mtk_vcodec_fw *fw, u32 mem_addr) +{ + return fw->ops->map_dm_addr(fw, mem_addr); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_map_dm_addr); + +int mtk_vcodec_fw_ipi_register(struct mtk_vcodec_fw *fw, int id, + mtk_vcodec_ipi_handler handler, + const char *name, void *priv) +{ + return fw->ops->ipi_register(fw, id, handler, name, priv); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_ipi_register); + +int mtk_vcodec_fw_ipi_send(struct mtk_vcodec_fw *fw, int id, void *buf, + unsigned int len, unsigned int wait) +{ + return fw->ops->ipi_send(fw, id, buf, len, wait); +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_ipi_send); + +int mtk_vcodec_fw_get_type(struct mtk_vcodec_fw *fw) +{ + return fw->type; +} +EXPORT_SYMBOL_GPL(mtk_vcodec_fw_get_type); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw.h new file mode 100644 index 000000000..168241146 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _MTK_VCODEC_FW_H_ +#define _MTK_VCODEC_FW_H_ + +#include +#include + +#include "../vpu/mtk_vpu.h" + +struct mtk_vcodec_dev; + +enum mtk_vcodec_fw_type { + VPU, + SCP, +}; + +enum mtk_vcodec_fw_use { + DECODER, + ENCODER, +}; + +struct mtk_vcodec_fw; + +typedef void (*mtk_vcodec_ipi_handler) (void *data, + unsigned int len, void *priv); + +struct mtk_vcodec_fw *mtk_vcodec_fw_select(struct mtk_vcodec_dev *dev, + enum mtk_vcodec_fw_type type, + enum mtk_vcodec_fw_use fw_use); +void mtk_vcodec_fw_release(struct mtk_vcodec_fw *fw); + +int mtk_vcodec_fw_load_firmware(struct mtk_vcodec_fw *fw); +unsigned int mtk_vcodec_fw_get_vdec_capa(struct mtk_vcodec_fw *fw); +unsigned int mtk_vcodec_fw_get_venc_capa(struct mtk_vcodec_fw *fw); +void *mtk_vcodec_fw_map_dm_addr(struct mtk_vcodec_fw *fw, u32 mem_addr); +int mtk_vcodec_fw_ipi_register(struct mtk_vcodec_fw *fw, int id, + mtk_vcodec_ipi_handler handler, + const char *name, void *priv); +int mtk_vcodec_fw_ipi_send(struct mtk_vcodec_fw *fw, int id, + void *buf, unsigned int len, unsigned int wait); +int mtk_vcodec_fw_get_type(struct mtk_vcodec_fw *fw); + +#endif /* _MTK_VCODEC_FW_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_priv.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_priv.h new file mode 100644 index 000000000..b41e66185 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_priv.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef _MTK_VCODEC_FW_PRIV_H_ +#define _MTK_VCODEC_FW_PRIV_H_ + +#include "mtk_vcodec_fw.h" + +struct mtk_vcodec_dev; + +struct mtk_vcodec_fw { + enum mtk_vcodec_fw_type type; + const struct mtk_vcodec_fw_ops *ops; + struct platform_device *pdev; + struct mtk_scp *scp; +}; + +struct mtk_vcodec_fw_ops { + int (*load_firmware)(struct mtk_vcodec_fw *fw); + unsigned int (*get_vdec_capa)(struct mtk_vcodec_fw *fw); + unsigned int (*get_venc_capa)(struct mtk_vcodec_fw *fw); + void *(*map_dm_addr)(struct mtk_vcodec_fw *fw, u32 dtcm_dmem_addr); + int (*ipi_register)(struct mtk_vcodec_fw *fw, int id, + mtk_vcodec_ipi_handler handler, const char *name, + void *priv); + int (*ipi_send)(struct mtk_vcodec_fw *fw, int id, void *buf, + unsigned int len, unsigned int wait); + void (*release)(struct mtk_vcodec_fw *fw); +}; + +#if IS_ENABLED(CONFIG_VIDEO_MEDIATEK_VCODEC_VPU) +struct mtk_vcodec_fw *mtk_vcodec_fw_vpu_init(struct mtk_vcodec_dev *dev, + enum mtk_vcodec_fw_use fw_use); +#else +static inline struct mtk_vcodec_fw * +mtk_vcodec_fw_vpu_init(struct mtk_vcodec_dev *dev, + enum mtk_vcodec_fw_use fw_use) +{ + return ERR_PTR(-ENODEV); +} +#endif /* CONFIG_VIDEO_MEDIATEK_VCODEC_VPU */ + +#if IS_ENABLED(CONFIG_VIDEO_MEDIATEK_VCODEC_SCP) +struct mtk_vcodec_fw *mtk_vcodec_fw_scp_init(struct mtk_vcodec_dev *dev); +#else +static inline struct mtk_vcodec_fw * +mtk_vcodec_fw_scp_init(struct mtk_vcodec_dev *dev) +{ + return ERR_PTR(-ENODEV); +} +#endif /* CONFIG_VIDEO_MEDIATEK_VCODEC_SCP */ + +#endif /* _MTK_VCODEC_FW_PRIV_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_scp.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_scp.c new file mode 100644 index 000000000..d8e66b645 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_scp.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "mtk_vcodec_fw_priv.h" +#include "mtk_vcodec_util.h" +#include "mtk_vcodec_drv.h" + +static int mtk_vcodec_scp_load_firmware(struct mtk_vcodec_fw *fw) +{ + return rproc_boot(scp_get_rproc(fw->scp)); +} + +static unsigned int mtk_vcodec_scp_get_vdec_capa(struct mtk_vcodec_fw *fw) +{ + return scp_get_vdec_hw_capa(fw->scp); +} + +static unsigned int mtk_vcodec_scp_get_venc_capa(struct mtk_vcodec_fw *fw) +{ + return scp_get_venc_hw_capa(fw->scp); +} + +static void *mtk_vcodec_vpu_scp_dm_addr(struct mtk_vcodec_fw *fw, + u32 dtcm_dmem_addr) +{ + return scp_mapping_dm_addr(fw->scp, dtcm_dmem_addr); +} + +static int mtk_vcodec_scp_set_ipi_register(struct mtk_vcodec_fw *fw, int id, + mtk_vcodec_ipi_handler handler, + const char *name, void *priv) +{ + return scp_ipi_register(fw->scp, id, handler, priv); +} + +static int mtk_vcodec_scp_ipi_send(struct mtk_vcodec_fw *fw, int id, void *buf, + unsigned int len, unsigned int wait) +{ + return scp_ipi_send(fw->scp, id, buf, len, wait); +} + +static void mtk_vcodec_scp_release(struct mtk_vcodec_fw *fw) +{ + scp_put(fw->scp); +} + +static const struct mtk_vcodec_fw_ops mtk_vcodec_rproc_msg = { + .load_firmware = mtk_vcodec_scp_load_firmware, + .get_vdec_capa = mtk_vcodec_scp_get_vdec_capa, + .get_venc_capa = mtk_vcodec_scp_get_venc_capa, + .map_dm_addr = mtk_vcodec_vpu_scp_dm_addr, + .ipi_register = mtk_vcodec_scp_set_ipi_register, + .ipi_send = mtk_vcodec_scp_ipi_send, + .release = mtk_vcodec_scp_release, +}; + +struct mtk_vcodec_fw *mtk_vcodec_fw_scp_init(struct mtk_vcodec_dev *dev) +{ + struct mtk_vcodec_fw *fw; + struct mtk_scp *scp; + + scp = scp_get(dev->plat_dev); + if (!scp) { + mtk_v4l2_err("could not get vdec scp handle"); + return ERR_PTR(-EPROBE_DEFER); + } + + fw = devm_kzalloc(&dev->plat_dev->dev, sizeof(*fw), GFP_KERNEL); + fw->type = SCP; + fw->ops = &mtk_vcodec_rproc_msg; + fw->scp = scp; + + return fw; +} diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_vpu.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_vpu.c new file mode 100644 index 000000000..cfc7ebed8 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_fw_vpu.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "mtk_vcodec_fw_priv.h" +#include "mtk_vcodec_util.h" +#include "mtk_vcodec_drv.h" + +static int mtk_vcodec_vpu_load_firmware(struct mtk_vcodec_fw *fw) +{ + return vpu_load_firmware(fw->pdev); +} + +static unsigned int mtk_vcodec_vpu_get_vdec_capa(struct mtk_vcodec_fw *fw) +{ + return vpu_get_vdec_hw_capa(fw->pdev); +} + +static unsigned int mtk_vcodec_vpu_get_venc_capa(struct mtk_vcodec_fw *fw) +{ + return vpu_get_venc_hw_capa(fw->pdev); +} + +static void *mtk_vcodec_vpu_map_dm_addr(struct mtk_vcodec_fw *fw, + u32 dtcm_dmem_addr) +{ + return vpu_mapping_dm_addr(fw->pdev, dtcm_dmem_addr); +} + +static int mtk_vcodec_vpu_set_ipi_register(struct mtk_vcodec_fw *fw, int id, + mtk_vcodec_ipi_handler handler, + const char *name, void *priv) +{ + /* + * The handler we receive takes a void * as its first argument. We + * cannot change this because it needs to be passed down to the rproc + * subsystem when SCP is used. VPU takes a const argument, which is + * more constrained, so the conversion below is safe. + */ + ipi_handler_t handler_const = (ipi_handler_t)handler; + + return vpu_ipi_register(fw->pdev, id, handler_const, name, priv); +} + +static int mtk_vcodec_vpu_ipi_send(struct mtk_vcodec_fw *fw, int id, void *buf, + unsigned int len, unsigned int wait) +{ + return vpu_ipi_send(fw->pdev, id, buf, len); +} + +static void mtk_vcodec_vpu_release(struct mtk_vcodec_fw *fw) +{ + put_device(&fw->pdev->dev); +} + +static void mtk_vcodec_vpu_reset_handler(void *priv) +{ + struct mtk_vcodec_dev *dev = priv; + struct mtk_vcodec_ctx *ctx; + + mtk_v4l2_err("Watchdog timeout!!"); + + mutex_lock(&dev->dev_mutex); + list_for_each_entry(ctx, &dev->ctx_list, list) { + ctx->state = MTK_STATE_ABORT; + mtk_v4l2_debug(0, "[%d] Change to state MTK_STATE_ABORT", + ctx->id); + } + mutex_unlock(&dev->dev_mutex); +} + +static const struct mtk_vcodec_fw_ops mtk_vcodec_vpu_msg = { + .load_firmware = mtk_vcodec_vpu_load_firmware, + .get_vdec_capa = mtk_vcodec_vpu_get_vdec_capa, + .get_venc_capa = mtk_vcodec_vpu_get_venc_capa, + .map_dm_addr = mtk_vcodec_vpu_map_dm_addr, + .ipi_register = mtk_vcodec_vpu_set_ipi_register, + .ipi_send = mtk_vcodec_vpu_ipi_send, + .release = mtk_vcodec_vpu_release, +}; + +struct mtk_vcodec_fw *mtk_vcodec_fw_vpu_init(struct mtk_vcodec_dev *dev, + enum mtk_vcodec_fw_use fw_use) +{ + struct platform_device *fw_pdev; + struct mtk_vcodec_fw *fw; + enum rst_id rst_id; + + switch (fw_use) { + case ENCODER: + rst_id = VPU_RST_ENC; + break; + case DECODER: + default: + rst_id = VPU_RST_DEC; + break; + } + + fw_pdev = vpu_get_plat_device(dev->plat_dev); + if (!fw_pdev) { + mtk_v4l2_err("firmware device is not ready"); + return ERR_PTR(-EINVAL); + } + vpu_wdt_reg_handler(fw_pdev, mtk_vcodec_vpu_reset_handler, dev, rst_id); + + fw = devm_kzalloc(&dev->plat_dev->dev, sizeof(*fw), GFP_KERNEL); + if (!fw) + return ERR_PTR(-ENOMEM); + fw->type = VPU; + fw->ops = &mtk_vcodec_vpu_msg; + fw->pdev = fw_pdev; + + return fw; +} diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_intr.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_intr.c new file mode 100644 index 000000000..552b4c93d --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_intr.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: Tiffany Lin +*/ + +#include +#include + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_intr.h" +#include "mtk_vcodec_util.h" + +int mtk_vcodec_wait_for_done_ctx(struct mtk_vcodec_ctx *ctx, + int command, unsigned int timeout_ms, + unsigned int hw_id) +{ + long timeout_jiff, ret; + int status = 0; + + timeout_jiff = msecs_to_jiffies(timeout_ms); + ret = wait_event_interruptible_timeout(ctx->queue[hw_id], + ctx->int_cond[hw_id], + timeout_jiff); + + if (!ret) { + status = -1; /* timeout */ + mtk_v4l2_err("[%d] cmd=%d, type=%d, dec timeout=%ums (%d %d)", + ctx->id, command, ctx->type, timeout_ms, + ctx->int_cond[hw_id], ctx->int_type[hw_id]); + } else if (-ERESTARTSYS == ret) { + status = -1; + mtk_v4l2_err("[%d] cmd=%d, type=%d, dec inter fail (%d %d)", + ctx->id, command, ctx->type, + ctx->int_cond[hw_id], ctx->int_type[hw_id]); + } + + ctx->int_cond[hw_id] = 0; + ctx->int_type[hw_id] = 0; + + return status; +} +EXPORT_SYMBOL(mtk_vcodec_wait_for_done_ctx); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_intr.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_intr.h new file mode 100644 index 000000000..9681f4928 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_intr.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: Tiffany Lin +*/ + +#ifndef _MTK_VCODEC_INTR_H_ +#define _MTK_VCODEC_INTR_H_ + +#define MTK_INST_IRQ_RECEIVED 0x1 + +struct mtk_vcodec_ctx; + +/* timeout is ms */ +int mtk_vcodec_wait_for_done_ctx(struct mtk_vcodec_ctx *ctx, + int command, unsigned int timeout_ms, + unsigned int hw_id); + +#endif /* _MTK_VCODEC_INTR_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_util.c b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_util.c new file mode 100644 index 000000000..ace78c4b5 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_util.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: PC Chen +* Tiffany Lin +*/ + +#include +#include +#include + +#include "mtk_vcodec_dec_hw.h" +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_util.h" + +void __iomem *mtk_vcodec_get_reg_addr(struct mtk_vcodec_ctx *data, + unsigned int reg_idx) +{ + struct mtk_vcodec_ctx *ctx = (struct mtk_vcodec_ctx *)data; + + if (!data || reg_idx >= NUM_MAX_VCODEC_REG_BASE) { + mtk_v4l2_err("Invalid arguments, reg_idx=%d", reg_idx); + return NULL; + } + return ctx->dev->reg_base[reg_idx]; +} +EXPORT_SYMBOL(mtk_vcodec_get_reg_addr); + +int mtk_vcodec_mem_alloc(struct mtk_vcodec_ctx *data, + struct mtk_vcodec_mem *mem) +{ + unsigned long size = mem->size; + struct mtk_vcodec_ctx *ctx = (struct mtk_vcodec_ctx *)data; + struct device *dev = &ctx->dev->plat_dev->dev; + + mem->va = dma_alloc_coherent(dev, size, &mem->dma_addr, GFP_KERNEL); + if (!mem->va) { + mtk_v4l2_err("%s dma_alloc size=%ld failed!", dev_name(dev), + size); + return -ENOMEM; + } + + mtk_v4l2_debug(3, "[%d] - va = %p", ctx->id, mem->va); + mtk_v4l2_debug(3, "[%d] - dma = 0x%lx", ctx->id, + (unsigned long)mem->dma_addr); + mtk_v4l2_debug(3, "[%d] size = 0x%lx", ctx->id, size); + + return 0; +} +EXPORT_SYMBOL(mtk_vcodec_mem_alloc); + +void mtk_vcodec_mem_free(struct mtk_vcodec_ctx *data, + struct mtk_vcodec_mem *mem) +{ + unsigned long size = mem->size; + struct mtk_vcodec_ctx *ctx = (struct mtk_vcodec_ctx *)data; + struct device *dev = &ctx->dev->plat_dev->dev; + + if (!mem->va) { + mtk_v4l2_err("%s dma_free size=%ld failed!", dev_name(dev), + size); + return; + } + + mtk_v4l2_debug(3, "[%d] - va = %p", ctx->id, mem->va); + mtk_v4l2_debug(3, "[%d] - dma = 0x%lx", ctx->id, + (unsigned long)mem->dma_addr); + mtk_v4l2_debug(3, "[%d] size = 0x%lx", ctx->id, size); + + dma_free_coherent(dev, size, mem->va, mem->dma_addr); + mem->va = NULL; + mem->dma_addr = 0; + mem->size = 0; +} +EXPORT_SYMBOL(mtk_vcodec_mem_free); + +void *mtk_vcodec_get_hw_dev(struct mtk_vcodec_dev *dev, int hw_idx) +{ + if (hw_idx >= MTK_VDEC_HW_MAX || hw_idx < 0 || !dev->subdev_dev[hw_idx]) { + mtk_v4l2_err("hw idx is out of range:%d", hw_idx); + return NULL; + } + + return dev->subdev_dev[hw_idx]; +} +EXPORT_SYMBOL(mtk_vcodec_get_hw_dev); + +void mtk_vcodec_set_curr_ctx(struct mtk_vcodec_dev *vdec_dev, + struct mtk_vcodec_ctx *ctx, int hw_idx) +{ + unsigned long flags; + struct mtk_vdec_hw_dev *subdev_dev; + + spin_lock_irqsave(&vdec_dev->irqlock, flags); + if (vdec_dev->vdec_pdata->is_subdev_supported) { + subdev_dev = mtk_vcodec_get_hw_dev(vdec_dev, hw_idx); + if (!subdev_dev) { + mtk_v4l2_err("Failed to get hw dev"); + spin_unlock_irqrestore(&vdec_dev->irqlock, flags); + return; + } + subdev_dev->curr_ctx = ctx; + } else { + vdec_dev->curr_ctx = ctx; + } + spin_unlock_irqrestore(&vdec_dev->irqlock, flags); +} +EXPORT_SYMBOL(mtk_vcodec_set_curr_ctx); + +struct mtk_vcodec_ctx *mtk_vcodec_get_curr_ctx(struct mtk_vcodec_dev *vdec_dev, + unsigned int hw_idx) +{ + unsigned long flags; + struct mtk_vcodec_ctx *ctx; + struct mtk_vdec_hw_dev *subdev_dev; + + spin_lock_irqsave(&vdec_dev->irqlock, flags); + if (vdec_dev->vdec_pdata->is_subdev_supported) { + subdev_dev = mtk_vcodec_get_hw_dev(vdec_dev, hw_idx); + if (!subdev_dev) { + mtk_v4l2_err("Failed to get hw dev"); + spin_unlock_irqrestore(&vdec_dev->irqlock, flags); + return NULL; + } + ctx = subdev_dev->curr_ctx; + } else { + ctx = vdec_dev->curr_ctx; + } + spin_unlock_irqrestore(&vdec_dev->irqlock, flags); + return ctx; +} +EXPORT_SYMBOL(mtk_vcodec_get_curr_ctx); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Mediatek video codec driver"); diff --git a/drivers/media/platform/mediatek/vcodec/mtk_vcodec_util.h b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_util.h new file mode 100644 index 000000000..71956627a --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/mtk_vcodec_util.h @@ -0,0 +1,63 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: PC Chen +* Tiffany Lin +*/ + +#ifndef _MTK_VCODEC_UTIL_H_ +#define _MTK_VCODEC_UTIL_H_ + +#include +#include + +struct mtk_vcodec_mem { + size_t size; + void *va; + dma_addr_t dma_addr; +}; + +struct mtk_vcodec_fb { + size_t size; + dma_addr_t dma_addr; +}; + +struct mtk_vcodec_ctx; +struct mtk_vcodec_dev; + +#undef pr_fmt +#define pr_fmt(fmt) "%s(),%d: " fmt, __func__, __LINE__ + +#define mtk_v4l2_err(fmt, args...) \ + pr_err("[MTK_V4L2][ERROR] " fmt "\n", ##args) + +#define mtk_vcodec_err(h, fmt, args...) \ + pr_err("[MTK_VCODEC][ERROR][%d]: " fmt "\n", \ + ((struct mtk_vcodec_ctx *)(h)->ctx)->id, ##args) + + +#define mtk_v4l2_debug(level, fmt, args...) pr_debug(fmt, ##args) + +#define mtk_v4l2_debug_enter() mtk_v4l2_debug(3, "+") +#define mtk_v4l2_debug_leave() mtk_v4l2_debug(3, "-") + +#define mtk_vcodec_debug(h, fmt, args...) \ + pr_debug("[MTK_VCODEC][%d]: " fmt "\n", \ + ((struct mtk_vcodec_ctx *)(h)->ctx)->id, ##args) + +#define mtk_vcodec_debug_enter(h) mtk_vcodec_debug(h, "+") +#define mtk_vcodec_debug_leave(h) mtk_vcodec_debug(h, "-") + +void __iomem *mtk_vcodec_get_reg_addr(struct mtk_vcodec_ctx *data, + unsigned int reg_idx); +int mtk_vcodec_mem_alloc(struct mtk_vcodec_ctx *data, + struct mtk_vcodec_mem *mem); +void mtk_vcodec_mem_free(struct mtk_vcodec_ctx *data, + struct mtk_vcodec_mem *mem); +void mtk_vcodec_set_curr_ctx(struct mtk_vcodec_dev *vdec_dev, + struct mtk_vcodec_ctx *ctx, int hw_idx); +struct mtk_vcodec_ctx *mtk_vcodec_get_curr_ctx(struct mtk_vcodec_dev *vdec_dev, + unsigned int hw_idx); +void *mtk_vcodec_get_hw_dev(struct mtk_vcodec_dev *dev, int hw_idx); + +#endif /* _MTK_VCODEC_UTIL_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_if.c new file mode 100644 index 000000000..481655bb6 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_if.c @@ -0,0 +1,503 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + */ + +#include +#include + +#include "../vdec_drv_if.h" +#include "../mtk_vcodec_util.h" +#include "../mtk_vcodec_dec.h" +#include "../mtk_vcodec_intr.h" +#include "../vdec_vpu_if.h" +#include "../vdec_drv_base.h" + +#define NAL_NON_IDR_SLICE 0x01 +#define NAL_IDR_SLICE 0x05 +#define NAL_H264_PPS 0x08 +#define NAL_TYPE(value) ((value) & 0x1F) + +#define BUF_PREDICTION_SZ (32 * 1024) + +#define MB_UNIT_LEN 16 + +/* motion vector size (bytes) for every macro block */ +#define HW_MB_STORE_SZ 64 + +#define H264_MAX_FB_NUM 17 +#define HDR_PARSING_BUF_SZ 1024 + +#define DEC_ERR_RET(ret) ((ret) >> 16) +#define H264_ERR_NOT_VALID 3 + +/** + * struct h264_fb - h264 decode frame buffer information + * @vdec_fb_va : virtual address of struct vdec_fb + * @y_fb_dma : dma address of Y frame buffer (luma) + * @c_fb_dma : dma address of C frame buffer (chroma) + * @poc : picture order count of frame buffer + * @reserved : for 8 bytes alignment + */ +struct h264_fb { + uint64_t vdec_fb_va; + uint64_t y_fb_dma; + uint64_t c_fb_dma; + int32_t poc; + uint32_t reserved; +}; + +/** + * struct h264_ring_fb_list - ring frame buffer list + * @fb_list : frame buffer array + * @read_idx : read index + * @write_idx : write index + * @count : buffer count in list + * @reserved : for 8 bytes alignment + */ +struct h264_ring_fb_list { + struct h264_fb fb_list[H264_MAX_FB_NUM]; + unsigned int read_idx; + unsigned int write_idx; + unsigned int count; + unsigned int reserved; +}; + +/** + * struct vdec_h264_dec_info - decode information + * @dpb_sz : decoding picture buffer size + * @resolution_changed : resolution change happen + * @realloc_mv_buf : flag to notify driver to re-allocate mv buffer + * @reserved : for 8 bytes alignment + * @bs_dma : Input bit-stream buffer dma address + * @y_fb_dma : Y frame buffer dma address + * @c_fb_dma : C frame buffer dma address + * @vdec_fb_va : VDEC frame buffer struct virtual address + */ +struct vdec_h264_dec_info { + uint32_t dpb_sz; + uint32_t resolution_changed; + uint32_t realloc_mv_buf; + uint32_t reserved; + uint64_t bs_dma; + uint64_t y_fb_dma; + uint64_t c_fb_dma; + uint64_t vdec_fb_va; +}; + +/** + * struct vdec_h264_vsi - shared memory for decode information exchange + * between VPU and Host. + * The memory is allocated by VPU then mapping to Host + * in vpu_dec_init() and freed in vpu_dec_deinit() + * by VPU. + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @hdr_buf : Header parsing buffer (AP-W, VPU-R) + * @pred_buf_dma : HW working predication buffer dma address (AP-W, VPU-R) + * @mv_buf_dma : HW working motion vector buffer dma address (AP-W, VPU-R) + * @list_free : free frame buffer ring list (AP-W/R, VPU-W) + * @list_disp : display frame buffer ring list (AP-R, VPU-W) + * @dec : decode information (AP-R, VPU-W) + * @pic : picture information (AP-R, VPU-W) + * @crop : crop information (AP-R, VPU-W) + */ +struct vdec_h264_vsi { + unsigned char hdr_buf[HDR_PARSING_BUF_SZ]; + uint64_t pred_buf_dma; + uint64_t mv_buf_dma[H264_MAX_FB_NUM]; + struct h264_ring_fb_list list_free; + struct h264_ring_fb_list list_disp; + struct vdec_h264_dec_info dec; + struct vdec_pic_info pic; + struct v4l2_rect crop; +}; + +/** + * struct vdec_h264_inst - h264 decoder instance + * @num_nalu : how many nalus be decoded + * @ctx : point to mtk_vcodec_ctx + * @pred_buf : HW working predication buffer + * @mv_buf : HW working motion vector buffer + * @vpu : VPU instance + * @vsi : VPU shared information + */ +struct vdec_h264_inst { + unsigned int num_nalu; + struct mtk_vcodec_ctx *ctx; + struct mtk_vcodec_mem pred_buf; + struct mtk_vcodec_mem mv_buf[H264_MAX_FB_NUM]; + struct vdec_vpu_inst vpu; + struct vdec_h264_vsi *vsi; +}; + +static unsigned int get_mv_buf_size(unsigned int width, unsigned int height) +{ + return HW_MB_STORE_SZ * (width/MB_UNIT_LEN) * (height/MB_UNIT_LEN); +} + +static int allocate_predication_buf(struct vdec_h264_inst *inst) +{ + int err = 0; + + inst->pred_buf.size = BUF_PREDICTION_SZ; + err = mtk_vcodec_mem_alloc(inst->ctx, &inst->pred_buf); + if (err) { + mtk_vcodec_err(inst, "failed to allocate ppl buf"); + return err; + } + + inst->vsi->pred_buf_dma = inst->pred_buf.dma_addr; + return 0; +} + +static void free_predication_buf(struct vdec_h264_inst *inst) +{ + struct mtk_vcodec_mem *mem = NULL; + + mtk_vcodec_debug_enter(inst); + + inst->vsi->pred_buf_dma = 0; + mem = &inst->pred_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); +} + +static int alloc_mv_buf(struct vdec_h264_inst *inst, struct vdec_pic_info *pic) +{ + int i; + int err; + struct mtk_vcodec_mem *mem = NULL; + unsigned int buf_sz = get_mv_buf_size(pic->buf_w, pic->buf_h); + + for (i = 0; i < H264_MAX_FB_NUM; i++) { + mem = &inst->mv_buf[i]; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + mem->size = buf_sz; + err = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (err) { + mtk_vcodec_err(inst, "failed to allocate mv buf"); + return err; + } + inst->vsi->mv_buf_dma[i] = mem->dma_addr; + } + + return 0; +} + +static void free_mv_buf(struct vdec_h264_inst *inst) +{ + int i; + struct mtk_vcodec_mem *mem = NULL; + + for (i = 0; i < H264_MAX_FB_NUM; i++) { + inst->vsi->mv_buf_dma[i] = 0; + mem = &inst->mv_buf[i]; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + } +} + +static int check_list_validity(struct vdec_h264_inst *inst, bool disp_list) +{ + struct h264_ring_fb_list *list; + + list = disp_list ? &inst->vsi->list_disp : &inst->vsi->list_free; + + if (list->count > H264_MAX_FB_NUM || + list->read_idx >= H264_MAX_FB_NUM || + list->write_idx >= H264_MAX_FB_NUM) { + mtk_vcodec_err(inst, "%s list err: cnt=%d r_idx=%d w_idx=%d", + disp_list ? "disp" : "free", list->count, + list->read_idx, list->write_idx); + return -EINVAL; + } + + return 0; +} + +static void put_fb_to_free(struct vdec_h264_inst *inst, struct vdec_fb *fb) +{ + struct h264_ring_fb_list *list; + + if (fb) { + if (check_list_validity(inst, false)) + return; + + list = &inst->vsi->list_free; + if (list->count == H264_MAX_FB_NUM) { + mtk_vcodec_err(inst, "[FB] put fb free_list full"); + return; + } + + mtk_vcodec_debug(inst, "[FB] put fb into free_list @(%p, %llx)", + fb->base_y.va, (u64)fb->base_y.dma_addr); + + list->fb_list[list->write_idx].vdec_fb_va = (u64)(uintptr_t)fb; + list->write_idx = (list->write_idx == H264_MAX_FB_NUM - 1) ? + 0 : list->write_idx + 1; + list->count++; + } +} + +static void get_pic_info(struct vdec_h264_inst *inst, + struct vdec_pic_info *pic) +{ + *pic = inst->vsi->pic; + mtk_vcodec_debug(inst, "pic(%d, %d), buf(%d, %d)", + pic->pic_w, pic->pic_h, pic->buf_w, pic->buf_h); + mtk_vcodec_debug(inst, "fb size: Y(%d), C(%d)", + pic->fb_sz[0], pic->fb_sz[1]); +} + +static void get_crop_info(struct vdec_h264_inst *inst, struct v4l2_rect *cr) +{ + cr->left = inst->vsi->crop.left; + cr->top = inst->vsi->crop.top; + cr->width = inst->vsi->crop.width; + cr->height = inst->vsi->crop.height; + + mtk_vcodec_debug(inst, "l=%d, t=%d, w=%d, h=%d", + cr->left, cr->top, cr->width, cr->height); +} + +static void get_dpb_size(struct vdec_h264_inst *inst, unsigned int *dpb_sz) +{ + *dpb_sz = inst->vsi->dec.dpb_sz; + mtk_vcodec_debug(inst, "sz=%d", *dpb_sz); +} + +static int vdec_h264_init(struct mtk_vcodec_ctx *ctx) +{ + struct vdec_h264_inst *inst = NULL; + int err; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->ctx = ctx; + + inst->vpu.id = IPI_VDEC_H264; + inst->vpu.ctx = ctx; + + err = vpu_dec_init(&inst->vpu); + if (err) { + mtk_vcodec_err(inst, "vdec_h264 init err=%d", err); + goto error_free_inst; + } + + inst->vsi = (struct vdec_h264_vsi *)inst->vpu.vsi; + err = allocate_predication_buf(inst); + if (err) + goto error_deinit; + + mtk_vcodec_debug(inst, "H264 Instance >> %p", inst); + + ctx->drv_handle = inst; + return 0; + +error_deinit: + vpu_dec_deinit(&inst->vpu); + +error_free_inst: + kfree(inst); + return err; +} + +static void vdec_h264_deinit(void *h_vdec) +{ + struct vdec_h264_inst *inst = (struct vdec_h264_inst *)h_vdec; + + mtk_vcodec_debug_enter(inst); + + vpu_dec_deinit(&inst->vpu); + free_predication_buf(inst); + free_mv_buf(inst); + + kfree(inst); +} + +static int find_start_code(unsigned char *data, unsigned int data_sz) +{ + if (data_sz > 3 && data[0] == 0 && data[1] == 0 && data[2] == 1) + return 3; + + if (data_sz > 4 && data[0] == 0 && data[1] == 0 && data[2] == 0 && + data[3] == 1) + return 4; + + return -1; +} + +static int vdec_h264_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + struct vdec_h264_inst *inst = (struct vdec_h264_inst *)h_vdec; + struct vdec_vpu_inst *vpu = &inst->vpu; + int nal_start_idx = 0; + int err = 0; + unsigned int nal_start; + unsigned int nal_type; + unsigned char *buf; + unsigned int buf_sz; + unsigned int data[2]; + uint64_t vdec_fb_va = (u64)(uintptr_t)fb; + uint64_t y_fb_dma = fb ? (u64)fb->base_y.dma_addr : 0; + uint64_t c_fb_dma = fb ? (u64)fb->base_c.dma_addr : 0; + + mtk_vcodec_debug(inst, "+ [%d] FB y_dma=%llx c_dma=%llx va=%p", + ++inst->num_nalu, y_fb_dma, c_fb_dma, fb); + + /* bs NULL means flush decoder */ + if (bs == NULL) + return vpu_dec_reset(vpu); + + buf = (unsigned char *)bs->va; + buf_sz = bs->size; + nal_start_idx = find_start_code(buf, buf_sz); + if (nal_start_idx < 0) { + mtk_vcodec_err(inst, "invalid nal start code"); + err = -EIO; + goto err_free_fb_out; + } + + nal_start = buf[nal_start_idx]; + nal_type = NAL_TYPE(buf[nal_start_idx]); + mtk_vcodec_debug(inst, "\n + NALU[%d] type %d +\n", inst->num_nalu, + nal_type); + + if (nal_type == NAL_H264_PPS) { + buf_sz -= nal_start_idx; + if (buf_sz > HDR_PARSING_BUF_SZ) { + err = -EILSEQ; + goto err_free_fb_out; + } + memcpy(inst->vsi->hdr_buf, buf + nal_start_idx, buf_sz); + } + + inst->vsi->dec.bs_dma = (uint64_t)bs->dma_addr; + inst->vsi->dec.y_fb_dma = y_fb_dma; + inst->vsi->dec.c_fb_dma = c_fb_dma; + inst->vsi->dec.vdec_fb_va = vdec_fb_va; + + data[0] = buf_sz; + data[1] = nal_start; + err = vpu_dec_start(vpu, data, 2); + if (err) { + if (err > 0 && (DEC_ERR_RET(err) == H264_ERR_NOT_VALID)) { + mtk_vcodec_err(inst, "- error bitstream - err = %d -", + err); + err = -EIO; + } + goto err_free_fb_out; + } + + *res_chg = inst->vsi->dec.resolution_changed; + if (*res_chg) { + struct vdec_pic_info pic; + + mtk_vcodec_debug(inst, "- resolution changed -"); + get_pic_info(inst, &pic); + + if (inst->vsi->dec.realloc_mv_buf) { + err = alloc_mv_buf(inst, &pic); + if (err) + goto err_free_fb_out; + } + } + + if (nal_type == NAL_NON_IDR_SLICE || nal_type == NAL_IDR_SLICE) { + /* wait decoder done interrupt */ + err = mtk_vcodec_wait_for_done_ctx(inst->ctx, + MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, 0); + if (err) + goto err_free_fb_out; + + vpu_dec_end(vpu); + } + + mtk_vcodec_debug(inst, "\n - NALU[%d] type=%d -\n", inst->num_nalu, + nal_type); + return 0; + +err_free_fb_out: + put_fb_to_free(inst, fb); + mtk_vcodec_err(inst, "\n - NALU[%d] err=%d -\n", inst->num_nalu, err); + return err; +} + +static void vdec_h264_get_fb(struct vdec_h264_inst *inst, + struct h264_ring_fb_list *list, + bool disp_list, struct vdec_fb **out_fb) +{ + struct vdec_fb *fb; + + if (check_list_validity(inst, disp_list)) + return; + + if (list->count == 0) { + mtk_vcodec_debug(inst, "[FB] there is no %s fb", + disp_list ? "disp" : "free"); + *out_fb = NULL; + return; + } + + fb = (struct vdec_fb *) + (uintptr_t)list->fb_list[list->read_idx].vdec_fb_va; + fb->status |= (disp_list ? FB_ST_DISPLAY : FB_ST_FREE); + + *out_fb = fb; + mtk_vcodec_debug(inst, "[FB] get %s fb st=%d poc=%d %llx", + disp_list ? "disp" : "free", + fb->status, list->fb_list[list->read_idx].poc, + list->fb_list[list->read_idx].vdec_fb_va); + + list->read_idx = (list->read_idx == H264_MAX_FB_NUM - 1) ? + 0 : list->read_idx + 1; + list->count--; +} + +static int vdec_h264_get_param(void *h_vdec, enum vdec_get_param_type type, + void *out) +{ + struct vdec_h264_inst *inst = (struct vdec_h264_inst *)h_vdec; + + switch (type) { + case GET_PARAM_DISP_FRAME_BUFFER: + vdec_h264_get_fb(inst, &inst->vsi->list_disp, true, out); + break; + + case GET_PARAM_FREE_FRAME_BUFFER: + vdec_h264_get_fb(inst, &inst->vsi->list_free, false, out); + break; + + case GET_PARAM_PIC_INFO: + get_pic_info(inst, out); + break; + + case GET_PARAM_DPB_SIZE: + get_dpb_size(inst, out); + break; + + case GET_PARAM_CROP_INFO: + get_crop_info(inst, out); + break; + + default: + mtk_vcodec_err(inst, "invalid get parameter type=%d", type); + return -EINVAL; + } + + return 0; +} + +const struct vdec_common_if vdec_h264_if = { + .init = vdec_h264_init, + .decode = vdec_h264_decode, + .get_param = vdec_h264_get_param, + .deinit = vdec_h264_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_common.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_common.c new file mode 100644 index 000000000..580ce979e --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_common.c @@ -0,0 +1,322 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Yunfei Dong + */ + +#include "vdec_h264_req_common.h" + +/* get used parameters for sps/pps */ +#define GET_MTK_VDEC_FLAG(cond, flag) \ + { dst_param->cond = ((src_param->flags & flag) ? (1) : (0)); } +#define GET_MTK_VDEC_PARAM(param) \ + { dst_param->param = src_param->param; } + +void mtk_vdec_h264_get_ref_list(u8 *ref_list, + const struct v4l2_h264_reference *v4l2_ref_list, + int num_valid) +{ + u32 i; + + /* + * TODO The firmware does not support field decoding. Future + * implementation must use v4l2_ref_list[i].fields to obtain + * the reference field parity. + */ + + for (i = 0; i < num_valid; i++) + ref_list[i] = v4l2_ref_list[i].index; + + /* + * The firmware expects unused reflist entries to have the value 0x20. + */ + memset(&ref_list[num_valid], 0x20, 32 - num_valid); +} + +void *mtk_vdec_h264_get_ctrl_ptr(struct mtk_vcodec_ctx *ctx, int id) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, id); + + if (!ctrl) + return ERR_PTR(-EINVAL); + + return ctrl->p_cur.p; +} + +void mtk_vdec_h264_fill_dpb_info(struct mtk_vcodec_ctx *ctx, + struct slice_api_h264_decode_param *decode_params, + struct mtk_h264_dpb_info *h264_dpb_info) +{ + const struct slice_h264_dpb_entry *dpb; + struct vb2_queue *vq; + struct vb2_buffer *vb; + struct vb2_v4l2_buffer *vb2_v4l2; + int index; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + for (index = 0; index < V4L2_H264_NUM_DPB_ENTRIES; index++) { + dpb = &decode_params->dpb[index]; + if (!(dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) { + h264_dpb_info[index].reference_flag = 0; + continue; + } + + vb = vb2_find_buffer(vq, dpb->reference_ts); + if (!vb) { + dev_err(&ctx->dev->plat_dev->dev, + "Reference invalid: dpb_index(%d) reference_ts(%lld)", + index, dpb->reference_ts); + continue; + } + + /* 1 for short term reference, 2 for long term reference */ + if (!(dpb->flags & V4L2_H264_DPB_ENTRY_FLAG_LONG_TERM)) + h264_dpb_info[index].reference_flag = 1; + else + h264_dpb_info[index].reference_flag = 2; + + vb2_v4l2 = container_of(vb, struct vb2_v4l2_buffer, vb2_buf); + h264_dpb_info[index].field = vb2_v4l2->field; + + h264_dpb_info[index].y_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 2) + h264_dpb_info[index].c_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + else + h264_dpb_info[index].c_dma_addr = + h264_dpb_info[index].y_dma_addr + + ctx->picinfo.fb_sz[0]; + } +} + +void mtk_vdec_h264_copy_sps_params(struct mtk_h264_sps_param *dst_param, + const struct v4l2_ctrl_h264_sps *src_param) +{ + GET_MTK_VDEC_PARAM(chroma_format_idc); + GET_MTK_VDEC_PARAM(bit_depth_luma_minus8); + GET_MTK_VDEC_PARAM(bit_depth_chroma_minus8); + GET_MTK_VDEC_PARAM(log2_max_frame_num_minus4); + GET_MTK_VDEC_PARAM(pic_order_cnt_type); + GET_MTK_VDEC_PARAM(log2_max_pic_order_cnt_lsb_minus4); + GET_MTK_VDEC_PARAM(max_num_ref_frames); + GET_MTK_VDEC_PARAM(pic_width_in_mbs_minus1); + GET_MTK_VDEC_PARAM(pic_height_in_map_units_minus1); + + GET_MTK_VDEC_FLAG(separate_colour_plane_flag, + V4L2_H264_SPS_FLAG_SEPARATE_COLOUR_PLANE); + GET_MTK_VDEC_FLAG(qpprime_y_zero_transform_bypass_flag, + V4L2_H264_SPS_FLAG_QPPRIME_Y_ZERO_TRANSFORM_BYPASS); + GET_MTK_VDEC_FLAG(delta_pic_order_always_zero_flag, + V4L2_H264_SPS_FLAG_DELTA_PIC_ORDER_ALWAYS_ZERO); + GET_MTK_VDEC_FLAG(frame_mbs_only_flag, + V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY); + GET_MTK_VDEC_FLAG(mb_adaptive_frame_field_flag, + V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD); + GET_MTK_VDEC_FLAG(direct_8x8_inference_flag, + V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE); +} + +void mtk_vdec_h264_copy_pps_params(struct mtk_h264_pps_param *dst_param, + const struct v4l2_ctrl_h264_pps *src_param) +{ + GET_MTK_VDEC_PARAM(num_ref_idx_l0_default_active_minus1); + GET_MTK_VDEC_PARAM(num_ref_idx_l1_default_active_minus1); + GET_MTK_VDEC_PARAM(weighted_bipred_idc); + GET_MTK_VDEC_PARAM(pic_init_qp_minus26); + GET_MTK_VDEC_PARAM(chroma_qp_index_offset); + GET_MTK_VDEC_PARAM(second_chroma_qp_index_offset); + + GET_MTK_VDEC_FLAG(entropy_coding_mode_flag, + V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE); + GET_MTK_VDEC_FLAG(pic_order_present_flag, + V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT); + GET_MTK_VDEC_FLAG(weighted_pred_flag, + V4L2_H264_PPS_FLAG_WEIGHTED_PRED); + GET_MTK_VDEC_FLAG(deblocking_filter_control_present_flag, + V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT); + GET_MTK_VDEC_FLAG(constrained_intra_pred_flag, + V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED); + GET_MTK_VDEC_FLAG(redundant_pic_cnt_present_flag, + V4L2_H264_PPS_FLAG_REDUNDANT_PIC_CNT_PRESENT); + GET_MTK_VDEC_FLAG(transform_8x8_mode_flag, + V4L2_H264_PPS_FLAG_TRANSFORM_8X8_MODE); + GET_MTK_VDEC_FLAG(scaling_matrix_present_flag, + V4L2_H264_PPS_FLAG_SCALING_MATRIX_PRESENT); +} + +void mtk_vdec_h264_copy_slice_hd_params(struct mtk_h264_slice_hd_param *dst_param, + const struct v4l2_ctrl_h264_slice_params *src_param, + const struct v4l2_ctrl_h264_decode_params *dec_param) +{ + int temp; + + GET_MTK_VDEC_PARAM(first_mb_in_slice); + GET_MTK_VDEC_PARAM(slice_type); + GET_MTK_VDEC_PARAM(cabac_init_idc); + GET_MTK_VDEC_PARAM(slice_qp_delta); + GET_MTK_VDEC_PARAM(disable_deblocking_filter_idc); + GET_MTK_VDEC_PARAM(slice_alpha_c0_offset_div2); + GET_MTK_VDEC_PARAM(slice_beta_offset_div2); + GET_MTK_VDEC_PARAM(num_ref_idx_l0_active_minus1); + GET_MTK_VDEC_PARAM(num_ref_idx_l1_active_minus1); + + dst_param->frame_num = dec_param->frame_num; + dst_param->pic_order_cnt_lsb = dec_param->pic_order_cnt_lsb; + + dst_param->delta_pic_order_cnt_bottom = + dec_param->delta_pic_order_cnt_bottom; + dst_param->delta_pic_order_cnt0 = + dec_param->delta_pic_order_cnt0; + dst_param->delta_pic_order_cnt1 = + dec_param->delta_pic_order_cnt1; + + temp = dec_param->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC; + dst_param->field_pic_flag = temp ? 1 : 0; + + temp = dec_param->flags & V4L2_H264_DECODE_PARAM_FLAG_BOTTOM_FIELD; + dst_param->bottom_field_flag = temp ? 1 : 0; + + GET_MTK_VDEC_FLAG(direct_spatial_mv_pred_flag, + V4L2_H264_SLICE_FLAG_DIRECT_SPATIAL_MV_PRED); +} + +void mtk_vdec_h264_copy_scaling_matrix(struct slice_api_h264_scaling_matrix *dst_matrix, + const struct v4l2_ctrl_h264_scaling_matrix *src_matrix) +{ + memcpy(dst_matrix->scaling_list_4x4, src_matrix->scaling_list_4x4, + sizeof(dst_matrix->scaling_list_4x4)); + + memcpy(dst_matrix->scaling_list_8x8, src_matrix->scaling_list_8x8, + sizeof(dst_matrix->scaling_list_8x8)); +} + +void +mtk_vdec_h264_copy_decode_params(struct slice_api_h264_decode_param *dst_params, + const struct v4l2_ctrl_h264_decode_params *src_params, + const struct v4l2_h264_dpb_entry dpb[V4L2_H264_NUM_DPB_ENTRIES]) +{ + struct slice_h264_dpb_entry *dst_entry; + const struct v4l2_h264_dpb_entry *src_entry; + int i; + + for (i = 0; i < ARRAY_SIZE(dst_params->dpb); i++) { + dst_entry = &dst_params->dpb[i]; + src_entry = &dpb[i]; + + dst_entry->reference_ts = src_entry->reference_ts; + dst_entry->frame_num = src_entry->frame_num; + dst_entry->pic_num = src_entry->pic_num; + dst_entry->top_field_order_cnt = src_entry->top_field_order_cnt; + dst_entry->bottom_field_order_cnt = + src_entry->bottom_field_order_cnt; + dst_entry->flags = src_entry->flags; + } + + /* num_slices is a leftover from the old H.264 support and is ignored + * by the firmware. + */ + dst_params->num_slices = 0; + dst_params->nal_ref_idc = src_params->nal_ref_idc; + dst_params->top_field_order_cnt = src_params->top_field_order_cnt; + dst_params->bottom_field_order_cnt = src_params->bottom_field_order_cnt; + dst_params->flags = src_params->flags; +} + +static bool mtk_vdec_h264_dpb_entry_match(const struct v4l2_h264_dpb_entry *a, + const struct v4l2_h264_dpb_entry *b) +{ + return a->top_field_order_cnt == b->top_field_order_cnt && + a->bottom_field_order_cnt == b->bottom_field_order_cnt; +} + +/* + * Move DPB entries of dec_param that refer to a frame already existing in dpb + * into the already existing slot in dpb, and move other entries into new slots. + * + * This function is an adaptation of the similarly-named function in + * hantro_h264.c. + */ +void mtk_vdec_h264_update_dpb(const struct v4l2_ctrl_h264_decode_params *dec_param, + struct v4l2_h264_dpb_entry *dpb) +{ + DECLARE_BITMAP(new, ARRAY_SIZE(dec_param->dpb)) = { 0, }; + DECLARE_BITMAP(in_use, ARRAY_SIZE(dec_param->dpb)) = { 0, }; + DECLARE_BITMAP(used, ARRAY_SIZE(dec_param->dpb)) = { 0, }; + unsigned int i, j; + + /* Disable all entries by default, and mark the ones in use. */ + for (i = 0; i < ARRAY_SIZE(dec_param->dpb); i++) { + if (dpb[i].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE) + set_bit(i, in_use); + dpb[i].flags &= ~V4L2_H264_DPB_ENTRY_FLAG_ACTIVE; + } + + /* Try to match new DPB entries with existing ones by their POCs. */ + for (i = 0; i < ARRAY_SIZE(dec_param->dpb); i++) { + const struct v4l2_h264_dpb_entry *ndpb = &dec_param->dpb[i]; + + if (!(ndpb->flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE)) + continue; + + /* + * To cut off some comparisons, iterate only on target DPB + * entries were already used. + */ + for_each_set_bit(j, in_use, ARRAY_SIZE(dec_param->dpb)) { + struct v4l2_h264_dpb_entry *cdpb; + + cdpb = &dpb[j]; + if (!mtk_vdec_h264_dpb_entry_match(cdpb, ndpb)) + continue; + + *cdpb = *ndpb; + set_bit(j, used); + /* Don't reiterate on this one. */ + clear_bit(j, in_use); + break; + } + + if (j == ARRAY_SIZE(dec_param->dpb)) + set_bit(i, new); + } + + /* For entries that could not be matched, use remaining free slots. */ + for_each_set_bit(i, new, ARRAY_SIZE(dec_param->dpb)) { + const struct v4l2_h264_dpb_entry *ndpb = &dec_param->dpb[i]; + struct v4l2_h264_dpb_entry *cdpb; + + /* + * Both arrays are of the same sizes, so there is no way + * we can end up with no space in target array, unless + * something is buggy. + */ + j = find_first_zero_bit(used, ARRAY_SIZE(dec_param->dpb)); + if (WARN_ON(j >= ARRAY_SIZE(dec_param->dpb))) + return; + + cdpb = &dpb[j]; + *cdpb = *ndpb; + set_bit(j, used); + } +} + +unsigned int mtk_vdec_h264_get_mv_buf_size(unsigned int width, unsigned int height) +{ + int unit_size = (width / MB_UNIT_LEN) * (height / MB_UNIT_LEN) + 8; + + return HW_MB_STORE_SZ * unit_size; +} + +int mtk_vdec_h264_find_start_code(unsigned char *data, unsigned int data_sz) +{ + if (data_sz > 3 && data[0] == 0 && data[1] == 0 && data[2] == 1) + return 3; + + if (data_sz > 4 && data[0] == 0 && data[1] == 0 && data[2] == 0 && + data[3] == 1) + return 4; + + return -1; +} diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_common.h b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_common.h new file mode 100644 index 000000000..53d0a7c96 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_common.h @@ -0,0 +1,277 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Yunfei Dong + */ + +#ifndef _VDEC_H264_REQ_COMMON_H_ +#define _VDEC_H264_REQ_COMMON_H_ + +#include +#include +#include +#include +#include + +#include "../mtk_vcodec_drv.h" + +#define NAL_NON_IDR_SLICE 0x01 +#define NAL_IDR_SLICE 0x05 +#define NAL_TYPE(value) ((value) & 0x1F) + +#define BUF_PREDICTION_SZ (64 * 4096) +#define MB_UNIT_LEN 16 + +/* motion vector size (bytes) for every macro block */ +#define HW_MB_STORE_SZ 64 + +#define H264_MAX_MV_NUM 32 + +/** + * struct mtk_h264_dpb_info - h264 dpb information + * + * @y_dma_addr: Y bitstream physical address + * @c_dma_addr: CbCr bitstream physical address + * @reference_flag: reference picture flag (short/long term reference picture) + * @field: field picture flag + */ +struct mtk_h264_dpb_info { + dma_addr_t y_dma_addr; + dma_addr_t c_dma_addr; + int reference_flag; + int field; +}; + +/* + * struct mtk_h264_sps_param - parameters for sps + */ +struct mtk_h264_sps_param { + unsigned char chroma_format_idc; + unsigned char bit_depth_luma_minus8; + unsigned char bit_depth_chroma_minus8; + unsigned char log2_max_frame_num_minus4; + unsigned char pic_order_cnt_type; + unsigned char log2_max_pic_order_cnt_lsb_minus4; + unsigned char max_num_ref_frames; + unsigned char separate_colour_plane_flag; + unsigned short pic_width_in_mbs_minus1; + unsigned short pic_height_in_map_units_minus1; + unsigned int max_frame_nums; + unsigned char qpprime_y_zero_transform_bypass_flag; + unsigned char delta_pic_order_always_zero_flag; + unsigned char frame_mbs_only_flag; + unsigned char mb_adaptive_frame_field_flag; + unsigned char direct_8x8_inference_flag; + unsigned char reserved[3]; +}; + +/* + * struct mtk_h264_pps_param - parameters for pps + */ +struct mtk_h264_pps_param { + unsigned char num_ref_idx_l0_default_active_minus1; + unsigned char num_ref_idx_l1_default_active_minus1; + unsigned char weighted_bipred_idc; + char pic_init_qp_minus26; + char chroma_qp_index_offset; + char second_chroma_qp_index_offset; + unsigned char entropy_coding_mode_flag; + unsigned char pic_order_present_flag; + unsigned char deblocking_filter_control_present_flag; + unsigned char constrained_intra_pred_flag; + unsigned char weighted_pred_flag; + unsigned char redundant_pic_cnt_present_flag; + unsigned char transform_8x8_mode_flag; + unsigned char scaling_matrix_present_flag; + unsigned char reserved[2]; +}; + +/* + * struct mtk_h264_slice_hd_param - parameters for slice header + */ +struct mtk_h264_slice_hd_param { + unsigned int first_mb_in_slice; + unsigned int field_pic_flag; + unsigned int slice_type; + unsigned int frame_num; + int pic_order_cnt_lsb; + int delta_pic_order_cnt_bottom; + unsigned int bottom_field_flag; + unsigned int direct_spatial_mv_pred_flag; + int delta_pic_order_cnt0; + int delta_pic_order_cnt1; + unsigned int cabac_init_idc; + int slice_qp_delta; + unsigned int disable_deblocking_filter_idc; + int slice_alpha_c0_offset_div2; + int slice_beta_offset_div2; + unsigned int num_ref_idx_l0_active_minus1; + unsigned int num_ref_idx_l1_active_minus1; + unsigned int reserved; +}; + +/* + * struct slice_api_h264_scaling_matrix - parameters for scaling list + */ +struct slice_api_h264_scaling_matrix { + unsigned char scaling_list_4x4[6][16]; + unsigned char scaling_list_8x8[6][64]; +}; + +/* + * struct slice_h264_dpb_entry - each dpb information + */ +struct slice_h264_dpb_entry { + unsigned long long reference_ts; + unsigned short frame_num; + unsigned short pic_num; + /* Note that field is indicated by v4l2_buffer.field */ + int top_field_order_cnt; + int bottom_field_order_cnt; + unsigned int flags; +}; + +/* + * struct slice_api_h264_decode_param - parameters for decode. + */ +struct slice_api_h264_decode_param { + struct slice_h264_dpb_entry dpb[V4L2_H264_NUM_DPB_ENTRIES]; + unsigned short num_slices; + unsigned short nal_ref_idc; + unsigned char ref_pic_list_p0[32]; + unsigned char ref_pic_list_b0[32]; + unsigned char ref_pic_list_b1[32]; + int top_field_order_cnt; + int bottom_field_order_cnt; + unsigned int flags; +}; + +/** + * struct h264_fb - h264 decode frame buffer information + * + * @vdec_fb_va: virtual address of struct vdec_fb + * @y_fb_dma: dma address of Y frame buffer (luma) + * @c_fb_dma: dma address of C frame buffer (chroma) + * @poc: picture order count of frame buffer + * @reserved: for 8 bytes alignment + */ +struct h264_fb { + u64 vdec_fb_va; + u64 y_fb_dma; + u64 c_fb_dma; + s32 poc; + u32 reserved; +}; + +/** + * mtk_vdec_h264_get_ref_list - translate V4L2 reference list + * + * @ref_list: Mediatek reference picture list + * @v4l2_ref_list: V4L2 reference picture list + * @num_valid: used reference number + */ +void mtk_vdec_h264_get_ref_list(u8 *ref_list, + const struct v4l2_h264_reference *v4l2_ref_list, + int num_valid); + +/** + * mtk_vdec_h264_get_ctrl_ptr - get each CID contrl address. + * + * @ctx: v4l2 ctx + * @id: CID control ID + * + * Return: returns CID ctrl address. + */ +void *mtk_vdec_h264_get_ctrl_ptr(struct mtk_vcodec_ctx *ctx, int id); + +/** + * mtk_vdec_h264_fill_dpb_info - get each CID contrl address. + * + * @ctx: v4l2 ctx + * @decode_params: slice decode params + * @h264_dpb_info: dpb buffer information + */ +void mtk_vdec_h264_fill_dpb_info(struct mtk_vcodec_ctx *ctx, + struct slice_api_h264_decode_param *decode_params, + struct mtk_h264_dpb_info *h264_dpb_info); + +/** + * mtk_vdec_h264_copy_sps_params - get sps params. + * + * @dst_param: sps params for hw decoder + * @src_param: sps params from user driver + */ +void mtk_vdec_h264_copy_sps_params(struct mtk_h264_sps_param *dst_param, + const struct v4l2_ctrl_h264_sps *src_param); + +/** + * mtk_vdec_h264_copy_pps_params - get pps params. + * + * @dst_param: pps params for hw decoder + * @src_param: pps params from user driver + */ +void mtk_vdec_h264_copy_pps_params(struct mtk_h264_pps_param *dst_param, + const struct v4l2_ctrl_h264_pps *src_param); + +/** + * mtk_vdec_h264_copy_slice_hd_params - get slice header params. + * + * @dst_param: slice params for hw decoder + * @src_param: slice params from user driver + * @dec_param: decode params from user driver + */ +void mtk_vdec_h264_copy_slice_hd_params(struct mtk_h264_slice_hd_param *dst_param, + const struct v4l2_ctrl_h264_slice_params *src_param, + const struct v4l2_ctrl_h264_decode_params *dec_param); + +/** + * mtk_vdec_h264_copy_scaling_matrix - get each CID contrl address. + * + * @dst_matrix: scaling list params for hw decoder + * @src_matrix: scaling list params from user driver + */ +void mtk_vdec_h264_copy_scaling_matrix(struct slice_api_h264_scaling_matrix *dst_matrix, + const struct v4l2_ctrl_h264_scaling_matrix *src_matrix); + +/** + * mtk_vdec_h264_copy_decode_params - get decode params. + * + * @dst_params: dst params for hw decoder + * @src_params: decode params from user driver + * @dpb: dpb information + */ +void +mtk_vdec_h264_copy_decode_params(struct slice_api_h264_decode_param *dst_params, + const struct v4l2_ctrl_h264_decode_params *src_params, + const struct v4l2_h264_dpb_entry dpb[V4L2_H264_NUM_DPB_ENTRIES]); + +/** + * mtk_vdec_h264_update_dpb - updata dpb list. + * + * @dec_param: v4l2 control decode params + * @dpb: dpb entry informaton + */ +void mtk_vdec_h264_update_dpb(const struct v4l2_ctrl_h264_decode_params *dec_param, + struct v4l2_h264_dpb_entry *dpb); + +/** + * mtk_vdec_h264_find_start_code - find h264 start code using sofeware. + * + * @data: input buffer address + * @data_sz: input buffer size + * + * Return: returns start code position. + */ +int mtk_vdec_h264_find_start_code(unsigned char *data, unsigned int data_sz); + +/** + * mtk_vdec_h264_get_mv_buf_size - get mv buffer size. + * + * @width: picture width + * @height: picture height + * + * Return: returns mv buffer size. + */ +unsigned int mtk_vdec_h264_get_mv_buf_size(unsigned int width, unsigned int height); + +#endif diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_if.c new file mode 100644 index 000000000..4bc05ab5a --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_if.c @@ -0,0 +1,449 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include +#include +#include + +#include "../mtk_vcodec_util.h" +#include "../mtk_vcodec_dec.h" +#include "../mtk_vcodec_intr.h" +#include "../vdec_drv_base.h" +#include "../vdec_drv_if.h" +#include "../vdec_vpu_if.h" +#include "vdec_h264_req_common.h" + +/* + * struct mtk_h264_dec_slice_param - parameters for decode current frame + */ +struct mtk_h264_dec_slice_param { + struct mtk_h264_sps_param sps; + struct mtk_h264_pps_param pps; + struct slice_api_h264_scaling_matrix scaling_matrix; + struct slice_api_h264_decode_param decode_params; + struct mtk_h264_dpb_info h264_dpb_info[16]; +}; + +/** + * struct vdec_h264_dec_info - decode information + * @dpb_sz : decoding picture buffer size + * @resolution_changed : resoltion change happen + * @realloc_mv_buf : flag to notify driver to re-allocate mv buffer + * @cap_num_planes : number planes of capture buffer + * @bs_dma : Input bit-stream buffer dma address + * @y_fb_dma : Y frame buffer dma address + * @c_fb_dma : C frame buffer dma address + * @vdec_fb_va : VDEC frame buffer struct virtual address + */ +struct vdec_h264_dec_info { + u32 dpb_sz; + u32 resolution_changed; + u32 realloc_mv_buf; + u32 cap_num_planes; + u64 bs_dma; + u64 y_fb_dma; + u64 c_fb_dma; + u64 vdec_fb_va; +}; + +/** + * struct vdec_h264_vsi - shared memory for decode information exchange + * between VPU and Host. + * The memory is allocated by VPU then mapping to Host + * in vpu_dec_init() and freed in vpu_dec_deinit() + * by VPU. + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @pred_buf_dma : HW working predication buffer dma address (AP-W, VPU-R) + * @mv_buf_dma : HW working motion vector buffer dma address (AP-W, VPU-R) + * @dec : decode information (AP-R, VPU-W) + * @pic : picture information (AP-R, VPU-W) + * @crop : crop information (AP-R, VPU-W) + * @h264_slice_params : the parameters that hardware use to decode + */ +struct vdec_h264_vsi { + u64 pred_buf_dma; + u64 mv_buf_dma[H264_MAX_MV_NUM]; + struct vdec_h264_dec_info dec; + struct vdec_pic_info pic; + struct v4l2_rect crop; + struct mtk_h264_dec_slice_param h264_slice_params; +}; + +/** + * struct vdec_h264_slice_inst - h264 decoder instance + * @num_nalu : how many nalus be decoded + * @ctx : point to mtk_vcodec_ctx + * @pred_buf : HW working predication buffer + * @mv_buf : HW working motion vector buffer + * @vpu : VPU instance + * @vsi_ctx : Local VSI data for this decoding context + * @h264_slice_param : the parameters that hardware use to decode + * @dpb : decoded picture buffer used to store reference buffer information + */ +struct vdec_h264_slice_inst { + unsigned int num_nalu; + struct mtk_vcodec_ctx *ctx; + struct mtk_vcodec_mem pred_buf; + struct mtk_vcodec_mem mv_buf[H264_MAX_MV_NUM]; + struct vdec_vpu_inst vpu; + struct vdec_h264_vsi vsi_ctx; + struct mtk_h264_dec_slice_param h264_slice_param; + + struct v4l2_h264_dpb_entry dpb[16]; +}; + +static int get_vdec_decode_parameters(struct vdec_h264_slice_inst *inst) +{ + const struct v4l2_ctrl_h264_decode_params *dec_params; + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; + const struct v4l2_ctrl_h264_scaling_matrix *scaling_matrix; + struct mtk_h264_dec_slice_param *slice_param = &inst->h264_slice_param; + struct v4l2_h264_reflist_builder reflist_builder; + struct v4l2_h264_reference v4l2_p0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b1_reflist[V4L2_H264_REF_LIST_LEN]; + u8 *p0_reflist = slice_param->decode_params.ref_pic_list_p0; + u8 *b0_reflist = slice_param->decode_params.ref_pic_list_b0; + u8 *b1_reflist = slice_param->decode_params.ref_pic_list_b1; + + dec_params = + mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_DECODE_PARAMS); + if (IS_ERR(dec_params)) + return PTR_ERR(dec_params); + + sps = mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_SPS); + if (IS_ERR(sps)) + return PTR_ERR(sps); + + pps = mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_PPS); + if (IS_ERR(pps)) + return PTR_ERR(pps); + + scaling_matrix = + mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_SCALING_MATRIX); + if (IS_ERR(scaling_matrix)) + return PTR_ERR(scaling_matrix); + + mtk_vdec_h264_update_dpb(dec_params, inst->dpb); + + mtk_vdec_h264_copy_sps_params(&slice_param->sps, sps); + mtk_vdec_h264_copy_pps_params(&slice_param->pps, pps); + mtk_vdec_h264_copy_scaling_matrix(&slice_param->scaling_matrix, scaling_matrix); + mtk_vdec_h264_copy_decode_params(&slice_param->decode_params, + dec_params, inst->dpb); + mtk_vdec_h264_fill_dpb_info(inst->ctx, &slice_param->decode_params, + slice_param->h264_dpb_info); + + /* Build the reference lists */ + v4l2_h264_init_reflist_builder(&reflist_builder, dec_params, sps, + inst->dpb); + v4l2_h264_build_p_ref_list(&reflist_builder, v4l2_p0_reflist); + v4l2_h264_build_b_ref_lists(&reflist_builder, v4l2_b0_reflist, + v4l2_b1_reflist); + + /* Adapt the built lists to the firmware's expectations */ + mtk_vdec_h264_get_ref_list(p0_reflist, v4l2_p0_reflist, reflist_builder.num_valid); + mtk_vdec_h264_get_ref_list(b0_reflist, v4l2_b0_reflist, reflist_builder.num_valid); + mtk_vdec_h264_get_ref_list(b1_reflist, v4l2_b1_reflist, reflist_builder.num_valid); + + memcpy(&inst->vsi_ctx.h264_slice_params, slice_param, + sizeof(inst->vsi_ctx.h264_slice_params)); + + return 0; +} + +static int allocate_predication_buf(struct vdec_h264_slice_inst *inst) +{ + int err; + + inst->pred_buf.size = BUF_PREDICTION_SZ; + err = mtk_vcodec_mem_alloc(inst->ctx, &inst->pred_buf); + if (err) { + mtk_vcodec_err(inst, "failed to allocate ppl buf"); + return err; + } + + inst->vsi_ctx.pred_buf_dma = inst->pred_buf.dma_addr; + return 0; +} + +static void free_predication_buf(struct vdec_h264_slice_inst *inst) +{ + struct mtk_vcodec_mem *mem = &inst->pred_buf; + + mtk_vcodec_debug_enter(inst); + + inst->vsi_ctx.pred_buf_dma = 0; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); +} + +static int alloc_mv_buf(struct vdec_h264_slice_inst *inst, + struct vdec_pic_info *pic) +{ + int i; + int err; + struct mtk_vcodec_mem *mem = NULL; + unsigned int buf_sz = mtk_vdec_h264_get_mv_buf_size(pic->buf_w, pic->buf_h); + + mtk_v4l2_debug(3, "size = 0x%x", buf_sz); + for (i = 0; i < H264_MAX_MV_NUM; i++) { + mem = &inst->mv_buf[i]; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + mem->size = buf_sz; + err = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (err) { + mtk_vcodec_err(inst, "failed to allocate mv buf"); + return err; + } + inst->vsi_ctx.mv_buf_dma[i] = mem->dma_addr; + } + + return 0; +} + +static void free_mv_buf(struct vdec_h264_slice_inst *inst) +{ + int i; + struct mtk_vcodec_mem *mem; + + for (i = 0; i < H264_MAX_MV_NUM; i++) { + inst->vsi_ctx.mv_buf_dma[i] = 0; + mem = &inst->mv_buf[i]; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + } +} + +static void get_pic_info(struct vdec_h264_slice_inst *inst, + struct vdec_pic_info *pic) +{ + struct mtk_vcodec_ctx *ctx = inst->ctx; + + ctx->picinfo.buf_w = ALIGN(ctx->picinfo.pic_w, VCODEC_DEC_ALIGNED_64); + ctx->picinfo.buf_h = ALIGN(ctx->picinfo.pic_h, VCODEC_DEC_ALIGNED_64); + ctx->picinfo.fb_sz[0] = ctx->picinfo.buf_w * ctx->picinfo.buf_h; + ctx->picinfo.fb_sz[1] = ctx->picinfo.fb_sz[0] >> 1; + inst->vsi_ctx.dec.cap_num_planes = + ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes; + + *pic = ctx->picinfo; + mtk_vcodec_debug(inst, "pic(%d, %d), buf(%d, %d)", + ctx->picinfo.pic_w, ctx->picinfo.pic_h, + ctx->picinfo.buf_w, ctx->picinfo.buf_h); + mtk_vcodec_debug(inst, "Y/C(%d, %d)", ctx->picinfo.fb_sz[0], + ctx->picinfo.fb_sz[1]); + + if (ctx->last_decoded_picinfo.pic_w != ctx->picinfo.pic_w || + ctx->last_decoded_picinfo.pic_h != ctx->picinfo.pic_h) { + inst->vsi_ctx.dec.resolution_changed = true; + if (ctx->last_decoded_picinfo.buf_w != ctx->picinfo.buf_w || + ctx->last_decoded_picinfo.buf_h != ctx->picinfo.buf_h) + inst->vsi_ctx.dec.realloc_mv_buf = true; + + mtk_v4l2_debug(1, "ResChg: (%d %d) : old(%d, %d) -> new(%d, %d)", + inst->vsi_ctx.dec.resolution_changed, + inst->vsi_ctx.dec.realloc_mv_buf, + ctx->last_decoded_picinfo.pic_w, + ctx->last_decoded_picinfo.pic_h, + ctx->picinfo.pic_w, ctx->picinfo.pic_h); + } +} + +static void get_crop_info(struct vdec_h264_slice_inst *inst, struct v4l2_rect *cr) +{ + cr->left = inst->vsi_ctx.crop.left; + cr->top = inst->vsi_ctx.crop.top; + cr->width = inst->vsi_ctx.crop.width; + cr->height = inst->vsi_ctx.crop.height; + + mtk_vcodec_debug(inst, "l=%d, t=%d, w=%d, h=%d", + cr->left, cr->top, cr->width, cr->height); +} + +static void get_dpb_size(struct vdec_h264_slice_inst *inst, unsigned int *dpb_sz) +{ + *dpb_sz = inst->vsi_ctx.dec.dpb_sz; + mtk_vcodec_debug(inst, "sz=%d", *dpb_sz); +} + +static int vdec_h264_slice_init(struct mtk_vcodec_ctx *ctx) +{ + struct vdec_h264_slice_inst *inst; + int err; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->ctx = ctx; + + inst->vpu.id = SCP_IPI_VDEC_H264; + inst->vpu.ctx = ctx; + + err = vpu_dec_init(&inst->vpu); + if (err) { + mtk_vcodec_err(inst, "vdec_h264 init err=%d", err); + goto error_free_inst; + } + + memcpy(&inst->vsi_ctx, inst->vpu.vsi, sizeof(inst->vsi_ctx)); + inst->vsi_ctx.dec.resolution_changed = true; + inst->vsi_ctx.dec.realloc_mv_buf = true; + + err = allocate_predication_buf(inst); + if (err) + goto error_deinit; + + mtk_vcodec_debug(inst, "struct size = %zu,%zu,%zu,%zu\n", + sizeof(struct mtk_h264_sps_param), + sizeof(struct mtk_h264_pps_param), + sizeof(struct mtk_h264_dec_slice_param), + sizeof(struct mtk_h264_dpb_info)); + + mtk_vcodec_debug(inst, "H264 Instance >> %p", inst); + + ctx->drv_handle = inst; + return 0; + +error_deinit: + vpu_dec_deinit(&inst->vpu); + +error_free_inst: + kfree(inst); + return err; +} + +static void vdec_h264_slice_deinit(void *h_vdec) +{ + struct vdec_h264_slice_inst *inst = h_vdec; + + mtk_vcodec_debug_enter(inst); + + vpu_dec_deinit(&inst->vpu); + free_predication_buf(inst); + free_mv_buf(inst); + + kfree(inst); +} + +static int vdec_h264_slice_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *unused, bool *res_chg) +{ + struct vdec_h264_slice_inst *inst = h_vdec; + const struct v4l2_ctrl_h264_decode_params *dec_params = + mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_DECODE_PARAMS); + struct vdec_vpu_inst *vpu = &inst->vpu; + struct mtk_video_dec_buf *src_buf_info; + struct mtk_video_dec_buf *dst_buf_info; + struct vdec_fb *fb; + u32 data[2]; + u64 y_fb_dma; + u64 c_fb_dma; + int err; + + inst->num_nalu++; + /* bs NULL means flush decoder */ + if (!bs) + return vpu_dec_reset(vpu); + + fb = inst->ctx->dev->vdec_pdata->get_cap_buffer(inst->ctx); + src_buf_info = container_of(bs, struct mtk_video_dec_buf, bs_buffer); + dst_buf_info = container_of(fb, struct mtk_video_dec_buf, frame_buffer); + + y_fb_dma = fb ? (u64)fb->base_y.dma_addr : 0; + c_fb_dma = fb ? (u64)fb->base_c.dma_addr : 0; + + mtk_vcodec_debug(inst, "+ [%d] FB y_dma=%llx c_dma=%llx va=%p", + inst->num_nalu, y_fb_dma, c_fb_dma, fb); + + inst->vsi_ctx.dec.bs_dma = (uint64_t)bs->dma_addr; + inst->vsi_ctx.dec.y_fb_dma = y_fb_dma; + inst->vsi_ctx.dec.c_fb_dma = c_fb_dma; + inst->vsi_ctx.dec.vdec_fb_va = (u64)(uintptr_t)fb; + + v4l2_m2m_buf_copy_metadata(&src_buf_info->m2m_buf.vb, + &dst_buf_info->m2m_buf.vb, true); + err = get_vdec_decode_parameters(inst); + if (err) + goto err_free_fb_out; + + data[0] = bs->size; + /* + * Reconstruct the first byte of the NAL unit, as the firmware requests + * that information to be passed even though it is present in the stream + * itself... + */ + data[1] = (dec_params->nal_ref_idc << 5) | + ((dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_IDR_PIC) + ? 0x5 : 0x1); + + *res_chg = inst->vsi_ctx.dec.resolution_changed; + if (*res_chg) { + mtk_vcodec_debug(inst, "- resolution changed -"); + if (inst->vsi_ctx.dec.realloc_mv_buf) { + err = alloc_mv_buf(inst, &inst->ctx->picinfo); + inst->vsi_ctx.dec.realloc_mv_buf = false; + if (err) + goto err_free_fb_out; + } + *res_chg = false; + } + + memcpy(inst->vpu.vsi, &inst->vsi_ctx, sizeof(inst->vsi_ctx)); + err = vpu_dec_start(vpu, data, 2); + if (err) + goto err_free_fb_out; + + /* wait decoder done interrupt */ + err = mtk_vcodec_wait_for_done_ctx(inst->ctx, + MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, 0); + if (err) + goto err_free_fb_out; + vpu_dec_end(vpu); + + memcpy(&inst->vsi_ctx, inst->vpu.vsi, sizeof(inst->vsi_ctx)); + mtk_vcodec_debug(inst, "\n - NALU[%d]", inst->num_nalu); + return 0; + +err_free_fb_out: + mtk_vcodec_err(inst, "\n - NALU[%d] err=%d -\n", inst->num_nalu, err); + return err; +} + +static int vdec_h264_slice_get_param(void *h_vdec, enum vdec_get_param_type type, void *out) +{ + struct vdec_h264_slice_inst *inst = h_vdec; + + switch (type) { + case GET_PARAM_PIC_INFO: + get_pic_info(inst, out); + break; + + case GET_PARAM_DPB_SIZE: + get_dpb_size(inst, out); + break; + + case GET_PARAM_CROP_INFO: + get_crop_info(inst, out); + break; + + default: + mtk_vcodec_err(inst, "invalid get parameter type=%d", type); + return -EINVAL; + } + + return 0; +} + +const struct vdec_common_if vdec_h264_slice_if = { + .init = vdec_h264_slice_init, + .decode = vdec_h264_slice_decode, + .get_param = vdec_h264_slice_get_param, + .deinit = vdec_h264_slice_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c new file mode 100644 index 000000000..999ce7ee5 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_h264_req_multi_if.c @@ -0,0 +1,855 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2022 MediaTek Inc. + * Author: Yunfei Dong + */ + +#include +#include +#include +#include +#include + +#include "../mtk_vcodec_util.h" +#include "../mtk_vcodec_dec.h" +#include "../mtk_vcodec_intr.h" +#include "../vdec_drv_base.h" +#include "../vdec_drv_if.h" +#include "../vdec_vpu_if.h" +#include "vdec_h264_req_common.h" + +/** + * enum vdec_h264_core_dec_err_type - core decode error type + * + * @TRANS_BUFFER_FULL: trans buffer is full + * @SLICE_HEADER_FULL: slice header buffer is full + */ +enum vdec_h264_core_dec_err_type { + TRANS_BUFFER_FULL = 1, + SLICE_HEADER_FULL, +}; + +/** + * struct vdec_h264_slice_lat_dec_param - parameters for decode current frame + * + * @sps: h264 sps syntax parameters + * @pps: h264 pps syntax parameters + * @slice_header: h264 slice header syntax parameters + * @scaling_matrix: h264 scaling list parameters + * @decode_params: decoder parameters of each frame used for hardware decode + * @h264_dpb_info: dpb reference list + */ +struct vdec_h264_slice_lat_dec_param { + struct mtk_h264_sps_param sps; + struct mtk_h264_pps_param pps; + struct mtk_h264_slice_hd_param slice_header; + struct slice_api_h264_scaling_matrix scaling_matrix; + struct slice_api_h264_decode_param decode_params; + struct mtk_h264_dpb_info h264_dpb_info[V4L2_H264_NUM_DPB_ENTRIES]; +}; + +/** + * struct vdec_h264_slice_info - decode information + * + * @nal_info: nal info of current picture + * @timeout: Decode timeout: 1 timeout, 0 no timeount + * @bs_buf_size: bitstream size + * @bs_buf_addr: bitstream buffer dma address + * @y_fb_dma: Y frame buffer dma address + * @c_fb_dma: C frame buffer dma address + * @vdec_fb_va: VDEC frame buffer struct virtual address + * @crc: Used to check whether hardware's status is right + */ +struct vdec_h264_slice_info { + u16 nal_info; + u16 timeout; + u32 bs_buf_size; + u64 bs_buf_addr; + u64 y_fb_dma; + u64 c_fb_dma; + u64 vdec_fb_va; + u32 crc[8]; +}; + +/** + * struct vdec_h264_slice_vsi - shared memory for decode information exchange + * between SCP and Host. + * + * @wdma_err_addr: wdma error dma address + * @wdma_start_addr: wdma start dma address + * @wdma_end_addr: wdma end dma address + * @slice_bc_start_addr: slice bc start dma address + * @slice_bc_end_addr: slice bc end dma address + * @row_info_start_addr: row info start dma address + * @row_info_end_addr: row info end dma address + * @trans_start: trans start dma address + * @trans_end: trans end dma address + * @wdma_end_addr_offset: wdma end address offset + * + * @mv_buf_dma: HW working motion vector buffer + * dma address (AP-W, VPU-R) + * @dec: decode information (AP-R, VPU-W) + * @h264_slice_params: decode parameters for hw used + */ +struct vdec_h264_slice_vsi { + /* LAT dec addr */ + u64 wdma_err_addr; + u64 wdma_start_addr; + u64 wdma_end_addr; + u64 slice_bc_start_addr; + u64 slice_bc_end_addr; + u64 row_info_start_addr; + u64 row_info_end_addr; + u64 trans_start; + u64 trans_end; + u64 wdma_end_addr_offset; + + u64 mv_buf_dma[H264_MAX_MV_NUM]; + struct vdec_h264_slice_info dec; + struct vdec_h264_slice_lat_dec_param h264_slice_params; +}; + +/** + * struct vdec_h264_slice_share_info - shared information used to exchange + * message between lat and core + * + * @sps: sequence header information from user space + * @dec_params: decoder params from user space + * @h264_slice_params: decoder params used for hardware + * @trans_start: trans start dma address + * @trans_end: trans end dma address + * @nal_info: nal info of current picture + */ +struct vdec_h264_slice_share_info { + struct v4l2_ctrl_h264_sps sps; + struct v4l2_ctrl_h264_decode_params dec_params; + struct vdec_h264_slice_lat_dec_param h264_slice_params; + u64 trans_start; + u64 trans_end; + u16 nal_info; +}; + +/** + * struct vdec_h264_slice_inst - h264 decoder instance + * + * @slice_dec_num: how many picture be decoded + * @ctx: point to mtk_vcodec_ctx + * @pred_buf: HW working predication buffer + * @mv_buf: HW working motion vector buffer + * @vpu: VPU instance + * @vsi: vsi used for lat + * @vsi_core: vsi used for core + * + * @vsi_ctx: Local VSI data for this decoding context + * @h264_slice_param: the parameters that hardware use to decode + * + * @resolution_changed:resolution changed + * @realloc_mv_buf: reallocate mv buffer + * @cap_num_planes: number of capture queue plane + * + * @dpb: decoded picture buffer used to store reference + * buffer information + *@is_field_bitstream: is field bitstream + */ +struct vdec_h264_slice_inst { + unsigned int slice_dec_num; + struct mtk_vcodec_ctx *ctx; + struct mtk_vcodec_mem pred_buf; + struct mtk_vcodec_mem mv_buf[H264_MAX_MV_NUM]; + struct vdec_vpu_inst vpu; + struct vdec_h264_slice_vsi *vsi; + struct vdec_h264_slice_vsi *vsi_core; + + struct vdec_h264_slice_vsi vsi_ctx; + struct vdec_h264_slice_lat_dec_param h264_slice_param; + + unsigned int resolution_changed; + unsigned int realloc_mv_buf; + unsigned int cap_num_planes; + + struct v4l2_h264_dpb_entry dpb[16]; + bool is_field_bitstream; +}; + +static int vdec_h264_slice_fill_decode_parameters(struct vdec_h264_slice_inst *inst, + struct vdec_h264_slice_share_info *share_info) +{ + struct vdec_h264_slice_lat_dec_param *slice_param = &inst->vsi->h264_slice_params; + const struct v4l2_ctrl_h264_decode_params *dec_params; + const struct v4l2_ctrl_h264_scaling_matrix *src_matrix; + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; + + dec_params = + mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_DECODE_PARAMS); + if (IS_ERR(dec_params)) + return PTR_ERR(dec_params); + + src_matrix = + mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_SCALING_MATRIX); + if (IS_ERR(src_matrix)) + return PTR_ERR(src_matrix); + + sps = mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_SPS); + if (IS_ERR(sps)) + return PTR_ERR(sps); + + pps = mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_PPS); + if (IS_ERR(pps)) + return PTR_ERR(pps); + + if (dec_params->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC) { + mtk_vcodec_err(inst, "No support for H.264 field decoding."); + inst->is_field_bitstream = true; + return -EINVAL; + } + + mtk_vdec_h264_copy_sps_params(&slice_param->sps, sps); + mtk_vdec_h264_copy_pps_params(&slice_param->pps, pps); + mtk_vdec_h264_copy_scaling_matrix(&slice_param->scaling_matrix, src_matrix); + + memcpy(&share_info->sps, sps, sizeof(*sps)); + memcpy(&share_info->dec_params, dec_params, sizeof(*dec_params)); + + return 0; +} + +static int get_vdec_sig_decode_parameters(struct vdec_h264_slice_inst *inst) +{ + const struct v4l2_ctrl_h264_decode_params *dec_params; + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; + const struct v4l2_ctrl_h264_scaling_matrix *scaling_matrix; + struct vdec_h264_slice_lat_dec_param *slice_param = &inst->h264_slice_param; + struct v4l2_h264_reflist_builder reflist_builder; + struct v4l2_h264_reference v4l2_p0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b1_reflist[V4L2_H264_REF_LIST_LEN]; + u8 *p0_reflist = slice_param->decode_params.ref_pic_list_p0; + u8 *b0_reflist = slice_param->decode_params.ref_pic_list_b0; + u8 *b1_reflist = slice_param->decode_params.ref_pic_list_b1; + + dec_params = + mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_DECODE_PARAMS); + if (IS_ERR(dec_params)) + return PTR_ERR(dec_params); + + sps = mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_SPS); + if (IS_ERR(sps)) + return PTR_ERR(sps); + + pps = mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_PPS); + if (IS_ERR(pps)) + return PTR_ERR(pps); + + scaling_matrix = + mtk_vdec_h264_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_H264_SCALING_MATRIX); + if (IS_ERR(scaling_matrix)) + return PTR_ERR(scaling_matrix); + + mtk_vdec_h264_update_dpb(dec_params, inst->dpb); + + mtk_vdec_h264_copy_sps_params(&slice_param->sps, sps); + mtk_vdec_h264_copy_pps_params(&slice_param->pps, pps); + mtk_vdec_h264_copy_scaling_matrix(&slice_param->scaling_matrix, scaling_matrix); + + mtk_vdec_h264_copy_decode_params(&slice_param->decode_params, dec_params, inst->dpb); + mtk_vdec_h264_fill_dpb_info(inst->ctx, &slice_param->decode_params, + slice_param->h264_dpb_info); + + /* Build the reference lists */ + v4l2_h264_init_reflist_builder(&reflist_builder, dec_params, sps, inst->dpb); + v4l2_h264_build_p_ref_list(&reflist_builder, v4l2_p0_reflist); + v4l2_h264_build_b_ref_lists(&reflist_builder, v4l2_b0_reflist, v4l2_b1_reflist); + + /* Adapt the built lists to the firmware's expectations */ + mtk_vdec_h264_get_ref_list(p0_reflist, v4l2_p0_reflist, reflist_builder.num_valid); + mtk_vdec_h264_get_ref_list(b0_reflist, v4l2_b0_reflist, reflist_builder.num_valid); + mtk_vdec_h264_get_ref_list(b1_reflist, v4l2_b1_reflist, reflist_builder.num_valid); + + memcpy(&inst->vsi_ctx.h264_slice_params, slice_param, + sizeof(inst->vsi_ctx.h264_slice_params)); + + return 0; +} + +static void vdec_h264_slice_fill_decode_reflist(struct vdec_h264_slice_inst *inst, + struct vdec_h264_slice_lat_dec_param *slice_param, + struct vdec_h264_slice_share_info *share_info) +{ + struct v4l2_ctrl_h264_decode_params *dec_params = &share_info->dec_params; + struct v4l2_ctrl_h264_sps *sps = &share_info->sps; + struct v4l2_h264_reflist_builder reflist_builder; + struct v4l2_h264_reference v4l2_p0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b0_reflist[V4L2_H264_REF_LIST_LEN]; + struct v4l2_h264_reference v4l2_b1_reflist[V4L2_H264_REF_LIST_LEN]; + u8 *p0_reflist = slice_param->decode_params.ref_pic_list_p0; + u8 *b0_reflist = slice_param->decode_params.ref_pic_list_b0; + u8 *b1_reflist = slice_param->decode_params.ref_pic_list_b1; + + mtk_vdec_h264_update_dpb(dec_params, inst->dpb); + + mtk_vdec_h264_copy_decode_params(&slice_param->decode_params, dec_params, + inst->dpb); + mtk_vdec_h264_fill_dpb_info(inst->ctx, &slice_param->decode_params, + slice_param->h264_dpb_info); + + mtk_v4l2_debug(3, "cur poc = %d\n", dec_params->bottom_field_order_cnt); + /* Build the reference lists */ + v4l2_h264_init_reflist_builder(&reflist_builder, dec_params, sps, + inst->dpb); + v4l2_h264_build_p_ref_list(&reflist_builder, v4l2_p0_reflist); + v4l2_h264_build_b_ref_lists(&reflist_builder, v4l2_b0_reflist, v4l2_b1_reflist); + + /* Adapt the built lists to the firmware's expectations */ + mtk_vdec_h264_get_ref_list(p0_reflist, v4l2_p0_reflist, reflist_builder.num_valid); + mtk_vdec_h264_get_ref_list(b0_reflist, v4l2_b0_reflist, reflist_builder.num_valid); + mtk_vdec_h264_get_ref_list(b1_reflist, v4l2_b1_reflist, reflist_builder.num_valid); +} + +static int vdec_h264_slice_alloc_mv_buf(struct vdec_h264_slice_inst *inst, + struct vdec_pic_info *pic) +{ + unsigned int buf_sz = mtk_vdec_h264_get_mv_buf_size(pic->buf_w, pic->buf_h); + struct mtk_vcodec_mem *mem; + int i, err; + + mtk_v4l2_debug(3, "size = 0x%x", buf_sz); + for (i = 0; i < H264_MAX_MV_NUM; i++) { + mem = &inst->mv_buf[i]; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + mem->size = buf_sz; + err = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (err) { + mtk_vcodec_err(inst, "failed to allocate mv buf"); + return err; + } + } + + return 0; +} + +static void vdec_h264_slice_free_mv_buf(struct vdec_h264_slice_inst *inst) +{ + int i; + struct mtk_vcodec_mem *mem; + + for (i = 0; i < H264_MAX_MV_NUM; i++) { + mem = &inst->mv_buf[i]; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + } +} + +static void vdec_h264_slice_get_pic_info(struct vdec_h264_slice_inst *inst) +{ + struct mtk_vcodec_ctx *ctx = inst->ctx; + u32 data[3]; + + data[0] = ctx->picinfo.pic_w; + data[1] = ctx->picinfo.pic_h; + data[2] = ctx->capture_fourcc; + vpu_dec_get_param(&inst->vpu, data, 3, GET_PARAM_PIC_INFO); + + ctx->picinfo.buf_w = ALIGN(ctx->picinfo.pic_w, VCODEC_DEC_ALIGNED_64); + ctx->picinfo.buf_h = ALIGN(ctx->picinfo.pic_h, VCODEC_DEC_ALIGNED_64); + ctx->picinfo.fb_sz[0] = inst->vpu.fb_sz[0]; + ctx->picinfo.fb_sz[1] = inst->vpu.fb_sz[1]; + inst->cap_num_planes = + ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes; + + mtk_vcodec_debug(inst, "pic(%d, %d), buf(%d, %d)", + ctx->picinfo.pic_w, ctx->picinfo.pic_h, + ctx->picinfo.buf_w, ctx->picinfo.buf_h); + mtk_vcodec_debug(inst, "Y/C(%d, %d)", ctx->picinfo.fb_sz[0], + ctx->picinfo.fb_sz[1]); + + if (ctx->last_decoded_picinfo.pic_w != ctx->picinfo.pic_w || + ctx->last_decoded_picinfo.pic_h != ctx->picinfo.pic_h) { + inst->resolution_changed = true; + if (ctx->last_decoded_picinfo.buf_w != ctx->picinfo.buf_w || + ctx->last_decoded_picinfo.buf_h != ctx->picinfo.buf_h) + inst->realloc_mv_buf = true; + + mtk_v4l2_debug(1, "resChg: (%d %d) : old(%d, %d) -> new(%d, %d)", + inst->resolution_changed, + inst->realloc_mv_buf, + ctx->last_decoded_picinfo.pic_w, + ctx->last_decoded_picinfo.pic_h, + ctx->picinfo.pic_w, ctx->picinfo.pic_h); + } +} + +static void vdec_h264_slice_get_crop_info(struct vdec_h264_slice_inst *inst, + struct v4l2_rect *cr) +{ + cr->left = 0; + cr->top = 0; + cr->width = inst->ctx->picinfo.pic_w; + cr->height = inst->ctx->picinfo.pic_h; + + mtk_vcodec_debug(inst, "l=%d, t=%d, w=%d, h=%d", + cr->left, cr->top, cr->width, cr->height); +} + +static int vdec_h264_slice_init(struct mtk_vcodec_ctx *ctx) +{ + struct vdec_h264_slice_inst *inst; + int err, vsi_size; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->ctx = ctx; + + inst->vpu.id = SCP_IPI_VDEC_LAT; + inst->vpu.core_id = SCP_IPI_VDEC_CORE; + inst->vpu.ctx = ctx; + inst->vpu.codec_type = ctx->current_codec; + inst->vpu.capture_type = ctx->capture_fourcc; + + err = vpu_dec_init(&inst->vpu); + if (err) { + mtk_vcodec_err(inst, "vdec_h264 init err=%d", err); + goto error_free_inst; + } + + vsi_size = round_up(sizeof(struct vdec_h264_slice_vsi), VCODEC_DEC_ALIGNED_64); + inst->vsi = inst->vpu.vsi; + inst->vsi_core = + (struct vdec_h264_slice_vsi *)(((char *)inst->vpu.vsi) + vsi_size); + inst->resolution_changed = true; + inst->realloc_mv_buf = true; + + mtk_vcodec_debug(inst, "lat struct size = %d,%d,%d,%d vsi: %d\n", + (int)sizeof(struct mtk_h264_sps_param), + (int)sizeof(struct mtk_h264_pps_param), + (int)sizeof(struct vdec_h264_slice_lat_dec_param), + (int)sizeof(struct mtk_h264_dpb_info), + vsi_size); + mtk_vcodec_debug(inst, "lat H264 instance >> %p, codec_type = 0x%x", + inst, inst->vpu.codec_type); + + ctx->drv_handle = inst; + return 0; + +error_free_inst: + kfree(inst); + return err; +} + +static void vdec_h264_slice_deinit(void *h_vdec) +{ + struct vdec_h264_slice_inst *inst = h_vdec; + + mtk_vcodec_debug_enter(inst); + + vpu_dec_deinit(&inst->vpu); + vdec_h264_slice_free_mv_buf(inst); + vdec_msg_queue_deinit(&inst->ctx->msg_queue, inst->ctx); + + kfree(inst); +} + +static int vdec_h264_slice_core_decode(struct vdec_lat_buf *lat_buf) +{ + struct vdec_fb *fb; + u64 vdec_fb_va; + u64 y_fb_dma, c_fb_dma; + int err, timeout, i; + struct mtk_vcodec_ctx *ctx = lat_buf->ctx; + struct vdec_h264_slice_inst *inst = ctx->drv_handle; + struct vb2_v4l2_buffer *vb2_v4l2; + struct vdec_h264_slice_share_info *share_info = lat_buf->private_data; + struct mtk_vcodec_mem *mem; + struct vdec_vpu_inst *vpu = &inst->vpu; + + mtk_vcodec_debug(inst, "[h264-core] vdec_h264 core decode"); + memcpy(&inst->vsi_core->h264_slice_params, &share_info->h264_slice_params, + sizeof(share_info->h264_slice_params)); + + fb = ctx->dev->vdec_pdata->get_cap_buffer(ctx); + if (!fb) { + err = -EBUSY; + mtk_vcodec_err(inst, "fb buffer is NULL"); + goto vdec_dec_end; + } + + vdec_fb_va = (unsigned long)fb; + y_fb_dma = (u64)fb->base_y.dma_addr; + if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 1) + c_fb_dma = + y_fb_dma + inst->ctx->picinfo.buf_w * inst->ctx->picinfo.buf_h; + else + c_fb_dma = (u64)fb->base_c.dma_addr; + + mtk_vcodec_debug(inst, "[h264-core] y/c addr = 0x%llx 0x%llx", y_fb_dma, + c_fb_dma); + + inst->vsi_core->dec.y_fb_dma = y_fb_dma; + inst->vsi_core->dec.c_fb_dma = c_fb_dma; + inst->vsi_core->dec.vdec_fb_va = vdec_fb_va; + inst->vsi_core->dec.nal_info = share_info->nal_info; + inst->vsi_core->wdma_start_addr = + lat_buf->ctx->msg_queue.wdma_addr.dma_addr; + inst->vsi_core->wdma_end_addr = + lat_buf->ctx->msg_queue.wdma_addr.dma_addr + + lat_buf->ctx->msg_queue.wdma_addr.size; + inst->vsi_core->wdma_err_addr = lat_buf->wdma_err_addr.dma_addr; + inst->vsi_core->slice_bc_start_addr = lat_buf->slice_bc_addr.dma_addr; + inst->vsi_core->slice_bc_end_addr = lat_buf->slice_bc_addr.dma_addr + + lat_buf->slice_bc_addr.size; + inst->vsi_core->trans_start = share_info->trans_start; + inst->vsi_core->trans_end = share_info->trans_end; + for (i = 0; i < H264_MAX_MV_NUM; i++) { + mem = &inst->mv_buf[i]; + inst->vsi_core->mv_buf_dma[i] = mem->dma_addr; + } + + vb2_v4l2 = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + v4l2_m2m_buf_copy_metadata(&lat_buf->ts_info, vb2_v4l2, true); + + vdec_h264_slice_fill_decode_reflist(inst, &inst->vsi_core->h264_slice_params, + share_info); + + err = vpu_dec_core(vpu); + if (err) { + mtk_vcodec_err(inst, "core decode err=%d", err); + goto vdec_dec_end; + } + + /* wait decoder done interrupt */ + timeout = mtk_vcodec_wait_for_done_ctx(inst->ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, MTK_VDEC_CORE); + if (timeout) + mtk_vcodec_err(inst, "core decode timeout: pic_%d", + ctx->decoded_frame_cnt); + inst->vsi_core->dec.timeout = !!timeout; + + vpu_dec_core_end(vpu); + mtk_vcodec_debug(inst, "pic[%d] crc: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", + ctx->decoded_frame_cnt, + inst->vsi_core->dec.crc[0], inst->vsi_core->dec.crc[1], + inst->vsi_core->dec.crc[2], inst->vsi_core->dec.crc[3], + inst->vsi_core->dec.crc[4], inst->vsi_core->dec.crc[5], + inst->vsi_core->dec.crc[6], inst->vsi_core->dec.crc[7]); + +vdec_dec_end: + vdec_msg_queue_update_ube_rptr(&lat_buf->ctx->msg_queue, share_info->trans_end); + ctx->dev->vdec_pdata->cap_to_disp(ctx, !!err, lat_buf->src_buf_req); + mtk_vcodec_debug(inst, "core decode done err=%d", err); + ctx->decoded_frame_cnt++; + return 0; +} + +static void vdec_h264_insert_startcode(struct mtk_vcodec_dev *vcodec_dev, unsigned char *buf, + size_t *bs_size, struct mtk_h264_pps_param *pps) +{ + struct device *dev = &vcodec_dev->plat_dev->dev; + + /* Need to add pending data at the end of bitstream when bs_sz is small than + * 20 bytes for cavlc bitstream, or lat will decode fail. This pending data is + * useful for mt8192 and mt8195 platform. + * + * cavlc bitstream when entropy_coding_mode_flag is false. + */ + if (pps->entropy_coding_mode_flag || *bs_size > 20 || + !(of_device_is_compatible(dev->of_node, "mediatek,mt8192-vcodec-dec") || + of_device_is_compatible(dev->of_node, "mediatek,mt8195-vcodec-dec"))) + return; + + buf[*bs_size] = 0; + buf[*bs_size + 1] = 0; + buf[*bs_size + 2] = 1; + buf[*bs_size + 3] = 0xff; + (*bs_size) += 4; +} + +static int vdec_h264_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + struct vdec_h264_slice_inst *inst = h_vdec; + struct vdec_vpu_inst *vpu = &inst->vpu; + struct mtk_video_dec_buf *src_buf_info; + int nal_start_idx, err, timeout = 0, i; + unsigned int data[2]; + struct vdec_lat_buf *lat_buf; + struct vdec_h264_slice_share_info *share_info; + unsigned char *buf; + struct mtk_vcodec_mem *mem; + + if (vdec_msg_queue_init(&inst->ctx->msg_queue, inst->ctx, + vdec_h264_slice_core_decode, + sizeof(*share_info))) + return -ENOMEM; + + /* bs NULL means flush decoder */ + if (!bs) { + vdec_msg_queue_wait_lat_buf_full(&inst->ctx->msg_queue); + return vpu_dec_reset(vpu); + } + + if (inst->is_field_bitstream) + return -EINVAL; + + lat_buf = vdec_msg_queue_dqbuf(&inst->ctx->msg_queue.lat_ctx); + if (!lat_buf) { + mtk_vcodec_err(inst, "failed to get lat buffer"); + return -EAGAIN; + } + share_info = lat_buf->private_data; + src_buf_info = container_of(bs, struct mtk_video_dec_buf, bs_buffer); + + buf = (unsigned char *)bs->va; + nal_start_idx = mtk_vdec_h264_find_start_code(buf, bs->size); + if (nal_start_idx < 0) { + err = -EINVAL; + goto err_free_fb_out; + } + + inst->vsi->dec.nal_info = buf[nal_start_idx]; + lat_buf->src_buf_req = src_buf_info->m2m_buf.vb.vb2_buf.req_obj.req; + v4l2_m2m_buf_copy_metadata(&src_buf_info->m2m_buf.vb, &lat_buf->ts_info, true); + + err = vdec_h264_slice_fill_decode_parameters(inst, share_info); + if (err) + goto err_free_fb_out; + + vdec_h264_insert_startcode(inst->ctx->dev, buf, &bs->size, + &share_info->h264_slice_params.pps); + + inst->vsi->dec.bs_buf_addr = (uint64_t)bs->dma_addr; + inst->vsi->dec.bs_buf_size = bs->size; + + *res_chg = inst->resolution_changed; + if (inst->resolution_changed) { + mtk_vcodec_debug(inst, "- resolution changed -"); + if (inst->realloc_mv_buf) { + err = vdec_h264_slice_alloc_mv_buf(inst, &inst->ctx->picinfo); + inst->realloc_mv_buf = false; + if (err) + goto err_free_fb_out; + } + inst->resolution_changed = false; + } + for (i = 0; i < H264_MAX_MV_NUM; i++) { + mem = &inst->mv_buf[i]; + inst->vsi->mv_buf_dma[i] = mem->dma_addr; + } + inst->vsi->wdma_start_addr = lat_buf->ctx->msg_queue.wdma_addr.dma_addr; + inst->vsi->wdma_end_addr = lat_buf->ctx->msg_queue.wdma_addr.dma_addr + + lat_buf->ctx->msg_queue.wdma_addr.size; + inst->vsi->wdma_err_addr = lat_buf->wdma_err_addr.dma_addr; + inst->vsi->slice_bc_start_addr = lat_buf->slice_bc_addr.dma_addr; + inst->vsi->slice_bc_end_addr = lat_buf->slice_bc_addr.dma_addr + + lat_buf->slice_bc_addr.size; + + inst->vsi->trans_end = inst->ctx->msg_queue.wdma_rptr_addr; + inst->vsi->trans_start = inst->ctx->msg_queue.wdma_wptr_addr; + mtk_vcodec_debug(inst, "lat:trans(0x%llx 0x%llx) err:0x%llx", + inst->vsi->wdma_start_addr, + inst->vsi->wdma_end_addr, + inst->vsi->wdma_err_addr); + + mtk_vcodec_debug(inst, "slice(0x%llx 0x%llx) rprt((0x%llx 0x%llx))", + inst->vsi->slice_bc_start_addr, + inst->vsi->slice_bc_end_addr, + inst->vsi->trans_start, + inst->vsi->trans_end); + err = vpu_dec_start(vpu, data, 2); + if (err) { + mtk_vcodec_debug(inst, "lat decode err: %d", err); + goto err_free_fb_out; + } + + share_info->trans_end = inst->ctx->msg_queue.wdma_addr.dma_addr + + inst->vsi->wdma_end_addr_offset; + share_info->trans_start = inst->ctx->msg_queue.wdma_wptr_addr; + share_info->nal_info = inst->vsi->dec.nal_info; + + if (IS_VDEC_INNER_RACING(inst->ctx->dev->dec_capability)) { + memcpy(&share_info->h264_slice_params, &inst->vsi->h264_slice_params, + sizeof(share_info->h264_slice_params)); + vdec_msg_queue_qbuf(&inst->ctx->dev->msg_queue_core_ctx, lat_buf); + } + + /* wait decoder done interrupt */ + timeout = mtk_vcodec_wait_for_done_ctx(inst->ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, MTK_VDEC_LAT0); + if (timeout) + mtk_vcodec_err(inst, "lat decode timeout: pic_%d", inst->slice_dec_num); + inst->vsi->dec.timeout = !!timeout; + + err = vpu_dec_end(vpu); + if (err == SLICE_HEADER_FULL || err == TRANS_BUFFER_FULL) { + if (!IS_VDEC_INNER_RACING(inst->ctx->dev->dec_capability)) + vdec_msg_queue_qbuf(&inst->ctx->msg_queue.lat_ctx, lat_buf); + inst->slice_dec_num++; + mtk_vcodec_err(inst, "lat dec fail: pic_%d err:%d", inst->slice_dec_num, err); + return -EINVAL; + } + + share_info->trans_end = inst->ctx->msg_queue.wdma_addr.dma_addr + + inst->vsi->wdma_end_addr_offset; + vdec_msg_queue_update_ube_wptr(&lat_buf->ctx->msg_queue, share_info->trans_end); + + if (!IS_VDEC_INNER_RACING(inst->ctx->dev->dec_capability)) { + memcpy(&share_info->h264_slice_params, &inst->vsi->h264_slice_params, + sizeof(share_info->h264_slice_params)); + vdec_msg_queue_qbuf(&inst->ctx->dev->msg_queue_core_ctx, lat_buf); + } + mtk_vcodec_debug(inst, "dec num: %d lat crc: 0x%x 0x%x 0x%x", inst->slice_dec_num, + inst->vsi->dec.crc[0], inst->vsi->dec.crc[1], inst->vsi->dec.crc[2]); + + inst->slice_dec_num++; + return 0; +err_free_fb_out: + vdec_msg_queue_qbuf(&inst->ctx->msg_queue.lat_ctx, lat_buf); + mtk_vcodec_err(inst, "slice dec number: %d err: %d", inst->slice_dec_num, err); + return err; +} + +static int vdec_h264_slice_single_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *unused, bool *res_chg) +{ + struct vdec_h264_slice_inst *inst = h_vdec; + struct vdec_vpu_inst *vpu = &inst->vpu; + struct mtk_video_dec_buf *src_buf_info, *dst_buf_info; + struct vdec_fb *fb; + unsigned char *buf; + unsigned int data[2], i; + u64 y_fb_dma, c_fb_dma; + struct mtk_vcodec_mem *mem; + int err, nal_start_idx; + + /* bs NULL means flush decoder */ + if (!bs) + return vpu_dec_reset(vpu); + + fb = inst->ctx->dev->vdec_pdata->get_cap_buffer(inst->ctx); + src_buf_info = container_of(bs, struct mtk_video_dec_buf, bs_buffer); + dst_buf_info = container_of(fb, struct mtk_video_dec_buf, frame_buffer); + + y_fb_dma = fb ? (u64)fb->base_y.dma_addr : 0; + c_fb_dma = fb ? (u64)fb->base_c.dma_addr : 0; + mtk_vcodec_debug(inst, "[h264-dec] [%d] y_dma=%llx c_dma=%llx", + inst->ctx->decoded_frame_cnt, y_fb_dma, c_fb_dma); + + inst->vsi_ctx.dec.bs_buf_addr = (u64)bs->dma_addr; + inst->vsi_ctx.dec.bs_buf_size = bs->size; + inst->vsi_ctx.dec.y_fb_dma = y_fb_dma; + inst->vsi_ctx.dec.c_fb_dma = c_fb_dma; + inst->vsi_ctx.dec.vdec_fb_va = (u64)(uintptr_t)fb; + + v4l2_m2m_buf_copy_metadata(&src_buf_info->m2m_buf.vb, + &dst_buf_info->m2m_buf.vb, true); + err = get_vdec_sig_decode_parameters(inst); + if (err) + goto err_free_fb_out; + + buf = (unsigned char *)bs->va; + nal_start_idx = mtk_vdec_h264_find_start_code(buf, bs->size); + if (nal_start_idx < 0) { + err = -EINVAL; + goto err_free_fb_out; + } + inst->vsi_ctx.dec.nal_info = buf[nal_start_idx]; + + *res_chg = inst->resolution_changed; + if (inst->resolution_changed) { + mtk_vcodec_debug(inst, "- resolution changed -"); + if (inst->realloc_mv_buf) { + err = vdec_h264_slice_alloc_mv_buf(inst, &inst->ctx->picinfo); + inst->realloc_mv_buf = false; + if (err) + goto err_free_fb_out; + } + inst->resolution_changed = false; + + for (i = 0; i < H264_MAX_MV_NUM; i++) { + mem = &inst->mv_buf[i]; + inst->vsi_ctx.mv_buf_dma[i] = mem->dma_addr; + } + } + + memcpy(inst->vpu.vsi, &inst->vsi_ctx, sizeof(inst->vsi_ctx)); + err = vpu_dec_start(vpu, data, 2); + if (err) + goto err_free_fb_out; + + /* wait decoder done interrupt */ + err = mtk_vcodec_wait_for_done_ctx(inst->ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, MTK_VDEC_CORE); + if (err) + mtk_vcodec_err(inst, "decode timeout: pic_%d", + inst->ctx->decoded_frame_cnt); + + inst->vsi->dec.timeout = !!err; + err = vpu_dec_end(vpu); + if (err) + goto err_free_fb_out; + + memcpy(&inst->vsi_ctx, inst->vpu.vsi, sizeof(inst->vsi_ctx)); + mtk_vcodec_debug(inst, "pic[%d] crc: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", + inst->ctx->decoded_frame_cnt, + inst->vsi_ctx.dec.crc[0], inst->vsi_ctx.dec.crc[1], + inst->vsi_ctx.dec.crc[2], inst->vsi_ctx.dec.crc[3], + inst->vsi_ctx.dec.crc[4], inst->vsi_ctx.dec.crc[5], + inst->vsi_ctx.dec.crc[6], inst->vsi_ctx.dec.crc[7]); + + inst->ctx->decoded_frame_cnt++; + return 0; + +err_free_fb_out: + mtk_vcodec_err(inst, "dec frame number: %d err: %d", + inst->ctx->decoded_frame_cnt, err); + return err; +} + +static int vdec_h264_slice_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *unused, bool *res_chg) +{ + struct vdec_h264_slice_inst *inst = h_vdec; + int ret; + + if (!h_vdec) + return -EINVAL; + + if (inst->ctx->dev->vdec_pdata->hw_arch == MTK_VDEC_PURE_SINGLE_CORE) + ret = vdec_h264_slice_single_decode(h_vdec, bs, unused, res_chg); + else + ret = vdec_h264_slice_lat_decode(h_vdec, bs, unused, res_chg); + + return ret; +} + +static int vdec_h264_slice_get_param(void *h_vdec, enum vdec_get_param_type type, + void *out) +{ + struct vdec_h264_slice_inst *inst = h_vdec; + + switch (type) { + case GET_PARAM_PIC_INFO: + vdec_h264_slice_get_pic_info(inst); + break; + case GET_PARAM_DPB_SIZE: + *(unsigned int *)out = 6; + break; + case GET_PARAM_CROP_INFO: + vdec_h264_slice_get_crop_info(inst, out); + break; + default: + mtk_vcodec_err(inst, "invalid get parameter type=%d", type); + return -EINVAL; + } + return 0; +} + +const struct vdec_common_if vdec_h264_slice_multi_if = { + .init = vdec_h264_slice_init, + .decode = vdec_h264_slice_decode, + .get_param = vdec_h264_slice_get_param, + .deinit = vdec_h264_slice_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp8_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp8_if.c new file mode 100644 index 000000000..88c046731 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp8_if.c @@ -0,0 +1,616 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Jungchang Tsao + * PC Chen + */ + +#include +#include "../vdec_drv_if.h" +#include "../mtk_vcodec_util.h" +#include "../mtk_vcodec_dec.h" +#include "../mtk_vcodec_intr.h" +#include "../vdec_vpu_if.h" +#include "../vdec_drv_base.h" + +/* Decoding picture buffer size (3 reference frames plus current frame) */ +#define VP8_DPB_SIZE 4 + +/* HW working buffer size (bytes) */ +#define VP8_WORKING_BUF_SZ (45 * 4096) + +/* HW control register address */ +#define VP8_SEGID_DRAM_ADDR 0x3c +#define VP8_HW_VLD_ADDR 0x93C +#define VP8_HW_VLD_VALUE 0x940 +#define VP8_BSASET 0x100 +#define VP8_BSDSET 0x104 +#define VP8_RW_CKEN_SET 0x0 +#define VP8_RW_DCM_CON 0x18 +#define VP8_WO_VLD_SRST 0x108 +#define VP8_RW_MISC_SYS_SEL 0x84 +#define VP8_RW_MISC_SPEC_CON 0xC8 +#define VP8_WO_VLD_SRST 0x108 +#define VP8_RW_VP8_CTRL 0xA4 +#define VP8_RW_MISC_DCM_CON 0xEC +#define VP8_RW_MISC_SRST 0xF4 +#define VP8_RW_MISC_FUNC_CON 0xCC + +#define VP8_MAX_FRM_BUF_NUM 5 +#define VP8_MAX_FRM_BUF_NODE_NUM (VP8_MAX_FRM_BUF_NUM * 2) + +/* required buffer size (bytes) to store decode information */ +#define VP8_HW_SEGMENT_DATA_SZ 272 +#define VP8_HW_SEGMENT_UINT 4 + +#define VP8_DEC_TABLE_PROC_LOOP 96 +#define VP8_DEC_TABLE_UNIT 3 +#define VP8_DEC_TABLE_SZ 300 +#define VP8_DEC_TABLE_OFFSET 2 +#define VP8_DEC_TABLE_RW_UNIT 4 + +/** + * struct vdec_vp8_dec_info - decode misc information + * @working_buf_dma : working buffer dma address + * @prev_y_dma : previous decoded frame buffer Y plane address + * @cur_y_fb_dma : current plane Y frame buffer dma address + * @cur_c_fb_dma : current plane C frame buffer dma address + * @bs_dma : bitstream dma address + * @bs_sz : bitstream size + * @resolution_changed: resolution change flag 1 - changed, 0 - not change + * @show_frame : display this frame or not + * @wait_key_frame : wait key frame coming + */ +struct vdec_vp8_dec_info { + uint64_t working_buf_dma; + uint64_t prev_y_dma; + uint64_t cur_y_fb_dma; + uint64_t cur_c_fb_dma; + uint64_t bs_dma; + uint32_t bs_sz; + uint32_t resolution_changed; + uint32_t show_frame; + uint32_t wait_key_frame; +}; + +/** + * struct vdec_vp8_vsi - VPU shared information + * @dec : decoding information + * @pic : picture information + * @dec_table : decoder coefficient table + * @segment_buf : segmentation buffer + * @load_data : flag to indicate reload decode data + */ +struct vdec_vp8_vsi { + struct vdec_vp8_dec_info dec; + struct vdec_pic_info pic; + uint32_t dec_table[VP8_DEC_TABLE_SZ]; + uint32_t segment_buf[VP8_HW_SEGMENT_DATA_SZ][VP8_HW_SEGMENT_UINT]; + uint32_t load_data; +}; + +/** + * struct vdec_vp8_hw_reg_base - HW register base + * @sys : base address for sys + * @misc : base address for misc + * @ld : base address for ld + * @top : base address for top + * @cm : base address for cm + * @hwd : base address for hwd + * @hwb : base address for hwb + */ +struct vdec_vp8_hw_reg_base { + void __iomem *sys; + void __iomem *misc; + void __iomem *ld; + void __iomem *top; + void __iomem *cm; + void __iomem *hwd; + void __iomem *hwb; +}; + +/** + * struct vdec_vp8_vpu_inst - VPU instance for VP8 decode + * @wq_hd : Wait queue to wait VPU message ack + * @signaled : 1 - Host has received ack message from VPU, 0 - not receive + * @failure : VPU execution result status 0 - success, others - fail + * @inst_addr : VPU decoder instance address + */ +struct vdec_vp8_vpu_inst { + wait_queue_head_t wq_hd; + int signaled; + int failure; + uint32_t inst_addr; +}; + +/* frame buffer (fb) list + * [available_fb_node_list] - decode fb are initialized to 0 and populated in + * [fb_use_list] - fb is set after decode and is moved to this list + * [fb_free_list] - fb is not needed for reference will be moved from + * [fb_use_list] to [fb_free_list] and + * once user remove fb from [fb_free_list], + * it is circulated back to [available_fb_node_list] + * [fb_disp_list] - fb is set after decode and is moved to this list + * once user remove fb from [fb_disp_list] it is + * circulated back to [available_fb_node_list] + */ + +/** + * struct vdec_vp8_inst - VP8 decoder instance + * @cur_fb : current frame buffer + * @dec_fb : decode frame buffer node + * @available_fb_node_list : list to store available frame buffer node + * @fb_use_list : list to store frame buffer in use + * @fb_free_list : list to store free frame buffer + * @fb_disp_list : list to store display ready frame buffer + * @working_buf : HW decoder working buffer + * @reg_base : HW register base address + * @frm_cnt : decode frame count + * @ctx : V4L2 context + * @vpu : VPU instance for decoder + * @vsi : VPU share information + */ +struct vdec_vp8_inst { + struct vdec_fb *cur_fb; + struct vdec_fb_node dec_fb[VP8_MAX_FRM_BUF_NODE_NUM]; + struct list_head available_fb_node_list; + struct list_head fb_use_list; + struct list_head fb_free_list; + struct list_head fb_disp_list; + struct mtk_vcodec_mem working_buf; + struct vdec_vp8_hw_reg_base reg_base; + unsigned int frm_cnt; + struct mtk_vcodec_ctx *ctx; + struct vdec_vpu_inst vpu; + struct vdec_vp8_vsi *vsi; +}; + +static void get_hw_reg_base(struct vdec_vp8_inst *inst) +{ + inst->reg_base.top = mtk_vcodec_get_reg_addr(inst->ctx, VDEC_TOP); + inst->reg_base.cm = mtk_vcodec_get_reg_addr(inst->ctx, VDEC_CM); + inst->reg_base.hwd = mtk_vcodec_get_reg_addr(inst->ctx, VDEC_HWD); + inst->reg_base.sys = mtk_vcodec_get_reg_addr(inst->ctx, VDEC_SYS); + inst->reg_base.misc = mtk_vcodec_get_reg_addr(inst->ctx, VDEC_MISC); + inst->reg_base.ld = mtk_vcodec_get_reg_addr(inst->ctx, VDEC_LD); + inst->reg_base.hwb = mtk_vcodec_get_reg_addr(inst->ctx, VDEC_HWB); +} + +static void write_hw_segmentation_data(struct vdec_vp8_inst *inst) +{ + int i, j; + u32 seg_id_addr; + u32 val; + void __iomem *cm = inst->reg_base.cm; + struct vdec_vp8_vsi *vsi = inst->vsi; + + seg_id_addr = readl(inst->reg_base.top + VP8_SEGID_DRAM_ADDR) >> 4; + + for (i = 0; i < ARRAY_SIZE(vsi->segment_buf); i++) { + for (j = ARRAY_SIZE(vsi->segment_buf[i]) - 1; j >= 0; j--) { + val = (1 << 16) + ((seg_id_addr + i) << 2) + j; + writel(val, cm + VP8_HW_VLD_ADDR); + + val = vsi->segment_buf[i][j]; + writel(val, cm + VP8_HW_VLD_VALUE); + } + } +} + +static void read_hw_segmentation_data(struct vdec_vp8_inst *inst) +{ + int i, j; + u32 seg_id_addr; + u32 val; + void __iomem *cm = inst->reg_base.cm; + struct vdec_vp8_vsi *vsi = inst->vsi; + + seg_id_addr = readl(inst->reg_base.top + VP8_SEGID_DRAM_ADDR) >> 4; + + for (i = 0; i < ARRAY_SIZE(vsi->segment_buf); i++) { + for (j = ARRAY_SIZE(vsi->segment_buf[i]) - 1; j >= 0; j--) { + val = ((seg_id_addr + i) << 2) + j; + writel(val, cm + VP8_HW_VLD_ADDR); + + val = readl(cm + VP8_HW_VLD_VALUE); + vsi->segment_buf[i][j] = val; + } + } +} + +/* reset HW and enable HW read/write data function */ +static void enable_hw_rw_function(struct vdec_vp8_inst *inst) +{ + u32 val = 0; + void __iomem *sys = inst->reg_base.sys; + void __iomem *misc = inst->reg_base.misc; + void __iomem *ld = inst->reg_base.ld; + void __iomem *hwb = inst->reg_base.hwb; + void __iomem *hwd = inst->reg_base.hwd; + + writel(0x1, sys + VP8_RW_CKEN_SET); + writel(0x101, ld + VP8_WO_VLD_SRST); + writel(0x101, hwb + VP8_WO_VLD_SRST); + + writel(1, sys); + val = readl(misc + VP8_RW_MISC_SRST); + writel((val & 0xFFFFFFFE), misc + VP8_RW_MISC_SRST); + + writel(0x1, misc + VP8_RW_MISC_SYS_SEL); + writel(0x17F, misc + VP8_RW_MISC_SPEC_CON); + writel(0x71201100, misc + VP8_RW_MISC_FUNC_CON); + writel(0x0, ld + VP8_WO_VLD_SRST); + writel(0x0, hwb + VP8_WO_VLD_SRST); + writel(0x1, sys + VP8_RW_DCM_CON); + writel(0x1, misc + VP8_RW_MISC_DCM_CON); + writel(0x1, hwd + VP8_RW_VP8_CTRL); +} + +static void store_dec_table(struct vdec_vp8_inst *inst) +{ + int i, j; + u32 addr = 0, val = 0; + void __iomem *hwd = inst->reg_base.hwd; + u32 *p = &inst->vsi->dec_table[VP8_DEC_TABLE_OFFSET]; + + for (i = 0; i < VP8_DEC_TABLE_PROC_LOOP; i++) { + writel(addr, hwd + VP8_BSASET); + for (j = 0; j < VP8_DEC_TABLE_UNIT ; j++) { + val = *p++; + writel(val, hwd + VP8_BSDSET); + } + addr += VP8_DEC_TABLE_RW_UNIT; + } +} + +static void load_dec_table(struct vdec_vp8_inst *inst) +{ + int i; + u32 addr = 0; + u32 *p = &inst->vsi->dec_table[VP8_DEC_TABLE_OFFSET]; + void __iomem *hwd = inst->reg_base.hwd; + + for (i = 0; i < VP8_DEC_TABLE_PROC_LOOP; i++) { + writel(addr, hwd + VP8_BSASET); + /* read total 11 bytes */ + *p++ = readl(hwd + VP8_BSDSET); + *p++ = readl(hwd + VP8_BSDSET); + *p++ = readl(hwd + VP8_BSDSET) & 0xFFFFFF; + addr += VP8_DEC_TABLE_RW_UNIT; + } +} + +static void get_pic_info(struct vdec_vp8_inst *inst, struct vdec_pic_info *pic) +{ + *pic = inst->vsi->pic; + + mtk_vcodec_debug(inst, "pic(%d, %d), buf(%d, %d)", + pic->pic_w, pic->pic_h, pic->buf_w, pic->buf_h); + mtk_vcodec_debug(inst, "fb size: Y(%d), C(%d)", + pic->fb_sz[0], pic->fb_sz[1]); +} + +static void vp8_dec_finish(struct vdec_vp8_inst *inst) +{ + struct vdec_fb_node *node; + uint64_t prev_y_dma = inst->vsi->dec.prev_y_dma; + + mtk_vcodec_debug(inst, "prev fb base dma=%llx", prev_y_dma); + + /* put last decode ok frame to fb_free_list */ + if (prev_y_dma != 0) { + list_for_each_entry(node, &inst->fb_use_list, list) { + struct vdec_fb *fb = (struct vdec_fb *)node->fb; + + if (prev_y_dma == (uint64_t)fb->base_y.dma_addr) { + list_move_tail(&node->list, + &inst->fb_free_list); + break; + } + } + } + + /* available_fb_node_list -> fb_use_list */ + node = list_first_entry(&inst->available_fb_node_list, + struct vdec_fb_node, list); + node->fb = inst->cur_fb; + list_move_tail(&node->list, &inst->fb_use_list); + + /* available_fb_node_list -> fb_disp_list */ + if (inst->vsi->dec.show_frame) { + node = list_first_entry(&inst->available_fb_node_list, + struct vdec_fb_node, list); + node->fb = inst->cur_fb; + list_move_tail(&node->list, &inst->fb_disp_list); + } +} + +static void move_fb_list_use_to_free(struct vdec_vp8_inst *inst) +{ + struct vdec_fb_node *node, *tmp; + + list_for_each_entry_safe(node, tmp, &inst->fb_use_list, list) + list_move_tail(&node->list, &inst->fb_free_list); +} + +static void init_list(struct vdec_vp8_inst *inst) +{ + int i; + + INIT_LIST_HEAD(&inst->available_fb_node_list); + INIT_LIST_HEAD(&inst->fb_use_list); + INIT_LIST_HEAD(&inst->fb_free_list); + INIT_LIST_HEAD(&inst->fb_disp_list); + + for (i = 0; i < ARRAY_SIZE(inst->dec_fb); i++) { + INIT_LIST_HEAD(&inst->dec_fb[i].list); + inst->dec_fb[i].fb = NULL; + list_add_tail(&inst->dec_fb[i].list, + &inst->available_fb_node_list); + } +} + +static void add_fb_to_free_list(struct vdec_vp8_inst *inst, void *fb) +{ + struct vdec_fb_node *node; + + if (fb) { + node = list_first_entry(&inst->available_fb_node_list, + struct vdec_fb_node, list); + node->fb = fb; + list_move_tail(&node->list, &inst->fb_free_list); + } +} + +static int alloc_working_buf(struct vdec_vp8_inst *inst) +{ + int err; + struct mtk_vcodec_mem *mem = &inst->working_buf; + + mem->size = VP8_WORKING_BUF_SZ; + err = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (err) { + mtk_vcodec_err(inst, "Cannot allocate working buffer"); + return err; + } + + inst->vsi->dec.working_buf_dma = (uint64_t)mem->dma_addr; + return 0; +} + +static void free_working_buf(struct vdec_vp8_inst *inst) +{ + struct mtk_vcodec_mem *mem = &inst->working_buf; + + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + + inst->vsi->dec.working_buf_dma = 0; +} + +static int vdec_vp8_init(struct mtk_vcodec_ctx *ctx) +{ + struct vdec_vp8_inst *inst; + int err; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->ctx = ctx; + + inst->vpu.id = IPI_VDEC_VP8; + inst->vpu.ctx = ctx; + + err = vpu_dec_init(&inst->vpu); + if (err) { + mtk_vcodec_err(inst, "vdec_vp8 init err=%d", err); + goto error_free_inst; + } + + inst->vsi = (struct vdec_vp8_vsi *)inst->vpu.vsi; + init_list(inst); + err = alloc_working_buf(inst); + if (err) + goto error_deinit; + + get_hw_reg_base(inst); + mtk_vcodec_debug(inst, "VP8 Instance >> %p", inst); + + ctx->drv_handle = inst; + return 0; + +error_deinit: + vpu_dec_deinit(&inst->vpu); +error_free_inst: + kfree(inst); + return err; +} + +static int vdec_vp8_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + struct vdec_vp8_inst *inst = (struct vdec_vp8_inst *)h_vdec; + struct vdec_vp8_dec_info *dec = &inst->vsi->dec; + struct vdec_vpu_inst *vpu = &inst->vpu; + unsigned char *bs_va; + unsigned int data; + int err = 0; + uint64_t y_fb_dma; + uint64_t c_fb_dma; + + /* bs NULL means flush decoder */ + if (bs == NULL) { + move_fb_list_use_to_free(inst); + return vpu_dec_reset(vpu); + } + + y_fb_dma = fb ? (u64)fb->base_y.dma_addr : 0; + c_fb_dma = fb ? (u64)fb->base_c.dma_addr : 0; + + mtk_vcodec_debug(inst, "+ [%d] FB y_dma=%llx c_dma=%llx fb=%p", + inst->frm_cnt, y_fb_dma, c_fb_dma, fb); + + inst->cur_fb = fb; + dec->bs_dma = (unsigned long)bs->dma_addr; + dec->bs_sz = bs->size; + dec->cur_y_fb_dma = y_fb_dma; + dec->cur_c_fb_dma = c_fb_dma; + + mtk_vcodec_debug(inst, "\n + FRAME[%d] +\n", inst->frm_cnt); + + write_hw_segmentation_data(inst); + enable_hw_rw_function(inst); + store_dec_table(inst); + + bs_va = (unsigned char *)bs->va; + + /* retrieve width/hight and scale info from header */ + data = (*(bs_va + 9) << 24) | (*(bs_va + 8) << 16) | + (*(bs_va + 7) << 8) | *(bs_va + 6); + err = vpu_dec_start(vpu, &data, 1); + if (err) { + add_fb_to_free_list(inst, fb); + if (dec->wait_key_frame) { + mtk_vcodec_debug(inst, "wait key frame !"); + return 0; + } + + goto error; + } + + if (dec->resolution_changed) { + mtk_vcodec_debug(inst, "- resolution_changed -"); + *res_chg = true; + add_fb_to_free_list(inst, fb); + return 0; + } + + /* wait decoder done interrupt */ + mtk_vcodec_wait_for_done_ctx(inst->ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, 0); + + if (inst->vsi->load_data) + load_dec_table(inst); + + vp8_dec_finish(inst); + read_hw_segmentation_data(inst); + + err = vpu_dec_end(vpu); + if (err) + goto error; + + mtk_vcodec_debug(inst, "\n - FRAME[%d] - show=%d\n", inst->frm_cnt, + dec->show_frame); + inst->frm_cnt++; + *res_chg = false; + return 0; + +error: + mtk_vcodec_err(inst, "\n - FRAME[%d] - err=%d\n", inst->frm_cnt, err); + return err; +} + +static void get_disp_fb(struct vdec_vp8_inst *inst, struct vdec_fb **out_fb) +{ + struct vdec_fb_node *node; + struct vdec_fb *fb; + + node = list_first_entry_or_null(&inst->fb_disp_list, + struct vdec_fb_node, list); + if (node) { + list_move_tail(&node->list, &inst->available_fb_node_list); + fb = (struct vdec_fb *)node->fb; + fb->status |= FB_ST_DISPLAY; + mtk_vcodec_debug(inst, "[FB] get disp fb %p st=%d", + node->fb, fb->status); + } else { + fb = NULL; + mtk_vcodec_debug(inst, "[FB] there is no disp fb"); + } + + *out_fb = fb; +} + +static void get_free_fb(struct vdec_vp8_inst *inst, struct vdec_fb **out_fb) +{ + struct vdec_fb_node *node; + struct vdec_fb *fb; + + node = list_first_entry_or_null(&inst->fb_free_list, + struct vdec_fb_node, list); + if (node) { + list_move_tail(&node->list, &inst->available_fb_node_list); + fb = (struct vdec_fb *)node->fb; + fb->status |= FB_ST_FREE; + mtk_vcodec_debug(inst, "[FB] get free fb %p st=%d", + node->fb, fb->status); + } else { + fb = NULL; + mtk_vcodec_debug(inst, "[FB] there is no free fb"); + } + + *out_fb = fb; +} + +static void get_crop_info(struct vdec_vp8_inst *inst, struct v4l2_rect *cr) +{ + cr->left = 0; + cr->top = 0; + cr->width = inst->vsi->pic.pic_w; + cr->height = inst->vsi->pic.pic_h; + mtk_vcodec_debug(inst, "get crop info l=%d, t=%d, w=%d, h=%d", + cr->left, cr->top, cr->width, cr->height); +} + +static int vdec_vp8_get_param(void *h_vdec, enum vdec_get_param_type type, + void *out) +{ + struct vdec_vp8_inst *inst = (struct vdec_vp8_inst *)h_vdec; + + switch (type) { + case GET_PARAM_DISP_FRAME_BUFFER: + get_disp_fb(inst, out); + break; + + case GET_PARAM_FREE_FRAME_BUFFER: + get_free_fb(inst, out); + break; + + case GET_PARAM_PIC_INFO: + get_pic_info(inst, out); + break; + + case GET_PARAM_CROP_INFO: + get_crop_info(inst, out); + break; + + case GET_PARAM_DPB_SIZE: + *((unsigned int *)out) = VP8_DPB_SIZE; + break; + + default: + mtk_vcodec_err(inst, "invalid get parameter type=%d", type); + return -EINVAL; + } + + return 0; +} + +static void vdec_vp8_deinit(void *h_vdec) +{ + struct vdec_vp8_inst *inst = (struct vdec_vp8_inst *)h_vdec; + + mtk_vcodec_debug_enter(inst); + + vpu_dec_deinit(&inst->vpu); + free_working_buf(inst); + kfree(inst); +} + +const struct vdec_common_if vdec_vp8_if = { + .init = vdec_vp8_init, + .decode = vdec_vp8_decode, + .get_param = vdec_vp8_get_param, + .deinit = vdec_vp8_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp8_req_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp8_req_if.c new file mode 100644 index 000000000..e1fe2603e --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp8_req_if.c @@ -0,0 +1,436 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + * Author: Yunfei Dong + */ + +#include +#include +#include +#include + +#include "../mtk_vcodec_util.h" +#include "../mtk_vcodec_dec.h" +#include "../mtk_vcodec_intr.h" +#include "../vdec_drv_base.h" +#include "../vdec_drv_if.h" +#include "../vdec_vpu_if.h" + +/* Decoding picture buffer size (3 reference frames plus current frame) */ +#define VP8_DPB_SIZE 4 + +/* HW working buffer size (bytes) */ +#define VP8_SEG_ID_SZ SZ_256K +#define VP8_PP_WRAPY_SZ SZ_64K +#define VP8_PP_WRAPC_SZ SZ_64K +#define VP8_VLD_PRED_SZ SZ_64K + +/** + * struct vdec_vp8_slice_info - decode misc information + * + * @vld_wrapper_dma: vld wrapper dma address + * @seg_id_buf_dma: seg id dma address + * @wrap_y_dma: wrap y dma address + * @wrap_c_dma: wrap y dma address + * @cur_y_fb_dma: current plane Y frame buffer dma address + * @cur_c_fb_dma: current plane C frame buffer dma address + * @bs_dma: bitstream dma address + * @bs_sz: bitstream size + * @resolution_changed:resolution change flag 1 - changed, 0 - not change + * @frame_header_type: current frame header type + * @wait_key_frame: wait key frame coming + * @crc: used to check whether hardware's status is right + * @reserved: reserved, currently unused + */ +struct vdec_vp8_slice_info { + u64 vld_wrapper_dma; + u64 seg_id_buf_dma; + u64 wrap_y_dma; + u64 wrap_c_dma; + u64 cur_y_fb_dma; + u64 cur_c_fb_dma; + u64 bs_dma; + u32 bs_sz; + u32 resolution_changed; + u32 frame_header_type; + u32 crc[8]; + u32 reserved; +}; + +/** + * struct vdec_vp8_slice_dpb_info - vp8 reference information + * + * @y_dma_addr: Y bitstream physical address + * @c_dma_addr: CbCr bitstream physical address + * @reference_flag: reference picture flag + * @reserved: 64bit align + */ +struct vdec_vp8_slice_dpb_info { + dma_addr_t y_dma_addr; + dma_addr_t c_dma_addr; + int reference_flag; + int reserved; +}; + +/** + * struct vdec_vp8_slice_vsi - VPU shared information + * + * @dec: decoding information + * @pic: picture information + * @vp8_dpb_info: reference buffer information + */ +struct vdec_vp8_slice_vsi { + struct vdec_vp8_slice_info dec; + struct vdec_pic_info pic; + struct vdec_vp8_slice_dpb_info vp8_dpb_info[3]; +}; + +/** + * struct vdec_vp8_slice_inst - VP8 decoder instance + * + * @seg_id_buf: seg buffer + * @wrap_y_buf: wrapper y buffer + * @wrap_c_buf: wrapper c buffer + * @vld_wrapper_buf: vld wrapper buffer + * @ctx: V4L2 context + * @vpu: VPU instance for decoder + * @vsi: VPU share information + */ +struct vdec_vp8_slice_inst { + struct mtk_vcodec_mem seg_id_buf; + struct mtk_vcodec_mem wrap_y_buf; + struct mtk_vcodec_mem wrap_c_buf; + struct mtk_vcodec_mem vld_wrapper_buf; + struct mtk_vcodec_ctx *ctx; + struct vdec_vpu_inst vpu; + struct vdec_vp8_slice_vsi *vsi; +}; + +static void *vdec_vp8_slice_get_ctrl_ptr(struct mtk_vcodec_ctx *ctx, int id) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, id); + + if (!ctrl) + return ERR_PTR(-EINVAL); + + return ctrl->p_cur.p; +} + +static void vdec_vp8_slice_get_pic_info(struct vdec_vp8_slice_inst *inst) +{ + struct mtk_vcodec_ctx *ctx = inst->ctx; + unsigned int data[3]; + + data[0] = ctx->picinfo.pic_w; + data[1] = ctx->picinfo.pic_h; + data[2] = ctx->capture_fourcc; + vpu_dec_get_param(&inst->vpu, data, 3, GET_PARAM_PIC_INFO); + + ctx->picinfo.buf_w = ALIGN(ctx->picinfo.pic_w, 64); + ctx->picinfo.buf_h = ALIGN(ctx->picinfo.pic_h, 64); + ctx->picinfo.fb_sz[0] = inst->vpu.fb_sz[0]; + ctx->picinfo.fb_sz[1] = inst->vpu.fb_sz[1]; + + inst->vsi->pic.pic_w = ctx->picinfo.pic_w; + inst->vsi->pic.pic_h = ctx->picinfo.pic_h; + inst->vsi->pic.buf_w = ctx->picinfo.buf_w; + inst->vsi->pic.buf_h = ctx->picinfo.buf_h; + inst->vsi->pic.fb_sz[0] = ctx->picinfo.fb_sz[0]; + inst->vsi->pic.fb_sz[1] = ctx->picinfo.fb_sz[1]; + mtk_vcodec_debug(inst, "pic(%d, %d), buf(%d, %d)", + ctx->picinfo.pic_w, ctx->picinfo.pic_h, + ctx->picinfo.buf_w, ctx->picinfo.buf_h); + mtk_vcodec_debug(inst, "fb size: Y(%d), C(%d)", + ctx->picinfo.fb_sz[0], ctx->picinfo.fb_sz[1]); +} + +static int vdec_vp8_slice_alloc_working_buf(struct vdec_vp8_slice_inst *inst) +{ + int err; + struct mtk_vcodec_mem *mem; + + mem = &inst->seg_id_buf; + mem->size = VP8_SEG_ID_SZ; + err = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (err) { + mtk_vcodec_err(inst, "Cannot allocate working buffer"); + return err; + } + inst->vsi->dec.seg_id_buf_dma = (u64)mem->dma_addr; + + mem = &inst->wrap_y_buf; + mem->size = VP8_PP_WRAPY_SZ; + err = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (err) { + mtk_vcodec_err(inst, "cannot allocate WRAP Y buffer"); + return err; + } + inst->vsi->dec.wrap_y_dma = (u64)mem->dma_addr; + + mem = &inst->wrap_c_buf; + mem->size = VP8_PP_WRAPC_SZ; + err = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (err) { + mtk_vcodec_err(inst, "cannot allocate WRAP C buffer"); + return err; + } + inst->vsi->dec.wrap_c_dma = (u64)mem->dma_addr; + + mem = &inst->vld_wrapper_buf; + mem->size = VP8_VLD_PRED_SZ; + err = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (err) { + mtk_vcodec_err(inst, "cannot allocate vld wrapper buffer"); + return err; + } + inst->vsi->dec.vld_wrapper_dma = (u64)mem->dma_addr; + + return 0; +} + +static void vdec_vp8_slice_free_working_buf(struct vdec_vp8_slice_inst *inst) +{ + struct mtk_vcodec_mem *mem; + + mem = &inst->seg_id_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + inst->vsi->dec.seg_id_buf_dma = 0; + + mem = &inst->wrap_y_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + inst->vsi->dec.wrap_y_dma = 0; + + mem = &inst->wrap_c_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + inst->vsi->dec.wrap_c_dma = 0; + + mem = &inst->vld_wrapper_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + inst->vsi->dec.vld_wrapper_dma = 0; +} + +static u64 vdec_vp8_slice_get_ref_by_ts(const struct v4l2_ctrl_vp8_frame *frame_header, + int index) +{ + switch (index) { + case 0: + return frame_header->last_frame_ts; + case 1: + return frame_header->golden_frame_ts; + case 2: + return frame_header->alt_frame_ts; + default: + break; + } + + return -1; +} + +static int vdec_vp8_slice_get_decode_parameters(struct vdec_vp8_slice_inst *inst) +{ + const struct v4l2_ctrl_vp8_frame *frame_header; + struct mtk_vcodec_ctx *ctx = inst->ctx; + struct vb2_queue *vq; + struct vb2_buffer *vb; + u64 referenct_ts; + int index; + + frame_header = vdec_vp8_slice_get_ctrl_ptr(inst->ctx, V4L2_CID_STATELESS_VP8_FRAME); + if (IS_ERR(frame_header)) + return PTR_ERR(frame_header); + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + for (index = 0; index < 3; index++) { + referenct_ts = vdec_vp8_slice_get_ref_by_ts(frame_header, index); + vb = vb2_find_buffer(vq, referenct_ts); + if (!vb) { + if (!V4L2_VP8_FRAME_IS_KEY_FRAME(frame_header)) + mtk_vcodec_err(inst, "reference invalid: index(%d) ts(%lld)", + index, referenct_ts); + inst->vsi->vp8_dpb_info[index].reference_flag = 0; + continue; + } + inst->vsi->vp8_dpb_info[index].reference_flag = 1; + + inst->vsi->vp8_dpb_info[index].y_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + if (ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 2) + inst->vsi->vp8_dpb_info[index].c_dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + else + inst->vsi->vp8_dpb_info[index].c_dma_addr = + inst->vsi->vp8_dpb_info[index].y_dma_addr + + ctx->picinfo.fb_sz[0]; + } + + inst->vsi->dec.frame_header_type = frame_header->flags >> 1; + + return 0; +} + +static int vdec_vp8_slice_init(struct mtk_vcodec_ctx *ctx) +{ + struct vdec_vp8_slice_inst *inst; + int err; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->ctx = ctx; + + inst->vpu.id = SCP_IPI_VDEC_LAT; + inst->vpu.core_id = SCP_IPI_VDEC_CORE; + inst->vpu.ctx = ctx; + inst->vpu.codec_type = ctx->current_codec; + inst->vpu.capture_type = ctx->capture_fourcc; + + err = vpu_dec_init(&inst->vpu); + if (err) { + mtk_vcodec_err(inst, "vdec_vp8 init err=%d", err); + goto error_free_inst; + } + + inst->vsi = inst->vpu.vsi; + err = vdec_vp8_slice_alloc_working_buf(inst); + if (err) + goto error_deinit; + + mtk_vcodec_debug(inst, "vp8 struct size = %d vsi: %d\n", + (int)sizeof(struct v4l2_ctrl_vp8_frame), + (int)sizeof(struct vdec_vp8_slice_vsi)); + mtk_vcodec_debug(inst, "vp8:%p, codec_type = 0x%x vsi: 0x%p", + inst, inst->vpu.codec_type, inst->vpu.vsi); + + ctx->drv_handle = inst; + return 0; + +error_deinit: + vpu_dec_deinit(&inst->vpu); +error_free_inst: + kfree(inst); + return err; +} + +static int vdec_vp8_slice_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + struct vdec_vp8_slice_inst *inst = h_vdec; + struct vdec_vpu_inst *vpu = &inst->vpu; + struct mtk_video_dec_buf *src_buf_info, *dst_buf_info; + unsigned int data; + u64 y_fb_dma, c_fb_dma; + int err, timeout; + + /* Resolution changes are never initiated by us */ + *res_chg = false; + + /* bs NULL means flush decoder */ + if (!bs) + return vpu_dec_reset(vpu); + + src_buf_info = container_of(bs, struct mtk_video_dec_buf, bs_buffer); + + fb = inst->ctx->dev->vdec_pdata->get_cap_buffer(inst->ctx); + dst_buf_info = container_of(fb, struct mtk_video_dec_buf, frame_buffer); + + y_fb_dma = fb ? (u64)fb->base_y.dma_addr : 0; + if (inst->ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes == 1) + c_fb_dma = y_fb_dma + + inst->ctx->picinfo.buf_w * inst->ctx->picinfo.buf_h; + else + c_fb_dma = fb ? (u64)fb->base_c.dma_addr : 0; + + inst->vsi->dec.bs_dma = (u64)bs->dma_addr; + inst->vsi->dec.bs_sz = bs->size; + inst->vsi->dec.cur_y_fb_dma = y_fb_dma; + inst->vsi->dec.cur_c_fb_dma = c_fb_dma; + + mtk_vcodec_debug(inst, "frame[%d] bs(%zu 0x%llx) y/c(0x%llx 0x%llx)", + inst->ctx->decoded_frame_cnt, + bs->size, (u64)bs->dma_addr, + y_fb_dma, c_fb_dma); + + v4l2_m2m_buf_copy_metadata(&src_buf_info->m2m_buf.vb, + &dst_buf_info->m2m_buf.vb, true); + + err = vdec_vp8_slice_get_decode_parameters(inst); + if (err) + goto error; + + err = vpu_dec_start(vpu, &data, 1); + if (err) { + mtk_vcodec_debug(inst, "vp8 dec start err!"); + goto error; + } + + if (inst->vsi->dec.resolution_changed) { + mtk_vcodec_debug(inst, "- resolution_changed -"); + *res_chg = true; + return 0; + } + + /* wait decode done interrupt */ + timeout = mtk_vcodec_wait_for_done_ctx(inst->ctx, MTK_INST_IRQ_RECEIVED, + 50, MTK_VDEC_CORE); + + err = vpu_dec_end(vpu); + if (err || timeout) + mtk_vcodec_debug(inst, "vp8 dec error timeout:%d err: %d pic_%d", + timeout, err, inst->ctx->decoded_frame_cnt); + + mtk_vcodec_debug(inst, "pic[%d] crc: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x", + inst->ctx->decoded_frame_cnt, + inst->vsi->dec.crc[0], inst->vsi->dec.crc[1], + inst->vsi->dec.crc[2], inst->vsi->dec.crc[3], + inst->vsi->dec.crc[4], inst->vsi->dec.crc[5], + inst->vsi->dec.crc[6], inst->vsi->dec.crc[7]); + + inst->ctx->decoded_frame_cnt++; +error: + return err; +} + +static int vdec_vp8_slice_get_param(void *h_vdec, enum vdec_get_param_type type, void *out) +{ + struct vdec_vp8_slice_inst *inst = h_vdec; + + switch (type) { + case GET_PARAM_PIC_INFO: + vdec_vp8_slice_get_pic_info(inst); + break; + case GET_PARAM_CROP_INFO: + mtk_vcodec_debug(inst, "No need to get vp8 crop information."); + break; + case GET_PARAM_DPB_SIZE: + *((unsigned int *)out) = VP8_DPB_SIZE; + break; + default: + mtk_vcodec_err(inst, "invalid get parameter type=%d", type); + return -EINVAL; + } + + return 0; +} + +static void vdec_vp8_slice_deinit(void *h_vdec) +{ + struct vdec_vp8_slice_inst *inst = h_vdec; + + mtk_vcodec_debug_enter(inst); + + vpu_dec_deinit(&inst->vpu); + vdec_vp8_slice_free_working_buf(inst); + kfree(inst); +} + +const struct vdec_common_if vdec_vp8_slice_if = { + .init = vdec_vp8_slice_init, + .decode = vdec_vp8_slice_decode, + .get_param = vdec_vp8_slice_get_param, + .deinit = vdec_vp8_slice_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_if.c new file mode 100644 index 000000000..a27a109d8 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_if.c @@ -0,0 +1,1029 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Daniel Hsiao + * Kai-Sean Yang + * Tiffany Lin + */ + +#include +#include +#include +#include +#include + +#include "../mtk_vcodec_intr.h" +#include "../vdec_drv_base.h" +#include "../vdec_vpu_if.h" + +#define VP9_SUPER_FRAME_BS_SZ 64 +#define MAX_VP9_DPB_SIZE 9 + +#define REFS_PER_FRAME 3 +#define MAX_NUM_REF_FRAMES 8 +#define VP9_MAX_FRM_BUF_NUM 9 +#define VP9_MAX_FRM_BUF_NODE_NUM (VP9_MAX_FRM_BUF_NUM * 2) +#define VP9_SEG_ID_SZ 0x12000 + +/** + * struct vp9_dram_buf - contains buffer info for vpu + * @va : cpu address + * @pa : iova address + * @sz : buffer size + * @padding : for 64 bytes alignment + */ +struct vp9_dram_buf { + unsigned long va; + unsigned long pa; + unsigned int sz; + unsigned int padding; +}; + +/** + * struct vp9_fb_info - contains frame buffer info + * @fb : frmae buffer + * @reserved : reserved field used by vpu + */ +struct vp9_fb_info { + struct vdec_fb *fb; + unsigned int reserved[32]; +}; + +/** + * struct vp9_ref_cnt_buf - contains reference buffer information + * @buf : referenced frame buffer + * @ref_cnt : referenced frame buffer's reference count. + * When reference count=0, remove it from reference list + */ +struct vp9_ref_cnt_buf { + struct vp9_fb_info buf; + unsigned int ref_cnt; +}; + +/** + * struct vp9_ref_buf - contains current frame's reference buffer information + * @buf : reference buffer + * @idx : reference buffer index to frm_bufs + * @reserved : reserved field used by vpu + */ +struct vp9_ref_buf { + struct vp9_fb_info *buf; + unsigned int idx; + unsigned int reserved[6]; +}; + +/** + * struct vp9_sf_ref_fb - contains frame buffer info + * @fb : super frame reference frame buffer + * @used : this reference frame info entry is used + * @padding : for 64 bytes size align + */ +struct vp9_sf_ref_fb { + struct vdec_fb fb; + int used; + int padding; +}; + +/* + * struct vdec_vp9_vsi - shared buffer between host and VPU firmware + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @sf_bs_buf : super frame backup buffer (AP-W, VPU-R) + * @sf_ref_fb : record supoer frame reference buffer information + * (AP-R/W, VPU-R/W) + * @sf_next_ref_fb_idx : next available super frame (AP-W, VPU-R) + * @sf_frm_cnt : super frame count, filled by vpu (AP-R, VPU-W) + * @sf_frm_offset : super frame offset, filled by vpu (AP-R, VPU-W) + * @sf_frm_sz : super frame size, filled by vpu (AP-R, VPU-W) + * @sf_frm_idx : current super frame (AP-R, VPU-W) + * @sf_init : inform super frame info already parsed by vpu (AP-R, VPU-W) + * @fb : capture buffer (AP-W, VPU-R) + * @bs : bs buffer (AP-W, VPU-R) + * @cur_fb : current show capture buffer (AP-R/W, VPU-R/W) + * @pic_w : picture width (AP-R, VPU-W) + * @pic_h : picture height (AP-R, VPU-W) + * @buf_w : codec width (AP-R, VPU-W) + * @buf_h : coded height (AP-R, VPU-W) + * @buf_sz_y_bs : ufo compressed y plane size (AP-R, VPU-W) + * @buf_sz_c_bs : ufo compressed cbcr plane size (AP-R, VPU-W) + * @buf_len_sz_y : size used to store y plane ufo info (AP-R, VPU-W) + * @buf_len_sz_c : size used to store cbcr plane ufo info (AP-R, VPU-W) + + * @profile : profile sparsed from vpu (AP-R, VPU-W) + * @show_frame : [BIT(0)] display this frame or not (AP-R, VPU-W) + * [BIT(1)] reset segment data or not (AP-R, VPU-W) + * [BIT(2)] trig decoder hardware or not (AP-R, VPU-W) + * [BIT(3)] ask VPU to set bits(0~4) accordingly (AP-W, VPU-R) + * [BIT(4)] do not reset segment data before every frame (AP-R, VPU-W) + * @show_existing_frame : inform this frame is show existing frame + * (AP-R, VPU-W) + * @frm_to_show_idx : index to show frame (AP-R, VPU-W) + + * @refresh_frm_flags : indicate when frame need to refine reference count + * (AP-R, VPU-W) + * @resolution_changed : resolution change in this frame (AP-R, VPU-W) + + * @frm_bufs : maintain reference buffer info (AP-R/W, VPU-R/W) + * @ref_frm_map : maintain reference buffer map info (AP-R/W, VPU-R/W) + * @new_fb_idx : index to frm_bufs array (AP-R, VPU-W) + * @frm_num : decoded frame number, include sub-frame count (AP-R, VPU-W) + * @mv_buf : motion vector working buffer (AP-W, VPU-R) + * @frm_refs : maintain three reference buffer info (AP-R/W, VPU-R/W) + * @seg_id_buf : segmentation map working buffer (AP-W, VPU-R) + */ +struct vdec_vp9_vsi { + unsigned char sf_bs_buf[VP9_SUPER_FRAME_BS_SZ]; + struct vp9_sf_ref_fb sf_ref_fb[VP9_MAX_FRM_BUF_NUM-1]; + int sf_next_ref_fb_idx; + unsigned int sf_frm_cnt; + unsigned int sf_frm_offset[VP9_MAX_FRM_BUF_NUM-1]; + unsigned int sf_frm_sz[VP9_MAX_FRM_BUF_NUM-1]; + unsigned int sf_frm_idx; + unsigned int sf_init; + struct vdec_fb fb; + struct mtk_vcodec_mem bs; + struct vdec_fb cur_fb; + unsigned int pic_w; + unsigned int pic_h; + unsigned int buf_w; + unsigned int buf_h; + unsigned int buf_sz_y_bs; + unsigned int buf_sz_c_bs; + unsigned int buf_len_sz_y; + unsigned int buf_len_sz_c; + unsigned int profile; + unsigned int show_frame; + unsigned int show_existing_frame; + unsigned int frm_to_show_idx; + unsigned int refresh_frm_flags; + unsigned int resolution_changed; + + struct vp9_ref_cnt_buf frm_bufs[VP9_MAX_FRM_BUF_NUM]; + int ref_frm_map[MAX_NUM_REF_FRAMES]; + unsigned int new_fb_idx; + unsigned int frm_num; + struct vp9_dram_buf mv_buf; + + struct vp9_ref_buf frm_refs[REFS_PER_FRAME]; + struct vp9_dram_buf seg_id_buf; + +}; + +/* + * struct vdec_vp9_inst - vp9 decode instance + * @mv_buf : working buffer for mv + * @seg_id_buf : working buffer for segmentation map + * @dec_fb : vdec_fb node to link fb to different fb_xxx_list + * @available_fb_node_list : current available vdec_fb node + * @fb_use_list : current used or referenced vdec_fb + * @fb_free_list : current available to free vdec_fb + * @fb_disp_list : current available to display vdec_fb + * @cur_fb : current frame buffer + * @ctx : current decode context + * @vpu : vpu instance information + * @vsi : shared buffer between host and VPU firmware + * @total_frm_cnt : total frame count, it do not include sub-frames in super + * frame + * @mem : instance memory information + */ +struct vdec_vp9_inst { + struct mtk_vcodec_mem mv_buf; + struct mtk_vcodec_mem seg_id_buf; + + struct vdec_fb_node dec_fb[VP9_MAX_FRM_BUF_NODE_NUM]; + struct list_head available_fb_node_list; + struct list_head fb_use_list; + struct list_head fb_free_list; + struct list_head fb_disp_list; + struct vdec_fb *cur_fb; + struct mtk_vcodec_ctx *ctx; + struct vdec_vpu_inst vpu; + struct vdec_vp9_vsi *vsi; + unsigned int total_frm_cnt; + struct mtk_vcodec_mem mem; +}; + +static bool vp9_is_sf_ref_fb(struct vdec_vp9_inst *inst, struct vdec_fb *fb) +{ + int i; + struct vdec_vp9_vsi *vsi = inst->vsi; + + for (i = 0; i < ARRAY_SIZE(vsi->sf_ref_fb); i++) { + if (fb == &vsi->sf_ref_fb[i].fb) + return true; + } + return false; +} + +static struct vdec_fb *vp9_rm_from_fb_use_list(struct vdec_vp9_inst + *inst, void *addr) +{ + struct vdec_fb *fb = NULL; + struct vdec_fb_node *node; + + list_for_each_entry(node, &inst->fb_use_list, list) { + fb = (struct vdec_fb *)node->fb; + if (fb->base_y.va == addr) { + list_move_tail(&node->list, + &inst->available_fb_node_list); + return fb; + } + } + + return NULL; +} + +static void vp9_add_to_fb_free_list(struct vdec_vp9_inst *inst, + struct vdec_fb *fb) +{ + struct vdec_fb_node *node; + + if (fb) { + node = list_first_entry_or_null(&inst->available_fb_node_list, + struct vdec_fb_node, list); + + if (node) { + node->fb = fb; + list_move_tail(&node->list, &inst->fb_free_list); + } + } else { + mtk_vcodec_debug(inst, "No free fb node"); + } +} + +static void vp9_free_sf_ref_fb(struct vdec_fb *fb) +{ + struct vp9_sf_ref_fb *sf_ref_fb = + container_of(fb, struct vp9_sf_ref_fb, fb); + + sf_ref_fb->used = 0; +} + +static void vp9_ref_cnt_fb(struct vdec_vp9_inst *inst, int *idx, + int new_idx) +{ + struct vdec_vp9_vsi *vsi = inst->vsi; + int ref_idx = *idx; + + if (ref_idx >= 0 && vsi->frm_bufs[ref_idx].ref_cnt > 0) { + vsi->frm_bufs[ref_idx].ref_cnt--; + + if (vsi->frm_bufs[ref_idx].ref_cnt == 0) { + if (!vp9_is_sf_ref_fb(inst, + vsi->frm_bufs[ref_idx].buf.fb)) { + struct vdec_fb *fb; + + fb = vp9_rm_from_fb_use_list(inst, + vsi->frm_bufs[ref_idx].buf.fb->base_y.va); + vp9_add_to_fb_free_list(inst, fb); + } else + vp9_free_sf_ref_fb( + vsi->frm_bufs[ref_idx].buf.fb); + } + } + + *idx = new_idx; + vsi->frm_bufs[new_idx].ref_cnt++; +} + +static void vp9_free_all_sf_ref_fb(struct vdec_vp9_inst *inst) +{ + int i; + struct vdec_vp9_vsi *vsi = inst->vsi; + + for (i = 0; i < ARRAY_SIZE(vsi->sf_ref_fb); i++) { + if (vsi->sf_ref_fb[i].fb.base_y.va) { + mtk_vcodec_mem_free(inst->ctx, + &vsi->sf_ref_fb[i].fb.base_y); + mtk_vcodec_mem_free(inst->ctx, + &vsi->sf_ref_fb[i].fb.base_c); + vsi->sf_ref_fb[i].used = 0; + } + } +} + +/* For each sub-frame except the last one, the driver will dynamically + * allocate reference buffer by calling vp9_get_sf_ref_fb() + * The last sub-frame will use the original fb provided by the + * vp9_dec_decode() interface + */ +static int vp9_get_sf_ref_fb(struct vdec_vp9_inst *inst) +{ + int idx; + struct mtk_vcodec_mem *mem_basy_y; + struct mtk_vcodec_mem *mem_basy_c; + struct vdec_vp9_vsi *vsi = inst->vsi; + + for (idx = 0; + idx < ARRAY_SIZE(vsi->sf_ref_fb); + idx++) { + if (vsi->sf_ref_fb[idx].fb.base_y.va && + vsi->sf_ref_fb[idx].used == 0) { + return idx; + } + } + + for (idx = 0; + idx < ARRAY_SIZE(vsi->sf_ref_fb); + idx++) { + if (vsi->sf_ref_fb[idx].fb.base_y.va == NULL) + break; + } + + if (idx == ARRAY_SIZE(vsi->sf_ref_fb)) { + mtk_vcodec_err(inst, "List Full"); + return -1; + } + + mem_basy_y = &vsi->sf_ref_fb[idx].fb.base_y; + mem_basy_y->size = vsi->buf_sz_y_bs + + vsi->buf_len_sz_y; + + if (mtk_vcodec_mem_alloc(inst->ctx, mem_basy_y)) { + mtk_vcodec_err(inst, "Cannot allocate sf_ref_buf y_buf"); + return -1; + } + + mem_basy_c = &vsi->sf_ref_fb[idx].fb.base_c; + mem_basy_c->size = vsi->buf_sz_c_bs + + vsi->buf_len_sz_c; + + if (mtk_vcodec_mem_alloc(inst->ctx, mem_basy_c)) { + mtk_vcodec_err(inst, "Cannot allocate sf_ref_fb c_buf"); + return -1; + } + vsi->sf_ref_fb[idx].used = 0; + + return idx; +} + +static bool vp9_alloc_work_buf(struct vdec_vp9_inst *inst) +{ + struct vdec_vp9_vsi *vsi = inst->vsi; + int result; + struct mtk_vcodec_mem *mem; + + unsigned int max_pic_w; + unsigned int max_pic_h; + + + if (!(inst->ctx->dev->dec_capability & + VCODEC_CAPABILITY_4K_DISABLED)) { + max_pic_w = VCODEC_DEC_4K_CODED_WIDTH; + max_pic_h = VCODEC_DEC_4K_CODED_HEIGHT; + } else { + max_pic_w = MTK_VDEC_MAX_W; + max_pic_h = MTK_VDEC_MAX_H; + } + + if ((vsi->pic_w > max_pic_w) || + (vsi->pic_h > max_pic_h)) { + mtk_vcodec_err(inst, "Invalid w/h %d/%d", + vsi->pic_w, vsi->pic_h); + return false; + } + + mtk_vcodec_debug(inst, "BUF CHG(%d): w/h/sb_w/sb_h=%d/%d/%d/%d", + vsi->resolution_changed, + vsi->pic_w, + vsi->pic_h, + vsi->buf_w, + vsi->buf_h); + + mem = &inst->mv_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + + mem->size = ((vsi->buf_w / 64) * + (vsi->buf_h / 64) + 2) * 36 * 16; + result = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (result) { + mem->size = 0; + mtk_vcodec_err(inst, "Cannot allocate mv_buf"); + return false; + } + /* Set the va again */ + vsi->mv_buf.va = (unsigned long)mem->va; + vsi->mv_buf.pa = (unsigned long)mem->dma_addr; + vsi->mv_buf.sz = (unsigned int)mem->size; + + + mem = &inst->seg_id_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + + mem->size = VP9_SEG_ID_SZ; + result = mtk_vcodec_mem_alloc(inst->ctx, mem); + if (result) { + mem->size = 0; + mtk_vcodec_err(inst, "Cannot allocate seg_id_buf"); + return false; + } + /* Set the va again */ + vsi->seg_id_buf.va = (unsigned long)mem->va; + vsi->seg_id_buf.pa = (unsigned long)mem->dma_addr; + vsi->seg_id_buf.sz = (unsigned int)mem->size; + + + vp9_free_all_sf_ref_fb(inst); + vsi->sf_next_ref_fb_idx = vp9_get_sf_ref_fb(inst); + + return true; +} + +static bool vp9_add_to_fb_disp_list(struct vdec_vp9_inst *inst, + struct vdec_fb *fb) +{ + struct vdec_fb_node *node; + + if (!fb) { + mtk_vcodec_err(inst, "fb == NULL"); + return false; + } + + node = list_first_entry_or_null(&inst->available_fb_node_list, + struct vdec_fb_node, list); + if (node) { + node->fb = fb; + list_move_tail(&node->list, &inst->fb_disp_list); + } else { + mtk_vcodec_err(inst, "No available fb node"); + return false; + } + + return true; +} + +/* If any buffer updating is signaled it should be done here. */ +static void vp9_swap_frm_bufs(struct vdec_vp9_inst *inst) +{ + struct vdec_vp9_vsi *vsi = inst->vsi; + struct vp9_fb_info *frm_to_show; + int ref_index = 0, mask; + + for (mask = vsi->refresh_frm_flags; mask; mask >>= 1) { + if (mask & 1) + vp9_ref_cnt_fb(inst, &vsi->ref_frm_map[ref_index], + vsi->new_fb_idx); + ++ref_index; + } + + frm_to_show = &vsi->frm_bufs[vsi->new_fb_idx].buf; + vsi->frm_bufs[vsi->new_fb_idx].ref_cnt--; + + if (frm_to_show->fb != inst->cur_fb) { + /* This frame is show exist frame and no decode output + * copy frame data from frm_to_show to current CAPTURE + * buffer + */ + if ((frm_to_show->fb != NULL) && + (inst->cur_fb->base_y.size >= + frm_to_show->fb->base_y.size) && + (inst->cur_fb->base_c.size >= + frm_to_show->fb->base_c.size)) { + memcpy((void *)inst->cur_fb->base_y.va, + (void *)frm_to_show->fb->base_y.va, + frm_to_show->fb->base_y.size); + memcpy((void *)inst->cur_fb->base_c.va, + (void *)frm_to_show->fb->base_c.va, + frm_to_show->fb->base_c.size); + } else { + /* After resolution change case, current CAPTURE buffer + * may have less buffer size than frm_to_show buffer + * size + */ + if (frm_to_show->fb != NULL) + mtk_vcodec_err(inst, + "inst->cur_fb->base_y.size=%zu, frm_to_show->fb.base_y.size=%zu", + inst->cur_fb->base_y.size, + frm_to_show->fb->base_y.size); + } + if (!vp9_is_sf_ref_fb(inst, inst->cur_fb)) { + if (vsi->show_frame & BIT(0)) + vp9_add_to_fb_disp_list(inst, inst->cur_fb); + } + } else { + if (!vp9_is_sf_ref_fb(inst, inst->cur_fb)) { + if (vsi->show_frame & BIT(0)) + vp9_add_to_fb_disp_list(inst, frm_to_show->fb); + } + } + + /* when ref_cnt ==0, move this fb to fb_free_list. v4l2 driver will + * clean fb_free_list + */ + if (vsi->frm_bufs[vsi->new_fb_idx].ref_cnt == 0) { + if (!vp9_is_sf_ref_fb( + inst, vsi->frm_bufs[vsi->new_fb_idx].buf.fb)) { + struct vdec_fb *fb; + + fb = vp9_rm_from_fb_use_list(inst, + vsi->frm_bufs[vsi->new_fb_idx].buf.fb->base_y.va); + + vp9_add_to_fb_free_list(inst, fb); + } else { + vp9_free_sf_ref_fb( + vsi->frm_bufs[vsi->new_fb_idx].buf.fb); + } + } + + /* if this super frame and it is not last sub-frame, get next fb for + * sub-frame decode + */ + if (vsi->sf_frm_cnt > 0 && vsi->sf_frm_idx != vsi->sf_frm_cnt - 1) + vsi->sf_next_ref_fb_idx = vp9_get_sf_ref_fb(inst); +} + +static bool vp9_wait_dec_end(struct vdec_vp9_inst *inst) +{ + struct mtk_vcodec_ctx *ctx = inst->ctx; + + mtk_vcodec_wait_for_done_ctx(inst->ctx, + MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, 0); + + if (ctx->irq_status & MTK_VDEC_IRQ_STATUS_DEC_SUCCESS) + return true; + else + return false; +} + +static struct vdec_vp9_inst *vp9_alloc_inst(struct mtk_vcodec_ctx *ctx) +{ + int result; + struct mtk_vcodec_mem mem; + struct vdec_vp9_inst *inst; + + memset(&mem, 0, sizeof(mem)); + mem.size = sizeof(struct vdec_vp9_inst); + result = mtk_vcodec_mem_alloc(ctx, &mem); + if (result) + return NULL; + + inst = mem.va; + inst->mem = mem; + + return inst; +} + +static void vp9_free_inst(struct vdec_vp9_inst *inst) +{ + struct mtk_vcodec_mem mem; + + mem = inst->mem; + if (mem.va) + mtk_vcodec_mem_free(inst->ctx, &mem); +} + +static bool vp9_decode_end_proc(struct vdec_vp9_inst *inst) +{ + struct vdec_vp9_vsi *vsi = inst->vsi; + bool ret = false; + + if (!vsi->show_existing_frame) { + ret = vp9_wait_dec_end(inst); + if (!ret) { + mtk_vcodec_err(inst, "Decode failed, Decode Timeout @[%d]", + vsi->frm_num); + return false; + } + + if (vpu_dec_end(&inst->vpu)) { + mtk_vcodec_err(inst, "vp9_dec_vpu_end failed"); + return false; + } + mtk_vcodec_debug(inst, "Decode Ok @%d (%d/%d)", vsi->frm_num, + vsi->pic_w, vsi->pic_h); + } else { + mtk_vcodec_debug(inst, "Decode Ok @%d (show_existing_frame)", + vsi->frm_num); + } + + vp9_swap_frm_bufs(inst); + vsi->frm_num++; + return true; +} + +static bool vp9_is_last_sub_frm(struct vdec_vp9_inst *inst) +{ + struct vdec_vp9_vsi *vsi = inst->vsi; + + if (vsi->sf_frm_cnt <= 0 || vsi->sf_frm_idx == vsi->sf_frm_cnt) + return true; + + return false; +} + +static struct vdec_fb *vp9_rm_from_fb_disp_list(struct vdec_vp9_inst *inst) +{ + struct vdec_fb_node *node; + struct vdec_fb *fb = NULL; + + node = list_first_entry_or_null(&inst->fb_disp_list, + struct vdec_fb_node, list); + if (node) { + fb = (struct vdec_fb *)node->fb; + fb->status |= FB_ST_DISPLAY; + list_move_tail(&node->list, &inst->available_fb_node_list); + mtk_vcodec_debug(inst, "[FB] get disp fb %p st=%d", + node->fb, fb->status); + } else + mtk_vcodec_debug(inst, "[FB] there is no disp fb"); + + return fb; +} + +static bool vp9_add_to_fb_use_list(struct vdec_vp9_inst *inst, + struct vdec_fb *fb) +{ + struct vdec_fb_node *node; + + if (!fb) { + mtk_vcodec_debug(inst, "fb == NULL"); + return false; + } + + node = list_first_entry_or_null(&inst->available_fb_node_list, + struct vdec_fb_node, list); + if (node) { + node->fb = fb; + list_move_tail(&node->list, &inst->fb_use_list); + } else { + mtk_vcodec_err(inst, "No free fb node"); + return false; + } + return true; +} + +static void vp9_reset(struct vdec_vp9_inst *inst) +{ + struct vdec_fb_node *node, *tmp; + + list_for_each_entry_safe(node, tmp, &inst->fb_use_list, list) + list_move_tail(&node->list, &inst->fb_free_list); + + vp9_free_all_sf_ref_fb(inst); + inst->vsi->sf_next_ref_fb_idx = vp9_get_sf_ref_fb(inst); + + if (vpu_dec_reset(&inst->vpu)) + mtk_vcodec_err(inst, "vp9_dec_vpu_reset failed"); + + /* Set the va again, since vpu_dec_reset will clear mv_buf in vpu */ + inst->vsi->mv_buf.va = (unsigned long)inst->mv_buf.va; + inst->vsi->mv_buf.pa = (unsigned long)inst->mv_buf.dma_addr; + inst->vsi->mv_buf.sz = (unsigned long)inst->mv_buf.size; + + /* Set the va again, since vpu_dec_reset will clear seg_id_buf in vpu */ + inst->vsi->seg_id_buf.va = (unsigned long)inst->seg_id_buf.va; + inst->vsi->seg_id_buf.pa = (unsigned long)inst->seg_id_buf.dma_addr; + inst->vsi->seg_id_buf.sz = (unsigned long)inst->seg_id_buf.size; + +} + +static void init_all_fb_lists(struct vdec_vp9_inst *inst) +{ + int i; + + INIT_LIST_HEAD(&inst->available_fb_node_list); + INIT_LIST_HEAD(&inst->fb_use_list); + INIT_LIST_HEAD(&inst->fb_free_list); + INIT_LIST_HEAD(&inst->fb_disp_list); + + for (i = 0; i < ARRAY_SIZE(inst->dec_fb); i++) { + INIT_LIST_HEAD(&inst->dec_fb[i].list); + inst->dec_fb[i].fb = NULL; + list_add_tail(&inst->dec_fb[i].list, + &inst->available_fb_node_list); + } +} + +static void get_pic_info(struct vdec_vp9_inst *inst, struct vdec_pic_info *pic) +{ + pic->fb_sz[0] = inst->vsi->buf_sz_y_bs + inst->vsi->buf_len_sz_y; + pic->fb_sz[1] = inst->vsi->buf_sz_c_bs + inst->vsi->buf_len_sz_c; + + pic->pic_w = inst->vsi->pic_w; + pic->pic_h = inst->vsi->pic_h; + pic->buf_w = inst->vsi->buf_w; + pic->buf_h = inst->vsi->buf_h; + + mtk_vcodec_debug(inst, "pic(%d, %d), buf(%d, %d)", + pic->pic_w, pic->pic_h, pic->buf_w, pic->buf_h); + mtk_vcodec_debug(inst, "fb size: Y(%d), C(%d)", + pic->fb_sz[0], + pic->fb_sz[1]); +} + +static void get_disp_fb(struct vdec_vp9_inst *inst, struct vdec_fb **out_fb) +{ + + *out_fb = vp9_rm_from_fb_disp_list(inst); + if (*out_fb) + (*out_fb)->status |= FB_ST_DISPLAY; +} + +static void get_free_fb(struct vdec_vp9_inst *inst, struct vdec_fb **out_fb) +{ + struct vdec_fb_node *node; + struct vdec_fb *fb = NULL; + + node = list_first_entry_or_null(&inst->fb_free_list, + struct vdec_fb_node, list); + if (node) { + list_move_tail(&node->list, &inst->available_fb_node_list); + fb = (struct vdec_fb *)node->fb; + fb->status |= FB_ST_FREE; + mtk_vcodec_debug(inst, "[FB] get free fb %p st=%d", + node->fb, fb->status); + } else { + mtk_vcodec_debug(inst, "[FB] there is no free fb"); + } + + *out_fb = fb; +} + +static int validate_vsi_array_indexes(struct vdec_vp9_inst *inst, + struct vdec_vp9_vsi *vsi) { + if (vsi->sf_frm_idx >= VP9_MAX_FRM_BUF_NUM - 1) { + mtk_vcodec_err(inst, "Invalid vsi->sf_frm_idx=%u.", + vsi->sf_frm_idx); + return -EIO; + } + if (vsi->frm_to_show_idx >= VP9_MAX_FRM_BUF_NUM) { + mtk_vcodec_err(inst, "Invalid vsi->frm_to_show_idx=%u.", + vsi->frm_to_show_idx); + return -EIO; + } + if (vsi->new_fb_idx >= VP9_MAX_FRM_BUF_NUM) { + mtk_vcodec_err(inst, "Invalid vsi->new_fb_idx=%u.", + vsi->new_fb_idx); + return -EIO; + } + return 0; +} + +static void vdec_vp9_deinit(void *h_vdec) +{ + struct vdec_vp9_inst *inst = (struct vdec_vp9_inst *)h_vdec; + struct mtk_vcodec_mem *mem; + int ret = 0; + + ret = vpu_dec_deinit(&inst->vpu); + if (ret) + mtk_vcodec_err(inst, "vpu_dec_deinit failed"); + + mem = &inst->mv_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + + mem = &inst->seg_id_buf; + if (mem->va) + mtk_vcodec_mem_free(inst->ctx, mem); + + vp9_free_all_sf_ref_fb(inst); + vp9_free_inst(inst); +} + +static int vdec_vp9_init(struct mtk_vcodec_ctx *ctx) +{ + struct vdec_vp9_inst *inst; + + inst = vp9_alloc_inst(ctx); + if (!inst) + return -ENOMEM; + + inst->total_frm_cnt = 0; + inst->ctx = ctx; + + inst->vpu.id = IPI_VDEC_VP9; + inst->vpu.ctx = ctx; + + if (vpu_dec_init(&inst->vpu)) { + mtk_vcodec_err(inst, "vp9_dec_vpu_init failed"); + goto err_deinit_inst; + } + + inst->vsi = (struct vdec_vp9_vsi *)inst->vpu.vsi; + + inst->vsi->show_frame |= BIT(3); + + init_all_fb_lists(inst); + + ctx->drv_handle = inst; + return 0; + +err_deinit_inst: + vp9_free_inst(inst); + + return -EINVAL; +} + +static int vdec_vp9_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + int ret = 0; + struct vdec_vp9_inst *inst = (struct vdec_vp9_inst *)h_vdec; + struct vdec_vp9_vsi *vsi = inst->vsi; + u32 data[3]; + int i; + + *res_chg = false; + + if ((bs == NULL) && (fb == NULL)) { + mtk_vcodec_debug(inst, "[EOS]"); + vp9_reset(inst); + return ret; + } + + if (bs == NULL) { + mtk_vcodec_err(inst, "bs == NULL"); + return -EINVAL; + } + + mtk_vcodec_debug(inst, "Input BS Size = %zu", bs->size); + + while (1) { + struct vdec_fb *cur_fb = NULL; + + data[0] = *((unsigned int *)bs->va); + data[1] = *((unsigned int *)(bs->va + 4)); + data[2] = *((unsigned int *)(bs->va + 8)); + + vsi->bs = *bs; + + if (fb) + vsi->fb = *fb; + + if (!vsi->sf_init) { + unsigned int sf_bs_sz; + unsigned int sf_bs_off; + unsigned char *sf_bs_src; + unsigned char *sf_bs_dst; + + sf_bs_sz = bs->size > VP9_SUPER_FRAME_BS_SZ ? + VP9_SUPER_FRAME_BS_SZ : bs->size; + sf_bs_off = VP9_SUPER_FRAME_BS_SZ - sf_bs_sz; + sf_bs_src = bs->va + bs->size - sf_bs_sz; + sf_bs_dst = vsi->sf_bs_buf + sf_bs_off; + memcpy(sf_bs_dst, sf_bs_src, sf_bs_sz); + } else { + if ((vsi->sf_frm_cnt > 0) && + (vsi->sf_frm_idx < vsi->sf_frm_cnt)) { + unsigned int idx = vsi->sf_frm_idx; + + memcpy((void *)bs->va, + (void *)(bs->va + + vsi->sf_frm_offset[idx]), + vsi->sf_frm_sz[idx]); + } + } + + if (!(vsi->show_frame & BIT(4))) + memset(inst->seg_id_buf.va, 0, inst->seg_id_buf.size); + + ret = vpu_dec_start(&inst->vpu, data, 3); + if (ret) { + mtk_vcodec_err(inst, "vpu_dec_start failed"); + goto DECODE_ERROR; + } + + if (vsi->show_frame & BIT(1)) { + memset(inst->seg_id_buf.va, 0, inst->seg_id_buf.size); + + if (vsi->show_frame & BIT(2)) { + ret = vpu_dec_start(&inst->vpu, NULL, 0); + if (ret) { + mtk_vcodec_err(inst, "vpu trig decoder failed"); + goto DECODE_ERROR; + } + } + } + + ret = validate_vsi_array_indexes(inst, vsi); + if (ret) { + mtk_vcodec_err(inst, "Invalid values from VPU."); + goto DECODE_ERROR; + } + + if (vsi->resolution_changed) { + if (!vp9_alloc_work_buf(inst)) { + ret = -EIO; + goto DECODE_ERROR; + } + } + + if (vsi->sf_frm_cnt > 0) { + cur_fb = &vsi->sf_ref_fb[vsi->sf_next_ref_fb_idx].fb; + + if (vsi->sf_frm_idx < vsi->sf_frm_cnt) + inst->cur_fb = cur_fb; + else + inst->cur_fb = fb; + } else { + inst->cur_fb = fb; + } + + vsi->frm_bufs[vsi->new_fb_idx].buf.fb = inst->cur_fb; + if (!vp9_is_sf_ref_fb(inst, inst->cur_fb)) + vp9_add_to_fb_use_list(inst, inst->cur_fb); + + mtk_vcodec_debug(inst, "[#pic %d]", vsi->frm_num); + + if (vsi->show_existing_frame) + mtk_vcodec_debug(inst, + "drv->new_fb_idx=%d, drv->frm_to_show_idx=%d", + vsi->new_fb_idx, vsi->frm_to_show_idx); + + if (vsi->show_existing_frame && (vsi->frm_to_show_idx < + VP9_MAX_FRM_BUF_NUM)) { + mtk_vcodec_debug(inst, + "Skip Decode drv->new_fb_idx=%d, drv->frm_to_show_idx=%d", + vsi->new_fb_idx, vsi->frm_to_show_idx); + + vp9_ref_cnt_fb(inst, &vsi->new_fb_idx, + vsi->frm_to_show_idx); + } + + /* VPU assign the buffer pointer in its address space, + * reassign here + */ + for (i = 0; i < ARRAY_SIZE(vsi->frm_refs); i++) { + unsigned int idx = vsi->frm_refs[i].idx; + + vsi->frm_refs[i].buf = &vsi->frm_bufs[idx].buf; + } + + if (vsi->resolution_changed) { + *res_chg = true; + mtk_vcodec_debug(inst, "VDEC_ST_RESOLUTION_CHANGED"); + + ret = 0; + goto DECODE_ERROR; + } + + if (!vp9_decode_end_proc(inst)) { + mtk_vcodec_err(inst, "vp9_decode_end_proc"); + ret = -EINVAL; + goto DECODE_ERROR; + } + + if (vp9_is_last_sub_frm(inst)) + break; + + } + inst->total_frm_cnt++; + +DECODE_ERROR: + if (ret < 0) + vp9_add_to_fb_free_list(inst, fb); + + return ret; +} + +static void get_crop_info(struct vdec_vp9_inst *inst, struct v4l2_rect *cr) +{ + cr->left = 0; + cr->top = 0; + cr->width = inst->vsi->pic_w; + cr->height = inst->vsi->pic_h; + mtk_vcodec_debug(inst, "get crop info l=%d, t=%d, w=%d, h=%d\n", + cr->left, cr->top, cr->width, cr->height); +} + +static int vdec_vp9_get_param(void *h_vdec, enum vdec_get_param_type type, + void *out) +{ + struct vdec_vp9_inst *inst = (struct vdec_vp9_inst *)h_vdec; + int ret = 0; + + switch (type) { + case GET_PARAM_DISP_FRAME_BUFFER: + get_disp_fb(inst, out); + break; + case GET_PARAM_FREE_FRAME_BUFFER: + get_free_fb(inst, out); + break; + case GET_PARAM_PIC_INFO: + get_pic_info(inst, out); + break; + case GET_PARAM_DPB_SIZE: + *((unsigned int *)out) = MAX_VP9_DPB_SIZE; + break; + case GET_PARAM_CROP_INFO: + get_crop_info(inst, out); + break; + default: + mtk_vcodec_err(inst, "not supported param type %d", type); + ret = -EINVAL; + break; + } + + return ret; +} + +const struct vdec_common_if vdec_vp9_if = { + .init = vdec_vp9_init, + .decode = vdec_vp9_decode, + .get_param = vdec_vp9_get_param, + .deinit = vdec_vp9_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c new file mode 100644 index 000000000..cf16cf280 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec/vdec_vp9_req_lat_if.c @@ -0,0 +1,2225 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + * Author: George Sun + */ + +#include +#include +#include +#include + +#include "../mtk_vcodec_util.h" +#include "../mtk_vcodec_dec.h" +#include "../mtk_vcodec_intr.h" +#include "../vdec_drv_base.h" +#include "../vdec_drv_if.h" +#include "../vdec_vpu_if.h" + +/* reset_frame_context defined in VP9 spec */ +#define VP9_RESET_FRAME_CONTEXT_NONE0 0 +#define VP9_RESET_FRAME_CONTEXT_NONE1 1 +#define VP9_RESET_FRAME_CONTEXT_SPEC 2 +#define VP9_RESET_FRAME_CONTEXT_ALL 3 + +#define VP9_TILE_BUF_SIZE 4096 +#define VP9_PROB_BUF_SIZE 2560 +#define VP9_COUNTS_BUF_SIZE 16384 + +#define HDR_FLAG(x) (!!((hdr)->flags & V4L2_VP9_FRAME_FLAG_##x)) +#define LF_FLAG(x) (!!((lf)->flags & V4L2_VP9_LOOP_FILTER_FLAG_##x)) +#define SEG_FLAG(x) (!!((seg)->flags & V4L2_VP9_SEGMENTATION_FLAG_##x)) +#define VP9_BAND_6(band) ((band) == 0 ? 3 : 6) + +/* + * struct vdec_vp9_slice_frame_ctx - vp9 prob tables footprint + */ +struct vdec_vp9_slice_frame_ctx { + struct { + u8 probs[6][3]; + u8 padding[2]; + } coef_probs[4][2][2][6]; + + u8 y_mode_prob[4][16]; + u8 switch_interp_prob[4][16]; + u8 seg[32]; /* ignore */ + u8 comp_inter_prob[16]; + u8 comp_ref_prob[16]; + u8 single_ref_prob[5][2]; + u8 single_ref_prob_padding[6]; + + u8 joint[3]; + u8 joint_padding[13]; + struct { + u8 sign; + u8 classes[10]; + u8 padding[5]; + } sign_classes[2]; + struct { + u8 class0[1]; + u8 bits[10]; + u8 padding[5]; + } class0_bits[2]; + struct { + u8 class0_fp[2][3]; + u8 fp[3]; + u8 class0_hp; + u8 hp; + u8 padding[5]; + } class0_fp_hp[2]; + + u8 uv_mode_prob[10][16]; + u8 uv_mode_prob_padding[2][16]; + + u8 partition_prob[16][4]; + + u8 inter_mode_probs[7][4]; + u8 skip_probs[4]; + + u8 tx_p8x8[2][4]; + u8 tx_p16x16[2][4]; + u8 tx_p32x32[2][4]; + u8 intra_inter_prob[8]; +}; + +/* + * struct vdec_vp9_slice_frame_counts - vp9 counts tables footprint + */ +struct vdec_vp9_slice_frame_counts { + union { + struct { + u32 band_0[3]; + u32 padding0[1]; + u32 band_1_5[5][6]; + u32 padding1[2]; + } eob_branch[4][2][2]; + u32 eob_branch_space[256 * 4]; + }; + + struct { + u32 band_0[3][4]; + u32 band_1_5[5][6][4]; + } coef_probs[4][2][2]; + + u32 intra_inter[4][2]; + u32 comp_inter[5][2]; + u32 comp_inter_padding[2]; + u32 comp_ref[5][2]; + u32 comp_ref_padding[2]; + u32 single_ref[5][2][2]; + u32 inter_mode[7][4]; + u32 y_mode[4][12]; + u32 uv_mode[10][10]; + u32 partition[16][4]; + u32 switchable_interp[4][4]; + + u32 tx_p8x8[2][2]; + u32 tx_p16x16[2][4]; + u32 tx_p32x32[2][4]; + + u32 skip[3][4]; + + u32 joint[4]; + + struct { + u32 sign[2]; + u32 class0[2]; + u32 classes[12]; + u32 bits[10][2]; + u32 padding[4]; + u32 class0_fp[2][4]; + u32 fp[4]; + u32 class0_hp[2]; + u32 hp[2]; + } mvcomp[2]; + + u32 reserved[126][4]; +}; + +/** + * struct vdec_vp9_slice_counts_map - vp9 counts tables to map + * v4l2_vp9_frame_symbol_counts + * @skip: skip counts. + * @y_mode: Y prediction mode counts. + * @filter: interpolation filter counts. + * @mv_joint: motion vector joint counts. + * @sign: motion vector sign counts. + * @classes: motion vector class counts. + * @class0: motion vector class0 bit counts. + * @bits: motion vector bits counts. + * @class0_fp: motion vector class0 fractional bit counts. + * @fp: motion vector fractional bit counts. + * @class0_hp: motion vector class0 high precision fractional bit counts. + * @hp: motion vector high precision fractional bit counts. + */ +struct vdec_vp9_slice_counts_map { + u32 skip[3][2]; + u32 y_mode[4][10]; + u32 filter[4][3]; + u32 sign[2][2]; + u32 classes[2][11]; + u32 class0[2][2]; + u32 bits[2][10][2]; + u32 class0_fp[2][2][4]; + u32 fp[2][4]; + u32 class0_hp[2][2]; + u32 hp[2][2]; +}; + +/* + * struct vdec_vp9_slice_uncompressed_header - vp9 uncompressed header syntax + * used for decoding + */ +struct vdec_vp9_slice_uncompressed_header { + u8 profile; + u8 last_frame_type; + u8 frame_type; + + u8 last_show_frame; + u8 show_frame; + u8 error_resilient_mode; + + u8 bit_depth; + u8 padding0[1]; + u16 last_frame_width; + u16 last_frame_height; + u16 frame_width; + u16 frame_height; + + u8 intra_only; + u8 reset_frame_context; + u8 ref_frame_sign_bias[4]; + u8 allow_high_precision_mv; + u8 interpolation_filter; + + u8 refresh_frame_context; + u8 frame_parallel_decoding_mode; + u8 frame_context_idx; + + /* loop_filter_params */ + u8 loop_filter_level; + u8 loop_filter_sharpness; + u8 loop_filter_delta_enabled; + s8 loop_filter_ref_deltas[4]; + s8 loop_filter_mode_deltas[2]; + + /* quantization_params */ + u8 base_q_idx; + s8 delta_q_y_dc; + s8 delta_q_uv_dc; + s8 delta_q_uv_ac; + + /* segmentation_params */ + u8 segmentation_enabled; + u8 segmentation_update_map; + u8 segmentation_tree_probs[7]; + u8 padding1[1]; + u8 segmentation_temporal_udpate; + u8 segmentation_pred_prob[3]; + u8 segmentation_update_data; + u8 segmentation_abs_or_delta_update; + u8 feature_enabled[8]; + s16 feature_value[8][4]; + + /* tile_info */ + u8 tile_cols_log2; + u8 tile_rows_log2; + u8 padding2[2]; + + u16 uncompressed_header_size; + u16 header_size_in_bytes; + + /* LAT OUT, CORE IN */ + u32 dequant[8][4]; +}; + +/* + * struct vdec_vp9_slice_compressed_header - vp9 compressed header syntax + * used for decoding. + */ +struct vdec_vp9_slice_compressed_header { + u8 tx_mode; + u8 ref_mode; + u8 comp_fixed_ref; + u8 comp_var_ref[2]; + u8 padding[3]; +}; + +/* + * struct vdec_vp9_slice_tiles - vp9 tile syntax + */ +struct vdec_vp9_slice_tiles { + u32 size[4][64]; + u32 mi_rows[4]; + u32 mi_cols[64]; + u8 actual_rows; + u8 padding[7]; +}; + +/* + * struct vdec_vp9_slice_reference - vp9 reference frame information + */ +struct vdec_vp9_slice_reference { + u16 frame_width; + u16 frame_height; + u8 bit_depth; + u8 subsampling_x; + u8 subsampling_y; + u8 padding; +}; + +/* + * struct vdec_vp9_slice_frame - vp9 syntax used for decoding + */ +struct vdec_vp9_slice_frame { + struct vdec_vp9_slice_uncompressed_header uh; + struct vdec_vp9_slice_compressed_header ch; + struct vdec_vp9_slice_tiles tiles; + struct vdec_vp9_slice_reference ref[3]; +}; + +/* + * struct vdec_vp9_slice_init_vsi - VSI used to initialize instance + */ +struct vdec_vp9_slice_init_vsi { + unsigned int architecture; + unsigned int reserved; + u64 core_vsi; + /* default frame context's position in MicroP */ + u64 default_frame_ctx; +}; + +/* + * struct vdec_vp9_slice_mem - memory address and size + */ +struct vdec_vp9_slice_mem { + union { + u64 buf; + dma_addr_t dma_addr; + }; + union { + size_t size; + dma_addr_t dma_addr_end; + u64 padding; + }; +}; + +/* + * struct vdec_vp9_slice_bs - input buffer for decoding + */ +struct vdec_vp9_slice_bs { + struct vdec_vp9_slice_mem buf; + struct vdec_vp9_slice_mem frame; +}; + +/* + * struct vdec_vp9_slice_fb - frame buffer for decoding + */ +struct vdec_vp9_slice_fb { + struct vdec_vp9_slice_mem y; + struct vdec_vp9_slice_mem c; +}; + +/* + * struct vdec_vp9_slice_state - decoding state + */ +struct vdec_vp9_slice_state { + int err; + unsigned int full; + unsigned int timeout; + unsigned int perf; + + unsigned int crc[12]; +}; + +/** + * struct vdec_vp9_slice_vsi - exchange decoding information + * between Main CPU and MicroP + * + * @bs: input buffer + * @fb: output buffer + * @ref: 3 reference buffers + * @mv: mv working buffer + * @seg: segmentation working buffer + * @tile: tile buffer + * @prob: prob table buffer, used to set/update prob table + * @counts: counts table buffer, used to update prob table + * @ube: general buffer + * @trans: trans buffer position in general buffer + * @err_map: error buffer + * @row_info: row info buffer + * @frame: decoding syntax + * @state: decoding state + */ +struct vdec_vp9_slice_vsi { + /* used in LAT stage */ + struct vdec_vp9_slice_bs bs; + /* used in Core stage */ + struct vdec_vp9_slice_fb fb; + struct vdec_vp9_slice_fb ref[3]; + + struct vdec_vp9_slice_mem mv[2]; + struct vdec_vp9_slice_mem seg[2]; + struct vdec_vp9_slice_mem tile; + struct vdec_vp9_slice_mem prob; + struct vdec_vp9_slice_mem counts; + + /* LAT stage's output, Core stage's input */ + struct vdec_vp9_slice_mem ube; + struct vdec_vp9_slice_mem trans; + struct vdec_vp9_slice_mem err_map; + struct vdec_vp9_slice_mem row_info; + + /* decoding parameters */ + struct vdec_vp9_slice_frame frame; + + struct vdec_vp9_slice_state state; +}; + +/** + * struct vdec_vp9_slice_pfc - per-frame context that contains a local vsi. + * pass it from lat to core + * + * @vsi: local vsi. copy to/from remote vsi before/after decoding + * @ref_idx: reference buffer index + * @seq: picture sequence + * @state: decoding state + */ +struct vdec_vp9_slice_pfc { + struct vdec_vp9_slice_vsi vsi; + + u64 ref_idx[3]; + + int seq; + + /* LAT/Core CRC */ + struct vdec_vp9_slice_state state[2]; +}; + +/* + * enum vdec_vp9_slice_resolution_level + */ +enum vdec_vp9_slice_resolution_level { + VP9_RES_NONE, + VP9_RES_FHD, + VP9_RES_4K, + VP9_RES_8K, +}; + +/* + * struct vdec_vp9_slice_ref - picture's width & height should kept + * for later decoding as reference picture + */ +struct vdec_vp9_slice_ref { + unsigned int width; + unsigned int height; +}; + +/** + * struct vdec_vp9_slice_instance - represent one vp9 instance + * + * @ctx: pointer to codec's context + * @vpu: VPU instance + * @seq: global picture sequence + * @level: level of current resolution + * @width: width of last picture + * @height: height of last picture + * @frame_type: frame_type of last picture + * @irq: irq to Main CPU or MicroP + * @show_frame: show_frame of last picture + * @dpb: picture information (width/height) for reference + * @mv: mv working buffer + * @seg: segmentation working buffer + * @tile: tile buffer + * @prob: prob table buffer, used to set/update prob table + * @counts: counts table buffer, used to update prob table + * @frame_ctx: 4 frame context according to VP9 Spec + * @frame_ctx_helper: 4 frame context according to newest kernel spec + * @dirty: state of each frame context + * @init_vsi: vsi used for initialized VP9 instance + * @vsi: vsi used for decoding/flush ... + * @core_vsi: vsi used for Core stage + * + * @sc_pfc: per frame context single core + * @counts_map: used map to counts_helper + * @counts_helper: counts table according to newest kernel spec + */ +struct vdec_vp9_slice_instance { + struct mtk_vcodec_ctx *ctx; + struct vdec_vpu_inst vpu; + + int seq; + + enum vdec_vp9_slice_resolution_level level; + + /* for resolution change and get_pic_info */ + unsigned int width; + unsigned int height; + + /* for last_frame_type */ + unsigned int frame_type; + unsigned int irq; + + unsigned int show_frame; + + /* maintain vp9 reference frame state */ + struct vdec_vp9_slice_ref dpb[VB2_MAX_FRAME]; + + /* + * normal working buffers + * mv[0]/seg[0]/tile/prob/counts is used for LAT + * mv[1]/seg[1] is used for CORE + */ + struct mtk_vcodec_mem mv[2]; + struct mtk_vcodec_mem seg[2]; + struct mtk_vcodec_mem tile; + struct mtk_vcodec_mem prob; + struct mtk_vcodec_mem counts; + + /* 4 prob tables */ + struct vdec_vp9_slice_frame_ctx frame_ctx[4]; + /*4 helper tables */ + struct v4l2_vp9_frame_context frame_ctx_helper; + unsigned char dirty[4]; + + /* MicroP vsi */ + union { + struct vdec_vp9_slice_init_vsi *init_vsi; + struct vdec_vp9_slice_vsi *vsi; + }; + struct vdec_vp9_slice_vsi *core_vsi; + + struct vdec_vp9_slice_pfc sc_pfc; + struct vdec_vp9_slice_counts_map counts_map; + struct v4l2_vp9_frame_symbol_counts counts_helper; +}; + +/* + * all VP9 instances could share this default frame context. + */ +static struct vdec_vp9_slice_frame_ctx *vdec_vp9_slice_default_frame_ctx; +static DEFINE_MUTEX(vdec_vp9_slice_frame_ctx_lock); + +static int vdec_vp9_slice_core_decode(struct vdec_lat_buf *lat_buf); + +static int vdec_vp9_slice_init_default_frame_ctx(struct vdec_vp9_slice_instance *instance) +{ + struct vdec_vp9_slice_frame_ctx *remote_frame_ctx; + struct vdec_vp9_slice_frame_ctx *frame_ctx; + struct mtk_vcodec_ctx *ctx; + struct vdec_vp9_slice_init_vsi *vsi; + int ret = 0; + + ctx = instance->ctx; + vsi = instance->vpu.vsi; + if (!ctx || !vsi) + return -EINVAL; + + remote_frame_ctx = mtk_vcodec_fw_map_dm_addr(ctx->dev->fw_handler, + (u32)vsi->default_frame_ctx); + if (!remote_frame_ctx) { + mtk_vcodec_err(instance, "failed to map default frame ctx\n"); + return -EINVAL; + } + + mutex_lock(&vdec_vp9_slice_frame_ctx_lock); + if (vdec_vp9_slice_default_frame_ctx) + goto out; + + frame_ctx = kmemdup(remote_frame_ctx, sizeof(*frame_ctx), GFP_KERNEL); + if (!frame_ctx) { + ret = -ENOMEM; + goto out; + } + + vdec_vp9_slice_default_frame_ctx = frame_ctx; + +out: + mutex_unlock(&vdec_vp9_slice_frame_ctx_lock); + + return ret; +} + +static int vdec_vp9_slice_alloc_working_buffer(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_vsi *vsi) +{ + struct mtk_vcodec_ctx *ctx = instance->ctx; + enum vdec_vp9_slice_resolution_level level; + /* super blocks */ + unsigned int max_sb_w; + unsigned int max_sb_h; + unsigned int max_w; + unsigned int max_h; + unsigned int w; + unsigned int h; + size_t size; + int ret; + int i; + + w = vsi->frame.uh.frame_width; + h = vsi->frame.uh.frame_height; + + if (w > VCODEC_DEC_4K_CODED_WIDTH || + h > VCODEC_DEC_4K_CODED_HEIGHT) { + return -EINVAL; + } else if (w > MTK_VDEC_MAX_W || h > MTK_VDEC_MAX_H) { + /* 4K */ + level = VP9_RES_4K; + max_w = VCODEC_DEC_4K_CODED_WIDTH; + max_h = VCODEC_DEC_4K_CODED_HEIGHT; + } else { + /* FHD */ + level = VP9_RES_FHD; + max_w = MTK_VDEC_MAX_W; + max_h = MTK_VDEC_MAX_H; + } + + if (level == instance->level) + return 0; + + mtk_vcodec_debug(instance, "resolution level changed, from %u to %u, %ux%u", + instance->level, level, w, h); + + max_sb_w = DIV_ROUND_UP(max_w, 64); + max_sb_h = DIV_ROUND_UP(max_h, 64); + ret = -ENOMEM; + + /* + * Lat-flush must wait core idle, otherwise core will + * use released buffers + */ + + size = (max_sb_w * max_sb_h + 2) * 576; + for (i = 0; i < 2; i++) { + if (instance->mv[i].va) + mtk_vcodec_mem_free(ctx, &instance->mv[i]); + instance->mv[i].size = size; + if (mtk_vcodec_mem_alloc(ctx, &instance->mv[i])) + goto err; + } + + size = (max_sb_w * max_sb_h * 32) + 256; + for (i = 0; i < 2; i++) { + if (instance->seg[i].va) + mtk_vcodec_mem_free(ctx, &instance->seg[i]); + instance->seg[i].size = size; + if (mtk_vcodec_mem_alloc(ctx, &instance->seg[i])) + goto err; + } + + if (!instance->tile.va) { + instance->tile.size = VP9_TILE_BUF_SIZE; + if (mtk_vcodec_mem_alloc(ctx, &instance->tile)) + goto err; + } + + if (!instance->prob.va) { + instance->prob.size = VP9_PROB_BUF_SIZE; + if (mtk_vcodec_mem_alloc(ctx, &instance->prob)) + goto err; + } + + if (!instance->counts.va) { + instance->counts.size = VP9_COUNTS_BUF_SIZE; + if (mtk_vcodec_mem_alloc(ctx, &instance->counts)) + goto err; + } + + instance->level = level; + return 0; + +err: + instance->level = VP9_RES_NONE; + return ret; +} + +static void vdec_vp9_slice_free_working_buffer(struct vdec_vp9_slice_instance *instance) +{ + struct mtk_vcodec_ctx *ctx = instance->ctx; + int i; + + for (i = 0; i < ARRAY_SIZE(instance->mv); i++) { + if (instance->mv[i].va) + mtk_vcodec_mem_free(ctx, &instance->mv[i]); + } + for (i = 0; i < ARRAY_SIZE(instance->seg); i++) { + if (instance->seg[i].va) + mtk_vcodec_mem_free(ctx, &instance->seg[i]); + } + if (instance->tile.va) + mtk_vcodec_mem_free(ctx, &instance->tile); + if (instance->prob.va) + mtk_vcodec_mem_free(ctx, &instance->prob); + if (instance->counts.va) + mtk_vcodec_mem_free(ctx, &instance->counts); + + instance->level = VP9_RES_NONE; +} + +static void vdec_vp9_slice_vsi_from_remote(struct vdec_vp9_slice_vsi *vsi, + struct vdec_vp9_slice_vsi *remote_vsi, + int skip) +{ + struct vdec_vp9_slice_frame *rf; + struct vdec_vp9_slice_frame *f; + + /* + * compressed header + * dequant + * buffer position + * decode state + */ + if (!skip) { + rf = &remote_vsi->frame; + f = &vsi->frame; + memcpy(&f->ch, &rf->ch, sizeof(f->ch)); + memcpy(&f->uh.dequant, &rf->uh.dequant, sizeof(f->uh.dequant)); + memcpy(&vsi->trans, &remote_vsi->trans, sizeof(vsi->trans)); + } + + memcpy(&vsi->state, &remote_vsi->state, sizeof(vsi->state)); +} + +static void vdec_vp9_slice_vsi_to_remote(struct vdec_vp9_slice_vsi *vsi, + struct vdec_vp9_slice_vsi *remote_vsi) +{ + memcpy(remote_vsi, vsi, sizeof(*vsi)); +} + +static int vdec_vp9_slice_tile_offset(int idx, int mi_num, int tile_log2) +{ + int sbs = (mi_num + 7) >> 3; + int offset = ((idx * sbs) >> tile_log2) << 3; + + return min(offset, mi_num); +} + +static +int vdec_vp9_slice_setup_single_from_src_to_dst(struct vdec_vp9_slice_instance *instance) +{ + struct vb2_v4l2_buffer *src; + struct vb2_v4l2_buffer *dst; + + src = v4l2_m2m_next_src_buf(instance->ctx->m2m_ctx); + if (!src) + return -EINVAL; + + dst = v4l2_m2m_next_dst_buf(instance->ctx->m2m_ctx); + if (!dst) + return -EINVAL; + + v4l2_m2m_buf_copy_metadata(src, dst, true); + + return 0; +} + +static int vdec_vp9_slice_setup_lat_from_src_buf(struct vdec_vp9_slice_instance *instance, + struct vdec_lat_buf *lat_buf) +{ + struct vb2_v4l2_buffer *src; + struct vb2_v4l2_buffer *dst; + + src = v4l2_m2m_next_src_buf(instance->ctx->m2m_ctx); + if (!src) + return -EINVAL; + + lat_buf->src_buf_req = src->vb2_buf.req_obj.req; + + dst = &lat_buf->ts_info; + v4l2_m2m_buf_copy_metadata(src, dst, true); + return 0; +} + +static void vdec_vp9_slice_setup_hdr(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_uncompressed_header *uh, + struct v4l2_ctrl_vp9_frame *hdr) +{ + int i; + + uh->profile = hdr->profile; + uh->last_frame_type = instance->frame_type; + uh->frame_type = !HDR_FLAG(KEY_FRAME); + uh->last_show_frame = instance->show_frame; + uh->show_frame = HDR_FLAG(SHOW_FRAME); + uh->error_resilient_mode = HDR_FLAG(ERROR_RESILIENT); + uh->bit_depth = hdr->bit_depth; + uh->last_frame_width = instance->width; + uh->last_frame_height = instance->height; + uh->frame_width = hdr->frame_width_minus_1 + 1; + uh->frame_height = hdr->frame_height_minus_1 + 1; + uh->intra_only = HDR_FLAG(INTRA_ONLY); + /* map v4l2 enum to values defined in VP9 spec for firmware */ + switch (hdr->reset_frame_context) { + case V4L2_VP9_RESET_FRAME_CTX_NONE: + uh->reset_frame_context = VP9_RESET_FRAME_CONTEXT_NONE0; + break; + case V4L2_VP9_RESET_FRAME_CTX_SPEC: + uh->reset_frame_context = VP9_RESET_FRAME_CONTEXT_SPEC; + break; + case V4L2_VP9_RESET_FRAME_CTX_ALL: + uh->reset_frame_context = VP9_RESET_FRAME_CONTEXT_ALL; + break; + default: + uh->reset_frame_context = VP9_RESET_FRAME_CONTEXT_NONE0; + break; + } + /* + * ref_frame_sign_bias specifies the intended direction + * of the motion vector in time for each reference frame. + * - INTRA_FRAME = 0, + * - LAST_FRAME = 1, + * - GOLDEN_FRAME = 2, + * - ALTREF_FRAME = 3, + * ref_frame_sign_bias[INTRA_FRAME] is always 0 + * and VDA only passes another 3 directions + */ + uh->ref_frame_sign_bias[0] = 0; + for (i = 0; i < 3; i++) + uh->ref_frame_sign_bias[i + 1] = + !!(hdr->ref_frame_sign_bias & (1 << i)); + uh->allow_high_precision_mv = HDR_FLAG(ALLOW_HIGH_PREC_MV); + uh->interpolation_filter = hdr->interpolation_filter; + uh->refresh_frame_context = HDR_FLAG(REFRESH_FRAME_CTX); + uh->frame_parallel_decoding_mode = HDR_FLAG(PARALLEL_DEC_MODE); + uh->frame_context_idx = hdr->frame_context_idx; + + /* tile info */ + uh->tile_cols_log2 = hdr->tile_cols_log2; + uh->tile_rows_log2 = hdr->tile_rows_log2; + + uh->uncompressed_header_size = hdr->uncompressed_header_size; + uh->header_size_in_bytes = hdr->compressed_header_size; +} + +static void vdec_vp9_slice_setup_frame_ctx(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_uncompressed_header *uh, + struct v4l2_ctrl_vp9_frame *hdr) +{ + int error_resilient_mode; + int reset_frame_context; + int key_frame; + int intra_only; + int i; + + key_frame = HDR_FLAG(KEY_FRAME); + intra_only = HDR_FLAG(INTRA_ONLY); + error_resilient_mode = HDR_FLAG(ERROR_RESILIENT); + reset_frame_context = uh->reset_frame_context; + + /* + * according to "6.2 Uncompressed header syntax" in + * "VP9 Bitstream & Decoding Process Specification", + * reset @frame_context_idx when (FrameIsIntra || error_resilient_mode) + */ + if (key_frame || intra_only || error_resilient_mode) { + /* + * @reset_frame_context specifies + * whether the frame context should be + * reset to default values: + * 0 or 1 means do not reset any frame context + * 2 resets just the context specified in the frame header + * 3 resets all contexts + */ + if (key_frame || error_resilient_mode || + reset_frame_context == 3) { + /* use default table */ + for (i = 0; i < 4; i++) + instance->dirty[i] = 0; + } else if (reset_frame_context == 2) { + instance->dirty[uh->frame_context_idx] = 0; + } + uh->frame_context_idx = 0; + } +} + +static void vdec_vp9_slice_setup_loop_filter(struct vdec_vp9_slice_uncompressed_header *uh, + struct v4l2_vp9_loop_filter *lf) +{ + int i; + + uh->loop_filter_level = lf->level; + uh->loop_filter_sharpness = lf->sharpness; + uh->loop_filter_delta_enabled = LF_FLAG(DELTA_ENABLED); + for (i = 0; i < 4; i++) + uh->loop_filter_ref_deltas[i] = lf->ref_deltas[i]; + for (i = 0; i < 2; i++) + uh->loop_filter_mode_deltas[i] = lf->mode_deltas[i]; +} + +static void vdec_vp9_slice_setup_quantization(struct vdec_vp9_slice_uncompressed_header *uh, + struct v4l2_vp9_quantization *quant) +{ + uh->base_q_idx = quant->base_q_idx; + uh->delta_q_y_dc = quant->delta_q_y_dc; + uh->delta_q_uv_dc = quant->delta_q_uv_dc; + uh->delta_q_uv_ac = quant->delta_q_uv_ac; +} + +static void vdec_vp9_slice_setup_segmentation(struct vdec_vp9_slice_uncompressed_header *uh, + struct v4l2_vp9_segmentation *seg) +{ + int i; + int j; + + uh->segmentation_enabled = SEG_FLAG(ENABLED); + uh->segmentation_update_map = SEG_FLAG(UPDATE_MAP); + for (i = 0; i < 7; i++) + uh->segmentation_tree_probs[i] = seg->tree_probs[i]; + uh->segmentation_temporal_udpate = SEG_FLAG(TEMPORAL_UPDATE); + for (i = 0; i < 3; i++) + uh->segmentation_pred_prob[i] = seg->pred_probs[i]; + uh->segmentation_update_data = SEG_FLAG(UPDATE_DATA); + uh->segmentation_abs_or_delta_update = SEG_FLAG(ABS_OR_DELTA_UPDATE); + for (i = 0; i < 8; i++) { + uh->feature_enabled[i] = seg->feature_enabled[i]; + for (j = 0; j < 4; j++) + uh->feature_value[i][j] = seg->feature_data[i][j]; + } +} + +static int vdec_vp9_slice_setup_tile(struct vdec_vp9_slice_vsi *vsi, + struct v4l2_ctrl_vp9_frame *hdr) +{ + unsigned int rows_log2; + unsigned int cols_log2; + unsigned int rows; + unsigned int cols; + unsigned int mi_rows; + unsigned int mi_cols; + struct vdec_vp9_slice_tiles *tiles; + int offset; + int start; + int end; + int i; + + rows_log2 = hdr->tile_rows_log2; + cols_log2 = hdr->tile_cols_log2; + rows = 1 << rows_log2; + cols = 1 << cols_log2; + tiles = &vsi->frame.tiles; + tiles->actual_rows = 0; + + if (rows > 4 || cols > 64) + return -EINVAL; + + /* setup mi rows/cols information */ + mi_rows = (hdr->frame_height_minus_1 + 1 + 7) >> 3; + mi_cols = (hdr->frame_width_minus_1 + 1 + 7) >> 3; + + for (i = 0; i < rows; i++) { + start = vdec_vp9_slice_tile_offset(i, mi_rows, rows_log2); + end = vdec_vp9_slice_tile_offset(i + 1, mi_rows, rows_log2); + offset = end - start; + tiles->mi_rows[i] = (offset + 7) >> 3; + if (tiles->mi_rows[i]) + tiles->actual_rows++; + } + + for (i = 0; i < cols; i++) { + start = vdec_vp9_slice_tile_offset(i, mi_cols, cols_log2); + end = vdec_vp9_slice_tile_offset(i + 1, mi_cols, cols_log2); + offset = end - start; + tiles->mi_cols[i] = (offset + 7) >> 3; + } + + return 0; +} + +static void vdec_vp9_slice_setup_state(struct vdec_vp9_slice_vsi *vsi) +{ + memset(&vsi->state, 0, sizeof(vsi->state)); +} + +static void vdec_vp9_slice_setup_ref_idx(struct vdec_vp9_slice_pfc *pfc, + struct v4l2_ctrl_vp9_frame *hdr) +{ + pfc->ref_idx[0] = hdr->last_frame_ts; + pfc->ref_idx[1] = hdr->golden_frame_ts; + pfc->ref_idx[2] = hdr->alt_frame_ts; +} + +static int vdec_vp9_slice_setup_pfc(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_pfc *pfc) +{ + struct v4l2_ctrl_vp9_frame *hdr; + struct vdec_vp9_slice_uncompressed_header *uh; + struct v4l2_ctrl *hdr_ctrl; + struct vdec_vp9_slice_vsi *vsi; + int ret; + + /* frame header */ + hdr_ctrl = v4l2_ctrl_find(&instance->ctx->ctrl_hdl, V4L2_CID_STATELESS_VP9_FRAME); + if (!hdr_ctrl || !hdr_ctrl->p_cur.p) + return -EINVAL; + + hdr = hdr_ctrl->p_cur.p; + vsi = &pfc->vsi; + uh = &vsi->frame.uh; + + /* setup vsi information */ + vdec_vp9_slice_setup_hdr(instance, uh, hdr); + vdec_vp9_slice_setup_frame_ctx(instance, uh, hdr); + vdec_vp9_slice_setup_loop_filter(uh, &hdr->lf); + vdec_vp9_slice_setup_quantization(uh, &hdr->quant); + vdec_vp9_slice_setup_segmentation(uh, &hdr->seg); + ret = vdec_vp9_slice_setup_tile(vsi, hdr); + if (ret) + return ret; + vdec_vp9_slice_setup_state(vsi); + + /* core stage needs buffer index to get ref y/c ... */ + vdec_vp9_slice_setup_ref_idx(pfc, hdr); + + pfc->seq = instance->seq; + instance->seq++; + + return 0; +} + +static int vdec_vp9_slice_setup_lat_buffer(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_vsi *vsi, + struct mtk_vcodec_mem *bs, + struct vdec_lat_buf *lat_buf) +{ + int i; + + vsi->bs.buf.dma_addr = bs->dma_addr; + vsi->bs.buf.size = bs->size; + vsi->bs.frame.dma_addr = bs->dma_addr; + vsi->bs.frame.size = bs->size; + + for (i = 0; i < 2; i++) { + vsi->mv[i].dma_addr = instance->mv[i].dma_addr; + vsi->mv[i].size = instance->mv[i].size; + } + for (i = 0; i < 2; i++) { + vsi->seg[i].dma_addr = instance->seg[i].dma_addr; + vsi->seg[i].size = instance->seg[i].size; + } + vsi->tile.dma_addr = instance->tile.dma_addr; + vsi->tile.size = instance->tile.size; + vsi->prob.dma_addr = instance->prob.dma_addr; + vsi->prob.size = instance->prob.size; + vsi->counts.dma_addr = instance->counts.dma_addr; + vsi->counts.size = instance->counts.size; + + vsi->ube.dma_addr = lat_buf->ctx->msg_queue.wdma_addr.dma_addr; + vsi->ube.size = lat_buf->ctx->msg_queue.wdma_addr.size; + vsi->trans.dma_addr = lat_buf->ctx->msg_queue.wdma_wptr_addr; + /* used to store trans end */ + vsi->trans.dma_addr_end = lat_buf->ctx->msg_queue.wdma_rptr_addr; + vsi->err_map.dma_addr = lat_buf->wdma_err_addr.dma_addr; + vsi->err_map.size = lat_buf->wdma_err_addr.size; + + vsi->row_info.buf = 0; + vsi->row_info.size = 0; + + return 0; +} + +static int vdec_vp9_slice_setup_prob_buffer(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_vsi *vsi) +{ + struct vdec_vp9_slice_frame_ctx *frame_ctx; + struct vdec_vp9_slice_uncompressed_header *uh; + + uh = &vsi->frame.uh; + + mtk_vcodec_debug(instance, "ctx dirty %u idx %d\n", + instance->dirty[uh->frame_context_idx], + uh->frame_context_idx); + + if (instance->dirty[uh->frame_context_idx]) + frame_ctx = &instance->frame_ctx[uh->frame_context_idx]; + else + frame_ctx = vdec_vp9_slice_default_frame_ctx; + memcpy(instance->prob.va, frame_ctx, sizeof(*frame_ctx)); + + return 0; +} + +static void vdec_vp9_slice_setup_seg_buffer(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_vsi *vsi, + struct mtk_vcodec_mem *buf) +{ + struct vdec_vp9_slice_uncompressed_header *uh; + + /* reset segment buffer */ + uh = &vsi->frame.uh; + if (uh->frame_type == 0 || + uh->intra_only || + uh->error_resilient_mode || + uh->frame_width != instance->width || + uh->frame_height != instance->height) { + mtk_vcodec_debug(instance, "reset seg\n"); + memset(buf->va, 0, buf->size); + } +} + +/* + * parse tiles according to `6.4 Decode tiles syntax` + * in "vp9-bitstream-specification" + * + * frame contains uncompress header, compressed header and several tiles. + * this function parses tiles' position and size, stores them to tile buffer + * for decoding. + */ +static int vdec_vp9_slice_setup_tile_buffer(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_vsi *vsi, + struct mtk_vcodec_mem *bs) +{ + struct vdec_vp9_slice_uncompressed_header *uh; + unsigned int rows_log2; + unsigned int cols_log2; + unsigned int rows; + unsigned int cols; + unsigned int mi_row; + unsigned int mi_col; + unsigned int offset; + unsigned int pa; + unsigned int size; + struct vdec_vp9_slice_tiles *tiles; + unsigned char *pos; + unsigned char *end; + unsigned char *va; + unsigned int *tb; + int i; + int j; + + uh = &vsi->frame.uh; + rows_log2 = uh->tile_rows_log2; + cols_log2 = uh->tile_cols_log2; + rows = 1 << rows_log2; + cols = 1 << cols_log2; + + if (rows > 4 || cols > 64) { + mtk_vcodec_err(instance, "tile_rows %u tile_cols %u\n", + rows, cols); + return -EINVAL; + } + + offset = uh->uncompressed_header_size + + uh->header_size_in_bytes; + if (bs->size <= offset) { + mtk_vcodec_err(instance, "bs size %zu tile offset %u\n", + bs->size, offset); + return -EINVAL; + } + + tiles = &vsi->frame.tiles; + /* setup tile buffer */ + + va = (unsigned char *)bs->va; + pos = va + offset; + end = va + bs->size; + /* truncated */ + pa = (unsigned int)bs->dma_addr + offset; + tb = instance->tile.va; + for (i = 0; i < rows; i++) { + for (j = 0; j < cols; j++) { + if (i == rows - 1 && + j == cols - 1) { + size = (unsigned int)(end - pos); + } else { + if (end - pos < 4) + return -EINVAL; + + size = (pos[0] << 24) | (pos[1] << 16) | + (pos[2] << 8) | pos[3]; + pos += 4; + pa += 4; + offset += 4; + if (end - pos < size) + return -EINVAL; + } + tiles->size[i][j] = size; + if (tiles->mi_rows[i]) { + *tb++ = (size << 3) + ((offset << 3) & 0x7f); + *tb++ = pa & ~0xf; + *tb++ = (pa << 3) & 0x7f; + mi_row = (tiles->mi_rows[i] - 1) & 0x1ff; + mi_col = (tiles->mi_cols[j] - 1) & 0x3f; + *tb++ = (mi_row << 6) + mi_col; + } + pos += size; + pa += size; + offset += size; + } + } + + return 0; +} + +static int vdec_vp9_slice_setup_lat(struct vdec_vp9_slice_instance *instance, + struct mtk_vcodec_mem *bs, + struct vdec_lat_buf *lat_buf, + struct vdec_vp9_slice_pfc *pfc) +{ + struct vdec_vp9_slice_vsi *vsi = &pfc->vsi; + int ret; + + ret = vdec_vp9_slice_setup_lat_from_src_buf(instance, lat_buf); + if (ret) + goto err; + + ret = vdec_vp9_slice_setup_pfc(instance, pfc); + if (ret) + goto err; + + ret = vdec_vp9_slice_alloc_working_buffer(instance, vsi); + if (ret) + goto err; + + ret = vdec_vp9_slice_setup_lat_buffer(instance, vsi, bs, lat_buf); + if (ret) + goto err; + + vdec_vp9_slice_setup_seg_buffer(instance, vsi, &instance->seg[0]); + + /* setup prob/tile buffers for LAT */ + + ret = vdec_vp9_slice_setup_prob_buffer(instance, vsi); + if (ret) + goto err; + + ret = vdec_vp9_slice_setup_tile_buffer(instance, vsi, bs); + if (ret) + goto err; + + return 0; + +err: + return ret; +} + +static +void vdec_vp9_slice_map_counts_eob_coef(unsigned int i, unsigned int j, unsigned int k, + struct vdec_vp9_slice_frame_counts *counts, + struct v4l2_vp9_frame_symbol_counts *counts_helper) +{ + u32 l = 0, m; + + /* + * helper eo -> mtk eo + * helpre e1 -> mtk c3 + * helper c0 -> c0 + * helper c1 -> c1 + * helper c2 -> c2 + */ + for (m = 0; m < 3; m++) { + counts_helper->coeff[i][j][k][l][m] = + (u32 (*)[3]) & counts->coef_probs[i][j][k].band_0[m]; + counts_helper->eob[i][j][k][l][m][0] = + &counts->eob_branch[i][j][k].band_0[m]; + counts_helper->eob[i][j][k][l][m][1] = + &counts->coef_probs[i][j][k].band_0[m][3]; + } + + for (l = 1; l < 6; l++) { + for (m = 0; m < 6; m++) { + counts_helper->coeff[i][j][k][l][m] = + (u32 (*)[3]) & counts->coef_probs[i][j][k].band_1_5[l - 1][m]; + counts_helper->eob[i][j][k][l][m][0] = + &counts->eob_branch[i][j][k].band_1_5[l - 1][m]; + counts_helper->eob[i][j][k][l][m][1] = + &counts->coef_probs[i][j][k].band_1_5[l - 1][m][3]; + } + } +} + +static void vdec_vp9_slice_counts_map_helper(struct vdec_vp9_slice_counts_map *counts_map, + struct vdec_vp9_slice_frame_counts *counts, + struct v4l2_vp9_frame_symbol_counts *counts_helper) +{ + int i, j, k; + + counts_helper->partition = &counts->partition; + counts_helper->intra_inter = &counts->intra_inter; + counts_helper->tx32p = &counts->tx_p32x32; + counts_helper->tx16p = &counts->tx_p16x16; + counts_helper->tx8p = &counts->tx_p8x8; + counts_helper->uv_mode = &counts->uv_mode; + + counts_helper->comp = &counts->comp_inter; + counts_helper->comp_ref = &counts->comp_ref; + counts_helper->single_ref = &counts->single_ref; + counts_helper->mv_mode = &counts->inter_mode; + counts_helper->mv_joint = &counts->joint; + + for (i = 0; i < ARRAY_SIZE(counts_map->skip); i++) + memcpy(counts_map->skip[i], counts->skip[i], + sizeof(counts_map->skip[0])); + counts_helper->skip = &counts_map->skip; + + for (i = 0; i < ARRAY_SIZE(counts_map->y_mode); i++) + memcpy(counts_map->y_mode[i], counts->y_mode[i], + sizeof(counts_map->y_mode[0])); + counts_helper->y_mode = &counts_map->y_mode; + + for (i = 0; i < ARRAY_SIZE(counts_map->filter); i++) + memcpy(counts_map->filter[i], counts->switchable_interp[i], + sizeof(counts_map->filter[0])); + counts_helper->filter = &counts_map->filter; + + for (i = 0; i < ARRAY_SIZE(counts_map->sign); i++) + memcpy(counts_map->sign[i], counts->mvcomp[i].sign, + sizeof(counts_map->sign[0])); + counts_helper->sign = &counts_map->sign; + + for (i = 0; i < ARRAY_SIZE(counts_map->classes); i++) + memcpy(counts_map->classes[i], counts->mvcomp[i].classes, + sizeof(counts_map->classes[0])); + counts_helper->classes = &counts_map->classes; + + for (i = 0; i < ARRAY_SIZE(counts_map->class0); i++) + memcpy(counts_map->class0[i], counts->mvcomp[i].class0, + sizeof(counts_map->class0[0])); + counts_helper->class0 = &counts_map->class0; + + for (i = 0; i < ARRAY_SIZE(counts_map->bits); i++) + for (j = 0; j < ARRAY_SIZE(counts_map->bits[0]); j++) + memcpy(counts_map->bits[i][j], counts->mvcomp[i].bits[j], + sizeof(counts_map->bits[0][0])); + counts_helper->bits = &counts_map->bits; + + for (i = 0; i < ARRAY_SIZE(counts_map->class0_fp); i++) + for (j = 0; j < ARRAY_SIZE(counts_map->class0_fp[0]); j++) + memcpy(counts_map->class0_fp[i][j], counts->mvcomp[i].class0_fp[j], + sizeof(counts_map->class0_fp[0][0])); + counts_helper->class0_fp = &counts_map->class0_fp; + + for (i = 0; i < ARRAY_SIZE(counts_map->fp); i++) + memcpy(counts_map->fp[i], counts->mvcomp[i].fp, + sizeof(counts_map->fp[0])); + counts_helper->fp = &counts_map->fp; + + for (i = 0; i < ARRAY_SIZE(counts_map->class0_hp); i++) + memcpy(counts_map->class0_hp[i], counts->mvcomp[i].class0_hp, + sizeof(counts_map->class0_hp[0])); + counts_helper->class0_hp = &counts_map->class0_hp; + + for (i = 0; i < ARRAY_SIZE(counts_map->hp); i++) + memcpy(counts_map->hp[i], counts->mvcomp[i].hp, sizeof(counts_map->hp[0])); + + counts_helper->hp = &counts_map->hp; + + for (i = 0; i < 4; i++) + for (j = 0; j < 2; j++) + for (k = 0; k < 2; k++) + vdec_vp9_slice_map_counts_eob_coef(i, j, k, counts, counts_helper); +} + +static void vdec_vp9_slice_map_to_coef(unsigned int i, unsigned int j, unsigned int k, + struct vdec_vp9_slice_frame_ctx *frame_ctx, + struct v4l2_vp9_frame_context *frame_ctx_helper) +{ + u32 l, m; + + for (l = 0; l < ARRAY_SIZE(frame_ctx_helper->coef[0][0][0]); l++) { + for (m = 0; m < VP9_BAND_6(l); m++) { + memcpy(frame_ctx_helper->coef[i][j][k][l][m], + frame_ctx->coef_probs[i][j][k][l].probs[m], + sizeof(frame_ctx_helper->coef[i][j][k][l][0])); + } + } +} + +static void vdec_vp9_slice_map_from_coef(unsigned int i, unsigned int j, unsigned int k, + struct vdec_vp9_slice_frame_ctx *frame_ctx, + struct v4l2_vp9_frame_context *frame_ctx_helper) +{ + u32 l, m; + + for (l = 0; l < ARRAY_SIZE(frame_ctx_helper->coef[0][0][0]); l++) { + for (m = 0; m < VP9_BAND_6(l); m++) { + memcpy(frame_ctx->coef_probs[i][j][k][l].probs[m], + frame_ctx_helper->coef[i][j][k][l][m], + sizeof(frame_ctx_helper->coef[i][j][k][l][0])); + } + } +} + +static +void vdec_vp9_slice_framectx_map_helper(bool frame_is_intra, + struct vdec_vp9_slice_frame_ctx *pre_frame_ctx, + struct vdec_vp9_slice_frame_ctx *frame_ctx, + struct v4l2_vp9_frame_context *frame_ctx_helper) +{ + struct v4l2_vp9_frame_mv_context *mv = &frame_ctx_helper->mv; + u32 i, j, k; + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->coef); i++) + for (j = 0; j < ARRAY_SIZE(frame_ctx_helper->coef[0]); j++) + for (k = 0; k < ARRAY_SIZE(frame_ctx_helper->coef[0][0]); k++) + vdec_vp9_slice_map_to_coef(i, j, k, pre_frame_ctx, + frame_ctx_helper); + + /* + * use previous prob when frame is not intra or + * we should use the prob updated by the compressed header parse + */ + if (!frame_is_intra) + frame_ctx = pre_frame_ctx; + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->tx8); i++) + memcpy(frame_ctx_helper->tx8[i], frame_ctx->tx_p8x8[i], + sizeof(frame_ctx_helper->tx8[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->tx16); i++) + memcpy(frame_ctx_helper->tx16[i], frame_ctx->tx_p16x16[i], + sizeof(frame_ctx_helper->tx16[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->tx32); i++) + memcpy(frame_ctx_helper->tx32[i], frame_ctx->tx_p32x32[i], + sizeof(frame_ctx_helper->tx32[0])); + + memcpy(frame_ctx_helper->skip, frame_ctx->skip_probs, sizeof(frame_ctx_helper->skip)); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->inter_mode); i++) + memcpy(frame_ctx_helper->inter_mode[i], frame_ctx->inter_mode_probs[i], + sizeof(frame_ctx_helper->inter_mode[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->interp_filter); i++) + memcpy(frame_ctx_helper->interp_filter[i], frame_ctx->switch_interp_prob[i], + sizeof(frame_ctx_helper->interp_filter[0])); + + memcpy(frame_ctx_helper->is_inter, frame_ctx->intra_inter_prob, + sizeof(frame_ctx_helper->is_inter)); + + memcpy(frame_ctx_helper->comp_mode, frame_ctx->comp_inter_prob, + sizeof(frame_ctx_helper->comp_mode)); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->single_ref); i++) + memcpy(frame_ctx_helper->single_ref[i], frame_ctx->single_ref_prob[i], + sizeof(frame_ctx_helper->single_ref[0])); + + memcpy(frame_ctx_helper->comp_ref, frame_ctx->comp_ref_prob, + sizeof(frame_ctx_helper->comp_ref)); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->y_mode); i++) + memcpy(frame_ctx_helper->y_mode[i], frame_ctx->y_mode_prob[i], + sizeof(frame_ctx_helper->y_mode[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->uv_mode); i++) + memcpy(frame_ctx_helper->uv_mode[i], frame_ctx->uv_mode_prob[i], + sizeof(frame_ctx_helper->uv_mode[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->partition); i++) + memcpy(frame_ctx_helper->partition[i], frame_ctx->partition_prob[i], + sizeof(frame_ctx_helper->partition[0])); + + memcpy(mv->joint, frame_ctx->joint, sizeof(mv->joint)); + + for (i = 0; i < ARRAY_SIZE(mv->sign); i++) + mv->sign[i] = frame_ctx->sign_classes[i].sign; + + for (i = 0; i < ARRAY_SIZE(mv->classes); i++) + memcpy(mv->classes[i], frame_ctx->sign_classes[i].classes, + sizeof(mv->classes[i])); + + for (i = 0; i < ARRAY_SIZE(mv->class0_bit); i++) + mv->class0_bit[i] = frame_ctx->class0_bits[i].class0[0]; + + for (i = 0; i < ARRAY_SIZE(mv->bits); i++) + memcpy(mv->bits[i], frame_ctx->class0_bits[i].bits, sizeof(mv->bits[0])); + + for (i = 0; i < ARRAY_SIZE(mv->class0_fr); i++) + for (j = 0; j < ARRAY_SIZE(mv->class0_fr[0]); j++) + memcpy(mv->class0_fr[i][j], frame_ctx->class0_fp_hp[i].class0_fp[j], + sizeof(mv->class0_fr[0][0])); + + for (i = 0; i < ARRAY_SIZE(mv->fr); i++) + memcpy(mv->fr[i], frame_ctx->class0_fp_hp[i].fp, sizeof(mv->fr[0])); + + for (i = 0; i < ARRAY_SIZE(mv->class0_hp); i++) + mv->class0_hp[i] = frame_ctx->class0_fp_hp[i].class0_hp; + + for (i = 0; i < ARRAY_SIZE(mv->hp); i++) + mv->hp[i] = frame_ctx->class0_fp_hp[i].hp; +} + +static void vdec_vp9_slice_helper_map_framectx(struct v4l2_vp9_frame_context *frame_ctx_helper, + struct vdec_vp9_slice_frame_ctx *frame_ctx) +{ + struct v4l2_vp9_frame_mv_context *mv = &frame_ctx_helper->mv; + u32 i, j, k; + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->tx8); i++) + memcpy(frame_ctx->tx_p8x8[i], frame_ctx_helper->tx8[i], + sizeof(frame_ctx_helper->tx8[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->tx16); i++) + memcpy(frame_ctx->tx_p16x16[i], frame_ctx_helper->tx16[i], + sizeof(frame_ctx_helper->tx16[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->tx32); i++) + memcpy(frame_ctx->tx_p32x32[i], frame_ctx_helper->tx32[i], + sizeof(frame_ctx_helper->tx32[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->coef); i++) + for (j = 0; j < ARRAY_SIZE(frame_ctx_helper->coef[0]); j++) + for (k = 0; k < ARRAY_SIZE(frame_ctx_helper->coef[0][0]); k++) + vdec_vp9_slice_map_from_coef(i, j, k, frame_ctx, + frame_ctx_helper); + + memcpy(frame_ctx->skip_probs, frame_ctx_helper->skip, sizeof(frame_ctx_helper->skip)); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->inter_mode); i++) + memcpy(frame_ctx->inter_mode_probs[i], frame_ctx_helper->inter_mode[i], + sizeof(frame_ctx_helper->inter_mode[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->interp_filter); i++) + memcpy(frame_ctx->switch_interp_prob[i], frame_ctx_helper->interp_filter[i], + sizeof(frame_ctx_helper->interp_filter[0])); + + memcpy(frame_ctx->intra_inter_prob, frame_ctx_helper->is_inter, + sizeof(frame_ctx_helper->is_inter)); + + memcpy(frame_ctx->comp_inter_prob, frame_ctx_helper->comp_mode, + sizeof(frame_ctx_helper->comp_mode)); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->single_ref); i++) + memcpy(frame_ctx->single_ref_prob[i], frame_ctx_helper->single_ref[i], + sizeof(frame_ctx_helper->single_ref[0])); + + memcpy(frame_ctx->comp_ref_prob, frame_ctx_helper->comp_ref, + sizeof(frame_ctx_helper->comp_ref)); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->y_mode); i++) + memcpy(frame_ctx->y_mode_prob[i], frame_ctx_helper->y_mode[i], + sizeof(frame_ctx_helper->y_mode[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->uv_mode); i++) + memcpy(frame_ctx->uv_mode_prob[i], frame_ctx_helper->uv_mode[i], + sizeof(frame_ctx_helper->uv_mode[0])); + + for (i = 0; i < ARRAY_SIZE(frame_ctx_helper->partition); i++) + memcpy(frame_ctx->partition_prob[i], frame_ctx_helper->partition[i], + sizeof(frame_ctx_helper->partition[0])); + + memcpy(frame_ctx->joint, mv->joint, sizeof(mv->joint)); + + for (i = 0; i < ARRAY_SIZE(mv->sign); i++) + frame_ctx->sign_classes[i].sign = mv->sign[i]; + + for (i = 0; i < ARRAY_SIZE(mv->classes); i++) + memcpy(frame_ctx->sign_classes[i].classes, mv->classes[i], + sizeof(mv->classes[i])); + + for (i = 0; i < ARRAY_SIZE(mv->class0_bit); i++) + frame_ctx->class0_bits[i].class0[0] = mv->class0_bit[i]; + + for (i = 0; i < ARRAY_SIZE(mv->bits); i++) + memcpy(frame_ctx->class0_bits[i].bits, mv->bits[i], sizeof(mv->bits[0])); + + for (i = 0; i < ARRAY_SIZE(mv->class0_fr); i++) + for (j = 0; j < ARRAY_SIZE(mv->class0_fr[0]); j++) + memcpy(frame_ctx->class0_fp_hp[i].class0_fp[j], mv->class0_fr[i][j], + sizeof(mv->class0_fr[0][0])); + + for (i = 0; i < ARRAY_SIZE(mv->fr); i++) + memcpy(frame_ctx->class0_fp_hp[i].fp, mv->fr[i], sizeof(mv->fr[0])); + + for (i = 0; i < ARRAY_SIZE(mv->class0_hp); i++) + frame_ctx->class0_fp_hp[i].class0_hp = mv->class0_hp[i]; + + for (i = 0; i < ARRAY_SIZE(mv->hp); i++) + frame_ctx->class0_fp_hp[i].hp = mv->hp[i]; +} + +static int vdec_vp9_slice_update_prob(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_vsi *vsi) +{ + struct vdec_vp9_slice_frame_ctx *pre_frame_ctx; + struct v4l2_vp9_frame_context *pre_frame_ctx_helper; + struct vdec_vp9_slice_frame_ctx *frame_ctx; + struct vdec_vp9_slice_frame_counts *counts; + struct v4l2_vp9_frame_symbol_counts *counts_helper; + struct vdec_vp9_slice_uncompressed_header *uh; + bool frame_is_intra; + bool use_128; + + uh = &vsi->frame.uh; + pre_frame_ctx = &instance->frame_ctx[uh->frame_context_idx]; + pre_frame_ctx_helper = &instance->frame_ctx_helper; + frame_ctx = (struct vdec_vp9_slice_frame_ctx *)instance->prob.va; + counts = (struct vdec_vp9_slice_frame_counts *)instance->counts.va; + counts_helper = &instance->counts_helper; + + if (!uh->refresh_frame_context) + return 0; + + if (!uh->frame_parallel_decoding_mode) { + vdec_vp9_slice_counts_map_helper(&instance->counts_map, counts, counts_helper); + + frame_is_intra = !vsi->frame.uh.frame_type || vsi->frame.uh.intra_only; + /* check default prob */ + if (!instance->dirty[uh->frame_context_idx]) + vdec_vp9_slice_framectx_map_helper(frame_is_intra, + vdec_vp9_slice_default_frame_ctx, + frame_ctx, + pre_frame_ctx_helper); + else + vdec_vp9_slice_framectx_map_helper(frame_is_intra, + pre_frame_ctx, + frame_ctx, + pre_frame_ctx_helper); + + use_128 = !frame_is_intra && !vsi->frame.uh.last_frame_type; + v4l2_vp9_adapt_coef_probs(pre_frame_ctx_helper, + counts_helper, + use_128, + frame_is_intra); + if (!frame_is_intra) + v4l2_vp9_adapt_noncoef_probs(pre_frame_ctx_helper, + counts_helper, + V4L2_VP9_REFERENCE_MODE_SINGLE_REFERENCE, + vsi->frame.uh.interpolation_filter, + vsi->frame.ch.tx_mode, + vsi->frame.uh.allow_high_precision_mv ? + V4L2_VP9_FRAME_FLAG_ALLOW_HIGH_PREC_MV : 0); + vdec_vp9_slice_helper_map_framectx(pre_frame_ctx_helper, pre_frame_ctx); + } else { + memcpy(pre_frame_ctx, frame_ctx, sizeof(*frame_ctx)); + } + + instance->dirty[uh->frame_context_idx] = 1; + + return 0; +} + +static int vdec_vp9_slice_update_single(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_pfc *pfc) +{ + struct vdec_vp9_slice_vsi *vsi; + + vsi = &pfc->vsi; + memcpy(&pfc->state[0], &vsi->state, sizeof(vsi->state)); + + mtk_vcodec_debug(instance, "Frame %u Y_CRC %08x %08x %08x %08x\n", + pfc->seq, + vsi->state.crc[0], vsi->state.crc[1], + vsi->state.crc[2], vsi->state.crc[3]); + mtk_vcodec_debug(instance, "Frame %u C_CRC %08x %08x %08x %08x\n", + pfc->seq, + vsi->state.crc[4], vsi->state.crc[5], + vsi->state.crc[6], vsi->state.crc[7]); + + vdec_vp9_slice_update_prob(instance, vsi); + + instance->width = vsi->frame.uh.frame_width; + instance->height = vsi->frame.uh.frame_height; + instance->frame_type = vsi->frame.uh.frame_type; + instance->show_frame = vsi->frame.uh.show_frame; + + return 0; +} + +static int vdec_vp9_slice_update_lat(struct vdec_vp9_slice_instance *instance, + struct vdec_lat_buf *lat_buf, + struct vdec_vp9_slice_pfc *pfc) +{ + struct vdec_vp9_slice_vsi *vsi; + + vsi = &pfc->vsi; + memcpy(&pfc->state[0], &vsi->state, sizeof(vsi->state)); + + mtk_vcodec_debug(instance, "Frame %u LAT CRC 0x%08x %lx %lx\n", + pfc->seq, vsi->state.crc[0], + (unsigned long)vsi->trans.dma_addr, + (unsigned long)vsi->trans.dma_addr_end); + + /* buffer full, need to re-decode */ + if (vsi->state.full) { + /* buffer not enough */ + if (vsi->trans.dma_addr_end - vsi->trans.dma_addr == + vsi->ube.size) + return -ENOMEM; + return -EAGAIN; + } + + vdec_vp9_slice_update_prob(instance, vsi); + + instance->width = vsi->frame.uh.frame_width; + instance->height = vsi->frame.uh.frame_height; + instance->frame_type = vsi->frame.uh.frame_type; + instance->show_frame = vsi->frame.uh.show_frame; + + return 0; +} + +static int vdec_vp9_slice_setup_core_to_dst_buf(struct vdec_vp9_slice_instance *instance, + struct vdec_lat_buf *lat_buf) +{ + struct vb2_v4l2_buffer *dst; + + dst = v4l2_m2m_next_dst_buf(instance->ctx->m2m_ctx); + if (!dst) + return -EINVAL; + + v4l2_m2m_buf_copy_metadata(&lat_buf->ts_info, dst, true); + return 0; +} + +static int vdec_vp9_slice_setup_core_buffer(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_pfc *pfc, + struct vdec_vp9_slice_vsi *vsi, + struct vdec_fb *fb, + struct vdec_lat_buf *lat_buf) +{ + struct vb2_buffer *vb; + struct vb2_queue *vq; + struct vdec_vp9_slice_reference *ref; + int plane; + int size; + int w; + int h; + int i; + + plane = instance->ctx->q_data[MTK_Q_DATA_DST].fmt->num_planes; + w = vsi->frame.uh.frame_width; + h = vsi->frame.uh.frame_height; + size = ALIGN(w, 64) * ALIGN(h, 64); + + /* frame buffer */ + vsi->fb.y.dma_addr = fb->base_y.dma_addr; + if (plane == 1) + vsi->fb.c.dma_addr = fb->base_y.dma_addr + size; + else + vsi->fb.c.dma_addr = fb->base_c.dma_addr; + + /* reference buffers */ + vq = v4l2_m2m_get_vq(instance->ctx->m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (!vq) + return -EINVAL; + + /* get current output buffer */ + vb = &v4l2_m2m_next_dst_buf(instance->ctx->m2m_ctx)->vb2_buf; + if (!vb) + return -EINVAL; + + /* update internal buffer's width/height */ + for (i = 0; i < vq->num_buffers; i++) { + if (vb == vq->bufs[i]) { + instance->dpb[i].width = w; + instance->dpb[i].height = h; + break; + } + } + + /* + * get buffer's width/height from instance + * get buffer address from vb2buf + */ + for (i = 0; i < 3; i++) { + ref = &vsi->frame.ref[i]; + vb = vb2_find_buffer(vq, pfc->ref_idx[i]); + if (!vb) { + ref->frame_width = w; + ref->frame_height = h; + memset(&vsi->ref[i], 0, sizeof(vsi->ref[i])); + } else { + int idx = vb->index; + + ref->frame_width = instance->dpb[idx].width; + ref->frame_height = instance->dpb[idx].height; + vsi->ref[i].y.dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 0); + if (plane == 1) + vsi->ref[i].c.dma_addr = + vsi->ref[i].y.dma_addr + size; + else + vsi->ref[i].c.dma_addr = + vb2_dma_contig_plane_dma_addr(vb, 1); + } + } + + return 0; +} + +static void vdec_vp9_slice_setup_single_buffer(struct vdec_vp9_slice_instance *instance, + struct vdec_vp9_slice_pfc *pfc, + struct vdec_vp9_slice_vsi *vsi, + struct mtk_vcodec_mem *bs, + struct vdec_fb *fb) +{ + int i; + + vsi->bs.buf.dma_addr = bs->dma_addr; + vsi->bs.buf.size = bs->size; + vsi->bs.frame.dma_addr = bs->dma_addr; + vsi->bs.frame.size = bs->size; + + for (i = 0; i < 2; i++) { + vsi->mv[i].dma_addr = instance->mv[i].dma_addr; + vsi->mv[i].size = instance->mv[i].size; + } + for (i = 0; i < 2; i++) { + vsi->seg[i].dma_addr = instance->seg[i].dma_addr; + vsi->seg[i].size = instance->seg[i].size; + } + vsi->tile.dma_addr = instance->tile.dma_addr; + vsi->tile.size = instance->tile.size; + vsi->prob.dma_addr = instance->prob.dma_addr; + vsi->prob.size = instance->prob.size; + vsi->counts.dma_addr = instance->counts.dma_addr; + vsi->counts.size = instance->counts.size; + + vsi->row_info.buf = 0; + vsi->row_info.size = 0; + + vdec_vp9_slice_setup_core_buffer(instance, pfc, vsi, fb, NULL); +} + +static int vdec_vp9_slice_setup_core(struct vdec_vp9_slice_instance *instance, + struct vdec_fb *fb, + struct vdec_lat_buf *lat_buf, + struct vdec_vp9_slice_pfc *pfc) +{ + struct vdec_vp9_slice_vsi *vsi = &pfc->vsi; + int ret; + + vdec_vp9_slice_setup_state(vsi); + + ret = vdec_vp9_slice_setup_core_to_dst_buf(instance, lat_buf); + if (ret) + goto err; + + ret = vdec_vp9_slice_setup_core_buffer(instance, pfc, vsi, fb, lat_buf); + if (ret) + goto err; + + vdec_vp9_slice_setup_seg_buffer(instance, vsi, &instance->seg[1]); + + return 0; + +err: + return ret; +} + +static int vdec_vp9_slice_setup_single(struct vdec_vp9_slice_instance *instance, + struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, + struct vdec_vp9_slice_pfc *pfc) +{ + struct vdec_vp9_slice_vsi *vsi = &pfc->vsi; + int ret; + + ret = vdec_vp9_slice_setup_single_from_src_to_dst(instance); + if (ret) + goto err; + + ret = vdec_vp9_slice_setup_pfc(instance, pfc); + if (ret) + goto err; + + ret = vdec_vp9_slice_alloc_working_buffer(instance, vsi); + if (ret) + goto err; + + vdec_vp9_slice_setup_single_buffer(instance, pfc, vsi, bs, fb); + vdec_vp9_slice_setup_seg_buffer(instance, vsi, &instance->seg[0]); + + ret = vdec_vp9_slice_setup_prob_buffer(instance, vsi); + if (ret) + goto err; + + ret = vdec_vp9_slice_setup_tile_buffer(instance, vsi, bs); + if (ret) + goto err; + + return 0; + +err: + return ret; +} + +static int vdec_vp9_slice_update_core(struct vdec_vp9_slice_instance *instance, + struct vdec_lat_buf *lat_buf, + struct vdec_vp9_slice_pfc *pfc) +{ + struct vdec_vp9_slice_vsi *vsi; + + vsi = &pfc->vsi; + memcpy(&pfc->state[1], &vsi->state, sizeof(vsi->state)); + + mtk_vcodec_debug(instance, "Frame %u Y_CRC %08x %08x %08x %08x\n", + pfc->seq, + vsi->state.crc[0], vsi->state.crc[1], + vsi->state.crc[2], vsi->state.crc[3]); + mtk_vcodec_debug(instance, "Frame %u C_CRC %08x %08x %08x %08x\n", + pfc->seq, + vsi->state.crc[4], vsi->state.crc[5], + vsi->state.crc[6], vsi->state.crc[7]); + + return 0; +} + +static int vdec_vp9_slice_init(struct mtk_vcodec_ctx *ctx) +{ + struct vdec_vp9_slice_instance *instance; + struct vdec_vp9_slice_init_vsi *vsi; + int ret; + + instance = kzalloc(sizeof(*instance), GFP_KERNEL); + if (!instance) + return -ENOMEM; + + instance->ctx = ctx; + instance->vpu.id = SCP_IPI_VDEC_LAT; + instance->vpu.core_id = SCP_IPI_VDEC_CORE; + instance->vpu.ctx = ctx; + instance->vpu.codec_type = ctx->current_codec; + + ret = vpu_dec_init(&instance->vpu); + if (ret) { + mtk_vcodec_err(instance, "failed to init vpu dec, ret %d\n", ret); + goto error_vpu_init; + } + + /* init vsi and global flags */ + + vsi = instance->vpu.vsi; + if (!vsi) { + mtk_vcodec_err(instance, "failed to get VP9 vsi\n"); + ret = -EINVAL; + goto error_vsi; + } + instance->init_vsi = vsi; + instance->core_vsi = mtk_vcodec_fw_map_dm_addr(ctx->dev->fw_handler, + (u32)vsi->core_vsi); + if (!instance->core_vsi) { + mtk_vcodec_err(instance, "failed to get VP9 core vsi\n"); + ret = -EINVAL; + goto error_vsi; + } + + instance->irq = 1; + + ret = vdec_vp9_slice_init_default_frame_ctx(instance); + if (ret) + goto error_default_frame_ctx; + + ctx->drv_handle = instance; + + return 0; + +error_default_frame_ctx: +error_vsi: + vpu_dec_deinit(&instance->vpu); +error_vpu_init: + kfree(instance); + return ret; +} + +static void vdec_vp9_slice_deinit(void *h_vdec) +{ + struct vdec_vp9_slice_instance *instance = h_vdec; + + if (!instance) + return; + + vpu_dec_deinit(&instance->vpu); + vdec_vp9_slice_free_working_buffer(instance); + vdec_msg_queue_deinit(&instance->ctx->msg_queue, instance->ctx); + kfree(instance); +} + +static int vdec_vp9_slice_flush(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + struct vdec_vp9_slice_instance *instance = h_vdec; + + mtk_vcodec_debug(instance, "flush ...\n"); + if (instance->ctx->dev->vdec_pdata->hw_arch != MTK_VDEC_PURE_SINGLE_CORE) + vdec_msg_queue_wait_lat_buf_full(&instance->ctx->msg_queue); + return vpu_dec_reset(&instance->vpu); +} + +static void vdec_vp9_slice_get_pic_info(struct vdec_vp9_slice_instance *instance) +{ + struct mtk_vcodec_ctx *ctx = instance->ctx; + unsigned int data[3]; + + mtk_vcodec_debug(instance, "w %u h %u\n", + ctx->picinfo.pic_w, ctx->picinfo.pic_h); + + data[0] = ctx->picinfo.pic_w; + data[1] = ctx->picinfo.pic_h; + data[2] = ctx->capture_fourcc; + vpu_dec_get_param(&instance->vpu, data, 3, GET_PARAM_PIC_INFO); + + ctx->picinfo.buf_w = ALIGN(ctx->picinfo.pic_w, 64); + ctx->picinfo.buf_h = ALIGN(ctx->picinfo.pic_h, 64); + ctx->picinfo.fb_sz[0] = instance->vpu.fb_sz[0]; + ctx->picinfo.fb_sz[1] = instance->vpu.fb_sz[1]; +} + +static void vdec_vp9_slice_get_dpb_size(struct vdec_vp9_slice_instance *instance, + unsigned int *dpb_sz) +{ + /* refer VP9 specification */ + *dpb_sz = 9; +} + +static int vdec_vp9_slice_get_param(void *h_vdec, enum vdec_get_param_type type, void *out) +{ + struct vdec_vp9_slice_instance *instance = h_vdec; + + switch (type) { + case GET_PARAM_PIC_INFO: + vdec_vp9_slice_get_pic_info(instance); + break; + case GET_PARAM_DPB_SIZE: + vdec_vp9_slice_get_dpb_size(instance, out); + break; + case GET_PARAM_CROP_INFO: + mtk_vcodec_debug(instance, "No need to get vp9 crop information."); + break; + default: + mtk_vcodec_err(instance, "invalid get parameter type=%d\n", + type); + return -EINVAL; + } + + return 0; +} + +static int vdec_vp9_slice_single_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + struct vdec_vp9_slice_instance *instance = h_vdec; + struct vdec_vp9_slice_pfc *pfc = &instance->sc_pfc; + struct vdec_vp9_slice_vsi *vsi; + struct mtk_vcodec_ctx *ctx; + int ret; + + if (!instance || !instance->ctx) + return -EINVAL; + ctx = instance->ctx; + + /* bs NULL means flush decoder */ + if (!bs) + return vdec_vp9_slice_flush(h_vdec, bs, fb, res_chg); + + fb = ctx->dev->vdec_pdata->get_cap_buffer(ctx); + if (!fb) + return -EBUSY; + + vsi = &pfc->vsi; + + ret = vdec_vp9_slice_setup_single(instance, bs, fb, pfc); + if (ret) { + mtk_vcodec_err(instance, "Failed to setup VP9 single ret %d\n", ret); + return ret; + } + vdec_vp9_slice_vsi_to_remote(vsi, instance->vsi); + + ret = vpu_dec_start(&instance->vpu, NULL, 0); + if (ret) { + mtk_vcodec_err(instance, "Failed to dec VP9 ret %d\n", ret); + return ret; + } + + ret = mtk_vcodec_wait_for_done_ctx(ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, MTK_VDEC_CORE); + /* update remote vsi if decode timeout */ + if (ret) { + mtk_vcodec_err(instance, "VP9 decode timeout %d\n", ret); + WRITE_ONCE(instance->vsi->state.timeout, 1); + } + + vpu_dec_end(&instance->vpu); + + vdec_vp9_slice_vsi_from_remote(vsi, instance->vsi, 0); + ret = vdec_vp9_slice_update_single(instance, pfc); + if (ret) { + mtk_vcodec_err(instance, "VP9 decode error: %d\n", ret); + return ret; + } + + instance->ctx->decoded_frame_cnt++; + return 0; +} + +static int vdec_vp9_slice_lat_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + struct vdec_vp9_slice_instance *instance = h_vdec; + struct vdec_lat_buf *lat_buf; + struct vdec_vp9_slice_pfc *pfc; + struct vdec_vp9_slice_vsi *vsi; + struct mtk_vcodec_ctx *ctx; + int ret; + + if (!instance || !instance->ctx) + return -EINVAL; + ctx = instance->ctx; + + /* init msgQ for the first time */ + if (vdec_msg_queue_init(&ctx->msg_queue, ctx, + vdec_vp9_slice_core_decode, + sizeof(*pfc))) + return -ENOMEM; + + /* bs NULL means flush decoder */ + if (!bs) + return vdec_vp9_slice_flush(h_vdec, bs, fb, res_chg); + + lat_buf = vdec_msg_queue_dqbuf(&instance->ctx->msg_queue.lat_ctx); + if (!lat_buf) { + mtk_vcodec_err(instance, "Failed to get VP9 lat buf\n"); + return -EAGAIN; + } + pfc = (struct vdec_vp9_slice_pfc *)lat_buf->private_data; + if (!pfc) { + ret = -EINVAL; + goto err_free_fb_out; + } + vsi = &pfc->vsi; + + ret = vdec_vp9_slice_setup_lat(instance, bs, lat_buf, pfc); + if (ret) { + mtk_vcodec_err(instance, "Failed to setup VP9 lat ret %d\n", ret); + goto err_free_fb_out; + } + vdec_vp9_slice_vsi_to_remote(vsi, instance->vsi); + + ret = vpu_dec_start(&instance->vpu, NULL, 0); + if (ret) { + mtk_vcodec_err(instance, "Failed to dec VP9 ret %d\n", ret); + goto err_free_fb_out; + } + + if (instance->irq) { + ret = mtk_vcodec_wait_for_done_ctx(ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, MTK_VDEC_LAT0); + /* update remote vsi if decode timeout */ + if (ret) { + mtk_vcodec_err(instance, "VP9 decode timeout %d pic %d\n", ret, pfc->seq); + WRITE_ONCE(instance->vsi->state.timeout, 1); + } + vpu_dec_end(&instance->vpu); + } + + vdec_vp9_slice_vsi_from_remote(vsi, instance->vsi, 0); + ret = vdec_vp9_slice_update_lat(instance, lat_buf, pfc); + + /* LAT trans full, no more UBE or decode timeout */ + if (ret) { + mtk_vcodec_err(instance, "VP9 decode error: %d\n", ret); + goto err_free_fb_out; + } + + mtk_vcodec_debug(instance, "lat dma addr: 0x%lx 0x%lx\n", + (unsigned long)pfc->vsi.trans.dma_addr, + (unsigned long)pfc->vsi.trans.dma_addr_end); + + vdec_msg_queue_update_ube_wptr(&ctx->msg_queue, + vsi->trans.dma_addr_end + + ctx->msg_queue.wdma_addr.dma_addr); + vdec_msg_queue_qbuf(&ctx->dev->msg_queue_core_ctx, lat_buf); + + return 0; +err_free_fb_out: + vdec_msg_queue_qbuf(&ctx->msg_queue.lat_ctx, lat_buf); + return ret; +} + +static int vdec_vp9_slice_decode(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + struct vdec_vp9_slice_instance *instance = h_vdec; + int ret; + + if (instance->ctx->dev->vdec_pdata->hw_arch == MTK_VDEC_PURE_SINGLE_CORE) + ret = vdec_vp9_slice_single_decode(h_vdec, bs, fb, res_chg); + else + ret = vdec_vp9_slice_lat_decode(h_vdec, bs, fb, res_chg); + + return ret; +} + +static int vdec_vp9_slice_core_decode(struct vdec_lat_buf *lat_buf) +{ + struct vdec_vp9_slice_instance *instance; + struct vdec_vp9_slice_pfc *pfc; + struct mtk_vcodec_ctx *ctx = NULL; + struct vdec_fb *fb = NULL; + int ret = -EINVAL; + + if (!lat_buf) + goto err; + + pfc = lat_buf->private_data; + ctx = lat_buf->ctx; + if (!pfc || !ctx) + goto err; + + instance = ctx->drv_handle; + if (!instance) + goto err; + + fb = ctx->dev->vdec_pdata->get_cap_buffer(ctx); + if (!fb) { + ret = -EBUSY; + goto err; + } + + ret = vdec_vp9_slice_setup_core(instance, fb, lat_buf, pfc); + if (ret) { + mtk_vcodec_err(instance, "vdec_vp9_slice_setup_core\n"); + goto err; + } + vdec_vp9_slice_vsi_to_remote(&pfc->vsi, instance->core_vsi); + + ret = vpu_dec_core(&instance->vpu); + if (ret) { + mtk_vcodec_err(instance, "vpu_dec_core\n"); + goto err; + } + + if (instance->irq) { + ret = mtk_vcodec_wait_for_done_ctx(ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, MTK_VDEC_CORE); + /* update remote vsi if decode timeout */ + if (ret) { + mtk_vcodec_err(instance, "VP9 core timeout pic %d\n", pfc->seq); + WRITE_ONCE(instance->core_vsi->state.timeout, 1); + } + vpu_dec_core_end(&instance->vpu); + } + + vdec_vp9_slice_vsi_from_remote(&pfc->vsi, instance->core_vsi, 1); + ret = vdec_vp9_slice_update_core(instance, lat_buf, pfc); + if (ret) { + mtk_vcodec_err(instance, "vdec_vp9_slice_update_core\n"); + goto err; + } + + pfc->vsi.trans.dma_addr_end += ctx->msg_queue.wdma_addr.dma_addr; + mtk_vcodec_debug(instance, "core dma_addr_end 0x%lx\n", + (unsigned long)pfc->vsi.trans.dma_addr_end); + vdec_msg_queue_update_ube_rptr(&ctx->msg_queue, pfc->vsi.trans.dma_addr_end); + ctx->dev->vdec_pdata->cap_to_disp(ctx, 0, lat_buf->src_buf_req); + + return 0; + +err: + if (ctx && pfc) { + /* always update read pointer */ + vdec_msg_queue_update_ube_rptr(&ctx->msg_queue, pfc->vsi.trans.dma_addr_end); + + if (fb) + ctx->dev->vdec_pdata->cap_to_disp(ctx, 1, lat_buf->src_buf_req); + } + return ret; +} + +const struct vdec_common_if vdec_vp9_slice_lat_if = { + .init = vdec_vp9_slice_init, + .decode = vdec_vp9_slice_decode, + .get_param = vdec_vp9_slice_get_param, + .deinit = vdec_vp9_slice_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/vdec_drv_base.h b/drivers/media/platform/mediatek/vcodec/vdec_drv_base.h new file mode 100644 index 000000000..e913f963b --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec_drv_base.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + */ + +#ifndef _VDEC_DRV_BASE_ +#define _VDEC_DRV_BASE_ + +#include "vdec_drv_if.h" + +struct vdec_common_if { + /** + * (*init)() - initialize decode driver + * @ctx : [in] mtk v4l2 context + * @h_vdec : [out] driver handle + */ + int (*init)(struct mtk_vcodec_ctx *ctx); + + /** + * (*decode)() - trigger decode + * @h_vdec : [in] driver handle + * @bs : [in] input bitstream + * @fb : [in] frame buffer to store decoded frame + * @res_chg : [out] resolution change happen + */ + int (*decode)(void *h_vdec, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg); + + /** + * (*get_param)() - get driver's parameter + * @h_vdec : [in] driver handle + * @type : [in] input parameter type + * @out : [out] buffer to store query result + */ + int (*get_param)(void *h_vdec, enum vdec_get_param_type type, + void *out); + + /** + * (*deinit)() - deinitialize driver. + * @h_vdec : [in] driver handle to be deinit + */ + void (*deinit)(void *h_vdec); +}; + +#endif diff --git a/drivers/media/platform/mediatek/vcodec/vdec_drv_if.c b/drivers/media/platform/mediatek/vcodec/vdec_drv_if.c new file mode 100644 index 000000000..f3807f03d --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec_drv_if.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + * Tiffany Lin + */ + +#include +#include +#include + +#include "vdec_drv_if.h" +#include "mtk_vcodec_dec.h" +#include "vdec_drv_base.h" +#include "mtk_vcodec_dec_pm.h" + +int vdec_if_init(struct mtk_vcodec_ctx *ctx, unsigned int fourcc) +{ + enum mtk_vdec_hw_arch hw_arch = ctx->dev->vdec_pdata->hw_arch; + int ret = 0; + + switch (fourcc) { + case V4L2_PIX_FMT_H264_SLICE: + if (!ctx->dev->vdec_pdata->is_subdev_supported) { + ctx->dec_if = &vdec_h264_slice_if; + ctx->hw_id = MTK_VDEC_CORE; + } else { + ctx->dec_if = &vdec_h264_slice_multi_if; + ctx->hw_id = IS_VDEC_LAT_ARCH(hw_arch) ? MTK_VDEC_LAT0 : MTK_VDEC_CORE; + } + break; + case V4L2_PIX_FMT_H264: + ctx->dec_if = &vdec_h264_if; + ctx->hw_id = MTK_VDEC_CORE; + break; + case V4L2_PIX_FMT_VP8_FRAME: + ctx->dec_if = &vdec_vp8_slice_if; + ctx->hw_id = MTK_VDEC_CORE; + break; + case V4L2_PIX_FMT_VP8: + ctx->dec_if = &vdec_vp8_if; + ctx->hw_id = MTK_VDEC_CORE; + break; + case V4L2_PIX_FMT_VP9: + ctx->dec_if = &vdec_vp9_if; + ctx->hw_id = MTK_VDEC_CORE; + break; + case V4L2_PIX_FMT_VP9_FRAME: + ctx->dec_if = &vdec_vp9_slice_lat_if; + ctx->hw_id = IS_VDEC_LAT_ARCH(hw_arch) ? MTK_VDEC_LAT0 : MTK_VDEC_CORE; + break; + default: + return -EINVAL; + } + + mtk_vcodec_dec_enable_hardware(ctx, ctx->hw_id); + ret = ctx->dec_if->init(ctx); + mtk_vcodec_dec_disable_hardware(ctx, ctx->hw_id); + + return ret; +} + +int vdec_if_decode(struct mtk_vcodec_ctx *ctx, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg) +{ + int ret = 0; + + if (bs) { + if ((bs->dma_addr & 63) != 0) { + mtk_v4l2_err("bs dma_addr should 64 byte align"); + return -EINVAL; + } + } + + if (fb) { + if (((fb->base_y.dma_addr & 511) != 0) || + ((fb->base_c.dma_addr & 511) != 0)) { + mtk_v4l2_err("frame buffer dma_addr should 512 byte align"); + return -EINVAL; + } + } + + if (!ctx->drv_handle) + return -EIO; + + mtk_vcodec_dec_enable_hardware(ctx, ctx->hw_id); + mtk_vcodec_set_curr_ctx(ctx->dev, ctx, ctx->hw_id); + ret = ctx->dec_if->decode(ctx->drv_handle, bs, fb, res_chg); + mtk_vcodec_set_curr_ctx(ctx->dev, NULL, ctx->hw_id); + mtk_vcodec_dec_disable_hardware(ctx, ctx->hw_id); + + return ret; +} + +int vdec_if_get_param(struct mtk_vcodec_ctx *ctx, enum vdec_get_param_type type, + void *out) +{ + int ret = 0; + + if (!ctx->drv_handle) + return -EIO; + + mtk_vdec_lock(ctx); + ret = ctx->dec_if->get_param(ctx->drv_handle, type, out); + mtk_vdec_unlock(ctx); + + return ret; +} + +void vdec_if_deinit(struct mtk_vcodec_ctx *ctx) +{ + if (!ctx->drv_handle) + return; + + mtk_vcodec_dec_enable_hardware(ctx, ctx->hw_id); + ctx->dec_if->deinit(ctx->drv_handle); + mtk_vcodec_dec_disable_hardware(ctx, ctx->hw_id); + + ctx->drv_handle = NULL; +} diff --git a/drivers/media/platform/mediatek/vcodec/vdec_drv_if.h b/drivers/media/platform/mediatek/vcodec/vdec_drv_if.h new file mode 100644 index 000000000..076306ff2 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec_drv_if.h @@ -0,0 +1,103 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + * Tiffany Lin + */ + +#ifndef _VDEC_DRV_IF_H_ +#define _VDEC_DRV_IF_H_ + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_dec.h" +#include "mtk_vcodec_util.h" + + +/** + * enum vdec_fb_status - decoder frame buffer status + * @FB_ST_NORMAL: initial state + * @FB_ST_DISPLAY: frame buffer is ready to be displayed + * @FB_ST_FREE: frame buffer is not used by decoder any more + */ +enum vdec_fb_status { + FB_ST_NORMAL = 0, + FB_ST_DISPLAY = (1 << 0), + FB_ST_FREE = (1 << 1) +}; + +/* For GET_PARAM_DISP_FRAME_BUFFER and GET_PARAM_FREE_FRAME_BUFFER, + * the caller does not own the returned buffer. The buffer will not be + * released before vdec_if_deinit. + * GET_PARAM_DISP_FRAME_BUFFER : get next displayable frame buffer, + * struct vdec_fb** + * GET_PARAM_FREE_FRAME_BUFFER : get non-referenced framebuffer, vdec_fb** + * GET_PARAM_PIC_INFO : get picture info, struct vdec_pic_info* + * GET_PARAM_CROP_INFO : get crop info, struct v4l2_crop* + * GET_PARAM_DPB_SIZE : get dpb size, unsigned int* + */ +enum vdec_get_param_type { + GET_PARAM_DISP_FRAME_BUFFER, + GET_PARAM_FREE_FRAME_BUFFER, + GET_PARAM_PIC_INFO, + GET_PARAM_CROP_INFO, + GET_PARAM_DPB_SIZE +}; + +/** + * struct vdec_fb_node - decoder frame buffer node + * @list : list to hold this node + * @fb : point to frame buffer (vdec_fb), fb could point to frame buffer and + * working buffer this is for maintain buffers in different state + */ +struct vdec_fb_node { + struct list_head list; + struct vdec_fb *fb; +}; + +extern const struct vdec_common_if vdec_h264_if; +extern const struct vdec_common_if vdec_h264_slice_if; +extern const struct vdec_common_if vdec_h264_slice_multi_if; +extern const struct vdec_common_if vdec_vp8_if; +extern const struct vdec_common_if vdec_vp8_slice_if; +extern const struct vdec_common_if vdec_vp9_if; +extern const struct vdec_common_if vdec_vp9_slice_lat_if; + +/** + * vdec_if_init() - initialize decode driver + * @ctx : [in] v4l2 context + * @fourcc : [in] video format fourcc, V4L2_PIX_FMT_H264/VP8/VP9.. + */ +int vdec_if_init(struct mtk_vcodec_ctx *ctx, unsigned int fourcc); + +/** + * vdec_if_deinit() - deinitialize decode driver + * @ctx : [in] v4l2 context + * + */ +void vdec_if_deinit(struct mtk_vcodec_ctx *ctx); + +/** + * vdec_if_decode() - trigger decode + * @ctx : [in] v4l2 context + * @bs : [in] input bitstream + * @fb : [in] frame buffer to store decoded frame, when null means parse + * header only + * @res_chg : [out] resolution change happens if current bs have different + * picture width/height + * Note: To flush the decoder when reaching EOF, set input bitstream as NULL. + * + * Return: 0 on success. -EIO on unrecoverable error. + */ +int vdec_if_decode(struct mtk_vcodec_ctx *ctx, struct mtk_vcodec_mem *bs, + struct vdec_fb *fb, bool *res_chg); + +/** + * vdec_if_get_param() - get driver's parameter + * @ctx : [in] v4l2 context + * @type : [in] input parameter type + * @out : [out] buffer to store query result + */ +int vdec_if_get_param(struct mtk_vcodec_ctx *ctx, enum vdec_get_param_type type, + void *out); + +#endif diff --git a/drivers/media/platform/mediatek/vcodec/vdec_ipi_msg.h b/drivers/media/platform/mediatek/vcodec/vdec_ipi_msg.h new file mode 100644 index 000000000..47070be2a --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec_ipi_msg.h @@ -0,0 +1,153 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + */ + +#ifndef _VDEC_IPI_MSG_H_ +#define _VDEC_IPI_MSG_H_ + +/* + * enum vdec_ipi_msgid - message id between AP and VPU + * @AP_IPIMSG_XXX : AP to VPU cmd message id + * @VPU_IPIMSG_XXX_ACK : VPU ack AP cmd message id + */ +enum vdec_ipi_msgid { + AP_IPIMSG_DEC_INIT = 0xA000, + AP_IPIMSG_DEC_START = 0xA001, + AP_IPIMSG_DEC_END = 0xA002, + AP_IPIMSG_DEC_DEINIT = 0xA003, + AP_IPIMSG_DEC_RESET = 0xA004, + AP_IPIMSG_DEC_CORE = 0xA005, + AP_IPIMSG_DEC_CORE_END = 0xA006, + AP_IPIMSG_DEC_GET_PARAM = 0xA007, + + VPU_IPIMSG_DEC_INIT_ACK = 0xB000, + VPU_IPIMSG_DEC_START_ACK = 0xB001, + VPU_IPIMSG_DEC_END_ACK = 0xB002, + VPU_IPIMSG_DEC_DEINIT_ACK = 0xB003, + VPU_IPIMSG_DEC_RESET_ACK = 0xB004, + VPU_IPIMSG_DEC_CORE_ACK = 0xB005, + VPU_IPIMSG_DEC_CORE_END_ACK = 0xB006, + VPU_IPIMSG_DEC_GET_PARAM_ACK = 0xB007, +}; + +/** + * struct vdec_ap_ipi_cmd - generic AP to VPU ipi command format + * @msg_id : vdec_ipi_msgid + * @vpu_inst_addr : VPU decoder instance address. Used if ABI version < 2. + * @inst_id : instance ID. Used if the ABI version >= 2. + * @codec_type : codec fourcc + * @reserved : reserved param + */ +struct vdec_ap_ipi_cmd { + uint32_t msg_id; + union { + uint32_t vpu_inst_addr; + uint32_t inst_id; + }; + u32 codec_type; + u32 reserved; +}; + +/** + * struct vdec_vpu_ipi_ack - generic VPU to AP ipi command format + * @msg_id : vdec_ipi_msgid + * @status : VPU exeuction result + * @ap_inst_addr : AP video decoder instance address + */ +struct vdec_vpu_ipi_ack { + uint32_t msg_id; + int32_t status; + uint64_t ap_inst_addr; +}; + +/** + * struct vdec_ap_ipi_init - for AP_IPIMSG_DEC_INIT + * @msg_id : AP_IPIMSG_DEC_INIT + * @codec_type : codec fourcc + * @ap_inst_addr : AP video decoder instance address + */ +struct vdec_ap_ipi_init { + uint32_t msg_id; + u32 codec_type; + uint64_t ap_inst_addr; +}; + +/** + * struct vdec_ap_ipi_dec_start - for AP_IPIMSG_DEC_START + * @msg_id : AP_IPIMSG_DEC_START + * @vpu_inst_addr : VPU decoder instance address. Used if ABI version < 2. + * @inst_id : instance ID. Used if the ABI version >= 2. + * @data : Header info + * H264 decoder [0]:buf_sz [1]:nal_start + * VP8 decoder [0]:width/height + * VP9 decoder [0]:profile, [1][2] width/height + * @codec_type : codec fourcc + */ +struct vdec_ap_ipi_dec_start { + uint32_t msg_id; + union { + uint32_t vpu_inst_addr; + uint32_t inst_id; + }; + uint32_t data[3]; + u32 codec_type; +}; + +/** + * struct vdec_vpu_ipi_init_ack - for VPU_IPIMSG_DEC_INIT_ACK + * @msg_id : VPU_IPIMSG_DEC_INIT_ACK + * @status : VPU exeuction result + * @ap_inst_addr : AP vcodec_vpu_inst instance address + * @vpu_inst_addr : VPU decoder instance address + * @vdec_abi_version: ABI version of the firmware. Kernel can use it to + * ensure that it is compatible with the firmware. + * This field is not valid for MT8173 and must not be + * accessed for this chip. + * @inst_id : instance ID. Valid only if the ABI version >= 2. + */ +struct vdec_vpu_ipi_init_ack { + uint32_t msg_id; + int32_t status; + uint64_t ap_inst_addr; + uint32_t vpu_inst_addr; + uint32_t vdec_abi_version; + uint32_t inst_id; +}; + +/** + * struct vdec_ap_ipi_get_param - for AP_IPIMSG_DEC_GET_PARAM + * @msg_id : AP_IPIMSG_DEC_GET_PARAM + * @inst_id : instance ID. Used if the ABI version >= 2. + * @data : picture information + * @param_type : get param type + * @codec_type : Codec fourcc + */ +struct vdec_ap_ipi_get_param { + u32 msg_id; + u32 inst_id; + u32 data[4]; + u32 param_type; + u32 codec_type; +}; + +/** + * struct vdec_vpu_ipi_get_param_ack - for VPU_IPIMSG_DEC_GET_PARAM_ACK + * @msg_id : VPU_IPIMSG_DEC_GET_PARAM_ACK + * @status : VPU execution result + * @ap_inst_addr : AP vcodec_vpu_inst instance address + * @data : picture information from SCP. + * @param_type : get param type + * @reserved : reserved param + */ +struct vdec_vpu_ipi_get_param_ack { + u32 msg_id; + s32 status; + u64 ap_inst_addr; + u32 data[4]; + u32 param_type; + u32 reserved; +}; + +#endif diff --git a/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c b/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c new file mode 100644 index 000000000..a81212c0a --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.c @@ -0,0 +1,358 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2021 MediaTek Inc. + * Author: Yunfei Dong + */ + +#include +#include +#include + +#include "mtk_vcodec_dec_pm.h" +#include "mtk_vcodec_drv.h" +#include "vdec_msg_queue.h" + +#define VDEC_MSG_QUEUE_TIMEOUT_MS 1500 + +/* the size used to store lat slice header information */ +#define VDEC_LAT_SLICE_HEADER_SZ (640 * SZ_1K) + +/* the size used to store avc error information */ +#define VDEC_ERR_MAP_SZ_AVC (17 * SZ_1K) + +/* core will read the trans buffer which decoded by lat to decode again. + * The trans buffer size of FHD and 4K bitstreams are different. + */ +static int vde_msg_queue_get_trans_size(int width, int height) +{ + if (width > 1920 || height > 1088) + return 30 * SZ_1M; + else + return 6 * SZ_1M; +} + +void vdec_msg_queue_init_ctx(struct vdec_msg_queue_ctx *ctx, int hardware_index) +{ + init_waitqueue_head(&ctx->ready_to_use); + INIT_LIST_HEAD(&ctx->ready_queue); + spin_lock_init(&ctx->ready_lock); + ctx->ready_num = 0; + ctx->hardware_index = hardware_index; +} + +static struct list_head *vdec_get_buf_list(int hardware_index, struct vdec_lat_buf *buf) +{ + switch (hardware_index) { + case MTK_VDEC_CORE: + return &buf->core_list; + case MTK_VDEC_LAT0: + return &buf->lat_list; + default: + return NULL; + } +} + +static void vdec_msg_queue_inc(struct vdec_msg_queue *msg_queue, int hardware_index) +{ + if (hardware_index == MTK_VDEC_CORE) + atomic_inc(&msg_queue->core_list_cnt); + else + atomic_inc(&msg_queue->lat_list_cnt); +} + +static void vdec_msg_queue_dec(struct vdec_msg_queue *msg_queue, int hardware_index) +{ + if (hardware_index == MTK_VDEC_CORE) + atomic_dec(&msg_queue->core_list_cnt); + else + atomic_dec(&msg_queue->lat_list_cnt); +} + +int vdec_msg_queue_qbuf(struct vdec_msg_queue_ctx *msg_ctx, struct vdec_lat_buf *buf) +{ + struct list_head *head; + + head = vdec_get_buf_list(msg_ctx->hardware_index, buf); + if (!head) { + mtk_v4l2_err("fail to qbuf: %d", msg_ctx->hardware_index); + return -EINVAL; + } + + spin_lock(&msg_ctx->ready_lock); + list_add_tail(head, &msg_ctx->ready_queue); + msg_ctx->ready_num++; + + vdec_msg_queue_inc(&buf->ctx->msg_queue, msg_ctx->hardware_index); + if (msg_ctx->hardware_index != MTK_VDEC_CORE) { + wake_up_all(&msg_ctx->ready_to_use); + } else { + if (!(buf->ctx->msg_queue.status & CONTEXT_LIST_QUEUED)) { + queue_work(buf->ctx->dev->core_workqueue, &buf->ctx->msg_queue.core_work); + buf->ctx->msg_queue.status |= CONTEXT_LIST_QUEUED; + } + } + + mtk_v4l2_debug(3, "enqueue buf type: %d addr: 0x%p num: %d", + msg_ctx->hardware_index, buf, msg_ctx->ready_num); + spin_unlock(&msg_ctx->ready_lock); + + return 0; +} + +static bool vdec_msg_queue_wait_event(struct vdec_msg_queue_ctx *msg_ctx) +{ + int ret; + + ret = wait_event_timeout(msg_ctx->ready_to_use, + !list_empty(&msg_ctx->ready_queue), + msecs_to_jiffies(VDEC_MSG_QUEUE_TIMEOUT_MS)); + if (!ret) + return false; + + return true; +} + +struct vdec_lat_buf *vdec_msg_queue_dqbuf(struct vdec_msg_queue_ctx *msg_ctx) +{ + struct vdec_lat_buf *buf; + struct list_head *head; + int ret; + + spin_lock(&msg_ctx->ready_lock); + if (list_empty(&msg_ctx->ready_queue)) { + mtk_v4l2_debug(3, "queue is NULL, type:%d num: %d", + msg_ctx->hardware_index, msg_ctx->ready_num); + spin_unlock(&msg_ctx->ready_lock); + + if (msg_ctx->hardware_index == MTK_VDEC_CORE) + return NULL; + + ret = vdec_msg_queue_wait_event(msg_ctx); + if (!ret) + return NULL; + spin_lock(&msg_ctx->ready_lock); + } + + if (msg_ctx->hardware_index == MTK_VDEC_CORE) + buf = list_first_entry(&msg_ctx->ready_queue, + struct vdec_lat_buf, core_list); + else + buf = list_first_entry(&msg_ctx->ready_queue, + struct vdec_lat_buf, lat_list); + + head = vdec_get_buf_list(msg_ctx->hardware_index, buf); + if (!head) { + spin_unlock(&msg_ctx->ready_lock); + mtk_v4l2_err("fail to dqbuf: %d", msg_ctx->hardware_index); + return NULL; + } + list_del(head); + vdec_msg_queue_dec(&buf->ctx->msg_queue, msg_ctx->hardware_index); + + msg_ctx->ready_num--; + mtk_v4l2_debug(3, "dqueue buf type:%d addr: 0x%p num: %d", + msg_ctx->hardware_index, buf, msg_ctx->ready_num); + spin_unlock(&msg_ctx->ready_lock); + + return buf; +} + +void vdec_msg_queue_update_ube_rptr(struct vdec_msg_queue *msg_queue, uint64_t ube_rptr) +{ + spin_lock(&msg_queue->lat_ctx.ready_lock); + msg_queue->wdma_rptr_addr = ube_rptr; + mtk_v4l2_debug(3, "update ube rprt (0x%llx)", ube_rptr); + spin_unlock(&msg_queue->lat_ctx.ready_lock); +} + +void vdec_msg_queue_update_ube_wptr(struct vdec_msg_queue *msg_queue, uint64_t ube_wptr) +{ + spin_lock(&msg_queue->lat_ctx.ready_lock); + msg_queue->wdma_wptr_addr = ube_wptr; + mtk_v4l2_debug(3, "update ube wprt: (0x%llx 0x%llx) offset: 0x%llx", + msg_queue->wdma_rptr_addr, msg_queue->wdma_wptr_addr, + ube_wptr); + spin_unlock(&msg_queue->lat_ctx.ready_lock); +} + +bool vdec_msg_queue_wait_lat_buf_full(struct vdec_msg_queue *msg_queue) +{ + struct vdec_lat_buf *buf, *tmp; + struct list_head *list_core[3]; + struct vdec_msg_queue_ctx *core_ctx; + int ret, i, in_core_count = 0, count = 0; + long timeout_jiff; + + core_ctx = &msg_queue->ctx->dev->msg_queue_core_ctx; + spin_lock(&core_ctx->ready_lock); + list_for_each_entry_safe(buf, tmp, &core_ctx->ready_queue, core_list) { + if (buf && buf->ctx == msg_queue->ctx) { + list_core[in_core_count++] = &buf->core_list; + list_del(&buf->core_list); + } + } + + for (i = 0; i < in_core_count; i++) { + list_add(list_core[in_core_count - (1 + i)], &core_ctx->ready_queue); + queue_work(msg_queue->ctx->dev->core_workqueue, &msg_queue->core_work); + } + spin_unlock(&core_ctx->ready_lock); + + timeout_jiff = msecs_to_jiffies(1000 * (NUM_BUFFER_COUNT + 2)); + ret = wait_event_timeout(msg_queue->ctx->msg_queue.core_dec_done, + msg_queue->lat_ctx.ready_num == NUM_BUFFER_COUNT, + timeout_jiff); + if (ret) { + mtk_v4l2_debug(3, "success to get lat buf: %d", + msg_queue->lat_ctx.ready_num); + return true; + } + + spin_lock(&core_ctx->ready_lock); + list_for_each_entry_safe(buf, tmp, &core_ctx->ready_queue, core_list) { + if (buf && buf->ctx == msg_queue->ctx) { + count++; + list_del(&buf->core_list); + } + } + spin_unlock(&core_ctx->ready_lock); + + mtk_v4l2_err("failed with lat buf isn't full: list(%d %d) count:%d", + atomic_read(&msg_queue->lat_list_cnt), + atomic_read(&msg_queue->core_list_cnt), count); + + return false; +} + +void vdec_msg_queue_deinit(struct vdec_msg_queue *msg_queue, + struct mtk_vcodec_ctx *ctx) +{ + struct vdec_lat_buf *lat_buf; + struct mtk_vcodec_mem *mem; + int i; + + mem = &msg_queue->wdma_addr; + if (mem->va) + mtk_vcodec_mem_free(ctx, mem); + for (i = 0; i < NUM_BUFFER_COUNT; i++) { + lat_buf = &msg_queue->lat_buf[i]; + + mem = &lat_buf->wdma_err_addr; + if (mem->va) + mtk_vcodec_mem_free(ctx, mem); + + mem = &lat_buf->slice_bc_addr; + if (mem->va) + mtk_vcodec_mem_free(ctx, mem); + + kfree(lat_buf->private_data); + lat_buf->private_data = NULL; + } +} + +static void vdec_msg_queue_core_work(struct work_struct *work) +{ + struct vdec_msg_queue *msg_queue = + container_of(work, struct vdec_msg_queue, core_work); + struct mtk_vcodec_ctx *ctx = + container_of(msg_queue, struct mtk_vcodec_ctx, msg_queue); + struct mtk_vcodec_dev *dev = ctx->dev; + struct vdec_lat_buf *lat_buf; + + spin_lock(&ctx->dev->msg_queue_core_ctx.ready_lock); + ctx->msg_queue.status &= ~CONTEXT_LIST_QUEUED; + spin_unlock(&ctx->dev->msg_queue_core_ctx.ready_lock); + + lat_buf = vdec_msg_queue_dqbuf(&dev->msg_queue_core_ctx); + if (!lat_buf) + return; + + ctx = lat_buf->ctx; + mtk_vcodec_dec_enable_hardware(ctx, MTK_VDEC_CORE); + mtk_vcodec_set_curr_ctx(dev, ctx, MTK_VDEC_CORE); + + lat_buf->core_decode(lat_buf); + + mtk_vcodec_set_curr_ctx(dev, NULL, MTK_VDEC_CORE); + mtk_vcodec_dec_disable_hardware(ctx, MTK_VDEC_CORE); + vdec_msg_queue_qbuf(&ctx->msg_queue.lat_ctx, lat_buf); + + wake_up_all(&ctx->msg_queue.core_dec_done); + if (!(ctx->msg_queue.status & CONTEXT_LIST_QUEUED) && + atomic_read(&msg_queue->core_list_cnt)) { + spin_lock(&ctx->dev->msg_queue_core_ctx.ready_lock); + ctx->msg_queue.status |= CONTEXT_LIST_QUEUED; + spin_unlock(&ctx->dev->msg_queue_core_ctx.ready_lock); + queue_work(ctx->dev->core_workqueue, &msg_queue->core_work); + } +} + +int vdec_msg_queue_init(struct vdec_msg_queue *msg_queue, + struct mtk_vcodec_ctx *ctx, core_decode_cb_t core_decode, + int private_size) +{ + struct vdec_lat_buf *lat_buf; + int i, err; + + /* already init msg queue */ + if (msg_queue->wdma_addr.size) + return 0; + + msg_queue->ctx = ctx; + vdec_msg_queue_init_ctx(&msg_queue->lat_ctx, MTK_VDEC_LAT0); + INIT_WORK(&msg_queue->core_work, vdec_msg_queue_core_work); + + atomic_set(&msg_queue->lat_list_cnt, 0); + atomic_set(&msg_queue->core_list_cnt, 0); + init_waitqueue_head(&msg_queue->core_dec_done); + msg_queue->status = CONTEXT_LIST_EMPTY; + + msg_queue->wdma_addr.size = + vde_msg_queue_get_trans_size(ctx->picinfo.buf_w, + ctx->picinfo.buf_h); + err = mtk_vcodec_mem_alloc(ctx, &msg_queue->wdma_addr); + if (err) { + mtk_v4l2_err("failed to allocate wdma_addr buf"); + msg_queue->wdma_addr.size = 0; + return -ENOMEM; + } + msg_queue->wdma_rptr_addr = msg_queue->wdma_addr.dma_addr; + msg_queue->wdma_wptr_addr = msg_queue->wdma_addr.dma_addr; + + for (i = 0; i < NUM_BUFFER_COUNT; i++) { + lat_buf = &msg_queue->lat_buf[i]; + + lat_buf->wdma_err_addr.size = VDEC_ERR_MAP_SZ_AVC; + err = mtk_vcodec_mem_alloc(ctx, &lat_buf->wdma_err_addr); + if (err) { + mtk_v4l2_err("failed to allocate wdma_err_addr buf[%d]", i); + goto mem_alloc_err; + } + + lat_buf->slice_bc_addr.size = VDEC_LAT_SLICE_HEADER_SZ; + err = mtk_vcodec_mem_alloc(ctx, &lat_buf->slice_bc_addr); + if (err) { + mtk_v4l2_err("failed to allocate wdma_addr buf[%d]", i); + goto mem_alloc_err; + } + + lat_buf->private_data = kzalloc(private_size, GFP_KERNEL); + if (!lat_buf->private_data) { + err = -ENOMEM; + goto mem_alloc_err; + } + + lat_buf->ctx = ctx; + lat_buf->core_decode = core_decode; + err = vdec_msg_queue_qbuf(&msg_queue->lat_ctx, lat_buf); + if (err) { + mtk_v4l2_err("failed to qbuf buf[%d]", i); + goto mem_alloc_err; + } + } + return 0; + +mem_alloc_err: + vdec_msg_queue_deinit(msg_queue, ctx); + return err; +} diff --git a/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.h b/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.h new file mode 100644 index 000000000..8f82d1484 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec_msg_queue.h @@ -0,0 +1,179 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 MediaTek Inc. + * Author: Yunfei Dong + */ + +#ifndef _VDEC_MSG_QUEUE_H_ +#define _VDEC_MSG_QUEUE_H_ + +#include +#include +#include +#include + +#include "mtk_vcodec_util.h" + +#define NUM_BUFFER_COUNT 3 + +struct vdec_lat_buf; +struct mtk_vcodec_ctx; +struct mtk_vcodec_dev; +typedef int (*core_decode_cb_t)(struct vdec_lat_buf *lat_buf); + +/** + * enum core_ctx_status - Context decode status for core hardwre. + * @CONTEXT_LIST_EMPTY: No buffer queued on core hardware(must always be 0) + * @CONTEXT_LIST_QUEUED: Buffer queued to core work list + * @CONTEXT_LIST_DEC_DONE: context decode done + */ +enum core_ctx_status { + CONTEXT_LIST_EMPTY = 0, + CONTEXT_LIST_QUEUED, + CONTEXT_LIST_DEC_DONE, +}; + +/** + * struct vdec_msg_queue_ctx - represents a queue for buffers ready to be processed + * @ready_to_use: ready used queue used to signalize when get a job queue + * @ready_queue: list of ready lat buffer queues + * @ready_lock: spin lock to protect the lat buffer usage + * @ready_num: number of buffers ready to be processed + * @hardware_index: hardware id that this queue is used for + */ +struct vdec_msg_queue_ctx { + wait_queue_head_t ready_to_use; + struct list_head ready_queue; + /* protect lat buffer */ + spinlock_t ready_lock; + int ready_num; + int hardware_index; +}; + +/** + * struct vdec_lat_buf - lat buffer message used to store lat info for core decode + * @wdma_err_addr: wdma error address used for lat hardware + * @slice_bc_addr: slice bc address used for lat hardware + * @ts_info: need to set timestamp from output to capture + * @src_buf_req: output buffer media request object + * + * @private_data: shared information used to lat and core hardware + * @ctx: mtk vcodec context information + * @core_decode: different codec use different decode callback function + * @lat_list: add lat buffer to lat head list + * @core_list: add lat buffer to core head list + */ +struct vdec_lat_buf { + struct mtk_vcodec_mem wdma_err_addr; + struct mtk_vcodec_mem slice_bc_addr; + struct vb2_v4l2_buffer ts_info; + struct media_request *src_buf_req; + + void *private_data; + struct mtk_vcodec_ctx *ctx; + core_decode_cb_t core_decode; + struct list_head lat_list; + struct list_head core_list; +}; + +/** + * struct vdec_msg_queue - used to store lat buffer message + * @lat_buf: lat buffer used to store lat buffer information + * @wdma_addr: wdma address used for ube + * @wdma_rptr_addr: ube read point + * @wdma_wptr_addr: ube write point + * @core_work: core hardware work + * @lat_ctx: used to store lat buffer list + * @ctx: point to mtk_vcodec_ctx + * + * @lat_list_cnt: used to record each instance lat list count + * @core_list_cnt: used to record each instance core list count + * @core_dec_done: core work queue decode done event + * @status: current context decode status for core hardware + */ +struct vdec_msg_queue { + struct vdec_lat_buf lat_buf[NUM_BUFFER_COUNT]; + + struct mtk_vcodec_mem wdma_addr; + u64 wdma_rptr_addr; + u64 wdma_wptr_addr; + + struct work_struct core_work; + struct vdec_msg_queue_ctx lat_ctx; + struct mtk_vcodec_ctx *ctx; + + atomic_t lat_list_cnt; + atomic_t core_list_cnt; + wait_queue_head_t core_dec_done; + int status; +}; + +/** + * vdec_msg_queue_init - init lat buffer information. + * @msg_queue: used to store the lat buffer information + * @ctx: v4l2 ctx + * @core_decode: core decode callback for each codec + * @private_size: the private data size used to share with core + * + * Return: returns 0 if init successfully, or fail. + */ +int vdec_msg_queue_init(struct vdec_msg_queue *msg_queue, + struct mtk_vcodec_ctx *ctx, core_decode_cb_t core_decode, + int private_size); + +/** + * vdec_msg_queue_init_ctx - used to init msg queue context information. + * @ctx: message queue context + * @hardware_index: hardware index + */ +void vdec_msg_queue_init_ctx(struct vdec_msg_queue_ctx *ctx, int hardware_index); + +/** + * vdec_msg_queue_qbuf - enqueue lat buffer to queue list. + * @ctx: message queue context + * @buf: current lat buffer + * + * Return: returns 0 if qbuf successfully, or fail. + */ +int vdec_msg_queue_qbuf(struct vdec_msg_queue_ctx *ctx, struct vdec_lat_buf *buf); + +/** + * vdec_msg_queue_dqbuf - dequeue lat buffer from queue list. + * @ctx: message queue context + * + * Return: returns not null if dq successfully, or fail. + */ +struct vdec_lat_buf *vdec_msg_queue_dqbuf(struct vdec_msg_queue_ctx *ctx); + +/** + * vdec_msg_queue_update_ube_rptr - used to updata the ube read point. + * @msg_queue: used to store the lat buffer information + * @ube_rptr: current ube read point + */ +void vdec_msg_queue_update_ube_rptr(struct vdec_msg_queue *msg_queue, uint64_t ube_rptr); + +/** + * vdec_msg_queue_update_ube_wptr - used to updata the ube write point. + * @msg_queue: used to store the lat buffer information + * @ube_wptr: current ube write point + */ +void vdec_msg_queue_update_ube_wptr(struct vdec_msg_queue *msg_queue, uint64_t ube_wptr); + +/** + * vdec_msg_queue_wait_lat_buf_full - used to check whether all lat buffer + * in lat list. + * @msg_queue: used to store the lat buffer information + * + * Return: returns true if successfully, or fail. + */ +bool vdec_msg_queue_wait_lat_buf_full(struct vdec_msg_queue *msg_queue); + +/** + * vdec_msg_queue_deinit - deinit lat buffer information. + * @msg_queue: used to store the lat buffer information + * @ctx: v4l2 ctx + */ +void vdec_msg_queue_deinit(struct vdec_msg_queue *msg_queue, + struct mtk_vcodec_ctx *ctx); + +#endif diff --git a/drivers/media/platform/mediatek/vcodec/vdec_vpu_if.c b/drivers/media/platform/mediatek/vcodec/vdec_vpu_if.c new file mode 100644 index 000000000..df309e8e9 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec_vpu_if.c @@ -0,0 +1,297 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + */ + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_util.h" +#include "vdec_drv_if.h" +#include "vdec_ipi_msg.h" +#include "vdec_vpu_if.h" +#include "mtk_vcodec_fw.h" + +static void handle_init_ack_msg(const struct vdec_vpu_ipi_init_ack *msg) +{ + struct vdec_vpu_inst *vpu = (struct vdec_vpu_inst *) + (unsigned long)msg->ap_inst_addr; + + mtk_vcodec_debug(vpu, "+ ap_inst_addr = 0x%llx", msg->ap_inst_addr); + + /* mapping VPU address to kernel virtual address */ + /* the content in vsi is initialized to 0 in VPU */ + vpu->vsi = mtk_vcodec_fw_map_dm_addr(vpu->ctx->dev->fw_handler, + msg->vpu_inst_addr); + vpu->inst_addr = msg->vpu_inst_addr; + + mtk_vcodec_debug(vpu, "- vpu_inst_addr = 0x%x", vpu->inst_addr); + + /* Set default ABI version if dealing with unversioned firmware. */ + vpu->fw_abi_version = 0; + /* + * Instance ID is only used if ABI version >= 2. Initialize it with + * garbage by default. + */ + vpu->inst_id = 0xdeadbeef; + + /* VPU firmware does not contain a version field. */ + if (mtk_vcodec_fw_get_type(vpu->ctx->dev->fw_handler) == VPU) + return; + + /* Check firmware version. */ + vpu->fw_abi_version = msg->vdec_abi_version; + mtk_vcodec_debug(vpu, "firmware version 0x%x\n", vpu->fw_abi_version); + switch (vpu->fw_abi_version) { + case 1: + break; + case 2: + vpu->inst_id = msg->inst_id; + break; + default: + mtk_vcodec_err(vpu, "unhandled firmware version 0x%x\n", + vpu->fw_abi_version); + vpu->failure = 1; + break; + } +} + +static void handle_get_param_msg_ack(const struct vdec_vpu_ipi_get_param_ack *msg) +{ + struct vdec_vpu_inst *vpu = (struct vdec_vpu_inst *) + (unsigned long)msg->ap_inst_addr; + + mtk_vcodec_debug(vpu, "+ ap_inst_addr = 0x%llx", msg->ap_inst_addr); + + /* param_type is enum vdec_get_param_type */ + switch (msg->param_type) { + case GET_PARAM_PIC_INFO: + vpu->fb_sz[0] = msg->data[0]; + vpu->fb_sz[1] = msg->data[1]; + break; + default: + mtk_vcodec_err(vpu, "invalid get param type=%d", msg->param_type); + vpu->failure = 1; + break; + } +} + +/* + * vpu_dec_ipi_handler - Handler for VPU ipi message. + * + * @data: ipi message + * @len : length of ipi message + * @priv: callback private data which is passed by decoder when register. + * + * This function runs in interrupt context and it means there's an IPI MSG + * from VPU. + */ +static void vpu_dec_ipi_handler(void *data, unsigned int len, void *priv) +{ + const struct vdec_vpu_ipi_ack *msg = data; + struct vdec_vpu_inst *vpu = (struct vdec_vpu_inst *) + (unsigned long)msg->ap_inst_addr; + + if (!vpu) { + mtk_v4l2_err("ap_inst_addr is NULL, did the SCP hang or crash?"); + return; + } + + mtk_vcodec_debug(vpu, "+ id=%X", msg->msg_id); + + vpu->failure = msg->status; + vpu->signaled = 1; + + if (msg->status == 0) { + switch (msg->msg_id) { + case VPU_IPIMSG_DEC_INIT_ACK: + handle_init_ack_msg(data); + break; + + case VPU_IPIMSG_DEC_START_ACK: + case VPU_IPIMSG_DEC_END_ACK: + case VPU_IPIMSG_DEC_DEINIT_ACK: + case VPU_IPIMSG_DEC_RESET_ACK: + case VPU_IPIMSG_DEC_CORE_ACK: + case VPU_IPIMSG_DEC_CORE_END_ACK: + break; + + case VPU_IPIMSG_DEC_GET_PARAM_ACK: + handle_get_param_msg_ack(data); + break; + default: + mtk_vcodec_err(vpu, "invalid msg=%X", msg->msg_id); + break; + } + } + + mtk_vcodec_debug(vpu, "- id=%X", msg->msg_id); +} + +static int vcodec_vpu_send_msg(struct vdec_vpu_inst *vpu, void *msg, int len) +{ + int err, id, msgid; + + msgid = *(uint32_t *)msg; + mtk_vcodec_debug(vpu, "id=%X", msgid); + + vpu->failure = 0; + vpu->signaled = 0; + + if (vpu->ctx->dev->vdec_pdata->hw_arch == MTK_VDEC_LAT_SINGLE_CORE) { + if (msgid == AP_IPIMSG_DEC_CORE || + msgid == AP_IPIMSG_DEC_CORE_END) + id = vpu->core_id; + else + id = vpu->id; + } else { + id = vpu->id; + } + + err = mtk_vcodec_fw_ipi_send(vpu->ctx->dev->fw_handler, id, msg, + len, 2000); + if (err) { + mtk_vcodec_err(vpu, "send fail vpu_id=%d msg_id=%X status=%d", + id, msgid, err); + return err; + } + + return vpu->failure; +} + +static int vcodec_send_ap_ipi(struct vdec_vpu_inst *vpu, unsigned int msg_id) +{ + struct vdec_ap_ipi_cmd msg; + int err = 0; + + mtk_vcodec_debug(vpu, "+ id=%X", msg_id); + + memset(&msg, 0, sizeof(msg)); + msg.msg_id = msg_id; + if (vpu->fw_abi_version < 2) + msg.vpu_inst_addr = vpu->inst_addr; + else + msg.inst_id = vpu->inst_id; + msg.codec_type = vpu->codec_type; + + err = vcodec_vpu_send_msg(vpu, &msg, sizeof(msg)); + mtk_vcodec_debug(vpu, "- id=%X ret=%d", msg_id, err); + return err; +} + +int vpu_dec_init(struct vdec_vpu_inst *vpu) +{ + struct vdec_ap_ipi_init msg; + int err; + + mtk_vcodec_debug_enter(vpu); + + init_waitqueue_head(&vpu->wq); + vpu->handler = vpu_dec_ipi_handler; + + err = mtk_vcodec_fw_ipi_register(vpu->ctx->dev->fw_handler, vpu->id, + vpu->handler, "vdec", NULL); + if (err) { + mtk_vcodec_err(vpu, "vpu_ipi_register fail status=%d", err); + return err; + } + + if (vpu->ctx->dev->vdec_pdata->hw_arch == MTK_VDEC_LAT_SINGLE_CORE) { + err = mtk_vcodec_fw_ipi_register(vpu->ctx->dev->fw_handler, + vpu->core_id, vpu->handler, + "vdec", NULL); + if (err) { + mtk_vcodec_err(vpu, "vpu_ipi_register core fail status=%d", err); + return err; + } + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_id = AP_IPIMSG_DEC_INIT; + msg.ap_inst_addr = (unsigned long)vpu; + msg.codec_type = vpu->codec_type; + + mtk_vcodec_debug(vpu, "vdec_inst=%p", vpu); + + err = vcodec_vpu_send_msg(vpu, (void *)&msg, sizeof(msg)); + mtk_vcodec_debug(vpu, "- ret=%d", err); + return err; +} + +int vpu_dec_start(struct vdec_vpu_inst *vpu, uint32_t *data, unsigned int len) +{ + struct vdec_ap_ipi_dec_start msg; + int i; + int err = 0; + + mtk_vcodec_debug_enter(vpu); + + if (len > ARRAY_SIZE(msg.data)) { + mtk_vcodec_err(vpu, "invalid len = %d\n", len); + return -EINVAL; + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_id = AP_IPIMSG_DEC_START; + if (vpu->fw_abi_version < 2) + msg.vpu_inst_addr = vpu->inst_addr; + else + msg.inst_id = vpu->inst_id; + + for (i = 0; i < len; i++) + msg.data[i] = data[i]; + msg.codec_type = vpu->codec_type; + + err = vcodec_vpu_send_msg(vpu, (void *)&msg, sizeof(msg)); + mtk_vcodec_debug(vpu, "- ret=%d", err); + return err; +} + +int vpu_dec_get_param(struct vdec_vpu_inst *vpu, uint32_t *data, + unsigned int len, unsigned int param_type) +{ + struct vdec_ap_ipi_get_param msg; + int err; + + mtk_vcodec_debug_enter(vpu); + + if (len > ARRAY_SIZE(msg.data)) { + mtk_vcodec_err(vpu, "invalid len = %d\n", len); + return -EINVAL; + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_id = AP_IPIMSG_DEC_GET_PARAM; + msg.inst_id = vpu->inst_id; + memcpy(msg.data, data, sizeof(unsigned int) * len); + msg.param_type = param_type; + msg.codec_type = vpu->codec_type; + + err = vcodec_vpu_send_msg(vpu, (void *)&msg, sizeof(msg)); + mtk_vcodec_debug(vpu, "- ret=%d", err); + return err; +} + +int vpu_dec_core(struct vdec_vpu_inst *vpu) +{ + return vcodec_send_ap_ipi(vpu, AP_IPIMSG_DEC_CORE); +} + +int vpu_dec_end(struct vdec_vpu_inst *vpu) +{ + return vcodec_send_ap_ipi(vpu, AP_IPIMSG_DEC_END); +} + +int vpu_dec_core_end(struct vdec_vpu_inst *vpu) +{ + return vcodec_send_ap_ipi(vpu, AP_IPIMSG_DEC_CORE_END); +} + +int vpu_dec_deinit(struct vdec_vpu_inst *vpu) +{ + return vcodec_send_ap_ipi(vpu, AP_IPIMSG_DEC_DEINIT); +} + +int vpu_dec_reset(struct vdec_vpu_inst *vpu) +{ + return vcodec_send_ap_ipi(vpu, AP_IPIMSG_DEC_RESET); +} diff --git a/drivers/media/platform/mediatek/vcodec/vdec_vpu_if.h b/drivers/media/platform/mediatek/vcodec/vdec_vpu_if.h new file mode 100644 index 000000000..0436bba91 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/vdec_vpu_if.h @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PC Chen + */ + +#ifndef _VDEC_VPU_IF_H_ +#define _VDEC_VPU_IF_H_ + +#include "mtk_vcodec_fw.h" + +struct mtk_vcodec_ctx; + +/** + * struct vdec_vpu_inst - VPU instance for video codec + * @id : ipi msg id for each decoder + * @core_id : core id used to separate different hardware + * @vsi : driver structure allocated by VPU side and shared to AP side + * for control and info share + * @failure : VPU execution result status, 0: success, others: fail + * @inst_addr : VPU decoder instance address + * @fw_abi_version : ABI version of the firmware. + * @inst_id : if fw_abi_version >= 2, contains the instance ID to be given + * in place of inst_addr in messages. + * @signaled : 1 - Host has received ack message from VPU, 0 - not received + * @ctx : context for v4l2 layer integration + * @dev : platform device of VPU + * @wq : wait queue to wait VPU message ack + * @handler : ipi handler for each decoder + * @codec_type : use codec type to separate different codecs + * @capture_type: used capture type to separate different capture format + * @fb_sz : frame buffer size of each plane + */ +struct vdec_vpu_inst { + int id; + int core_id; + void *vsi; + int32_t failure; + uint32_t inst_addr; + uint32_t fw_abi_version; + uint32_t inst_id; + unsigned int signaled; + struct mtk_vcodec_ctx *ctx; + wait_queue_head_t wq; + mtk_vcodec_ipi_handler handler; + unsigned int codec_type; + unsigned int capture_type; + unsigned int fb_sz[2]; +}; + +/** + * vpu_dec_init - init decoder instance and allocate required resource in VPU. + * + * @vpu: instance for vdec_vpu_inst + */ +int vpu_dec_init(struct vdec_vpu_inst *vpu); + +/** + * vpu_dec_start - start decoding, basically the function will be invoked once + * every frame. + * + * @vpu : instance for vdec_vpu_inst + * @data: meta data to pass bitstream info to VPU decoder + * @len : meta data length + */ +int vpu_dec_start(struct vdec_vpu_inst *vpu, uint32_t *data, unsigned int len); + +/** + * vpu_dec_end - end decoding, basically the function will be invoked once + * when HW decoding done interrupt received successfully. The + * decoder in VPU will continue to do reference frame management + * and check if there is a new decoded frame available to display. + * + * @vpu : instance for vdec_vpu_inst + */ +int vpu_dec_end(struct vdec_vpu_inst *vpu); + +/** + * vpu_dec_deinit - deinit decoder instance and resource freed in VPU. + * + * @vpu: instance for vdec_vpu_inst + */ +int vpu_dec_deinit(struct vdec_vpu_inst *vpu); + +/** + * vpu_dec_reset - reset decoder, use for flush decoder when end of stream or + * seek. Remainig non displayed frame will be pushed to display. + * + * @vpu: instance for vdec_vpu_inst + */ +int vpu_dec_reset(struct vdec_vpu_inst *vpu); + +/** + * vpu_dec_core - core start decoding, basically the function will be invoked once + * every frame. + * + * @vpu : instance for vdec_vpu_inst + */ +int vpu_dec_core(struct vdec_vpu_inst *vpu); + +/** + * vpu_dec_core_end - core end decoding, basically the function will be invoked once + * when core HW decoding done and receive interrupt successfully. The + * decoder in VPU will updata hardware information and deinit hardware + * and check if there is a new decoded frame available to display. + * + * @vpu : instance for vdec_vpu_inst + */ +int vpu_dec_core_end(struct vdec_vpu_inst *vpu); + +/** + * vpu_dec_get_param - get param from scp + * + * @vpu : instance for vdec_vpu_inst + * @data: meta data to pass bitstream info to VPU decoder + * @len : meta data length + * @param_type : get param type + */ +int vpu_dec_get_param(struct vdec_vpu_inst *vpu, uint32_t *data, + unsigned int len, unsigned int param_type); + +#endif diff --git a/drivers/media/platform/mediatek/vcodec/venc/venc_h264_if.c b/drivers/media/platform/mediatek/vcodec/venc/venc_h264_if.c new file mode 100644 index 000000000..13c4f860f --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/venc/venc_h264_if.c @@ -0,0 +1,842 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Jungchang Tsao + * Daniel Hsiao + * PoChun Lin + */ + +#include +#include +#include + +#include "../mtk_vcodec_drv.h" +#include "../mtk_vcodec_util.h" +#include "../mtk_vcodec_intr.h" +#include "../mtk_vcodec_enc.h" +#include "../mtk_vcodec_enc_pm.h" +#include "../venc_drv_base.h" +#include "../venc_ipi_msg.h" +#include "../venc_vpu_if.h" + +static const char h264_filler_marker[] = {0x0, 0x0, 0x0, 0x1, 0xc}; + +#define H264_FILLER_MARKER_SIZE ARRAY_SIZE(h264_filler_marker) +#define VENC_PIC_BITSTREAM_BYTE_CNT 0x0098 + +/* + * enum venc_h264_frame_type - h264 encoder output bitstream frame type + */ +enum venc_h264_frame_type { + VENC_H264_IDR_FRM, + VENC_H264_I_FRM, + VENC_H264_P_FRM, + VENC_H264_B_FRM, +}; + +/* + * enum venc_h264_vpu_work_buf - h264 encoder buffer index + */ +enum venc_h264_vpu_work_buf { + VENC_H264_VPU_WORK_BUF_RC_INFO, + VENC_H264_VPU_WORK_BUF_RC_CODE, + VENC_H264_VPU_WORK_BUF_REC_LUMA, + VENC_H264_VPU_WORK_BUF_REC_CHROMA, + VENC_H264_VPU_WORK_BUF_REF_LUMA, + VENC_H264_VPU_WORK_BUF_REF_CHROMA, + VENC_H264_VPU_WORK_BUF_MV_INFO_1, + VENC_H264_VPU_WORK_BUF_MV_INFO_2, + VENC_H264_VPU_WORK_BUF_SKIP_FRAME, + VENC_H264_VPU_WORK_BUF_MAX, +}; + +/* + * enum venc_h264_bs_mode - for bs_mode argument in h264_enc_vpu_encode + */ +enum venc_h264_bs_mode { + H264_BS_MODE_SPS, + H264_BS_MODE_PPS, + H264_BS_MODE_FRAME, +}; + +/* + * struct venc_h264_vpu_config - Structure for h264 encoder configuration + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @input_fourcc: input fourcc + * @bitrate: target bitrate (in bps) + * @pic_w: picture width. Picture size is visible stream resolution, in pixels, + * to be used for display purposes; must be smaller or equal to buffer + * size. + * @pic_h: picture height + * @buf_w: buffer width. Buffer size is stream resolution in pixels aligned to + * hardware requirements. + * @buf_h: buffer height + * @gop_size: group of picture size (idr frame) + * @intra_period: intra frame period + * @framerate: frame rate in fps + * @profile: as specified in standard + * @level: as specified in standard + * @wfd: WFD mode 1:on, 0:off + */ +struct venc_h264_vpu_config { + u32 input_fourcc; + u32 bitrate; + u32 pic_w; + u32 pic_h; + u32 buf_w; + u32 buf_h; + u32 gop_size; + u32 intra_period; + u32 framerate; + u32 profile; + u32 level; + u32 wfd; +}; + +/* + * struct venc_h264_vpu_buf - Structure for buffer information + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @iova: IO virtual address + * @vpua: VPU side memory addr which is used by RC_CODE + * @size: buffer size (in bytes) + */ +struct venc_h264_vpu_buf { + u32 iova; + u32 vpua; + u32 size; +}; + +/* + * struct venc_h264_vsi - Structure for VPU driver control and info share + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * This structure is allocated in VPU side and shared to AP side. + * @config: h264 encoder configuration + * @work_bufs: working buffer information in VPU side + * The work_bufs here is for storing the 'size' info shared to AP side. + * The similar item in struct venc_h264_inst is for memory allocation + * in AP side. The AP driver will copy the 'size' from here to the one in + * struct mtk_vcodec_mem, then invoke mtk_vcodec_mem_alloc to allocate + * the buffer. After that, bypass the 'dma_addr' to the 'iova' field here for + * register setting in VPU side. + */ +struct venc_h264_vsi { + struct venc_h264_vpu_config config; + struct venc_h264_vpu_buf work_bufs[VENC_H264_VPU_WORK_BUF_MAX]; +}; + +/** + * struct venc_h264_vpu_config_ext - Structure for h264 encoder configuration + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @input_fourcc: input fourcc + * @bitrate: target bitrate (in bps) + * @pic_w: picture width. Picture size is visible stream resolution, in pixels, + * to be used for display purposes; must be smaller or equal to buffer + * size. + * @pic_h: picture height + * @buf_w: buffer width. Buffer size is stream resolution in pixels aligned to + * hardware requirements. + * @buf_h: buffer height + * @gop_size: group of picture size (idr frame) + * @intra_period: intra frame period + * @framerate: frame rate in fps + * @profile: as specified in standard + * @level: as specified in standard + * @wfd: WFD mode 1:on, 0:off + * @max_qp: max quant parameter + * @min_qp: min quant parameter + * @reserved: reserved configs + */ +struct venc_h264_vpu_config_ext { + u32 input_fourcc; + u32 bitrate; + u32 pic_w; + u32 pic_h; + u32 buf_w; + u32 buf_h; + u32 gop_size; + u32 intra_period; + u32 framerate; + u32 profile; + u32 level; + u32 wfd; + u32 max_qp; + u32 min_qp; + u32 reserved[8]; +}; + +/** + * struct venc_h264_vpu_buf_34 - Structure for 34-bit buffer information + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @iova: 34-bit IO virtual address + * @vpua: VPU side memory addr which is used by RC_CODE + * @size: buffer size (in bytes) + */ +struct venc_h264_vpu_buf_34 { + u64 iova; + u32 vpua; + u32 size; +}; + +/** + * struct venc_h264_vsi_34 - Structure for VPU driver control and info share + * Used for 34-bit iova sharing + * @config: h264 encoder configuration + * @work_bufs: working buffer information in VPU side + */ +struct venc_h264_vsi_34 { + struct venc_h264_vpu_config_ext config; + struct venc_h264_vpu_buf_34 work_bufs[VENC_H264_VPU_WORK_BUF_MAX]; +}; + +/* + * struct venc_h264_inst - h264 encoder AP driver instance + * @hw_base: h264 encoder hardware register base + * @work_bufs: working buffer + * @pps_buf: buffer to store the pps bitstream + * @work_buf_allocated: working buffer allocated flag + * @frm_cnt: encoded frame count + * @prepend_hdr: when the v4l2 layer send VENC_SET_PARAM_PREPEND_HEADER cmd + * through h264_enc_set_param interface, it will set this flag and prepend the + * sps/pps in h264_enc_encode function. + * @vpu_inst: VPU instance to exchange information between AP and VPU + * @vsi: driver structure allocated by VPU side and shared to AP side for + * control and info share + * @vsi_34: driver structure allocated by VPU side and shared to AP side for + * control and info share, used for 34-bit iova sharing. + * @ctx: context for v4l2 layer integration + */ +struct venc_h264_inst { + void __iomem *hw_base; + struct mtk_vcodec_mem work_bufs[VENC_H264_VPU_WORK_BUF_MAX]; + struct mtk_vcodec_mem pps_buf; + bool work_buf_allocated; + unsigned int frm_cnt; + unsigned int skip_frm_cnt; + unsigned int prepend_hdr; + struct venc_vpu_inst vpu_inst; + struct venc_h264_vsi *vsi; + struct venc_h264_vsi_34 *vsi_34; + struct mtk_vcodec_ctx *ctx; +}; + +static inline u32 h264_read_reg(struct venc_h264_inst *inst, u32 addr) +{ + return readl(inst->hw_base + addr); +} + +static unsigned int h264_get_profile(struct venc_h264_inst *inst, + unsigned int profile) +{ + switch (profile) { + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + return 66; + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + return 77; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + return 100; + case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE: + mtk_vcodec_err(inst, "unsupported CONSTRAINED_BASELINE"); + return 0; + case V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED: + mtk_vcodec_err(inst, "unsupported EXTENDED"); + return 0; + default: + mtk_vcodec_debug(inst, "unsupported profile %d", profile); + return 100; + } +} + +static unsigned int h264_get_level(struct venc_h264_inst *inst, + unsigned int level) +{ + switch (level) { + case V4L2_MPEG_VIDEO_H264_LEVEL_1B: + mtk_vcodec_err(inst, "unsupported 1B"); + return 0; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_0: + return 10; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_1: + return 11; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_2: + return 12; + case V4L2_MPEG_VIDEO_H264_LEVEL_1_3: + return 13; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_0: + return 20; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_1: + return 21; + case V4L2_MPEG_VIDEO_H264_LEVEL_2_2: + return 22; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_0: + return 30; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_1: + return 31; + case V4L2_MPEG_VIDEO_H264_LEVEL_3_2: + return 32; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_0: + return 40; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_1: + return 41; + case V4L2_MPEG_VIDEO_H264_LEVEL_4_2: + return 42; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_0: + return 50; + case V4L2_MPEG_VIDEO_H264_LEVEL_5_1: + return 51; + default: + mtk_vcodec_debug(inst, "unsupported level %d", level); + return 31; + } +} + +static void h264_enc_free_work_buf(struct venc_h264_inst *inst) +{ + int i; + + mtk_vcodec_debug_enter(inst); + + /* Except the SKIP_FRAME buffers, + * other buffers need to be freed by AP. + */ + for (i = 0; i < VENC_H264_VPU_WORK_BUF_MAX; i++) { + if (i != VENC_H264_VPU_WORK_BUF_SKIP_FRAME) + mtk_vcodec_mem_free(inst->ctx, &inst->work_bufs[i]); + } + + mtk_vcodec_mem_free(inst->ctx, &inst->pps_buf); + + mtk_vcodec_debug_leave(inst); +} + +static int h264_enc_alloc_work_buf(struct venc_h264_inst *inst, bool is_34bit) +{ + struct venc_h264_vpu_buf *wb = NULL; + struct venc_h264_vpu_buf_34 *wb_34 = NULL; + int i; + u32 vpua, wb_size; + int ret = 0; + + mtk_vcodec_debug_enter(inst); + + if (is_34bit) + wb_34 = inst->vsi_34->work_bufs; + else + wb = inst->vsi->work_bufs; + + for (i = 0; i < VENC_H264_VPU_WORK_BUF_MAX; i++) { + /* + * This 'wb' structure is set by VPU side and shared to AP for + * buffer allocation and IO virtual addr mapping. For most of + * the buffers, AP will allocate the buffer according to 'size' + * field and store the IO virtual addr in 'iova' field. There + * are two exceptions: + * (1) RC_CODE buffer, it's pre-allocated in the VPU side, and + * save the VPU addr in the 'vpua' field. The AP will translate + * the VPU addr to the corresponding IO virtual addr and store + * in 'iova' field for reg setting in VPU side. + * (2) SKIP_FRAME buffer, it's pre-allocated in the VPU side, + * and save the VPU addr in the 'vpua' field. The AP will + * translate the VPU addr to the corresponding AP side virtual + * address and do some memcpy access to move to bitstream buffer + * assigned by v4l2 layer. + */ + if (is_34bit) { + inst->work_bufs[i].size = wb_34[i].size; + vpua = wb_34[i].vpua; + wb_size = wb_34[i].size; + } else { + inst->work_bufs[i].size = wb[i].size; + vpua = wb[i].vpua; + wb_size = wb[i].size; + } + + if (i == VENC_H264_VPU_WORK_BUF_SKIP_FRAME) { + struct mtk_vcodec_fw *handler; + + handler = inst->vpu_inst.ctx->dev->fw_handler; + inst->work_bufs[i].va = + mtk_vcodec_fw_map_dm_addr(handler, vpua); + inst->work_bufs[i].dma_addr = 0; + } else { + ret = mtk_vcodec_mem_alloc(inst->ctx, + &inst->work_bufs[i]); + if (ret) { + mtk_vcodec_err(inst, + "cannot allocate buf %d", i); + goto err_alloc; + } + /* + * This RC_CODE is pre-allocated by VPU and saved in VPU + * addr. So we need use memcpy to copy RC_CODE from VPU + * addr into IO virtual addr in 'iova' field for reg + * setting in VPU side. + */ + if (i == VENC_H264_VPU_WORK_BUF_RC_CODE) { + struct mtk_vcodec_fw *handler; + void *tmp_va; + + handler = inst->vpu_inst.ctx->dev->fw_handler; + tmp_va = mtk_vcodec_fw_map_dm_addr(handler, + vpua); + memcpy(inst->work_bufs[i].va, tmp_va, wb_size); + } + } + if (is_34bit) + wb_34[i].iova = inst->work_bufs[i].dma_addr; + else + wb[i].iova = inst->work_bufs[i].dma_addr; + + mtk_vcodec_debug(inst, + "work_buf[%d] va=0x%p iova=%pad size=%zu", + i, inst->work_bufs[i].va, + &inst->work_bufs[i].dma_addr, + inst->work_bufs[i].size); + } + + /* the pps_buf is used by AP side only */ + inst->pps_buf.size = 128; + ret = mtk_vcodec_mem_alloc(inst->ctx, &inst->pps_buf); + if (ret) { + mtk_vcodec_err(inst, "cannot allocate pps_buf"); + goto err_alloc; + } + + mtk_vcodec_debug_leave(inst); + + return ret; + +err_alloc: + h264_enc_free_work_buf(inst); + + return ret; +} + +static unsigned int h264_enc_wait_venc_done(struct venc_h264_inst *inst) +{ + unsigned int irq_status = 0; + struct mtk_vcodec_ctx *ctx = (struct mtk_vcodec_ctx *)inst->ctx; + + if (!mtk_vcodec_wait_for_done_ctx(ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, 0)) { + irq_status = ctx->irq_status; + mtk_vcodec_debug(inst, "irq_status %x <-", irq_status); + } + return irq_status; +} + +static int h264_frame_type(unsigned int frm_cnt, unsigned int gop_size, + unsigned int intra_period) +{ + if ((gop_size != 0 && (frm_cnt % gop_size) == 0) || + (frm_cnt == 0 && gop_size == 0)) { + /* IDR frame */ + return VENC_H264_IDR_FRM; + } else if ((intra_period != 0 && (frm_cnt % intra_period) == 0) || + (frm_cnt == 0 && intra_period == 0)) { + /* I frame */ + return VENC_H264_I_FRM; + } else { + return VENC_H264_P_FRM; /* Note: B frames are not supported */ + } +} + +static int h264_encode_sps(struct venc_h264_inst *inst, + struct mtk_vcodec_mem *bs_buf, + unsigned int *bs_size) +{ + int ret = 0; + unsigned int irq_status; + + mtk_vcodec_debug_enter(inst); + + ret = vpu_enc_encode(&inst->vpu_inst, H264_BS_MODE_SPS, NULL, bs_buf, NULL); + if (ret) + return ret; + + irq_status = h264_enc_wait_venc_done(inst); + if (irq_status != MTK_VENC_IRQ_STATUS_SPS) { + mtk_vcodec_err(inst, "expect irq status %d", + MTK_VENC_IRQ_STATUS_SPS); + return -EINVAL; + } + + *bs_size = h264_read_reg(inst, VENC_PIC_BITSTREAM_BYTE_CNT); + mtk_vcodec_debug(inst, "bs size %d <-", *bs_size); + + return ret; +} + +static int h264_encode_pps(struct venc_h264_inst *inst, + struct mtk_vcodec_mem *bs_buf, + unsigned int *bs_size) +{ + int ret = 0; + unsigned int irq_status; + + mtk_vcodec_debug_enter(inst); + + ret = vpu_enc_encode(&inst->vpu_inst, H264_BS_MODE_PPS, NULL, bs_buf, NULL); + if (ret) + return ret; + + irq_status = h264_enc_wait_venc_done(inst); + if (irq_status != MTK_VENC_IRQ_STATUS_PPS) { + mtk_vcodec_err(inst, "expect irq status %d", + MTK_VENC_IRQ_STATUS_PPS); + return -EINVAL; + } + + *bs_size = h264_read_reg(inst, VENC_PIC_BITSTREAM_BYTE_CNT); + mtk_vcodec_debug(inst, "bs size %d <-", *bs_size); + + return ret; +} + +static int h264_encode_header(struct venc_h264_inst *inst, + struct mtk_vcodec_mem *bs_buf, + unsigned int *bs_size) +{ + int ret = 0; + unsigned int bs_size_sps; + unsigned int bs_size_pps; + + ret = h264_encode_sps(inst, bs_buf, &bs_size_sps); + if (ret) + return ret; + + ret = h264_encode_pps(inst, &inst->pps_buf, &bs_size_pps); + if (ret) + return ret; + + memcpy(bs_buf->va + bs_size_sps, inst->pps_buf.va, bs_size_pps); + *bs_size = bs_size_sps + bs_size_pps; + + return ret; +} + +static int h264_encode_frame(struct venc_h264_inst *inst, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + unsigned int *bs_size) +{ + int ret = 0; + unsigned int gop_size; + unsigned int intra_period; + unsigned int irq_status; + struct venc_frame_info frame_info; + struct mtk_vcodec_ctx *ctx = inst->ctx; + + mtk_vcodec_debug_enter(inst); + mtk_vcodec_debug(inst, "frm_cnt = %d\n ", inst->frm_cnt); + + if (MTK_ENC_IOVA_IS_34BIT(ctx)) { + gop_size = inst->vsi_34->config.gop_size; + intra_period = inst->vsi_34->config.intra_period; + } else { + gop_size = inst->vsi->config.gop_size; + intra_period = inst->vsi->config.intra_period; + } + frame_info.frm_count = inst->frm_cnt; + frame_info.skip_frm_count = inst->skip_frm_cnt; + frame_info.frm_type = h264_frame_type(inst->frm_cnt, gop_size, + intra_period); + mtk_vcodec_debug(inst, "frm_count = %d,skip_frm_count =%d,frm_type=%d.\n", + frame_info.frm_count, frame_info.skip_frm_count, + frame_info.frm_type); + + ret = vpu_enc_encode(&inst->vpu_inst, H264_BS_MODE_FRAME, + frm_buf, bs_buf, &frame_info); + if (ret) + return ret; + + /* + * skip frame case: The skip frame buffer is composed by vpu side only, + * it does not trigger the hw, so skip the wait interrupt operation. + */ + if (inst->vpu_inst.state == VEN_IPI_MSG_ENC_STATE_SKIP) { + *bs_size = inst->vpu_inst.bs_size; + memcpy(bs_buf->va, + inst->work_bufs[VENC_H264_VPU_WORK_BUF_SKIP_FRAME].va, + *bs_size); + ++inst->frm_cnt; + ++inst->skip_frm_cnt; + return ret; + } + + irq_status = h264_enc_wait_venc_done(inst); + if (irq_status != MTK_VENC_IRQ_STATUS_FRM) { + mtk_vcodec_err(inst, "irq_status=%d failed", irq_status); + return -EIO; + } + + *bs_size = h264_read_reg(inst, VENC_PIC_BITSTREAM_BYTE_CNT); + + ++inst->frm_cnt; + mtk_vcodec_debug(inst, "frm %d bs_size %d key_frm %d <-", + inst->frm_cnt, *bs_size, inst->vpu_inst.is_key_frm); + + return ret; +} + +static void h264_encode_filler(struct venc_h264_inst *inst, void *buf, + int size) +{ + unsigned char *p = buf; + + if (size < H264_FILLER_MARKER_SIZE) { + mtk_vcodec_err(inst, "filler size too small %d", size); + return; + } + + memcpy(p, h264_filler_marker, ARRAY_SIZE(h264_filler_marker)); + size -= H264_FILLER_MARKER_SIZE; + p += H264_FILLER_MARKER_SIZE; + memset(p, 0xff, size); +} + +static int h264_enc_init(struct mtk_vcodec_ctx *ctx) +{ + const bool is_ext = MTK_ENC_CTX_IS_EXT(ctx); + int ret = 0; + struct venc_h264_inst *inst; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->ctx = ctx; + inst->vpu_inst.ctx = ctx; + inst->vpu_inst.id = is_ext ? SCP_IPI_VENC_H264 : IPI_VENC_H264; + inst->hw_base = mtk_vcodec_get_reg_addr(inst->ctx, VENC_SYS); + + mtk_vcodec_debug_enter(inst); + + ret = vpu_enc_init(&inst->vpu_inst); + + if (MTK_ENC_IOVA_IS_34BIT(ctx)) + inst->vsi_34 = (struct venc_h264_vsi_34 *)inst->vpu_inst.vsi; + else + inst->vsi = (struct venc_h264_vsi *)inst->vpu_inst.vsi; + + mtk_vcodec_debug_leave(inst); + + if (ret) + kfree(inst); + else + ctx->drv_handle = inst; + + return ret; +} + +static int h264_enc_encode(void *handle, + enum venc_start_opt opt, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_done_result *result) +{ + int ret = 0; + struct venc_h264_inst *inst = (struct venc_h264_inst *)handle; + struct mtk_vcodec_ctx *ctx = inst->ctx; + + mtk_vcodec_debug(inst, "opt %d ->", opt); + + enable_irq(ctx->dev->enc_irq); + + switch (opt) { + case VENC_START_OPT_ENCODE_SEQUENCE_HEADER: { + unsigned int bs_size_hdr; + + ret = h264_encode_header(inst, bs_buf, &bs_size_hdr); + if (ret) + goto encode_err; + + result->bs_size = bs_size_hdr; + result->is_key_frm = false; + break; + } + + case VENC_START_OPT_ENCODE_FRAME: { + int hdr_sz; + int hdr_sz_ext; + int filler_sz = 0; + const int bs_alignment = 128; + struct mtk_vcodec_mem tmp_bs_buf; + unsigned int bs_size_hdr; + unsigned int bs_size_frm; + + if (!inst->prepend_hdr) { + ret = h264_encode_frame(inst, frm_buf, bs_buf, + &result->bs_size); + if (ret) + goto encode_err; + result->is_key_frm = inst->vpu_inst.is_key_frm; + break; + } + + mtk_vcodec_debug(inst, "h264_encode_frame prepend SPS/PPS"); + + ret = h264_encode_header(inst, bs_buf, &bs_size_hdr); + if (ret) + goto encode_err; + + hdr_sz = bs_size_hdr; + hdr_sz_ext = (hdr_sz & (bs_alignment - 1)); + if (hdr_sz_ext) { + filler_sz = bs_alignment - hdr_sz_ext; + if (hdr_sz_ext + H264_FILLER_MARKER_SIZE > bs_alignment) + filler_sz += bs_alignment; + h264_encode_filler(inst, bs_buf->va + hdr_sz, + filler_sz); + } + + tmp_bs_buf.va = bs_buf->va + hdr_sz + filler_sz; + tmp_bs_buf.dma_addr = bs_buf->dma_addr + hdr_sz + filler_sz; + tmp_bs_buf.size = bs_buf->size - (hdr_sz + filler_sz); + + ret = h264_encode_frame(inst, frm_buf, &tmp_bs_buf, + &bs_size_frm); + if (ret) + goto encode_err; + + result->bs_size = hdr_sz + filler_sz + bs_size_frm; + + mtk_vcodec_debug(inst, "hdr %d filler %d frame %d bs %d", + hdr_sz, filler_sz, bs_size_frm, + result->bs_size); + + inst->prepend_hdr = 0; + result->is_key_frm = inst->vpu_inst.is_key_frm; + break; + } + + default: + mtk_vcodec_err(inst, "venc_start_opt %d not supported", opt); + ret = -EINVAL; + break; + } + +encode_err: + + disable_irq(ctx->dev->enc_irq); + mtk_vcodec_debug(inst, "opt %d <-", opt); + + return ret; +} + +static void h264_enc_set_vsi_configs(struct venc_h264_inst *inst, + struct venc_enc_param *enc_prm) +{ + inst->vsi->config.input_fourcc = enc_prm->input_yuv_fmt; + inst->vsi->config.bitrate = enc_prm->bitrate; + inst->vsi->config.pic_w = enc_prm->width; + inst->vsi->config.pic_h = enc_prm->height; + inst->vsi->config.buf_w = enc_prm->buf_width; + inst->vsi->config.buf_h = enc_prm->buf_height; + inst->vsi->config.gop_size = enc_prm->gop_size; + inst->vsi->config.framerate = enc_prm->frm_rate; + inst->vsi->config.intra_period = enc_prm->intra_period; + inst->vsi->config.profile = + h264_get_profile(inst, enc_prm->h264_profile); + inst->vsi->config.level = + h264_get_level(inst, enc_prm->h264_level); + inst->vsi->config.wfd = 0; +} + +static void h264_enc_set_vsi_34_configs(struct venc_h264_inst *inst, + struct venc_enc_param *enc_prm) +{ + inst->vsi_34->config.input_fourcc = enc_prm->input_yuv_fmt; + inst->vsi_34->config.bitrate = enc_prm->bitrate; + inst->vsi_34->config.pic_w = enc_prm->width; + inst->vsi_34->config.pic_h = enc_prm->height; + inst->vsi_34->config.buf_w = enc_prm->buf_width; + inst->vsi_34->config.buf_h = enc_prm->buf_height; + inst->vsi_34->config.gop_size = enc_prm->gop_size; + inst->vsi_34->config.framerate = enc_prm->frm_rate; + inst->vsi_34->config.intra_period = enc_prm->intra_period; + inst->vsi_34->config.profile = + h264_get_profile(inst, enc_prm->h264_profile); + inst->vsi_34->config.level = + h264_get_level(inst, enc_prm->h264_level); + inst->vsi_34->config.wfd = 0; +} + +static int h264_enc_set_param(void *handle, + enum venc_set_param_type type, + struct venc_enc_param *enc_prm) +{ + int ret = 0; + struct venc_h264_inst *inst = (struct venc_h264_inst *)handle; + struct mtk_vcodec_ctx *ctx = inst->ctx; + const bool is_34bit = MTK_ENC_IOVA_IS_34BIT(ctx); + + mtk_vcodec_debug(inst, "->type=%d", type); + + switch (type) { + case VENC_SET_PARAM_ENC: + if (is_34bit) + h264_enc_set_vsi_34_configs(inst, enc_prm); + else + h264_enc_set_vsi_configs(inst, enc_prm); + ret = vpu_enc_set_param(&inst->vpu_inst, type, enc_prm); + if (ret) + break; + if (inst->work_buf_allocated) { + h264_enc_free_work_buf(inst); + inst->work_buf_allocated = false; + } + ret = h264_enc_alloc_work_buf(inst, is_34bit); + if (ret) + break; + inst->work_buf_allocated = true; + break; + + case VENC_SET_PARAM_PREPEND_HEADER: + inst->prepend_hdr = 1; + mtk_vcodec_debug(inst, "set prepend header mode"); + break; + case VENC_SET_PARAM_FORCE_INTRA: + case VENC_SET_PARAM_GOP_SIZE: + case VENC_SET_PARAM_INTRA_PERIOD: + inst->frm_cnt = 0; + inst->skip_frm_cnt = 0; + fallthrough; + default: + ret = vpu_enc_set_param(&inst->vpu_inst, type, enc_prm); + break; + } + + mtk_vcodec_debug_leave(inst); + + return ret; +} + +static int h264_enc_deinit(void *handle) +{ + int ret = 0; + struct venc_h264_inst *inst = (struct venc_h264_inst *)handle; + + mtk_vcodec_debug_enter(inst); + + ret = vpu_enc_deinit(&inst->vpu_inst); + + if (inst->work_buf_allocated) + h264_enc_free_work_buf(inst); + + mtk_vcodec_debug_leave(inst); + kfree(inst); + + return ret; +} + +const struct venc_common_if venc_h264_if = { + .init = h264_enc_init, + .encode = h264_enc_encode, + .set_param = h264_enc_set_param, + .deinit = h264_enc_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/venc/venc_vp8_if.c b/drivers/media/platform/mediatek/vcodec/venc/venc_vp8_if.c new file mode 100644 index 000000000..56ce58f76 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/venc/venc_vp8_if.c @@ -0,0 +1,468 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Daniel Hsiao + * PoChun Lin + */ + +#include +#include +#include + +#include "../mtk_vcodec_drv.h" +#include "../mtk_vcodec_util.h" +#include "../mtk_vcodec_intr.h" +#include "../mtk_vcodec_enc.h" +#include "../mtk_vcodec_enc_pm.h" +#include "../venc_drv_base.h" +#include "../venc_ipi_msg.h" +#include "../venc_vpu_if.h" + +#define VENC_BITSTREAM_FRAME_SIZE 0x0098 +#define VENC_BITSTREAM_HEADER_LEN 0x00e8 + +/* This ac_tag is vp8 frame tag. */ +#define MAX_AC_TAG_SIZE 10 + +/* + * enum venc_vp8_vpu_work_buf - vp8 encoder buffer index + */ +enum venc_vp8_vpu_work_buf { + VENC_VP8_VPU_WORK_BUF_LUMA, + VENC_VP8_VPU_WORK_BUF_LUMA2, + VENC_VP8_VPU_WORK_BUF_LUMA3, + VENC_VP8_VPU_WORK_BUF_CHROMA, + VENC_VP8_VPU_WORK_BUF_CHROMA2, + VENC_VP8_VPU_WORK_BUF_CHROMA3, + VENC_VP8_VPU_WORK_BUF_MV_INFO, + VENC_VP8_VPU_WORK_BUF_BS_HEADER, + VENC_VP8_VPU_WORK_BUF_PROB_BUF, + VENC_VP8_VPU_WORK_BUF_RC_INFO, + VENC_VP8_VPU_WORK_BUF_RC_CODE, + VENC_VP8_VPU_WORK_BUF_RC_CODE2, + VENC_VP8_VPU_WORK_BUF_RC_CODE3, + VENC_VP8_VPU_WORK_BUF_MAX, +}; + +/* + * struct venc_vp8_vpu_config - Structure for vp8 encoder configuration + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @input_fourcc: input fourcc + * @bitrate: target bitrate (in bps) + * @pic_w: picture width. Picture size is visible stream resolution, in pixels, + * to be used for display purposes; must be smaller or equal to buffer + * size. + * @pic_h: picture height + * @buf_w: buffer width (with 16 alignment). Buffer size is stream resolution + * in pixels aligned to hardware requirements. + * @buf_h: buffer height (with 16 alignment) + * @gop_size: group of picture size (key frame) + * @framerate: frame rate in fps + * @ts_mode: temporal scalability mode (0: disable, 1: enable) + * support three temporal layers - 0: 7.5fps 1: 7.5fps 2: 15fps. + */ +struct venc_vp8_vpu_config { + u32 input_fourcc; + u32 bitrate; + u32 pic_w; + u32 pic_h; + u32 buf_w; + u32 buf_h; + u32 gop_size; + u32 framerate; + u32 ts_mode; +}; + +/* + * struct venc_vp8_vpu_buf - Structure for buffer information + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * @iova: IO virtual address + * @vpua: VPU side memory addr which is used by RC_CODE + * @size: buffer size (in bytes) + */ +struct venc_vp8_vpu_buf { + u32 iova; + u32 vpua; + u32 size; +}; + +/* + * struct venc_vp8_vsi - Structure for VPU driver control and info share + * AP-W/R : AP is writer/reader on this item + * VPU-W/R: VPU is write/reader on this item + * This structure is allocated in VPU side and shared to AP side. + * @config: vp8 encoder configuration + * @work_bufs: working buffer information in VPU side + * The work_bufs here is for storing the 'size' info shared to AP side. + * The similar item in struct venc_vp8_inst is for memory allocation + * in AP side. The AP driver will copy the 'size' from here to the one in + * struct mtk_vcodec_mem, then invoke mtk_vcodec_mem_alloc to allocate + * the buffer. After that, bypass the 'dma_addr' to the 'iova' field here for + * register setting in VPU side. + */ +struct venc_vp8_vsi { + struct venc_vp8_vpu_config config; + struct venc_vp8_vpu_buf work_bufs[VENC_VP8_VPU_WORK_BUF_MAX]; +}; + +/* + * struct venc_vp8_inst - vp8 encoder AP driver instance + * @hw_base: vp8 encoder hardware register base + * @work_bufs: working buffer + * @work_buf_allocated: working buffer allocated flag + * @frm_cnt: encoded frame count, it's used for I-frame judgement and + * reset when force intra cmd received. + * @ts_mode: temporal scalability mode (0: disable, 1: enable) + * support three temporal layers - 0: 7.5fps 1: 7.5fps 2: 15fps. + * @vpu_inst: VPU instance to exchange information between AP and VPU + * @vsi: driver structure allocated by VPU side and shared to AP side for + * control and info share + * @ctx: context for v4l2 layer integration + */ +struct venc_vp8_inst { + void __iomem *hw_base; + struct mtk_vcodec_mem work_bufs[VENC_VP8_VPU_WORK_BUF_MAX]; + bool work_buf_allocated; + unsigned int frm_cnt; + unsigned int ts_mode; + struct venc_vpu_inst vpu_inst; + struct venc_vp8_vsi *vsi; + struct mtk_vcodec_ctx *ctx; +}; + +static inline u32 vp8_enc_read_reg(struct venc_vp8_inst *inst, u32 addr) +{ + return readl(inst->hw_base + addr); +} + +static void vp8_enc_free_work_buf(struct venc_vp8_inst *inst) +{ + int i; + + mtk_vcodec_debug_enter(inst); + + /* Buffers need to be freed by AP. */ + for (i = 0; i < VENC_VP8_VPU_WORK_BUF_MAX; i++) { + if (inst->work_bufs[i].size == 0) + continue; + mtk_vcodec_mem_free(inst->ctx, &inst->work_bufs[i]); + } + + mtk_vcodec_debug_leave(inst); +} + +static int vp8_enc_alloc_work_buf(struct venc_vp8_inst *inst) +{ + int i; + int ret = 0; + struct venc_vp8_vpu_buf *wb = inst->vsi->work_bufs; + + mtk_vcodec_debug_enter(inst); + + for (i = 0; i < VENC_VP8_VPU_WORK_BUF_MAX; i++) { + if (wb[i].size == 0) + continue; + /* + * This 'wb' structure is set by VPU side and shared to AP for + * buffer allocation and IO virtual addr mapping. For most of + * the buffers, AP will allocate the buffer according to 'size' + * field and store the IO virtual addr in 'iova' field. For the + * RC_CODEx buffers, they are pre-allocated in the VPU side + * because they are inside VPU SRAM, and save the VPU addr in + * the 'vpua' field. The AP will translate the VPU addr to the + * corresponding IO virtual addr and store in 'iova' field. + */ + inst->work_bufs[i].size = wb[i].size; + ret = mtk_vcodec_mem_alloc(inst->ctx, &inst->work_bufs[i]); + if (ret) { + mtk_vcodec_err(inst, + "cannot alloc work_bufs[%d]", i); + goto err_alloc; + } + /* + * This RC_CODEx is pre-allocated by VPU and saved in VPU addr. + * So we need use memcpy to copy RC_CODEx from VPU addr into IO + * virtual addr in 'iova' field for reg setting in VPU side. + */ + if (i == VENC_VP8_VPU_WORK_BUF_RC_CODE || + i == VENC_VP8_VPU_WORK_BUF_RC_CODE2 || + i == VENC_VP8_VPU_WORK_BUF_RC_CODE3) { + struct mtk_vcodec_fw *handler; + void *tmp_va; + + handler = inst->vpu_inst.ctx->dev->fw_handler; + tmp_va = mtk_vcodec_fw_map_dm_addr(handler, + wb[i].vpua); + memcpy(inst->work_bufs[i].va, tmp_va, wb[i].size); + } + wb[i].iova = inst->work_bufs[i].dma_addr; + + mtk_vcodec_debug(inst, + "work_bufs[%d] va=0x%p,iova=%pad,size=%zu", + i, inst->work_bufs[i].va, + &inst->work_bufs[i].dma_addr, + inst->work_bufs[i].size); + } + + mtk_vcodec_debug_leave(inst); + + return ret; + +err_alloc: + vp8_enc_free_work_buf(inst); + + return ret; +} + +static unsigned int vp8_enc_wait_venc_done(struct venc_vp8_inst *inst) +{ + unsigned int irq_status = 0; + struct mtk_vcodec_ctx *ctx = (struct mtk_vcodec_ctx *)inst->ctx; + + if (!mtk_vcodec_wait_for_done_ctx(ctx, MTK_INST_IRQ_RECEIVED, + WAIT_INTR_TIMEOUT_MS, 0)) { + irq_status = ctx->irq_status; + mtk_vcodec_debug(inst, "isr return %x", irq_status); + } + return irq_status; +} + +/* + * Compose ac_tag, bitstream header and bitstream payload into + * one bitstream buffer. + */ +static int vp8_enc_compose_one_frame(struct venc_vp8_inst *inst, + struct mtk_vcodec_mem *bs_buf, + unsigned int *bs_size) +{ + unsigned int not_key; + u32 bs_frm_size; + u32 bs_hdr_len; + unsigned int ac_tag_size; + u8 ac_tag[MAX_AC_TAG_SIZE]; + u32 tag; + + bs_frm_size = vp8_enc_read_reg(inst, VENC_BITSTREAM_FRAME_SIZE); + bs_hdr_len = vp8_enc_read_reg(inst, VENC_BITSTREAM_HEADER_LEN); + + /* if a frame is key frame, not_key is 0 */ + not_key = !inst->vpu_inst.is_key_frm; + tag = (bs_hdr_len << 5) | 0x10 | not_key; + ac_tag[0] = tag & 0xff; + ac_tag[1] = (tag >> 8) & 0xff; + ac_tag[2] = (tag >> 16) & 0xff; + + /* key frame */ + if (not_key == 0) { + ac_tag_size = MAX_AC_TAG_SIZE; + ac_tag[3] = 0x9d; + ac_tag[4] = 0x01; + ac_tag[5] = 0x2a; + ac_tag[6] = inst->vsi->config.pic_w; + ac_tag[7] = inst->vsi->config.pic_w >> 8; + ac_tag[8] = inst->vsi->config.pic_h; + ac_tag[9] = inst->vsi->config.pic_h >> 8; + } else { + ac_tag_size = 3; + } + + if (bs_buf->size < bs_hdr_len + bs_frm_size + ac_tag_size) { + mtk_vcodec_err(inst, "bitstream buf size is too small(%zu)", + bs_buf->size); + return -EINVAL; + } + + /* + * (1) The vp8 bitstream header and body are generated by the HW vp8 + * encoder separately at the same time. We cannot know the bitstream + * header length in advance. + * (2) From the vp8 spec, there is no stuffing byte allowed between the + * ac tag, bitstream header and bitstream body. + */ + memmove(bs_buf->va + bs_hdr_len + ac_tag_size, + bs_buf->va, bs_frm_size); + memcpy(bs_buf->va + ac_tag_size, + inst->work_bufs[VENC_VP8_VPU_WORK_BUF_BS_HEADER].va, + bs_hdr_len); + memcpy(bs_buf->va, ac_tag, ac_tag_size); + *bs_size = bs_frm_size + bs_hdr_len + ac_tag_size; + + return 0; +} + +static int vp8_enc_encode_frame(struct venc_vp8_inst *inst, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + unsigned int *bs_size) +{ + int ret = 0; + unsigned int irq_status; + + mtk_vcodec_debug(inst, "->frm_cnt=%d", inst->frm_cnt); + + ret = vpu_enc_encode(&inst->vpu_inst, 0, frm_buf, bs_buf, NULL); + if (ret) + return ret; + + irq_status = vp8_enc_wait_venc_done(inst); + if (irq_status != MTK_VENC_IRQ_STATUS_FRM) { + mtk_vcodec_err(inst, "irq_status=%d failed", irq_status); + return -EIO; + } + + if (vp8_enc_compose_one_frame(inst, bs_buf, bs_size)) { + mtk_vcodec_err(inst, "vp8_enc_compose_one_frame failed"); + return -EINVAL; + } + + inst->frm_cnt++; + mtk_vcodec_debug(inst, "<-size=%d key_frm=%d", *bs_size, + inst->vpu_inst.is_key_frm); + + return ret; +} + +static int vp8_enc_init(struct mtk_vcodec_ctx *ctx) +{ + int ret = 0; + struct venc_vp8_inst *inst; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + inst->ctx = ctx; + inst->vpu_inst.ctx = ctx; + inst->vpu_inst.id = IPI_VENC_VP8; + inst->hw_base = mtk_vcodec_get_reg_addr(inst->ctx, VENC_LT_SYS); + + mtk_vcodec_debug_enter(inst); + + ret = vpu_enc_init(&inst->vpu_inst); + + inst->vsi = (struct venc_vp8_vsi *)inst->vpu_inst.vsi; + + mtk_vcodec_debug_leave(inst); + + if (ret) + kfree(inst); + else + ctx->drv_handle = inst; + + return ret; +} + +static int vp8_enc_encode(void *handle, + enum venc_start_opt opt, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_done_result *result) +{ + int ret = 0; + struct venc_vp8_inst *inst = (struct venc_vp8_inst *)handle; + struct mtk_vcodec_ctx *ctx = inst->ctx; + + mtk_vcodec_debug_enter(inst); + + enable_irq(ctx->dev->enc_irq); + + switch (opt) { + case VENC_START_OPT_ENCODE_FRAME: + ret = vp8_enc_encode_frame(inst, frm_buf, bs_buf, + &result->bs_size); + if (ret) + goto encode_err; + result->is_key_frm = inst->vpu_inst.is_key_frm; + break; + + default: + mtk_vcodec_err(inst, "opt not support:%d", opt); + ret = -EINVAL; + break; + } + +encode_err: + + disable_irq(ctx->dev->enc_irq); + mtk_vcodec_debug_leave(inst); + + return ret; +} + +static int vp8_enc_set_param(void *handle, + enum venc_set_param_type type, + struct venc_enc_param *enc_prm) +{ + int ret = 0; + struct venc_vp8_inst *inst = (struct venc_vp8_inst *)handle; + + mtk_vcodec_debug(inst, "->type=%d", type); + + switch (type) { + case VENC_SET_PARAM_ENC: + inst->vsi->config.input_fourcc = enc_prm->input_yuv_fmt; + inst->vsi->config.bitrate = enc_prm->bitrate; + inst->vsi->config.pic_w = enc_prm->width; + inst->vsi->config.pic_h = enc_prm->height; + inst->vsi->config.buf_w = enc_prm->buf_width; + inst->vsi->config.buf_h = enc_prm->buf_height; + inst->vsi->config.gop_size = enc_prm->gop_size; + inst->vsi->config.framerate = enc_prm->frm_rate; + inst->vsi->config.ts_mode = inst->ts_mode; + ret = vpu_enc_set_param(&inst->vpu_inst, type, enc_prm); + if (ret) + break; + if (inst->work_buf_allocated) { + vp8_enc_free_work_buf(inst); + inst->work_buf_allocated = false; + } + ret = vp8_enc_alloc_work_buf(inst); + if (ret) + break; + inst->work_buf_allocated = true; + break; + + /* + * VENC_SET_PARAM_TS_MODE must be called before VENC_SET_PARAM_ENC + */ + case VENC_SET_PARAM_TS_MODE: + inst->ts_mode = 1; + mtk_vcodec_debug(inst, "set ts_mode"); + break; + + default: + ret = vpu_enc_set_param(&inst->vpu_inst, type, enc_prm); + break; + } + + mtk_vcodec_debug_leave(inst); + + return ret; +} + +static int vp8_enc_deinit(void *handle) +{ + int ret = 0; + struct venc_vp8_inst *inst = (struct venc_vp8_inst *)handle; + + mtk_vcodec_debug_enter(inst); + + ret = vpu_enc_deinit(&inst->vpu_inst); + + if (inst->work_buf_allocated) + vp8_enc_free_work_buf(inst); + + mtk_vcodec_debug_leave(inst); + kfree(inst); + + return ret; +} + +const struct venc_common_if venc_vp8_if = { + .init = vp8_enc_init, + .encode = vp8_enc_encode, + .set_param = vp8_enc_set_param, + .deinit = vp8_enc_deinit, +}; diff --git a/drivers/media/platform/mediatek/vcodec/venc_drv_base.h b/drivers/media/platform/mediatek/vcodec/venc_drv_base.h new file mode 100644 index 000000000..3d718411d --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/venc_drv_base.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Daniel Hsiao + * Jungchang Tsao + * Tiffany Lin + */ + +#ifndef _VENC_DRV_BASE_ +#define _VENC_DRV_BASE_ + +#include "mtk_vcodec_drv.h" + +#include "venc_drv_if.h" + +struct venc_common_if { + /** + * (*init)() - initialize driver + * @ctx: [in] mtk v4l2 context + * @handle: [out] driver handle + */ + int (*init)(struct mtk_vcodec_ctx *ctx); + + /** + * (*encode)() - trigger encode + * @handle: [in] driver handle + * @opt: [in] encode option + * @frm_buf: [in] frame buffer to store input frame + * @bs_buf: [in] bitstream buffer to store output bitstream + * @result: [out] encode result + */ + int (*encode)(void *handle, enum venc_start_opt opt, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_done_result *result); + + /** + * (*set_param)() - set driver's parameter + * @handle: [in] driver handle + * @type: [in] parameter type + * @in: [in] buffer to store the parameter + */ + int (*set_param)(void *handle, enum venc_set_param_type type, + struct venc_enc_param *in); + + /** + * (*deinit)() - deinitialize driver. + * @handle: [in] driver handle + */ + int (*deinit)(void *handle); +}; + +#endif diff --git a/drivers/media/platform/mediatek/vcodec/venc_drv_if.c b/drivers/media/platform/mediatek/vcodec/venc_drv_if.c new file mode 100644 index 000000000..ce0bce811 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/venc_drv_if.c @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Daniel Hsiao + * Jungchang Tsao + * Tiffany Lin + */ + +#include +#include +#include + +#include "venc_drv_base.h" +#include "venc_drv_if.h" + +#include "mtk_vcodec_enc.h" +#include "mtk_vcodec_enc_pm.h" + +int venc_if_init(struct mtk_vcodec_ctx *ctx, unsigned int fourcc) +{ + int ret = 0; + + switch (fourcc) { + case V4L2_PIX_FMT_VP8: + ctx->enc_if = &venc_vp8_if; + break; + case V4L2_PIX_FMT_H264: + ctx->enc_if = &venc_h264_if; + break; + default: + return -EINVAL; + } + + mtk_venc_lock(ctx); + mtk_vcodec_enc_clock_on(&ctx->dev->pm); + ret = ctx->enc_if->init(ctx); + mtk_vcodec_enc_clock_off(&ctx->dev->pm); + mtk_venc_unlock(ctx); + + return ret; +} + +int venc_if_set_param(struct mtk_vcodec_ctx *ctx, + enum venc_set_param_type type, struct venc_enc_param *in) +{ + int ret = 0; + + mtk_venc_lock(ctx); + mtk_vcodec_enc_clock_on(&ctx->dev->pm); + ret = ctx->enc_if->set_param(ctx->drv_handle, type, in); + mtk_vcodec_enc_clock_off(&ctx->dev->pm); + mtk_venc_unlock(ctx); + + return ret; +} + +int venc_if_encode(struct mtk_vcodec_ctx *ctx, + enum venc_start_opt opt, struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_done_result *result) +{ + int ret = 0; + unsigned long flags; + + mtk_venc_lock(ctx); + + spin_lock_irqsave(&ctx->dev->irqlock, flags); + ctx->dev->curr_ctx = ctx; + spin_unlock_irqrestore(&ctx->dev->irqlock, flags); + + mtk_vcodec_enc_clock_on(&ctx->dev->pm); + ret = ctx->enc_if->encode(ctx->drv_handle, opt, frm_buf, + bs_buf, result); + mtk_vcodec_enc_clock_off(&ctx->dev->pm); + + spin_lock_irqsave(&ctx->dev->irqlock, flags); + ctx->dev->curr_ctx = NULL; + spin_unlock_irqrestore(&ctx->dev->irqlock, flags); + + mtk_venc_unlock(ctx); + return ret; +} + +int venc_if_deinit(struct mtk_vcodec_ctx *ctx) +{ + int ret = 0; + + if (!ctx->drv_handle) + return 0; + + mtk_venc_lock(ctx); + mtk_vcodec_enc_clock_on(&ctx->dev->pm); + ret = ctx->enc_if->deinit(ctx->drv_handle); + mtk_vcodec_enc_clock_off(&ctx->dev->pm); + mtk_venc_unlock(ctx); + + ctx->drv_handle = NULL; + + return ret; +} diff --git a/drivers/media/platform/mediatek/vcodec/venc_drv_if.h b/drivers/media/platform/mediatek/vcodec/venc_drv_if.h new file mode 100644 index 000000000..0b04a1020 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/venc_drv_if.h @@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Daniel Hsiao + * Jungchang Tsao + * Tiffany Lin + */ + +#ifndef _VENC_DRV_IF_H_ +#define _VENC_DRV_IF_H_ + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_util.h" + +/* + * enum venc_yuv_fmt - The type of input yuv format + * (VPU related: If you change the order, you must also update the VPU codes.) + * @VENC_YUV_FORMAT_I420: I420 YUV format + * @VENC_YUV_FORMAT_YV12: YV12 YUV format + * @VENC_YUV_FORMAT_NV12: NV12 YUV format + * @VENC_YUV_FORMAT_NV21: NV21 YUV format + */ +enum venc_yuv_fmt { + VENC_YUV_FORMAT_I420 = 3, + VENC_YUV_FORMAT_YV12 = 5, + VENC_YUV_FORMAT_NV12 = 6, + VENC_YUV_FORMAT_NV21 = 7, +}; + +/* + * enum venc_start_opt - encode frame option used in venc_if_encode() + * @VENC_START_OPT_ENCODE_SEQUENCE_HEADER: encode SPS/PPS for H264 + * @VENC_START_OPT_ENCODE_FRAME: encode normal frame + */ +enum venc_start_opt { + VENC_START_OPT_ENCODE_SEQUENCE_HEADER, + VENC_START_OPT_ENCODE_FRAME, +}; + +/* + * enum venc_set_param_type - The type of set parameter used in + * venc_if_set_param() + * (VPU related: If you change the order, you must also update the VPU codes.) + * @VENC_SET_PARAM_ENC: set encoder parameters + * @VENC_SET_PARAM_FORCE_INTRA: force an intra frame + * @VENC_SET_PARAM_ADJUST_BITRATE: adjust bitrate (in bps) + * @VENC_SET_PARAM_ADJUST_FRAMERATE: set frame rate + * @VENC_SET_PARAM_GOP_SIZE: set IDR interval + * @VENC_SET_PARAM_INTRA_PERIOD: set I frame interval + * @VENC_SET_PARAM_SKIP_FRAME: set H264 skip one frame + * @VENC_SET_PARAM_PREPEND_HEADER: set H264 prepend SPS/PPS before IDR + * @VENC_SET_PARAM_TS_MODE: set VP8 temporal scalability mode + */ +enum venc_set_param_type { + VENC_SET_PARAM_ENC, + VENC_SET_PARAM_FORCE_INTRA, + VENC_SET_PARAM_ADJUST_BITRATE, + VENC_SET_PARAM_ADJUST_FRAMERATE, + VENC_SET_PARAM_GOP_SIZE, + VENC_SET_PARAM_INTRA_PERIOD, + VENC_SET_PARAM_SKIP_FRAME, + VENC_SET_PARAM_PREPEND_HEADER, + VENC_SET_PARAM_TS_MODE, +}; + +/* + * struct venc_enc_prm - encoder settings for VENC_SET_PARAM_ENC used in + * venc_if_set_param() + * @input_fourcc: input yuv format + * @h264_profile: V4L2 defined H.264 profile + * @h264_level: V4L2 defined H.264 level + * @width: image width + * @height: image height + * @buf_width: buffer width + * @buf_height: buffer height + * @frm_rate: frame rate in fps + * @intra_period: intra frame period + * @bitrate: target bitrate in bps + * @gop_size: group of picture size + */ +struct venc_enc_param { + enum venc_yuv_fmt input_yuv_fmt; + unsigned int h264_profile; + unsigned int h264_level; + unsigned int width; + unsigned int height; + unsigned int buf_width; + unsigned int buf_height; + unsigned int frm_rate; + unsigned int intra_period; + unsigned int bitrate; + unsigned int gop_size; +}; + +/** + * struct venc_frame_info - per-frame information to pass to the firmware. + * + * @frm_count: sequential number for this frame + * @skip_frm_count: number of frames skipped so far while decoding + * @frm_type: type of the frame, from enum venc_h264_frame_type + */ +struct venc_frame_info { + unsigned int frm_count; /* per frame update */ + unsigned int skip_frm_count; /* per frame update */ + unsigned int frm_type; /* per frame update */ +}; + +/* + * struct venc_frm_buf - frame buffer information used in venc_if_encode() + * @fb_addr: plane frame buffer addresses + */ +struct venc_frm_buf { + struct mtk_vcodec_fb fb_addr[MTK_VCODEC_MAX_PLANES]; +}; + +/* + * struct venc_done_result - This is return information used in venc_if_encode() + * @bs_size: output bitstream size + * @is_key_frm: output is key frame or not + */ +struct venc_done_result { + unsigned int bs_size; + bool is_key_frm; +}; + +extern const struct venc_common_if venc_h264_if; +extern const struct venc_common_if venc_vp8_if; + +/* + * venc_if_init - Create the driver handle + * @ctx: device context + * @fourcc: encoder input format + * Return: 0 if creating handle successfully, otherwise it is failed. + */ +int venc_if_init(struct mtk_vcodec_ctx *ctx, unsigned int fourcc); + +/* + * venc_if_deinit - Release the driver handle + * @ctx: device context + * Return: 0 if releasing handle successfully, otherwise it is failed. + */ +int venc_if_deinit(struct mtk_vcodec_ctx *ctx); + +/* + * venc_if_set_param - Set parameter to driver + * @ctx: device context + * @type: parameter type + * @in: input parameter + * Return: 0 if setting param successfully, otherwise it is failed. + */ +int venc_if_set_param(struct mtk_vcodec_ctx *ctx, + enum venc_set_param_type type, + struct venc_enc_param *in); + +/* + * venc_if_encode - Encode one frame + * @ctx: device context + * @opt: encode frame option + * @frm_buf: input frame buffer information + * @bs_buf: output bitstream buffer infomraiton + * @result: encode result + * Return: 0 if encoding frame successfully, otherwise it is failed. + */ +int venc_if_encode(struct mtk_vcodec_ctx *ctx, + enum venc_start_opt opt, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_done_result *result); + +#endif /* _VENC_DRV_IF_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/venc_ipi_msg.h b/drivers/media/platform/mediatek/vcodec/venc_ipi_msg.h new file mode 100644 index 000000000..bb16d96a7 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/venc_ipi_msg.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: Jungchang Tsao + * Daniel Hsiao + * Tiffany Lin + */ + +#ifndef _VENC_IPI_MSG_H_ +#define _VENC_IPI_MSG_H_ + +#define AP_IPIMSG_VENC_BASE 0xC000 +#define VPU_IPIMSG_VENC_BASE 0xD000 + +/* + * enum venc_ipi_msg_id - message id between AP and VPU + * (ipi stands for inter-processor interrupt) + * @AP_IPIMSG_ENC_XXX: AP to VPU cmd message id + * @VPU_IPIMSG_ENC_XXX_DONE: VPU ack AP cmd message id + */ +enum venc_ipi_msg_id { + AP_IPIMSG_ENC_INIT = AP_IPIMSG_VENC_BASE, + AP_IPIMSG_ENC_SET_PARAM, + AP_IPIMSG_ENC_ENCODE, + AP_IPIMSG_ENC_DEINIT, + + VPU_IPIMSG_ENC_INIT_DONE = VPU_IPIMSG_VENC_BASE, + VPU_IPIMSG_ENC_SET_PARAM_DONE, + VPU_IPIMSG_ENC_ENCODE_DONE, + VPU_IPIMSG_ENC_DEINIT_DONE, +}; + +/** + * struct venc_ap_ipi_msg_init - AP to VPU init cmd structure + * @msg_id: message id (AP_IPIMSG_XXX_ENC_INIT) + * @reserved: reserved for future use. vpu is running in 32bit. Without + * this reserved field, if kernel run in 64bit. this struct size + * will be different between kernel and vpu + * @venc_inst: AP encoder instance + * (struct venc_vp8_inst/venc_h264_inst *) + */ +struct venc_ap_ipi_msg_init { + uint32_t msg_id; + uint32_t reserved; + uint64_t venc_inst; +}; + +/** + * struct venc_ap_ipi_msg_set_param - AP to VPU set_param cmd structure + * @msg_id: message id (AP_IPIMSG_XXX_ENC_SET_PARAM) + * @vpu_inst_addr: VPU encoder instance addr + * (struct venc_vp8_vsi/venc_h264_vsi *) + * @param_id: parameter id (venc_set_param_type) + * @data_item: number of items in the data array + * @data: data array to store the set parameters + */ +struct venc_ap_ipi_msg_set_param { + uint32_t msg_id; + uint32_t vpu_inst_addr; + uint32_t param_id; + uint32_t data_item; + uint32_t data[8]; +}; + +struct venc_ap_ipi_msg_set_param_ext { + struct venc_ap_ipi_msg_set_param base; + uint32_t data_ext[24]; +}; + +/** + * struct venc_ap_ipi_msg_enc - AP to VPU enc cmd structure + * @msg_id: message id (AP_IPIMSG_XXX_ENC_ENCODE) + * @vpu_inst_addr: VPU encoder instance addr + * (struct venc_vp8_vsi/venc_h264_vsi *) + * @bs_mode: bitstream mode for h264 + * (H264_BS_MODE_SPS/H264_BS_MODE_PPS/H264_BS_MODE_FRAME) + * @input_addr: pointer to input image buffer plane + * @bs_addr: pointer to output bit stream buffer + * @bs_size: bit stream buffer size + */ +struct venc_ap_ipi_msg_enc { + uint32_t msg_id; + uint32_t vpu_inst_addr; + uint32_t bs_mode; + uint32_t input_addr[3]; + uint32_t bs_addr; + uint32_t bs_size; +}; + +/** + * struct venc_ap_ipi_msg_enc_ext - AP to SCP extended enc cmd structure + * + * @base: base msg structure + * @data_item: number of items in the data array + * @data: data array to store the set parameters + */ +struct venc_ap_ipi_msg_enc_ext { + struct venc_ap_ipi_msg_enc base; + uint32_t data_item; + uint32_t data[32]; +}; + +/** + * struct venc_ap_ipi_msg_enc_ext_34 - AP to SCP extended enc cmd structure + * @msg_id: message id (AP_IPIMSG_XXX_ENC_ENCODE) + * @vpu_inst_addr: VPU encoder instance addr + * @bs_mode: bitstream mode for h264 + * @reserved: for struct padding + * @input_addr: input frame buffer 34 bit address + * @bs_addr: output bitstream buffer 34 bit address + * @bs_size: bitstream buffer size + * @data_item: number of items in the data array + * @data: data array to store the set parameters + */ +struct venc_ap_ipi_msg_enc_ext_34 { + u32 msg_id; + u32 vpu_inst_addr; + u32 bs_mode; + u32 reserved; + u64 input_addr[3]; + u64 bs_addr; + u32 bs_size; + u32 data_item; + u32 data[32]; +}; + +/** + * struct venc_ap_ipi_msg_deinit - AP to VPU deinit cmd structure + * @msg_id: message id (AP_IPIMSG_XXX_ENC_DEINIT) + * @vpu_inst_addr: VPU encoder instance addr + * (struct venc_vp8_vsi/venc_h264_vsi *) + */ +struct venc_ap_ipi_msg_deinit { + uint32_t msg_id; + uint32_t vpu_inst_addr; +}; + +/* + * enum venc_ipi_msg_status - VPU ack AP cmd status + */ +enum venc_ipi_msg_status { + VENC_IPI_MSG_STATUS_OK, + VENC_IPI_MSG_STATUS_FAIL, +}; + +/** + * struct venc_vpu_ipi_msg_common - VPU ack AP cmd common structure + * @msg_id: message id (VPU_IPIMSG_XXX_DONE) + * @status: cmd status (venc_ipi_msg_status) + * @venc_inst: AP encoder instance (struct venc_vp8_inst/venc_h264_inst *) + */ +struct venc_vpu_ipi_msg_common { + uint32_t msg_id; + uint32_t status; + uint64_t venc_inst; +}; + +/** + * struct venc_vpu_ipi_msg_init - VPU ack AP init cmd structure + * @msg_id: message id (VPU_IPIMSG_XXX_ENC_SET_PARAM_DONE) + * @status: cmd status (venc_ipi_msg_status) + * @venc_inst: AP encoder instance (struct venc_vp8_inst/venc_h264_inst *) + * @vpu_inst_addr: VPU encoder instance addr + * (struct venc_vp8_vsi/venc_h264_vsi *) + * @venc_abi_version: ABI version of the firmware. Kernel can use it to + * ensure that it is compatible with the firmware. + * For MT8173 the value of this field is undefined and + * should not be used. + */ +struct venc_vpu_ipi_msg_init { + uint32_t msg_id; + uint32_t status; + uint64_t venc_inst; + uint32_t vpu_inst_addr; + uint32_t venc_abi_version; +}; + +/** + * struct venc_vpu_ipi_msg_set_param - VPU ack AP set_param cmd structure + * @msg_id: message id (VPU_IPIMSG_XXX_ENC_SET_PARAM_DONE) + * @status: cmd status (venc_ipi_msg_status) + * @venc_inst: AP encoder instance (struct venc_vp8_inst/venc_h264_inst *) + * @param_id: parameter id (venc_set_param_type) + * @data_item: number of items in the data array + * @data: data array to store the return result + */ +struct venc_vpu_ipi_msg_set_param { + uint32_t msg_id; + uint32_t status; + uint64_t venc_inst; + uint32_t param_id; + uint32_t data_item; + uint32_t data[6]; +}; + +/** + * enum venc_ipi_msg_enc_state - Type of encode state + * @VEN_IPI_MSG_ENC_STATE_FRAME: one frame being encoded + * @VEN_IPI_MSG_ENC_STATE_PART: bit stream buffer full + * @VEN_IPI_MSG_ENC_STATE_SKIP: encoded skip frame + * @VEN_IPI_MSG_ENC_STATE_ERROR: encounter error + */ +enum venc_ipi_msg_enc_state { + VEN_IPI_MSG_ENC_STATE_FRAME, + VEN_IPI_MSG_ENC_STATE_PART, + VEN_IPI_MSG_ENC_STATE_SKIP, + VEN_IPI_MSG_ENC_STATE_ERROR, +}; + +/** + * struct venc_vpu_ipi_msg_enc - VPU ack AP enc cmd structure + * @msg_id: message id (VPU_IPIMSG_XXX_ENC_ENCODE_DONE) + * @status: cmd status (venc_ipi_msg_status) + * @venc_inst: AP encoder instance (struct venc_vp8_inst/venc_h264_inst *) + * @state: encode state (venc_ipi_msg_enc_state) + * @is_key_frm: whether the encoded frame is key frame + * @bs_size: encoded bitstream size + * @reserved: reserved for future use. vpu is running in 32bit. Without + * this reserved field, if kernel run in 64bit. this struct size + * will be different between kernel and vpu + */ +struct venc_vpu_ipi_msg_enc { + uint32_t msg_id; + uint32_t status; + uint64_t venc_inst; + uint32_t state; + uint32_t is_key_frm; + uint32_t bs_size; + uint32_t reserved; +}; + +/** + * struct venc_vpu_ipi_msg_deinit - VPU ack AP deinit cmd structure + * @msg_id: message id (VPU_IPIMSG_XXX_ENC_DEINIT_DONE) + * @status: cmd status (venc_ipi_msg_status) + * @venc_inst: AP encoder instance (struct venc_vp8_inst/venc_h264_inst *) + */ +struct venc_vpu_ipi_msg_deinit { + uint32_t msg_id; + uint32_t status; + uint64_t venc_inst; +}; + +#endif /* _VENC_IPI_MSG_H_ */ diff --git a/drivers/media/platform/mediatek/vcodec/venc_vpu_if.c b/drivers/media/platform/mediatek/vcodec/venc_vpu_if.c new file mode 100644 index 000000000..09e7eaa25 --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/venc_vpu_if.c @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PoChun Lin + */ + +#include "mtk_vcodec_drv.h" +#include "mtk_vcodec_fw.h" +#include "venc_ipi_msg.h" +#include "venc_vpu_if.h" + +static void handle_enc_init_msg(struct venc_vpu_inst *vpu, const void *data) +{ + const struct venc_vpu_ipi_msg_init *msg = data; + + vpu->inst_addr = msg->vpu_inst_addr; + vpu->vsi = mtk_vcodec_fw_map_dm_addr(vpu->ctx->dev->fw_handler, + msg->vpu_inst_addr); + + /* Firmware version field value is unspecified on MT8173. */ + if (mtk_vcodec_fw_get_type(vpu->ctx->dev->fw_handler) == VPU) + return; + + /* Check firmware version. */ + mtk_vcodec_debug(vpu, "firmware version: 0x%x\n", + msg->venc_abi_version); + switch (msg->venc_abi_version) { + case 1: + break; + default: + mtk_vcodec_err(vpu, "unhandled firmware version 0x%x\n", + msg->venc_abi_version); + vpu->failure = 1; + break; + } +} + +static void handle_enc_encode_msg(struct venc_vpu_inst *vpu, const void *data) +{ + const struct venc_vpu_ipi_msg_enc *msg = data; + + vpu->state = msg->state; + vpu->bs_size = msg->bs_size; + vpu->is_key_frm = msg->is_key_frm; +} + +static void vpu_enc_ipi_handler(void *data, unsigned int len, void *priv) +{ + const struct venc_vpu_ipi_msg_common *msg = data; + struct venc_vpu_inst *vpu = + (struct venc_vpu_inst *)(unsigned long)msg->venc_inst; + + mtk_vcodec_debug(vpu, "msg_id %x inst %p status %d", + msg->msg_id, vpu, msg->status); + + vpu->signaled = 1; + vpu->failure = (msg->status != VENC_IPI_MSG_STATUS_OK); + if (vpu->failure) + goto failure; + + switch (msg->msg_id) { + case VPU_IPIMSG_ENC_INIT_DONE: + handle_enc_init_msg(vpu, data); + break; + case VPU_IPIMSG_ENC_SET_PARAM_DONE: + break; + case VPU_IPIMSG_ENC_ENCODE_DONE: + handle_enc_encode_msg(vpu, data); + break; + case VPU_IPIMSG_ENC_DEINIT_DONE: + break; + default: + mtk_vcodec_err(vpu, "unknown msg id %x", msg->msg_id); + break; + } + +failure: + mtk_vcodec_debug_leave(vpu); +} + +static int vpu_enc_send_msg(struct venc_vpu_inst *vpu, void *msg, + int len) +{ + int status; + + mtk_vcodec_debug_enter(vpu); + + if (!vpu->ctx->dev->fw_handler) { + mtk_vcodec_err(vpu, "inst dev is NULL"); + return -EINVAL; + } + + status = mtk_vcodec_fw_ipi_send(vpu->ctx->dev->fw_handler, vpu->id, msg, + len, 2000); + if (status) { + mtk_vcodec_err(vpu, "vpu_ipi_send msg_id %x len %d fail %d", + *(uint32_t *)msg, len, status); + return -EINVAL; + } + if (vpu->failure) + return -EINVAL; + + mtk_vcodec_debug_leave(vpu); + + return 0; +} + +int vpu_enc_init(struct venc_vpu_inst *vpu) +{ + int status; + struct venc_ap_ipi_msg_init out; + + mtk_vcodec_debug_enter(vpu); + + init_waitqueue_head(&vpu->wq_hd); + vpu->signaled = 0; + vpu->failure = 0; + + status = mtk_vcodec_fw_ipi_register(vpu->ctx->dev->fw_handler, vpu->id, + vpu_enc_ipi_handler, "venc", NULL); + + if (status) { + mtk_vcodec_err(vpu, "vpu_ipi_register fail %d", status); + return -EINVAL; + } + + memset(&out, 0, sizeof(out)); + out.msg_id = AP_IPIMSG_ENC_INIT; + out.venc_inst = (unsigned long)vpu; + if (vpu_enc_send_msg(vpu, &out, sizeof(out))) { + mtk_vcodec_err(vpu, "AP_IPIMSG_ENC_INIT fail"); + return -EINVAL; + } + + mtk_vcodec_debug_leave(vpu); + + return 0; +} + +static unsigned int venc_enc_param_crop_right(struct venc_vpu_inst *vpu, + struct venc_enc_param *enc_prm) +{ + unsigned int img_crop_right = enc_prm->buf_width - enc_prm->width; + + return img_crop_right % 16; +} + +static unsigned int venc_enc_param_crop_bottom(struct venc_enc_param *enc_prm) +{ + return round_up(enc_prm->height, 16) - enc_prm->height; +} + +static unsigned int venc_enc_param_num_mb(struct venc_enc_param *enc_prm) +{ + return DIV_ROUND_UP(enc_prm->width, 16) * + DIV_ROUND_UP(enc_prm->height, 16); +} + +int vpu_enc_set_param(struct venc_vpu_inst *vpu, + enum venc_set_param_type id, + struct venc_enc_param *enc_param) +{ + const bool is_ext = MTK_ENC_CTX_IS_EXT(vpu->ctx); + size_t msg_size = is_ext ? + sizeof(struct venc_ap_ipi_msg_set_param_ext) : + sizeof(struct venc_ap_ipi_msg_set_param); + struct venc_ap_ipi_msg_set_param_ext out; + + mtk_vcodec_debug(vpu, "id %d ->", id); + + memset(&out, 0, sizeof(out)); + out.base.msg_id = AP_IPIMSG_ENC_SET_PARAM; + out.base.vpu_inst_addr = vpu->inst_addr; + out.base.param_id = id; + switch (id) { + case VENC_SET_PARAM_ENC: + if (is_ext) { + out.base.data_item = 3; + out.base.data[0] = + venc_enc_param_crop_right(vpu, enc_param); + out.base.data[1] = + venc_enc_param_crop_bottom(enc_param); + out.base.data[2] = venc_enc_param_num_mb(enc_param); + } else { + out.base.data_item = 0; + } + break; + case VENC_SET_PARAM_FORCE_INTRA: + out.base.data_item = 0; + break; + case VENC_SET_PARAM_ADJUST_BITRATE: + out.base.data_item = 1; + out.base.data[0] = enc_param->bitrate; + break; + case VENC_SET_PARAM_ADJUST_FRAMERATE: + out.base.data_item = 1; + out.base.data[0] = enc_param->frm_rate; + break; + case VENC_SET_PARAM_GOP_SIZE: + out.base.data_item = 1; + out.base.data[0] = enc_param->gop_size; + break; + case VENC_SET_PARAM_INTRA_PERIOD: + out.base.data_item = 1; + out.base.data[0] = enc_param->intra_period; + break; + case VENC_SET_PARAM_SKIP_FRAME: + out.base.data_item = 0; + break; + default: + mtk_vcodec_err(vpu, "id %d not supported", id); + return -EINVAL; + } + if (vpu_enc_send_msg(vpu, &out, msg_size)) { + mtk_vcodec_err(vpu, + "AP_IPIMSG_ENC_SET_PARAM %d fail", id); + return -EINVAL; + } + + mtk_vcodec_debug(vpu, "id %d <-", id); + + return 0; +} + +static int vpu_enc_encode_32bits(struct venc_vpu_inst *vpu, + unsigned int bs_mode, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_frame_info *frame_info) +{ + const bool is_ext = MTK_ENC_CTX_IS_EXT(vpu->ctx); + size_t msg_size = is_ext ? + sizeof(struct venc_ap_ipi_msg_enc_ext) : + sizeof(struct venc_ap_ipi_msg_enc); + struct venc_ap_ipi_msg_enc_ext out; + + mtk_vcodec_debug(vpu, "bs_mode %d ->", bs_mode); + + memset(&out, 0, sizeof(out)); + out.base.msg_id = AP_IPIMSG_ENC_ENCODE; + out.base.vpu_inst_addr = vpu->inst_addr; + out.base.bs_mode = bs_mode; + if (frm_buf) { + if ((frm_buf->fb_addr[0].dma_addr % 16 == 0) && + (frm_buf->fb_addr[1].dma_addr % 16 == 0) && + (frm_buf->fb_addr[2].dma_addr % 16 == 0)) { + out.base.input_addr[0] = frm_buf->fb_addr[0].dma_addr; + out.base.input_addr[1] = frm_buf->fb_addr[1].dma_addr; + out.base.input_addr[2] = frm_buf->fb_addr[2].dma_addr; + } else { + mtk_vcodec_err(vpu, "dma_addr not align to 16"); + return -EINVAL; + } + } + if (bs_buf) { + out.base.bs_addr = bs_buf->dma_addr; + out.base.bs_size = bs_buf->size; + } + if (is_ext && frame_info) { + out.data_item = 3; + out.data[0] = frame_info->frm_count; + out.data[1] = frame_info->skip_frm_count; + out.data[2] = frame_info->frm_type; + } + if (vpu_enc_send_msg(vpu, &out, msg_size)) { + mtk_vcodec_err(vpu, "AP_IPIMSG_ENC_ENCODE %d fail", + bs_mode); + return -EINVAL; + } + + return 0; +} + +static int vpu_enc_encode_34bits(struct venc_vpu_inst *vpu, + unsigned int bs_mode, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_frame_info *frame_info) +{ + struct venc_ap_ipi_msg_enc_ext_34 out; + size_t msg_size = sizeof(struct venc_ap_ipi_msg_enc_ext_34); + + mtk_vcodec_debug(vpu, "bs_mode %d ->", bs_mode); + + memset(&out, 0, sizeof(out)); + out.msg_id = AP_IPIMSG_ENC_ENCODE; + out.vpu_inst_addr = vpu->inst_addr; + out.bs_mode = bs_mode; + + if (frm_buf) { + if ((frm_buf->fb_addr[0].dma_addr % 16 == 0) && + (frm_buf->fb_addr[1].dma_addr % 16 == 0) && + (frm_buf->fb_addr[2].dma_addr % 16 == 0)) { + out.input_addr[0] = frm_buf->fb_addr[0].dma_addr; + out.input_addr[1] = frm_buf->fb_addr[1].dma_addr; + out.input_addr[2] = frm_buf->fb_addr[2].dma_addr; + } else { + mtk_vcodec_err(vpu, "dma_addr not align to 16"); + return -EINVAL; + } + } + if (bs_buf) { + out.bs_addr = bs_buf->dma_addr; + out.bs_size = bs_buf->size; + } + if (frame_info) { + out.data_item = 3; + out.data[0] = frame_info->frm_count; + out.data[1] = frame_info->skip_frm_count; + out.data[2] = frame_info->frm_type; + } + if (vpu_enc_send_msg(vpu, &out, msg_size)) { + mtk_vcodec_err(vpu, "AP_IPIMSG_ENC_ENCODE %d fail", + bs_mode); + return -EINVAL; + } + + return 0; +} + +int vpu_enc_encode(struct venc_vpu_inst *vpu, unsigned int bs_mode, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_frame_info *frame_info) +{ + int ret; + + if (MTK_ENC_IOVA_IS_34BIT(vpu->ctx)) + ret = vpu_enc_encode_34bits(vpu, bs_mode, + frm_buf, bs_buf, frame_info); + else + ret = vpu_enc_encode_32bits(vpu, bs_mode, + frm_buf, bs_buf, frame_info); + + if (ret) + return ret; + + mtk_vcodec_debug(vpu, "bs_mode %d state %d size %d key_frm %d <-", + bs_mode, vpu->state, vpu->bs_size, vpu->is_key_frm); + + return 0; +} + +int vpu_enc_deinit(struct venc_vpu_inst *vpu) +{ + struct venc_ap_ipi_msg_deinit out; + + mtk_vcodec_debug_enter(vpu); + + memset(&out, 0, sizeof(out)); + out.msg_id = AP_IPIMSG_ENC_DEINIT; + out.vpu_inst_addr = vpu->inst_addr; + if (vpu_enc_send_msg(vpu, &out, sizeof(out))) { + mtk_vcodec_err(vpu, "AP_IPIMSG_ENC_DEINIT fail"); + return -EINVAL; + } + + mtk_vcodec_debug_leave(vpu); + + return 0; +} diff --git a/drivers/media/platform/mediatek/vcodec/venc_vpu_if.h b/drivers/media/platform/mediatek/vcodec/venc_vpu_if.h new file mode 100644 index 000000000..f83bc1b3f --- /dev/null +++ b/drivers/media/platform/mediatek/vcodec/venc_vpu_if.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016 MediaTek Inc. + * Author: PoChun Lin + */ + +#ifndef _VENC_VPU_IF_H_ +#define _VENC_VPU_IF_H_ + +#include "mtk_vcodec_fw.h" +#include "venc_drv_if.h" + +/* + * struct venc_vpu_inst - encoder VPU driver instance + * @wq_hd: wait queue used for vpu cmd trigger then wait vpu interrupt done + * @signaled: flag used for checking vpu interrupt done + * @failure: flag to show vpu cmd succeeds or not + * @state: enum venc_ipi_msg_enc_state + * @bs_size: bitstream size for skip frame case usage + * @is_key_frm: key frame flag + * @inst_addr: VPU instance addr + * @vsi: driver structure allocated by VPU side and shared to AP side for + * control and info share + * @id: the id of inter-processor interrupt + * @ctx: context for v4l2 layer integration + * @dev: device for v4l2 layer integration + */ +struct venc_vpu_inst { + wait_queue_head_t wq_hd; + int signaled; + int failure; + int state; + int bs_size; + int is_key_frm; + unsigned int inst_addr; + void *vsi; + int id; + struct mtk_vcodec_ctx *ctx; +}; + +int vpu_enc_init(struct venc_vpu_inst *vpu); +int vpu_enc_set_param(struct venc_vpu_inst *vpu, + enum venc_set_param_type id, + struct venc_enc_param *param); +int vpu_enc_encode(struct venc_vpu_inst *vpu, unsigned int bs_mode, + struct venc_frm_buf *frm_buf, + struct mtk_vcodec_mem *bs_buf, + struct venc_frame_info *frame_info); +int vpu_enc_deinit(struct venc_vpu_inst *vpu); + +#endif diff --git a/drivers/media/platform/mediatek/vpu/Kconfig b/drivers/media/platform/mediatek/vpu/Kconfig new file mode 100644 index 000000000..2a8443a93 --- /dev/null +++ b/drivers/media/platform/mediatek/vpu/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_MEDIATEK_VPU + tristate "Mediatek Video Processor Unit" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_MEDIATEK || COMPILE_TEST + help + This driver provides downloading VPU firmware and + communicating with VPU. This driver for hw video + codec embedded in Mediatek's MT8173 SOCs. It is able + to handle video decoding/encoding in a range of formats. + + To compile this driver as a module, choose M here: the + module will be called mtk-vpu. diff --git a/drivers/media/platform/mediatek/vpu/Makefile b/drivers/media/platform/mediatek/vpu/Makefile new file mode 100644 index 000000000..ecd2d392b --- /dev/null +++ b/drivers/media/platform/mediatek/vpu/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +mtk-vpu-y += mtk_vpu.o + +obj-$(CONFIG_VIDEO_MEDIATEK_VPU) += mtk-vpu.o diff --git a/drivers/media/platform/mediatek/vpu/mtk_vpu.c b/drivers/media/platform/mediatek/vpu/mtk_vpu.c new file mode 100644 index 000000000..6beab9e86 --- /dev/null +++ b/drivers/media/platform/mediatek/vpu/mtk_vpu.c @@ -0,0 +1,1056 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: Andrew-CT Chen +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mtk_vpu.h" + +/* + * VPU (video processor unit) is a tiny processor controlling video hardware + * related to video codec, scaling and color format converting. + * VPU interfaces with other blocks by share memory and interrupt. + */ + +#define INIT_TIMEOUT_MS 2000U +#define IPI_TIMEOUT_MS 2000U +#define VPU_IDLE_TIMEOUT_MS 1000U +#define VPU_FW_VER_LEN 16 + +/* maximum program/data TCM (Tightly-Coupled Memory) size */ +#define VPU_PTCM_SIZE (96 * SZ_1K) +#define VPU_DTCM_SIZE (32 * SZ_1K) +/* the offset to get data tcm address */ +#define VPU_DTCM_OFFSET 0x18000UL +/* daynamic allocated maximum extended memory size */ +#define VPU_EXT_P_SIZE SZ_1M +#define VPU_EXT_D_SIZE SZ_4M +/* maximum binary firmware size */ +#define VPU_P_FW_SIZE (VPU_PTCM_SIZE + VPU_EXT_P_SIZE) +#define VPU_D_FW_SIZE (VPU_DTCM_SIZE + VPU_EXT_D_SIZE) +/* the size of share buffer between Host and VPU */ +#define SHARE_BUF_SIZE 48 + +/* binary firmware name */ +#define VPU_P_FW "vpu_p.bin" +#define VPU_D_FW "vpu_d.bin" +#define VPU_P_FW_NEW "mediatek/mt8173/vpu_p.bin" +#define VPU_D_FW_NEW "mediatek/mt8173/vpu_d.bin" + +#define VPU_RESET 0x0 +#define VPU_TCM_CFG 0x0008 +#define VPU_PMEM_EXT0_ADDR 0x000C +#define VPU_PMEM_EXT1_ADDR 0x0010 +#define VPU_TO_HOST 0x001C +#define VPU_DMEM_EXT0_ADDR 0x0014 +#define VPU_DMEM_EXT1_ADDR 0x0018 +#define HOST_TO_VPU 0x0024 +#define VPU_IDLE_REG 0x002C +#define VPU_INT_STATUS 0x0034 +#define VPU_PC_REG 0x0060 +#define VPU_SP_REG 0x0064 +#define VPU_RA_REG 0x0068 +#define VPU_WDT_REG 0x0084 + +/* vpu inter-processor communication interrupt */ +#define VPU_IPC_INT BIT(8) +/* vpu idle state */ +#define VPU_IDLE_STATE BIT(23) + +/** + * enum vpu_fw_type - VPU firmware type + * + * @P_FW: program firmware + * @D_FW: data firmware + * + */ +enum vpu_fw_type { + P_FW, + D_FW, +}; + +/** + * struct vpu_mem - VPU extended program/data memory information + * + * @va: the kernel virtual memory address of VPU extended memory + * @pa: the physical memory address of VPU extended memory + * + */ +struct vpu_mem { + void *va; + dma_addr_t pa; +}; + +/** + * struct vpu_regs - VPU TCM and configuration registers + * + * @tcm: the register for VPU Tightly-Coupled Memory + * @cfg: the register for VPU configuration + * @irq: the irq number for VPU interrupt + */ +struct vpu_regs { + void __iomem *tcm; + void __iomem *cfg; + int irq; +}; + +/** + * struct vpu_wdt_handler - VPU watchdog reset handler + * + * @reset_func: reset handler + * @priv: private data + */ +struct vpu_wdt_handler { + void (*reset_func)(void *); + void *priv; +}; + +/** + * struct vpu_wdt - VPU watchdog workqueue + * + * @handler: VPU watchdog reset handler + * @ws: workstruct for VPU watchdog + * @wq: workqueue for VPU watchdog + */ +struct vpu_wdt { + struct vpu_wdt_handler handler[VPU_RST_MAX]; + struct work_struct ws; + struct workqueue_struct *wq; +}; + +/** + * struct vpu_run - VPU initialization status + * + * @signaled: the signal of vpu initialization completed + * @fw_ver: VPU firmware version + * @dec_capability: decoder capability which is not used for now and + * the value is reserved for future use + * @enc_capability: encoder capability which is not used for now and + * the value is reserved for future use + * @wq: wait queue for VPU initialization status + */ +struct vpu_run { + u32 signaled; + char fw_ver[VPU_FW_VER_LEN]; + unsigned int dec_capability; + unsigned int enc_capability; + wait_queue_head_t wq; +}; + +/** + * struct vpu_ipi_desc - VPU IPI descriptor + * + * @handler: IPI handler + * @name: the name of IPI handler + * @priv: the private data of IPI handler + */ +struct vpu_ipi_desc { + ipi_handler_t handler; + const char *name; + void *priv; +}; + +/** + * struct share_obj - DTCM (Data Tightly-Coupled Memory) buffer shared with + * AP and VPU + * + * @id: IPI id + * @len: share buffer length + * @share_buf: share buffer data + */ +struct share_obj { + s32 id; + u32 len; + unsigned char share_buf[SHARE_BUF_SIZE]; +}; + +/** + * struct mtk_vpu - vpu driver data + * @extmem: VPU extended memory information + * @reg: VPU TCM and configuration registers + * @run: VPU initialization status + * @wdt: VPU watchdog workqueue + * @ipi_desc: VPU IPI descriptor + * @recv_buf: VPU DTCM share buffer for receiving. The + * receive buffer is only accessed in interrupt context. + * @send_buf: VPU DTCM share buffer for sending + * @dev: VPU struct device + * @clk: VPU clock on/off + * @fw_loaded: indicate VPU firmware loaded + * @enable_4GB: VPU 4GB mode on/off + * @vpu_mutex: protect mtk_vpu (except recv_buf) and ensure only + * one client to use VPU service at a time. For example, + * suppose a client is using VPU to decode VP8. + * If the other client wants to encode VP8, + * it has to wait until VP8 decode completes. + * @wdt_refcnt: WDT reference count to make sure the watchdog can be + * disabled if no other client is using VPU service + * @ack_wq: The wait queue for each codec and mdp. When sleeping + * processes wake up, they will check the condition + * "ipi_id_ack" to run the corresponding action or + * go back to sleep. + * @ipi_id_ack: The ACKs for registered IPI function sending + * interrupt to VPU + * + */ +struct mtk_vpu { + struct vpu_mem extmem[2]; + struct vpu_regs reg; + struct vpu_run run; + struct vpu_wdt wdt; + struct vpu_ipi_desc ipi_desc[IPI_MAX]; + struct share_obj __iomem *recv_buf; + struct share_obj __iomem *send_buf; + struct device *dev; + struct clk *clk; + bool fw_loaded; + bool enable_4GB; + struct mutex vpu_mutex; /* for protecting vpu data data structure */ + u32 wdt_refcnt; + wait_queue_head_t ack_wq; + bool ipi_id_ack[IPI_MAX]; +}; + +static inline void vpu_cfg_writel(struct mtk_vpu *vpu, u32 val, u32 offset) +{ + writel(val, vpu->reg.cfg + offset); +} + +static inline u32 vpu_cfg_readl(struct mtk_vpu *vpu, u32 offset) +{ + return readl(vpu->reg.cfg + offset); +} + +static inline bool vpu_running(struct mtk_vpu *vpu) +{ + return vpu_cfg_readl(vpu, VPU_RESET) & BIT(0); +} + +static void vpu_clock_disable(struct mtk_vpu *vpu) +{ + /* Disable VPU watchdog */ + mutex_lock(&vpu->vpu_mutex); + if (!--vpu->wdt_refcnt) + vpu_cfg_writel(vpu, + vpu_cfg_readl(vpu, VPU_WDT_REG) & ~(1L << 31), + VPU_WDT_REG); + mutex_unlock(&vpu->vpu_mutex); + + clk_disable(vpu->clk); +} + +static int vpu_clock_enable(struct mtk_vpu *vpu) +{ + int ret; + + ret = clk_enable(vpu->clk); + if (ret) + return ret; + /* Enable VPU watchdog */ + mutex_lock(&vpu->vpu_mutex); + if (!vpu->wdt_refcnt++) + vpu_cfg_writel(vpu, + vpu_cfg_readl(vpu, VPU_WDT_REG) | (1L << 31), + VPU_WDT_REG); + mutex_unlock(&vpu->vpu_mutex); + + return ret; +} + +static void vpu_dump_status(struct mtk_vpu *vpu) +{ + dev_info(vpu->dev, + "vpu: run %x, pc = 0x%x, ra = 0x%x, sp = 0x%x, idle = 0x%x\n" + "vpu: int %x, hv = 0x%x, vh = 0x%x, wdt = 0x%x\n", + vpu_running(vpu), vpu_cfg_readl(vpu, VPU_PC_REG), + vpu_cfg_readl(vpu, VPU_RA_REG), vpu_cfg_readl(vpu, VPU_SP_REG), + vpu_cfg_readl(vpu, VPU_IDLE_REG), + vpu_cfg_readl(vpu, VPU_INT_STATUS), + vpu_cfg_readl(vpu, HOST_TO_VPU), + vpu_cfg_readl(vpu, VPU_TO_HOST), + vpu_cfg_readl(vpu, VPU_WDT_REG)); +} + +int vpu_ipi_register(struct platform_device *pdev, + enum ipi_id id, ipi_handler_t handler, + const char *name, void *priv) +{ + struct mtk_vpu *vpu = platform_get_drvdata(pdev); + struct vpu_ipi_desc *ipi_desc; + + if (!vpu) { + dev_err(&pdev->dev, "vpu device in not ready\n"); + return -EPROBE_DEFER; + } + + if (id < IPI_MAX && handler) { + ipi_desc = vpu->ipi_desc; + ipi_desc[id].name = name; + ipi_desc[id].handler = handler; + ipi_desc[id].priv = priv; + return 0; + } + + dev_err(&pdev->dev, "register vpu ipi id %d with invalid arguments\n", + id); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(vpu_ipi_register); + +int vpu_ipi_send(struct platform_device *pdev, + enum ipi_id id, void *buf, + unsigned int len) +{ + struct mtk_vpu *vpu = platform_get_drvdata(pdev); + struct share_obj __iomem *send_obj = vpu->send_buf; + unsigned long timeout; + int ret = 0; + + if (id <= IPI_VPU_INIT || id >= IPI_MAX || + len > sizeof(send_obj->share_buf) || !buf) { + dev_err(vpu->dev, "failed to send ipi message\n"); + return -EINVAL; + } + + ret = vpu_clock_enable(vpu); + if (ret) { + dev_err(vpu->dev, "failed to enable vpu clock\n"); + return ret; + } + if (!vpu_running(vpu)) { + dev_err(vpu->dev, "vpu_ipi_send: VPU is not running\n"); + ret = -EINVAL; + goto clock_disable; + } + + mutex_lock(&vpu->vpu_mutex); + + /* Wait until VPU receives the last command */ + timeout = jiffies + msecs_to_jiffies(IPI_TIMEOUT_MS); + do { + if (time_after(jiffies, timeout)) { + dev_err(vpu->dev, "vpu_ipi_send: IPI timeout!\n"); + ret = -EIO; + vpu_dump_status(vpu); + goto mut_unlock; + } + } while (vpu_cfg_readl(vpu, HOST_TO_VPU)); + + memcpy_toio(send_obj->share_buf, buf, len); + writel(len, &send_obj->len); + writel(id, &send_obj->id); + + vpu->ipi_id_ack[id] = false; + /* send the command to VPU */ + vpu_cfg_writel(vpu, 0x1, HOST_TO_VPU); + + mutex_unlock(&vpu->vpu_mutex); + + /* wait for VPU's ACK */ + timeout = msecs_to_jiffies(IPI_TIMEOUT_MS); + ret = wait_event_timeout(vpu->ack_wq, vpu->ipi_id_ack[id], timeout); + vpu->ipi_id_ack[id] = false; + if (ret == 0) { + dev_err(vpu->dev, "vpu ipi %d ack time out !\n", id); + ret = -EIO; + vpu_dump_status(vpu); + goto clock_disable; + } + vpu_clock_disable(vpu); + + return 0; + +mut_unlock: + mutex_unlock(&vpu->vpu_mutex); +clock_disable: + vpu_clock_disable(vpu); + + return ret; +} +EXPORT_SYMBOL_GPL(vpu_ipi_send); + +static void vpu_wdt_reset_func(struct work_struct *ws) +{ + struct vpu_wdt *wdt = container_of(ws, struct vpu_wdt, ws); + struct mtk_vpu *vpu = container_of(wdt, struct mtk_vpu, wdt); + struct vpu_wdt_handler *handler = wdt->handler; + int index, ret; + + dev_info(vpu->dev, "vpu reset\n"); + ret = vpu_clock_enable(vpu); + if (ret) { + dev_err(vpu->dev, "[VPU] wdt enables clock failed %d\n", ret); + return; + } + mutex_lock(&vpu->vpu_mutex); + vpu_cfg_writel(vpu, 0x0, VPU_RESET); + vpu->fw_loaded = false; + mutex_unlock(&vpu->vpu_mutex); + vpu_clock_disable(vpu); + + for (index = 0; index < VPU_RST_MAX; index++) { + if (handler[index].reset_func) { + handler[index].reset_func(handler[index].priv); + dev_dbg(vpu->dev, "wdt handler func %d\n", index); + } + } +} + +int vpu_wdt_reg_handler(struct platform_device *pdev, + void wdt_reset(void *), + void *priv, enum rst_id id) +{ + struct mtk_vpu *vpu = platform_get_drvdata(pdev); + struct vpu_wdt_handler *handler; + + if (!vpu) { + dev_err(&pdev->dev, "vpu device in not ready\n"); + return -EPROBE_DEFER; + } + + handler = vpu->wdt.handler; + + if (id < VPU_RST_MAX && wdt_reset) { + dev_dbg(vpu->dev, "wdt register id %d\n", id); + mutex_lock(&vpu->vpu_mutex); + handler[id].reset_func = wdt_reset; + handler[id].priv = priv; + mutex_unlock(&vpu->vpu_mutex); + return 0; + } + + dev_err(vpu->dev, "register vpu wdt handler failed\n"); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(vpu_wdt_reg_handler); + +unsigned int vpu_get_vdec_hw_capa(struct platform_device *pdev) +{ + struct mtk_vpu *vpu = platform_get_drvdata(pdev); + + return vpu->run.dec_capability; +} +EXPORT_SYMBOL_GPL(vpu_get_vdec_hw_capa); + +unsigned int vpu_get_venc_hw_capa(struct platform_device *pdev) +{ + struct mtk_vpu *vpu = platform_get_drvdata(pdev); + + return vpu->run.enc_capability; +} +EXPORT_SYMBOL_GPL(vpu_get_venc_hw_capa); + +void *vpu_mapping_dm_addr(struct platform_device *pdev, + u32 dtcm_dmem_addr) +{ + struct mtk_vpu *vpu = platform_get_drvdata(pdev); + + if (!dtcm_dmem_addr || + (dtcm_dmem_addr > (VPU_DTCM_SIZE + VPU_EXT_D_SIZE))) { + dev_err(vpu->dev, "invalid virtual data memory address\n"); + return ERR_PTR(-EINVAL); + } + + if (dtcm_dmem_addr < VPU_DTCM_SIZE) + return (__force void *)(dtcm_dmem_addr + vpu->reg.tcm + + VPU_DTCM_OFFSET); + + return vpu->extmem[D_FW].va + (dtcm_dmem_addr - VPU_DTCM_SIZE); +} +EXPORT_SYMBOL_GPL(vpu_mapping_dm_addr); + +struct platform_device *vpu_get_plat_device(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *vpu_node; + struct platform_device *vpu_pdev; + + vpu_node = of_parse_phandle(dev->of_node, "mediatek,vpu", 0); + if (!vpu_node) { + dev_err(dev, "can't get vpu node\n"); + return NULL; + } + + vpu_pdev = of_find_device_by_node(vpu_node); + of_node_put(vpu_node); + if (WARN_ON(!vpu_pdev)) { + dev_err(dev, "vpu pdev failed\n"); + return NULL; + } + + return vpu_pdev; +} +EXPORT_SYMBOL_GPL(vpu_get_plat_device); + +/* load vpu program/data memory */ +static int load_requested_vpu(struct mtk_vpu *vpu, + u8 fw_type) +{ + size_t tcm_size = fw_type ? VPU_DTCM_SIZE : VPU_PTCM_SIZE; + size_t fw_size = fw_type ? VPU_D_FW_SIZE : VPU_P_FW_SIZE; + char *fw_name = fw_type ? VPU_D_FW : VPU_P_FW; + char *fw_new_name = fw_type ? VPU_D_FW_NEW : VPU_P_FW_NEW; + const struct firmware *vpu_fw; + size_t dl_size = 0; + size_t extra_fw_size = 0; + void *dest; + int ret; + + ret = request_firmware(&vpu_fw, fw_new_name, vpu->dev); + if (ret < 0) { + dev_info(vpu->dev, "Failed to load %s, %d, retry\n", + fw_new_name, ret); + + ret = request_firmware(&vpu_fw, fw_name, vpu->dev); + if (ret < 0) { + dev_err(vpu->dev, "Failed to load %s, %d\n", fw_name, + ret); + return ret; + } + } + dl_size = vpu_fw->size; + if (dl_size > fw_size) { + dev_err(vpu->dev, "fw %s size %zu is abnormal\n", fw_name, + dl_size); + release_firmware(vpu_fw); + return -EFBIG; + } + dev_dbg(vpu->dev, "Downloaded fw %s size: %zu.\n", + fw_name, + dl_size); + /* reset VPU */ + vpu_cfg_writel(vpu, 0x0, VPU_RESET); + + /* handle extended firmware size */ + if (dl_size > tcm_size) { + dev_dbg(vpu->dev, "fw size %zu > limited fw size %zu\n", + dl_size, tcm_size); + extra_fw_size = dl_size - tcm_size; + dev_dbg(vpu->dev, "extra_fw_size %zu\n", extra_fw_size); + dl_size = tcm_size; + } + dest = (__force void *)vpu->reg.tcm; + if (fw_type == D_FW) + dest += VPU_DTCM_OFFSET; + memcpy(dest, vpu_fw->data, dl_size); + /* download to extended memory if need */ + if (extra_fw_size > 0) { + dest = vpu->extmem[fw_type].va; + dev_dbg(vpu->dev, "download extended memory type %x\n", + fw_type); + memcpy(dest, vpu_fw->data + tcm_size, extra_fw_size); + } + + release_firmware(vpu_fw); + + return 0; +} + +int vpu_load_firmware(struct platform_device *pdev) +{ + struct mtk_vpu *vpu; + struct device *dev; + struct vpu_run *run; + int ret; + + if (!pdev) { + pr_err("VPU platform device is invalid\n"); + return -EINVAL; + } + + dev = &pdev->dev; + + vpu = platform_get_drvdata(pdev); + run = &vpu->run; + + mutex_lock(&vpu->vpu_mutex); + if (vpu->fw_loaded) { + mutex_unlock(&vpu->vpu_mutex); + return 0; + } + mutex_unlock(&vpu->vpu_mutex); + + ret = vpu_clock_enable(vpu); + if (ret) { + dev_err(dev, "enable clock failed %d\n", ret); + return ret; + } + + mutex_lock(&vpu->vpu_mutex); + + run->signaled = false; + dev_dbg(vpu->dev, "firmware request\n"); + /* Downloading program firmware to device*/ + ret = load_requested_vpu(vpu, P_FW); + if (ret < 0) { + dev_err(dev, "Failed to request %s, %d\n", VPU_P_FW, ret); + goto OUT_LOAD_FW; + } + + /* Downloading data firmware to device */ + ret = load_requested_vpu(vpu, D_FW); + if (ret < 0) { + dev_err(dev, "Failed to request %s, %d\n", VPU_D_FW, ret); + goto OUT_LOAD_FW; + } + + vpu->fw_loaded = true; + /* boot up vpu */ + vpu_cfg_writel(vpu, 0x1, VPU_RESET); + + ret = wait_event_interruptible_timeout(run->wq, + run->signaled, + msecs_to_jiffies(INIT_TIMEOUT_MS) + ); + if (ret == 0) { + ret = -ETIME; + dev_err(dev, "wait vpu initialization timeout!\n"); + goto OUT_LOAD_FW; + } else if (-ERESTARTSYS == ret) { + dev_err(dev, "wait vpu interrupted by a signal!\n"); + goto OUT_LOAD_FW; + } + + ret = 0; + dev_info(dev, "vpu is ready. Fw version %s\n", run->fw_ver); + +OUT_LOAD_FW: + mutex_unlock(&vpu->vpu_mutex); + vpu_clock_disable(vpu); + + return ret; +} +EXPORT_SYMBOL_GPL(vpu_load_firmware); + +static void vpu_init_ipi_handler(const void *data, unsigned int len, void *priv) +{ + struct mtk_vpu *vpu = priv; + const struct vpu_run *run = data; + + vpu->run.signaled = run->signaled; + strscpy(vpu->run.fw_ver, run->fw_ver, sizeof(vpu->run.fw_ver)); + vpu->run.dec_capability = run->dec_capability; + vpu->run.enc_capability = run->enc_capability; + wake_up_interruptible(&vpu->run.wq); +} + +#ifdef CONFIG_DEBUG_FS +static ssize_t vpu_debug_read(struct file *file, char __user *user_buf, + size_t count, loff_t *ppos) +{ + char buf[256]; + unsigned int len; + unsigned int running, pc, vpu_to_host, host_to_vpu, wdt, idle, ra, sp; + int ret; + struct device *dev = file->private_data; + struct mtk_vpu *vpu = dev_get_drvdata(dev); + + ret = vpu_clock_enable(vpu); + if (ret) { + dev_err(vpu->dev, "[VPU] enable clock failed %d\n", ret); + return 0; + } + + /* vpu register status */ + running = vpu_running(vpu); + pc = vpu_cfg_readl(vpu, VPU_PC_REG); + wdt = vpu_cfg_readl(vpu, VPU_WDT_REG); + host_to_vpu = vpu_cfg_readl(vpu, HOST_TO_VPU); + vpu_to_host = vpu_cfg_readl(vpu, VPU_TO_HOST); + ra = vpu_cfg_readl(vpu, VPU_RA_REG); + sp = vpu_cfg_readl(vpu, VPU_SP_REG); + idle = vpu_cfg_readl(vpu, VPU_IDLE_REG); + + vpu_clock_disable(vpu); + + if (running) { + len = snprintf(buf, sizeof(buf), "VPU is running\n\n" + "FW Version: %s\n" + "PC: 0x%x\n" + "WDT: 0x%x\n" + "Host to VPU: 0x%x\n" + "VPU to Host: 0x%x\n" + "SP: 0x%x\n" + "RA: 0x%x\n" + "idle: 0x%x\n", + vpu->run.fw_ver, pc, wdt, + host_to_vpu, vpu_to_host, sp, ra, idle); + } else { + len = snprintf(buf, sizeof(buf), "VPU not running\n"); + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, len); +} + +static const struct file_operations vpu_debug_fops = { + .open = simple_open, + .read = vpu_debug_read, +}; +#endif /* CONFIG_DEBUG_FS */ + +static void vpu_free_ext_mem(struct mtk_vpu *vpu, u8 fw_type) +{ + struct device *dev = vpu->dev; + size_t fw_ext_size = fw_type ? VPU_EXT_D_SIZE : VPU_EXT_P_SIZE; + + dma_free_coherent(dev, fw_ext_size, vpu->extmem[fw_type].va, + vpu->extmem[fw_type].pa); +} + +static int vpu_alloc_ext_mem(struct mtk_vpu *vpu, u32 fw_type) +{ + struct device *dev = vpu->dev; + size_t fw_ext_size = fw_type ? VPU_EXT_D_SIZE : VPU_EXT_P_SIZE; + u32 vpu_ext_mem0 = fw_type ? VPU_DMEM_EXT0_ADDR : VPU_PMEM_EXT0_ADDR; + u32 vpu_ext_mem1 = fw_type ? VPU_DMEM_EXT1_ADDR : VPU_PMEM_EXT1_ADDR; + u32 offset_4gb = vpu->enable_4GB ? 0x40000000 : 0; + + vpu->extmem[fw_type].va = dma_alloc_coherent(dev, + fw_ext_size, + &vpu->extmem[fw_type].pa, + GFP_KERNEL); + if (!vpu->extmem[fw_type].va) { + dev_err(dev, "Failed to allocate the extended program memory\n"); + return -ENOMEM; + } + + /* Disable extend0. Enable extend1 */ + vpu_cfg_writel(vpu, 0x1, vpu_ext_mem0); + vpu_cfg_writel(vpu, (vpu->extmem[fw_type].pa & 0xFFFFF000) + offset_4gb, + vpu_ext_mem1); + + dev_info(dev, "%s extend memory phy=0x%llx virt=0x%p\n", + fw_type ? "Data" : "Program", + (unsigned long long)vpu->extmem[fw_type].pa, + vpu->extmem[fw_type].va); + + return 0; +} + +static void vpu_ipi_handler(struct mtk_vpu *vpu) +{ + struct share_obj __iomem *rcv_obj = vpu->recv_buf; + struct vpu_ipi_desc *ipi_desc = vpu->ipi_desc; + unsigned char data[SHARE_BUF_SIZE]; + s32 id = readl(&rcv_obj->id); + + memcpy_fromio(data, rcv_obj->share_buf, sizeof(data)); + if (id < IPI_MAX && ipi_desc[id].handler) { + ipi_desc[id].handler(data, readl(&rcv_obj->len), + ipi_desc[id].priv); + if (id > IPI_VPU_INIT) { + vpu->ipi_id_ack[id] = true; + wake_up(&vpu->ack_wq); + } + } else { + dev_err(vpu->dev, "No such ipi id = %d\n", id); + } +} + +static int vpu_ipi_init(struct mtk_vpu *vpu) +{ + /* Disable VPU to host interrupt */ + vpu_cfg_writel(vpu, 0x0, VPU_TO_HOST); + + /* shared buffer initialization */ + vpu->recv_buf = vpu->reg.tcm + VPU_DTCM_OFFSET; + vpu->send_buf = vpu->recv_buf + 1; + memset_io(vpu->recv_buf, 0, sizeof(struct share_obj)); + memset_io(vpu->send_buf, 0, sizeof(struct share_obj)); + + return 0; +} + +static irqreturn_t vpu_irq_handler(int irq, void *priv) +{ + struct mtk_vpu *vpu = priv; + u32 vpu_to_host; + int ret; + + /* + * Clock should have been enabled already. + * Enable again in case vpu_ipi_send times out + * and has disabled the clock. + */ + ret = clk_enable(vpu->clk); + if (ret) { + dev_err(vpu->dev, "[VPU] enable clock failed %d\n", ret); + return IRQ_NONE; + } + vpu_to_host = vpu_cfg_readl(vpu, VPU_TO_HOST); + if (vpu_to_host & VPU_IPC_INT) { + vpu_ipi_handler(vpu); + } else { + dev_err(vpu->dev, "vpu watchdog timeout! 0x%x", vpu_to_host); + queue_work(vpu->wdt.wq, &vpu->wdt.ws); + } + + /* VPU won't send another interrupt until we set VPU_TO_HOST to 0. */ + vpu_cfg_writel(vpu, 0x0, VPU_TO_HOST); + clk_disable(vpu->clk); + + return IRQ_HANDLED; +} + +#ifdef CONFIG_DEBUG_FS +static struct dentry *vpu_debugfs; +#endif +static int mtk_vpu_probe(struct platform_device *pdev) +{ + struct mtk_vpu *vpu; + struct device *dev; + int ret = 0; + + dev_dbg(&pdev->dev, "initialization\n"); + + dev = &pdev->dev; + vpu = devm_kzalloc(dev, sizeof(*vpu), GFP_KERNEL); + if (!vpu) + return -ENOMEM; + + vpu->dev = &pdev->dev; + vpu->reg.tcm = devm_platform_ioremap_resource_byname(pdev, "tcm"); + if (IS_ERR((__force void *)vpu->reg.tcm)) + return PTR_ERR((__force void *)vpu->reg.tcm); + + vpu->reg.cfg = devm_platform_ioremap_resource_byname(pdev, "cfg_reg"); + if (IS_ERR((__force void *)vpu->reg.cfg)) + return PTR_ERR((__force void *)vpu->reg.cfg); + + /* Get VPU clock */ + vpu->clk = devm_clk_get(dev, "main"); + if (IS_ERR(vpu->clk)) { + dev_err(dev, "get vpu clock failed\n"); + return PTR_ERR(vpu->clk); + } + + platform_set_drvdata(pdev, vpu); + + ret = clk_prepare(vpu->clk); + if (ret) { + dev_err(dev, "prepare vpu clock failed\n"); + return ret; + } + + /* VPU watchdog */ + vpu->wdt.wq = create_singlethread_workqueue("vpu_wdt"); + if (!vpu->wdt.wq) { + dev_err(dev, "initialize wdt workqueue failed\n"); + ret = -ENOMEM; + goto clk_unprepare; + } + INIT_WORK(&vpu->wdt.ws, vpu_wdt_reset_func); + mutex_init(&vpu->vpu_mutex); + + ret = vpu_clock_enable(vpu); + if (ret) { + dev_err(dev, "enable vpu clock failed\n"); + goto workqueue_destroy; + } + + dev_dbg(dev, "vpu ipi init\n"); + ret = vpu_ipi_init(vpu); + if (ret) { + dev_err(dev, "Failed to init ipi\n"); + goto disable_vpu_clk; + } + + /* register vpu initialization IPI */ + ret = vpu_ipi_register(pdev, IPI_VPU_INIT, vpu_init_ipi_handler, + "vpu_init", vpu); + if (ret) { + dev_err(dev, "Failed to register IPI_VPU_INIT\n"); + goto vpu_mutex_destroy; + } + +#ifdef CONFIG_DEBUG_FS + vpu_debugfs = debugfs_create_file("mtk_vpu", S_IRUGO, NULL, (void *)dev, + &vpu_debug_fops); +#endif + + /* Set PTCM to 96K and DTCM to 32K */ + vpu_cfg_writel(vpu, 0x2, VPU_TCM_CFG); + + vpu->enable_4GB = !!(totalram_pages() > (SZ_2G >> PAGE_SHIFT)); + dev_info(dev, "4GB mode %u\n", vpu->enable_4GB); + + if (vpu->enable_4GB) { + ret = of_reserved_mem_device_init(dev); + if (ret) + dev_info(dev, "init reserved memory failed\n"); + /* continue to use dynamic allocation if failed */ + } + + ret = vpu_alloc_ext_mem(vpu, D_FW); + if (ret) { + dev_err(dev, "Allocate DM failed\n"); + goto remove_debugfs; + } + + ret = vpu_alloc_ext_mem(vpu, P_FW); + if (ret) { + dev_err(dev, "Allocate PM failed\n"); + goto free_d_mem; + } + + init_waitqueue_head(&vpu->run.wq); + init_waitqueue_head(&vpu->ack_wq); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto free_p_mem; + vpu->reg.irq = ret; + ret = devm_request_irq(dev, vpu->reg.irq, vpu_irq_handler, 0, + pdev->name, vpu); + if (ret) { + dev_err(dev, "failed to request irq\n"); + goto free_p_mem; + } + + vpu_clock_disable(vpu); + dev_dbg(dev, "initialization completed\n"); + + return 0; + +free_p_mem: + vpu_free_ext_mem(vpu, P_FW); +free_d_mem: + vpu_free_ext_mem(vpu, D_FW); +remove_debugfs: + of_reserved_mem_device_release(dev); +#ifdef CONFIG_DEBUG_FS + debugfs_remove(vpu_debugfs); +#endif + memset(vpu->ipi_desc, 0, sizeof(struct vpu_ipi_desc) * IPI_MAX); +vpu_mutex_destroy: + mutex_destroy(&vpu->vpu_mutex); +disable_vpu_clk: + vpu_clock_disable(vpu); +workqueue_destroy: + destroy_workqueue(vpu->wdt.wq); +clk_unprepare: + clk_unprepare(vpu->clk); + + return ret; +} + +static const struct of_device_id mtk_vpu_match[] = { + { + .compatible = "mediatek,mt8173-vpu", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, mtk_vpu_match); + +static int mtk_vpu_remove(struct platform_device *pdev) +{ + struct mtk_vpu *vpu = platform_get_drvdata(pdev); + +#ifdef CONFIG_DEBUG_FS + debugfs_remove(vpu_debugfs); +#endif + if (vpu->wdt.wq) + destroy_workqueue(vpu->wdt.wq); + vpu_free_ext_mem(vpu, P_FW); + vpu_free_ext_mem(vpu, D_FW); + mutex_destroy(&vpu->vpu_mutex); + clk_unprepare(vpu->clk); + + return 0; +} + +static int mtk_vpu_suspend(struct device *dev) +{ + struct mtk_vpu *vpu = dev_get_drvdata(dev); + unsigned long timeout; + int ret; + + ret = vpu_clock_enable(vpu); + if (ret) { + dev_err(dev, "failed to enable vpu clock\n"); + return ret; + } + + if (!vpu_running(vpu)) { + vpu_clock_disable(vpu); + clk_unprepare(vpu->clk); + return 0; + } + + mutex_lock(&vpu->vpu_mutex); + /* disable vpu timer interrupt */ + vpu_cfg_writel(vpu, vpu_cfg_readl(vpu, VPU_INT_STATUS) | VPU_IDLE_STATE, + VPU_INT_STATUS); + /* check if vpu is idle for system suspend */ + timeout = jiffies + msecs_to_jiffies(VPU_IDLE_TIMEOUT_MS); + do { + if (time_after(jiffies, timeout)) { + dev_err(dev, "vpu idle timeout\n"); + mutex_unlock(&vpu->vpu_mutex); + vpu_clock_disable(vpu); + return -EIO; + } + } while (!vpu_cfg_readl(vpu, VPU_IDLE_REG)); + + mutex_unlock(&vpu->vpu_mutex); + vpu_clock_disable(vpu); + clk_unprepare(vpu->clk); + + return 0; +} + +static int mtk_vpu_resume(struct device *dev) +{ + struct mtk_vpu *vpu = dev_get_drvdata(dev); + int ret; + + clk_prepare(vpu->clk); + ret = vpu_clock_enable(vpu); + if (ret) { + dev_err(dev, "failed to enable vpu clock\n"); + return ret; + } + + mutex_lock(&vpu->vpu_mutex); + /* enable vpu timer interrupt */ + vpu_cfg_writel(vpu, + vpu_cfg_readl(vpu, VPU_INT_STATUS) & ~(VPU_IDLE_STATE), + VPU_INT_STATUS); + mutex_unlock(&vpu->vpu_mutex); + vpu_clock_disable(vpu); + + return 0; +} + +static const struct dev_pm_ops mtk_vpu_pm = { + .suspend = mtk_vpu_suspend, + .resume = mtk_vpu_resume, +}; + +static struct platform_driver mtk_vpu_driver = { + .probe = mtk_vpu_probe, + .remove = mtk_vpu_remove, + .driver = { + .name = "mtk_vpu", + .pm = &mtk_vpu_pm, + .of_match_table = mtk_vpu_match, + }, +}; + +module_platform_driver(mtk_vpu_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Mediatek Video Processor Unit driver"); diff --git a/drivers/media/platform/mediatek/vpu/mtk_vpu.h b/drivers/media/platform/mediatek/vpu/mtk_vpu.h new file mode 100644 index 000000000..a56053ff1 --- /dev/null +++ b/drivers/media/platform/mediatek/vpu/mtk_vpu.h @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* +* Copyright (c) 2016 MediaTek Inc. +* Author: Andrew-CT Chen +*/ + +#ifndef _MTK_VPU_H +#define _MTK_VPU_H + +#include + +/** + * DOC: VPU + * + * VPU (video processor unit) is a tiny processor controlling video hardware + * related to video codec, scaling and color format converting. + * VPU interfaces with other blocks by share memory and interrupt. + */ + +typedef void (*ipi_handler_t) (const void *data, + unsigned int len, + void *priv); + +/** + * enum ipi_id - the id of inter-processor interrupt + * + * @IPI_VPU_INIT: The interrupt from vpu is to notfiy kernel + * VPU initialization completed. + * IPI_VPU_INIT is sent from VPU when firmware is + * loaded. AP doesn't need to send IPI_VPU_INIT + * command to VPU. + * For other IPI below, AP should send the request + * to VPU to trigger the interrupt. + * @IPI_VDEC_H264: The interrupt from vpu is to notify kernel to + * handle H264 vidoe decoder job, and vice versa. + * Decode output format is always MT21 no matter what + * the input format is. + * @IPI_VDEC_VP8: The interrupt from is to notify kernel to + * handle VP8 video decoder job, and vice versa. + * Decode output format is always MT21 no matter what + * the input format is. + * @IPI_VDEC_VP9: The interrupt from vpu is to notify kernel to + * handle VP9 video decoder job, and vice versa. + * Decode output format is always MT21 no matter what + * the input format is. + * @IPI_VENC_H264: The interrupt from vpu is to notify kernel to + * handle H264 video encoder job, and vice versa. + * @IPI_VENC_VP8: The interrupt fro vpu is to notify kernel to + * handle VP8 video encoder job,, and vice versa. + * @IPI_MDP: The interrupt from vpu is to notify kernel to + * handle MDP (Media Data Path) job, and vice versa. + * @IPI_MAX: The maximum IPI number + */ + +enum ipi_id { + IPI_VPU_INIT = 0, + IPI_VDEC_H264, + IPI_VDEC_VP8, + IPI_VDEC_VP9, + IPI_VENC_H264, + IPI_VENC_VP8, + IPI_MDP, + IPI_MAX, +}; + +/** + * enum rst_id - reset id to register reset function for VPU watchdog timeout + * + * @VPU_RST_ENC: encoder reset id + * @VPU_RST_DEC: decoder reset id + * @VPU_RST_MDP: MDP (Media Data Path) reset id + * @VPU_RST_MAX: maximum reset id + */ +enum rst_id { + VPU_RST_ENC, + VPU_RST_DEC, + VPU_RST_MDP, + VPU_RST_MAX, +}; + +/** + * vpu_ipi_register - register an ipi function + * + * @pdev: VPU platform device + * @id: IPI ID + * @handler: IPI handler + * @name: IPI name + * @priv: private data for IPI handler + * + * Register an ipi function to receive ipi interrupt from VPU. + * + * Return: Return 0 if ipi registers successfully, otherwise it is failed. + */ +int vpu_ipi_register(struct platform_device *pdev, enum ipi_id id, + ipi_handler_t handler, const char *name, void *priv); + +/** + * vpu_ipi_send - send data from AP to vpu. + * + * @pdev: VPU platform device + * @id: IPI ID + * @buf: the data buffer + * @len: the data buffer length + * + * This function is thread-safe. When this function returns, + * VPU has received the data and starts the processing. + * When the processing completes, IPI handler registered + * by vpu_ipi_register will be called in interrupt context. + * + * Return: Return 0 if sending data successfully, otherwise it is failed. + **/ +int vpu_ipi_send(struct platform_device *pdev, + enum ipi_id id, void *buf, + unsigned int len); + +/** + * vpu_get_plat_device - get VPU's platform device + * + * @pdev: the platform device of the module requesting VPU platform + * device for using VPU API. + * + * Return: Return NULL if it is failed. + * otherwise it is VPU's platform device + **/ +struct platform_device *vpu_get_plat_device(struct platform_device *pdev); + +/** + * vpu_wdt_reg_handler - register a VPU watchdog handler + * + * @pdev: VPU platform device + * @vpu_wdt_reset_func(): the callback reset function + * @priv: the private data for reset function + * @priv: the private data for reset function + * @id: reset id + * + * Register a handler performing own tasks when vpu reset by watchdog + * + * Return: Return 0 if the handler is added successfully, + * otherwise it is failed. + **/ +int vpu_wdt_reg_handler(struct platform_device *pdev, + void vpu_wdt_reset_func(void *priv), + void *priv, enum rst_id id); + +/** + * vpu_get_vdec_hw_capa - get video decoder hardware capability + * + * @pdev: VPU platform device + * + * Return: video decoder hardware capability + **/ +unsigned int vpu_get_vdec_hw_capa(struct platform_device *pdev); + +/** + * vpu_get_venc_hw_capa - get video encoder hardware capability + * + * @pdev: VPU platform device + * + * Return: video encoder hardware capability + **/ +unsigned int vpu_get_venc_hw_capa(struct platform_device *pdev); + +/** + * vpu_load_firmware - download VPU firmware and boot it + * + * @pdev: VPU platform device + * + * Return: Return 0 if downloading firmware successfully, + * otherwise it is failed + **/ +int vpu_load_firmware(struct platform_device *pdev); + +/** + * vpu_mapping_dm_addr - Mapping DTCM/DMEM to kernel virtual address + * + * @pdev: VPU platform device + * @dtcm_dmem_addr: VPU's data memory address + * + * Mapping the VPU's DTCM (Data Tightly-Coupled Memory) / + * DMEM (Data Extended Memory) memory address to + * kernel virtual address. + * + * Return: Return ERR_PTR(-EINVAL) if mapping failed, + * otherwise the mapped kernel virtual address + **/ +void *vpu_mapping_dm_addr(struct platform_device *pdev, + u32 dtcm_dmem_addr); +#endif /* _MTK_VPU_H */ diff --git a/drivers/media/platform/nvidia/Kconfig b/drivers/media/platform/nvidia/Kconfig new file mode 100644 index 000000000..b211b4687 --- /dev/null +++ b/drivers/media/platform/nvidia/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "NVidia media platform drivers" + +source "drivers/media/platform/nvidia/tegra-vde/Kconfig" diff --git a/drivers/media/platform/nvidia/Makefile b/drivers/media/platform/nvidia/Makefile new file mode 100644 index 000000000..428415ff8 --- /dev/null +++ b/drivers/media/platform/nvidia/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-y += tegra-vde/ diff --git a/drivers/media/platform/nvidia/tegra-vde/Kconfig b/drivers/media/platform/nvidia/tegra-vde/Kconfig new file mode 100644 index 000000000..f7454823b --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/Kconfig @@ -0,0 +1,17 @@ +config VIDEO_TEGRA_VDE + tristate "NVIDIA Tegra Video Decoder Engine driver" + depends on V4L_MEM2MEM_DRIVERS + depends on ARCH_TEGRA || COMPILE_TEST + depends on VIDEO_DEV + select DMA_SHARED_BUFFER + select IOMMU_IOVA + select MEDIA_CONTROLLER + select MEDIA_CONTROLLER_REQUEST_API + select SRAM + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_DMA_SG + select V4L2_H264 + select V4L2_MEM2MEM_DEV + help + Support for the NVIDIA Tegra video decoder unit. + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/nvidia/tegra-vde/Makefile b/drivers/media/platform/nvidia/tegra-vde/Makefile new file mode 100644 index 000000000..4e96f3305 --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +tegra-vde-y := vde.o iommu.o dmabuf-cache.o h264.o v4l2.o +obj-$(CONFIG_VIDEO_TEGRA_VDE) += tegra-vde.o diff --git a/drivers/media/platform/nvidia/tegra-vde/dmabuf-cache.c b/drivers/media/platform/nvidia/tegra-vde/dmabuf-cache.c new file mode 100644 index 000000000..69c346148 --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/dmabuf-cache.c @@ -0,0 +1,229 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NVIDIA Tegra Video decoder driver + * + * Copyright (C) 2016-2019 GRATE-DRIVER project + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vde.h" + +MODULE_IMPORT_NS(DMA_BUF); + +struct tegra_vde_cache_entry { + enum dma_data_direction dma_dir; + struct dma_buf_attachment *a; + struct delayed_work dwork; + struct tegra_vde *vde; + struct list_head list; + struct sg_table *sgt; + struct iova *iova; + unsigned int refcnt; +}; + +static void tegra_vde_release_entry(struct tegra_vde_cache_entry *entry) +{ + struct dma_buf *dmabuf = entry->a->dmabuf; + + WARN_ON_ONCE(entry->refcnt); + + if (entry->vde->domain) + tegra_vde_iommu_unmap(entry->vde, entry->iova); + + dma_buf_unmap_attachment(entry->a, entry->sgt, entry->dma_dir); + dma_buf_detach(dmabuf, entry->a); + dma_buf_put(dmabuf); + + list_del(&entry->list); + kfree(entry); +} + +static void tegra_vde_delayed_unmap(struct work_struct *work) +{ + struct tegra_vde_cache_entry *entry; + struct tegra_vde *vde; + + entry = container_of(work, struct tegra_vde_cache_entry, + dwork.work); + vde = entry->vde; + + mutex_lock(&vde->map_lock); + tegra_vde_release_entry(entry); + mutex_unlock(&vde->map_lock); +} + +int tegra_vde_dmabuf_cache_map(struct tegra_vde *vde, + struct dma_buf *dmabuf, + enum dma_data_direction dma_dir, + struct dma_buf_attachment **ap, + dma_addr_t *addrp) +{ + struct dma_buf_attachment *attachment; + struct tegra_vde_cache_entry *entry; + struct device *dev = vde->dev; + struct sg_table *sgt; + struct iova *iova; + int err; + + mutex_lock(&vde->map_lock); + + list_for_each_entry(entry, &vde->map_list, list) { + if (entry->a->dmabuf != dmabuf) + continue; + + if (!cancel_delayed_work(&entry->dwork)) + continue; + + if (entry->dma_dir != dma_dir) + entry->dma_dir = DMA_BIDIRECTIONAL; + + dma_buf_put(dmabuf); + + if (vde->domain) + *addrp = iova_dma_addr(&vde->iova, entry->iova); + else + *addrp = sg_dma_address(entry->sgt->sgl); + + goto ref; + } + + attachment = dma_buf_attach(dmabuf, dev); + if (IS_ERR(attachment)) { + dev_err(dev, "Failed to attach dmabuf\n"); + err = PTR_ERR(attachment); + goto err_unlock; + } + + sgt = dma_buf_map_attachment(attachment, dma_dir); + if (IS_ERR(sgt)) { + dev_err(dev, "Failed to get dmabufs sg_table\n"); + err = PTR_ERR(sgt); + goto err_detach; + } + + if (!vde->domain && sgt->nents > 1) { + dev_err(dev, "Sparse DMA region is unsupported, please enable IOMMU\n"); + err = -EINVAL; + goto err_unmap; + } + + entry = kzalloc(sizeof(*entry), GFP_KERNEL); + if (!entry) { + err = -ENOMEM; + goto err_unmap; + } + + if (vde->domain) { + err = tegra_vde_iommu_map(vde, sgt, &iova, dmabuf->size); + if (err) + goto err_free; + + *addrp = iova_dma_addr(&vde->iova, iova); + } else { + *addrp = sg_dma_address(sgt->sgl); + iova = NULL; + } + + INIT_DELAYED_WORK(&entry->dwork, tegra_vde_delayed_unmap); + list_add(&entry->list, &vde->map_list); + + entry->dma_dir = dma_dir; + entry->iova = iova; + entry->vde = vde; + entry->sgt = sgt; + entry->a = attachment; +ref: + entry->refcnt++; + + *ap = entry->a; + + mutex_unlock(&vde->map_lock); + + return 0; + +err_free: + kfree(entry); +err_unmap: + dma_buf_unmap_attachment(attachment, sgt, dma_dir); +err_detach: + dma_buf_detach(dmabuf, attachment); +err_unlock: + mutex_unlock(&vde->map_lock); + + return err; +} + +void tegra_vde_dmabuf_cache_unmap(struct tegra_vde *vde, + struct dma_buf_attachment *a, + bool release) +{ + struct tegra_vde_cache_entry *entry; + + mutex_lock(&vde->map_lock); + + list_for_each_entry(entry, &vde->map_list, list) { + if (entry->a != a) + continue; + + WARN_ON_ONCE(!entry->refcnt); + + if (--entry->refcnt == 0) { + if (release) + tegra_vde_release_entry(entry); + else + schedule_delayed_work(&entry->dwork, 5 * HZ); + } + break; + } + + mutex_unlock(&vde->map_lock); +} + +void tegra_vde_dmabuf_cache_unmap_sync(struct tegra_vde *vde) +{ + struct tegra_vde_cache_entry *entry, *tmp; + + mutex_lock(&vde->map_lock); + + list_for_each_entry_safe(entry, tmp, &vde->map_list, list) { + if (entry->refcnt) + continue; + + if (!cancel_delayed_work(&entry->dwork)) + continue; + + tegra_vde_release_entry(entry); + } + + mutex_unlock(&vde->map_lock); +} + +void tegra_vde_dmabuf_cache_unmap_all(struct tegra_vde *vde) +{ + struct tegra_vde_cache_entry *entry, *tmp; + + mutex_lock(&vde->map_lock); + + while (!list_empty(&vde->map_list)) { + list_for_each_entry_safe(entry, tmp, &vde->map_list, list) { + if (!cancel_delayed_work(&entry->dwork)) + continue; + + tegra_vde_release_entry(entry); + } + + mutex_unlock(&vde->map_lock); + schedule(); + mutex_lock(&vde->map_lock); + } + + mutex_unlock(&vde->map_lock); +} diff --git a/drivers/media/platform/nvidia/tegra-vde/h264.c b/drivers/media/platform/nvidia/tegra-vde/h264.c new file mode 100644 index 000000000..204e474d5 --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/h264.c @@ -0,0 +1,950 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NVIDIA Tegra Video decoder driver + * + * Copyright (C) 2016-2022 Dmitry Osipenko + * + */ + +#include +#include +#include +#include + +#include + +#include "trace.h" +#include "vde.h" + +#define FLAG_B_FRAME 0x1 +#define FLAG_REFERENCE 0x2 + +struct tegra_vde_h264_frame { + unsigned int frame_num; + unsigned int flags; +}; + +struct tegra_vde_h264_decoder_ctx { + unsigned int dpb_frames_nb; + unsigned int dpb_ref_frames_with_earlier_poc_nb; + unsigned int baseline_profile; + unsigned int level_idc; + unsigned int log2_max_pic_order_cnt_lsb; + unsigned int log2_max_frame_num; + unsigned int pic_order_cnt_type; + unsigned int direct_8x8_inference_flag; + unsigned int pic_width_in_mbs; + unsigned int pic_height_in_mbs; + unsigned int pic_init_qp; + unsigned int deblocking_filter_control_present_flag; + unsigned int constrained_intra_pred_flag; + unsigned int chroma_qp_index_offset; + unsigned int pic_order_present_flag; + unsigned int num_ref_idx_l0_active_minus1; + unsigned int num_ref_idx_l1_active_minus1; +}; + +struct h264_reflists { + struct v4l2_h264_reference p[V4L2_H264_NUM_DPB_ENTRIES]; + struct v4l2_h264_reference b0[V4L2_H264_NUM_DPB_ENTRIES]; + struct v4l2_h264_reference b1[V4L2_H264_NUM_DPB_ENTRIES]; +}; + +static int tegra_vde_wait_mbe(struct tegra_vde *vde) +{ + u32 tmp; + + return readl_relaxed_poll_timeout(vde->mbe + 0x8C, tmp, + tmp >= 0x10, 1, 100); +} + +static int tegra_vde_setup_mbe_frame_idx(struct tegra_vde *vde, + unsigned int refs_nb, + bool setup_refs) +{ + u32 value, frame_idx_enb_mask = 0; + unsigned int frame_idx; + unsigned int idx; + int err; + + tegra_vde_writel(vde, 0xD0000000 | (0 << 23), vde->mbe, 0x80); + tegra_vde_writel(vde, 0xD0200000 | (0 << 23), vde->mbe, 0x80); + + err = tegra_vde_wait_mbe(vde); + if (err) + return err; + + if (!setup_refs) + return 0; + + for (idx = 0, frame_idx = 1; idx < refs_nb; idx++, frame_idx++) { + tegra_vde_writel(vde, 0xD0000000 | (frame_idx << 23), + vde->mbe, 0x80); + tegra_vde_writel(vde, 0xD0200000 | (frame_idx << 23), + vde->mbe, 0x80); + + frame_idx_enb_mask |= frame_idx << (6 * (idx % 4)); + + if (idx % 4 == 3 || idx == refs_nb - 1) { + value = 0xC0000000; + value |= (idx >> 2) << 24; + value |= frame_idx_enb_mask; + + tegra_vde_writel(vde, value, vde->mbe, 0x80); + + err = tegra_vde_wait_mbe(vde); + if (err) + return err; + + frame_idx_enb_mask = 0; + } + } + + return 0; +} + +static void tegra_vde_mbe_set_0xa_reg(struct tegra_vde *vde, int reg, u32 val) +{ + tegra_vde_writel(vde, 0xA0000000 | (reg << 24) | (val & 0xFFFF), + vde->mbe, 0x80); + tegra_vde_writel(vde, 0xA0000000 | ((reg + 1) << 24) | (val >> 16), + vde->mbe, 0x80); +} + +static int tegra_vde_wait_bsev(struct tegra_vde *vde, bool wait_dma) +{ + struct device *dev = vde->dev; + u32 value; + int err; + + err = readl_relaxed_poll_timeout(vde->bsev + INTR_STATUS, value, + !(value & BIT(2)), 1, 100); + if (err) { + dev_err(dev, "BSEV unknown bit timeout\n"); + return err; + } + + err = readl_relaxed_poll_timeout(vde->bsev + INTR_STATUS, value, + (value & BSE_ICMDQUE_EMPTY), 1, 100); + if (err) { + dev_err(dev, "BSEV ICMDQUE flush timeout\n"); + return err; + } + + if (!wait_dma) + return 0; + + err = readl_relaxed_poll_timeout(vde->bsev + INTR_STATUS, value, + !(value & BSE_DMA_BUSY), 1, 1000); + if (err) { + dev_err(dev, "BSEV DMA timeout\n"); + return err; + } + + return 0; +} + +static int tegra_vde_push_to_bsev_icmdqueue(struct tegra_vde *vde, + u32 value, bool wait_dma) +{ + tegra_vde_writel(vde, value, vde->bsev, ICMDQUE_WR); + + return tegra_vde_wait_bsev(vde, wait_dma); +} + +static void tegra_vde_setup_frameid(struct tegra_vde *vde, + struct tegra_video_frame *frame, + unsigned int frameid, + u32 mbs_width, u32 mbs_height) +{ + u32 y_addr = frame ? frame->y_addr : 0x6CDEAD00; + u32 cb_addr = frame ? frame->cb_addr : 0x6CDEAD00; + u32 cr_addr = frame ? frame->cr_addr : 0x6CDEAD00; + u32 value1 = frame ? ((frame->luma_atoms_pitch << 16) | mbs_height) : 0; + u32 value2 = frame ? ((frame->chroma_atoms_pitch << 6) | 1) : 0; + + tegra_vde_writel(vde, y_addr >> 8, vde->frameid, 0x000 + frameid * 4); + tegra_vde_writel(vde, cb_addr >> 8, vde->frameid, 0x100 + frameid * 4); + tegra_vde_writel(vde, cr_addr >> 8, vde->frameid, 0x180 + frameid * 4); + tegra_vde_writel(vde, value1, vde->frameid, 0x080 + frameid * 4); + tegra_vde_writel(vde, value2, vde->frameid, 0x280 + frameid * 4); +} + +static void tegra_setup_frameidx(struct tegra_vde *vde, + struct tegra_video_frame *frames, + unsigned int frames_nb, + u32 mbs_width, u32 mbs_height) +{ + unsigned int idx; + + for (idx = 0; idx < frames_nb; idx++) + tegra_vde_setup_frameid(vde, &frames[idx], idx, + mbs_width, mbs_height); + + for (; idx < 17; idx++) + tegra_vde_setup_frameid(vde, NULL, idx, 0, 0); +} + +static void tegra_vde_setup_iram_entry(struct tegra_vde *vde, + unsigned int table, + unsigned int row, + u32 value1, u32 value2) +{ + u32 *iram_tables = vde->iram; + + trace_vde_setup_iram_entry(table, row, value1, value2); + + iram_tables[0x20 * table + row * 2 + 0] = value1; + iram_tables[0x20 * table + row * 2 + 1] = value2; +} + +static void tegra_vde_setup_iram_tables(struct tegra_vde *vde, + struct tegra_video_frame *dpb_frames, + unsigned int ref_frames_nb, + unsigned int with_earlier_poc_nb) +{ + struct tegra_video_frame *frame; + int with_later_poc_nb; + u32 value, aux_addr; + unsigned int i, k; + + trace_vde_ref_l0(dpb_frames[0].frame_num); + + for (i = 0; i < 16; i++) { + if (i < ref_frames_nb) { + frame = &dpb_frames[i + 1]; + + aux_addr = frame->aux_addr; + + value = (i + 1) << 26; + value |= !(frame->flags & FLAG_B_FRAME) << 25; + value |= 1 << 24; + value |= frame->frame_num; + } else { + aux_addr = 0x6ADEAD00; + value = 0x3f; + } + + tegra_vde_setup_iram_entry(vde, 0, i, value, aux_addr); + tegra_vde_setup_iram_entry(vde, 1, i, value, aux_addr); + tegra_vde_setup_iram_entry(vde, 2, i, value, aux_addr); + tegra_vde_setup_iram_entry(vde, 3, i, value, aux_addr); + } + + if (!(dpb_frames[0].flags & FLAG_B_FRAME)) + return; + + if (with_earlier_poc_nb >= ref_frames_nb) + return; + + with_later_poc_nb = ref_frames_nb - with_earlier_poc_nb; + + trace_vde_ref_l1(with_later_poc_nb, with_earlier_poc_nb); + + for (i = 0, k = with_earlier_poc_nb; i < with_later_poc_nb; i++, k++) { + frame = &dpb_frames[k + 1]; + + aux_addr = frame->aux_addr; + + value = (k + 1) << 26; + value |= !(frame->flags & FLAG_B_FRAME) << 25; + value |= 1 << 24; + value |= frame->frame_num; + + tegra_vde_setup_iram_entry(vde, 2, i, value, aux_addr); + } + + for (k = 0; i < ref_frames_nb; i++, k++) { + frame = &dpb_frames[k + 1]; + + aux_addr = frame->aux_addr; + + value = (k + 1) << 26; + value |= !(frame->flags & FLAG_B_FRAME) << 25; + value |= 1 << 24; + value |= frame->frame_num; + + tegra_vde_setup_iram_entry(vde, 2, i, value, aux_addr); + } +} + +static int tegra_vde_setup_hw_context(struct tegra_vde *vde, + struct tegra_vde_h264_decoder_ctx *ctx, + struct tegra_video_frame *dpb_frames, + dma_addr_t bitstream_data_addr, + size_t bitstream_data_size, + unsigned int macroblocks_nb) +{ + struct device *dev = vde->dev; + u32 value; + int err; + + tegra_vde_set_bits(vde, 0x000A, vde->sxe, 0xF0); + tegra_vde_set_bits(vde, 0x000B, vde->bsev, CMDQUE_CONTROL); + tegra_vde_set_bits(vde, 0x8002, vde->mbe, 0x50); + tegra_vde_set_bits(vde, 0x000A, vde->mbe, 0xA0); + tegra_vde_set_bits(vde, 0x000A, vde->ppe, 0x14); + tegra_vde_set_bits(vde, 0x000A, vde->ppe, 0x28); + tegra_vde_set_bits(vde, 0x0A00, vde->mce, 0x08); + tegra_vde_set_bits(vde, 0x000A, vde->tfe, 0x00); + tegra_vde_set_bits(vde, 0x0005, vde->vdma, 0x04); + + tegra_vde_writel(vde, 0x00000000, vde->vdma, 0x1C); + tegra_vde_writel(vde, 0x00000000, vde->vdma, 0x00); + tegra_vde_writel(vde, 0x00000007, vde->vdma, 0x04); + tegra_vde_writel(vde, 0x00000007, vde->frameid, 0x200); + tegra_vde_writel(vde, 0x00000005, vde->tfe, 0x04); + tegra_vde_writel(vde, 0x00000000, vde->mbe, 0x84); + tegra_vde_writel(vde, 0x00000010, vde->sxe, 0x08); + tegra_vde_writel(vde, 0x00000150, vde->sxe, 0x54); + tegra_vde_writel(vde, 0x0000054C, vde->sxe, 0x58); + tegra_vde_writel(vde, 0x00000E34, vde->sxe, 0x5C); + tegra_vde_writel(vde, 0x063C063C, vde->mce, 0x10); + tegra_vde_writel(vde, 0x0003FC00, vde->bsev, INTR_STATUS); + tegra_vde_writel(vde, 0x0000150D, vde->bsev, BSE_CONFIG); + tegra_vde_writel(vde, 0x00000100, vde->bsev, BSE_INT_ENB); + tegra_vde_writel(vde, 0x00000000, vde->bsev, 0x98); + tegra_vde_writel(vde, 0x00000060, vde->bsev, 0x9C); + + memset(vde->iram + 128, 0, macroblocks_nb / 2); + + tegra_setup_frameidx(vde, dpb_frames, ctx->dpb_frames_nb, + ctx->pic_width_in_mbs, ctx->pic_height_in_mbs); + + tegra_vde_setup_iram_tables(vde, dpb_frames, + ctx->dpb_frames_nb - 1, + ctx->dpb_ref_frames_with_earlier_poc_nb); + + /* + * The IRAM mapping is write-combine, ensure that CPU buffers have + * been flushed at this point. + */ + wmb(); + + tegra_vde_writel(vde, 0x00000000, vde->bsev, 0x8C); + tegra_vde_writel(vde, bitstream_data_addr + bitstream_data_size, + vde->bsev, 0x54); + + vde->bitstream_data_addr = bitstream_data_addr; + + value = ctx->pic_width_in_mbs << 11 | ctx->pic_height_in_mbs << 3; + + tegra_vde_writel(vde, value, vde->bsev, 0x88); + + err = tegra_vde_wait_bsev(vde, false); + if (err) + return err; + + err = tegra_vde_push_to_bsev_icmdqueue(vde, 0x800003FC, false); + if (err) + return err; + + value = 0x01500000; + value |= ((vde->iram_lists_addr + 512) >> 2) & 0xFFFF; + + err = tegra_vde_push_to_bsev_icmdqueue(vde, value, true); + if (err) + return err; + + err = tegra_vde_push_to_bsev_icmdqueue(vde, 0x840F054C, false); + if (err) + return err; + + err = tegra_vde_push_to_bsev_icmdqueue(vde, 0x80000080, false); + if (err) + return err; + + value = 0x0E340000 | ((vde->iram_lists_addr >> 2) & 0xFFFF); + + err = tegra_vde_push_to_bsev_icmdqueue(vde, value, true); + if (err) + return err; + + value = 0x00800005; + value |= ctx->pic_width_in_mbs << 11; + value |= ctx->pic_height_in_mbs << 3; + + tegra_vde_writel(vde, value, vde->sxe, 0x10); + + value = !ctx->baseline_profile << 17; + value |= ctx->level_idc << 13; + value |= ctx->log2_max_pic_order_cnt_lsb << 7; + value |= ctx->pic_order_cnt_type << 5; + value |= ctx->log2_max_frame_num; + + tegra_vde_writel(vde, value, vde->sxe, 0x40); + + value = ctx->pic_init_qp << 25; + value |= !!(ctx->deblocking_filter_control_present_flag) << 2; + value |= !!ctx->pic_order_present_flag; + + tegra_vde_writel(vde, value, vde->sxe, 0x44); + + value = ctx->chroma_qp_index_offset; + value |= ctx->num_ref_idx_l0_active_minus1 << 5; + value |= ctx->num_ref_idx_l1_active_minus1 << 10; + value |= !!ctx->constrained_intra_pred_flag << 15; + + tegra_vde_writel(vde, value, vde->sxe, 0x48); + + value = 0x0C000000; + value |= !!(dpb_frames[0].flags & FLAG_B_FRAME) << 24; + + tegra_vde_writel(vde, value, vde->sxe, 0x4C); + + value = 0x03800000; + value |= bitstream_data_size & GENMASK(19, 15); + + tegra_vde_writel(vde, value, vde->sxe, 0x68); + + tegra_vde_writel(vde, bitstream_data_addr, vde->sxe, 0x6C); + + if (vde->soc->supports_ref_pic_marking) + tegra_vde_writel(vde, vde->secure_bo->dma_addr, vde->sxe, 0x7c); + + value = 0x10000005; + value |= ctx->pic_width_in_mbs << 11; + value |= ctx->pic_height_in_mbs << 3; + + tegra_vde_writel(vde, value, vde->mbe, 0x80); + + value = 0x26800000; + value |= ctx->level_idc << 4; + value |= !ctx->baseline_profile << 1; + value |= !!ctx->direct_8x8_inference_flag; + + tegra_vde_writel(vde, value, vde->mbe, 0x80); + + tegra_vde_writel(vde, 0xF4000001, vde->mbe, 0x80); + tegra_vde_writel(vde, 0x20000000, vde->mbe, 0x80); + tegra_vde_writel(vde, 0xF4000101, vde->mbe, 0x80); + + value = 0x20000000; + value |= ctx->chroma_qp_index_offset << 8; + + tegra_vde_writel(vde, value, vde->mbe, 0x80); + + err = tegra_vde_setup_mbe_frame_idx(vde, + ctx->dpb_frames_nb - 1, + ctx->pic_order_cnt_type == 0); + if (err) { + dev_err(dev, "MBE frames setup failed %d\n", err); + return err; + } + + tegra_vde_mbe_set_0xa_reg(vde, 0, 0x000009FC); + tegra_vde_mbe_set_0xa_reg(vde, 2, 0x61DEAD00); + tegra_vde_mbe_set_0xa_reg(vde, 4, 0x62DEAD00); + tegra_vde_mbe_set_0xa_reg(vde, 6, 0x63DEAD00); + tegra_vde_mbe_set_0xa_reg(vde, 8, dpb_frames[0].aux_addr); + + value = 0xFC000000; + value |= !!(dpb_frames[0].flags & FLAG_B_FRAME) << 2; + + if (!ctx->baseline_profile) + value |= !!(dpb_frames[0].flags & FLAG_REFERENCE) << 1; + + tegra_vde_writel(vde, value, vde->mbe, 0x80); + + err = tegra_vde_wait_mbe(vde); + if (err) { + dev_err(dev, "MBE programming failed %d\n", err); + return err; + } + + return 0; +} + +static void tegra_vde_decode_frame(struct tegra_vde *vde, + unsigned int macroblocks_nb) +{ + reinit_completion(&vde->decode_completion); + + tegra_vde_writel(vde, 0x00000001, vde->bsev, 0x8C); + tegra_vde_writel(vde, 0x20000000 | (macroblocks_nb - 1), + vde->sxe, 0x00); +} + +static int tegra_vde_validate_h264_ctx(struct device *dev, + struct tegra_vde_h264_decoder_ctx *ctx) +{ + if (ctx->dpb_frames_nb == 0 || ctx->dpb_frames_nb > 17) { + dev_err(dev, "Bad DPB size %u\n", ctx->dpb_frames_nb); + return -EINVAL; + } + + if (ctx->level_idc > 15) { + dev_err(dev, "Bad level value %u\n", ctx->level_idc); + return -EINVAL; + } + + if (ctx->pic_init_qp > 52) { + dev_err(dev, "Bad pic_init_qp value %u\n", ctx->pic_init_qp); + return -EINVAL; + } + + if (ctx->log2_max_pic_order_cnt_lsb > 16) { + dev_err(dev, "Bad log2_max_pic_order_cnt_lsb value %u\n", + ctx->log2_max_pic_order_cnt_lsb); + return -EINVAL; + } + + if (ctx->log2_max_frame_num > 16) { + dev_err(dev, "Bad log2_max_frame_num value %u\n", + ctx->log2_max_frame_num); + return -EINVAL; + } + + if (ctx->chroma_qp_index_offset > 31) { + dev_err(dev, "Bad chroma_qp_index_offset value %u\n", + ctx->chroma_qp_index_offset); + return -EINVAL; + } + + if (ctx->pic_order_cnt_type > 2) { + dev_err(dev, "Bad pic_order_cnt_type value %u\n", + ctx->pic_order_cnt_type); + return -EINVAL; + } + + if (ctx->num_ref_idx_l0_active_minus1 > 15) { + dev_err(dev, "Bad num_ref_idx_l0_active_minus1 value %u\n", + ctx->num_ref_idx_l0_active_minus1); + return -EINVAL; + } + + if (ctx->num_ref_idx_l1_active_minus1 > 15) { + dev_err(dev, "Bad num_ref_idx_l1_active_minus1 value %u\n", + ctx->num_ref_idx_l1_active_minus1); + return -EINVAL; + } + + if (!ctx->pic_width_in_mbs || ctx->pic_width_in_mbs > 127) { + dev_err(dev, "Bad pic_width_in_mbs value %u\n", + ctx->pic_width_in_mbs); + return -EINVAL; + } + + if (!ctx->pic_height_in_mbs || ctx->pic_height_in_mbs > 127) { + dev_err(dev, "Bad pic_height_in_mbs value %u\n", + ctx->pic_height_in_mbs); + return -EINVAL; + } + + return 0; +} + +static int tegra_vde_decode_begin(struct tegra_vde *vde, + struct tegra_vde_h264_decoder_ctx *ctx, + struct tegra_video_frame *dpb_frames, + dma_addr_t bitstream_data_addr, + size_t bitstream_data_size) +{ + struct device *dev = vde->dev; + unsigned int macroblocks_nb; + int err; + + err = mutex_lock_interruptible(&vde->lock); + if (err) + return err; + + err = pm_runtime_resume_and_get(dev); + if (err < 0) + goto unlock; + + /* + * We rely on the VDE registers reset value, otherwise VDE + * causes bus lockup. + */ + err = reset_control_assert(vde->rst_mc); + if (err) { + dev_err(dev, "DEC start: Failed to assert MC reset: %d\n", + err); + goto put_runtime_pm; + } + + err = reset_control_reset(vde->rst); + if (err) { + dev_err(dev, "DEC start: Failed to reset HW: %d\n", err); + goto put_runtime_pm; + } + + err = reset_control_deassert(vde->rst_mc); + if (err) { + dev_err(dev, "DEC start: Failed to deassert MC reset: %d\n", + err); + goto put_runtime_pm; + } + + macroblocks_nb = ctx->pic_width_in_mbs * ctx->pic_height_in_mbs; + + err = tegra_vde_setup_hw_context(vde, ctx, dpb_frames, + bitstream_data_addr, + bitstream_data_size, + macroblocks_nb); + if (err) + goto put_runtime_pm; + + tegra_vde_decode_frame(vde, macroblocks_nb); + + return 0; + +put_runtime_pm: + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + +unlock: + mutex_unlock(&vde->lock); + + return err; +} + +static void tegra_vde_decode_abort(struct tegra_vde *vde) +{ + struct device *dev = vde->dev; + int err; + + /* + * At first reset memory client to avoid resetting VDE HW in the + * middle of DMA which could result into memory corruption or hang + * the whole system. + */ + err = reset_control_assert(vde->rst_mc); + if (err) + dev_err(dev, "DEC end: Failed to assert MC reset: %d\n", err); + + err = reset_control_assert(vde->rst); + if (err) + dev_err(dev, "DEC end: Failed to assert HW reset: %d\n", err); + + pm_runtime_mark_last_busy(dev); + pm_runtime_put_autosuspend(dev); + + mutex_unlock(&vde->lock); +} + +static int tegra_vde_decode_end(struct tegra_vde *vde) +{ + unsigned int read_bytes, macroblocks_nb; + struct device *dev = vde->dev; + dma_addr_t bsev_ptr; + long timeout; + int ret; + + timeout = wait_for_completion_interruptible_timeout( + &vde->decode_completion, msecs_to_jiffies(1000)); + if (timeout == 0) { + bsev_ptr = tegra_vde_readl(vde, vde->bsev, 0x10); + macroblocks_nb = tegra_vde_readl(vde, vde->sxe, 0xC8) & 0x1FFF; + read_bytes = bsev_ptr ? bsev_ptr - vde->bitstream_data_addr : 0; + + dev_err(dev, "Decoding failed: read 0x%X bytes, %u macroblocks parsed\n", + read_bytes, macroblocks_nb); + + ret = -EIO; + } else if (timeout < 0) { + ret = timeout; + } else { + ret = 0; + } + + tegra_vde_decode_abort(vde); + + return ret; +} + +static struct vb2_buffer *get_ref_buf(struct tegra_ctx *ctx, + struct vb2_v4l2_buffer *dst, + unsigned int dpb_idx) +{ + const struct v4l2_h264_dpb_entry *dpb = ctx->h264.decode_params->dpb; + struct vb2_queue *cap_q = &ctx->fh.m2m_ctx->cap_q_ctx.q; + struct vb2_buffer *vb = NULL; + + if (dpb[dpb_idx].flags & V4L2_H264_DPB_ENTRY_FLAG_ACTIVE) + vb = vb2_find_buffer(cap_q, dpb[dpb_idx].reference_ts); + + /* + * If a DPB entry is unused or invalid, address of current destination + * buffer is returned. + */ + if (!vb) + return &dst->vb2_buf; + + return vb; +} + +static int tegra_vde_validate_vb_size(struct tegra_ctx *ctx, + struct vb2_buffer *vb, + unsigned int plane_id, + size_t min_size) +{ + u64 offset = vb->planes[plane_id].data_offset; + struct device *dev = ctx->vde->dev; + + if (offset + min_size > vb2_plane_size(vb, plane_id)) { + dev_err(dev, "Too small plane[%u] size %lu @0x%llX, should be at least %zu\n", + plane_id, vb2_plane_size(vb, plane_id), offset, min_size); + return -EINVAL; + } + + return 0; +} + +static int tegra_vde_h264_setup_frame(struct tegra_ctx *ctx, + struct tegra_vde_h264_decoder_ctx *h264, + struct v4l2_h264_reflist_builder *b, + struct vb2_buffer *vb, + unsigned int ref_id, + unsigned int id) +{ + struct v4l2_pix_format_mplane *pixfmt = &ctx->decoded_fmt.fmt.pix_mp; + struct tegra_m2m_buffer *tb = vb_to_tegra_buf(vb); + struct tegra_ctx_h264 *h = &ctx->h264; + struct tegra_vde *vde = ctx->vde; + struct device *dev = vde->dev; + unsigned int cstride, lstride; + unsigned int flags = 0; + size_t lsize, csize; + int err, frame_num; + + lsize = h264->pic_width_in_mbs * 16 * h264->pic_height_in_mbs * 16; + csize = h264->pic_width_in_mbs * 8 * h264->pic_height_in_mbs * 8; + lstride = pixfmt->plane_fmt[0].bytesperline; + cstride = pixfmt->plane_fmt[1].bytesperline; + + err = tegra_vde_validate_vb_size(ctx, vb, 0, lsize); + if (err) + return err; + + err = tegra_vde_validate_vb_size(ctx, vb, 1, csize); + if (err) + return err; + + err = tegra_vde_validate_vb_size(ctx, vb, 2, csize); + if (err) + return err; + + if (!tb->aux || tb->aux->size < csize) { + dev_err(dev, "Too small aux size %zd, should be at least %zu\n", + tb->aux ? tb->aux->size : -1, csize); + return -EINVAL; + } + + if (id == 0) { + frame_num = h->decode_params->frame_num; + + if (h->decode_params->nal_ref_idc) + flags |= FLAG_REFERENCE; + } else { + frame_num = b->refs[ref_id].frame_num; + } + + if (tb->b_frame) + flags |= FLAG_B_FRAME; + + vde->frames[id].flags = flags; + vde->frames[id].y_addr = tb->dma_addr[0]; + vde->frames[id].cb_addr = tb->dma_addr[1]; + vde->frames[id].cr_addr = tb->dma_addr[2]; + vde->frames[id].aux_addr = tb->aux->dma_addr; + vde->frames[id].frame_num = frame_num & 0x7fffff; + vde->frames[id].luma_atoms_pitch = lstride / VDE_ATOM; + vde->frames[id].chroma_atoms_pitch = cstride / VDE_ATOM; + + return 0; +} + +static int tegra_vde_h264_setup_frames(struct tegra_ctx *ctx, + struct tegra_vde_h264_decoder_ctx *h264) +{ + struct vb2_v4l2_buffer *src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + struct vb2_v4l2_buffer *dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + const struct v4l2_h264_dpb_entry *dpb = ctx->h264.decode_params->dpb; + struct tegra_m2m_buffer *tb = vb_to_tegra_buf(&dst->vb2_buf); + struct tegra_ctx_h264 *h = &ctx->h264; + struct v4l2_h264_reflist_builder b; + struct v4l2_h264_reference *dpb_id; + struct h264_reflists reflists; + struct vb2_buffer *ref; + unsigned int i; + int err; + + /* + * Tegra hardware requires information about frame's type, assuming + * that frame consists of the same type slices. Userspace must tag + * frame's type appropriately. + * + * Decoding of a non-uniform frames isn't supported by hardware and + * require software preprocessing that we don't implement. Decoding + * is expected to fail in this case. Such video streams are rare in + * practice, so not a big deal. + * + * If userspace doesn't tell us frame's type, then we will try decode + * as-is. + */ + v4l2_m2m_buf_copy_metadata(src, dst, true); + + if (h->decode_params->flags & V4L2_H264_DECODE_PARAM_FLAG_BFRAME) + tb->b_frame = true; + else + tb->b_frame = false; + + err = tegra_vde_h264_setup_frame(ctx, h264, NULL, &dst->vb2_buf, 0, + h264->dpb_frames_nb++); + if (err) + return err; + + if (!(h->decode_params->flags & (V4L2_H264_DECODE_PARAM_FLAG_PFRAME | + V4L2_H264_DECODE_PARAM_FLAG_BFRAME))) + return 0; + + v4l2_h264_init_reflist_builder(&b, h->decode_params, h->sps, dpb); + + if (h->decode_params->flags & V4L2_H264_DECODE_PARAM_FLAG_BFRAME) { + v4l2_h264_build_b_ref_lists(&b, reflists.b0, reflists.b1); + dpb_id = reflists.b0; + } else { + v4l2_h264_build_p_ref_list(&b, reflists.p); + dpb_id = reflists.p; + } + + for (i = 0; i < b.num_valid; i++) { + int dpb_idx = dpb_id[i].index; + + ref = get_ref_buf(ctx, dst, dpb_idx); + + err = tegra_vde_h264_setup_frame(ctx, h264, &b, ref, dpb_idx, + h264->dpb_frames_nb++); + if (err) + return err; + + if (b.refs[dpb_idx].top_field_order_cnt < b.cur_pic_order_count) + h264->dpb_ref_frames_with_earlier_poc_nb++; + } + + return 0; +} + +static unsigned int to_tegra_vde_h264_level_idc(unsigned int level_idc) +{ + switch (level_idc) { + case 11: + return 2; + case 12: + return 3; + case 13: + return 4; + case 20: + return 5; + case 21: + return 6; + case 22: + return 7; + case 30: + return 8; + case 31: + return 9; + case 32: + return 10; + case 40: + return 11; + case 41: + return 12; + case 42: + return 13; + case 50: + return 14; + default: + break; + } + + return 15; +} + +static int tegra_vde_h264_setup_context(struct tegra_ctx *ctx, + struct tegra_vde_h264_decoder_ctx *h264) +{ + struct tegra_ctx_h264 *h = &ctx->h264; + struct tegra_vde *vde = ctx->vde; + struct device *dev = vde->dev; + int err; + + memset(h264, 0, sizeof(*h264)); + memset(vde->frames, 0, sizeof(vde->frames)); + + tegra_vde_prepare_control_data(ctx, V4L2_CID_STATELESS_H264_DECODE_PARAMS); + tegra_vde_prepare_control_data(ctx, V4L2_CID_STATELESS_H264_SPS); + tegra_vde_prepare_control_data(ctx, V4L2_CID_STATELESS_H264_PPS); + + /* CABAC unsupported by hardware, requires software preprocessing */ + if (h->pps->flags & V4L2_H264_PPS_FLAG_ENTROPY_CODING_MODE) + return -EOPNOTSUPP; + + if (h->decode_params->flags & V4L2_H264_DECODE_PARAM_FLAG_FIELD_PIC) + return -EOPNOTSUPP; + + if (h->sps->profile_idc == 66) + h264->baseline_profile = 1; + + if (h->sps->flags & V4L2_H264_SPS_FLAG_DIRECT_8X8_INFERENCE) + h264->direct_8x8_inference_flag = 1; + + if (h->pps->flags & V4L2_H264_PPS_FLAG_CONSTRAINED_INTRA_PRED) + h264->constrained_intra_pred_flag = 1; + + if (h->pps->flags & V4L2_H264_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT) + h264->deblocking_filter_control_present_flag = 1; + + if (h->pps->flags & V4L2_H264_PPS_FLAG_BOTTOM_FIELD_PIC_ORDER_IN_FRAME_PRESENT) + h264->pic_order_present_flag = 1; + + h264->level_idc = to_tegra_vde_h264_level_idc(h->sps->level_idc); + h264->log2_max_pic_order_cnt_lsb = h->sps->log2_max_pic_order_cnt_lsb_minus4 + 4; + h264->log2_max_frame_num = h->sps->log2_max_frame_num_minus4 + 4; + h264->pic_order_cnt_type = h->sps->pic_order_cnt_type; + h264->pic_width_in_mbs = h->sps->pic_width_in_mbs_minus1 + 1; + h264->pic_height_in_mbs = h->sps->pic_height_in_map_units_minus1 + 1; + + h264->num_ref_idx_l0_active_minus1 = h->pps->num_ref_idx_l0_default_active_minus1; + h264->num_ref_idx_l1_active_minus1 = h->pps->num_ref_idx_l1_default_active_minus1; + h264->chroma_qp_index_offset = h->pps->chroma_qp_index_offset & 0x1f; + h264->pic_init_qp = h->pps->pic_init_qp_minus26 + 26; + + err = tegra_vde_h264_setup_frames(ctx, h264); + if (err) + return err; + + err = tegra_vde_validate_h264_ctx(dev, h264); + if (err) + return err; + + return 0; +} + +int tegra_vde_h264_decode_run(struct tegra_ctx *ctx) +{ + struct vb2_v4l2_buffer *src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + struct tegra_m2m_buffer *bitstream = vb_to_tegra_buf(&src->vb2_buf); + size_t bitstream_size = vb2_get_plane_payload(&src->vb2_buf, 0); + struct tegra_vde_h264_decoder_ctx h264; + struct tegra_vde *vde = ctx->vde; + int err; + + err = tegra_vde_h264_setup_context(ctx, &h264); + if (err) + return err; + + err = tegra_vde_decode_begin(vde, &h264, vde->frames, + bitstream->dma_addr[0], + bitstream_size); + if (err) + return err; + + return 0; +} + +int tegra_vde_h264_decode_wait(struct tegra_ctx *ctx) +{ + return tegra_vde_decode_end(ctx->vde); +} diff --git a/drivers/media/platform/nvidia/tegra-vde/iommu.c b/drivers/media/platform/nvidia/tegra-vde/iommu.c new file mode 100644 index 000000000..5521ed3e4 --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/iommu.c @@ -0,0 +1,157 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NVIDIA Tegra Video decoder driver + * + * Copyright (C) 2016-2019 GRATE-DRIVER project + */ + +#include +#include +#include +#include + +#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU) +#include +#endif + +#include "vde.h" + +int tegra_vde_iommu_map(struct tegra_vde *vde, + struct sg_table *sgt, + struct iova **iovap, + size_t size) +{ + struct iova *iova; + unsigned long shift; + unsigned long end; + dma_addr_t addr; + + end = vde->domain->geometry.aperture_end; + size = iova_align(&vde->iova, size); + shift = iova_shift(&vde->iova); + + iova = alloc_iova(&vde->iova, size >> shift, end >> shift, true); + if (!iova) + return -ENOMEM; + + addr = iova_dma_addr(&vde->iova, iova); + + size = iommu_map_sgtable(vde->domain, addr, sgt, + IOMMU_READ | IOMMU_WRITE); + if (!size) { + __free_iova(&vde->iova, iova); + return -ENXIO; + } + + *iovap = iova; + + return 0; +} + +void tegra_vde_iommu_unmap(struct tegra_vde *vde, struct iova *iova) +{ + unsigned long shift = iova_shift(&vde->iova); + unsigned long size = iova_size(iova) << shift; + dma_addr_t addr = iova_dma_addr(&vde->iova, iova); + + iommu_unmap(vde->domain, addr, size); + __free_iova(&vde->iova, iova); +} + +int tegra_vde_iommu_init(struct tegra_vde *vde) +{ + struct device *dev = vde->dev; + struct iova *iova; + unsigned long order; + unsigned long shift; + int err; + + vde->group = iommu_group_get(dev); + if (!vde->group) + return 0; + +#if IS_ENABLED(CONFIG_ARM_DMA_USE_IOMMU) + if (dev->archdata.mapping) { + struct dma_iommu_mapping *mapping = to_dma_iommu_mapping(dev); + + arm_iommu_detach_device(dev); + arm_iommu_release_mapping(mapping); + } +#endif + vde->domain = iommu_domain_alloc(&platform_bus_type); + if (!vde->domain) { + err = -ENOMEM; + goto put_group; + } + + err = iova_cache_get(); + if (err) + goto free_domain; + + order = __ffs(vde->domain->pgsize_bitmap); + init_iova_domain(&vde->iova, 1UL << order, 0); + + err = iommu_attach_group(vde->domain, vde->group); + if (err) + goto put_iova; + + /* + * We're using some static addresses that are not accessible by VDE + * to trap invalid memory accesses. + */ + shift = iova_shift(&vde->iova); + iova = reserve_iova(&vde->iova, 0x60000000 >> shift, + 0x70000000 >> shift); + if (!iova) { + err = -ENOMEM; + goto detach_group; + } + + vde->iova_resv_static_addresses = iova; + + /* + * BSEV's end-address wraps around due to integer overflow during + * of hardware context preparation if IOVA is allocated at the end + * of address space and VDE can't handle that. Hence simply reserve + * the last page to avoid the problem. + */ + iova = reserve_iova(&vde->iova, 0xffffffff >> shift, + (0xffffffff >> shift) + 1); + if (!iova) { + err = -ENOMEM; + goto unreserve_iova; + } + + vde->iova_resv_last_page = iova; + + return 0; + +unreserve_iova: + __free_iova(&vde->iova, vde->iova_resv_static_addresses); +detach_group: + iommu_detach_group(vde->domain, vde->group); +put_iova: + put_iova_domain(&vde->iova); + iova_cache_put(); +free_domain: + iommu_domain_free(vde->domain); +put_group: + iommu_group_put(vde->group); + + return err; +} + +void tegra_vde_iommu_deinit(struct tegra_vde *vde) +{ + if (vde->domain) { + __free_iova(&vde->iova, vde->iova_resv_last_page); + __free_iova(&vde->iova, vde->iova_resv_static_addresses); + iommu_detach_group(vde->domain, vde->group); + put_iova_domain(&vde->iova); + iova_cache_put(); + iommu_domain_free(vde->domain); + iommu_group_put(vde->group); + + vde->domain = NULL; + } +} diff --git a/drivers/media/platform/nvidia/tegra-vde/trace.h b/drivers/media/platform/nvidia/tegra-vde/trace.h new file mode 100644 index 000000000..7853ab095 --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/trace.h @@ -0,0 +1,95 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM tegra_vde + +#if !defined(TEGRA_VDE_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define TEGRA_VDE_TRACE_H + +#include + +#include "vde.h" + +DECLARE_EVENT_CLASS(register_access, + TP_PROTO(struct tegra_vde *vde, void __iomem *base, + u32 offset, u32 value), + TP_ARGS(vde, base, offset, value), + TP_STRUCT__entry( + __string(hw_name, tegra_vde_reg_base_name(vde, base)) + __field(u32, offset) + __field(u32, value) + ), + TP_fast_assign( + __assign_str(hw_name, tegra_vde_reg_base_name(vde, base)); + __entry->offset = offset; + __entry->value = value; + ), + TP_printk("%s:0x%03x 0x%08x", __get_str(hw_name), __entry->offset, + __entry->value) +); + +DEFINE_EVENT(register_access, vde_writel, + TP_PROTO(struct tegra_vde *vde, void __iomem *base, + u32 offset, u32 value), + TP_ARGS(vde, base, offset, value)); +DEFINE_EVENT(register_access, vde_readl, + TP_PROTO(struct tegra_vde *vde, void __iomem *base, + u32 offset, u32 value), + TP_ARGS(vde, base, offset, value)); + +TRACE_EVENT(vde_setup_iram_entry, + TP_PROTO(unsigned int table, unsigned int row, u32 value, u32 aux_addr), + TP_ARGS(table, row, value, aux_addr), + TP_STRUCT__entry( + __field(unsigned int, table) + __field(unsigned int, row) + __field(u32, value) + __field(u32, aux_addr) + ), + TP_fast_assign( + __entry->table = table; + __entry->row = row; + __entry->value = value; + __entry->aux_addr = aux_addr; + ), + TP_printk("[%u][%u] = { 0x%08x (flags = \"%s\", frame_num = %u); 0x%08x }", + __entry->table, __entry->row, __entry->value, + __print_flags(__entry->value, " ", { (1 << 25), "B" }), + __entry->value & 0x7FFFFF, __entry->aux_addr) +); + +TRACE_EVENT(vde_ref_l0, + TP_PROTO(unsigned int frame_num), + TP_ARGS(frame_num), + TP_STRUCT__entry( + __field(unsigned int, frame_num) + ), + TP_fast_assign( + __entry->frame_num = frame_num; + ), + TP_printk("REF L0: DPB: Frame 0: frame_num = %u", __entry->frame_num) +); + +TRACE_EVENT(vde_ref_l1, + TP_PROTO(unsigned int with_later_poc_nb, + unsigned int with_earlier_poc_nb), + TP_ARGS(with_later_poc_nb, with_earlier_poc_nb), + TP_STRUCT__entry( + __field(unsigned int, with_later_poc_nb) + __field(unsigned int, with_earlier_poc_nb) + ), + TP_fast_assign( + __entry->with_later_poc_nb = with_later_poc_nb; + __entry->with_earlier_poc_nb = with_earlier_poc_nb; + ), + TP_printk("REF L1: with_later_poc_nb %u, with_earlier_poc_nb %u", + __entry->with_later_poc_nb, __entry->with_earlier_poc_nb) +); + +#endif /* TEGRA_VDE_TRACE_H */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH ../../drivers/media/platform/nvidia/tegra-vde +#define TRACE_INCLUDE_FILE trace +#include diff --git a/drivers/media/platform/nvidia/tegra-vde/v4l2.c b/drivers/media/platform/nvidia/tegra-vde/v4l2.c new file mode 100644 index 000000000..bd8c207d5 --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/v4l2.c @@ -0,0 +1,1018 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NVIDIA Tegra Video decoder driver + * + * Copyright (C) 2019-2022 Dmitry Osipenko + * + * Based on Cedrus driver by Bootlin. + * Copyright (C) 2016 Florent Revest + * Copyright (C) 2018 Paul Kocialkowski + * + * Based on Rockchip driver by Collabora. + * Copyright (C) 2019 Boris Brezillon + */ + +#include +#include + +#include "vde.h" + +static const struct v4l2_ctrl_config ctrl_cfgs[] = { + { .id = V4L2_CID_STATELESS_H264_DECODE_PARAMS, }, + { .id = V4L2_CID_STATELESS_H264_SPS, }, + { .id = V4L2_CID_STATELESS_H264_PPS, }, + { + .id = V4L2_CID_STATELESS_H264_DECODE_MODE, + .min = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .max = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + .def = V4L2_STATELESS_H264_DECODE_MODE_FRAME_BASED, + }, + { + .id = V4L2_CID_STATELESS_H264_START_CODE, + .min = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .max = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + .def = V4L2_STATELESS_H264_START_CODE_ANNEX_B, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .max = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, + .def = V4L2_MPEG_VIDEO_H264_PROFILE_MAIN, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, + .min = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, + .max = V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + }, +}; + +static inline struct tegra_ctx *fh_to_tegra_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct tegra_ctx, fh); +} + +static void tegra_set_control_data(struct tegra_ctx *ctx, void *data, u32 id) +{ + switch (id) { + case V4L2_CID_STATELESS_H264_DECODE_PARAMS: + ctx->h264.decode_params = data; + break; + case V4L2_CID_STATELESS_H264_SPS: + ctx->h264.sps = data; + break; + case V4L2_CID_STATELESS_H264_PPS: + ctx->h264.pps = data; + break; + } +} + +void tegra_vde_prepare_control_data(struct tegra_ctx *ctx, u32 id) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ctrl_cfgs); i++) { + if (ctx->ctrls[i]->id == id) { + tegra_set_control_data(ctx, ctx->ctrls[i]->p_cur.p, id); + return; + } + } + + tegra_set_control_data(ctx, NULL, id); +} + +static int tegra_queue_setup(struct vb2_queue *vq, + unsigned int *nbufs, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct tegra_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_format *f; + unsigned int i; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + f = &ctx->coded_fmt; + else + f = &ctx->decoded_fmt; + + if (*num_planes) { + if (*num_planes != f->fmt.pix_mp.num_planes) + return -EINVAL; + + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) { + if (sizes[i] < f->fmt.pix_mp.plane_fmt[i].sizeimage) + return -EINVAL; + } + } else { + *num_planes = f->fmt.pix_mp.num_planes; + + for (i = 0; i < f->fmt.pix_mp.num_planes; i++) + sizes[i] = f->fmt.pix_mp.plane_fmt[i].sizeimage; + } + + return 0; +} + +static int tegra_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + return 0; +} + +static void __tegra_buf_cleanup(struct vb2_buffer *vb, unsigned int i) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct tegra_ctx *ctx = vb2_get_drv_priv(vq); + struct tegra_m2m_buffer *tb = vb_to_tegra_buf(vb); + + while (i--) { + if (tb->a[i]) { + tegra_vde_dmabuf_cache_unmap(ctx->vde, tb->a[i], true); + tb->a[i] = NULL; + } + + if (tb->iova[i]) { + tegra_vde_iommu_unmap(ctx->vde, tb->iova[i]); + tb->iova[i] = NULL; + } + } + + if (tb->aux) { + tegra_vde_free_bo(tb->aux); + tb->aux = NULL; + } +} + +static int tegra_buf_init(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct tegra_ctx *ctx = vb2_get_drv_priv(vq); + struct tegra_m2m_buffer *tb = vb_to_tegra_buf(vb); + struct tegra_vde *vde = ctx->vde; + enum dma_data_direction dma_dir; + struct sg_table *sgt; + unsigned int i; + int err; + + if (V4L2_TYPE_IS_CAPTURE(vq->type) && vb->num_planes > 1) { + /* + * Tegra decoder writes auxiliary data for I/P frames. + * This data is needed for decoding of B frames. + */ + err = tegra_vde_alloc_bo(vde, &tb->aux, DMA_FROM_DEVICE, + vb2_plane_size(vb, 1)); + if (err) + return err; + } + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + dma_dir = DMA_TO_DEVICE; + else + dma_dir = DMA_FROM_DEVICE; + + for (i = 0; i < vb->num_planes; i++) { + if (vq->memory == VB2_MEMORY_DMABUF) { + get_dma_buf(vb->planes[i].dbuf); + + err = tegra_vde_dmabuf_cache_map(vde, vb->planes[i].dbuf, + dma_dir, &tb->a[i], + &tb->dma_base[i]); + if (err) { + dma_buf_put(vb->planes[i].dbuf); + goto cleanup; + } + + continue; + } + + if (vde->domain) { + sgt = vb2_dma_sg_plane_desc(vb, i); + + err = tegra_vde_iommu_map(vde, sgt, &tb->iova[i], + vb2_plane_size(vb, i)); + if (err) + goto cleanup; + + tb->dma_base[i] = iova_dma_addr(&vde->iova, tb->iova[i]); + } else { + tb->dma_base[i] = vb2_dma_contig_plane_dma_addr(vb, i); + } + } + + return 0; + +cleanup: + __tegra_buf_cleanup(vb, i); + + return err; +} + +static void tegra_buf_cleanup(struct vb2_buffer *vb) +{ + __tegra_buf_cleanup(vb, vb->num_planes); +} + +static int tegra_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct tegra_ctx *ctx = vb2_get_drv_priv(vq); + struct tegra_m2m_buffer *tb = vb_to_tegra_buf(vb); + size_t hw_align, hw_size, hw_payload, size, offset; + struct v4l2_pix_format_mplane *pixfmt; + unsigned int i; + void *vb_data; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + hw_align = BSEV_ALIGN; + pixfmt = &ctx->coded_fmt.fmt.pix_mp; + } else { + hw_align = FRAMEID_ALIGN; + pixfmt = &ctx->decoded_fmt.fmt.pix_mp; + } + + for (i = 0; i < vb->num_planes; i++) { + offset = vb->planes[i].data_offset; + + if (offset & (hw_align - 1)) + return -EINVAL; + + if (V4L2_TYPE_IS_CAPTURE(vq->type)) { + size = pixfmt->plane_fmt[i].sizeimage; + hw_payload = ALIGN(size, VDE_ATOM); + } else { + size = vb2_get_plane_payload(vb, i) - offset; + hw_payload = ALIGN(size + VDE_ATOM, SXE_BUFFER); + } + + hw_size = offset + hw_payload; + + if (vb2_plane_size(vb, i) < hw_size) + return -EINVAL; + + vb2_set_plane_payload(vb, i, hw_payload); + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + vb_data = vb2_plane_vaddr(vb, i); + + /* + * Hardware requires zero-padding of coded data. + * Otherwise it will fail to parse the trailing + * data and abort the decoding. + */ + if (vb_data) + memset(vb_data + offset + size, 0, + hw_size - offset - size); + } + + tb->dma_addr[i] = tb->dma_base[i] + offset; + } + + switch (pixfmt->pixelformat) { + case V4L2_PIX_FMT_YVU420M: + swap(tb->dma_addr[1], tb->dma_addr[2]); + break; + } + + return 0; +} + +static void tegra_buf_queue(struct vb2_buffer *vb) +{ + struct tegra_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void tegra_buf_request_complete(struct vb2_buffer *vb) +{ + struct tegra_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_ctrl_request_complete(vb->req_obj.req, &ctx->hdl); +} + +static int tegra_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + return 0; +} + +static void tegra_stop_streaming(struct vb2_queue *vq) +{ + struct tegra_ctx *ctx = vb2_get_drv_priv(vq); + + while (true) { + struct vb2_v4l2_buffer *vbuf; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (!vbuf) + break; + + v4l2_ctrl_request_complete(vbuf->vb2_buf.req_obj.req, &ctx->hdl); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops tegra_qops = { + .queue_setup = tegra_queue_setup, + .buf_init = tegra_buf_init, + .buf_cleanup = tegra_buf_cleanup, + .buf_prepare = tegra_buf_prepare, + .buf_queue = tegra_buf_queue, + .buf_out_validate = tegra_buf_out_validate, + .buf_request_complete = tegra_buf_request_complete, + .start_streaming = tegra_start_streaming, + .stop_streaming = tegra_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int tegra_queue_init(void *priv, + struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct tegra_ctx *ctx = priv; + struct tegra_vde *vde = ctx->vde; + const struct vb2_mem_ops *mem_ops; + unsigned long dma_attrs; + int err; + + /* + * TODO: Switch to use of vb2_dma_contig_memops uniformly once we + * will add IOMMU_DOMAIN support for video decoder to tegra-smmu + * driver. For now we need to stick with SG ops in order to be able + * to get SGT table easily. This is suboptimal since SG mappings are + * wasting CPU cache and we don't need that caching. + */ + if (vde->domain) + mem_ops = &vb2_dma_sg_memops; + else + mem_ops = &vb2_dma_contig_memops; + + dma_attrs = DMA_ATTR_WRITE_COMBINE; + + src_vq->buf_struct_size = sizeof(struct tegra_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_DMABUF | VB2_MMAP; + src_vq->supports_requests = true; + src_vq->requires_requests = true; + src_vq->lock = &vde->v4l2_lock; + src_vq->dma_attrs = dma_attrs; + src_vq->mem_ops = mem_ops; + src_vq->ops = &tegra_qops; + src_vq->drv_priv = ctx; + src_vq->dev = vde->dev; + + err = vb2_queue_init(src_vq); + if (err) { + v4l2_err(&vde->v4l2_dev, + "failed to initialize src queue: %d\n", err); + return err; + } + + /* + * We may need to zero the end of bitstream in kernel if userspace + * doesn't do that, hence kmap is needed for the coded data. It's not + * needed for framebuffers. + */ + dma_attrs |= DMA_ATTR_NO_KERNEL_MAPPING; + + dst_vq->buf_struct_size = sizeof(struct tegra_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_DMABUF | VB2_MMAP; + dst_vq->lock = &vde->v4l2_lock; + dst_vq->dma_attrs = dma_attrs; + dst_vq->mem_ops = mem_ops; + dst_vq->ops = &tegra_qops; + dst_vq->drv_priv = ctx; + dst_vq->dev = vde->dev; + + err = vb2_queue_init(dst_vq); + if (err) { + v4l2_err(&vde->v4l2_dev, + "failed to initialize dst queue: %d\n", err); + return err; + } + + return 0; +} + +static void tegra_reset_fmt(struct tegra_ctx *ctx, struct v4l2_format *f, + u32 fourcc) +{ + memset(f, 0, sizeof(*f)); + f->fmt.pix_mp.pixelformat = fourcc; + f->fmt.pix_mp.field = V4L2_FIELD_NONE; + f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_DEFAULT; + f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709; + f->fmt.pix_mp.quantization = V4L2_QUANTIZATION_DEFAULT; +} + +static void tegra_reset_coded_fmt(struct tegra_ctx *ctx) +{ + const struct tegra_vde_soc *soc = ctx->vde->soc; + struct v4l2_format *f = &ctx->coded_fmt; + + ctx->coded_fmt_desc = &soc->coded_fmts[0]; + tegra_reset_fmt(ctx, f, ctx->coded_fmt_desc->fourcc); + + f->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + f->fmt.pix_mp.width = ctx->coded_fmt_desc->frmsize.min_width; + f->fmt.pix_mp.height = ctx->coded_fmt_desc->frmsize.min_height; +} + +static void tegra_fill_pixfmt_mp(struct v4l2_pix_format_mplane *pixfmt, + u32 pixelformat, u32 width, u32 height) +{ + const struct v4l2_format_info *info = v4l2_format_info(pixelformat); + struct v4l2_plane_pix_format *plane; + unsigned int i; + + switch (pixelformat) { + case V4L2_PIX_FMT_YUV420M: + case V4L2_PIX_FMT_YVU420M: + pixfmt->width = width; + pixfmt->height = height; + pixfmt->pixelformat = pixelformat; + pixfmt->num_planes = info->mem_planes; + + for (i = 0; i < pixfmt->num_planes; i++) { + unsigned int hdiv = (i == 0) ? 1 : 2; + unsigned int vdiv = (i == 0) ? 1 : 2; + + /* + * VDE is connected to Graphics Memory using 128bit port, + * all memory accesses are made using 16B atoms. + * + * V4L requires Cb/Cr strides to be exactly half of the + * Y stride, hence we're aligning Y to 16B x 2. + */ + plane = &pixfmt->plane_fmt[i]; + plane->bytesperline = ALIGN(width, VDE_ATOM * 2) / hdiv; + plane->sizeimage = plane->bytesperline * height / vdiv; + } + + break; + } +} + +static void tegra_reset_decoded_fmt(struct tegra_ctx *ctx) +{ + struct v4l2_format *f = &ctx->decoded_fmt; + + tegra_reset_fmt(ctx, f, ctx->coded_fmt_desc->decoded_fmts[0]); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + tegra_fill_pixfmt_mp(&f->fmt.pix_mp, + ctx->coded_fmt_desc->decoded_fmts[0], + ctx->coded_fmt.fmt.pix_mp.width, + ctx->coded_fmt.fmt.pix_mp.height); +} + +static void tegra_job_finish(struct tegra_ctx *ctx, + enum vb2_buffer_state result) +{ + v4l2_m2m_buf_done_and_job_finish(ctx->vde->m2m, ctx->fh.m2m_ctx, + result); +} + +static void tegra_decode_complete(struct work_struct *work) +{ + struct tegra_ctx *ctx = container_of(work, struct tegra_ctx, work); + int err; + + err = ctx->coded_fmt_desc->decode_wait(ctx); + if (err) + tegra_job_finish(ctx, VB2_BUF_STATE_ERROR); + else + tegra_job_finish(ctx, VB2_BUF_STATE_DONE); +} + +static int tegra_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->bus_info, "platform:tegra-vde", sizeof(cap->bus_info)); + strscpy(cap->driver, "tegra-vde", sizeof(cap->driver)); + strscpy(cap->card, "tegra-vde", sizeof(cap->card)); + + return 0; +} + +static int tegra_enum_decoded_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + + if (WARN_ON(!ctx->coded_fmt_desc)) + return -EINVAL; + + if (f->index >= ctx->coded_fmt_desc->num_decoded_fmts) + return -EINVAL; + + f->pixelformat = ctx->coded_fmt_desc->decoded_fmts[f->index]; + + return 0; +} + +static int tegra_g_decoded_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + + *f = ctx->decoded_fmt; + return 0; +} + +static int tegra_try_decoded_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + const struct tegra_coded_fmt_desc *coded_desc; + unsigned int i; + + /* + * The codec context should point to a coded format desc, if the format + * on the coded end has not been set yet, it should point to the + * default value. + */ + coded_desc = ctx->coded_fmt_desc; + if (WARN_ON(!coded_desc)) + return -EINVAL; + + if (!coded_desc->num_decoded_fmts) + return -EINVAL; + + for (i = 0; i < coded_desc->num_decoded_fmts; i++) { + if (coded_desc->decoded_fmts[i] == pix_mp->pixelformat) + break; + } + + if (i == coded_desc->num_decoded_fmts) + pix_mp->pixelformat = coded_desc->decoded_fmts[0]; + + /* always apply the frmsize constraint of the coded end */ + v4l2_apply_frmsize_constraints(&pix_mp->width, + &pix_mp->height, + &coded_desc->frmsize); + + tegra_fill_pixfmt_mp(pix_mp, pix_mp->pixelformat, + pix_mp->width, pix_mp->height); + pix_mp->field = V4L2_FIELD_NONE; + + return 0; +} + +static int tegra_s_decoded_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + struct vb2_queue *vq; + int err; + + /* change not allowed if queue is busy */ + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (vb2_is_busy(vq)) + return -EBUSY; + + err = tegra_try_decoded_fmt(file, priv, f); + if (err) + return err; + + ctx->decoded_fmt = *f; + + return 0; +} + +static int tegra_enum_coded_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + const struct tegra_vde_soc *soc = ctx->vde->soc; + + if (f->index >= soc->num_coded_fmts) + return -EINVAL; + + f->pixelformat = soc->coded_fmts[f->index].fourcc; + + return 0; +} + +static int tegra_g_coded_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + + *f = ctx->coded_fmt; + return 0; +} + +static const struct tegra_coded_fmt_desc * +tegra_find_coded_fmt_desc(struct tegra_ctx *ctx, u32 fourcc) +{ + const struct tegra_vde_soc *soc = ctx->vde->soc; + unsigned int i; + + for (i = 0; i < soc->num_coded_fmts; i++) { + if (soc->coded_fmts[i].fourcc == fourcc) + return &soc->coded_fmts[i]; + } + + return NULL; +} + +static int tegra_try_coded_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + const struct tegra_vde_soc *soc = ctx->vde->soc; + int size = pix_mp->plane_fmt[0].sizeimage; + const struct tegra_coded_fmt_desc *desc; + + desc = tegra_find_coded_fmt_desc(ctx, pix_mp->pixelformat); + if (!desc) { + pix_mp->pixelformat = soc->coded_fmts[0].fourcc; + desc = &soc->coded_fmts[0]; + } + + v4l2_apply_frmsize_constraints(&pix_mp->width, + &pix_mp->height, + &desc->frmsize); + + pix_mp->plane_fmt[0].sizeimage = max(ALIGN(size, SXE_BUFFER), SZ_2M); + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->num_planes = 1; + + return 0; +} + +static int tegra_s_coded_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + const struct tegra_coded_fmt_desc *desc; + struct vb2_queue *peer_vq, *vq; + struct v4l2_format *cap_fmt; + int err; + + /* + * In order to support dynamic resolution change, the decoder admits + * a resolution change, as long as the pixelformat remains. Can't be + * done if streaming. + */ + vq = v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (vb2_is_streaming(vq) || + (vb2_is_busy(vq) && + f->fmt.pix_mp.pixelformat != ctx->coded_fmt.fmt.pix_mp.pixelformat)) + return -EBUSY; + + /* + * Since format change on the OUTPUT queue will reset the CAPTURE + * queue, we can't allow doing so when the CAPTURE queue has buffers + * allocated. + */ + peer_vq = v4l2_m2m_get_vq(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (vb2_is_busy(peer_vq)) + return -EBUSY; + + err = tegra_try_coded_fmt(file, priv, f); + if (err) + return err; + + desc = tegra_find_coded_fmt_desc(ctx, f->fmt.pix_mp.pixelformat); + if (!desc) + return -EINVAL; + + ctx->coded_fmt_desc = desc; + ctx->coded_fmt = *f; + + /* + * Current decoded format might have become invalid with newly + * selected codec, so reset it to default just to be safe and + * keep internal driver state sane. User is mandated to set + * the decoded format again after we return, so we don't need + * anything smarter. + * + * Note that this will propagates any size changes to the decoded format. + */ + tegra_reset_decoded_fmt(ctx); + + /* propagate colorspace information to capture */ + cap_fmt = &ctx->decoded_fmt; + cap_fmt->fmt.pix_mp.xfer_func = f->fmt.pix_mp.xfer_func; + cap_fmt->fmt.pix_mp.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; + cap_fmt->fmt.pix_mp.colorspace = f->fmt.pix_mp.colorspace; + cap_fmt->fmt.pix_mp.quantization = f->fmt.pix_mp.quantization; + + return 0; +} + +static int tegra_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct tegra_ctx *ctx = fh_to_tegra_ctx(priv); + const struct tegra_coded_fmt_desc *fmt; + + if (fsize->index) + return -EINVAL; + + fmt = tegra_find_coded_fmt_desc(ctx, fsize->pixel_format); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise = fmt->frmsize; + + return 0; +} + +static const struct v4l2_ioctl_ops tegra_v4l2_ioctl_ops = { + .vidioc_querycap = tegra_querycap, + .vidioc_enum_framesizes = tegra_enum_framesizes, + + .vidioc_try_fmt_vid_out_mplane = tegra_try_coded_fmt, + .vidioc_g_fmt_vid_out_mplane = tegra_g_coded_fmt, + .vidioc_s_fmt_vid_out_mplane = tegra_s_coded_fmt, + .vidioc_enum_fmt_vid_out = tegra_enum_coded_fmt, + + .vidioc_try_fmt_vid_cap_mplane = tegra_try_decoded_fmt, + .vidioc_g_fmt_vid_cap_mplane = tegra_g_decoded_fmt, + .vidioc_s_fmt_vid_cap_mplane = tegra_s_decoded_fmt, + .vidioc_enum_fmt_vid_cap = tegra_enum_decoded_fmt, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int tegra_init_ctrls(struct tegra_ctx *ctx) +{ + unsigned int i; + int err; + + err = v4l2_ctrl_handler_init(&ctx->hdl, ARRAY_SIZE(ctrl_cfgs)); + if (err) + return err; + + for (i = 0; i < ARRAY_SIZE(ctrl_cfgs); i++) { + ctx->ctrls[i] = v4l2_ctrl_new_custom(&ctx->hdl, &ctrl_cfgs[i], + NULL); + if (ctx->hdl.error) { + err = ctx->hdl.error; + goto free_ctrls; + } + } + + err = v4l2_ctrl_handler_setup(&ctx->hdl); + if (err) + goto free_ctrls; + + ctx->fh.ctrl_handler = &ctx->hdl; + + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(&ctx->hdl); + + return err; +} + +static int tegra_init_m2m(struct tegra_ctx *ctx) +{ + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(ctx->vde->m2m, + ctx, tegra_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) + return PTR_ERR(ctx->fh.m2m_ctx); + + return 0; +} + +static int tegra_open(struct file *file) +{ + struct tegra_vde *vde = video_drvdata(file); + struct tegra_ctx *ctx; + int err; + + ctx = kzalloc(offsetof(struct tegra_ctx, ctrls[ARRAY_SIZE(ctrl_cfgs)]), + GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->vde = vde; + v4l2_fh_init(&ctx->fh, video_devdata(file)); + INIT_WORK(&ctx->work, tegra_decode_complete); + + err = tegra_init_ctrls(ctx); + if (err) { + v4l2_err(&vde->v4l2_dev, "failed to add controls: %d\n", err); + goto free_ctx; + } + + err = tegra_init_m2m(ctx); + if (err) { + v4l2_err(&vde->v4l2_dev, "failed to initialize m2m: %d\n", err); + goto free_ctrls; + } + + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + tegra_reset_coded_fmt(ctx); + tegra_try_coded_fmt(file, file->private_data, &ctx->coded_fmt); + + tegra_reset_decoded_fmt(ctx); + tegra_try_decoded_fmt(file, file->private_data, &ctx->decoded_fmt); + + return 0; + +free_ctrls: + v4l2_ctrl_handler_free(&ctx->hdl); +free_ctx: + kfree(ctx); + + return err; +} + +static int tegra_release(struct file *file) +{ + struct v4l2_fh *fh = file->private_data; + struct tegra_ctx *ctx = fh_to_tegra_ctx(fh); + struct tegra_vde *vde = ctx->vde; + + v4l2_fh_del(fh); + v4l2_m2m_ctx_release(fh->m2m_ctx); + v4l2_ctrl_handler_free(&ctx->hdl); + v4l2_fh_exit(fh); + kfree(ctx); + + tegra_vde_dmabuf_cache_unmap_sync(vde); + + return 0; +} + +static const struct v4l2_file_operations tegra_v4l2_fops = { + .owner = THIS_MODULE, + .open = tegra_open, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, + .release = tegra_release, + .unlocked_ioctl = video_ioctl2, +}; + +static void tegra_device_run(void *priv) +{ + struct tegra_ctx *ctx = priv; + struct vb2_v4l2_buffer *src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + struct media_request *src_req = src->vb2_buf.req_obj.req; + int err; + + v4l2_ctrl_request_setup(src_req, &ctx->hdl); + + err = ctx->coded_fmt_desc->decode_run(ctx); + + v4l2_ctrl_request_complete(src_req, &ctx->hdl); + + if (err) + tegra_job_finish(ctx, VB2_BUF_STATE_ERROR); + else + queue_work(ctx->vde->wq, &ctx->work); +} + +static const struct v4l2_m2m_ops tegra_v4l2_m2m_ops = { + .device_run = tegra_device_run, +}; + +static int tegra_request_validate(struct media_request *req) +{ + unsigned int count; + + count = vb2_request_buffer_cnt(req); + if (!count) + return -ENOENT; + else if (count > 1) + return -EINVAL; + + return vb2_request_validate(req); +} + +static const struct media_device_ops tegra_media_device_ops = { + .req_validate = tegra_request_validate, + .req_queue = v4l2_m2m_request_queue, +}; + +int tegra_vde_v4l2_init(struct tegra_vde *vde) +{ + struct device *dev = vde->dev; + int err; + + mutex_init(&vde->v4l2_lock); + media_device_init(&vde->mdev); + video_set_drvdata(&vde->vdev, vde); + + vde->vdev.lock = &vde->v4l2_lock, + vde->vdev.fops = &tegra_v4l2_fops, + vde->vdev.vfl_dir = VFL_DIR_M2M, + vde->vdev.release = video_device_release_empty, + vde->vdev.v4l2_dev = &vde->v4l2_dev; + vde->vdev.ioctl_ops = &tegra_v4l2_ioctl_ops, + vde->vdev.device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING, + + vde->v4l2_dev.mdev = &vde->mdev; + vde->mdev.ops = &tegra_media_device_ops; + vde->mdev.dev = dev; + + strscpy(vde->mdev.model, "tegra-vde", sizeof(vde->mdev.model)); + strscpy(vde->vdev.name, "tegra-vde", sizeof(vde->vdev.name)); + strscpy(vde->mdev.bus_info, "platform:tegra-vde", + sizeof(vde->mdev.bus_info)); + + vde->wq = create_workqueue("tegra-vde"); + if (!vde->wq) + return -ENOMEM; + + err = media_device_register(&vde->mdev); + if (err) { + dev_err(dev, "failed to register media device: %d\n", err); + goto clean_up_media_device; + } + + err = v4l2_device_register(dev, &vde->v4l2_dev); + if (err) { + dev_err(dev, "failed to register v4l2 device: %d\n", err); + goto unreg_media_device; + } + + err = video_register_device(&vde->vdev, VFL_TYPE_VIDEO, -1); + if (err) { + dev_err(dev, "failed to register video device: %d\n", err); + goto unreg_v4l2; + } + + vde->m2m = v4l2_m2m_init(&tegra_v4l2_m2m_ops); + err = PTR_ERR_OR_ZERO(vde->m2m); + if (err) { + dev_err(dev, "failed to initialize m2m device: %d\n", err); + goto unreg_video_device; + } + + err = v4l2_m2m_register_media_controller(vde->m2m, &vde->vdev, + MEDIA_ENT_F_PROC_VIDEO_DECODER); + if (err) { + dev_err(dev, "failed to register media controller: %d\n", err); + goto release_m2m; + } + + v4l2_info(&vde->v4l2_dev, "v4l2 device registered as /dev/video%d\n", + vde->vdev.num); + + return 0; + +release_m2m: + v4l2_m2m_release(vde->m2m); +unreg_video_device: + video_unregister_device(&vde->vdev); +unreg_v4l2: + v4l2_device_unregister(&vde->v4l2_dev); +unreg_media_device: + media_device_unregister(&vde->mdev); +clean_up_media_device: + media_device_cleanup(&vde->mdev); + + destroy_workqueue(vde->wq); + + return err; +} + +void tegra_vde_v4l2_deinit(struct tegra_vde *vde) +{ + v4l2_m2m_unregister_media_controller(vde->m2m); + v4l2_m2m_release(vde->m2m); + + video_unregister_device(&vde->vdev); + v4l2_device_unregister(&vde->v4l2_dev); + + media_device_unregister(&vde->mdev); + media_device_cleanup(&vde->mdev); + + destroy_workqueue(vde->wq); +} diff --git a/drivers/media/platform/nvidia/tegra-vde/vde.c b/drivers/media/platform/nvidia/tegra-vde/vde.c new file mode 100644 index 000000000..f3e863a94 --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/vde.c @@ -0,0 +1,551 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * NVIDIA Tegra Video decoder driver + * + * Copyright (C) 2016-2017 Dmitry Osipenko + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "vde.h" + +#define CREATE_TRACE_POINTS +#include "trace.h" + +void tegra_vde_writel(struct tegra_vde *vde, u32 value, + void __iomem *base, u32 offset) +{ + trace_vde_writel(vde, base, offset, value); + + writel_relaxed(value, base + offset); +} + +u32 tegra_vde_readl(struct tegra_vde *vde, void __iomem *base, u32 offset) +{ + u32 value = readl_relaxed(base + offset); + + trace_vde_readl(vde, base, offset, value); + + return value; +} + +void tegra_vde_set_bits(struct tegra_vde *vde, u32 mask, + void __iomem *base, u32 offset) +{ + u32 value = tegra_vde_readl(vde, base, offset); + + tegra_vde_writel(vde, value | mask, base, offset); +} + +int tegra_vde_alloc_bo(struct tegra_vde *vde, + struct tegra_vde_bo **ret_bo, + enum dma_data_direction dma_dir, + size_t size) +{ + struct device *dev = vde->dev; + struct tegra_vde_bo *bo; + int err; + + bo = kzalloc(sizeof(*bo), GFP_KERNEL); + if (!bo) + return -ENOMEM; + + bo->vde = vde; + bo->size = size; + bo->dma_dir = dma_dir; + bo->dma_attrs = DMA_ATTR_WRITE_COMBINE | + DMA_ATTR_NO_KERNEL_MAPPING; + + if (!vde->domain) + bo->dma_attrs |= DMA_ATTR_FORCE_CONTIGUOUS; + + bo->dma_cookie = dma_alloc_attrs(dev, bo->size, &bo->dma_handle, + GFP_KERNEL, bo->dma_attrs); + if (!bo->dma_cookie) { + dev_err(dev, "Failed to allocate DMA buffer of size: %zu\n", + bo->size); + err = -ENOMEM; + goto free_bo; + } + + err = dma_get_sgtable_attrs(dev, &bo->sgt, bo->dma_cookie, + bo->dma_handle, bo->size, bo->dma_attrs); + if (err) { + dev_err(dev, "Failed to get DMA buffer SG table: %d\n", err); + goto free_attrs; + } + + err = dma_map_sgtable(dev, &bo->sgt, bo->dma_dir, bo->dma_attrs); + if (err) { + dev_err(dev, "Failed to map DMA buffer SG table: %d\n", err); + goto free_table; + } + + if (vde->domain) { + err = tegra_vde_iommu_map(vde, &bo->sgt, &bo->iova, bo->size); + if (err) { + dev_err(dev, "Failed to map DMA buffer IOVA: %d\n", err); + goto unmap_sgtable; + } + + bo->dma_addr = iova_dma_addr(&vde->iova, bo->iova); + } else { + bo->dma_addr = sg_dma_address(bo->sgt.sgl); + } + + *ret_bo = bo; + + return 0; + +unmap_sgtable: + dma_unmap_sgtable(dev, &bo->sgt, bo->dma_dir, bo->dma_attrs); +free_table: + sg_free_table(&bo->sgt); +free_attrs: + dma_free_attrs(dev, bo->size, bo->dma_cookie, bo->dma_handle, + bo->dma_attrs); +free_bo: + kfree(bo); + + return err; +} + +void tegra_vde_free_bo(struct tegra_vde_bo *bo) +{ + struct tegra_vde *vde = bo->vde; + struct device *dev = vde->dev; + + if (vde->domain) + tegra_vde_iommu_unmap(vde, bo->iova); + + dma_unmap_sgtable(dev, &bo->sgt, bo->dma_dir, bo->dma_attrs); + + sg_free_table(&bo->sgt); + + dma_free_attrs(dev, bo->size, bo->dma_cookie, bo->dma_handle, + bo->dma_attrs); + kfree(bo); +} + +static irqreturn_t tegra_vde_isr(int irq, void *data) +{ + struct tegra_vde *vde = data; + + if (completion_done(&vde->decode_completion)) + return IRQ_NONE; + + tegra_vde_set_bits(vde, 0, vde->frameid, 0x208); + complete(&vde->decode_completion); + + return IRQ_HANDLED; +} + +static __maybe_unused int tegra_vde_runtime_suspend(struct device *dev) +{ + struct tegra_vde *vde = dev_get_drvdata(dev); + int err; + + if (!dev->pm_domain) { + err = tegra_powergate_power_off(TEGRA_POWERGATE_VDEC); + if (err) { + dev_err(dev, "Failed to power down HW: %d\n", err); + return err; + } + } + + clk_disable_unprepare(vde->clk); + reset_control_release(vde->rst); + reset_control_release(vde->rst_mc); + + return 0; +} + +static __maybe_unused int tegra_vde_runtime_resume(struct device *dev) +{ + struct tegra_vde *vde = dev_get_drvdata(dev); + int err; + + err = reset_control_acquire(vde->rst_mc); + if (err) { + dev_err(dev, "Failed to acquire mc reset: %d\n", err); + return err; + } + + err = reset_control_acquire(vde->rst); + if (err) { + dev_err(dev, "Failed to acquire reset: %d\n", err); + goto release_mc_reset; + } + + if (!dev->pm_domain) { + err = tegra_powergate_sequence_power_up(TEGRA_POWERGATE_VDEC, + vde->clk, vde->rst); + if (err) { + dev_err(dev, "Failed to power up HW : %d\n", err); + goto release_reset; + } + } else { + /* + * tegra_powergate_sequence_power_up() leaves clocks enabled, + * while GENPD not. + */ + err = clk_prepare_enable(vde->clk); + if (err) { + dev_err(dev, "Failed to enable clock: %d\n", err); + goto release_reset; + } + } + + return 0; + +release_reset: + reset_control_release(vde->rst); +release_mc_reset: + reset_control_release(vde->rst_mc); + + return err; +} + +static int tegra_vde_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct tegra_vde *vde; + int irq, err; + + vde = devm_kzalloc(dev, sizeof(*vde), GFP_KERNEL); + if (!vde) + return -ENOMEM; + + platform_set_drvdata(pdev, vde); + + vde->soc = of_device_get_match_data(&pdev->dev); + vde->dev = dev; + + vde->sxe = devm_platform_ioremap_resource_byname(pdev, "sxe"); + if (IS_ERR(vde->sxe)) + return PTR_ERR(vde->sxe); + + vde->bsev = devm_platform_ioremap_resource_byname(pdev, "bsev"); + if (IS_ERR(vde->bsev)) + return PTR_ERR(vde->bsev); + + vde->mbe = devm_platform_ioremap_resource_byname(pdev, "mbe"); + if (IS_ERR(vde->mbe)) + return PTR_ERR(vde->mbe); + + vde->ppe = devm_platform_ioremap_resource_byname(pdev, "ppe"); + if (IS_ERR(vde->ppe)) + return PTR_ERR(vde->ppe); + + vde->mce = devm_platform_ioremap_resource_byname(pdev, "mce"); + if (IS_ERR(vde->mce)) + return PTR_ERR(vde->mce); + + vde->tfe = devm_platform_ioremap_resource_byname(pdev, "tfe"); + if (IS_ERR(vde->tfe)) + return PTR_ERR(vde->tfe); + + vde->ppb = devm_platform_ioremap_resource_byname(pdev, "ppb"); + if (IS_ERR(vde->ppb)) + return PTR_ERR(vde->ppb); + + vde->vdma = devm_platform_ioremap_resource_byname(pdev, "vdma"); + if (IS_ERR(vde->vdma)) + return PTR_ERR(vde->vdma); + + vde->frameid = devm_platform_ioremap_resource_byname(pdev, "frameid"); + if (IS_ERR(vde->frameid)) + return PTR_ERR(vde->frameid); + + vde->clk = devm_clk_get(dev, NULL); + if (IS_ERR(vde->clk)) { + err = PTR_ERR(vde->clk); + dev_err(dev, "Could not get VDE clk %d\n", err); + return err; + } + + vde->rst = devm_reset_control_get_exclusive_released(dev, NULL); + if (IS_ERR(vde->rst)) { + err = PTR_ERR(vde->rst); + dev_err(dev, "Could not get VDE reset %d\n", err); + return err; + } + + vde->rst_mc = devm_reset_control_get_optional_exclusive_released(dev, "mc"); + if (IS_ERR(vde->rst_mc)) { + err = PTR_ERR(vde->rst_mc); + dev_err(dev, "Could not get MC reset %d\n", err); + return err; + } + + irq = platform_get_irq_byname(pdev, "sync-token"); + if (irq < 0) + return irq; + + err = devm_request_irq(dev, irq, tegra_vde_isr, 0, + dev_name(dev), vde); + if (err) { + dev_err(dev, "Could not request IRQ %d\n", err); + return err; + } + + err = devm_tegra_core_dev_init_opp_table_common(dev); + if (err) { + dev_err(dev, "Could initialize OPP table %d\n", err); + return err; + } + + vde->iram_pool = of_gen_pool_get(dev->of_node, "iram", 0); + if (!vde->iram_pool) { + dev_err(dev, "Could not get IRAM pool\n"); + return -EPROBE_DEFER; + } + + vde->iram = gen_pool_dma_alloc(vde->iram_pool, + gen_pool_size(vde->iram_pool), + &vde->iram_lists_addr); + if (!vde->iram) { + dev_err(dev, "Could not reserve IRAM\n"); + return -ENOMEM; + } + + INIT_LIST_HEAD(&vde->map_list); + mutex_init(&vde->map_lock); + mutex_init(&vde->lock); + init_completion(&vde->decode_completion); + + err = tegra_vde_iommu_init(vde); + if (err) { + dev_err(dev, "Failed to initialize IOMMU: %d\n", err); + goto err_gen_free; + } + + pm_runtime_enable(dev); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_autosuspend_delay(dev, 300); + + /* + * VDE partition may be left ON after bootloader, hence let's + * power-cycle it in order to put hardware into a predictable lower + * power state. + */ + err = pm_runtime_resume_and_get(dev); + if (err) + goto err_pm_runtime; + + pm_runtime_put(dev); + + err = tegra_vde_alloc_bo(vde, &vde->secure_bo, DMA_FROM_DEVICE, 4096); + if (err) { + dev_err(dev, "Failed to allocate secure BO: %d\n", err); + goto err_pm_runtime; + } + + err = tegra_vde_v4l2_init(vde); + if (err) { + dev_err(dev, "Failed to initialize V4L2: %d\n", err); + goto err_free_secure_bo; + } + + return 0; + +err_free_secure_bo: + tegra_vde_free_bo(vde->secure_bo); +err_pm_runtime: + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + + tegra_vde_iommu_deinit(vde); + +err_gen_free: + gen_pool_free(vde->iram_pool, (unsigned long)vde->iram, + gen_pool_size(vde->iram_pool)); + + return err; +} + +static int tegra_vde_remove(struct platform_device *pdev) +{ + struct tegra_vde *vde = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + tegra_vde_v4l2_deinit(vde); + tegra_vde_free_bo(vde->secure_bo); + + /* + * As it increments RPM usage_count even on errors, we don't need to + * check the returned code here. + */ + pm_runtime_get_sync(dev); + + pm_runtime_dont_use_autosuspend(dev); + pm_runtime_disable(dev); + + /* + * Balance RPM state, the VDE power domain is left ON and hardware + * is clock-gated. It's safe to reboot machine now. + */ + pm_runtime_put_noidle(dev); + clk_disable_unprepare(vde->clk); + + tegra_vde_dmabuf_cache_unmap_all(vde); + tegra_vde_iommu_deinit(vde); + + gen_pool_free(vde->iram_pool, (unsigned long)vde->iram, + gen_pool_size(vde->iram_pool)); + + return 0; +} + +static void tegra_vde_shutdown(struct platform_device *pdev) +{ + /* + * On some devices bootloader isn't ready to a power-gated VDE on + * a warm-reboot, machine will hang in that case. + */ + pm_runtime_get_sync(&pdev->dev); +} + +static __maybe_unused int tegra_vde_pm_suspend(struct device *dev) +{ + struct tegra_vde *vde = dev_get_drvdata(dev); + int err; + + mutex_lock(&vde->lock); + + err = pm_runtime_force_suspend(dev); + if (err < 0) + return err; + + return 0; +} + +static __maybe_unused int tegra_vde_pm_resume(struct device *dev) +{ + struct tegra_vde *vde = dev_get_drvdata(dev); + int err; + + err = pm_runtime_force_resume(dev); + if (err < 0) + return err; + + mutex_unlock(&vde->lock); + + return 0; +} + +static const struct dev_pm_ops tegra_vde_pm_ops = { + SET_RUNTIME_PM_OPS(tegra_vde_runtime_suspend, + tegra_vde_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(tegra_vde_pm_suspend, + tegra_vde_pm_resume) +}; + +static const u32 tegra124_decoded_fmts[] = { + /* TBD: T124 supports only a non-standard Tegra tiled format */ +}; + +static const struct tegra_coded_fmt_desc tegra124_coded_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_H264_SLICE, + .frmsize = { + .min_width = 16, + .max_width = 1920, + .step_width = 16, + .min_height = 16, + .max_height = 2032, + .step_height = 16, + }, + .num_decoded_fmts = ARRAY_SIZE(tegra124_decoded_fmts), + .decoded_fmts = tegra124_decoded_fmts, + .decode_run = tegra_vde_h264_decode_run, + .decode_wait = tegra_vde_h264_decode_wait, + }, +}; + +static const u32 tegra20_decoded_fmts[] = { + V4L2_PIX_FMT_YUV420M, + V4L2_PIX_FMT_YVU420M, +}; + +static const struct tegra_coded_fmt_desc tegra20_coded_fmts[] = { + { + .fourcc = V4L2_PIX_FMT_H264_SLICE, + .frmsize = { + .min_width = 16, + .max_width = 1920, + .step_width = 16, + .min_height = 16, + .max_height = 2032, + .step_height = 16, + }, + .num_decoded_fmts = ARRAY_SIZE(tegra20_decoded_fmts), + .decoded_fmts = tegra20_decoded_fmts, + .decode_run = tegra_vde_h264_decode_run, + .decode_wait = tegra_vde_h264_decode_wait, + }, +}; + +static const struct tegra_vde_soc tegra124_vde_soc = { + .supports_ref_pic_marking = true, + .coded_fmts = tegra124_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(tegra124_coded_fmts), +}; + +static const struct tegra_vde_soc tegra114_vde_soc = { + .supports_ref_pic_marking = true, + .coded_fmts = tegra20_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(tegra20_coded_fmts), +}; + +static const struct tegra_vde_soc tegra30_vde_soc = { + .supports_ref_pic_marking = false, + .coded_fmts = tegra20_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(tegra20_coded_fmts), +}; + +static const struct tegra_vde_soc tegra20_vde_soc = { + .supports_ref_pic_marking = false, + .coded_fmts = tegra20_coded_fmts, + .num_coded_fmts = ARRAY_SIZE(tegra20_coded_fmts), +}; + +static const struct of_device_id tegra_vde_of_match[] = { + { .compatible = "nvidia,tegra124-vde", .data = &tegra124_vde_soc }, + { .compatible = "nvidia,tegra114-vde", .data = &tegra114_vde_soc }, + { .compatible = "nvidia,tegra30-vde", .data = &tegra30_vde_soc }, + { .compatible = "nvidia,tegra20-vde", .data = &tegra20_vde_soc }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_vde_of_match); + +static struct platform_driver tegra_vde_driver = { + .probe = tegra_vde_probe, + .remove = tegra_vde_remove, + .shutdown = tegra_vde_shutdown, + .driver = { + .name = "tegra-vde", + .of_match_table = tegra_vde_of_match, + .pm = &tegra_vde_pm_ops, + }, +}; +module_platform_driver(tegra_vde_driver); + +MODULE_DESCRIPTION("NVIDIA Tegra Video Decoder driver"); +MODULE_AUTHOR("Dmitry Osipenko "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/nvidia/tegra-vde/vde.h b/drivers/media/platform/nvidia/tegra-vde/vde.h new file mode 100644 index 000000000..0fbb1f3d2 --- /dev/null +++ b/drivers/media/platform/nvidia/tegra-vde/vde.h @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * NVIDIA Tegra Video decoder driver + * + * Copyright (C) 2016-2019 GRATE-DRIVER project + */ + +#ifndef TEGRA_VDE_H +#define TEGRA_VDE_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define ICMDQUE_WR 0x00 +#define CMDQUE_CONTROL 0x08 +#define INTR_STATUS 0x18 +#define BSE_INT_ENB 0x40 +#define BSE_CONFIG 0x44 + +#define BSE_ICMDQUE_EMPTY BIT(3) +#define BSE_DMA_BUSY BIT(23) + +#define BSEV_ALIGN SZ_1 +#define FRAMEID_ALIGN SZ_256 +#define SXE_BUFFER SZ_32K +#define VDE_ATOM SZ_16 + +struct clk; +struct dma_buf; +struct gen_pool; +struct tegra_ctx; +struct iommu_group; +struct iommu_domain; +struct reset_control; +struct dma_buf_attachment; +struct tegra_vde_h264_frame; +struct tegra_vde_h264_decoder_ctx; + +struct tegra_video_frame { + struct dma_buf_attachment *y_dmabuf_attachment; + struct dma_buf_attachment *cb_dmabuf_attachment; + struct dma_buf_attachment *cr_dmabuf_attachment; + struct dma_buf_attachment *aux_dmabuf_attachment; + dma_addr_t y_addr; + dma_addr_t cb_addr; + dma_addr_t cr_addr; + dma_addr_t aux_addr; + u32 frame_num; + u32 flags; + u32 luma_atoms_pitch; + u32 chroma_atoms_pitch; +}; + +struct tegra_coded_fmt_desc { + u32 fourcc; + struct v4l2_frmsize_stepwise frmsize; + unsigned int num_decoded_fmts; + const u32 *decoded_fmts; + int (*decode_run)(struct tegra_ctx *ctx); + int (*decode_wait)(struct tegra_ctx *ctx); +}; + +struct tegra_vde_soc { + bool supports_ref_pic_marking; + const struct tegra_coded_fmt_desc *coded_fmts; + u32 num_coded_fmts; +}; + +struct tegra_vde_bo { + struct iova *iova; + struct sg_table sgt; + struct tegra_vde *vde; + enum dma_data_direction dma_dir; + unsigned long dma_attrs; + dma_addr_t dma_handle; + dma_addr_t dma_addr; + void *dma_cookie; + size_t size; +}; + +struct tegra_vde { + void __iomem *sxe; + void __iomem *bsev; + void __iomem *mbe; + void __iomem *ppe; + void __iomem *mce; + void __iomem *tfe; + void __iomem *ppb; + void __iomem *vdma; + void __iomem *frameid; + struct device *dev; + struct mutex lock; + struct mutex map_lock; + struct list_head map_list; + struct reset_control *rst; + struct reset_control *rst_mc; + struct gen_pool *iram_pool; + struct completion decode_completion; + struct clk *clk; + struct iommu_domain *domain; + struct iommu_group *group; + struct iova_domain iova; + struct iova *iova_resv_static_addresses; + struct iova *iova_resv_last_page; + const struct tegra_vde_soc *soc; + struct tegra_vde_bo *secure_bo; + dma_addr_t bitstream_data_addr; + dma_addr_t iram_lists_addr; + u32 *iram; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m; + struct media_device mdev; + struct video_device vdev; + struct mutex v4l2_lock; + struct workqueue_struct *wq; + struct tegra_video_frame frames[V4L2_H264_NUM_DPB_ENTRIES + 1]; +}; + +int tegra_vde_alloc_bo(struct tegra_vde *vde, + struct tegra_vde_bo **ret_bo, + enum dma_data_direction dma_dir, + size_t size); +void tegra_vde_free_bo(struct tegra_vde_bo *bo); + +struct tegra_ctx_h264 { + const struct v4l2_ctrl_h264_decode_params *decode_params; + const struct v4l2_ctrl_h264_sps *sps; + const struct v4l2_ctrl_h264_pps *pps; +}; + +struct tegra_ctx { + struct tegra_vde *vde; + struct tegra_ctx_h264 h264; + struct work_struct work; + struct v4l2_fh fh; + struct v4l2_ctrl_handler hdl; + struct v4l2_format coded_fmt; + struct v4l2_format decoded_fmt; + const struct tegra_coded_fmt_desc *coded_fmt_desc; + struct v4l2_ctrl *ctrls[]; +}; + +struct tegra_m2m_buffer { + struct v4l2_m2m_buffer m2m; + struct dma_buf_attachment *a[VB2_MAX_PLANES]; + dma_addr_t dma_base[VB2_MAX_PLANES]; + dma_addr_t dma_addr[VB2_MAX_PLANES]; + struct iova *iova[VB2_MAX_PLANES]; + struct tegra_vde_bo *aux; + bool b_frame; +}; + +static inline struct tegra_m2m_buffer * +vb_to_tegra_buf(struct vb2_buffer *vb) +{ + struct v4l2_m2m_buffer *m2m = container_of(vb, struct v4l2_m2m_buffer, + vb.vb2_buf); + + return container_of(m2m, struct tegra_m2m_buffer, m2m); +} + +void tegra_vde_prepare_control_data(struct tegra_ctx *ctx, u32 id); + +void tegra_vde_writel(struct tegra_vde *vde, u32 value, void __iomem *base, + u32 offset); +u32 tegra_vde_readl(struct tegra_vde *vde, void __iomem *base, u32 offset); +void tegra_vde_set_bits(struct tegra_vde *vde, u32 mask, void __iomem *base, + u32 offset); + +int tegra_vde_h264_decode_run(struct tegra_ctx *ctx); +int tegra_vde_h264_decode_wait(struct tegra_ctx *ctx); + +int tegra_vde_iommu_init(struct tegra_vde *vde); +void tegra_vde_iommu_deinit(struct tegra_vde *vde); +int tegra_vde_iommu_map(struct tegra_vde *vde, + struct sg_table *sgt, + struct iova **iovap, + size_t size); +void tegra_vde_iommu_unmap(struct tegra_vde *vde, struct iova *iova); + +int tegra_vde_dmabuf_cache_map(struct tegra_vde *vde, + struct dma_buf *dmabuf, + enum dma_data_direction dma_dir, + struct dma_buf_attachment **ap, + dma_addr_t *addrp); +void tegra_vde_dmabuf_cache_unmap(struct tegra_vde *vde, + struct dma_buf_attachment *a, + bool release); +void tegra_vde_dmabuf_cache_unmap_sync(struct tegra_vde *vde); +void tegra_vde_dmabuf_cache_unmap_all(struct tegra_vde *vde); + +static __maybe_unused char const * +tegra_vde_reg_base_name(struct tegra_vde *vde, void __iomem *base) +{ + if (vde->sxe == base) + return "SXE"; + + if (vde->bsev == base) + return "BSEV"; + + if (vde->mbe == base) + return "MBE"; + + if (vde->ppe == base) + return "PPE"; + + if (vde->mce == base) + return "MCE"; + + if (vde->tfe == base) + return "TFE"; + + if (vde->ppb == base) + return "PPB"; + + if (vde->vdma == base) + return "VDMA"; + + if (vde->frameid == base) + return "FRAMEID"; + + return "???"; +} + +int tegra_vde_v4l2_init(struct tegra_vde *vde); +void tegra_vde_v4l2_deinit(struct tegra_vde *vde); + +#endif /* TEGRA_VDE_H */ diff --git a/drivers/media/platform/nxp/Kconfig b/drivers/media/platform/nxp/Kconfig new file mode 100644 index 000000000..591763488 --- /dev/null +++ b/drivers/media/platform/nxp/Kconfig @@ -0,0 +1,43 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# V4L drivers + +comment "NXP media platform drivers" + +config VIDEO_IMX_MIPI_CSIS + tristate "NXP MIPI CSI-2 CSIS receiver found on i.MX7 and i.MX8 models" + depends on ARCH_MXC || COMPILE_TEST + depends on VIDEO_DEV + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + help + Video4Linux2 sub-device driver for the MIPI CSI-2 CSIS receiver + v3.3/v3.6.3 found on some i.MX7 and i.MX8 SoCs. + +# mem2mem drivers + +config VIDEO_IMX_PXP + tristate "NXP i.MX Pixel Pipeline (PXP)" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV && (ARCH_MXC || COMPILE_TEST) + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + The i.MX Pixel Pipeline is a memory-to-memory engine for scaling, + color space conversion, and rotation. + +config VIDEO_MX2_EMMAPRP + tristate "NXP MX2 eMMa-PrP support" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on SOC_IMX27 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + MX2X chips have a PrP that can be used to process buffers from + memory to memory. Operations include resizing and format + conversion. + +source "drivers/media/platform/nxp/dw100/Kconfig" +source "drivers/media/platform/nxp/imx-jpeg/Kconfig" diff --git a/drivers/media/platform/nxp/Makefile b/drivers/media/platform/nxp/Makefile new file mode 100644 index 000000000..81ab304ef --- /dev/null +++ b/drivers/media/platform/nxp/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-y += dw100/ +obj-y += imx-jpeg/ + +obj-$(CONFIG_VIDEO_IMX_MIPI_CSIS) += imx-mipi-csis.o +obj-$(CONFIG_VIDEO_IMX_PXP) += imx-pxp.o +obj-$(CONFIG_VIDEO_MX2_EMMAPRP) += mx2_emmaprp.o diff --git a/drivers/media/platform/nxp/dw100/Kconfig b/drivers/media/platform/nxp/dw100/Kconfig new file mode 100644 index 000000000..cd4531bb3 --- /dev/null +++ b/drivers/media/platform/nxp/dw100/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_DW100 + tristate "NXP i.MX DW100 dewarper" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_MXC || COMPILE_TEST + select MEDIA_CONTROLLER + select V4L2_MEM2MEM_DEV + select VIDEOBUF2_DMA_CONTIG + help + DW100 is a memory-to-memory engine performing geometrical + transformation on source images through a programmable dewarping map. + + To compile this driver as a module, choose M here: the module + will be called dw100. diff --git a/drivers/media/platform/nxp/dw100/Makefile b/drivers/media/platform/nxp/dw100/Makefile new file mode 100644 index 000000000..49db80589 --- /dev/null +++ b/drivers/media/platform/nxp/dw100/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0+ + +obj-$(CONFIG_VIDEO_DW100) += dw100.o diff --git a/drivers/media/platform/nxp/dw100/dw100.c b/drivers/media/platform/nxp/dw100/dw100.c new file mode 100644 index 000000000..f6d48c36f --- /dev/null +++ b/drivers/media/platform/nxp/dw100/dw100.c @@ -0,0 +1,1707 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * DW100 Hardware dewarper + * + * Copyright 2022 NXP + * Author: Xavier Roumegue (xavier.roumegue@oss.nxp.com) + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "dw100_regs.h" + +#define DRV_NAME "dw100" + +#define DW100_MIN_W 176u +#define DW100_MIN_H 144u +#define DW100_MAX_W 4096u +#define DW100_MAX_H 3072u +#define DW100_ALIGN_W 3 +#define DW100_ALIGN_H 3 + +#define DW100_BLOCK_SIZE 16 + +#define DW100_DEF_W 640u +#define DW100_DEF_H 480u +#define DW100_DEF_LUT_W (DIV_ROUND_UP(DW100_DEF_W, DW100_BLOCK_SIZE) + 1) +#define DW100_DEF_LUT_H (DIV_ROUND_UP(DW100_DEF_H, DW100_BLOCK_SIZE) + 1) + +/* + * 16 controls have been reserved for this driver for future extension, but + * let's limit the related driver allocation to the effective number of controls + * in use. + */ +#define DW100_MAX_CTRLS 1 +#define DW100_CTRL_DEWARPING_MAP 0 + +enum { + DW100_QUEUE_SRC = 0, + DW100_QUEUE_DST = 1, +}; + +enum { + DW100_FMT_CAPTURE = BIT(0), + DW100_FMT_OUTPUT = BIT(1), +}; + +struct dw100_device { + struct platform_device *pdev; + struct v4l2_m2m_dev *m2m_dev; + struct v4l2_device v4l2_dev; + struct video_device vfd; + struct media_device mdev; + /* Video device lock */ + struct mutex vfd_mutex; + void __iomem *mmio; + struct clk_bulk_data *clks; + int num_clks; + struct dentry *debugfs_root; +}; + +struct dw100_q_data { + struct v4l2_pix_format_mplane pix_fmt; + unsigned int sequence; + const struct dw100_fmt *fmt; + struct v4l2_rect crop; +}; + +struct dw100_ctx { + struct v4l2_fh fh; + struct dw100_device *dw_dev; + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *ctrls[DW100_MAX_CTRLS]; + /* per context m2m queue lock */ + struct mutex vq_mutex; + + /* Look Up Table for pixel remapping */ + unsigned int *map; + dma_addr_t map_dma; + size_t map_size; + unsigned int map_width; + unsigned int map_height; + bool user_map_is_set; + + /* Source and destination queue data */ + struct dw100_q_data q_data[2]; +}; + +static const struct v4l2_frmsize_stepwise dw100_frmsize_stepwise = { + .min_width = DW100_MIN_W, + .min_height = DW100_MIN_H, + .max_width = DW100_MAX_W, + .max_height = DW100_MAX_H, + .step_width = 1UL << DW100_ALIGN_W, + .step_height = 1UL << DW100_ALIGN_H, +}; + +static const struct dw100_fmt { + u32 fourcc; + u32 types; + u32 reg_format; + bool reg_swap_uv; +} formats[] = { + { + .fourcc = V4L2_PIX_FMT_NV16, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_NV16M, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .types = DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, + .reg_swap_uv = true, + }, { + .fourcc = V4L2_PIX_FMT_NV61M, + .types = DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_SP, + .reg_swap_uv = true, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED, + .reg_swap_uv = true, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_NV12M, + .types = DW100_FMT_OUTPUT | DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, + .reg_swap_uv = false, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .types = DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, + .reg_swap_uv = true, + }, { + .fourcc = V4L2_PIX_FMT_NV21M, + .types = DW100_FMT_CAPTURE, + .reg_format = DW100_DEWARP_CTRL_FORMAT_YUV420_SP, + .reg_swap_uv = true, + }, +}; + +static inline int to_dw100_fmt_type(enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return DW100_FMT_OUTPUT; + else + return DW100_FMT_CAPTURE; +} + +static const struct dw100_fmt *dw100_find_pixel_format(u32 pixel_format, + int fmt_type) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + const struct dw100_fmt *fmt = &formats[i]; + + if (fmt->fourcc == pixel_format && fmt->types & fmt_type) + return fmt; + } + + return NULL; +} + +static const struct dw100_fmt *dw100_find_format(struct v4l2_format *f) +{ + return dw100_find_pixel_format(f->fmt.pix_mp.pixelformat, + to_dw100_fmt_type(f->type)); +} + +static inline u32 dw100_read(struct dw100_device *dw_dev, u32 reg) +{ + return readl(dw_dev->mmio + reg); +} + +static inline void dw100_write(struct dw100_device *dw_dev, u32 reg, u32 val) +{ + writel(val, dw_dev->mmio + reg); +} + +static inline int dw100_dump_regs(struct seq_file *m) +{ + struct dw100_device *dw_dev = m->private; +#define __DECLARE_REG(x) { #x, x } + unsigned int i; + static const struct reg_desc { + const char * const name; + unsigned int addr; + } dw100_regs[] = { + __DECLARE_REG(DW100_DEWARP_ID), + __DECLARE_REG(DW100_DEWARP_CTRL), + __DECLARE_REG(DW100_MAP_LUT_ADDR), + __DECLARE_REG(DW100_MAP_LUT_SIZE), + __DECLARE_REG(DW100_MAP_LUT_ADDR2), + __DECLARE_REG(DW100_MAP_LUT_SIZE2), + __DECLARE_REG(DW100_SRC_IMG_Y_BASE), + __DECLARE_REG(DW100_SRC_IMG_UV_BASE), + __DECLARE_REG(DW100_SRC_IMG_SIZE), + __DECLARE_REG(DW100_SRC_IMG_STRIDE), + __DECLARE_REG(DW100_DST_IMG_Y_BASE), + __DECLARE_REG(DW100_DST_IMG_UV_BASE), + __DECLARE_REG(DW100_DST_IMG_SIZE), + __DECLARE_REG(DW100_DST_IMG_STRIDE), + __DECLARE_REG(DW100_DST_IMG_Y_SIZE1), + __DECLARE_REG(DW100_DST_IMG_UV_SIZE1), + __DECLARE_REG(DW100_SRC_IMG_Y_BASE2), + __DECLARE_REG(DW100_SRC_IMG_UV_BASE2), + __DECLARE_REG(DW100_SRC_IMG_SIZE2), + __DECLARE_REG(DW100_SRC_IMG_STRIDE2), + __DECLARE_REG(DW100_DST_IMG_Y_BASE2), + __DECLARE_REG(DW100_DST_IMG_UV_BASE2), + __DECLARE_REG(DW100_DST_IMG_SIZE2), + __DECLARE_REG(DW100_DST_IMG_STRIDE2), + __DECLARE_REG(DW100_DST_IMG_Y_SIZE2), + __DECLARE_REG(DW100_DST_IMG_UV_SIZE2), + __DECLARE_REG(DW100_SWAP_CONTROL), + __DECLARE_REG(DW100_VERTICAL_SPLIT_LINE), + __DECLARE_REG(DW100_HORIZON_SPLIT_LINE), + __DECLARE_REG(DW100_SCALE_FACTOR), + __DECLARE_REG(DW100_ROI_START), + __DECLARE_REG(DW100_BOUNDARY_PIXEL), + __DECLARE_REG(DW100_INTERRUPT_STATUS), + __DECLARE_REG(DW100_BUS_CTRL), + __DECLARE_REG(DW100_BUS_CTRL1), + __DECLARE_REG(DW100_BUS_TIME_OUT_CYCLE), + }; + + for (i = 0; i < ARRAY_SIZE(dw100_regs); i++) + seq_printf(m, "%s: %#x\n", dw100_regs[i].name, + dw100_read(dw_dev, dw100_regs[i].addr)); + + return 0; +} + +static inline struct dw100_ctx *dw100_file2ctx(struct file *file) +{ + return container_of(file->private_data, struct dw100_ctx, fh); +} + +static struct dw100_q_data *dw100_get_q_data(struct dw100_ctx *ctx, + enum v4l2_buf_type type) +{ + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return &ctx->q_data[DW100_QUEUE_SRC]; + else + return &ctx->q_data[DW100_QUEUE_DST]; +} + +static u32 dw100_get_n_vertices_from_length(u32 length) +{ + return DIV_ROUND_UP(length, DW100_BLOCK_SIZE) + 1; +} + +static u16 dw100_map_convert_to_uq12_4(u32 a) +{ + return (u16)((a & 0xfff) << 4); +} + +static u32 dw100_map_format_coordinates(u16 xq, u16 yq) +{ + return (u32)((yq << 16) | xq); +} + +static u32 *dw100_get_user_map(struct dw100_ctx *ctx) +{ + struct v4l2_ctrl *ctrl = ctx->ctrls[DW100_CTRL_DEWARPING_MAP]; + + return ctrl->p_cur.p_u32; +} + +/* + * Create the dewarp map used by the hardware from the V4L2 control values which + * have been initialized with an identity map or set by the application. + */ +static int dw100_create_mapping(struct dw100_ctx *ctx) +{ + u32 *user_map; + + if (ctx->map) + dma_free_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, + ctx->map, ctx->map_dma); + + ctx->map = dma_alloc_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, + &ctx->map_dma, GFP_KERNEL); + + if (!ctx->map) + return -ENOMEM; + + user_map = dw100_get_user_map(ctx); + memcpy(ctx->map, user_map, ctx->map_size); + + dev_dbg(&ctx->dw_dev->pdev->dev, + "%ux%u %s mapping created (d:%pad-c:%p) for stream %ux%u->%ux%u\n", + ctx->map_width, ctx->map_height, + ctx->user_map_is_set ? "user" : "identity", + &ctx->map_dma, ctx->map, + ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width, + ctx->q_data[DW100_QUEUE_DST].pix_fmt.height, + ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width, + ctx->q_data[DW100_QUEUE_DST].pix_fmt.height); + + return 0; +} + +static void dw100_destroy_mapping(struct dw100_ctx *ctx) +{ + if (ctx->map) { + dma_free_coherent(&ctx->dw_dev->pdev->dev, ctx->map_size, + ctx->map, ctx->map_dma); + ctx->map = NULL; + } +} + +static int dw100_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dw100_ctx *ctx = + container_of(ctrl->handler, struct dw100_ctx, hdl); + + switch (ctrl->id) { + case V4L2_CID_DW100_DEWARPING_16x16_VERTEX_MAP: + ctx->user_map_is_set = true; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops dw100_ctrl_ops = { + .s_ctrl = dw100_s_ctrl, +}; + +/* + * Initialize the dewarping map with an identity mapping. + * + * A 16 pixels cell size grid is mapped on the destination image. + * The last cells width/height might be lesser than 16 if the destination image + * width/height is not divisible by 16. This dewarping grid map specifies the + * source image pixel location (x, y) on each grid intersection point. + * Bilinear interpolation is used to compute inner cell points locations. + * + * The coordinates are saved in UQ12.4 fixed point format. + */ +static void dw100_ctrl_dewarping_map_init(const struct v4l2_ctrl *ctrl, + u32 from_idx, + union v4l2_ctrl_ptr ptr) +{ + struct dw100_ctx *ctx = + container_of(ctrl->handler, struct dw100_ctx, hdl); + + u32 sw, sh, mw, mh, idx; + u16 qx, qy, qdx, qdy, qsh, qsw; + u32 *map = ctrl->p_cur.p_u32; + + sw = ctx->q_data[DW100_QUEUE_SRC].pix_fmt.width; + sh = ctx->q_data[DW100_QUEUE_SRC].pix_fmt.height; + + mw = ctrl->dims[0]; + mh = ctrl->dims[1]; + + qsw = dw100_map_convert_to_uq12_4(sw); + qsh = dw100_map_convert_to_uq12_4(sh); + qdx = qsw / (mw - 1); + qdy = qsh / (mh - 1); + + ctx->map_width = mw; + ctx->map_height = mh; + ctx->map_size = mh * mw * sizeof(u32); + + for (idx = from_idx; idx < ctrl->elems; idx++) { + qy = min_t(u32, (idx / mw) * qdy, qsh); + qx = min_t(u32, (idx % mw) * qdx, qsw); + map[idx] = dw100_map_format_coordinates(qx, qy); + } + + ctx->user_map_is_set = false; +} + +static const struct v4l2_ctrl_type_ops dw100_ctrl_type_ops = { + .init = dw100_ctrl_dewarping_map_init, + .validate = v4l2_ctrl_type_op_validate, + .log = v4l2_ctrl_type_op_log, + .equal = v4l2_ctrl_type_op_equal, +}; + +static const struct v4l2_ctrl_config controls[] = { + [DW100_CTRL_DEWARPING_MAP] = { + .ops = &dw100_ctrl_ops, + .type_ops = &dw100_ctrl_type_ops, + .id = V4L2_CID_DW100_DEWARPING_16x16_VERTEX_MAP, + .name = "Dewarping Vertex Map", + .type = V4L2_CTRL_TYPE_U32, + .min = 0x00000000, + .max = 0xffffffff, + .step = 1, + .def = 0, + .dims = { DW100_DEF_LUT_W, DW100_DEF_LUT_H }, + }, +}; + +static int dw100_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct dw100_ctx *ctx = vb2_get_drv_priv(vq); + const struct v4l2_pix_format_mplane *format; + unsigned int i; + + format = &dw100_get_q_data(ctx, vq->type)->pix_fmt; + + if (*nplanes) { + if (*nplanes != format->num_planes) + return -EINVAL; + + for (i = 0; i < *nplanes; ++i) { + if (sizes[i] < format->plane_fmt[i].sizeimage) + return -EINVAL; + } + + return 0; + } + + *nplanes = format->num_planes; + + for (i = 0; i < format->num_planes; ++i) + sizes[i] = format->plane_fmt[i].sizeimage; + + return 0; +} + +static int dw100_buf_prepare(struct vb2_buffer *vb) +{ + unsigned int i; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dw100_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct dw100_device *dw_dev = ctx->dw_dev; + const struct v4l2_pix_format_mplane *pix_fmt = + &dw100_get_q_data(ctx, vb->vb2_queue->type)->pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field != V4L2_FIELD_NONE) { + dev_dbg(&dw_dev->pdev->dev, "%x field isn't supported\n", + vbuf->field); + return -EINVAL; + } + } + + for (i = 0; i < pix_fmt->num_planes; i++) { + unsigned long size = pix_fmt->plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + dev_dbg(&dw_dev->pdev->dev, + "User buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static void dw100_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dw100_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void dw100_return_all_buffers(struct vb2_queue *q, + enum vb2_buffer_state state) +{ + struct dw100_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) + return; + v4l2_m2m_buf_done(vbuf, state); + } +} + +static int dw100_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct dw100_ctx *ctx = vb2_get_drv_priv(q); + struct dw100_q_data *q_data = dw100_get_q_data(ctx, q->type); + int ret; + + q_data->sequence = 0; + + ret = dw100_create_mapping(ctx); + if (ret) + goto err; + + ret = pm_runtime_resume_and_get(&ctx->dw_dev->pdev->dev); + if (ret) { + dw100_destroy_mapping(ctx); + goto err; + } + + return 0; +err: + dw100_return_all_buffers(q, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void dw100_stop_streaming(struct vb2_queue *q) +{ + struct dw100_ctx *ctx = vb2_get_drv_priv(q); + + dw100_return_all_buffers(q, VB2_BUF_STATE_ERROR); + + pm_runtime_put_sync(&ctx->dw_dev->pdev->dev); + + dw100_destroy_mapping(ctx); +} + +static const struct vb2_ops dw100_qops = { + .queue_setup = dw100_queue_setup, + .buf_prepare = dw100_buf_prepare, + .buf_queue = dw100_buf_queue, + .start_streaming = dw100_start_streaming, + .stop_streaming = dw100_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int dw100_m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct dw100_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &dw100_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->vq_mutex; + src_vq->dev = ctx->dw_dev->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &dw100_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->vq_mutex; + dst_vq->dev = ctx->dw_dev->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +static int dw100_open(struct file *file) +{ + struct dw100_device *dw_dev = video_drvdata(file); + struct dw100_ctx *ctx; + struct v4l2_ctrl_handler *hdl; + struct v4l2_pix_format_mplane *pix_fmt; + int ret, i; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + mutex_init(&ctx->vq_mutex); + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dw_dev = dw_dev; + + ctx->q_data[DW100_QUEUE_SRC].fmt = &formats[0]; + + pix_fmt = &ctx->q_data[DW100_QUEUE_SRC].pix_fmt; + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = V4L2_COLORSPACE_REC709; + pix_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix_fmt->colorspace); + pix_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_fmt->colorspace); + pix_fmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, pix_fmt->colorspace, + pix_fmt->ycbcr_enc); + + v4l2_fill_pixfmt_mp(pix_fmt, formats[0].fourcc, DW100_DEF_W, DW100_DEF_H); + + ctx->q_data[DW100_QUEUE_SRC].crop.top = 0; + ctx->q_data[DW100_QUEUE_SRC].crop.left = 0; + ctx->q_data[DW100_QUEUE_SRC].crop.width = DW100_DEF_W; + ctx->q_data[DW100_QUEUE_SRC].crop.height = DW100_DEF_H; + + ctx->q_data[DW100_QUEUE_DST] = ctx->q_data[DW100_QUEUE_SRC]; + + hdl = &ctx->hdl; + v4l2_ctrl_handler_init(hdl, ARRAY_SIZE(controls)); + for (i = 0; i < ARRAY_SIZE(controls); i++) { + ctx->ctrls[i] = v4l2_ctrl_new_custom(hdl, &controls[i], NULL); + if (hdl->error) { + dev_err(&ctx->dw_dev->pdev->dev, + "Adding control (%d) failed\n", i); + ret = hdl->error; + goto err; + } + } + ctx->fh.ctrl_handler = hdl; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dw_dev->m2m_dev, + ctx, &dw100_m2m_queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto err; + } + + v4l2_fh_add(&ctx->fh); + + return 0; + +err: + v4l2_ctrl_handler_free(hdl); + v4l2_fh_exit(&ctx->fh); + mutex_destroy(&ctx->vq_mutex); + kfree(ctx); + + return ret; +} + +static int dw100_release(struct file *file) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_destroy(&ctx->vq_mutex); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations dw100_fops = { + .owner = THIS_MODULE, + .open = dw100_open, + .release = dw100_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int dw100_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DRV_NAME, sizeof(cap->driver)); + strscpy(cap->card, "DW100 dewarper", sizeof(cap->card)); + + return 0; +} + +static int dw100_enum_fmt_vid(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + int i, num = 0; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].types & to_dw100_fmt_type(f->type)) { + if (num == f->index) { + f->pixelformat = formats[i].fourcc; + return 0; + } + ++num; + } + } + + return -EINVAL; +} + +static int dw100_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + const struct dw100_fmt *fmt; + + if (fsize->index) + return -EINVAL; + + fmt = dw100_find_pixel_format(fsize->pixel_format, + DW100_FMT_OUTPUT | DW100_FMT_CAPTURE); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise = dw100_frmsize_stepwise; + + return 0; +} + +static int dw100_g_fmt_vid(struct file *file, void *priv, struct v4l2_format *f) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + struct vb2_queue *vq; + struct dw100_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = dw100_get_q_data(ctx, f->type); + + f->fmt.pix_mp = q_data->pix_fmt; + + return 0; +} + +static int dw100_try_fmt(struct file *file, struct v4l2_format *f) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + const struct dw100_fmt *fmt; + + fmt = dw100_find_format(f); + if (!fmt) { + fmt = &formats[0]; + pix->pixelformat = fmt->fourcc; + } + + v4l2_apply_frmsize_constraints(&pix->width, &pix->height, + &dw100_frmsize_stepwise); + + v4l2_fill_pixfmt_mp(pix, fmt->fourcc, pix->width, pix->height); + + pix->field = V4L2_FIELD_NONE; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (pix->colorspace == V4L2_COLORSPACE_DEFAULT) + pix->colorspace = V4L2_COLORSPACE_REC709; + if (pix->xfer_func == V4L2_XFER_FUNC_DEFAULT) + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + if (pix->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + if (pix->quantization == V4L2_QUANTIZATION_DEFAULT) + pix->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, + pix->colorspace, + pix->ycbcr_enc); + } else { + /* + * The DW100 can't perform colorspace conversion, the colorspace + * on the capture queue must be identical to the output queue. + */ + const struct dw100_q_data *q_data = + dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + pix->colorspace = q_data->pix_fmt.colorspace; + pix->xfer_func = q_data->pix_fmt.xfer_func; + pix->ycbcr_enc = q_data->pix_fmt.ycbcr_enc; + pix->quantization = q_data->pix_fmt.quantization; + } + + return 0; +} + +static int dw100_s_fmt(struct dw100_ctx *ctx, struct v4l2_format *f) +{ + struct dw100_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = dw100_get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + dev_dbg(&ctx->dw_dev->pdev->dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = dw100_find_format(f); + q_data->pix_fmt = f->fmt.pix_mp; + q_data->crop.top = 0; + q_data->crop.left = 0; + q_data->crop.width = f->fmt.pix_mp.width; + q_data->crop.height = f->fmt.pix_mp.height; + + /* Propagate buffers encoding */ + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + struct dw100_q_data *dst_q_data = + dw100_get_q_data(ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + dst_q_data->pix_fmt.colorspace = q_data->pix_fmt.colorspace; + dst_q_data->pix_fmt.ycbcr_enc = q_data->pix_fmt.ycbcr_enc; + dst_q_data->pix_fmt.quantization = q_data->pix_fmt.quantization; + dst_q_data->pix_fmt.xfer_func = q_data->pix_fmt.xfer_func; + } + + dev_dbg(&ctx->dw_dev->pdev->dev, + "Setting format for type %u, wxh: %ux%u, fmt: %p4cc\n", + f->type, q_data->pix_fmt.width, q_data->pix_fmt.height, + &q_data->pix_fmt.pixelformat); + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + int ret; + u32 dims[V4L2_CTRL_MAX_DIMS] = {}; + struct v4l2_ctrl *ctrl = ctx->ctrls[DW100_CTRL_DEWARPING_MAP]; + + dims[0] = dw100_get_n_vertices_from_length(q_data->pix_fmt.width); + dims[1] = dw100_get_n_vertices_from_length(q_data->pix_fmt.height); + + ret = v4l2_ctrl_modify_dimensions(ctrl, dims); + + if (ret) { + dev_err(&ctx->dw_dev->pdev->dev, + "Modifying LUT dimensions failed with error %d\n", + ret); + return ret; + } + } + + return 0; +} + +static int dw100_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + return dw100_try_fmt(file, f); +} + +static int dw100_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + int ret; + + ret = dw100_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ret = dw100_s_fmt(ctx, f); + if (ret) + return ret; + + return 0; +} + +static int dw100_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + return dw100_try_fmt(file, f); +} + +static int dw100_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + int ret; + + ret = dw100_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = dw100_s_fmt(ctx, f); + if (ret) + return ret; + + return 0; +} + +static int dw100_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + struct dw100_q_data *src_q_data; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + src_q_data = dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = src_q_data->pix_fmt.width; + sel->r.height = src_q_data->pix_fmt.height; + break; + case V4L2_SEL_TGT_CROP: + sel->r.top = src_q_data->crop.top; + sel->r.left = src_q_data->crop.left; + sel->r.width = src_q_data->crop.width; + sel->r.height = src_q_data->crop.height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dw100_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct dw100_ctx *ctx = dw100_file2ctx(file); + struct dw100_q_data *src_q_data; + u32 qscalex, qscaley, qscale; + int x, y, w, h; + unsigned int wframe, hframe; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + src_q_data = dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + dev_dbg(&ctx->dw_dev->pdev->dev, + ">>> Buffer Type: %u Target: %u Rect: %ux%u@%d.%d\n", + sel->type, sel->target, + sel->r.width, sel->r.height, sel->r.left, sel->r.top); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + wframe = src_q_data->pix_fmt.width; + hframe = src_q_data->pix_fmt.height; + + sel->r.top = clamp_t(int, sel->r.top, 0, hframe - DW100_MIN_H); + sel->r.left = clamp_t(int, sel->r.left, 0, wframe - DW100_MIN_W); + sel->r.height = + clamp(sel->r.height, DW100_MIN_H, hframe - sel->r.top); + sel->r.width = + clamp(sel->r.width, DW100_MIN_W, wframe - sel->r.left); + + /* UQ16.16 for float operations */ + qscalex = (sel->r.width << 16) / wframe; + qscaley = (sel->r.height << 16) / hframe; + y = sel->r.top; + x = sel->r.left; + if (qscalex == qscaley) { + qscale = qscalex; + } else { + switch (sel->flags) { + case 0: + qscale = (qscalex + qscaley) / 2; + break; + case V4L2_SEL_FLAG_GE: + qscale = max(qscaley, qscalex); + break; + case V4L2_SEL_FLAG_LE: + qscale = min(qscaley, qscalex); + break; + case V4L2_SEL_FLAG_LE | V4L2_SEL_FLAG_GE: + return -ERANGE; + default: + return -EINVAL; + } + } + + w = (u32)((((u64)wframe << 16) * qscale) >> 32); + h = (u32)((((u64)hframe << 16) * qscale) >> 32); + x = x + (sel->r.width - w) / 2; + y = y + (sel->r.height - h) / 2; + x = min(wframe - w, (unsigned int)max(0, x)); + y = min(hframe - h, (unsigned int)max(0, y)); + + sel->r.top = y; + sel->r.left = x; + sel->r.width = w; + sel->r.height = h; + + src_q_data->crop.top = sel->r.top; + src_q_data->crop.left = sel->r.left; + src_q_data->crop.width = sel->r.width; + src_q_data->crop.height = sel->r.height; + break; + + default: + return -EINVAL; + } + + dev_dbg(&ctx->dw_dev->pdev->dev, + "<<< Buffer Type: %u Target: %u Rect: %ux%u@%d.%d\n", + sel->type, sel->target, + sel->r.width, sel->r.height, sel->r.left, sel->r.top); + + return 0; +} + +static const struct v4l2_ioctl_ops dw100_ioctl_ops = { + .vidioc_querycap = dw100_querycap, + + .vidioc_enum_fmt_vid_cap = dw100_enum_fmt_vid, + .vidioc_enum_framesizes = dw100_enum_framesizes, + .vidioc_g_fmt_vid_cap_mplane = dw100_g_fmt_vid, + .vidioc_try_fmt_vid_cap_mplane = dw100_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = dw100_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = dw100_enum_fmt_vid, + .vidioc_g_fmt_vid_out_mplane = dw100_g_fmt_vid, + .vidioc_try_fmt_vid_out_mplane = dw100_try_fmt_vid_out, + .vidioc_s_fmt_vid_out_mplane = dw100_s_fmt_vid_out, + + .vidioc_g_selection = dw100_g_selection, + .vidioc_s_selection = dw100_s_selection, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static void dw100_job_finish(struct dw100_device *dw_dev, bool with_error) +{ + struct dw100_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + enum vb2_buffer_state buf_state; + + curr_ctx = v4l2_m2m_get_curr_priv(dw_dev->m2m_dev); + + if (!curr_ctx) { + dev_err(&dw_dev->pdev->dev, + "Instance released before the end of transaction\n"); + return; + } + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + if (likely(!with_error)) + buf_state = VB2_BUF_STATE_DONE; + else + buf_state = VB2_BUF_STATE_ERROR; + + v4l2_m2m_buf_done(src_vb, buf_state); + v4l2_m2m_buf_done(dst_vb, buf_state); + + dev_dbg(&dw_dev->pdev->dev, "Finishing transaction with%s error(s)\n", + with_error ? "" : "out"); + + v4l2_m2m_job_finish(dw_dev->m2m_dev, curr_ctx->fh.m2m_ctx); +} + +static void dw100_hw_reset(struct dw100_device *dw_dev) +{ + u32 val; + + val = dw100_read(dw_dev, DW100_DEWARP_CTRL); + val |= DW100_DEWARP_CTRL_ENABLE; + val |= DW100_DEWARP_CTRL_SOFT_RESET; + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); + val &= ~DW100_DEWARP_CTRL_SOFT_RESET; + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); +} + +static void _dw100_hw_set_master_bus_enable(struct dw100_device *dw_dev, + unsigned int enable) +{ + u32 val; + + dev_dbg(&dw_dev->pdev->dev, "%sable master bus\n", + enable ? "En" : "Dis"); + + val = dw100_read(dw_dev, DW100_BUS_CTRL); + + if (enable) + val |= DW100_BUS_CTRL_AXI_MASTER_ENABLE; + else + val &= ~DW100_BUS_CTRL_AXI_MASTER_ENABLE; + + dw100_write(dw_dev, DW100_BUS_CTRL, val); +} + +static void dw100_hw_master_bus_enable(struct dw100_device *dw_dev) +{ + _dw100_hw_set_master_bus_enable(dw_dev, 1); +} + +static void dw100_hw_master_bus_disable(struct dw100_device *dw_dev) +{ + _dw100_hw_set_master_bus_enable(dw_dev, 0); +} + +static void dw100_hw_dewarp_start(struct dw100_device *dw_dev) +{ + u32 val; + + val = dw100_read(dw_dev, DW100_DEWARP_CTRL); + + dev_dbg(&dw_dev->pdev->dev, "Starting Hardware CTRL:0x%08x\n", val); + dw100_write(dw_dev, DW100_DEWARP_CTRL, val | DW100_DEWARP_CTRL_START); + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); +} + +static void dw100_hw_init_ctrl(struct dw100_device *dw_dev) +{ + u32 val; + /* + * Input format YUV422_SP + * Output format YUV422_SP + * No hardware handshake (SW) + * No automatic double src buffering (Single) + * No automatic double dst buffering (Single) + * No Black Line + * Prefetch image pixel traversal + */ + + val = DW100_DEWARP_CTRL_ENABLE + /* Valid only for auto prefetch mode*/ + | DW100_DEWARP_CTRL_PREFETCH_THRESHOLD(32); + + /* + * Calculation mode required to support any scaling factor, + * but x4 slower than traversal mode. + * + * DW100_DEWARP_CTRL_PREFETCH_MODE_TRAVERSAL + * DW100_DEWARP_CTRL_PREFETCH_MODE_CALCULATION + * DW100_DEWARP_CTRL_PREFETCH_MODE_AUTO + * + * TODO: Find heuristics requiring calculation mode + */ + val |= DW100_DEWARP_CTRL_PREFETCH_MODE_CALCULATION; + + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); +} + +static void dw100_hw_set_pixel_boundary(struct dw100_device *dw_dev) +{ + u32 val; + + val = DW100_BOUNDARY_PIXEL_V(128) + | DW100_BOUNDARY_PIXEL_U(128) + | DW100_BOUNDARY_PIXEL_Y(0); + + dw100_write(dw_dev, DW100_BOUNDARY_PIXEL, val); +} + +static void dw100_hw_set_scale(struct dw100_device *dw_dev, u8 scale) +{ + dev_dbg(&dw_dev->pdev->dev, "Setting scale factor to %u\n", scale); + + dw100_write(dw_dev, DW100_SCALE_FACTOR, scale); +} + +static void dw100_hw_set_roi(struct dw100_device *dw_dev, u32 x, u32 y) +{ + u32 val; + + dev_dbg(&dw_dev->pdev->dev, "Setting ROI region to %u.%u\n", x, y); + + val = DW100_ROI_START_X(x) | DW100_ROI_START_Y(y); + + dw100_write(dw_dev, DW100_ROI_START, val); +} + +static void dw100_hw_set_src_crop(struct dw100_device *dw_dev, + const struct dw100_q_data *src_q_data, + const struct dw100_q_data *dst_q_data) +{ + const struct v4l2_rect *rect = &src_q_data->crop; + u32 src_scale, qscale, left_scale, top_scale; + + /* HW Scale is UQ1.7 encoded */ + src_scale = (rect->width << 7) / src_q_data->pix_fmt.width; + dw100_hw_set_scale(dw_dev, src_scale); + + qscale = (dst_q_data->pix_fmt.width << 7) / src_q_data->pix_fmt.width; + + left_scale = ((rect->left << 7) * qscale) >> 14; + top_scale = ((rect->top << 7) * qscale) >> 14; + + dw100_hw_set_roi(dw_dev, left_scale, top_scale); +} + +static void dw100_hw_set_source(struct dw100_device *dw_dev, + const struct dw100_q_data *q_data, + struct vb2_buffer *buffer) +{ + u32 width, height, stride, fourcc, val; + const struct dw100_fmt *fmt = q_data->fmt; + dma_addr_t addr_y = vb2_dma_contig_plane_dma_addr(buffer, 0); + dma_addr_t addr_uv; + + width = q_data->pix_fmt.width; + height = q_data->pix_fmt.height; + stride = q_data->pix_fmt.plane_fmt[0].bytesperline; + fourcc = q_data->fmt->fourcc; + + if (q_data->pix_fmt.num_planes == 2) + addr_uv = vb2_dma_contig_plane_dma_addr(buffer, 1); + else + addr_uv = addr_y + (stride * height); + + dev_dbg(&dw_dev->pdev->dev, + "Set HW source registers for %ux%u - stride %u, pixfmt: %p4cc, dma:%pad\n", + width, height, stride, &fourcc, &addr_y); + + /* Pixel Format */ + val = dw100_read(dw_dev, DW100_DEWARP_CTRL); + + val &= ~DW100_DEWARP_CTRL_INPUT_FORMAT_MASK; + val |= DW100_DEWARP_CTRL_INPUT_FORMAT(fmt->reg_format); + + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); + + /* Swap */ + val = dw100_read(dw_dev, DW100_SWAP_CONTROL); + + val &= ~DW100_SWAP_CONTROL_SRC_MASK; + /* + * Data swapping is performed only on Y plane for source image. + */ + if (fmt->reg_swap_uv && + fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED) + val |= DW100_SWAP_CONTROL_SRC(DW100_SWAP_CONTROL_Y + (DW100_SWAP_CONTROL_BYTE)); + + dw100_write(dw_dev, DW100_SWAP_CONTROL, val); + + /* Image resolution */ + dw100_write(dw_dev, DW100_SRC_IMG_SIZE, + DW100_IMG_SIZE_WIDTH(width) | DW100_IMG_SIZE_HEIGHT(height)); + + dw100_write(dw_dev, DW100_SRC_IMG_STRIDE, stride); + + /* Buffers */ + dw100_write(dw_dev, DW100_SRC_IMG_Y_BASE, DW100_IMG_Y_BASE(addr_y)); + dw100_write(dw_dev, DW100_SRC_IMG_UV_BASE, DW100_IMG_UV_BASE(addr_uv)); +} + +static void dw100_hw_set_destination(struct dw100_device *dw_dev, + const struct dw100_q_data *q_data, + const struct dw100_fmt *ifmt, + struct vb2_buffer *buffer) +{ + u32 width, height, stride, fourcc, val, size_y, size_uv; + const struct dw100_fmt *fmt = q_data->fmt; + dma_addr_t addr_y, addr_uv; + + width = q_data->pix_fmt.width; + height = q_data->pix_fmt.height; + stride = q_data->pix_fmt.plane_fmt[0].bytesperline; + fourcc = fmt->fourcc; + + addr_y = vb2_dma_contig_plane_dma_addr(buffer, 0); + size_y = q_data->pix_fmt.plane_fmt[0].sizeimage; + + if (q_data->pix_fmt.num_planes == 2) { + addr_uv = vb2_dma_contig_plane_dma_addr(buffer, 1); + size_uv = q_data->pix_fmt.plane_fmt[1].sizeimage; + } else { + addr_uv = addr_y + ALIGN(stride * height, 16); + size_uv = size_y; + if (fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV420_SP) + size_uv /= 2; + } + + dev_dbg(&dw_dev->pdev->dev, + "Set HW source registers for %ux%u - stride %u, pixfmt: %p4cc, dma:%pad\n", + width, height, stride, &fourcc, &addr_y); + + /* Pixel Format */ + val = dw100_read(dw_dev, DW100_DEWARP_CTRL); + + val &= ~DW100_DEWARP_CTRL_OUTPUT_FORMAT_MASK; + val |= DW100_DEWARP_CTRL_OUTPUT_FORMAT(fmt->reg_format); + + dw100_write(dw_dev, DW100_DEWARP_CTRL, val); + + /* Swap */ + val = dw100_read(dw_dev, DW100_SWAP_CONTROL); + + val &= ~DW100_SWAP_CONTROL_DST_MASK; + + /* + * Avoid to swap twice + */ + if (fmt->reg_swap_uv ^ + (ifmt->reg_swap_uv && ifmt->reg_format != + DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED)) { + if (fmt->reg_format == DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED) + val |= DW100_SWAP_CONTROL_DST(DW100_SWAP_CONTROL_Y + (DW100_SWAP_CONTROL_BYTE)); + else + val |= DW100_SWAP_CONTROL_DST(DW100_SWAP_CONTROL_UV + (DW100_SWAP_CONTROL_BYTE)); + } + + dw100_write(dw_dev, DW100_SWAP_CONTROL, val); + + /* Image resolution */ + dw100_write(dw_dev, DW100_DST_IMG_SIZE, + DW100_IMG_SIZE_WIDTH(width) | DW100_IMG_SIZE_HEIGHT(height)); + dw100_write(dw_dev, DW100_DST_IMG_STRIDE, stride); + dw100_write(dw_dev, DW100_DST_IMG_Y_BASE, DW100_IMG_Y_BASE(addr_y)); + dw100_write(dw_dev, DW100_DST_IMG_UV_BASE, DW100_IMG_UV_BASE(addr_uv)); + dw100_write(dw_dev, DW100_DST_IMG_Y_SIZE1, DW100_DST_IMG_Y_SIZE(size_y)); + dw100_write(dw_dev, DW100_DST_IMG_UV_SIZE1, + DW100_DST_IMG_UV_SIZE(size_uv)); +} + +static void dw100_hw_set_mapping(struct dw100_device *dw_dev, dma_addr_t addr, + u32 width, u32 height) +{ + dev_dbg(&dw_dev->pdev->dev, + "Set HW mapping registers for %ux%u addr:%pad", + width, height, &addr); + + dw100_write(dw_dev, DW100_MAP_LUT_ADDR, DW100_MAP_LUT_ADDR_ADDR(addr)); + dw100_write(dw_dev, DW100_MAP_LUT_SIZE, DW100_MAP_LUT_SIZE_WIDTH(width) + | DW100_MAP_LUT_SIZE_HEIGHT(height)); +} + +static void dw100_hw_clear_irq(struct dw100_device *dw_dev, unsigned int irq) +{ + dw100_write(dw_dev, DW100_INTERRUPT_STATUS, + DW100_INTERRUPT_STATUS_INT_CLEAR(irq)); +} + +static void dw100_hw_enable_irq(struct dw100_device *dw_dev) +{ + dw100_write(dw_dev, DW100_INTERRUPT_STATUS, + DW100_INTERRUPT_STATUS_INT_ENABLE_MASK); +} + +static void dw100_hw_disable_irq(struct dw100_device *dw_dev) +{ + dw100_write(dw_dev, DW100_INTERRUPT_STATUS, 0); +} + +static u32 dw_hw_get_pending_irqs(struct dw100_device *dw_dev) +{ + u32 val; + + val = dw100_read(dw_dev, DW100_INTERRUPT_STATUS); + + return DW100_INTERRUPT_STATUS_INT_STATUS(val); +} + +static irqreturn_t dw100_irq_handler(int irq, void *dev_id) +{ + struct dw100_device *dw_dev = dev_id; + u32 pending_irqs, err_irqs, frame_done_irq; + bool with_error = true; + + pending_irqs = dw_hw_get_pending_irqs(dw_dev); + frame_done_irq = pending_irqs & DW100_INTERRUPT_STATUS_INT_FRAME_DONE; + err_irqs = DW100_INTERRUPT_STATUS_INT_ERR_STATUS(pending_irqs); + + if (frame_done_irq) { + dev_dbg(&dw_dev->pdev->dev, "Frame done interrupt\n"); + with_error = false; + err_irqs &= ~DW100_INTERRUPT_STATUS_INT_ERR_STATUS + (DW100_INTERRUPT_STATUS_INT_ERR_FRAME_DONE); + } + + if (err_irqs) + dev_err(&dw_dev->pdev->dev, "Interrupt error: %#x\n", err_irqs); + + dw100_hw_disable_irq(dw_dev); + dw100_hw_master_bus_disable(dw_dev); + dw100_hw_clear_irq(dw_dev, pending_irqs | + DW100_INTERRUPT_STATUS_INT_ERR_TIME_OUT); + + dw100_job_finish(dw_dev, with_error); + + return IRQ_HANDLED; +} + +static void dw100_start(struct dw100_ctx *ctx, struct vb2_v4l2_buffer *in_vb, + struct vb2_v4l2_buffer *out_vb) +{ + struct dw100_device *dw_dev = ctx->dw_dev; + + out_vb->sequence = + dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)->sequence++; + in_vb->sequence = + dw100_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)->sequence++; + + dev_dbg(&ctx->dw_dev->pdev->dev, + "Starting queues %p->%p, sequence %u->%u\n", + v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE), + v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE), + in_vb->sequence, out_vb->sequence); + + v4l2_m2m_buf_copy_metadata(in_vb, out_vb, true); + + /* Now, let's deal with hardware ... */ + dw100_hw_master_bus_disable(dw_dev); + dw100_hw_init_ctrl(dw_dev); + dw100_hw_set_pixel_boundary(dw_dev); + dw100_hw_set_src_crop(dw_dev, &ctx->q_data[DW100_QUEUE_SRC], + &ctx->q_data[DW100_QUEUE_DST]); + dw100_hw_set_source(dw_dev, &ctx->q_data[DW100_QUEUE_SRC], + &in_vb->vb2_buf); + dw100_hw_set_destination(dw_dev, &ctx->q_data[DW100_QUEUE_DST], + ctx->q_data[DW100_QUEUE_SRC].fmt, + &out_vb->vb2_buf); + dw100_hw_set_mapping(dw_dev, ctx->map_dma, + ctx->map_width, ctx->map_height); + dw100_hw_enable_irq(dw_dev); + dw100_hw_dewarp_start(dw_dev); + + /* Enable Bus */ + dw100_hw_master_bus_enable(dw_dev); +} + +static void dw100_device_run(void *priv) +{ + struct dw100_ctx *ctx = priv; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + dw100_start(ctx, src_buf, dst_buf); +} + +static const struct v4l2_m2m_ops dw100_m2m_ops = { + .device_run = dw100_device_run, +}; + +static struct video_device *dw100_init_video_device(struct dw100_device *dw_dev) +{ + struct video_device *vfd = &dw_dev->vfd; + + vfd->vfl_dir = VFL_DIR_M2M; + vfd->fops = &dw100_fops; + vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + vfd->ioctl_ops = &dw100_ioctl_ops; + vfd->minor = -1; + vfd->release = video_device_release_empty; + vfd->v4l2_dev = &dw_dev->v4l2_dev; + vfd->lock = &dw_dev->vfd_mutex; + + strscpy(vfd->name, DRV_NAME, sizeof(vfd->name)); + mutex_init(vfd->lock); + video_set_drvdata(vfd, dw_dev); + + return vfd; +} + +static int dw100_dump_regs_show(struct seq_file *m, void *private) +{ + struct dw100_device *dw_dev = m->private; + int ret; + + ret = pm_runtime_resume_and_get(&dw_dev->pdev->dev); + if (ret < 0) + return ret; + + ret = dw100_dump_regs(m); + + pm_runtime_put_sync(&dw_dev->pdev->dev); + + return ret; +} +DEFINE_SHOW_ATTRIBUTE(dw100_dump_regs); + +static void dw100_debugfs_init(struct dw100_device *dw_dev) +{ + dw_dev->debugfs_root = + debugfs_create_dir(dev_name(&dw_dev->pdev->dev), NULL); + + debugfs_create_file("dump_regs", 0600, dw_dev->debugfs_root, dw_dev, + &dw100_dump_regs_fops); +} + +static void dw100_debugfs_exit(struct dw100_device *dw_dev) +{ + debugfs_remove_recursive(dw_dev->debugfs_root); +} + +static int dw100_probe(struct platform_device *pdev) +{ + struct dw100_device *dw_dev; + struct video_device *vfd; + struct resource *res; + int ret, irq; + + dw_dev = devm_kzalloc(&pdev->dev, sizeof(*dw_dev), GFP_KERNEL); + if (!dw_dev) + return -ENOMEM; + dw_dev->pdev = pdev; + + ret = devm_clk_bulk_get_all(&pdev->dev, &dw_dev->clks); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to get clocks: %d\n", ret); + return ret; + } + dw_dev->num_clks = ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + dw_dev->mmio = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dw_dev->mmio)) + return PTR_ERR(dw_dev->mmio); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + platform_set_drvdata(pdev, dw_dev); + + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) { + dev_err(&pdev->dev, "Unable to resume the device: %d\n", ret); + goto err_pm; + } + + pm_runtime_put_sync(&pdev->dev); + + ret = devm_request_irq(&pdev->dev, irq, dw100_irq_handler, IRQF_ONESHOT, + dev_name(&pdev->dev), dw_dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + return ret; + } + + ret = v4l2_device_register(&pdev->dev, &dw_dev->v4l2_dev); + if (ret) + goto err_pm; + + vfd = dw100_init_video_device(dw_dev); + + dw_dev->m2m_dev = v4l2_m2m_init(&dw100_m2m_ops); + if (IS_ERR(dw_dev->m2m_dev)) { + dev_err(&pdev->dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dw_dev->m2m_dev); + goto err_v4l2; + } + + dw_dev->mdev.dev = &pdev->dev; + strscpy(dw_dev->mdev.model, "dw100", sizeof(dw_dev->mdev.model)); + media_device_init(&dw_dev->mdev); + dw_dev->v4l2_dev.mdev = &dw_dev->mdev; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(&pdev->dev, "Failed to register video device\n"); + goto err_m2m; + } + + ret = v4l2_m2m_register_media_controller(dw_dev->m2m_dev, vfd, + MEDIA_ENT_F_PROC_VIDEO_SCALER); + if (ret) { + dev_err(&pdev->dev, "Failed to init mem2mem media controller\n"); + goto error_v4l2; + } + + ret = media_device_register(&dw_dev->mdev); + if (ret) { + dev_err(&pdev->dev, "Failed to register mem2mem media device\n"); + goto error_m2m_mc; + } + + dw100_debugfs_init(dw_dev); + + dev_info(&pdev->dev, + "dw100 v4l2 m2m registered as /dev/video%u\n", vfd->num); + + return 0; + +error_m2m_mc: + v4l2_m2m_unregister_media_controller(dw_dev->m2m_dev); +error_v4l2: + video_unregister_device(vfd); +err_m2m: + media_device_cleanup(&dw_dev->mdev); + v4l2_m2m_release(dw_dev->m2m_dev); +err_v4l2: + v4l2_device_unregister(&dw_dev->v4l2_dev); +err_pm: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int dw100_remove(struct platform_device *pdev) +{ + struct dw100_device *dw_dev = platform_get_drvdata(pdev); + + dw100_debugfs_exit(dw_dev); + + pm_runtime_disable(&pdev->dev); + + media_device_unregister(&dw_dev->mdev); + v4l2_m2m_unregister_media_controller(dw_dev->m2m_dev); + media_device_cleanup(&dw_dev->mdev); + + video_unregister_device(&dw_dev->vfd); + mutex_destroy(dw_dev->vfd.lock); + v4l2_m2m_release(dw_dev->m2m_dev); + v4l2_device_unregister(&dw_dev->v4l2_dev); + + return 0; +} + +static int __maybe_unused dw100_runtime_suspend(struct device *dev) +{ + struct dw100_device *dw_dev = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(dw_dev->num_clks, dw_dev->clks); + + return 0; +} + +static int __maybe_unused dw100_runtime_resume(struct device *dev) +{ + int ret; + struct dw100_device *dw_dev = dev_get_drvdata(dev); + + ret = clk_bulk_prepare_enable(dw_dev->num_clks, dw_dev->clks); + + if (ret) + return ret; + + dw100_hw_reset(dw_dev); + + return 0; +} + +static const struct dev_pm_ops dw100_pm = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(dw100_runtime_suspend, + dw100_runtime_resume, NULL) +}; + +static const struct of_device_id dw100_dt_ids[] = { + { .compatible = "nxp,imx8mp-dw100", .data = NULL }, + { }, +}; +MODULE_DEVICE_TABLE(of, dw100_dt_ids); + +static struct platform_driver dw100_driver = { + .probe = dw100_probe, + .remove = dw100_remove, + .driver = { + .name = DRV_NAME, + .pm = &dw100_pm, + .of_match_table = dw100_dt_ids, + }, +}; + +module_platform_driver(dw100_driver); + +MODULE_DESCRIPTION("DW100 Hardware dewarper"); +MODULE_AUTHOR("Xavier Roumegue "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/nxp/dw100/dw100_regs.h b/drivers/media/platform/nxp/dw100/dw100_regs.h new file mode 100644 index 000000000..e85dfeff9 --- /dev/null +++ b/drivers/media/platform/nxp/dw100/dw100_regs.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * DW100 Hardware dewarper + * + * Copyright 2022 NXP + * Author: Xavier Roumegue (xavier.roumegue@oss.nxp.com) + */ + +#ifndef _DW100_REGS_H_ +#define _DW100_REGS_H_ + +/* AHB register offset */ +#define DW100_DEWARP_ID 0x00 +#define DW100_DEWARP_CTRL 0x04 +#define DW100_DEWARP_CTRL_ENABLE BIT(0) +#define DW100_DEWARP_CTRL_START BIT(1) +#define DW100_DEWARP_CTRL_SOFT_RESET BIT(2) +#define DW100_DEWARP_CTRL_FORMAT_YUV422_SP 0UL +#define DW100_DEWARP_CTRL_FORMAT_YUV422_PACKED 1UL +#define DW100_DEWARP_CTRL_FORMAT_YUV420_SP 2UL +#define DW100_DEWARP_CTRL_INPUT_FORMAT_MASK GENMASK(5, 4) +#define DW100_DEWARP_CTRL_INPUT_FORMAT(x) ((x) << 4) +#define DW100_DEWARP_CTRL_OUTPUT_FORMAT(x) ((x) << 6) +#define DW100_DEWARP_CTRL_OUTPUT_FORMAT_MASK GENMASK(7, 6) +#define DW100_DEWARP_CTRL_SRC_AUTO_SHADOW BIT(8) +#define DW100_DEWARP_CTRL_HW_HANDSHAKE BIT(9) +#define DW100_DEWARP_CTRL_DST_AUTO_SHADOW BIT(10) +#define DW100_DEWARP_CTRL_SPLIT_LINE BIT(11) +#define DW100_DEWARP_CTRL_PREFETCH_MODE_MASK GENMASK(17, 16) +#define DW100_DEWARP_CTRL_PREFETCH_MODE_TRAVERSAL (0UL << 16) +#define DW100_DEWARP_CTRL_PREFETCH_MODE_CALCULATION (1UL << 16) +#define DW100_DEWARP_CTRL_PREFETCH_MODE_AUTO (2UL << 16) +#define DW100_DEWARP_CTRL_PREFETCH_THRESHOLD_MASK GENMASK(24, 18) +#define DW100_DEWARP_CTRL_PREFETCH_THRESHOLD(x) ((x) << 18) + +#define DW100_MAP_LUT_ADDR 0x08 +#define DW100_MAP_LUT_ADDR_ADDR(addr) (((addr) >> 4) & GENMASK(29, 0)) +#define DW100_MAP_LUT_SIZE 0x0c +#define DW100_MAP_LUT_SIZE_WIDTH(w) (((w) & GENMASK(10, 0)) << 0) +#define DW100_MAP_LUT_SIZE_HEIGHT(h) (((h) & GENMASK(10, 0)) << 16) +#define DW100_SRC_IMG_Y_BASE 0x10 +#define DW100_IMG_Y_BASE(base) (((base) >> 4) & GENMASK(29, 0)) +#define DW100_SRC_IMG_UV_BASE 0x14 +#define DW100_IMG_UV_BASE(base) (((base) >> 4) & GENMASK(29, 0)) +#define DW100_SRC_IMG_SIZE 0x18 +#define DW100_IMG_SIZE_WIDTH(w) (((w) & GENMASK(12, 0)) << 0) +#define DW100_IMG_SIZE_HEIGHT(h) (((h) & GENMASK(12, 0)) << 16) + +#define DW100_SRC_IMG_STRIDE 0x1c +#define DW100_MAP_LUT_ADDR2 0x20 +#define DW100_MAP_LUT_SIZE2 0x24 +#define DW100_SRC_IMG_Y_BASE2 0x28 +#define DW100_SRC_IMG_UV_BASE2 0x2c +#define DW100_SRC_IMG_SIZE2 0x30 +#define DW100_SRC_IMG_STRIDE2 0x34 +#define DW100_DST_IMG_Y_BASE 0x38 +#define DW100_DST_IMG_UV_BASE 0x3c +#define DW100_DST_IMG_SIZE 0x40 +#define DW100_DST_IMG_STRIDE 0x44 +#define DW100_DST_IMG_Y_BASE2 0x48 +#define DW100_DST_IMG_UV_BASE2 0x4c +#define DW100_DST_IMG_SIZE2 0x50 +#define DW100_DST_IMG_STRIDE2 0x54 +#define DW100_SWAP_CONTROL 0x58 +#define DW100_SWAP_CONTROL_BYTE BIT(0) +#define DW100_SWAP_CONTROL_SHORT BIT(1) +#define DW100_SWAP_CONTROL_WORD BIT(2) +#define DW100_SWAP_CONTROL_LONG BIT(3) +#define DW100_SWAP_CONTROL_Y(x) (((x) & GENMASK(3, 0)) << 0) +#define DW100_SWAP_CONTROL_UV(x) (((x) & GENMASK(3, 0)) << 4) +#define DW100_SWAP_CONTROL_SRC(x) (((x) & GENMASK(7, 0)) << 0) +#define DW100_SWAP_CONTROL_DST(x) (((x) & GENMASK(7, 0)) << 8) +#define DW100_SWAP_CONTROL_SRC2(x) (((x) & GENMASK(7, 0)) << 16) +#define DW100_SWAP_CONTROL_DST2(x) (((x) & GENMASK(7, 0)) << 24) +#define DW100_SWAP_CONTROL_SRC_MASK GENMASK(7, 0) +#define DW100_SWAP_CONTROL_DST_MASK GENMASK(15, 8) +#define DW100_SWAP_CONTROL_SRC2_MASK GENMASK(23, 16) +#define DW100_SWAP_CONTROL_DST2_MASK GENMASK(31, 24) +#define DW100_VERTICAL_SPLIT_LINE 0x5c +#define DW100_HORIZON_SPLIT_LINE 0x60 +#define DW100_SCALE_FACTOR 0x64 +#define DW100_ROI_START 0x68 +#define DW100_ROI_START_X(x) (((x) & GENMASK(12, 0)) << 0) +#define DW100_ROI_START_Y(y) (((y) & GENMASK(12, 0)) << 16) +#define DW100_BOUNDARY_PIXEL 0x6c +#define DW100_BOUNDARY_PIXEL_V(v) (((v) & GENMASK(7, 0)) << 0) +#define DW100_BOUNDARY_PIXEL_U(u) (((u) & GENMASK(7, 0)) << 8) +#define DW100_BOUNDARY_PIXEL_Y(y) (((y) & GENMASK(7, 0)) << 16) + +#define DW100_INTERRUPT_STATUS 0x70 +#define DW100_INTERRUPT_STATUS_INT_FRAME_DONE BIT(0) +#define DW100_INTERRUPT_STATUS_INT_ERR_TIME_OUT BIT(1) +#define DW100_INTERRUPT_STATUS_INT_ERR_AXI_RESP BIT(2) +#define DW100_INTERRUPT_STATUS_INT_ERR_X BIT(3) +#define DW100_INTERRUPT_STATUS_INT_ERR_MB_FETCH BIT(4) +#define DW100_INTERRUPT_STATUS_INT_ERR_FRAME2 BIT(5) +#define DW100_INTERRUPT_STATUS_INT_ERR_FRAME3 BIT(6) +#define DW100_INTERRUPT_STATUS_INT_ERR_FRAME_DONE BIT(7) +#define DW100_INTERRUPT_STATUS_INT_ERR_STATUS(x) (((x) >> 1) & 0x7f) +#define DW100_INTERRUPT_STATUS_INT_STATUS(x) ((x) & 0xff) + +#define DW100_INTERRUPT_STATUS_INT_ENABLE_MASK GENMASK(15, 8) +#define DW100_INTERRUPT_STATUS_INT_ENABLE(x) (((x) & GENMASK(7, 0)) << 8) +#define DW100_INTERRUPT_STATUS_FRAME_BUSY BIT(16) +#define DW100_INTERRUPT_STATUS_INT_CLEAR(x) (((x) & GENMASK(7, 0)) << 24) +#define DW100_BUS_CTRL 0x74 +#define DW100_BUS_CTRL_AXI_MASTER_ENABLE BIT(31) +#define DW100_BUS_CTRL1 0x78 +#define DW100_BUS_TIME_OUT_CYCLE 0x7c +#define DW100_DST_IMG_Y_SIZE1 0x80 +#define DW100_DST_IMG_Y_SIZE(sz) (((sz) >> 4) & GENMASK(29, 0)) +#define DW100_DST_IMG_UV_SIZE(sz) (((sz) >> 4) & GENMASK(29, 0)) +#define DW100_DST_IMG_UV_SIZE1 0x84 +#define DW100_DST_IMG_Y_SIZE2 0x88 +#define DW100_DST_IMG_UV_SIZE2 0x8c + +#endif /* _DW100_REGS_H_ */ diff --git a/drivers/media/platform/nxp/imx-jpeg/Kconfig b/drivers/media/platform/nxp/imx-jpeg/Kconfig new file mode 100644 index 000000000..5214dcd7f --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +config VIDEO_IMX8_JPEG + tristate "IMX8 JPEG Encoder/Decoder" + depends on V4L_MEM2MEM_DRIVERS + depends on ARCH_MXC || COMPILE_TEST + depends on VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select V4L2_JPEG_HELPER + help + This is a video4linux2 driver for the i.MX8 QXP/QM integrated + JPEG encoder/decoder. diff --git a/drivers/media/platform/nxp/imx-jpeg/Makefile b/drivers/media/platform/nxp/imx-jpeg/Makefile new file mode 100644 index 000000000..bf19c82e6 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +mxc-jpeg-encdec-objs := mxc-jpeg-hw.o mxc-jpeg.o +obj-$(CONFIG_VIDEO_IMX8_JPEG) += mxc-jpeg-encdec.o diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c new file mode 100644 index 000000000..ef28122a5 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#include +#include +#include "mxc-jpeg-hw.h" + +#define print_wrapper_reg(dev, base_address, reg_offset)\ + internal_print_wrapper_reg(dev, (base_address), #reg_offset,\ + (reg_offset)) +#define internal_print_wrapper_reg(dev, base_address, reg_name, reg_offset) {\ + int val;\ + val = readl((base_address) + (reg_offset));\ + dev_dbg(dev, "Wrapper reg %s = 0x%x\n", reg_name, val);\ +} + +void print_descriptor_info(struct device *dev, struct mxc_jpeg_desc *desc) +{ + dev_dbg(dev, " MXC JPEG NEXT_DESCPT_PTR 0x%x\n", + desc->next_descpt_ptr); + dev_dbg(dev, " MXC JPEG BUF_BASE0 0x%x\n", desc->buf_base0); + dev_dbg(dev, " MXC JPEG BUF_BASE1 0x%x\n", desc->buf_base1); + dev_dbg(dev, " MXC JPEG LINE_PITCH %d\n", desc->line_pitch); + dev_dbg(dev, " MXC JPEG STM_BUFBASE 0x%x\n", desc->stm_bufbase); + dev_dbg(dev, " MXC JPEG STM_BUFSIZE %d\n", desc->stm_bufsize); + dev_dbg(dev, " MXC JPEG IMGSIZE %x (%d x %d)\n", desc->imgsize, + desc->imgsize >> 16, desc->imgsize & 0xFFFF); + dev_dbg(dev, " MXC JPEG STM_CTRL 0x%x\n", desc->stm_ctrl); +} + +void print_cast_status(struct device *dev, void __iomem *reg, + unsigned int mode) +{ + dev_dbg(dev, "CAST IP status regs:\n"); + print_wrapper_reg(dev, reg, CAST_STATUS0); + print_wrapper_reg(dev, reg, CAST_STATUS1); + print_wrapper_reg(dev, reg, CAST_STATUS2); + print_wrapper_reg(dev, reg, CAST_STATUS3); + print_wrapper_reg(dev, reg, CAST_STATUS4); + print_wrapper_reg(dev, reg, CAST_STATUS5); + print_wrapper_reg(dev, reg, CAST_STATUS6); + print_wrapper_reg(dev, reg, CAST_STATUS7); + print_wrapper_reg(dev, reg, CAST_STATUS8); + print_wrapper_reg(dev, reg, CAST_STATUS9); + print_wrapper_reg(dev, reg, CAST_STATUS10); + print_wrapper_reg(dev, reg, CAST_STATUS11); + print_wrapper_reg(dev, reg, CAST_STATUS12); + print_wrapper_reg(dev, reg, CAST_STATUS13); + if (mode == MXC_JPEG_DECODE) + return; + print_wrapper_reg(dev, reg, CAST_STATUS14); + print_wrapper_reg(dev, reg, CAST_STATUS15); + print_wrapper_reg(dev, reg, CAST_STATUS16); + print_wrapper_reg(dev, reg, CAST_STATUS17); + print_wrapper_reg(dev, reg, CAST_STATUS18); + print_wrapper_reg(dev, reg, CAST_STATUS19); +} + +void print_wrapper_info(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "Wrapper regs:\n"); + print_wrapper_reg(dev, reg, GLB_CTRL); + print_wrapper_reg(dev, reg, COM_STATUS); + print_wrapper_reg(dev, reg, BUF_BASE0); + print_wrapper_reg(dev, reg, BUF_BASE1); + print_wrapper_reg(dev, reg, LINE_PITCH); + print_wrapper_reg(dev, reg, STM_BUFBASE); + print_wrapper_reg(dev, reg, STM_BUFSIZE); + print_wrapper_reg(dev, reg, IMGSIZE); + print_wrapper_reg(dev, reg, STM_CTRL); +} + +void mxc_jpeg_enable_irq(void __iomem *reg, int slot) +{ + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); + writel(0xF0C, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); +} + +void mxc_jpeg_disable_irq(void __iomem *reg, int slot) +{ + writel(0x0, reg + MXC_SLOT_OFFSET(slot, SLOT_IRQ_EN)); + writel(0xFFFFFFFF, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); +} + +void mxc_jpeg_sw_reset(void __iomem *reg) +{ + /* + * engine soft reset, internal state machine reset + * this will not reset registers, however, it seems + * the registers may remain inconsistent with the internal state + * so, on purpose, at least let GLB_CTRL bits clear after this reset + */ + writel(GLB_CTRL_SFT_RST, reg + GLB_CTRL); +} + +void mxc_jpeg_enc_mode_conf(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "CAST Encoder CONFIG...\n"); + /* + * "Config_Mode" enabled, "Config_Mode auto clear enabled", + */ + writel(0xa0, reg + CAST_MODE); + + /* all markers and segments */ + writel(0x3ff, reg + CAST_CFG_MODE); +} + +void mxc_jpeg_enc_mode_go(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "CAST Encoder GO...\n"); + /* + * "GO" enabled, "GO bit auto clear" enabled + */ + writel(0x140, reg + CAST_MODE); +} + +void mxc_jpeg_enc_set_quality(struct device *dev, void __iomem *reg, u8 quality) +{ + dev_dbg(dev, "CAST Encoder Quality %d...\n", quality); + + /* quality factor */ + writel(quality, reg + CAST_QUALITY); +} + +void mxc_jpeg_dec_mode_go(struct device *dev, void __iomem *reg) +{ + dev_dbg(dev, "CAST Decoder GO...\n"); + writel(MXC_DEC_EXIT_IDLE_MODE, reg + CAST_CTRL); +} + +int mxc_jpeg_enable(void __iomem *reg) +{ + u32 regval; + + writel(GLB_CTRL_JPG_EN, reg + GLB_CTRL); + regval = readl(reg); + return regval; +} + +void mxc_jpeg_enable_slot(void __iomem *reg, int slot) +{ + u32 regval; + + regval = readl(reg + GLB_CTRL); + writel(GLB_CTRL_SLOT_EN(slot) | regval, reg + GLB_CTRL); +} + +void mxc_jpeg_set_l_endian(void __iomem *reg, int le) +{ + u32 regval; + + regval = readl(reg + GLB_CTRL); + regval &= ~GLB_CTRL_L_ENDIAN(1); /* clear */ + writel(GLB_CTRL_L_ENDIAN(le) | regval, reg + GLB_CTRL); /* set */ +} + +void mxc_jpeg_set_bufsize(struct mxc_jpeg_desc *desc, u32 bufsize) +{ + desc->stm_bufsize = bufsize; +} + +void mxc_jpeg_set_res(struct mxc_jpeg_desc *desc, u16 w, u16 h) +{ + desc->imgsize = w << 16 | h; +} + +void mxc_jpeg_set_line_pitch(struct mxc_jpeg_desc *desc, u32 line_pitch) +{ + desc->line_pitch = line_pitch; +} + +void mxc_jpeg_set_desc(u32 desc, void __iomem *reg, int slot) +{ + writel(desc | MXC_NXT_DESCPT_EN, + reg + MXC_SLOT_OFFSET(slot, SLOT_NXT_DESCPT_PTR)); +} diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h new file mode 100644 index 000000000..ecf3b6562 --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg-hw.h @@ -0,0 +1,142 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#ifndef _MXC_JPEG_HW_H +#define _MXC_JPEG_HW_H + +/* JPEG Decoder/Encoder Wrapper Register Map */ +#define GLB_CTRL 0x0 +#define COM_STATUS 0x4 +#define BUF_BASE0 0x14 +#define BUF_BASE1 0x18 +#define LINE_PITCH 0x1C +#define STM_BUFBASE 0x20 +#define STM_BUFSIZE 0x24 +#define IMGSIZE 0x28 +#define STM_CTRL 0x2C + +/* CAST JPEG-Decoder/Encoder Status Register Map (read-only)*/ +#define CAST_STATUS0 0x100 +#define CAST_STATUS1 0x104 +#define CAST_STATUS2 0x108 +#define CAST_STATUS3 0x10c +#define CAST_STATUS4 0x110 +#define CAST_STATUS5 0x114 +#define CAST_STATUS6 0x118 +#define CAST_STATUS7 0x11c +#define CAST_STATUS8 0x120 +#define CAST_STATUS9 0x124 +#define CAST_STATUS10 0x128 +#define CAST_STATUS11 0x12c +#define CAST_STATUS12 0x130 +#define CAST_STATUS13 0x134 +/* the following are for encoder only */ +#define CAST_STATUS14 0x138 +#define CAST_STATUS15 0x13c +#define CAST_STATUS16 0x140 +#define CAST_STATUS17 0x144 +#define CAST_STATUS18 0x148 +#define CAST_STATUS19 0x14c + +/* CAST JPEG-Decoder Control Register Map (write-only) */ +#define CAST_CTRL CAST_STATUS13 + +/* CAST JPEG-Encoder Control Register Map (write-only) */ +#define CAST_MODE CAST_STATUS0 +#define CAST_CFG_MODE CAST_STATUS1 +#define CAST_QUALITY CAST_STATUS2 +#define CAST_RSVD CAST_STATUS3 +#define CAST_REC_REGS_SEL CAST_STATUS4 +#define CAST_LUMTH CAST_STATUS5 +#define CAST_CHRTH CAST_STATUS6 +#define CAST_NOMFRSIZE_LO CAST_STATUS16 +#define CAST_NOMFRSIZE_HI CAST_STATUS17 +#define CAST_OFBSIZE_LO CAST_STATUS18 +#define CAST_OFBSIZE_HI CAST_STATUS19 + +#define MXC_MAX_SLOTS 1 /* TODO use all 4 slots*/ +/* JPEG-Decoder Wrapper Slot Registers 0..3 */ +#define SLOT_BASE 0x10000 +#define SLOT_STATUS 0x0 +#define SLOT_IRQ_EN 0x4 +#define SLOT_BUF_PTR 0x8 +#define SLOT_CUR_DESCPT_PTR 0xC +#define SLOT_NXT_DESCPT_PTR 0x10 +#define MXC_SLOT_OFFSET(slot, offset) ((SLOT_BASE * ((slot) + 1)) + (offset)) + +/* GLB_CTRL fields */ +#define GLB_CTRL_JPG_EN 0x1 +#define GLB_CTRL_SFT_RST (0x1 << 1) +#define GLB_CTRL_DEC_GO (0x1 << 2) +#define GLB_CTRL_L_ENDIAN(le) ((le) << 3) +#define GLB_CTRL_SLOT_EN(slot) (0x1 << ((slot) + 4)) + +/* COM_STAUS fields */ +#define COM_STATUS_DEC_ONGOING(r) (((r) & (1 << 31)) >> 31) +#define COM_STATUS_CUR_SLOT(r) (((r) & (0x3 << 29)) >> 29) + +/* STM_CTRL fields */ +#define STM_CTRL_PIXEL_PRECISION (0x1 << 2) +#define STM_CTRL_IMAGE_FORMAT(img_fmt) ((img_fmt) << 3) +#define STM_CTRL_IMAGE_FORMAT_MASK (0xF << 3) +#define STM_CTRL_BITBUF_PTR_CLR(clr) ((clr) << 7) +#define STM_CTRL_AUTO_START(go) ((go) << 8) +#define STM_CTRL_CONFIG_MOD(mod) ((mod) << 9) + +/* SLOT_STATUS fields for slots 0..3 */ +#define SLOT_STATUS_FRMDONE (0x1 << 3) +#define SLOT_STATUS_ENC_CONFIG_ERR (0x1 << 8) + +/* SLOT_IRQ_EN fields TBD */ + +#define MXC_NXT_DESCPT_EN 0x1 +#define MXC_DEC_EXIT_IDLE_MODE 0x4 + +/* JPEG-Decoder Wrapper - STM_CTRL Register Fields */ +#define MXC_PIXEL_PRECISION(precision) ((precision) / 8 << 2) +enum mxc_jpeg_image_format { + MXC_JPEG_INVALID = -1, + MXC_JPEG_YUV420 = 0x0, /* 2 Plannar, Y=1st plane UV=2nd plane */ + MXC_JPEG_YUV422 = 0x1, /* 1 Plannar, YUYV sequence */ + MXC_JPEG_BGR = 0x2, /* BGR packed format */ + MXC_JPEG_YUV444 = 0x3, /* 1 Plannar, YUVYUV sequence */ + MXC_JPEG_GRAY = 0x4, /* Y8 or Y12 or Single Component */ + MXC_JPEG_RESERVED = 0x5, + MXC_JPEG_ABGR = 0x6, +}; + +#include "mxc-jpeg.h" +void print_descriptor_info(struct device *dev, struct mxc_jpeg_desc *desc); +void print_cast_status(struct device *dev, void __iomem *reg, + unsigned int mode); +void print_wrapper_info(struct device *dev, void __iomem *reg); +void mxc_jpeg_sw_reset(void __iomem *reg); +int mxc_jpeg_enable(void __iomem *reg); +void wait_frmdone(struct device *dev, void __iomem *reg); +void mxc_jpeg_enc_mode_conf(struct device *dev, void __iomem *reg); +void mxc_jpeg_enc_mode_go(struct device *dev, void __iomem *reg); +void mxc_jpeg_enc_set_quality(struct device *dev, void __iomem *reg, u8 quality); +void mxc_jpeg_dec_mode_go(struct device *dev, void __iomem *reg); +int mxc_jpeg_get_slot(void __iomem *reg); +u32 mxc_jpeg_get_offset(void __iomem *reg, int slot); +void mxc_jpeg_enable_slot(void __iomem *reg, int slot); +void mxc_jpeg_set_l_endian(void __iomem *reg, int le); +void mxc_jpeg_enable_irq(void __iomem *reg, int slot); +void mxc_jpeg_disable_irq(void __iomem *reg, int slot); +int mxc_jpeg_set_input(void __iomem *reg, u32 in_buf, u32 bufsize); +int mxc_jpeg_set_output(void __iomem *reg, u16 out_pitch, u32 out_buf, + u16 w, u16 h); +void mxc_jpeg_set_config_mode(void __iomem *reg, int config_mode); +int mxc_jpeg_set_params(struct mxc_jpeg_desc *desc, u32 bufsize, u16 + out_pitch, u32 format); +void mxc_jpeg_set_bufsize(struct mxc_jpeg_desc *desc, u32 bufsize); +void mxc_jpeg_set_res(struct mxc_jpeg_desc *desc, u16 w, u16 h); +void mxc_jpeg_set_line_pitch(struct mxc_jpeg_desc *desc, u32 line_pitch); +void mxc_jpeg_set_desc(u32 desc, void __iomem *reg, int slot); +void mxc_jpeg_set_regs_from_desc(struct mxc_jpeg_desc *desc, + void __iomem *reg); +#endif diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c new file mode 100644 index 000000000..81a44702a --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -0,0 +1,2388 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 driver for the JPEG encoder/decoder from i.MX8QXP/i.MX8QM application + * processors. + * + * The multi-planar buffers API is used. + * + * Baseline and extended sequential jpeg decoding is supported. + * Progressive jpeg decoding is not supported by the IP. + * Supports encode and decode of various formats: + * YUV444, YUV422, YUV420, BGR, ABGR, Gray + * YUV420 is the only multi-planar format supported. + * Minimum resolution is 64 x 64, maximum 8192 x 8192. + * To achieve 8192 x 8192, modify in defconfig: CONFIG_CMA_SIZE_MBYTES=320 + * The alignment requirements for the resolution depend on the format, + * multiple of 16 resolutions should work for all formats. + * Special workarounds are made in the driver to support NV12 1080p. + * When decoding, the driver detects image resolution and pixel format + * from the jpeg stream, by parsing the jpeg markers. + * + * The IP has 4 slots available for context switching, but only slot 0 + * was fully tested to work. Context switching is not used by the driver. + * Each driver instance (context) allocates a slot for itself, but this + * is postponed until device_run, to allow unlimited opens. + * + * The driver submits jobs to the IP by setting up a descriptor for the + * used slot, and then validating it. The encoder has an additional descriptor + * for the configuration phase. The driver expects FRM_DONE interrupt from + * IP to mark the job as finished. + * + * The decoder IP has some limitations regarding the component ID's, + * but the driver works around this by replacing them in the jpeg stream. + * + * A module parameter is available for debug purpose (jpeg_tracing), to enable + * it, enable dynamic debug for this module and: + * echo 1 > /sys/module/mxc_jpeg_encdec/parameters/jpeg_tracing + * + * This is inspired by the drivers/media/platform/samsung/s5p-jpeg driver + * + * Copyright 2018-2019 NXP + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mxc-jpeg-hw.h" +#include "mxc-jpeg.h" + +static const struct mxc_jpeg_fmt mxc_formats[] = { + { + .name = "JPEG", + .fourcc = V4L2_PIX_FMT_JPEG, + .subsampling = -1, + .nc = -1, + .colplanes = 1, + .flags = MXC_JPEG_FMT_TYPE_ENC, + }, + { + .name = "BGR", /*BGR packed format*/ + .fourcc = V4L2_PIX_FMT_BGR24, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 3, + .depth = 24, + .colplanes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "ABGR", /* ABGR packed format */ + .fourcc = V4L2_PIX_FMT_ABGR32, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 4, + .depth = 32, + .colplanes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV420", /* 1st plane = Y, 2nd plane = UV */ + .fourcc = V4L2_PIX_FMT_NV12M, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + .nc = 3, + .depth = 12, /* 6 bytes (4Y + UV) for 4 pixels */ + .colplanes = 2, /* 1 plane Y, 1 plane UV interleaved */ + .h_align = 4, + .v_align = 4, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV422", /* YUYV */ + .fourcc = V4L2_PIX_FMT_YUYV, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + .nc = 3, + .depth = 16, + .colplanes = 1, + .h_align = 4, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "YUV444", /* YUVYUV */ + .fourcc = V4L2_PIX_FMT_YUV24, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + .nc = 3, + .depth = 24, + .colplanes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, + { + .name = "Gray", /* Gray (Y8/Y12) or Single Comp */ + .fourcc = V4L2_PIX_FMT_GREY, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, + .nc = 1, + .depth = 8, + .colplanes = 1, + .h_align = 3, + .v_align = 3, + .flags = MXC_JPEG_FMT_TYPE_RAW, + .precision = 8, + }, +}; + +#define MXC_JPEG_NUM_FORMATS ARRAY_SIZE(mxc_formats) + +static const int mxc_decode_mode = MXC_JPEG_DECODE; +static const int mxc_encode_mode = MXC_JPEG_ENCODE; + +static const struct of_device_id mxc_jpeg_match[] = { + { + .compatible = "nxp,imx8qxp-jpgdec", + .data = &mxc_decode_mode, + }, + { + .compatible = "nxp,imx8qxp-jpgenc", + .data = &mxc_encode_mode, + }, + { }, +}; + +/* + * default configuration stream, 64x64 yuv422 + * split by JPEG marker, so it's easier to modify & use + */ +static const unsigned char jpeg_soi[] = { + 0xFF, 0xD8 +}; + +static const unsigned char jpeg_app0[] = { + 0xFF, 0xE0, + 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, + 0x01, 0x01, 0x00, 0x00, 0x01, 0x00, 0x01, + 0x00, 0x00 +}; + +static const unsigned char jpeg_app14[] = { + 0xFF, 0xEE, + 0x00, 0x0E, 0x41, 0x64, 0x6F, 0x62, 0x65, + 0x00, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static const unsigned char jpeg_dqt[] = { + 0xFF, 0xDB, + 0x00, 0x84, 0x00, 0x10, 0x0B, 0x0C, 0x0E, + 0x0C, 0x0A, 0x10, 0x0E, 0x0D, 0x0E, 0x12, + 0x11, 0x10, 0x13, 0x18, 0x28, 0x1A, 0x18, + 0x16, 0x16, 0x18, 0x31, 0x23, 0x25, 0x1D, + 0x28, 0x3A, 0x33, 0x3D, 0x3C, 0x39, 0x33, + 0x38, 0x37, 0x40, 0x48, 0x5C, 0x4E, 0x40, + 0x44, 0x57, 0x45, 0x37, 0x38, 0x50, 0x6D, + 0x51, 0x57, 0x5F, 0x62, 0x67, 0x68, 0x67, + 0x3E, 0x4D, 0x71, 0x79, 0x70, 0x64, 0x78, + 0x5C, 0x65, 0x67, 0x63, 0x01, 0x11, 0x12, + 0x12, 0x18, 0x15, 0x18, 0x2F, 0x1A, 0x1A, + 0x2F, 0x63, 0x42, 0x38, 0x42, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, + 0x63, 0x63, 0x63, 0x63, 0x63, 0x63 +}; + +static const unsigned char jpeg_sof_maximal[] = { + 0xFF, 0xC0, + 0x00, 0x14, 0x08, 0x00, 0x40, 0x00, 0x40, + 0x04, 0x01, 0x11, 0x00, 0x02, 0x11, 0x01, + 0x03, 0x11, 0x01, 0x04, 0x11, 0x01 +}; + +static const unsigned char jpeg_dht[] = { + 0xFF, 0xC4, + 0x01, 0xA2, 0x00, 0x00, 0x01, 0x05, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x10, 0x00, 0x02, 0x01, + 0x03, 0x03, 0x02, 0x04, 0x03, 0x05, 0x05, + 0x04, 0x04, 0x00, 0x00, 0x01, 0x7D, 0x01, + 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, + 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, + 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, + 0x52, 0xD1, 0xF0, 0x24, 0x33, 0x62, 0x72, + 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, + 0x1A, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, + 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, + 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, + 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, + 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, + 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, + 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, + 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, + 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, + 0x01, 0x00, 0x03, 0x01, 0x01, 0x01, 0x01, + 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, + 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, + 0x0B, 0x11, 0x00, 0x02, 0x01, 0x02, 0x04, + 0x04, 0x03, 0x04, 0x07, 0x05, 0x04, 0x04, + 0x00, 0x01, 0x02, 0x77, 0x00, 0x01, 0x02, + 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, + 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 0x13, + 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, + 0xF0, 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, + 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, + 0x19, 0x1A, 0x26, 0x27, 0x28, 0x29, 0x2A, + 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, + 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, + 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, + 0x86, 0x87, 0x88, 0x89, 0x8A, 0x92, 0x93, + 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, + 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, + 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, + 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, + 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, + 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, + 0xF6, 0xF7, 0xF8, 0xF9, 0xFA +}; + +static const unsigned char jpeg_dri[] = { + 0xFF, 0xDD, + 0x00, 0x04, 0x00, 0x20 +}; + +static const unsigned char jpeg_sos_maximal[] = { + 0xFF, 0xDA, + 0x00, 0x0C, 0x04, 0x01, 0x00, 0x02, 0x11, 0x03, + 0x11, 0x04, 0x11, 0x00, 0x3F, 0x00 +}; + +static const unsigned char jpeg_image_red[] = { + 0xFC, 0x5F, 0xA2, 0xBF, 0xCA, 0x73, 0xFE, 0xFE, + 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, + 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, + 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, + 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, + 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, + 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, + 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, + 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00, 0x28, + 0xA0, 0x02, 0x8A, 0x00, 0x28, 0xA0, 0x02, 0x8A, + 0x00, 0x28, 0xA0, 0x02, 0x8A, 0x00 +}; + +static const unsigned char jpeg_eoi[] = { + 0xFF, 0xD9 +}; + +struct mxc_jpeg_src_buf { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer b; + struct list_head list; + + /* mxc-jpeg specific */ + bool dht_needed; + bool jpeg_parse_error; + const struct mxc_jpeg_fmt *fmt; + int w; + int h; +}; + +static inline struct mxc_jpeg_src_buf *vb2_to_mxc_buf(struct vb2_buffer *vb) +{ + return container_of(to_vb2_v4l2_buffer(vb), + struct mxc_jpeg_src_buf, b); +} + +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-3)"); + +static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, u32 precision); +static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q); + +static void _bswap16(u16 *a) +{ + *a = ((*a & 0x00FF) << 8) | ((*a & 0xFF00) >> 8); +} + +static void print_mxc_buf(struct mxc_jpeg_dev *jpeg, struct vb2_buffer *buf, + unsigned long len) +{ + unsigned int plane_no; + u32 dma_addr; + void *vaddr; + unsigned long payload; + + if (debug < 3) + return; + + for (plane_no = 0; plane_no < buf->num_planes; plane_no++) { + payload = vb2_get_plane_payload(buf, plane_no); + if (len == 0) + len = payload; + dma_addr = vb2_dma_contig_plane_dma_addr(buf, plane_no); + vaddr = vb2_plane_vaddr(buf, plane_no); + v4l2_dbg(3, debug, &jpeg->v4l2_dev, + "plane %d (vaddr=%p dma_addr=%x payload=%ld):", + plane_no, vaddr, dma_addr, payload); + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 32, 1, + vaddr, len, false); + } +} + +static inline struct mxc_jpeg_ctx *mxc_jpeg_fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct mxc_jpeg_ctx, fh); +} + +static int enum_fmt(const struct mxc_jpeg_fmt *mxc_formats, int n, + struct v4l2_fmtdesc *f, u32 type) +{ + int i, num = 0; + + for (i = 0; i < n; ++i) { + if (mxc_formats[i].flags == type) { + /* index-th format of searched type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index + */ + ++num; + } + } + + /* Format not found */ + if (i >= n) + return -EINVAL; + + f->pixelformat = mxc_formats[i].fourcc; + + return 0; +} + +static const struct mxc_jpeg_fmt *mxc_jpeg_find_format(struct mxc_jpeg_ctx *ctx, + u32 pixelformat) +{ + unsigned int k; + + for (k = 0; k < MXC_JPEG_NUM_FORMATS; k++) { + const struct mxc_jpeg_fmt *fmt = &mxc_formats[k]; + + if (fmt->fourcc == pixelformat) + return fmt; + } + return NULL; +} + +static enum mxc_jpeg_image_format mxc_jpeg_fourcc_to_imgfmt(u32 fourcc) +{ + switch (fourcc) { + case V4L2_PIX_FMT_GREY: + return MXC_JPEG_GRAY; + case V4L2_PIX_FMT_YUYV: + return MXC_JPEG_YUV422; + case V4L2_PIX_FMT_NV12M: + return MXC_JPEG_YUV420; + case V4L2_PIX_FMT_YUV24: + return MXC_JPEG_YUV444; + case V4L2_PIX_FMT_BGR24: + return MXC_JPEG_BGR; + case V4L2_PIX_FMT_ABGR32: + return MXC_JPEG_ABGR; + default: + return MXC_JPEG_INVALID; + } +} + +static struct mxc_jpeg_q_data *mxc_jpeg_get_q_data(struct mxc_jpeg_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->out_q; + return &ctx->cap_q; +} + +static void mxc_jpeg_addrs(struct mxc_jpeg_desc *desc, + struct vb2_buffer *raw_buf, + struct vb2_buffer *jpeg_buf, int offset) +{ + int img_fmt = desc->stm_ctrl & STM_CTRL_IMAGE_FORMAT_MASK; + + desc->buf_base0 = vb2_dma_contig_plane_dma_addr(raw_buf, 0); + desc->buf_base1 = 0; + if (img_fmt == STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV420)) { + WARN_ON(raw_buf->num_planes < 2); + desc->buf_base1 = vb2_dma_contig_plane_dma_addr(raw_buf, 1); + } + desc->stm_bufbase = vb2_dma_contig_plane_dma_addr(jpeg_buf, 0) + + offset; +} + +static void notify_eos(struct mxc_jpeg_ctx *ctx) +{ + const struct v4l2_event ev = { + .type = V4L2_EVENT_EOS + }; + + dev_dbg(ctx->mxc_jpeg->dev, "Notify app event EOS reached"); + v4l2_event_queue_fh(&ctx->fh, &ev); +} + +static void notify_src_chg(struct mxc_jpeg_ctx *ctx) +{ + const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = + V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + dev_dbg(ctx->mxc_jpeg->dev, "Notify app event SRC_CH_RESOLUTION"); + v4l2_event_queue_fh(&ctx->fh, &ev); +} + +static int mxc_get_free_slot(struct mxc_jpeg_slot_data slot_data[], int n) +{ + int free_slot = 0; + + while (slot_data[free_slot].used && free_slot < n) + free_slot++; + + return free_slot; /* >=n when there are no more free slots */ +} + +static bool mxc_jpeg_alloc_slot_data(struct mxc_jpeg_dev *jpeg, + unsigned int slot) +{ + struct mxc_jpeg_desc *desc; + struct mxc_jpeg_desc *cfg_desc; + void *cfg_stm; + + if (jpeg->slot_data[slot].desc) + goto skip_alloc; /* already allocated, reuse it */ + + /* allocate descriptor for decoding/encoding phase */ + desc = dma_alloc_coherent(jpeg->dev, + sizeof(struct mxc_jpeg_desc), + &jpeg->slot_data[slot].desc_handle, + GFP_ATOMIC); + if (!desc) + goto err; + jpeg->slot_data[slot].desc = desc; + + /* allocate descriptor for configuration phase (encoder only) */ + cfg_desc = dma_alloc_coherent(jpeg->dev, + sizeof(struct mxc_jpeg_desc), + &jpeg->slot_data[slot].cfg_desc_handle, + GFP_ATOMIC); + if (!cfg_desc) + goto err; + jpeg->slot_data[slot].cfg_desc = cfg_desc; + + /* allocate configuration stream */ + cfg_stm = dma_alloc_coherent(jpeg->dev, + MXC_JPEG_MAX_CFG_STREAM, + &jpeg->slot_data[slot].cfg_stream_handle, + GFP_ATOMIC); + if (!cfg_stm) + goto err; + memset(cfg_stm, 0, MXC_JPEG_MAX_CFG_STREAM); + jpeg->slot_data[slot].cfg_stream_vaddr = cfg_stm; + +skip_alloc: + jpeg->slot_data[slot].used = true; + + return true; +err: + dev_err(jpeg->dev, "Could not allocate descriptors for slot %d", slot); + + return false; +} + +static void mxc_jpeg_free_slot_data(struct mxc_jpeg_dev *jpeg, + unsigned int slot) +{ + if (slot >= MXC_MAX_SLOTS) { + dev_err(jpeg->dev, "Invalid slot %d, nothing to free.", slot); + return; + } + + /* free descriptor for decoding/encoding phase */ + dma_free_coherent(jpeg->dev, sizeof(struct mxc_jpeg_desc), + jpeg->slot_data[slot].desc, + jpeg->slot_data[slot].desc_handle); + + /* free descriptor for encoder configuration phase / decoder DHT */ + dma_free_coherent(jpeg->dev, sizeof(struct mxc_jpeg_desc), + jpeg->slot_data[slot].cfg_desc, + jpeg->slot_data[slot].cfg_desc_handle); + + /* free configuration stream */ + dma_free_coherent(jpeg->dev, MXC_JPEG_MAX_CFG_STREAM, + jpeg->slot_data[slot].cfg_stream_vaddr, + jpeg->slot_data[slot].cfg_stream_handle); + + jpeg->slot_data[slot].used = false; +} + +static void mxc_jpeg_check_and_set_last_buffer(struct mxc_jpeg_ctx *ctx, + struct vb2_v4l2_buffer *src_buf, + struct vb2_v4l2_buffer *dst_buf) +{ + if (v4l2_m2m_is_last_draining_src_buf(ctx->fh.m2m_ctx, src_buf)) { + dst_buf->flags |= V4L2_BUF_FLAG_LAST; + v4l2_m2m_mark_stopped(ctx->fh.m2m_ctx); + notify_eos(ctx); + ctx->header_parsed = false; + } +} + +static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) +{ + struct mxc_jpeg_dev *jpeg = priv; + struct mxc_jpeg_ctx *ctx; + void __iomem *reg = jpeg->base_reg; + struct device *dev = jpeg->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct mxc_jpeg_src_buf *jpeg_src_buf; + enum vb2_buffer_state buf_state; + u32 dec_ret, com_status; + unsigned long payload; + struct mxc_jpeg_q_data *q_data; + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + unsigned int slot; + + spin_lock(&jpeg->hw_lock); + + com_status = readl(reg + COM_STATUS); + slot = COM_STATUS_CUR_SLOT(com_status); + dev_dbg(dev, "Irq %d on slot %d.\n", irq, slot); + + ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + if (WARN_ON(!ctx)) + goto job_unlock; + + if (slot != ctx->slot) { + /* TODO investigate when adding multi-instance support */ + dev_warn(dev, "IRQ slot %d != context slot %d.\n", + slot, ctx->slot); + goto job_unlock; + } + + dec_ret = readl(reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); + writel(dec_ret, reg + MXC_SLOT_OFFSET(slot, SLOT_STATUS)); /* w1c */ + + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + if (!dst_buf || !src_buf) { + dev_err(dev, "No source or destination buffer.\n"); + goto job_unlock; + } + jpeg_src_buf = vb2_to_mxc_buf(&src_buf->vb2_buf); + + if (dec_ret & SLOT_STATUS_ENC_CONFIG_ERR) { + u32 ret = readl(reg + CAST_STATUS12); + + dev_err(dev, "Encoder/decoder error, status=0x%08x", ret); + mxc_jpeg_sw_reset(reg); + buf_state = VB2_BUF_STATE_ERROR; + goto buffers_done; + } + + if (!(dec_ret & SLOT_STATUS_FRMDONE)) + goto job_unlock; + + if (jpeg->mode == MXC_JPEG_ENCODE && + ctx->enc_state == MXC_JPEG_ENC_CONF) { + ctx->enc_state = MXC_JPEG_ENCODING; + dev_dbg(dev, "Encoder config finished. Start encoding...\n"); + mxc_jpeg_enc_set_quality(dev, reg, ctx->jpeg_quality); + mxc_jpeg_enc_mode_go(dev, reg); + goto job_unlock; + } + if (jpeg->mode == MXC_JPEG_DECODE && jpeg_src_buf->dht_needed) { + jpeg_src_buf->dht_needed = false; + dev_dbg(dev, "Decoder DHT cfg finished. Start decoding...\n"); + goto job_unlock; + } + + if (jpeg->mode == MXC_JPEG_ENCODE) { + payload = readl(reg + MXC_SLOT_OFFSET(slot, SLOT_BUF_PTR)); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload); + dev_dbg(dev, "Encoding finished, payload size: %ld\n", + payload); + } else { + q_data = mxc_jpeg_get_q_data(ctx, cap_type); + payload = q_data->sizeimage[0]; + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload); + vb2_set_plane_payload(&dst_buf->vb2_buf, 1, 0); + if (q_data->fmt->colplanes == 2) { + payload = q_data->sizeimage[1]; + vb2_set_plane_payload(&dst_buf->vb2_buf, 1, payload); + } + dev_dbg(dev, "Decoding finished, payload size: %ld + %ld\n", + vb2_get_plane_payload(&dst_buf->vb2_buf, 0), + vb2_get_plane_payload(&dst_buf->vb2_buf, 1)); + } + + /* short preview of the results */ + dev_dbg(dev, "src_buf preview: "); + print_mxc_buf(jpeg, &src_buf->vb2_buf, 32); + dev_dbg(dev, "dst_buf preview: "); + print_mxc_buf(jpeg, &dst_buf->vb2_buf, 32); + buf_state = VB2_BUF_STATE_DONE; + +buffers_done: + mxc_jpeg_disable_irq(reg, ctx->slot); + jpeg->slot_data[slot].used = false; /* unused, but don't free */ + mxc_jpeg_check_and_set_last_buffer(ctx, src_buf, dst_buf); + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, buf_state); + v4l2_m2m_buf_done(dst_buf, buf_state); + spin_unlock(&jpeg->hw_lock); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return IRQ_HANDLED; +job_unlock: + spin_unlock(&jpeg->hw_lock); + return IRQ_HANDLED; +} + +static int mxc_jpeg_fixup_sof(struct mxc_jpeg_sof *sof, + u32 fourcc, + u16 w, u16 h) +{ + int sof_length; + + sof->precision = 8; /* TODO allow 8/12 bit precision*/ + sof->height = h; + _bswap16(&sof->height); + sof->width = w; + _bswap16(&sof->width); + + switch (fourcc) { + case V4L2_PIX_FMT_NV12M: + sof->components_no = 3; + sof->comp[0].v = 0x2; + sof->comp[0].h = 0x2; + break; + case V4L2_PIX_FMT_YUYV: + sof->components_no = 3; + sof->comp[0].v = 0x1; + sof->comp[0].h = 0x2; + break; + case V4L2_PIX_FMT_YUV24: + case V4L2_PIX_FMT_BGR24: + default: + sof->components_no = 3; + break; + case V4L2_PIX_FMT_ABGR32: + sof->components_no = 4; + break; + case V4L2_PIX_FMT_GREY: + sof->components_no = 1; + break; + } + sof_length = 8 + 3 * sof->components_no; + sof->length = sof_length; + _bswap16(&sof->length); + + return sof_length; /* not swaped */ +} + +static int mxc_jpeg_fixup_sos(struct mxc_jpeg_sos *sos, + u32 fourcc) +{ + int sos_length; + u8 *sof_u8 = (u8 *)sos; + + switch (fourcc) { + case V4L2_PIX_FMT_NV12M: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_YUYV: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_YUV24: + case V4L2_PIX_FMT_BGR24: + default: + sos->components_no = 3; + break; + case V4L2_PIX_FMT_ABGR32: + sos->components_no = 4; + break; + case V4L2_PIX_FMT_GREY: + sos->components_no = 1; + break; + } + sos_length = 6 + 2 * sos->components_no; + sos->length = sos_length; + _bswap16(&sos->length); + + /* SOS ignorable bytes, not so ignorable after all */ + sof_u8[sos_length - 1] = 0x0; + sof_u8[sos_length - 2] = 0x3f; + sof_u8[sos_length - 3] = 0x0; + + return sos_length; /* not swaped */ +} + +static unsigned int mxc_jpeg_setup_cfg_stream(void *cfg_stream_vaddr, + u32 fourcc, + u16 w, u16 h) +{ + /* + * There is a hardware issue that first 128 bytes of configuration data + * can't be loaded correctly. + * To avoid this issue, we need to write the configuration from + * an offset which should be no less than 0x80 (128 bytes). + */ + unsigned int offset = 0x80; + u8 *cfg = (u8 *)cfg_stream_vaddr; + struct mxc_jpeg_sof *sof; + struct mxc_jpeg_sos *sos; + + memcpy(cfg + offset, jpeg_soi, ARRAY_SIZE(jpeg_soi)); + offset += ARRAY_SIZE(jpeg_soi); + + if (fourcc == V4L2_PIX_FMT_BGR24 || + fourcc == V4L2_PIX_FMT_ABGR32) { + memcpy(cfg + offset, jpeg_app14, sizeof(jpeg_app14)); + offset += sizeof(jpeg_app14); + } else { + memcpy(cfg + offset, jpeg_app0, sizeof(jpeg_app0)); + offset += sizeof(jpeg_app0); + } + + memcpy(cfg + offset, jpeg_dqt, sizeof(jpeg_dqt)); + offset += sizeof(jpeg_dqt); + + memcpy(cfg + offset, jpeg_sof_maximal, sizeof(jpeg_sof_maximal)); + offset += 2; /* skip marker ID */ + sof = (struct mxc_jpeg_sof *)(cfg + offset); + offset += mxc_jpeg_fixup_sof(sof, fourcc, w, h); + + memcpy(cfg + offset, jpeg_dht, sizeof(jpeg_dht)); + offset += sizeof(jpeg_dht); + + memcpy(cfg + offset, jpeg_dri, sizeof(jpeg_dri)); + offset += sizeof(jpeg_dri); + + memcpy(cfg + offset, jpeg_sos_maximal, sizeof(jpeg_sos_maximal)); + offset += 2; /* skip marker ID */ + sos = (struct mxc_jpeg_sos *)(cfg + offset); + offset += mxc_jpeg_fixup_sos(sos, fourcc); + + memcpy(cfg + offset, jpeg_image_red, sizeof(jpeg_image_red)); + offset += sizeof(jpeg_image_red); + + memcpy(cfg + offset, jpeg_eoi, sizeof(jpeg_eoi)); + offset += sizeof(jpeg_eoi); + + return offset; +} + +static void mxc_jpeg_config_dec_desc(struct vb2_buffer *out_buf, + struct mxc_jpeg_ctx *ctx, + struct vb2_buffer *src_buf, + struct vb2_buffer *dst_buf) +{ + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + struct mxc_jpeg_q_data *q_data_cap; + enum mxc_jpeg_image_format img_fmt; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + unsigned int slot = ctx->slot; + struct mxc_jpeg_desc *desc = jpeg->slot_data[slot].desc; + struct mxc_jpeg_desc *cfg_desc = jpeg->slot_data[slot].cfg_desc; + dma_addr_t desc_handle = jpeg->slot_data[slot].desc_handle; + dma_addr_t cfg_desc_handle = jpeg->slot_data[slot].cfg_desc_handle; + dma_addr_t cfg_stream_handle = jpeg->slot_data[slot].cfg_stream_handle; + unsigned int *cfg_size = &jpeg->slot_data[slot].cfg_stream_size; + void *cfg_stream_vaddr = jpeg->slot_data[slot].cfg_stream_vaddr; + struct mxc_jpeg_src_buf *jpeg_src_buf; + + jpeg_src_buf = vb2_to_mxc_buf(src_buf); + + /* setup the decoding descriptor */ + desc->next_descpt_ptr = 0; /* end of chain */ + q_data_cap = mxc_jpeg_get_q_data(ctx, cap_type); + desc->imgsize = q_data_cap->w_adjusted << 16 | q_data_cap->h_adjusted; + img_fmt = mxc_jpeg_fourcc_to_imgfmt(q_data_cap->fmt->fourcc); + desc->stm_ctrl &= ~STM_CTRL_IMAGE_FORMAT(0xF); /* clear image format */ + desc->stm_ctrl |= STM_CTRL_IMAGE_FORMAT(img_fmt); + desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + desc->line_pitch = q_data_cap->bytesperline[0]; + mxc_jpeg_addrs(desc, dst_buf, src_buf, 0); + mxc_jpeg_set_bufsize(desc, ALIGN(vb2_plane_size(src_buf, 0), 1024)); + print_descriptor_info(jpeg->dev, desc); + + if (!jpeg_src_buf->dht_needed) { + /* validate the decoding descriptor */ + mxc_jpeg_set_desc(desc_handle, reg, slot); + return; + } + + /* + * if a default huffman table is needed, use the config descriptor to + * inject a DHT, by chaining it before the decoding descriptor + */ + *cfg_size = mxc_jpeg_setup_cfg_stream(cfg_stream_vaddr, + V4L2_PIX_FMT_YUYV, + MXC_JPEG_MIN_WIDTH, + MXC_JPEG_MIN_HEIGHT); + cfg_desc->next_descpt_ptr = desc_handle | MXC_NXT_DESCPT_EN; + cfg_desc->buf_base0 = vb2_dma_contig_plane_dma_addr(dst_buf, 0); + cfg_desc->buf_base1 = 0; + cfg_desc->imgsize = MXC_JPEG_MIN_WIDTH << 16; + cfg_desc->imgsize |= MXC_JPEG_MIN_HEIGHT; + cfg_desc->line_pitch = MXC_JPEG_MIN_WIDTH * 2; + cfg_desc->stm_ctrl = STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV422); + cfg_desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + cfg_desc->stm_bufbase = cfg_stream_handle; + cfg_desc->stm_bufsize = ALIGN(*cfg_size, 1024); + print_descriptor_info(jpeg->dev, cfg_desc); + + /* validate the configuration descriptor */ + mxc_jpeg_set_desc(cfg_desc_handle, reg, slot); +} + +static void mxc_jpeg_config_enc_desc(struct vb2_buffer *out_buf, + struct mxc_jpeg_ctx *ctx, + struct vb2_buffer *src_buf, + struct vb2_buffer *dst_buf) +{ + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + unsigned int slot = ctx->slot; + struct mxc_jpeg_desc *desc = jpeg->slot_data[slot].desc; + struct mxc_jpeg_desc *cfg_desc = jpeg->slot_data[slot].cfg_desc; + dma_addr_t desc_handle = jpeg->slot_data[slot].desc_handle; + dma_addr_t cfg_desc_handle = jpeg->slot_data[slot].cfg_desc_handle; + void *cfg_stream_vaddr = jpeg->slot_data[slot].cfg_stream_vaddr; + struct mxc_jpeg_q_data *q_data; + enum mxc_jpeg_image_format img_fmt; + int w, h; + + q_data = mxc_jpeg_get_q_data(ctx, src_buf->vb2_queue->type); + + jpeg->slot_data[slot].cfg_stream_size = + mxc_jpeg_setup_cfg_stream(cfg_stream_vaddr, + q_data->fmt->fourcc, + q_data->w, + q_data->h); + + /* chain the config descriptor with the encoding descriptor */ + cfg_desc->next_descpt_ptr = desc_handle | MXC_NXT_DESCPT_EN; + + cfg_desc->buf_base0 = jpeg->slot_data[slot].cfg_stream_handle; + cfg_desc->buf_base1 = 0; + cfg_desc->line_pitch = 0; + cfg_desc->stm_bufbase = 0; /* no output expected */ + cfg_desc->stm_bufsize = 0x0; + cfg_desc->imgsize = 0; + cfg_desc->stm_ctrl = STM_CTRL_CONFIG_MOD(1); + cfg_desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + + desc->next_descpt_ptr = 0; /* end of chain */ + + /* use adjusted resolution for CAST IP job */ + w = q_data->w_adjusted; + h = q_data->h_adjusted; + mxc_jpeg_set_res(desc, w, h); + mxc_jpeg_set_line_pitch(desc, w * (q_data->fmt->depth / 8)); + mxc_jpeg_set_bufsize(desc, desc->line_pitch * h); + img_fmt = mxc_jpeg_fourcc_to_imgfmt(q_data->fmt->fourcc); + if (img_fmt == MXC_JPEG_INVALID) + dev_err(jpeg->dev, "No valid image format detected\n"); + desc->stm_ctrl = STM_CTRL_CONFIG_MOD(0) | + STM_CTRL_IMAGE_FORMAT(img_fmt); + desc->stm_ctrl |= STM_CTRL_BITBUF_PTR_CLR(1); + mxc_jpeg_addrs(desc, src_buf, dst_buf, 0); + dev_dbg(jpeg->dev, "cfg_desc:\n"); + print_descriptor_info(jpeg->dev, cfg_desc); + dev_dbg(jpeg->dev, "enc desc:\n"); + print_descriptor_info(jpeg->dev, desc); + print_wrapper_info(jpeg->dev, reg); + print_cast_status(jpeg->dev, reg, MXC_JPEG_ENCODE); + + /* validate the configuration descriptor */ + mxc_jpeg_set_desc(cfg_desc_handle, reg, slot); +} + +static bool mxc_jpeg_source_change(struct mxc_jpeg_ctx *ctx, + struct mxc_jpeg_src_buf *jpeg_src_buf) +{ + struct device *dev = ctx->mxc_jpeg->dev; + struct mxc_jpeg_q_data *q_data_cap; + + if (!jpeg_src_buf->fmt) + return false; + + q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (q_data_cap->fmt != jpeg_src_buf->fmt || + q_data_cap->w != jpeg_src_buf->w || + q_data_cap->h != jpeg_src_buf->h) { + dev_dbg(dev, "Detected jpeg res=(%dx%d)->(%dx%d), pixfmt=%c%c%c%c\n", + q_data_cap->w, q_data_cap->h, + jpeg_src_buf->w, jpeg_src_buf->h, + (jpeg_src_buf->fmt->fourcc & 0xff), + (jpeg_src_buf->fmt->fourcc >> 8) & 0xff, + (jpeg_src_buf->fmt->fourcc >> 16) & 0xff, + (jpeg_src_buf->fmt->fourcc >> 24) & 0xff); + + /* + * set-up the capture queue with the pixelformat and resolution + * detected from the jpeg output stream + */ + q_data_cap->w = jpeg_src_buf->w; + q_data_cap->h = jpeg_src_buf->h; + q_data_cap->fmt = jpeg_src_buf->fmt; + q_data_cap->w_adjusted = q_data_cap->w; + q_data_cap->h_adjusted = q_data_cap->h; + + /* + * align up the resolution for CAST IP, + * but leave the buffer resolution unchanged + */ + v4l_bound_align_image(&q_data_cap->w_adjusted, + q_data_cap->w_adjusted, /* adjust up */ + MXC_JPEG_MAX_WIDTH, + q_data_cap->fmt->h_align, + &q_data_cap->h_adjusted, + q_data_cap->h_adjusted, /* adjust up */ + MXC_JPEG_MAX_HEIGHT, + 0, + 0); + + /* setup bytesperline/sizeimage for capture queue */ + mxc_jpeg_bytesperline(q_data_cap, jpeg_src_buf->fmt->precision); + mxc_jpeg_sizeimage(q_data_cap); + notify_src_chg(ctx); + ctx->source_change = 1; + } + return ctx->source_change ? true : false; +} + +static int mxc_jpeg_job_ready(void *priv) +{ + struct mxc_jpeg_ctx *ctx = priv; + + return ctx->source_change ? 0 : 1; +} + +static void mxc_jpeg_device_run(void *priv) +{ + struct mxc_jpeg_ctx *ctx = priv; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + void __iomem *reg = jpeg->base_reg; + struct device *dev = jpeg->dev; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned long flags; + struct mxc_jpeg_q_data *q_data_cap, *q_data_out; + struct mxc_jpeg_src_buf *jpeg_src_buf; + + spin_lock_irqsave(&ctx->mxc_jpeg->hw_lock, flags); + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!src_buf || !dst_buf) { + dev_err(dev, "Null src or dst buf\n"); + goto end; + } + + q_data_cap = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (!q_data_cap) + goto end; + q_data_out = mxc_jpeg_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (!q_data_out) + goto end; + src_buf->sequence = q_data_out->sequence++; + dst_buf->sequence = q_data_cap->sequence++; + + v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true); + + jpeg_src_buf = vb2_to_mxc_buf(&src_buf->vb2_buf); + if (q_data_cap->fmt->colplanes != dst_buf->vb2_buf.num_planes) { + dev_err(dev, "Capture format %s has %d planes, but capture buffer has %d planes\n", + q_data_cap->fmt->name, q_data_cap->fmt->colplanes, + dst_buf->vb2_buf.num_planes); + jpeg_src_buf->jpeg_parse_error = true; + } + if (jpeg_src_buf->jpeg_parse_error) { + mxc_jpeg_check_and_set_last_buffer(ctx, src_buf, dst_buf); + v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + + return; + } + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) { + if (ctx->source_change || mxc_jpeg_source_change(ctx, jpeg_src_buf)) { + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); + v4l2_m2m_job_finish(jpeg->m2m_dev, ctx->fh.m2m_ctx); + return; + } + } + + mxc_jpeg_enable(reg); + mxc_jpeg_set_l_endian(reg, 1); + + ctx->slot = mxc_get_free_slot(jpeg->slot_data, MXC_MAX_SLOTS); + if (ctx->slot >= MXC_MAX_SLOTS) { + dev_err(dev, "No more free slots\n"); + goto end; + } + if (!mxc_jpeg_alloc_slot_data(jpeg, ctx->slot)) { + dev_err(dev, "Cannot allocate slot data\n"); + goto end; + } + + mxc_jpeg_enable_slot(reg, ctx->slot); + mxc_jpeg_enable_irq(reg, ctx->slot); + + if (jpeg->mode == MXC_JPEG_ENCODE) { + dev_dbg(dev, "Encoding on slot %d\n", ctx->slot); + ctx->enc_state = MXC_JPEG_ENC_CONF; + mxc_jpeg_config_enc_desc(&dst_buf->vb2_buf, ctx, + &src_buf->vb2_buf, &dst_buf->vb2_buf); + mxc_jpeg_enc_mode_conf(dev, reg); /* start config phase */ + } else { + dev_dbg(dev, "Decoding on slot %d\n", ctx->slot); + print_mxc_buf(jpeg, &src_buf->vb2_buf, 0); + mxc_jpeg_config_dec_desc(&dst_buf->vb2_buf, ctx, + &src_buf->vb2_buf, &dst_buf->vb2_buf); + mxc_jpeg_dec_mode_go(dev, reg); + } +end: + spin_unlock_irqrestore(&ctx->mxc_jpeg->hw_lock, flags); +} + +static int mxc_jpeg_decoder_cmd(struct file *file, void *priv, + struct v4l2_decoder_cmd *cmd) +{ + struct v4l2_fh *fh = file->private_data; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + int ret; + + ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, cmd); + if (ret < 0) + return ret; + + if (!vb2_is_streaming(v4l2_m2m_get_src_vq(fh->m2m_ctx))) + return 0; + + ret = v4l2_m2m_ioctl_decoder_cmd(file, priv, cmd); + if (ret < 0) + return ret; + + if (cmd->cmd == V4L2_DEC_CMD_STOP && + v4l2_m2m_has_stopped(fh->m2m_ctx)) { + notify_eos(ctx); + ctx->header_parsed = false; + } + + if (cmd->cmd == V4L2_DEC_CMD_START && + v4l2_m2m_has_stopped(fh->m2m_ctx)) + vb2_clear_last_buffer_dequeued(&fh->m2m_ctx->cap_q_ctx.q); + return 0; +} + +static int mxc_jpeg_encoder_cmd(struct file *file, void *priv, + struct v4l2_encoder_cmd *cmd) +{ + struct v4l2_fh *fh = file->private_data; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(fh); + int ret; + + ret = v4l2_m2m_ioctl_try_encoder_cmd(file, fh, cmd); + if (ret < 0) + return ret; + + if (!vb2_is_streaming(v4l2_m2m_get_src_vq(fh->m2m_ctx)) || + !vb2_is_streaming(v4l2_m2m_get_dst_vq(fh->m2m_ctx))) + return 0; + + ret = v4l2_m2m_ioctl_encoder_cmd(file, fh, cmd); + if (ret < 0) + return 0; + + if (cmd->cmd == V4L2_ENC_CMD_STOP && + v4l2_m2m_has_stopped(fh->m2m_ctx)) + notify_eos(ctx); + + if (cmd->cmd == V4L2_ENC_CMD_START && + v4l2_m2m_has_stopped(fh->m2m_ctx)) + vb2_clear_last_buffer_dequeued(&fh->m2m_ctx->cap_q_ctx.q); + + return 0; +} + +static int mxc_jpeg_queue_setup(struct vb2_queue *q, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_ctxs[]) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct mxc_jpeg_q_data *q_data = NULL; + struct mxc_jpeg_q_data tmp_q; + int i; + + q_data = mxc_jpeg_get_q_data(ctx, q->type); + if (!q_data) + return -EINVAL; + + tmp_q.fmt = q_data->fmt; + tmp_q.w = q_data->w_adjusted; + tmp_q.h = q_data->h_adjusted; + for (i = 0; i < MXC_JPEG_MAX_PLANES; i++) { + tmp_q.bytesperline[i] = q_data->bytesperline[i]; + tmp_q.sizeimage[i] = q_data->sizeimage[i]; + } + mxc_jpeg_sizeimage(&tmp_q); + for (i = 0; i < MXC_JPEG_MAX_PLANES; i++) + tmp_q.sizeimage[i] = max(tmp_q.sizeimage[i], q_data->sizeimage[i]); + + /* Handle CREATE_BUFS situation - *nplanes != 0 */ + if (*nplanes) { + if (*nplanes != q_data->fmt->colplanes) + return -EINVAL; + for (i = 0; i < *nplanes; i++) { + if (sizes[i] < tmp_q.sizeimage[i]) + return -EINVAL; + } + return 0; + } + + /* Handle REQBUFS situation */ + *nplanes = q_data->fmt->colplanes; + for (i = 0; i < *nplanes; i++) + sizes[i] = tmp_q.sizeimage[i]; + + return 0; +} + +static int mxc_jpeg_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct mxc_jpeg_q_data *q_data = mxc_jpeg_get_q_data(ctx, q->type); + int ret; + + v4l2_m2m_update_start_streaming_state(ctx->fh.m2m_ctx, q); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE && V4L2_TYPE_IS_CAPTURE(q->type)) + ctx->source_change = 0; + dev_dbg(ctx->mxc_jpeg->dev, "Start streaming ctx=%p", ctx); + q_data->sequence = 0; + + ret = pm_runtime_resume_and_get(ctx->mxc_jpeg->dev); + if (ret < 0) { + dev_err(ctx->mxc_jpeg->dev, "Failed to power up jpeg\n"); + return ret; + } + + return 0; +} + +static void mxc_jpeg_stop_streaming(struct vb2_queue *q) +{ + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + dev_dbg(ctx->mxc_jpeg->dev, "Stop streaming ctx=%p", ctx); + + /* Release all active buffers */ + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) + break; + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } + + v4l2_m2m_update_stop_streaming_state(ctx->fh.m2m_ctx, q); + if (V4L2_TYPE_IS_OUTPUT(q->type) && + v4l2_m2m_has_stopped(ctx->fh.m2m_ctx)) { + notify_eos(ctx); + ctx->header_parsed = false; + } + + pm_runtime_put_sync(&ctx->mxc_jpeg->pdev->dev); +} + +static int mxc_jpeg_valid_comp_id(struct device *dev, + struct mxc_jpeg_sof *sof, + struct mxc_jpeg_sos *sos) +{ + int valid = 1; + int i; + + /* + * there's a limitation in the IP that the component IDs must be + * between 0..4, if they are not, let's patch them + */ + for (i = 0; i < sof->components_no; i++) + if (sof->comp[i].id > MXC_JPEG_MAX_COMPONENTS) { + valid = 0; + dev_err(dev, "Component %d has invalid ID: %d", + i, sof->comp[i].id); + } + if (!valid) + /* patch all comp IDs if at least one is invalid */ + for (i = 0; i < sof->components_no; i++) { + dev_warn(dev, "Component %d ID patched to: %d", + i, i + 1); + sof->comp[i].id = i + 1; + sos->comp[i].id = i + 1; + } + + return valid; +} + +static u32 mxc_jpeg_get_image_format(struct device *dev, + const struct v4l2_jpeg_header *header) +{ + int i; + u32 fourcc = 0; + + for (i = 0; i < MXC_JPEG_NUM_FORMATS; i++) + if (mxc_formats[i].subsampling == header->frame.subsampling && + mxc_formats[i].nc == header->frame.num_components && + mxc_formats[i].precision == header->frame.precision) { + fourcc = mxc_formats[i].fourcc; + break; + } + if (fourcc == 0) { + dev_err(dev, + "Could not identify image format nc=%d, subsampling=%d, precision=%d\n", + header->frame.num_components, + header->frame.subsampling, + header->frame.precision); + return fourcc; + } + /* + * If the transform flag from APP14 marker is 0, images that are + * encoded with 3 components have RGB colorspace, see Recommendation + * ITU-T T.872 chapter 6.5.3 APP14 marker segment for colour encoding + */ + if (fourcc == V4L2_PIX_FMT_YUV24 || fourcc == V4L2_PIX_FMT_BGR24) { + if (header->app14_tf == V4L2_JPEG_APP14_TF_CMYK_RGB) + fourcc = V4L2_PIX_FMT_BGR24; + else + fourcc = V4L2_PIX_FMT_YUV24; + } + + return fourcc; +} + +static void mxc_jpeg_bytesperline(struct mxc_jpeg_q_data *q, u32 precision) +{ + /* Bytes distance between the leftmost pixels in two adjacent lines */ + if (q->fmt->fourcc == V4L2_PIX_FMT_JPEG) { + /* bytesperline unused for compressed formats */ + q->bytesperline[0] = 0; + q->bytesperline[1] = 0; + } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_420) { + /* When the image format is planar the bytesperline value + * applies to the first plane and is divided by the same factor + * as the width field for the other planes + */ + q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8); + q->bytesperline[1] = q->bytesperline[0]; + } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_422) { + q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8) * 2; + q->bytesperline[1] = 0; + } else if (q->fmt->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_444) { + q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8) * q->fmt->nc; + q->bytesperline[1] = 0; + } else { + /* grayscale */ + q->bytesperline[0] = q->w * DIV_ROUND_UP(precision, 8); + q->bytesperline[1] = 0; + } +} + +static void mxc_jpeg_sizeimage(struct mxc_jpeg_q_data *q) +{ + if (q->fmt->fourcc == V4L2_PIX_FMT_JPEG) { + /* if no sizeimage from user, assume worst jpeg compression */ + if (!q->sizeimage[0]) + q->sizeimage[0] = 6 * q->w * q->h; + q->sizeimage[1] = 0; + + if (q->sizeimage[0] > MXC_JPEG_MAX_SIZEIMAGE) + q->sizeimage[0] = MXC_JPEG_MAX_SIZEIMAGE; + + /* jpeg stream size must be multiple of 1K */ + q->sizeimage[0] = ALIGN(q->sizeimage[0], 1024); + } else { + q->sizeimage[0] = q->bytesperline[0] * q->h; + q->sizeimage[1] = 0; + if (q->fmt->fourcc == V4L2_PIX_FMT_NV12M) + q->sizeimage[1] = q->sizeimage[0] / 2; + } +} + +static int mxc_jpeg_parse(struct mxc_jpeg_ctx *ctx, struct vb2_buffer *vb) +{ + struct device *dev = ctx->mxc_jpeg->dev; + struct mxc_jpeg_q_data *q_data_out; + u32 fourcc; + struct v4l2_jpeg_header header; + struct mxc_jpeg_sof *psof = NULL; + struct mxc_jpeg_sos *psos = NULL; + struct mxc_jpeg_src_buf *jpeg_src_buf = vb2_to_mxc_buf(vb); + u8 *src_addr = (u8 *)vb2_plane_vaddr(vb, 0); + u32 size = vb2_get_plane_payload(vb, 0); + int ret; + + memset(&header, 0, sizeof(header)); + ret = v4l2_jpeg_parse_header((void *)src_addr, size, &header); + if (ret < 0) { + dev_err(dev, "Error parsing JPEG stream markers\n"); + return ret; + } + + /* if DHT marker present, no need to inject default one */ + jpeg_src_buf->dht_needed = (header.num_dht == 0); + + q_data_out = mxc_jpeg_get_q_data(ctx, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (q_data_out->w == 0 && q_data_out->h == 0) { + dev_warn(dev, "Invalid user resolution 0x0"); + dev_warn(dev, "Keeping resolution from JPEG: %dx%d", + header.frame.width, header.frame.height); + } else if (header.frame.width != q_data_out->w || + header.frame.height != q_data_out->h) { + dev_err(dev, + "Resolution mismatch: %dx%d (JPEG) versus %dx%d(user)", + header.frame.width, header.frame.height, + q_data_out->w, q_data_out->h); + } + q_data_out->w = header.frame.width; + q_data_out->h = header.frame.height; + if (header.frame.width > MXC_JPEG_MAX_WIDTH || + header.frame.height > MXC_JPEG_MAX_HEIGHT) { + dev_err(dev, "JPEG width or height should be <= 8192: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + if (header.frame.width < MXC_JPEG_MIN_WIDTH || + header.frame.height < MXC_JPEG_MIN_HEIGHT) { + dev_err(dev, "JPEG width or height should be > 64: %dx%d\n", + header.frame.width, header.frame.height); + return -EINVAL; + } + if (header.frame.num_components > V4L2_JPEG_MAX_COMPONENTS) { + dev_err(dev, "JPEG number of components should be <=%d", + V4L2_JPEG_MAX_COMPONENTS); + return -EINVAL; + } + /* check and, if necessary, patch component IDs*/ + psof = (struct mxc_jpeg_sof *)header.sof.start; + psos = (struct mxc_jpeg_sos *)header.sos.start; + if (!mxc_jpeg_valid_comp_id(dev, psof, psos)) + dev_warn(dev, "JPEG component ids should be 0-3 or 1-4"); + + fourcc = mxc_jpeg_get_image_format(dev, &header); + if (fourcc == 0) + return -EINVAL; + + jpeg_src_buf->fmt = mxc_jpeg_find_format(ctx, fourcc); + jpeg_src_buf->w = header.frame.width; + jpeg_src_buf->h = header.frame.height; + ctx->header_parsed = true; + + if (!v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx)) + mxc_jpeg_source_change(ctx, jpeg_src_buf); + + return 0; +} + +static void mxc_jpeg_buf_queue(struct vb2_buffer *vb) +{ + int ret; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mxc_jpeg_src_buf *jpeg_src_buf; + + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type) && + vb2_is_streaming(vb->vb2_queue) && + v4l2_m2m_dst_buf_is_last(ctx->fh.m2m_ctx)) { + struct mxc_jpeg_q_data *q_data; + + q_data = mxc_jpeg_get_q_data(ctx, vb->vb2_queue->type); + vbuf->field = V4L2_FIELD_NONE; + vbuf->sequence = q_data->sequence++; + v4l2_m2m_last_buffer_done(ctx->fh.m2m_ctx, vbuf); + notify_eos(ctx); + ctx->header_parsed = false; + return; + } + + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + goto end; + + /* for V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE */ + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE) + goto end; + + jpeg_src_buf = vb2_to_mxc_buf(vb); + jpeg_src_buf->jpeg_parse_error = false; + ret = mxc_jpeg_parse(ctx, vb); + if (ret) + jpeg_src_buf->jpeg_parse_error = true; + +end: + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int mxc_jpeg_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + vbuf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int mxc_jpeg_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mxc_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct mxc_jpeg_q_data *q_data = NULL; + struct device *dev = ctx->mxc_jpeg->dev; + unsigned long sizeimage; + int i; + + vbuf->field = V4L2_FIELD_NONE; + + q_data = mxc_jpeg_get_q_data(ctx, vb->vb2_queue->type); + if (!q_data) + return -EINVAL; + for (i = 0; i < q_data->fmt->colplanes; i++) { + sizeimage = q_data->sizeimage[i]; + if (vb2_plane_size(vb, i) < sizeimage) { + dev_err(dev, "plane %d too small (%lu < %lu)", + i, vb2_plane_size(vb, i), sizeimage); + return -EINVAL; + } + } + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) { + vb2_set_plane_payload(vb, 0, 0); + vb2_set_plane_payload(vb, 1, 0); + } + return 0; +} + +static const struct vb2_ops mxc_jpeg_qops = { + .queue_setup = mxc_jpeg_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_out_validate = mxc_jpeg_buf_out_validate, + .buf_prepare = mxc_jpeg_buf_prepare, + .start_streaming = mxc_jpeg_start_streaming, + .stop_streaming = mxc_jpeg_stop_streaming, + .buf_queue = mxc_jpeg_buf_queue, +}; + +static int mxc_jpeg_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct mxc_jpeg_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct mxc_jpeg_src_buf); + src_vq->ops = &mxc_jpeg_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->mxc_jpeg->lock; + src_vq->dev = ctx->mxc_jpeg->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &mxc_jpeg_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->mxc_jpeg->lock; + dst_vq->dev = ctx->mxc_jpeg->dev; + + ret = vb2_queue_init(dst_vq); + return ret; +} + +static void mxc_jpeg_set_default_params(struct mxc_jpeg_ctx *ctx) +{ + struct mxc_jpeg_q_data *out_q = &ctx->out_q; + struct mxc_jpeg_q_data *cap_q = &ctx->cap_q; + struct mxc_jpeg_q_data *q[2] = {out_q, cap_q}; + int i; + + if (ctx->mxc_jpeg->mode == MXC_JPEG_ENCODE) { + out_q->fmt = mxc_jpeg_find_format(ctx, MXC_JPEG_DEFAULT_PFMT); + cap_q->fmt = mxc_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG); + } else { + out_q->fmt = mxc_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG); + cap_q->fmt = mxc_jpeg_find_format(ctx, MXC_JPEG_DEFAULT_PFMT); + } + + for (i = 0; i < 2; i++) { + q[i]->w = MXC_JPEG_DEFAULT_WIDTH; + q[i]->h = MXC_JPEG_DEFAULT_HEIGHT; + q[i]->w_adjusted = MXC_JPEG_DEFAULT_WIDTH; + q[i]->h_adjusted = MXC_JPEG_DEFAULT_HEIGHT; + mxc_jpeg_bytesperline(q[i], q[i]->fmt->precision); + mxc_jpeg_sizeimage(q[i]); + } +} + +static int mxc_jpeg_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct mxc_jpeg_ctx *ctx = + container_of(ctrl->handler, struct mxc_jpeg_ctx, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + ctx->jpeg_quality = ctrl->val; + break; + default: + dev_err(ctx->mxc_jpeg->dev, "Invalid control, id = %d, val = %d\n", + ctrl->id, ctrl->val); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops mxc_jpeg_ctrl_ops = { + .s_ctrl = mxc_jpeg_s_ctrl, +}; + +static void mxc_jpeg_encode_ctrls(struct mxc_jpeg_ctx *ctx) +{ + v4l2_ctrl_new_std(&ctx->ctrl_handler, &mxc_jpeg_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, 1, 100, 1, 75); +} + +static int mxc_jpeg_ctrls_setup(struct mxc_jpeg_ctx *ctx) +{ + int err; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 2); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_ENCODE) + mxc_jpeg_encode_ctrls(ctx); + + if (ctx->ctrl_handler.error) { + err = ctx->ctrl_handler.error; + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; + } + + err = v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + if (err) + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; +} + +static int mxc_jpeg_open(struct file *file) +{ + struct mxc_jpeg_dev *mxc_jpeg = video_drvdata(file); + struct video_device *mxc_vfd = video_devdata(file); + struct device *dev = mxc_jpeg->dev; + struct mxc_jpeg_ctx *ctx; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (mutex_lock_interruptible(&mxc_jpeg->lock)) { + ret = -ERESTARTSYS; + goto free; + } + + v4l2_fh_init(&ctx->fh, mxc_vfd); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->mxc_jpeg = mxc_jpeg; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(mxc_jpeg->m2m_dev, ctx, + mxc_jpeg_queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto error; + } + + ret = mxc_jpeg_ctrls_setup(ctx); + if (ret) { + dev_err(ctx->mxc_jpeg->dev, "failed to setup mxc jpeg controls\n"); + goto err_ctrls_setup; + } + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + mxc_jpeg_set_default_params(ctx); + ctx->slot = MXC_MAX_SLOTS; /* slot not allocated yet */ + + if (mxc_jpeg->mode == MXC_JPEG_DECODE) + dev_dbg(dev, "Opened JPEG decoder instance %p\n", ctx); + else + dev_dbg(dev, "Opened JPEG encoder instance %p\n", ctx); + mutex_unlock(&mxc_jpeg->lock); + + return 0; + +err_ctrls_setup: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); +error: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + mutex_unlock(&mxc_jpeg->lock); +free: + kfree(ctx); + return ret; +} + +static int mxc_jpeg_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MXC_JPEG_NAME " codec", sizeof(cap->driver)); + strscpy(cap->card, MXC_JPEG_NAME " codec", sizeof(cap->card)); + cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; + cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS; + + return 0; +} + +static int mxc_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_q_data *q_data = mxc_jpeg_get_q_data(ctx, f->type); + + if (ctx->mxc_jpeg->mode == MXC_JPEG_ENCODE) { + return enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, + MXC_JPEG_FMT_TYPE_ENC); + } else if (!ctx->header_parsed) { + return enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, + MXC_JPEG_FMT_TYPE_RAW); + } else { + /* For the decoder CAPTURE queue, only enumerate the raw formats + * supported for the format currently active on OUTPUT + * (more precisely what was propagated on capture queue + * after jpeg parse on the output buffer) + */ + if (f->index) + return -EINVAL; + f->pixelformat = q_data->fmt->fourcc; + return 0; + } +} + +static int mxc_jpeg_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + u32 type = ctx->mxc_jpeg->mode == MXC_JPEG_DECODE ? MXC_JPEG_FMT_TYPE_ENC : + MXC_JPEG_FMT_TYPE_RAW; + int ret; + + ret = enum_fmt(mxc_formats, MXC_JPEG_NUM_FORMATS, f, type); + if (ret) + return ret; + if (ctx->mxc_jpeg->mode == MXC_JPEG_DECODE) + f->flags = V4L2_FMT_FLAG_DYN_RESOLUTION; + return 0; +} + +static int mxc_jpeg_try_fmt(struct v4l2_format *f, const struct mxc_jpeg_fmt *fmt, + struct mxc_jpeg_ctx *ctx, int q_type) +{ + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *pfmt; + u32 w = (pix_mp->width < MXC_JPEG_MAX_WIDTH) ? + pix_mp->width : MXC_JPEG_MAX_WIDTH; + u32 h = (pix_mp->height < MXC_JPEG_MAX_HEIGHT) ? + pix_mp->height : MXC_JPEG_MAX_HEIGHT; + int i; + struct mxc_jpeg_q_data tmp_q; + + memset(pix_mp->reserved, 0, sizeof(pix_mp->reserved)); + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->num_planes = fmt->colplanes; + pix_mp->pixelformat = fmt->fourcc; + + pix_mp->width = w; + pix_mp->height = h; + v4l_bound_align_image(&w, + w, /* adjust upwards*/ + MXC_JPEG_MAX_WIDTH, + fmt->h_align, + &h, + h, /* adjust upwards*/ + MXC_JPEG_MAX_HEIGHT, + 0, + 0); + + /* get user input into the tmp_q */ + tmp_q.w = w; + tmp_q.h = h; + tmp_q.fmt = fmt; + for (i = 0; i < pix_mp->num_planes; i++) { + pfmt = &pix_mp->plane_fmt[i]; + tmp_q.bytesperline[i] = pfmt->bytesperline; + tmp_q.sizeimage[i] = pfmt->sizeimage; + } + + /* calculate bytesperline & sizeimage into the tmp_q */ + mxc_jpeg_bytesperline(&tmp_q, fmt->precision); + mxc_jpeg_sizeimage(&tmp_q); + + /* adjust user format according to our calculations */ + for (i = 0; i < pix_mp->num_planes; i++) { + pfmt = &pix_mp->plane_fmt[i]; + memset(pfmt->reserved, 0, sizeof(pfmt->reserved)); + pfmt->bytesperline = tmp_q.bytesperline[i]; + pfmt->sizeimage = tmp_q.sizeimage[i]; + } + + /* fix colorspace information to sRGB for both output & capture */ + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_mp->xfer_func = V4L2_XFER_FUNC_SRGB; + /* + * this hardware does not change the range of the samples + * but since inside JPEG the YUV quantization is full-range, + * this driver will always use full-range for the raw frames, too + */ + pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + return 0; +} + +static int mxc_jpeg_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + const struct mxc_jpeg_fmt *fmt; + u32 fourcc = f->fmt.pix_mp.pixelformat; + + int q_type = (jpeg->mode == MXC_JPEG_DECODE) ? + MXC_JPEG_FMT_TYPE_RAW : MXC_JPEG_FMT_TYPE_ENC; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + fmt = mxc_jpeg_find_format(ctx, fourcc); + if (!fmt || fmt->flags != q_type) { + dev_warn(dev, "Format not supported: %c%c%c%c, use the default.\n", + (fourcc & 0xff), + (fourcc >> 8) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 24) & 0xff); + f->fmt.pix_mp.pixelformat = (jpeg->mode == MXC_JPEG_DECODE) ? + MXC_JPEG_DEFAULT_PFMT : V4L2_PIX_FMT_JPEG; + fmt = mxc_jpeg_find_format(ctx, f->fmt.pix_mp.pixelformat); + } + return mxc_jpeg_try_fmt(f, fmt, ctx, q_type); +} + +static int mxc_jpeg_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + const struct mxc_jpeg_fmt *fmt; + u32 fourcc = f->fmt.pix_mp.pixelformat; + + int q_type = (jpeg->mode == MXC_JPEG_ENCODE) ? + MXC_JPEG_FMT_TYPE_RAW : MXC_JPEG_FMT_TYPE_ENC; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "TRY_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + fmt = mxc_jpeg_find_format(ctx, fourcc); + if (!fmt || fmt->flags != q_type) { + dev_warn(dev, "Format not supported: %c%c%c%c, use the default.\n", + (fourcc & 0xff), + (fourcc >> 8) & 0xff, + (fourcc >> 16) & 0xff, + (fourcc >> 24) & 0xff); + f->fmt.pix_mp.pixelformat = (jpeg->mode == MXC_JPEG_ENCODE) ? + MXC_JPEG_DEFAULT_PFMT : V4L2_PIX_FMT_JPEG; + fmt = mxc_jpeg_find_format(ctx, f->fmt.pix_mp.pixelformat); + } + return mxc_jpeg_try_fmt(f, fmt, ctx, q_type); +} + +static int mxc_jpeg_s_fmt(struct mxc_jpeg_ctx *ctx, + struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct mxc_jpeg_q_data *q_data = NULL; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + int i; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = mxc_jpeg_get_q_data(ctx, f->type); + + if (vb2_is_busy(vq)) { + v4l2_err(&jpeg->v4l2_dev, "queue busy\n"); + return -EBUSY; + } + + q_data->fmt = mxc_jpeg_find_format(ctx, pix_mp->pixelformat); + q_data->w = pix_mp->width; + q_data->h = pix_mp->height; + + q_data->w_adjusted = q_data->w; + q_data->h_adjusted = q_data->h; + /* + * align up the resolution for CAST IP, + * but leave the buffer resolution unchanged + */ + v4l_bound_align_image(&q_data->w_adjusted, + q_data->w_adjusted, /* adjust upwards */ + MXC_JPEG_MAX_WIDTH, + q_data->fmt->h_align, + &q_data->h_adjusted, + q_data->h_adjusted, /* adjust upwards */ + MXC_JPEG_MAX_HEIGHT, + q_data->fmt->v_align, + 0); + + for (i = 0; i < pix_mp->num_planes; i++) { + q_data->bytesperline[i] = pix_mp->plane_fmt[i].bytesperline; + q_data->sizeimage[i] = pix_mp->plane_fmt[i].sizeimage; + } + + return 0; +} + +static int mxc_jpeg_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = mxc_jpeg_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); +} + +static int mxc_jpeg_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct vb2_queue *dst_vq; + struct mxc_jpeg_q_data *q_data_cap; + enum v4l2_buf_type cap_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + struct v4l2_format fc; + + ret = mxc_jpeg_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = mxc_jpeg_s_fmt(mxc_jpeg_fh_to_ctx(priv), f); + if (ret) + return ret; + + if (ctx->mxc_jpeg->mode != MXC_JPEG_DECODE) + return 0; + + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, cap_type); + if (!dst_vq) + return -EINVAL; + + if (vb2_is_busy(dst_vq)) + return 0; + + q_data_cap = mxc_jpeg_get_q_data(ctx, cap_type); + if (q_data_cap->w == f->fmt.pix_mp.width && q_data_cap->h == f->fmt.pix_mp.height) + return 0; + memset(&fc, 0, sizeof(fc)); + fc.type = cap_type; + fc.fmt.pix_mp.pixelformat = q_data_cap->fmt->fourcc; + fc.fmt.pix_mp.width = f->fmt.pix_mp.width; + fc.fmt.pix_mp.height = f->fmt.pix_mp.height; + + return mxc_jpeg_s_fmt_vid_cap(file, priv, &fc); +} + +static int mxc_jpeg_g_fmt_vid(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(priv); + struct mxc_jpeg_dev *jpeg = ctx->mxc_jpeg; + struct device *dev = jpeg->dev; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + struct mxc_jpeg_q_data *q_data = mxc_jpeg_get_q_data(ctx, f->type); + int i; + + if (!V4L2_TYPE_IS_MULTIPLANAR(f->type)) { + dev_err(dev, "G_FMT with Invalid type: %d\n", f->type); + return -EINVAL; + } + + pix_mp->pixelformat = q_data->fmt->fourcc; + pix_mp->width = q_data->w; + pix_mp->height = q_data->h; + pix_mp->field = V4L2_FIELD_NONE; + + /* fix colorspace information to sRGB for both output & capture */ + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_mp->xfer_func = V4L2_XFER_FUNC_SRGB; + pix_mp->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + pix_mp->num_planes = q_data->fmt->colplanes; + for (i = 0; i < pix_mp->num_planes; i++) { + pix_mp->plane_fmt[i].bytesperline = q_data->bytesperline[i]; + pix_mp->plane_fmt[i].sizeimage = q_data->sizeimage[i]; + } + + return 0; +} + +static int mxc_jpeg_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_ioctl_ops mxc_jpeg_ioctl_ops = { + .vidioc_querycap = mxc_jpeg_querycap, + .vidioc_enum_fmt_vid_cap = mxc_jpeg_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = mxc_jpeg_enum_fmt_vid_out, + + .vidioc_try_fmt_vid_cap_mplane = mxc_jpeg_try_fmt_vid_cap, + .vidioc_try_fmt_vid_out_mplane = mxc_jpeg_try_fmt_vid_out, + + .vidioc_s_fmt_vid_cap_mplane = mxc_jpeg_s_fmt_vid_cap, + .vidioc_s_fmt_vid_out_mplane = mxc_jpeg_s_fmt_vid_out, + + .vidioc_g_fmt_vid_cap_mplane = mxc_jpeg_g_fmt_vid, + .vidioc_g_fmt_vid_out_mplane = mxc_jpeg_g_fmt_vid, + + .vidioc_subscribe_event = mxc_jpeg_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, + .vidioc_decoder_cmd = mxc_jpeg_decoder_cmd, + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, + .vidioc_encoder_cmd = mxc_jpeg_encoder_cmd, + + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + +static int mxc_jpeg_release(struct file *file) +{ + struct mxc_jpeg_dev *mxc_jpeg = video_drvdata(file); + struct mxc_jpeg_ctx *ctx = mxc_jpeg_fh_to_ctx(file->private_data); + struct device *dev = mxc_jpeg->dev; + + mutex_lock(&mxc_jpeg->lock); + if (mxc_jpeg->mode == MXC_JPEG_DECODE) + dev_dbg(dev, "Release JPEG decoder instance on slot %d.", + ctx->slot); + else + dev_dbg(dev, "Release JPEG encoder instance on slot %d.", + ctx->slot); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&mxc_jpeg->lock); + + return 0; +} + +static const struct v4l2_file_operations mxc_jpeg_fops = { + .owner = THIS_MODULE, + .open = mxc_jpeg_open, + .release = mxc_jpeg_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct v4l2_m2m_ops mxc_jpeg_m2m_ops = { + .job_ready = mxc_jpeg_job_ready, + .device_run = mxc_jpeg_device_run, +}; + +static void mxc_jpeg_detach_pm_domains(struct mxc_jpeg_dev *jpeg) +{ + int i; + + for (i = 0; i < jpeg->num_domains; i++) { + if (jpeg->pd_link[i] && !IS_ERR(jpeg->pd_link[i])) + device_link_del(jpeg->pd_link[i]); + if (jpeg->pd_dev[i] && !IS_ERR(jpeg->pd_dev[i])) + dev_pm_domain_detach(jpeg->pd_dev[i], true); + jpeg->pd_dev[i] = NULL; + jpeg->pd_link[i] = NULL; + } +} + +static int mxc_jpeg_attach_pm_domains(struct mxc_jpeg_dev *jpeg) +{ + struct device *dev = jpeg->dev; + struct device_node *np = jpeg->pdev->dev.of_node; + int i; + int ret; + + jpeg->num_domains = of_count_phandle_with_args(np, "power-domains", + "#power-domain-cells"); + if (jpeg->num_domains < 0) { + dev_err(dev, "No power domains defined for jpeg node\n"); + return jpeg->num_domains; + } + + jpeg->pd_dev = devm_kmalloc_array(dev, jpeg->num_domains, + sizeof(*jpeg->pd_dev), GFP_KERNEL); + if (!jpeg->pd_dev) + return -ENOMEM; + + jpeg->pd_link = devm_kmalloc_array(dev, jpeg->num_domains, + sizeof(*jpeg->pd_link), GFP_KERNEL); + if (!jpeg->pd_link) + return -ENOMEM; + + for (i = 0; i < jpeg->num_domains; i++) { + jpeg->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i); + if (IS_ERR(jpeg->pd_dev[i])) { + ret = PTR_ERR(jpeg->pd_dev[i]); + goto fail; + } + + jpeg->pd_link[i] = device_link_add(dev, jpeg->pd_dev[i], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME); + if (!jpeg->pd_link[i]) { + ret = -EINVAL; + goto fail; + } + } + + return 0; +fail: + mxc_jpeg_detach_pm_domains(jpeg); + return ret; +} + +static int mxc_jpeg_probe(struct platform_device *pdev) +{ + struct mxc_jpeg_dev *jpeg; + struct device *dev = &pdev->dev; + int dec_irq; + int ret; + int mode; + const struct of_device_id *of_id; + unsigned int slot; + + of_id = of_match_node(mxc_jpeg_match, dev->of_node); + mode = *(const int *)of_id->data; + + jpeg = devm_kzalloc(dev, sizeof(struct mxc_jpeg_dev), GFP_KERNEL); + if (!jpeg) + return -ENOMEM; + + mutex_init(&jpeg->lock); + spin_lock_init(&jpeg->hw_lock); + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) { + dev_err(&pdev->dev, "No suitable DMA available.\n"); + goto err_irq; + } + + jpeg->base_reg = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(jpeg->base_reg)) + return PTR_ERR(jpeg->base_reg); + + for (slot = 0; slot < MXC_MAX_SLOTS; slot++) { + dec_irq = platform_get_irq(pdev, slot); + if (dec_irq < 0) { + ret = dec_irq; + goto err_irq; + } + ret = devm_request_irq(&pdev->dev, dec_irq, mxc_jpeg_dec_irq, + 0, pdev->name, jpeg); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq %d (%d)\n", + dec_irq, ret); + goto err_irq; + } + } + + jpeg->pdev = pdev; + jpeg->dev = dev; + jpeg->mode = mode; + + /* Get clocks */ + ret = devm_clk_bulk_get_all(&pdev->dev, &jpeg->clks); + if (ret < 0) { + dev_err(dev, "failed to get clock\n"); + goto err_clk; + } + jpeg->num_clks = ret; + + ret = mxc_jpeg_attach_pm_domains(jpeg); + if (ret < 0) { + dev_err(dev, "failed to attach power domains %d\n", ret); + return ret; + } + + /* v4l2 */ + ret = v4l2_device_register(dev, &jpeg->v4l2_dev); + if (ret) { + dev_err(dev, "failed to register v4l2 device\n"); + goto err_register; + } + jpeg->m2m_dev = v4l2_m2m_init(&mxc_jpeg_m2m_ops); + if (IS_ERR(jpeg->m2m_dev)) { + dev_err(dev, "failed to register v4l2 device\n"); + ret = PTR_ERR(jpeg->m2m_dev); + goto err_m2m; + } + + jpeg->dec_vdev = video_device_alloc(); + if (!jpeg->dec_vdev) { + dev_err(dev, "failed to register v4l2 device\n"); + ret = -ENOMEM; + goto err_vdev_alloc; + } + if (mode == MXC_JPEG_ENCODE) + snprintf(jpeg->dec_vdev->name, + sizeof(jpeg->dec_vdev->name), + "%s-enc", MXC_JPEG_NAME); + else + snprintf(jpeg->dec_vdev->name, + sizeof(jpeg->dec_vdev->name), + "%s-dec", MXC_JPEG_NAME); + + jpeg->dec_vdev->fops = &mxc_jpeg_fops; + jpeg->dec_vdev->ioctl_ops = &mxc_jpeg_ioctl_ops; + jpeg->dec_vdev->minor = -1; + jpeg->dec_vdev->release = video_device_release; + jpeg->dec_vdev->lock = &jpeg->lock; /* lock for ioctl serialization */ + jpeg->dec_vdev->v4l2_dev = &jpeg->v4l2_dev; + jpeg->dec_vdev->vfl_dir = VFL_DIR_M2M; + jpeg->dec_vdev->device_caps = V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_M2M_MPLANE; + if (mode == MXC_JPEG_ENCODE) { + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_DECODER_CMD); + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_TRY_DECODER_CMD); + } else { + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_ENCODER_CMD); + v4l2_disable_ioctl(jpeg->dec_vdev, VIDIOC_TRY_ENCODER_CMD); + } + ret = video_register_device(jpeg->dec_vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(dev, "failed to register video device\n"); + goto err_vdev_register; + } + video_set_drvdata(jpeg->dec_vdev, jpeg); + if (mode == MXC_JPEG_ENCODE) + v4l2_info(&jpeg->v4l2_dev, + "encoder device registered as /dev/video%d (%d,%d)\n", + jpeg->dec_vdev->num, VIDEO_MAJOR, + jpeg->dec_vdev->minor); + else + v4l2_info(&jpeg->v4l2_dev, + "decoder device registered as /dev/video%d (%d,%d)\n", + jpeg->dec_vdev->num, VIDEO_MAJOR, + jpeg->dec_vdev->minor); + + platform_set_drvdata(pdev, jpeg); + pm_runtime_enable(dev); + + return 0; + +err_vdev_register: + video_device_release(jpeg->dec_vdev); + +err_vdev_alloc: + v4l2_m2m_release(jpeg->m2m_dev); + +err_m2m: + v4l2_device_unregister(&jpeg->v4l2_dev); + +err_register: + mxc_jpeg_detach_pm_domains(jpeg); + +err_irq: +err_clk: + return ret; +} + +#ifdef CONFIG_PM +static int mxc_jpeg_runtime_resume(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + int ret; + + ret = clk_bulk_prepare_enable(jpeg->num_clks, jpeg->clks); + if (ret < 0) { + dev_err(dev, "failed to enable clock\n"); + return ret; + } + + return 0; +} + +static int mxc_jpeg_runtime_suspend(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(jpeg->num_clks, jpeg->clks); + + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int mxc_jpeg_suspend(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + + v4l2_m2m_suspend(jpeg->m2m_dev); + return pm_runtime_force_suspend(dev); +} + +static int mxc_jpeg_resume(struct device *dev) +{ + struct mxc_jpeg_dev *jpeg = dev_get_drvdata(dev); + int ret; + + ret = pm_runtime_force_resume(dev); + if (ret < 0) + return ret; + + v4l2_m2m_resume(jpeg->m2m_dev); + return ret; +} +#endif + +static const struct dev_pm_ops mxc_jpeg_pm_ops = { + SET_RUNTIME_PM_OPS(mxc_jpeg_runtime_suspend, + mxc_jpeg_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(mxc_jpeg_suspend, mxc_jpeg_resume) +}; + +static int mxc_jpeg_remove(struct platform_device *pdev) +{ + unsigned int slot; + struct mxc_jpeg_dev *jpeg = platform_get_drvdata(pdev); + + for (slot = 0; slot < MXC_MAX_SLOTS; slot++) + mxc_jpeg_free_slot_data(jpeg, slot); + + pm_runtime_disable(&pdev->dev); + video_unregister_device(jpeg->dec_vdev); + v4l2_m2m_release(jpeg->m2m_dev); + v4l2_device_unregister(&jpeg->v4l2_dev); + mxc_jpeg_detach_pm_domains(jpeg); + + return 0; +} + +MODULE_DEVICE_TABLE(of, mxc_jpeg_match); + +static struct platform_driver mxc_jpeg_driver = { + .probe = mxc_jpeg_probe, + .remove = mxc_jpeg_remove, + .driver = { + .name = "mxc-jpeg", + .of_match_table = mxc_jpeg_match, + .pm = &mxc_jpeg_pm_ops, + }, +}; +module_platform_driver(mxc_jpeg_driver); + +MODULE_AUTHOR("Zhengyu Shen "); +MODULE_AUTHOR("Mirela Rabulea "); +MODULE_DESCRIPTION("V4L2 driver for i.MX8 QXP/QM JPEG encoder/decoder"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h new file mode 100644 index 000000000..d742b638d --- /dev/null +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h @@ -0,0 +1,187 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * i.MX8QXP/i.MX8QM JPEG encoder/decoder v4l2 driver + * + * Copyright 2018-2019 NXP + */ + +#include +#include +#include + +#ifndef _MXC_JPEG_CORE_H +#define _MXC_JPEG_CORE_H + +#define MXC_JPEG_NAME "mxc-jpeg" +#define MXC_JPEG_FMT_TYPE_ENC 0 +#define MXC_JPEG_FMT_TYPE_RAW 1 +#define MXC_JPEG_DEFAULT_WIDTH 1280 +#define MXC_JPEG_DEFAULT_HEIGHT 720 +#define MXC_JPEG_DEFAULT_PFMT V4L2_PIX_FMT_BGR24 +#define MXC_JPEG_MIN_WIDTH 64 +#define MXC_JPEG_MIN_HEIGHT 64 +#define MXC_JPEG_MAX_WIDTH 0x2000 +#define MXC_JPEG_MAX_HEIGHT 0x2000 +#define MXC_JPEG_MAX_CFG_STREAM 0x1000 +#define MXC_JPEG_H_ALIGN 3 +#define MXC_JPEG_W_ALIGN 3 +#define MXC_JPEG_MAX_SIZEIMAGE 0xFFFFFC00 +#define MXC_JPEG_MAX_PLANES 2 + +enum mxc_jpeg_enc_state { + MXC_JPEG_ENCODING = 0, /* jpeg encode phase */ + MXC_JPEG_ENC_CONF = 1, /* jpeg encoder config phase */ +}; + +enum mxc_jpeg_mode { + MXC_JPEG_DECODE = 0, /* jpeg decode mode */ + MXC_JPEG_ENCODE = 1, /* jpeg encode mode */ +}; + +/** + * struct mxc_jpeg_fmt - driver's internal color format data + * @name: format description + * @fourcc: fourcc code, 0 if not applicable + * @subsampling: subsampling of jpeg components + * @nc: number of color components + * @depth: number of bits per pixel + * @colplanes: number of color planes (1 for packed formats) + * @h_align: horizontal alignment order (align to 2^h_align) + * @v_align: vertical alignment order (align to 2^v_align) + * @flags: flags describing format applicability + * @precision: jpeg sample precision + */ +struct mxc_jpeg_fmt { + const char *name; + u32 fourcc; + enum v4l2_jpeg_chroma_subsampling subsampling; + int nc; + int depth; + int colplanes; + int h_align; + int v_align; + u32 flags; + u8 precision; +}; + +struct mxc_jpeg_desc { + u32 next_descpt_ptr; + u32 buf_base0; + u32 buf_base1; + u32 line_pitch; + u32 stm_bufbase; + u32 stm_bufsize; + u32 imgsize; + u32 stm_ctrl; +} __packed; + +struct mxc_jpeg_q_data { + const struct mxc_jpeg_fmt *fmt; + u32 sizeimage[MXC_JPEG_MAX_PLANES]; + u32 bytesperline[MXC_JPEG_MAX_PLANES]; + int w; + int w_adjusted; + int h; + int h_adjusted; + unsigned int sequence; +}; + +struct mxc_jpeg_ctx { + struct mxc_jpeg_dev *mxc_jpeg; + struct mxc_jpeg_q_data out_q; + struct mxc_jpeg_q_data cap_q; + struct v4l2_fh fh; + enum mxc_jpeg_enc_state enc_state; + unsigned int slot; + unsigned int source_change; + bool header_parsed; + struct v4l2_ctrl_handler ctrl_handler; + u8 jpeg_quality; +}; + +struct mxc_jpeg_slot_data { + bool used; + struct mxc_jpeg_desc *desc; // enc/dec descriptor + struct mxc_jpeg_desc *cfg_desc; // configuration descriptor + void *cfg_stream_vaddr; // configuration bitstream virtual address + unsigned int cfg_stream_size; + dma_addr_t desc_handle; + dma_addr_t cfg_desc_handle; // configuration descriptor dma address + dma_addr_t cfg_stream_handle; // configuration bitstream dma address +}; + +struct mxc_jpeg_dev { + spinlock_t hw_lock; /* hardware access lock */ + unsigned int mode; + struct mutex lock; /* v4l2 ioctls serialization */ + struct clk_bulk_data *clks; + int num_clks; + struct platform_device *pdev; + struct device *dev; + void __iomem *base_reg; + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct video_device *dec_vdev; + struct mxc_jpeg_slot_data slot_data[MXC_MAX_SLOTS]; + int num_domains; + struct device **pd_dev; + struct device_link **pd_link; +}; + +/** + * struct mxc_jpeg_sof_comp - JPEG Start Of Frame component fields + * @id: component id + * @v: vertical sampling + * @h: horizontal sampling + * @quantization_table_no: id of quantization table + */ +struct mxc_jpeg_sof_comp { + u8 id; + u8 v :4; + u8 h :4; + u8 quantization_table_no; +} __packed; + +#define MXC_JPEG_MAX_COMPONENTS 4 +/** + * struct mxc_jpeg_sof - JPEG Start Of Frame marker fields + * @length: Start of Frame length + * @precision: precision (bits per pixel per color component) + * @height: image height + * @width: image width + * @components_no: number of color components + * @comp: component fields for each color component + */ +struct mxc_jpeg_sof { + u16 length; + u8 precision; + u16 height, width; + u8 components_no; + struct mxc_jpeg_sof_comp comp[MXC_JPEG_MAX_COMPONENTS]; +} __packed; + +/** + * struct mxc_jpeg_sos_comp - JPEG Start Of Scan component fields + * @id: component id + * @huffman_table_no: id of the Huffman table + */ +struct mxc_jpeg_sos_comp { + u8 id; /*component id*/ + u8 huffman_table_no; +} __packed; + +/** + * struct mxc_jpeg_sos - JPEG Start Of Scan marker fields + * @length: Start of Frame length + * @components_no: number of color components + * @comp: SOS component fields for each color component + * @ignorable_bytes: ignorable bytes + */ +struct mxc_jpeg_sos { + u16 length; + u8 components_no; + struct mxc_jpeg_sos_comp comp[MXC_JPEG_MAX_COMPONENTS]; + u8 ignorable_bytes[3]; +} __packed; + +#endif diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c new file mode 100644 index 000000000..196f2bba4 --- /dev/null +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -0,0 +1,1601 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Samsung CSIS MIPI CSI-2 receiver driver. + * + * The Samsung CSIS IP is a MIPI CSI-2 receiver found in various NXP i.MX7 and + * i.MX8 SoCs. The i.MX7 features version 3.3 of the IP, while i.MX8 features + * version 3.6.3. + * + * Copyright (C) 2019 Linaro Ltd + * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved. + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define CSIS_DRIVER_NAME "imx-mipi-csis" + +#define CSIS_PAD_SINK 0 +#define CSIS_PAD_SOURCE 1 +#define CSIS_PADS_NUM 2 + +#define MIPI_CSIS_DEF_PIX_WIDTH 640 +#define MIPI_CSIS_DEF_PIX_HEIGHT 480 + +/* Register map definition */ + +/* CSIS version */ +#define MIPI_CSIS_VERSION 0x00 +#define MIPI_CSIS_VERSION_IMX7D 0x03030505 +#define MIPI_CSIS_VERSION_IMX8MP 0x03060301 + +/* CSIS common control */ +#define MIPI_CSIS_CMN_CTRL 0x04 +#define MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW BIT(16) +#define MIPI_CSIS_CMN_CTRL_INTER_MODE BIT(10) +#define MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW_CTRL BIT(2) +#define MIPI_CSIS_CMN_CTRL_RESET BIT(1) +#define MIPI_CSIS_CMN_CTRL_ENABLE BIT(0) + +#define MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET 8 +#define MIPI_CSIS_CMN_CTRL_LANE_NR_MASK (3 << 8) + +/* CSIS clock control */ +#define MIPI_CSIS_CLK_CTRL 0x08 +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH3(x) ((x) << 28) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH2(x) ((x) << 24) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH1(x) ((x) << 20) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH0(x) ((x) << 16) +#define MIPI_CSIS_CLK_CTRL_CLKGATE_EN_MSK (0xf << 4) +#define MIPI_CSIS_CLK_CTRL_WCLK_SRC BIT(0) + +/* CSIS Interrupt mask */ +#define MIPI_CSIS_INT_MSK 0x10 +#define MIPI_CSIS_INT_MSK_EVEN_BEFORE BIT(31) +#define MIPI_CSIS_INT_MSK_EVEN_AFTER BIT(30) +#define MIPI_CSIS_INT_MSK_ODD_BEFORE BIT(29) +#define MIPI_CSIS_INT_MSK_ODD_AFTER BIT(28) +#define MIPI_CSIS_INT_MSK_FRAME_START BIT(24) +#define MIPI_CSIS_INT_MSK_FRAME_END BIT(20) +#define MIPI_CSIS_INT_MSK_ERR_SOT_HS BIT(16) +#define MIPI_CSIS_INT_MSK_ERR_LOST_FS BIT(12) +#define MIPI_CSIS_INT_MSK_ERR_LOST_FE BIT(8) +#define MIPI_CSIS_INT_MSK_ERR_OVER BIT(4) +#define MIPI_CSIS_INT_MSK_ERR_WRONG_CFG BIT(3) +#define MIPI_CSIS_INT_MSK_ERR_ECC BIT(2) +#define MIPI_CSIS_INT_MSK_ERR_CRC BIT(1) +#define MIPI_CSIS_INT_MSK_ERR_UNKNOWN BIT(0) + +/* CSIS Interrupt source */ +#define MIPI_CSIS_INT_SRC 0x14 +#define MIPI_CSIS_INT_SRC_EVEN_BEFORE BIT(31) +#define MIPI_CSIS_INT_SRC_EVEN_AFTER BIT(30) +#define MIPI_CSIS_INT_SRC_EVEN BIT(30) +#define MIPI_CSIS_INT_SRC_ODD_BEFORE BIT(29) +#define MIPI_CSIS_INT_SRC_ODD_AFTER BIT(28) +#define MIPI_CSIS_INT_SRC_ODD (0x3 << 28) +#define MIPI_CSIS_INT_SRC_NON_IMAGE_DATA (0xf << 28) +#define MIPI_CSIS_INT_SRC_FRAME_START BIT(24) +#define MIPI_CSIS_INT_SRC_FRAME_END BIT(20) +#define MIPI_CSIS_INT_SRC_ERR_SOT_HS BIT(16) +#define MIPI_CSIS_INT_SRC_ERR_LOST_FS BIT(12) +#define MIPI_CSIS_INT_SRC_ERR_LOST_FE BIT(8) +#define MIPI_CSIS_INT_SRC_ERR_OVER BIT(4) +#define MIPI_CSIS_INT_SRC_ERR_WRONG_CFG BIT(3) +#define MIPI_CSIS_INT_SRC_ERR_ECC BIT(2) +#define MIPI_CSIS_INT_SRC_ERR_CRC BIT(1) +#define MIPI_CSIS_INT_SRC_ERR_UNKNOWN BIT(0) +#define MIPI_CSIS_INT_SRC_ERRORS 0xfffff + +/* D-PHY status control */ +#define MIPI_CSIS_DPHY_STATUS 0x20 +#define MIPI_CSIS_DPHY_STATUS_ULPS_DAT BIT(8) +#define MIPI_CSIS_DPHY_STATUS_STOPSTATE_DAT BIT(4) +#define MIPI_CSIS_DPHY_STATUS_ULPS_CLK BIT(1) +#define MIPI_CSIS_DPHY_STATUS_STOPSTATE_CLK BIT(0) + +/* D-PHY common control */ +#define MIPI_CSIS_DPHY_CMN_CTRL 0x24 +#define MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE(n) ((n) << 24) +#define MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE_MASK GENMASK(31, 24) +#define MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE(n) ((n) << 22) +#define MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE_MASK GENMASK(23, 22) +#define MIPI_CSIS_DPHY_CMN_CTRL_DPDN_SWAP_CLK BIT(6) +#define MIPI_CSIS_DPHY_CMN_CTRL_DPDN_SWAP_DAT BIT(5) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE_DAT BIT(1) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE_CLK BIT(0) +#define MIPI_CSIS_DPHY_CMN_CTRL_ENABLE (0x1f << 0) + +/* D-PHY Master and Slave Control register Low */ +#define MIPI_CSIS_DPHY_BCTRL_L 0x30 +#define MIPI_CSIS_DPHY_BCTRL_L_USER_DATA_PATTERN_LOW(n) (((n) & 3U) << 30) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_715MV (0 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_724MV (1 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_733MV (2 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_706MV (3 << 28) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_3MHZ (0 << 27) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_1_5MHZ (1 << 27) +#define MIPI_CSIS_DPHY_BCTRL_L_VREG12_EXTPWR_EN_CTL BIT(26) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_2V (0 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_23V (1 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_17V (2 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_26V (3 << 24) +#define MIPI_CSIS_DPHY_BCTRL_L_REG_1P2_LVL_SEL BIT(23) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_80MV (0 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_100MV (1 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_120MV (2 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_140MV (3 << 21) +#define MIPI_CSIS_DPHY_BCTRL_L_VREF_SRC_SEL BIT(20) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_715MV (0 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_743MV (1 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_650MV (2 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_682MV (3 << 18) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_RX_PULSE_REJECT BIT(17) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_0 (0 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_15P (1 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_DOWN_30P (3 << 15) +#define MIPI_CSIS_DPHY_BCTRL_L_MSTRCLK_LP_SLEW_RATE_UP BIT(14) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_60MV (0 << 13) +#define MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_70MV (1 << 13) +#define MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_EN BIT(12) +#define MIPI_CSIS_DPHY_BCTRL_L_ERRCONTENTION_LP_EN BIT(11) +#define MIPI_CSIS_DPHY_BCTRL_L_TXTRIGGER_CLK_EN BIT(10) +#define MIPI_CSIS_DPHY_BCTRL_L_B_DPHYCTRL(n) (((n) * 25 / 1000000) << 0) + +/* D-PHY Master and Slave Control register High */ +#define MIPI_CSIS_DPHY_BCTRL_H 0x34 +/* D-PHY Slave Control register Low */ +#define MIPI_CSIS_DPHY_SCTRL_L 0x38 +/* D-PHY Slave Control register High */ +#define MIPI_CSIS_DPHY_SCTRL_H 0x3c + +/* ISP Configuration register */ +#define MIPI_CSIS_ISP_CONFIG_CH(n) (0x40 + (n) * 0x10) +#define MIPI_CSIS_ISPCFG_MEM_FULL_GAP_MSK (0xff << 24) +#define MIPI_CSIS_ISPCFG_MEM_FULL_GAP(x) ((x) << 24) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_SINGLE (0 << 12) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_DUAL (1 << 12) +#define MIPI_CSIS_ISPCFG_PIXEL_MODE_QUAD (2 << 12) /* i.MX8M[MNP] only */ +#define MIPI_CSIS_ISPCFG_PIXEL_MASK (3 << 12) +#define MIPI_CSIS_ISPCFG_ALIGN_32BIT BIT(11) +#define MIPI_CSIS_ISPCFG_FMT(fmt) ((fmt) << 2) +#define MIPI_CSIS_ISPCFG_FMT_MASK (0x3f << 2) + +/* ISP Image Resolution register */ +#define MIPI_CSIS_ISP_RESOL_CH(n) (0x44 + (n) * 0x10) +#define CSIS_MAX_PIX_WIDTH 0xffff +#define CSIS_MAX_PIX_HEIGHT 0xffff + +/* ISP SYNC register */ +#define MIPI_CSIS_ISP_SYNC_CH(n) (0x48 + (n) * 0x10) +#define MIPI_CSIS_ISP_SYNC_HSYNC_LINTV_OFFSET 18 +#define MIPI_CSIS_ISP_SYNC_VSYNC_SINTV_OFFSET 12 +#define MIPI_CSIS_ISP_SYNC_VSYNC_EINTV_OFFSET 0 + +/* ISP shadow registers */ +#define MIPI_CSIS_SDW_CONFIG_CH(n) (0x80 + (n) * 0x10) +#define MIPI_CSIS_SDW_RESOL_CH(n) (0x84 + (n) * 0x10) +#define MIPI_CSIS_SDW_SYNC_CH(n) (0x88 + (n) * 0x10) + +/* Debug control register */ +#define MIPI_CSIS_DBG_CTRL 0xc0 +#define MIPI_CSIS_DBG_INTR_MSK 0xc4 +#define MIPI_CSIS_DBG_INTR_MSK_DT_NOT_SUPPORT BIT(25) +#define MIPI_CSIS_DBG_INTR_MSK_DT_IGNORE BIT(24) +#define MIPI_CSIS_DBG_INTR_MSK_ERR_FRAME_SIZE BIT(20) +#define MIPI_CSIS_DBG_INTR_MSK_TRUNCATED_FRAME BIT(16) +#define MIPI_CSIS_DBG_INTR_MSK_EARLY_FE BIT(12) +#define MIPI_CSIS_DBG_INTR_MSK_EARLY_FS BIT(8) +#define MIPI_CSIS_DBG_INTR_MSK_CAM_VSYNC_FALL BIT(4) +#define MIPI_CSIS_DBG_INTR_MSK_CAM_VSYNC_RISE BIT(0) +#define MIPI_CSIS_DBG_INTR_SRC 0xc8 +#define MIPI_CSIS_DBG_INTR_SRC_DT_NOT_SUPPORT BIT(25) +#define MIPI_CSIS_DBG_INTR_SRC_DT_IGNORE BIT(24) +#define MIPI_CSIS_DBG_INTR_SRC_ERR_FRAME_SIZE BIT(20) +#define MIPI_CSIS_DBG_INTR_SRC_TRUNCATED_FRAME BIT(16) +#define MIPI_CSIS_DBG_INTR_SRC_EARLY_FE BIT(12) +#define MIPI_CSIS_DBG_INTR_SRC_EARLY_FS BIT(8) +#define MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_FALL BIT(4) +#define MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_RISE BIT(0) + +#define MIPI_CSIS_FRAME_COUNTER_CH(n) (0x0100 + (n) * 4) + +/* Non-image packet data buffers */ +#define MIPI_CSIS_PKTDATA_ODD 0x2000 +#define MIPI_CSIS_PKTDATA_EVEN 0x3000 +#define MIPI_CSIS_PKTDATA_SIZE SZ_4K + +#define DEFAULT_SCLK_CSIS_FREQ 166000000UL + +/* MIPI CSI-2 Data Types */ +#define MIPI_CSI2_DATA_TYPE_YUV420_8 0x18 +#define MIPI_CSI2_DATA_TYPE_YUV420_10 0x19 +#define MIPI_CSI2_DATA_TYPE_LE_YUV420_8 0x1a +#define MIPI_CSI2_DATA_TYPE_CS_YUV420_8 0x1c +#define MIPI_CSI2_DATA_TYPE_CS_YUV420_10 0x1d +#define MIPI_CSI2_DATA_TYPE_YUV422_8 0x1e +#define MIPI_CSI2_DATA_TYPE_YUV422_10 0x1f +#define MIPI_CSI2_DATA_TYPE_RGB565 0x22 +#define MIPI_CSI2_DATA_TYPE_RGB666 0x23 +#define MIPI_CSI2_DATA_TYPE_RGB888 0x24 +#define MIPI_CSI2_DATA_TYPE_RAW6 0x28 +#define MIPI_CSI2_DATA_TYPE_RAW7 0x29 +#define MIPI_CSI2_DATA_TYPE_RAW8 0x2a +#define MIPI_CSI2_DATA_TYPE_RAW10 0x2b +#define MIPI_CSI2_DATA_TYPE_RAW12 0x2c +#define MIPI_CSI2_DATA_TYPE_RAW14 0x2d +#define MIPI_CSI2_DATA_TYPE_USER(x) (0x30 + (x)) + +struct mipi_csis_event { + bool debug; + u32 mask; + const char * const name; + unsigned int counter; +}; + +static const struct mipi_csis_event mipi_csis_events[] = { + /* Errors */ + { false, MIPI_CSIS_INT_SRC_ERR_SOT_HS, "SOT Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_LOST_FS, "Lost Frame Start Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_LOST_FE, "Lost Frame End Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_OVER, "FIFO Overflow Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_WRONG_CFG, "Wrong Configuration Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_ECC, "ECC Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_CRC, "CRC Error" }, + { false, MIPI_CSIS_INT_SRC_ERR_UNKNOWN, "Unknown Error" }, + { true, MIPI_CSIS_DBG_INTR_SRC_DT_NOT_SUPPORT, "Data Type Not Supported" }, + { true, MIPI_CSIS_DBG_INTR_SRC_DT_IGNORE, "Data Type Ignored" }, + { true, MIPI_CSIS_DBG_INTR_SRC_ERR_FRAME_SIZE, "Frame Size Error" }, + { true, MIPI_CSIS_DBG_INTR_SRC_TRUNCATED_FRAME, "Truncated Frame" }, + { true, MIPI_CSIS_DBG_INTR_SRC_EARLY_FE, "Early Frame End" }, + { true, MIPI_CSIS_DBG_INTR_SRC_EARLY_FS, "Early Frame Start" }, + /* Non-image data receive events */ + { false, MIPI_CSIS_INT_SRC_EVEN_BEFORE, "Non-image data before even frame" }, + { false, MIPI_CSIS_INT_SRC_EVEN_AFTER, "Non-image data after even frame" }, + { false, MIPI_CSIS_INT_SRC_ODD_BEFORE, "Non-image data before odd frame" }, + { false, MIPI_CSIS_INT_SRC_ODD_AFTER, "Non-image data after odd frame" }, + /* Frame start/end */ + { false, MIPI_CSIS_INT_SRC_FRAME_START, "Frame Start" }, + { false, MIPI_CSIS_INT_SRC_FRAME_END, "Frame End" }, + { true, MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_FALL, "VSYNC Falling Edge" }, + { true, MIPI_CSIS_DBG_INTR_SRC_CAM_VSYNC_RISE, "VSYNC Rising Edge" }, +}; + +#define MIPI_CSIS_NUM_EVENTS ARRAY_SIZE(mipi_csis_events) + +enum mipi_csis_clk { + MIPI_CSIS_CLK_PCLK, + MIPI_CSIS_CLK_WRAP, + MIPI_CSIS_CLK_PHY, + MIPI_CSIS_CLK_AXI, +}; + +static const char * const mipi_csis_clk_id[] = { + "pclk", + "wrap", + "phy", + "axi", +}; + +enum mipi_csis_version { + MIPI_CSIS_V3_3, + MIPI_CSIS_V3_6_3, +}; + +struct mipi_csis_info { + enum mipi_csis_version version; + unsigned int num_clocks; +}; + +struct mipi_csis_device { + struct device *dev; + void __iomem *regs; + struct clk_bulk_data *clks; + struct reset_control *mrst; + struct regulator *mipi_phy_regulator; + const struct mipi_csis_info *info; + + struct v4l2_subdev sd; + struct media_pad pads[CSIS_PADS_NUM]; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *src_sd; + + struct v4l2_mbus_config_mipi_csi2 bus; + u32 clk_frequency; + u32 hs_settle; + u32 clk_settle; + + struct mutex lock; /* Protect csis_fmt and format_mbus */ + const struct csis_pix_format *csis_fmt; + struct v4l2_mbus_framefmt format_mbus[CSIS_PADS_NUM]; + + spinlock_t slock; /* Protect events */ + struct mipi_csis_event events[MIPI_CSIS_NUM_EVENTS]; + struct dentry *debugfs_root; + struct { + bool enable; + u32 hs_settle; + u32 clk_settle; + } debug; +}; + +/* ----------------------------------------------------------------------------- + * Format helpers + */ + +struct csis_pix_format { + u32 code; + u32 output; + u32 data_type; + u8 width; +}; + +static const struct csis_pix_format mipi_csis_formats[] = { + /* YUV formats. */ + { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .output = MEDIA_BUS_FMT_UYVY8_1X16, + .data_type = MIPI_CSI2_DATA_TYPE_YUV422_8, + .width = 16, + }, + /* RGB formats. */ + { + .code = MEDIA_BUS_FMT_RGB565_1X16, + .output = MEDIA_BUS_FMT_RGB565_1X16, + .data_type = MIPI_CSI2_DATA_TYPE_RGB565, + .width = 16, + }, { + .code = MEDIA_BUS_FMT_BGR888_1X24, + .output = MEDIA_BUS_FMT_RGB888_1X24, + .data_type = MIPI_CSI2_DATA_TYPE_RGB888, + .width = 24, + }, + /* RAW (Bayer and greyscale) formats. */ + { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .output = MEDIA_BUS_FMT_SBGGR8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .output = MEDIA_BUS_FMT_SGBRG8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .output = MEDIA_BUS_FMT_SGRBG8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .output = MEDIA_BUS_FMT_SRGGB8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_Y8_1X8, + .output = MEDIA_BUS_FMT_Y8_1X8, + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + }, { + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .output = MEDIA_BUS_FMT_SBGGR10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .output = MEDIA_BUS_FMT_SGBRG10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .output = MEDIA_BUS_FMT_SGRBG10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .output = MEDIA_BUS_FMT_SRGGB10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .output = MEDIA_BUS_FMT_Y10_1X10, + .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .width = 10, + }, { + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .output = MEDIA_BUS_FMT_SBGGR12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .output = MEDIA_BUS_FMT_SGBRG12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .output = MEDIA_BUS_FMT_SGRBG12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .output = MEDIA_BUS_FMT_SRGGB12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_Y12_1X12, + .output = MEDIA_BUS_FMT_Y12_1X12, + .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .width = 12, + }, { + .code = MEDIA_BUS_FMT_SBGGR14_1X14, + .output = MEDIA_BUS_FMT_SBGGR14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGBRG14_1X14, + .output = MEDIA_BUS_FMT_SGBRG14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SGRBG14_1X14, + .output = MEDIA_BUS_FMT_SGRBG14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, { + .code = MEDIA_BUS_FMT_SRGGB14_1X14, + .output = MEDIA_BUS_FMT_SRGGB14_1X14, + .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .width = 14, + }, + /* JPEG */ + { + .code = MEDIA_BUS_FMT_JPEG_1X8, + .output = MEDIA_BUS_FMT_JPEG_1X8, + /* + * Map JPEG_1X8 to the RAW8 datatype. + * + * The CSI-2 specification suggests in Annex A "JPEG8 Data + * Format (informative)" to transmit JPEG data using one of the + * Data Types aimed to represent arbitrary data, such as the + * "User Defined Data Type 1" (0x30). + * + * However, when configured with a User Defined Data Type, the + * CSIS outputs data in quad pixel mode regardless of the mode + * selected in the MIPI_CSIS_ISP_CONFIG_CH register. Neither of + * the IP cores connected to the CSIS in i.MX SoCs (CSI bridge + * or ISI) support quad pixel mode, so this will never work in + * practice. + * + * Some sensors (such as the OV5640) send JPEG data using the + * RAW8 data type. This is usable and works, so map the JPEG + * format to RAW8. If the CSIS ends up being integrated in an + * SoC that can support quad pixel mode, this will have to be + * revisited. + */ + .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .width = 8, + } +}; + +static const struct csis_pix_format *find_csis_format(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mipi_csis_formats); i++) + if (code == mipi_csis_formats[i].code) + return &mipi_csis_formats[i]; + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Hardware configuration + */ + +static inline u32 mipi_csis_read(struct mipi_csis_device *csis, u32 reg) +{ + return readl(csis->regs + reg); +} + +static inline void mipi_csis_write(struct mipi_csis_device *csis, u32 reg, + u32 val) +{ + writel(val, csis->regs + reg); +} + +static void mipi_csis_enable_interrupts(struct mipi_csis_device *csis, bool on) +{ + mipi_csis_write(csis, MIPI_CSIS_INT_MSK, on ? 0xffffffff : 0); + mipi_csis_write(csis, MIPI_CSIS_DBG_INTR_MSK, on ? 0xffffffff : 0); +} + +static void mipi_csis_sw_reset(struct mipi_csis_device *csis) +{ + u32 val = mipi_csis_read(csis, MIPI_CSIS_CMN_CTRL); + + mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, + val | MIPI_CSIS_CMN_CTRL_RESET); + usleep_range(10, 20); +} + +static void mipi_csis_system_enable(struct mipi_csis_device *csis, int on) +{ + u32 val, mask; + + val = mipi_csis_read(csis, MIPI_CSIS_CMN_CTRL); + if (on) + val |= MIPI_CSIS_CMN_CTRL_ENABLE; + else + val &= ~MIPI_CSIS_CMN_CTRL_ENABLE; + mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, val); + + val = mipi_csis_read(csis, MIPI_CSIS_DPHY_CMN_CTRL); + val &= ~MIPI_CSIS_DPHY_CMN_CTRL_ENABLE; + if (on) { + mask = (1 << (csis->bus.num_data_lanes + 1)) - 1; + val |= (mask & MIPI_CSIS_DPHY_CMN_CTRL_ENABLE); + } + mipi_csis_write(csis, MIPI_CSIS_DPHY_CMN_CTRL, val); +} + +/* Called with the csis.lock mutex held */ +static void __mipi_csis_set_format(struct mipi_csis_device *csis) +{ + struct v4l2_mbus_framefmt *mf = &csis->format_mbus[CSIS_PAD_SINK]; + u32 val; + + /* Color format */ + val = mipi_csis_read(csis, MIPI_CSIS_ISP_CONFIG_CH(0)); + val &= ~(MIPI_CSIS_ISPCFG_ALIGN_32BIT | MIPI_CSIS_ISPCFG_FMT_MASK + | MIPI_CSIS_ISPCFG_PIXEL_MASK); + + /* + * YUV 4:2:2 can be transferred with 8 or 16 bits per clock sample + * (referred to in the documentation as single and dual pixel modes + * respectively, although the 8-bit mode transfers half a pixel per + * clock sample and the 16-bit mode one pixel). While both mode work + * when the CSIS is connected to a receiver that supports either option, + * single pixel mode requires clock rates twice as high. As all SoCs + * that integrate the CSIS can operate in 16-bit bit mode, and some do + * not support 8-bit mode (this is the case of the i.MX8MP), use dual + * pixel mode unconditionally. + * + * TODO: Verify which other formats require DUAL (or QUAD) modes. + */ + if (csis->csis_fmt->data_type == MIPI_CSI2_DATA_TYPE_YUV422_8) + val |= MIPI_CSIS_ISPCFG_PIXEL_MODE_DUAL; + + val |= MIPI_CSIS_ISPCFG_FMT(csis->csis_fmt->data_type); + mipi_csis_write(csis, MIPI_CSIS_ISP_CONFIG_CH(0), val); + + /* Pixel resolution */ + val = mf->width | (mf->height << 16); + mipi_csis_write(csis, MIPI_CSIS_ISP_RESOL_CH(0), val); +} + +static int mipi_csis_calculate_params(struct mipi_csis_device *csis) +{ + s64 link_freq; + u32 lane_rate; + + /* Calculate the line rate from the pixel rate. */ + link_freq = v4l2_get_link_freq(csis->src_sd->ctrl_handler, + csis->csis_fmt->width, + csis->bus.num_data_lanes * 2); + if (link_freq < 0) { + dev_err(csis->dev, "Unable to obtain link frequency: %d\n", + (int)link_freq); + return link_freq; + } + + lane_rate = link_freq * 2; + + if (lane_rate < 80000000 || lane_rate > 1500000000) { + dev_dbg(csis->dev, "Out-of-bound lane rate %u\n", lane_rate); + return -EINVAL; + } + + /* + * The HSSETTLE counter value is document in a table, but can also + * easily be calculated. Hardcode the CLKSETTLE value to 0 for now + * (which is documented as corresponding to CSI-2 v0.87 to v1.00) until + * we figure out how to compute it correctly. + */ + csis->hs_settle = (lane_rate - 5000000) / 45000000; + csis->clk_settle = 0; + + dev_dbg(csis->dev, "lane rate %u, Tclk_settle %u, Ths_settle %u\n", + lane_rate, csis->clk_settle, csis->hs_settle); + + if (csis->debug.hs_settle < 0xff) { + dev_dbg(csis->dev, "overriding Ths_settle with %u\n", + csis->debug.hs_settle); + csis->hs_settle = csis->debug.hs_settle; + } + + if (csis->debug.clk_settle < 4) { + dev_dbg(csis->dev, "overriding Tclk_settle with %u\n", + csis->debug.clk_settle); + csis->clk_settle = csis->debug.clk_settle; + } + + return 0; +} + +static void mipi_csis_set_params(struct mipi_csis_device *csis) +{ + int lanes = csis->bus.num_data_lanes; + u32 val; + + val = mipi_csis_read(csis, MIPI_CSIS_CMN_CTRL); + val &= ~MIPI_CSIS_CMN_CTRL_LANE_NR_MASK; + val |= (lanes - 1) << MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET; + if (csis->info->version == MIPI_CSIS_V3_3) + val |= MIPI_CSIS_CMN_CTRL_INTER_MODE; + mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, val); + + __mipi_csis_set_format(csis); + + mipi_csis_write(csis, MIPI_CSIS_DPHY_CMN_CTRL, + MIPI_CSIS_DPHY_CMN_CTRL_HSSETTLE(csis->hs_settle) | + MIPI_CSIS_DPHY_CMN_CTRL_CLKSETTLE(csis->clk_settle)); + + val = (0 << MIPI_CSIS_ISP_SYNC_HSYNC_LINTV_OFFSET) + | (0 << MIPI_CSIS_ISP_SYNC_VSYNC_SINTV_OFFSET) + | (0 << MIPI_CSIS_ISP_SYNC_VSYNC_EINTV_OFFSET); + mipi_csis_write(csis, MIPI_CSIS_ISP_SYNC_CH(0), val); + + val = mipi_csis_read(csis, MIPI_CSIS_CLK_CTRL); + val |= MIPI_CSIS_CLK_CTRL_WCLK_SRC; + val |= MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH0(15); + val &= ~MIPI_CSIS_CLK_CTRL_CLKGATE_EN_MSK; + mipi_csis_write(csis, MIPI_CSIS_CLK_CTRL, val); + + mipi_csis_write(csis, MIPI_CSIS_DPHY_BCTRL_L, + MIPI_CSIS_DPHY_BCTRL_L_BIAS_REF_VOLT_715MV | + MIPI_CSIS_DPHY_BCTRL_L_BGR_CHOPPER_FREQ_3MHZ | + MIPI_CSIS_DPHY_BCTRL_L_REG_12P_LVL_CTL_1_2V | + MIPI_CSIS_DPHY_BCTRL_L_LP_RX_HYS_LVL_80MV | + MIPI_CSIS_DPHY_BCTRL_L_LP_RX_VREF_LVL_715MV | + MIPI_CSIS_DPHY_BCTRL_L_LP_CD_HYS_60MV | + MIPI_CSIS_DPHY_BCTRL_L_B_DPHYCTRL(20000000)); + mipi_csis_write(csis, MIPI_CSIS_DPHY_BCTRL_H, 0); + + /* Update the shadow register. */ + val = mipi_csis_read(csis, MIPI_CSIS_CMN_CTRL); + mipi_csis_write(csis, MIPI_CSIS_CMN_CTRL, + val | MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW | + MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW_CTRL); +} + +static int mipi_csis_clk_enable(struct mipi_csis_device *csis) +{ + return clk_bulk_prepare_enable(csis->info->num_clocks, csis->clks); +} + +static void mipi_csis_clk_disable(struct mipi_csis_device *csis) +{ + clk_bulk_disable_unprepare(csis->info->num_clocks, csis->clks); +} + +static int mipi_csis_clk_get(struct mipi_csis_device *csis) +{ + unsigned int i; + int ret; + + csis->clks = devm_kcalloc(csis->dev, csis->info->num_clocks, + sizeof(*csis->clks), GFP_KERNEL); + + if (!csis->clks) + return -ENOMEM; + + for (i = 0; i < csis->info->num_clocks; i++) + csis->clks[i].id = mipi_csis_clk_id[i]; + + ret = devm_clk_bulk_get(csis->dev, csis->info->num_clocks, + csis->clks); + if (ret < 0) + return ret; + + /* Set clock rate */ + ret = clk_set_rate(csis->clks[MIPI_CSIS_CLK_WRAP].clk, + csis->clk_frequency); + if (ret < 0) + dev_err(csis->dev, "set rate=%d failed: %d\n", + csis->clk_frequency, ret); + + return ret; +} + +static void mipi_csis_start_stream(struct mipi_csis_device *csis) +{ + mipi_csis_sw_reset(csis); + mipi_csis_set_params(csis); + mipi_csis_system_enable(csis, true); + mipi_csis_enable_interrupts(csis, true); +} + +static void mipi_csis_stop_stream(struct mipi_csis_device *csis) +{ + mipi_csis_enable_interrupts(csis, false); + mipi_csis_system_enable(csis, false); +} + +static irqreturn_t mipi_csis_irq_handler(int irq, void *dev_id) +{ + struct mipi_csis_device *csis = dev_id; + unsigned long flags; + unsigned int i; + u32 status; + u32 dbg_status; + + status = mipi_csis_read(csis, MIPI_CSIS_INT_SRC); + dbg_status = mipi_csis_read(csis, MIPI_CSIS_DBG_INTR_SRC); + + spin_lock_irqsave(&csis->slock, flags); + + /* Update the event/error counters */ + if ((status & MIPI_CSIS_INT_SRC_ERRORS) || csis->debug.enable) { + for (i = 0; i < MIPI_CSIS_NUM_EVENTS; i++) { + struct mipi_csis_event *event = &csis->events[i]; + + if ((!event->debug && (status & event->mask)) || + (event->debug && (dbg_status & event->mask))) + event->counter++; + } + } + spin_unlock_irqrestore(&csis->slock, flags); + + mipi_csis_write(csis, MIPI_CSIS_INT_SRC, status); + mipi_csis_write(csis, MIPI_CSIS_DBG_INTR_SRC, dbg_status); + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * PHY regulator and reset + */ + +static int mipi_csis_phy_enable(struct mipi_csis_device *csis) +{ + if (csis->info->version != MIPI_CSIS_V3_3) + return 0; + + return regulator_enable(csis->mipi_phy_regulator); +} + +static int mipi_csis_phy_disable(struct mipi_csis_device *csis) +{ + if (csis->info->version != MIPI_CSIS_V3_3) + return 0; + + return regulator_disable(csis->mipi_phy_regulator); +} + +static void mipi_csis_phy_reset(struct mipi_csis_device *csis) +{ + if (csis->info->version != MIPI_CSIS_V3_3) + return; + + reset_control_assert(csis->mrst); + msleep(20); + reset_control_deassert(csis->mrst); +} + +static int mipi_csis_phy_init(struct mipi_csis_device *csis) +{ + if (csis->info->version != MIPI_CSIS_V3_3) + return 0; + + /* Get MIPI PHY reset and regulator. */ + csis->mrst = devm_reset_control_get_exclusive(csis->dev, NULL); + if (IS_ERR(csis->mrst)) + return PTR_ERR(csis->mrst); + + csis->mipi_phy_regulator = devm_regulator_get(csis->dev, "phy"); + if (IS_ERR(csis->mipi_phy_regulator)) + return PTR_ERR(csis->mipi_phy_regulator); + + return regulator_set_voltage(csis->mipi_phy_regulator, 1000000, + 1000000); +} + +/* ----------------------------------------------------------------------------- + * Debug + */ + +static void mipi_csis_clear_counters(struct mipi_csis_device *csis) +{ + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&csis->slock, flags); + for (i = 0; i < MIPI_CSIS_NUM_EVENTS; i++) + csis->events[i].counter = 0; + spin_unlock_irqrestore(&csis->slock, flags); +} + +static void mipi_csis_log_counters(struct mipi_csis_device *csis, bool non_errors) +{ + unsigned int num_events = non_errors ? MIPI_CSIS_NUM_EVENTS + : MIPI_CSIS_NUM_EVENTS - 8; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&csis->slock, flags); + + for (i = 0; i < num_events; ++i) { + if (csis->events[i].counter > 0 || csis->debug.enable) + dev_info(csis->dev, "%s events: %d\n", + csis->events[i].name, + csis->events[i].counter); + } + spin_unlock_irqrestore(&csis->slock, flags); +} + +static int mipi_csis_dump_regs(struct mipi_csis_device *csis) +{ + static const struct { + u32 offset; + const char * const name; + } registers[] = { + { MIPI_CSIS_CMN_CTRL, "CMN_CTRL" }, + { MIPI_CSIS_CLK_CTRL, "CLK_CTRL" }, + { MIPI_CSIS_INT_MSK, "INT_MSK" }, + { MIPI_CSIS_DPHY_STATUS, "DPHY_STATUS" }, + { MIPI_CSIS_DPHY_CMN_CTRL, "DPHY_CMN_CTRL" }, + { MIPI_CSIS_DPHY_SCTRL_L, "DPHY_SCTRL_L" }, + { MIPI_CSIS_DPHY_SCTRL_H, "DPHY_SCTRL_H" }, + { MIPI_CSIS_ISP_CONFIG_CH(0), "ISP_CONFIG_CH0" }, + { MIPI_CSIS_ISP_RESOL_CH(0), "ISP_RESOL_CH0" }, + { MIPI_CSIS_SDW_CONFIG_CH(0), "SDW_CONFIG_CH0" }, + { MIPI_CSIS_SDW_RESOL_CH(0), "SDW_RESOL_CH0" }, + { MIPI_CSIS_DBG_CTRL, "DBG_CTRL" }, + { MIPI_CSIS_FRAME_COUNTER_CH(0), "FRAME_COUNTER_CH0" }, + }; + + unsigned int i; + u32 cfg; + + if (!pm_runtime_get_if_in_use(csis->dev)) + return 0; + + dev_info(csis->dev, "--- REGISTERS ---\n"); + + for (i = 0; i < ARRAY_SIZE(registers); i++) { + cfg = mipi_csis_read(csis, registers[i].offset); + dev_info(csis->dev, "%14s: 0x%08x\n", registers[i].name, cfg); + } + + pm_runtime_put(csis->dev); + + return 0; +} + +static int mipi_csis_dump_regs_show(struct seq_file *m, void *private) +{ + struct mipi_csis_device *csis = m->private; + + return mipi_csis_dump_regs(csis); +} +DEFINE_SHOW_ATTRIBUTE(mipi_csis_dump_regs); + +static void mipi_csis_debugfs_init(struct mipi_csis_device *csis) +{ + csis->debug.hs_settle = UINT_MAX; + csis->debug.clk_settle = UINT_MAX; + + csis->debugfs_root = debugfs_create_dir(dev_name(csis->dev), NULL); + + debugfs_create_bool("debug_enable", 0600, csis->debugfs_root, + &csis->debug.enable); + debugfs_create_file("dump_regs", 0600, csis->debugfs_root, csis, + &mipi_csis_dump_regs_fops); + debugfs_create_u32("tclk_settle", 0600, csis->debugfs_root, + &csis->debug.clk_settle); + debugfs_create_u32("ths_settle", 0600, csis->debugfs_root, + &csis->debug.hs_settle); +} + +static void mipi_csis_debugfs_exit(struct mipi_csis_device *csis) +{ + debugfs_remove_recursive(csis->debugfs_root); +} + +/* ----------------------------------------------------------------------------- + * V4L2 subdev operations + */ + +static struct mipi_csis_device *sd_to_mipi_csis_device(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct mipi_csis_device, sd); +} + +static int mipi_csis_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + int ret; + + if (!enable) { + mutex_lock(&csis->lock); + + v4l2_subdev_call(csis->src_sd, video, s_stream, 0); + + mipi_csis_stop_stream(csis); + if (csis->debug.enable) + mipi_csis_log_counters(csis, true); + + mutex_unlock(&csis->lock); + + pm_runtime_put(csis->dev); + + return 0; + } + + ret = mipi_csis_calculate_params(csis); + if (ret < 0) + return ret; + + mipi_csis_clear_counters(csis); + + ret = pm_runtime_resume_and_get(csis->dev); + if (ret < 0) + return ret; + + mutex_lock(&csis->lock); + + mipi_csis_start_stream(csis); + ret = v4l2_subdev_call(csis->src_sd, video, s_stream, 1); + if (ret < 0) + goto error; + + mipi_csis_log_counters(csis, true); + + mutex_unlock(&csis->lock); + + return 0; + +error: + mipi_csis_stop_stream(csis); + mutex_unlock(&csis->lock); + pm_runtime_put(csis->dev); + + return ret; +} + +static struct v4l2_mbus_framefmt * +mipi_csis_get_format(struct mipi_csis_device *csis, + struct v4l2_subdev_state *sd_state, + enum v4l2_subdev_format_whence which, + unsigned int pad) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&csis->sd, sd_state, pad); + + return &csis->format_mbus[pad]; +} + +static int mipi_csis_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + struct v4l2_mbus_framefmt *fmt_sink; + struct v4l2_mbus_framefmt *fmt_source; + enum v4l2_subdev_format_whence which; + + which = sd_state ? V4L2_SUBDEV_FORMAT_TRY : V4L2_SUBDEV_FORMAT_ACTIVE; + fmt_sink = mipi_csis_get_format(csis, sd_state, which, CSIS_PAD_SINK); + + fmt_sink->code = MEDIA_BUS_FMT_UYVY8_1X16; + fmt_sink->width = MIPI_CSIS_DEF_PIX_WIDTH; + fmt_sink->height = MIPI_CSIS_DEF_PIX_HEIGHT; + fmt_sink->field = V4L2_FIELD_NONE; + + fmt_sink->colorspace = V4L2_COLORSPACE_SMPTE170M; + fmt_sink->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt_sink->colorspace); + fmt_sink->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt_sink->colorspace); + fmt_sink->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, fmt_sink->colorspace, + fmt_sink->ycbcr_enc); + + fmt_source = mipi_csis_get_format(csis, sd_state, which, + CSIS_PAD_SOURCE); + *fmt_source = *fmt_sink; + + return 0; +} + +static int mipi_csis_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + struct v4l2_mbus_framefmt *fmt; + + fmt = mipi_csis_get_format(csis, sd_state, sdformat->which, + sdformat->pad); + + mutex_lock(&csis->lock); + sdformat->format = *fmt; + mutex_unlock(&csis->lock); + + return 0; +} + +static int mipi_csis_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + + /* + * The CSIS can't transcode in any way, the source format is identical + * to the sink format. + */ + if (code->pad == CSIS_PAD_SOURCE) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) + return -EINVAL; + + fmt = mipi_csis_get_format(csis, sd_state, code->which, + code->pad); + code->code = fmt->code; + return 0; + } + + if (code->pad != CSIS_PAD_SINK) + return -EINVAL; + + if (code->index >= ARRAY_SIZE(mipi_csis_formats)) + return -EINVAL; + + code->code = mipi_csis_formats[code->index].code; + + return 0; +} + +static int mipi_csis_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *sdformat) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + struct csis_pix_format const *csis_fmt; + struct v4l2_mbus_framefmt *fmt; + unsigned int align; + + /* + * The CSIS can't transcode in any way, the source format can't be + * modified. + */ + if (sdformat->pad == CSIS_PAD_SOURCE) + return mipi_csis_get_fmt(sd, sd_state, sdformat); + + if (sdformat->pad != CSIS_PAD_SINK) + return -EINVAL; + + /* + * Validate the media bus code and clamp and align the size. + * + * The total number of bits per line must be a multiple of 8. We thus + * need to align the width for formats that are not multiples of 8 + * bits. + */ + csis_fmt = find_csis_format(sdformat->format.code); + if (!csis_fmt) + csis_fmt = &mipi_csis_formats[0]; + + switch (csis_fmt->width % 8) { + case 0: + align = 0; + break; + case 4: + align = 1; + break; + case 2: + case 6: + align = 2; + break; + default: + /* 1, 3, 5, 7 */ + align = 3; + break; + } + + v4l_bound_align_image(&sdformat->format.width, 1, + CSIS_MAX_PIX_WIDTH, align, + &sdformat->format.height, 1, + CSIS_MAX_PIX_HEIGHT, 0, 0); + + fmt = mipi_csis_get_format(csis, sd_state, sdformat->which, + sdformat->pad); + + mutex_lock(&csis->lock); + + fmt->code = csis_fmt->code; + fmt->width = sdformat->format.width; + fmt->height = sdformat->format.height; + fmt->colorspace = sdformat->format.colorspace; + fmt->quantization = sdformat->format.quantization; + fmt->xfer_func = sdformat->format.xfer_func; + fmt->ycbcr_enc = sdformat->format.ycbcr_enc; + + sdformat->format = *fmt; + + /* Propagate the format from sink to source. */ + fmt = mipi_csis_get_format(csis, sd_state, sdformat->which, + CSIS_PAD_SOURCE); + *fmt = sdformat->format; + + /* The format on the source pad might change due to unpacking. */ + fmt->code = csis_fmt->output; + + /* Store the CSIS format descriptor for active formats. */ + if (sdformat->which == V4L2_SUBDEV_FORMAT_ACTIVE) + csis->csis_fmt = csis_fmt; + + mutex_unlock(&csis->lock); + + return 0; +} + +static int mipi_csis_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad, + struct v4l2_mbus_frame_desc *fd) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + struct v4l2_mbus_frame_desc_entry *entry = &fd->entry[0]; + + if (pad != CSIS_PAD_SOURCE) + return -EINVAL; + + fd->type = V4L2_MBUS_FRAME_DESC_TYPE_PARALLEL; + fd->num_entries = 1; + + memset(entry, 0, sizeof(*entry)); + + mutex_lock(&csis->lock); + + entry->flags = 0; + entry->pixelcode = csis->csis_fmt->code; + entry->bus.csi2.vc = 0; + entry->bus.csi2.dt = csis->csis_fmt->data_type; + + mutex_unlock(&csis->lock); + + return 0; +} + +static int mipi_csis_log_status(struct v4l2_subdev *sd) +{ + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + + mipi_csis_log_counters(csis, true); + if (csis->debug.enable) + mipi_csis_dump_regs(csis); + + return 0; +} + +static const struct v4l2_subdev_core_ops mipi_csis_core_ops = { + .log_status = mipi_csis_log_status, +}; + +static const struct v4l2_subdev_video_ops mipi_csis_video_ops = { + .s_stream = mipi_csis_s_stream, +}; + +static const struct v4l2_subdev_pad_ops mipi_csis_pad_ops = { + .init_cfg = mipi_csis_init_cfg, + .enum_mbus_code = mipi_csis_enum_mbus_code, + .get_fmt = mipi_csis_get_fmt, + .set_fmt = mipi_csis_set_fmt, + .get_frame_desc = mipi_csis_get_frame_desc, +}; + +static const struct v4l2_subdev_ops mipi_csis_subdev_ops = { + .core = &mipi_csis_core_ops, + .video = &mipi_csis_video_ops, + .pad = &mipi_csis_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Media entity operations + */ + +static int mipi_csis_link_setup(struct media_entity *entity, + const struct media_pad *local_pad, + const struct media_pad *remote_pad, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + struct v4l2_subdev *remote_sd; + + dev_dbg(csis->dev, "link setup %s -> %s", remote_pad->entity->name, + local_pad->entity->name); + + /* We only care about the link to the source. */ + if (!(local_pad->flags & MEDIA_PAD_FL_SINK)) + return 0; + + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (csis->src_sd) + return -EBUSY; + + csis->src_sd = remote_sd; + } else { + csis->src_sd = NULL; + } + + return 0; +} + +static const struct media_entity_operations mipi_csis_entity_ops = { + .link_setup = mipi_csis_link_setup, + .link_validate = v4l2_subdev_link_validate, + .get_fwnode_pad = v4l2_subdev_get_fwnode_pad_1_to_1, +}; + +/* ----------------------------------------------------------------------------- + * Async subdev notifier + */ + +static struct mipi_csis_device * +mipi_notifier_to_csis_state(struct v4l2_async_notifier *n) +{ + return container_of(n, struct mipi_csis_device, notifier); +} + +static int mipi_csis_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct mipi_csis_device *csis = mipi_notifier_to_csis_state(notifier); + struct media_pad *sink = &csis->sd.entity.pads[CSIS_PAD_SINK]; + + return v4l2_create_fwnode_links_to_pad(sd, sink, 0); +} + +static const struct v4l2_async_notifier_operations mipi_csis_notify_ops = { + .bound = mipi_csis_notify_bound, +}; + +static int mipi_csis_async_register(struct mipi_csis_device *csis) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep; + unsigned int i; + int ret; + + v4l2_async_nf_init(&csis->notifier); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csis->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -ENOTCONN; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_parse; + + for (i = 0; i < vep.bus.mipi_csi2.num_data_lanes; ++i) { + if (vep.bus.mipi_csi2.data_lanes[i] != i + 1) { + dev_err(csis->dev, + "data lanes reordering is not supported"); + ret = -EINVAL; + goto err_parse; + } + } + + csis->bus = vep.bus.mipi_csi2; + + dev_dbg(csis->dev, "data lanes: %d\n", csis->bus.num_data_lanes); + dev_dbg(csis->dev, "flags: 0x%08x\n", csis->bus.flags); + + asd = v4l2_async_nf_add_fwnode_remote(&csis->notifier, ep, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto err_parse; + } + + fwnode_handle_put(ep); + + csis->notifier.ops = &mipi_csis_notify_ops; + + ret = v4l2_async_subdev_nf_register(&csis->sd, &csis->notifier); + if (ret) + return ret; + + return v4l2_async_register_subdev(&csis->sd); + +err_parse: + fwnode_handle_put(ep); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Suspend/resume + */ + +static int __maybe_unused mipi_csis_runtime_suspend(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + int ret = 0; + + mutex_lock(&csis->lock); + + ret = mipi_csis_phy_disable(csis); + if (ret) + goto unlock; + + mipi_csis_clk_disable(csis); + +unlock: + mutex_unlock(&csis->lock); + + return ret ? -EAGAIN : 0; +} + +static int __maybe_unused mipi_csis_runtime_resume(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + int ret = 0; + + mutex_lock(&csis->lock); + + ret = mipi_csis_phy_enable(csis); + if (ret) + goto unlock; + + mipi_csis_clk_enable(csis); + +unlock: + mutex_unlock(&csis->lock); + + return ret ? -EAGAIN : 0; +} + +static const struct dev_pm_ops mipi_csis_pm_ops = { + SET_RUNTIME_PM_OPS(mipi_csis_runtime_suspend, mipi_csis_runtime_resume, + NULL) +}; + +/* ----------------------------------------------------------------------------- + * Probe/remove & platform driver + */ + +static int mipi_csis_subdev_init(struct mipi_csis_device *csis) +{ + struct v4l2_subdev *sd = &csis->sd; + + v4l2_subdev_init(sd, &mipi_csis_subdev_ops); + sd->owner = THIS_MODULE; + snprintf(sd->name, sizeof(sd->name), "csis-%s", + dev_name(csis->dev)); + + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->ctrl_handler = NULL; + + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->entity.ops = &mipi_csis_entity_ops; + + sd->dev = csis->dev; + + sd->fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(csis->dev), + 1, 0, 0); + if (!sd->fwnode) { + dev_err(csis->dev, "Unable to retrieve endpoint for port@1\n"); + return -ENOENT; + } + + csis->csis_fmt = &mipi_csis_formats[0]; + mipi_csis_init_cfg(sd, NULL); + + csis->pads[CSIS_PAD_SINK].flags = MEDIA_PAD_FL_SINK + | MEDIA_PAD_FL_MUST_CONNECT; + csis->pads[CSIS_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE + | MEDIA_PAD_FL_MUST_CONNECT; + return media_entity_pads_init(&sd->entity, CSIS_PADS_NUM, + csis->pads); +} + +static int mipi_csis_parse_dt(struct mipi_csis_device *csis) +{ + struct device_node *node = csis->dev->of_node; + + if (of_property_read_u32(node, "clock-frequency", + &csis->clk_frequency)) + csis->clk_frequency = DEFAULT_SCLK_CSIS_FREQ; + + return 0; +} + +static int mipi_csis_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mipi_csis_device *csis; + int irq; + int ret; + + csis = devm_kzalloc(dev, sizeof(*csis), GFP_KERNEL); + if (!csis) + return -ENOMEM; + + mutex_init(&csis->lock); + spin_lock_init(&csis->slock); + + csis->dev = dev; + csis->info = of_device_get_match_data(dev); + + memcpy(csis->events, mipi_csis_events, sizeof(csis->events)); + + /* Parse DT properties. */ + ret = mipi_csis_parse_dt(csis); + if (ret < 0) { + dev_err(dev, "Failed to parse device tree: %d\n", ret); + return ret; + } + + /* Acquire resources. */ + csis->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csis->regs)) + return PTR_ERR(csis->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = mipi_csis_phy_init(csis); + if (ret < 0) + return ret; + + ret = mipi_csis_clk_get(csis); + if (ret < 0) + return ret; + + /* Reset PHY and enable the clocks. */ + mipi_csis_phy_reset(csis); + + ret = mipi_csis_clk_enable(csis); + if (ret < 0) { + dev_err(csis->dev, "failed to enable clocks: %d\n", ret); + return ret; + } + + /* Now that the hardware is initialized, request the interrupt. */ + ret = devm_request_irq(dev, irq, mipi_csis_irq_handler, 0, + dev_name(dev), csis); + if (ret) { + dev_err(dev, "Interrupt request failed\n"); + goto disable_clock; + } + + /* Initialize and register the subdev. */ + ret = mipi_csis_subdev_init(csis); + if (ret < 0) + goto disable_clock; + + platform_set_drvdata(pdev, &csis->sd); + + ret = mipi_csis_async_register(csis); + if (ret < 0) { + dev_err(dev, "async register failed: %d\n", ret); + goto cleanup; + } + + /* Initialize debugfs. */ + mipi_csis_debugfs_init(csis); + + /* Enable runtime PM. */ + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = mipi_csis_runtime_resume(dev); + if (ret < 0) + goto unregister_all; + } + + dev_info(dev, "lanes: %d, freq: %u\n", + csis->bus.num_data_lanes, csis->clk_frequency); + + return 0; + +unregister_all: + mipi_csis_debugfs_exit(csis); +cleanup: + media_entity_cleanup(&csis->sd.entity); + v4l2_async_nf_unregister(&csis->notifier); + v4l2_async_nf_cleanup(&csis->notifier); + v4l2_async_unregister_subdev(&csis->sd); +disable_clock: + mipi_csis_clk_disable(csis); + fwnode_handle_put(csis->sd.fwnode); + mutex_destroy(&csis->lock); + + return ret; +} + +static int mipi_csis_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct mipi_csis_device *csis = sd_to_mipi_csis_device(sd); + + mipi_csis_debugfs_exit(csis); + v4l2_async_nf_unregister(&csis->notifier); + v4l2_async_nf_cleanup(&csis->notifier); + v4l2_async_unregister_subdev(&csis->sd); + + if (!pm_runtime_enabled(&pdev->dev)) + mipi_csis_runtime_suspend(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + mipi_csis_clk_disable(csis); + media_entity_cleanup(&csis->sd.entity); + fwnode_handle_put(csis->sd.fwnode); + mutex_destroy(&csis->lock); + pm_runtime_set_suspended(&pdev->dev); + + return 0; +} + +static const struct of_device_id mipi_csis_of_match[] = { + { + .compatible = "fsl,imx7-mipi-csi2", + .data = &(const struct mipi_csis_info){ + .version = MIPI_CSIS_V3_3, + .num_clocks = 3, + }, + }, { + .compatible = "fsl,imx8mm-mipi-csi2", + .data = &(const struct mipi_csis_info){ + .version = MIPI_CSIS_V3_6_3, + .num_clocks = 4, + }, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, mipi_csis_of_match); + +static struct platform_driver mipi_csis_driver = { + .probe = mipi_csis_probe, + .remove = mipi_csis_remove, + .driver = { + .of_match_table = mipi_csis_of_match, + .name = CSIS_DRIVER_NAME, + .pm = &mipi_csis_pm_ops, + }, +}; + +module_platform_driver(mipi_csis_driver); + +MODULE_DESCRIPTION("i.MX7 & i.MX8 MIPI CSI-2 receiver driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:imx-mipi-csi2"); diff --git a/drivers/media/platform/nxp/imx-pxp.c b/drivers/media/platform/nxp/imx-pxp.c new file mode 100644 index 000000000..689ae5e6a --- /dev/null +++ b/drivers/media/platform/nxp/imx-pxp.c @@ -0,0 +1,1788 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * i.MX Pixel Pipeline (PXP) mem-to-mem scaler/CSC/rotator driver + * + * Copyright (c) 2018 Pengutronix, Philipp Zabel + * + * based on vim2m + * + * Copyright (c) 2009-2010 Samsung Electronics Co., Ltd. + * Pawel Osciak, + * Marek Szyprowski, + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "imx-pxp.h" + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "activates debug info"); + +#define MIN_W 8 +#define MIN_H 8 +#define MAX_W 4096 +#define MAX_H 4096 +#define ALIGN_W 3 /* 8x8 pixel blocks */ +#define ALIGN_H 3 + +/* Flags that indicate a format can be used for capture/output */ +#define MEM2MEM_CAPTURE (1 << 0) +#define MEM2MEM_OUTPUT (1 << 1) + +#define MEM2MEM_NAME "pxp" + +/* Flags that indicate processing mode */ +#define MEM2MEM_HFLIP (1 << 0) +#define MEM2MEM_VFLIP (1 << 1) + +#define dprintk(dev, fmt, arg...) \ + v4l2_dbg(1, debug, &dev->v4l2_dev, "%s: " fmt, __func__, ## arg) + +struct pxp_fmt { + u32 fourcc; + int depth; + /* Types the format can be used for */ + u32 types; +}; + +static struct pxp_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_XBGR32, + .depth = 32, + /* Both capture and output format */ + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_ABGR32, + .depth = 32, + /* Capture-only format */ + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .depth = 24, + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_RGB444, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_VUYA32, + .depth = 32, + .types = MEM2MEM_CAPTURE, + }, { + .fourcc = V4L2_PIX_FMT_VUYX32, + .depth = 32, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + /* Output-only format */ + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_Y4, + .depth = 4, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 12, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .depth = 16, + .types = MEM2MEM_CAPTURE | MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .types = MEM2MEM_OUTPUT, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .types = MEM2MEM_OUTPUT, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +/* Per-queue, driver-specific private data */ +struct pxp_q_data { + unsigned int width; + unsigned int height; + unsigned int bytesperline; + unsigned int sizeimage; + unsigned int sequence; + struct pxp_fmt *fmt; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quant; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +static struct pxp_fmt *find_format(struct v4l2_format *f) +{ + struct pxp_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct pxp_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; + + struct clk *clk; + void __iomem *mmio; + + atomic_t num_inst; + struct mutex dev_mutex; + spinlock_t irqlock; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct pxp_ctx { + struct v4l2_fh fh; + struct pxp_dev *dev; + + struct v4l2_ctrl_handler hdl; + + /* Abort requested by m2m */ + int aborting; + + /* Processing mode */ + int mode; + u8 alpha_component; + u8 rotation; + + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + + /* Source and destination queue data */ + struct pxp_q_data q_data[2]; +}; + +static inline struct pxp_ctx *file2ctx(struct file *file) +{ + return container_of(file->private_data, struct pxp_ctx, fh); +} + +static struct pxp_q_data *get_q_data(struct pxp_ctx *ctx, + enum v4l2_buf_type type) +{ + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + return &ctx->q_data[V4L2_M2M_SRC]; + else + return &ctx->q_data[V4L2_M2M_DST]; +} + +static u32 pxp_v4l2_pix_fmt_to_ps_format(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_XBGR32: return BV_PXP_PS_CTRL_FORMAT__RGB888; + case V4L2_PIX_FMT_RGB555: return BV_PXP_PS_CTRL_FORMAT__RGB555; + case V4L2_PIX_FMT_RGB444: return BV_PXP_PS_CTRL_FORMAT__RGB444; + case V4L2_PIX_FMT_RGB565: return BV_PXP_PS_CTRL_FORMAT__RGB565; + case V4L2_PIX_FMT_VUYX32: return BV_PXP_PS_CTRL_FORMAT__YUV1P444; + case V4L2_PIX_FMT_UYVY: return BV_PXP_PS_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_YUYV: return BM_PXP_PS_CTRL_WB_SWAP | + BV_PXP_PS_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_VYUY: return BV_PXP_PS_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_YVYU: return BM_PXP_PS_CTRL_WB_SWAP | + BV_PXP_PS_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_GREY: return BV_PXP_PS_CTRL_FORMAT__Y8; + default: + case V4L2_PIX_FMT_Y4: return BV_PXP_PS_CTRL_FORMAT__Y4; + case V4L2_PIX_FMT_NV16: return BV_PXP_PS_CTRL_FORMAT__YUV2P422; + case V4L2_PIX_FMT_NV12: return BV_PXP_PS_CTRL_FORMAT__YUV2P420; + case V4L2_PIX_FMT_NV21: return BV_PXP_PS_CTRL_FORMAT__YVU2P420; + case V4L2_PIX_FMT_NV61: return BV_PXP_PS_CTRL_FORMAT__YVU2P422; + case V4L2_PIX_FMT_YUV422P: return BV_PXP_PS_CTRL_FORMAT__YUV422; + case V4L2_PIX_FMT_YUV420: return BV_PXP_PS_CTRL_FORMAT__YUV420; + } +} + +static u32 pxp_v4l2_pix_fmt_to_out_format(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_XBGR32: return BV_PXP_OUT_CTRL_FORMAT__RGB888; + case V4L2_PIX_FMT_ABGR32: return BV_PXP_OUT_CTRL_FORMAT__ARGB8888; + case V4L2_PIX_FMT_BGR24: return BV_PXP_OUT_CTRL_FORMAT__RGB888P; + /* Missing V4L2 pixel formats for ARGB1555 and ARGB4444 */ + case V4L2_PIX_FMT_RGB555: return BV_PXP_OUT_CTRL_FORMAT__RGB555; + case V4L2_PIX_FMT_RGB444: return BV_PXP_OUT_CTRL_FORMAT__RGB444; + case V4L2_PIX_FMT_RGB565: return BV_PXP_OUT_CTRL_FORMAT__RGB565; + case V4L2_PIX_FMT_VUYA32: + case V4L2_PIX_FMT_VUYX32: return BV_PXP_OUT_CTRL_FORMAT__YUV1P444; + case V4L2_PIX_FMT_UYVY: return BV_PXP_OUT_CTRL_FORMAT__UYVY1P422; + case V4L2_PIX_FMT_VYUY: return BV_PXP_OUT_CTRL_FORMAT__VYUY1P422; + case V4L2_PIX_FMT_GREY: return BV_PXP_OUT_CTRL_FORMAT__Y8; + default: + case V4L2_PIX_FMT_Y4: return BV_PXP_OUT_CTRL_FORMAT__Y4; + case V4L2_PIX_FMT_NV16: return BV_PXP_OUT_CTRL_FORMAT__YUV2P422; + case V4L2_PIX_FMT_NV12: return BV_PXP_OUT_CTRL_FORMAT__YUV2P420; + case V4L2_PIX_FMT_NV61: return BV_PXP_OUT_CTRL_FORMAT__YVU2P422; + case V4L2_PIX_FMT_NV21: return BV_PXP_OUT_CTRL_FORMAT__YVU2P420; + } +} + +static bool pxp_v4l2_pix_fmt_is_yuv(u32 v4l2_pix_fmt) +{ + switch (v4l2_pix_fmt) { + case V4L2_PIX_FMT_VUYA32: + case V4L2_PIX_FMT_VUYX32: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y4: + return true; + default: + return false; + } +} + +static void pxp_setup_csc(struct pxp_ctx *ctx) +{ + struct pxp_dev *dev = ctx->dev; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + + if (pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) && + !pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_DST].fmt->fourcc)) { + /* + * CSC1 YUV/YCbCr to RGB conversion is implemented as follows: + * + * |R| |C0 0 C1| |Y + Yoffset | + * |G| = |C0 C3 C2| * |Cb + UVoffset| + * |B| |C0 C4 0 | |Cr + UVoffset| + * + * Results are clamped to 0..255. + * + * BT.601 limited range: + * + * |R| |1.1644 0.0000 1.5960| |Y - 16 | + * |G| = |1.1644 -0.3917 -0.8129| * |Cb - 128| + * |B| |1.1644 2.0172 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt601_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x198) | /* 1.5938 (-0.23 %) */ + BF_PXP_CSC1_COEF1_C4(0x204), /* 2.0156 (-0.16 %) */ + BF_PXP_CSC1_COEF2_C2(0x730) | /* -0.8125 (+0.04 %) */ + BF_PXP_CSC1_COEF2_C3(0x79c), /* -0.3906 (+0.11 %) */ + }; + /* + * BT.601 full range: + * + * |R| |1.0000 0.0000 1.4020| |Y + 0 | + * |G| = |1.0000 -0.3441 -0.7141| * |Cb - 128| + * |B| |1.0000 1.7720 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt601_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x166) | /* 1.3984 (-0.36 %) */ + BF_PXP_CSC1_COEF1_C4(0x1c5), /* 1.7695 (-0.25 %) */ + BF_PXP_CSC1_COEF2_C2(0x74a) | /* -0.7109 (+0.32 %) */ + BF_PXP_CSC1_COEF2_C3(0x7a8), /* -0.3438 (+0.04 %) */ + }; + /* + * Rec.709 limited range: + * + * |R| |1.1644 0.0000 1.7927| |Y - 16 | + * |G| = |1.1644 -0.2132 -0.5329| * |Cb - 128| + * |B| |1.1644 2.1124 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_rec709_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1ca) | /* 1.7891 (-0.37 %) */ + BF_PXP_CSC1_COEF1_C4(0x21c), /* 2.1094 (-0.30 %) */ + BF_PXP_CSC1_COEF2_C2(0x778) | /* -0.5312 (+0.16 %) */ + BF_PXP_CSC1_COEF2_C3(0x7ca), /* -0.2109 (+0.23 %) */ + }; + /* + * Rec.709 full range: + * + * |R| |1.0000 0.0000 1.5748| |Y + 0 | + * |G| = |1.0000 -0.1873 -0.4681| * |Cb - 128| + * |B| |1.0000 1.8556 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_rec709_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x193) | /* 1.5742 (-0.06 %) */ + BF_PXP_CSC1_COEF1_C4(0x1db), /* 1.8555 (-0.01 %) */ + BF_PXP_CSC1_COEF2_C2(0x789) | /* -0.4648 (+0.33 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d1), /* -0.1836 (+0.37 %) */ + }; + /* + * BT.2020 limited range: + * + * |R| |1.1644 0.0000 1.6787| |Y - 16 | + * |G| = |1.1644 -0.1874 -0.6505| * |Cb - 128| + * |B| |1.1644 2.1418 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt2020_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1ad) | /* 1.6758 (-0.29 %) */ + BF_PXP_CSC1_COEF1_C4(0x224), /* 2.1406 (-0.11 %) */ + BF_PXP_CSC1_COEF2_C2(0x75a) | /* -0.6484 (+0.20 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d1), /* -0.1836 (+0.38 %) */ + }; + /* + * BT.2020 full range: + * + * |R| |1.0000 0.0000 1.4746| |Y + 0 | + * |G| = |1.0000 -0.1646 -0.5714| * |Cb - 128| + * |B| |1.0000 1.8814 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_bt2020_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x179) | /* 1.4727 (-0.19 %) */ + BF_PXP_CSC1_COEF1_C4(0x1e1), /* 1.8789 (-0.25 %) */ + BF_PXP_CSC1_COEF2_C2(0x76e) | /* -0.5703 (+0.11 %) */ + BF_PXP_CSC1_COEF2_C3(0x7d6), /* -0.1641 (+0.05 %) */ + }; + /* + * SMPTE 240m limited range: + * + * |R| |1.1644 0.0000 1.7937| |Y - 16 | + * |G| = |1.1644 -0.2565 -0.5427| * |Cb - 128| + * |B| |1.1644 2.0798 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_smpte240m_lim[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x12a) | /* 1.1641 (-0.03 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(-16), + BF_PXP_CSC1_COEF1_C1(0x1cb) | /* 1.7930 (-0.07 %) */ + BF_PXP_CSC1_COEF1_C4(0x214), /* 2.0781 (-0.17 %) */ + BF_PXP_CSC1_COEF2_C2(0x776) | /* -0.5391 (+0.36 %) */ + BF_PXP_CSC1_COEF2_C3(0x7bf), /* -0.2539 (+0.26 %) */ + }; + /* + * SMPTE 240m full range: + * + * |R| |1.0000 0.0000 1.5756| |Y + 0 | + * |G| = |1.0000 -0.2253 -0.4767| * |Cb - 128| + * |B| |1.0000 1.8270 0.0000| |Cr - 128| + */ + static const u32 csc1_coef_smpte240m_full[3] = { + BM_PXP_CSC1_COEF0_YCBCR_MODE | + BF_PXP_CSC1_COEF0_C0(0x100) | /* 1.0000 (+0.00 %) */ + BF_PXP_CSC1_COEF0_UV_OFFSET(-128) | + BF_PXP_CSC1_COEF0_Y_OFFSET(0), + BF_PXP_CSC1_COEF1_C1(0x193) | /* 1.5742 (-0.14 %) */ + BF_PXP_CSC1_COEF1_C4(0x1d3), /* 1.8242 (-0.28 %) */ + BF_PXP_CSC1_COEF2_C2(0x786) | /* -0.4766 (+0.01 %) */ + BF_PXP_CSC1_COEF2_C3(0x7c7), /* -0.2227 (+0.26 %) */ + }; + const u32 *csc1_coef; + + ycbcr_enc = ctx->q_data[V4L2_M2M_SRC].ycbcr_enc; + quantization = ctx->q_data[V4L2_M2M_SRC].quant; + + if (ycbcr_enc == V4L2_YCBCR_ENC_601) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_bt601_full; + else + csc1_coef = csc1_coef_bt601_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_709) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_rec709_full; + else + csc1_coef = csc1_coef_rec709_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_BT2020) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_bt2020_full; + else + csc1_coef = csc1_coef_bt2020_lim; + } else { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc1_coef = csc1_coef_smpte240m_full; + else + csc1_coef = csc1_coef_smpte240m_lim; + } + + writel(csc1_coef[0], dev->mmio + HW_PXP_CSC1_COEF0); + writel(csc1_coef[1], dev->mmio + HW_PXP_CSC1_COEF1); + writel(csc1_coef[2], dev->mmio + HW_PXP_CSC1_COEF2); + } else { + writel(BM_PXP_CSC1_COEF0_BYPASS, dev->mmio + HW_PXP_CSC1_COEF0); + } + + if (!pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) && + pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_DST].fmt->fourcc)) { + /* + * CSC2 RGB to YUV/YCbCr conversion is implemented as follows: + * + * |Y | |A1 A2 A3| |R| |D1| + * |Cb| = |B1 B2 B3| * |G| + |D2| + * |Cr| |C1 C2 C3| |B| |D3| + * + * Results are clamped to 0..255. + * + * BT.601 limited range: + * + * |Y | | 0.2568 0.5041 0.0979| |R| |16 | + * |Cb| = |-0.1482 -0.2910 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3678| |B| |128| + */ + static const u32 csc2_coef_bt601_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x081) | /* 0.5039 (-0.02 %) */ + BF_PXP_CSC2_COEF0_A1(0x041), /* 0.2539 (-0.29 %) */ + BF_PXP_CSC2_COEF1_B1(0x7db) | /* -0.1445 (+0.37 %) */ + BF_PXP_CSC2_COEF1_A3(0x019), /* 0.0977 (-0.02 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7b6), /* -0.2891 (+0.20 %) */ + BF_PXP_CSC2_COEF3_C2(0x7a2) | /* -0.3672 (+0.06 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7ee), /* -0.0703 (+0.11 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.601 full range: + * + * |Y | | 0.2990 0.5870 0.1140| |R| |0 | + * |Cb| = |-0.1687 -0.3313 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4187| |B| |128| + */ + static const u32 csc2_coef_bt601_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x096) | /* 0.5859 (-0.11 %) */ + BF_PXP_CSC2_COEF0_A1(0x04c), /* 0.2969 (-0.21 %) */ + BF_PXP_CSC2_COEF1_B1(0x7d5) | /* -0.1680 (+0.07 %) */ + BF_PXP_CSC2_COEF1_A3(0x01d), /* 0.1133 (-0.07 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x7ac), /* -0.3281 (+0.32 %) */ + BF_PXP_CSC2_COEF3_C2(0x795) | /* -0.4180 (+0.07 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7ec), /* -0.0781 (+0.32 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * Rec.709 limited range: + * + * |Y | | 0.1826 0.6142 0.0620| |R| |16 | + * |Cb| = |-0.1007 -0.3385 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3990| |B| |128| + */ + static const u32 csc2_coef_rec709_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x09d) | /* 0.6133 (-0.09 %) */ + BF_PXP_CSC2_COEF0_A1(0x02e), /* 0.1797 (-0.29 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e7) | /* -0.0977 (+0.30 %) */ + BF_PXP_CSC2_COEF1_A3(0x00f), /* 0.0586 (-0.34 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7aa), /* -0.3359 (+0.26 %) */ + BF_PXP_CSC2_COEF3_C2(0x79a) | /* -0.3984 (+0.05 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f6), /* -0.0391 (+0.12 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * Rec.709 full range: + * + * |Y | | 0.2126 0.7152 0.0722| |R| |0 | + * |Cb| = |-0.1146 -0.3854 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4542| |B| |128| + */ + static const u32 csc2_coef_rec709_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0b7) | /* 0.7148 (-0.04 %) */ + BF_PXP_CSC2_COEF0_A1(0x036), /* 0.2109 (-0.17 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e3) | /* -0.1133 (+0.13 %) */ + BF_PXP_CSC2_COEF1_A3(0x012), /* 0.0703 (-0.19 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x79e), /* -0.3828 (+0.26 %) */ + BF_PXP_CSC2_COEF3_C2(0x78c) | /* -0.4531 (+0.11 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f5), /* -0.0430 (+0.28 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.2020 limited range: + * + * |Y | | 0.2256 0.5823 0.0509| |R| |16 | + * |Cb| = |-0.1226 -0.3166 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.4039| |B| |128| + */ + static const u32 csc2_coef_bt2020_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x095) | /* 0.5820 (-0.03 %) */ + BF_PXP_CSC2_COEF0_A1(0x039), /* 0.2227 (-0.30 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e1) | /* -0.1211 (+0.15 %) */ + BF_PXP_CSC2_COEF1_A3(0x00d), /* 0.0508 (-0.01 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7af), /* -0.3164 (+0.02 %) */ + BF_PXP_CSC2_COEF3_C2(0x799) | /* -0.4023 (+0.16 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f7), /* -0.0352 (+0.02 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * BT.2020 full range: + * + * |Y | | 0.2627 0.6780 0.0593| |R| |0 | + * |Cb| = |-0.1396 -0.3604 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4598| |B| |128| + */ + static const u32 csc2_coef_bt2020_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0ad) | /* 0.6758 (-0.22 %) */ + BF_PXP_CSC2_COEF0_A1(0x043), /* 0.2617 (-0.10 %) */ + BF_PXP_CSC2_COEF1_B1(0x7dd) | /* -0.1367 (+0.29 %) */ + BF_PXP_CSC2_COEF1_A3(0x00f), /* 0.0586 (-0.07 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x7a4), /* -0.3594 (+0.10 %) */ + BF_PXP_CSC2_COEF3_C2(0x78b) | /* -0.4570 (+0.28 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f6), /* -0.0391 (+0.11 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * SMPTE 240m limited range: + * + * |Y | | 0.1821 0.6020 0.0747| |R| |16 | + * |Cb| = |-0.1019 -0.3373 0.4392| * |G| + |128| + * |Cr| | 0.4392 0.4392 -0.3909| |B| |128| + */ + static const u32 csc2_coef_smpte240m_lim[6] = { + BF_PXP_CSC2_COEF0_A2(0x09a) | /* 0.6016 (-0.05 %) */ + BF_PXP_CSC2_COEF0_A1(0x02e), /* 0.1797 (-0.24 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e6) | /* -0.1016 (+0.03 %) */ + BF_PXP_CSC2_COEF1_A3(0x013), /* 0.0742 (-0.05 %) */ + BF_PXP_CSC2_COEF2_B3(0x070) | /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF2_B2(0x7aa), /* -0.3359 (+0.14 %) */ + BF_PXP_CSC2_COEF3_C2(0x79c) | /* -0.3906 (+0.03 %) */ + BF_PXP_CSC2_COEF3_C1(0x070), /* 0.4375 (-0.17 %) */ + BF_PXP_CSC2_COEF4_D1(16) | + BF_PXP_CSC2_COEF4_C3(0x7f4), /* -0.0469 (+0.14 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + /* + * SMPTE 240m full range: + * + * |Y | | 0.2120 0.7010 0.0870| |R| |0 | + * |Cb| = |-0.1160 -0.3840 0.5000| * |G| + |128| + * |Cr| | 0.5000 0.5000 -0.4450| |B| |128| + */ + static const u32 csc2_coef_smpte240m_full[6] = { + BF_PXP_CSC2_COEF0_A2(0x0b3) | /* 0.6992 (-0.18 %) */ + BF_PXP_CSC2_COEF0_A1(0x036), /* 0.2109 (-0.11 %) */ + BF_PXP_CSC2_COEF1_B1(0x7e3) | /* -0.1133 (+0.27 %) */ + BF_PXP_CSC2_COEF1_A3(0x016), /* 0.0859 (-0.11 %) */ + BF_PXP_CSC2_COEF2_B3(0x080) | /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF2_B2(0x79e), /* -0.3828 (+0.12 %) */ + BF_PXP_CSC2_COEF3_C2(0x78f) | /* -0.4414 (+0.36 %) */ + BF_PXP_CSC2_COEF3_C1(0x080), /* 0.5000 (+0.00 %) */ + BF_PXP_CSC2_COEF4_D1(0) | + BF_PXP_CSC2_COEF4_C3(0x7f2), /* -0.0547 (+0.03 %) */ + BF_PXP_CSC2_COEF5_D3(128) | + BF_PXP_CSC2_COEF5_D2(128), + }; + const u32 *csc2_coef; + u32 csc2_ctrl; + + ycbcr_enc = ctx->q_data[V4L2_M2M_DST].ycbcr_enc; + quantization = ctx->q_data[V4L2_M2M_DST].quant; + + if (ycbcr_enc == V4L2_YCBCR_ENC_601) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_bt601_full; + else + csc2_coef = csc2_coef_bt601_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_709) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_rec709_full; + else + csc2_coef = csc2_coef_rec709_lim; + } else if (ycbcr_enc == V4L2_YCBCR_ENC_BT2020) { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_bt2020_full; + else + csc2_coef = csc2_coef_bt2020_lim; + } else { + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) + csc2_coef = csc2_coef_smpte240m_full; + else + csc2_coef = csc2_coef_smpte240m_lim; + } + if (quantization == V4L2_QUANTIZATION_FULL_RANGE) { + csc2_ctrl = BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YUV << + BP_PXP_CSC2_CTRL_CSC_MODE; + } else { + csc2_ctrl = BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YCbCr << + BP_PXP_CSC2_CTRL_CSC_MODE; + } + + writel(csc2_ctrl, dev->mmio + HW_PXP_CSC2_CTRL); + writel(csc2_coef[0], dev->mmio + HW_PXP_CSC2_COEF0); + writel(csc2_coef[1], dev->mmio + HW_PXP_CSC2_COEF1); + writel(csc2_coef[2], dev->mmio + HW_PXP_CSC2_COEF2); + writel(csc2_coef[3], dev->mmio + HW_PXP_CSC2_COEF3); + writel(csc2_coef[4], dev->mmio + HW_PXP_CSC2_COEF4); + writel(csc2_coef[5], dev->mmio + HW_PXP_CSC2_COEF5); + } else { + writel(BM_PXP_CSC2_CTRL_BYPASS, dev->mmio + HW_PXP_CSC2_CTRL); + } +} + +static int pxp_start(struct pxp_ctx *ctx, struct vb2_v4l2_buffer *in_vb, + struct vb2_v4l2_buffer *out_vb) +{ + struct pxp_dev *dev = ctx->dev; + struct pxp_q_data *q_data; + u32 src_width, src_height, src_stride, src_fourcc; + u32 dst_width, dst_height, dst_stride, dst_fourcc; + dma_addr_t p_in, p_out; + u32 ctrl, out_ctrl, out_buf, out_buf2, out_pitch, out_lrc, out_ps_ulc; + u32 out_ps_lrc; + u32 ps_ctrl, ps_buf, ps_ubuf, ps_vbuf, ps_pitch, ps_scale, ps_offset; + u32 as_ulc, as_lrc; + u32 y_size; + u32 decx, decy, xscale, yscale; + + q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + + src_width = ctx->q_data[V4L2_M2M_SRC].width; + dst_width = ctx->q_data[V4L2_M2M_DST].width; + src_height = ctx->q_data[V4L2_M2M_SRC].height; + dst_height = ctx->q_data[V4L2_M2M_DST].height; + src_stride = ctx->q_data[V4L2_M2M_SRC].bytesperline; + dst_stride = ctx->q_data[V4L2_M2M_DST].bytesperline; + src_fourcc = ctx->q_data[V4L2_M2M_SRC].fmt->fourcc; + dst_fourcc = ctx->q_data[V4L2_M2M_DST].fmt->fourcc; + + p_in = vb2_dma_contig_plane_dma_addr(&in_vb->vb2_buf, 0); + p_out = vb2_dma_contig_plane_dma_addr(&out_vb->vb2_buf, 0); + + if (!p_in || !p_out) { + v4l2_err(&dev->v4l2_dev, + "Acquiring DMA addresses of buffers failed\n"); + return -EFAULT; + } + + out_vb->sequence = + get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE)->sequence++; + in_vb->sequence = q_data->sequence++; + out_vb->vb2_buf.timestamp = in_vb->vb2_buf.timestamp; + + if (in_vb->flags & V4L2_BUF_FLAG_TIMECODE) + out_vb->timecode = in_vb->timecode; + out_vb->field = in_vb->field; + out_vb->flags = in_vb->flags & + (V4L2_BUF_FLAG_TIMECODE | + V4L2_BUF_FLAG_KEYFRAME | + V4L2_BUF_FLAG_PFRAME | + V4L2_BUF_FLAG_BFRAME | + V4L2_BUF_FLAG_TSTAMP_SRC_MASK); + + /* 8x8 block size */ + ctrl = BF_PXP_CTRL_VFLIP0(!!(ctx->mode & MEM2MEM_VFLIP)) | + BF_PXP_CTRL_HFLIP0(!!(ctx->mode & MEM2MEM_HFLIP)) | + BF_PXP_CTRL_ROTATE0(ctx->rotation); + /* Always write alpha value as V4L2_CID_ALPHA_COMPONENT */ + out_ctrl = BF_PXP_OUT_CTRL_ALPHA(ctx->alpha_component) | + BF_PXP_OUT_CTRL_ALPHA_OUTPUT(1) | + pxp_v4l2_pix_fmt_to_out_format(dst_fourcc); + out_buf = p_out; + + if (ctx->rotation == BV_PXP_CTRL_ROTATE0__ROT_90 || + ctx->rotation == BV_PXP_CTRL_ROTATE0__ROT_270) + swap(dst_width, dst_height); + + switch (dst_fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + out_buf2 = out_buf + dst_stride * dst_height; + break; + default: + out_buf2 = 0; + } + + out_pitch = BF_PXP_OUT_PITCH_PITCH(dst_stride); + out_lrc = BF_PXP_OUT_LRC_X(dst_width - 1) | + BF_PXP_OUT_LRC_Y(dst_height - 1); + /* PS covers whole output */ + out_ps_ulc = BF_PXP_OUT_PS_ULC_X(0) | BF_PXP_OUT_PS_ULC_Y(0); + out_ps_lrc = BF_PXP_OUT_PS_LRC_X(dst_width - 1) | + BF_PXP_OUT_PS_LRC_Y(dst_height - 1); + /* no AS */ + as_ulc = BF_PXP_OUT_AS_ULC_X(1) | BF_PXP_OUT_AS_ULC_Y(1); + as_lrc = BF_PXP_OUT_AS_LRC_X(0) | BF_PXP_OUT_AS_LRC_Y(0); + + decx = (src_width <= dst_width) ? 0 : ilog2(src_width / dst_width); + decy = (src_height <= dst_height) ? 0 : ilog2(src_height / dst_height); + ps_ctrl = BF_PXP_PS_CTRL_DECX(decx) | BF_PXP_PS_CTRL_DECY(decy) | + pxp_v4l2_pix_fmt_to_ps_format(src_fourcc); + ps_buf = p_in; + y_size = src_stride * src_height; + switch (src_fourcc) { + case V4L2_PIX_FMT_YUV420: + ps_ubuf = ps_buf + y_size; + ps_vbuf = ps_ubuf + y_size / 4; + break; + case V4L2_PIX_FMT_YUV422P: + ps_ubuf = ps_buf + y_size; + ps_vbuf = ps_ubuf + y_size / 2; + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + ps_ubuf = ps_buf + y_size; + ps_vbuf = 0; + break; + case V4L2_PIX_FMT_GREY: + case V4L2_PIX_FMT_Y4: + ps_ubuf = 0; + /* In grayscale mode, ps_vbuf contents are reused as CbCr */ + ps_vbuf = 0x8080; + break; + default: + ps_ubuf = 0; + ps_vbuf = 0; + break; + } + ps_pitch = BF_PXP_PS_PITCH_PITCH(src_stride); + if (decx) { + xscale = (src_width >> decx) * 0x1000 / dst_width; + } else { + switch (src_fourcc) { + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_YUV420: + /* + * This avoids sampling past the right edge for + * horizontally chroma subsampled formats. + */ + xscale = (src_width - 2) * 0x1000 / (dst_width - 1); + break; + default: + xscale = (src_width - 1) * 0x1000 / (dst_width - 1); + break; + } + } + if (decy) + yscale = (src_height >> decy) * 0x1000 / dst_height; + else + yscale = (src_height - 1) * 0x1000 / (dst_height - 1); + ps_scale = BF_PXP_PS_SCALE_YSCALE(yscale) | + BF_PXP_PS_SCALE_XSCALE(xscale); + ps_offset = BF_PXP_PS_OFFSET_YOFFSET(0) | BF_PXP_PS_OFFSET_XOFFSET(0); + + writel(ctrl, dev->mmio + HW_PXP_CTRL); + /* skip STAT */ + writel(out_ctrl, dev->mmio + HW_PXP_OUT_CTRL); + writel(out_buf, dev->mmio + HW_PXP_OUT_BUF); + writel(out_buf2, dev->mmio + HW_PXP_OUT_BUF2); + writel(out_pitch, dev->mmio + HW_PXP_OUT_PITCH); + writel(out_lrc, dev->mmio + HW_PXP_OUT_LRC); + writel(out_ps_ulc, dev->mmio + HW_PXP_OUT_PS_ULC); + writel(out_ps_lrc, dev->mmio + HW_PXP_OUT_PS_LRC); + writel(as_ulc, dev->mmio + HW_PXP_OUT_AS_ULC); + writel(as_lrc, dev->mmio + HW_PXP_OUT_AS_LRC); + writel(ps_ctrl, dev->mmio + HW_PXP_PS_CTRL); + writel(ps_buf, dev->mmio + HW_PXP_PS_BUF); + writel(ps_ubuf, dev->mmio + HW_PXP_PS_UBUF); + writel(ps_vbuf, dev->mmio + HW_PXP_PS_VBUF); + writel(ps_pitch, dev->mmio + HW_PXP_PS_PITCH); + writel(0x00ffffff, dev->mmio + HW_PXP_PS_BACKGROUND_0); + writel(ps_scale, dev->mmio + HW_PXP_PS_SCALE); + writel(ps_offset, dev->mmio + HW_PXP_PS_OFFSET); + /* disable processed surface color keying */ + writel(0x00ffffff, dev->mmio + HW_PXP_PS_CLRKEYLOW_0); + writel(0x00000000, dev->mmio + HW_PXP_PS_CLRKEYHIGH_0); + + /* disable alpha surface color keying */ + writel(0x00ffffff, dev->mmio + HW_PXP_AS_CLRKEYLOW_0); + writel(0x00000000, dev->mmio + HW_PXP_AS_CLRKEYHIGH_0); + + /* setup CSC */ + pxp_setup_csc(ctx); + + /* bypass LUT */ + writel(BM_PXP_LUT_CTRL_BYPASS, dev->mmio + HW_PXP_LUT_CTRL); + + writel(BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(1)| + BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(1)| + BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(0)| + BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(0), + dev->mmio + HW_PXP_DATA_PATH_CTRL0); + writel(BF_PXP_DATA_PATH_CTRL1_MUX17_SEL(1) | + BF_PXP_DATA_PATH_CTRL1_MUX16_SEL(1), + dev->mmio + HW_PXP_DATA_PATH_CTRL1); + + writel(0xffff, dev->mmio + HW_PXP_IRQ_MASK); + + /* ungate, enable PS/AS/OUT and PXP operation */ + writel(BM_PXP_CTRL_IRQ_ENABLE, dev->mmio + HW_PXP_CTRL_SET); + writel(BM_PXP_CTRL_ENABLE | BM_PXP_CTRL_ENABLE_CSC2 | + BM_PXP_CTRL_ENABLE_LUT | BM_PXP_CTRL_ENABLE_ROTATE0 | + BM_PXP_CTRL_ENABLE_PS_AS_OUT, dev->mmio + HW_PXP_CTRL_SET); + + return 0; +} + +static void pxp_job_finish(struct pxp_dev *dev) +{ + struct pxp_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + unsigned long flags; + + curr_ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + + if (curr_ctx == NULL) { + pr_err("Instance released before the end of transaction\n"); + return; + } + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + spin_lock_irqsave(&dev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&dev->irqlock, flags); + + dprintk(curr_ctx->dev, "Finishing transaction\n"); + v4l2_m2m_job_finish(dev->m2m_dev, curr_ctx->fh.m2m_ctx); +} + +/* + * mem2mem callbacks + */ +static void pxp_device_run(void *priv) +{ + struct pxp_ctx *ctx = priv; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + pxp_start(ctx, src_buf, dst_buf); +} + +static int pxp_job_ready(void *priv) +{ + struct pxp_ctx *ctx = priv; + + if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) < 1 || + v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) < 1) { + dprintk(ctx->dev, "Not enough buffers available\n"); + return 0; + } + + return 1; +} + +static void pxp_job_abort(void *priv) +{ + struct pxp_ctx *ctx = priv; + + /* Will cancel the transaction in the next interrupt handler */ + ctx->aborting = 1; +} + +/* + * interrupt handler + */ +static irqreturn_t pxp_irq_handler(int irq, void *dev_id) +{ + struct pxp_dev *dev = dev_id; + u32 stat; + + stat = readl(dev->mmio + HW_PXP_STAT); + + if (stat & BM_PXP_STAT_IRQ0) { + /* we expect x = 0, y = height, irq0 = 1 */ + if (stat & ~(BM_PXP_STAT_BLOCKX | BM_PXP_STAT_BLOCKY | + BM_PXP_STAT_IRQ0)) + dprintk(dev, "%s: stat = 0x%08x\n", __func__, stat); + writel(BM_PXP_STAT_IRQ0, dev->mmio + HW_PXP_STAT_CLR); + + pxp_job_finish(dev); + } else { + u32 irq = readl(dev->mmio + HW_PXP_IRQ); + + dprintk(dev, "%s: stat = 0x%08x\n", __func__, stat); + dprintk(dev, "%s: irq = 0x%08x\n", __func__, irq); + + writel(irq, dev->mmio + HW_PXP_IRQ_CLR); + } + + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int pxp_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strscpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", MEM2MEM_NAME); + return 0; +} + +static int pxp_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct pxp_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* + * Correct type but haven't reached our index yet, + * just increment per-type index + */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int pxp_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return pxp_enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int pxp_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return pxp_enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int pxp_g_fmt(struct pxp_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct pxp_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + f->fmt.pix.bytesperline = q_data->bytesperline; + f->fmt.pix.sizeimage = q_data->sizeimage; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = q_data->ycbcr_enc; + f->fmt.pix.quantization = q_data->quant; + + return 0; +} + +static int pxp_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return pxp_g_fmt(file2ctx(file), f); +} + +static int pxp_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return pxp_g_fmt(file2ctx(file), f); +} + +static inline u32 pxp_bytesperline(struct pxp_fmt *fmt, u32 width) +{ + switch (fmt->fourcc) { + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + return width; + default: + return (width * fmt->depth) >> 3; + } +} + +static inline u32 pxp_sizeimage(struct pxp_fmt *fmt, u32 width, u32 height) +{ + return (fmt->depth * width * height) >> 3; +} + +static int pxp_try_fmt(struct v4l2_format *f, struct pxp_fmt *fmt) +{ + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, ALIGN_W, + &f->fmt.pix.height, MIN_H, MAX_H, ALIGN_H, 0); + + f->fmt.pix.bytesperline = pxp_bytesperline(fmt, f->fmt.pix.width); + f->fmt.pix.sizeimage = pxp_sizeimage(fmt, f->fmt.pix.width, + f->fmt.pix.height); + f->fmt.pix.field = V4L2_FIELD_NONE; + + return 0; +} + +static void +pxp_fixup_colorimetry_cap(struct pxp_ctx *ctx, u32 dst_fourcc, + enum v4l2_ycbcr_encoding *ycbcr_enc, + enum v4l2_quantization *quantization) +{ + bool dst_is_yuv = pxp_v4l2_pix_fmt_is_yuv(dst_fourcc); + + if (pxp_v4l2_pix_fmt_is_yuv(ctx->q_data[V4L2_M2M_SRC].fmt->fourcc) == + dst_is_yuv) { + /* + * There is no support for conversion between different YCbCr + * encodings or between RGB limited and full range. + */ + *ycbcr_enc = ctx->q_data[V4L2_M2M_SRC].ycbcr_enc; + *quantization = ctx->q_data[V4L2_M2M_SRC].quant; + } else { + *ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(ctx->colorspace); + *quantization = V4L2_MAP_QUANTIZATION_DEFAULT(!dst_is_yuv, + ctx->colorspace, + *ycbcr_enc); + } +} + +static int pxp_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_fmt *fmt; + struct pxp_ctx *ctx = file2ctx(file); + + fmt = find_format(f); + if (!fmt) { + f->fmt.pix.pixelformat = formats[0].fourcc; + fmt = find_format(f); + } + if (!(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + + pxp_fixup_colorimetry_cap(ctx, fmt->fourcc, + &f->fmt.pix.ycbcr_enc, + &f->fmt.pix.quantization); + + return pxp_try_fmt(f, fmt); +} + +static int pxp_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_fmt *fmt; + struct pxp_ctx *ctx = file2ctx(file); + + fmt = find_format(f); + if (!fmt) { + f->fmt.pix.pixelformat = formats[0].fourcc; + fmt = find_format(f); + } + if (!(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + if (!f->fmt.pix.colorspace) + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + return pxp_try_fmt(f, fmt); +} + +static int pxp_s_fmt(struct pxp_ctx *ctx, struct v4l2_format *f) +{ + struct pxp_q_data *q_data; + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + q_data->fmt = find_format(f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + q_data->bytesperline = f->fmt.pix.bytesperline; + q_data->sizeimage = f->fmt.pix.sizeimage; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int pxp_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_ctx *ctx = file2ctx(file); + int ret; + + ret = pxp_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + ret = pxp_s_fmt(file2ctx(file), f); + if (ret) + return ret; + + ctx->q_data[V4L2_M2M_DST].ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->q_data[V4L2_M2M_DST].quant = f->fmt.pix.quantization; + + return 0; +} + +static int pxp_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct pxp_ctx *ctx = file2ctx(file); + int ret; + + ret = pxp_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + ret = pxp_s_fmt(file2ctx(file), f); + if (ret) + return ret; + + ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->q_data[V4L2_M2M_SRC].ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->q_data[V4L2_M2M_SRC].quant = f->fmt.pix.quantization; + + pxp_fixup_colorimetry_cap(ctx, ctx->q_data[V4L2_M2M_DST].fmt->fourcc, + &ctx->q_data[V4L2_M2M_DST].ycbcr_enc, + &ctx->q_data[V4L2_M2M_DST].quant); + + return 0; +} + +static u8 pxp_degrees_to_rot_mode(u32 degrees) +{ + switch (degrees) { + case 90: + return BV_PXP_CTRL_ROTATE0__ROT_90; + case 180: + return BV_PXP_CTRL_ROTATE0__ROT_180; + case 270: + return BV_PXP_CTRL_ROTATE0__ROT_270; + case 0: + default: + return BV_PXP_CTRL_ROTATE0__ROT_0; + } +} + +static int pxp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct pxp_ctx *ctx = + container_of(ctrl->handler, struct pxp_ctx, hdl); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + if (ctrl->val) + ctx->mode |= MEM2MEM_HFLIP; + else + ctx->mode &= ~MEM2MEM_HFLIP; + break; + + case V4L2_CID_VFLIP: + if (ctrl->val) + ctx->mode |= MEM2MEM_VFLIP; + else + ctx->mode &= ~MEM2MEM_VFLIP; + break; + + case V4L2_CID_ROTATE: + ctx->rotation = pxp_degrees_to_rot_mode(ctrl->val); + break; + + case V4L2_CID_ALPHA_COMPONENT: + ctx->alpha_component = ctrl->val; + break; + + default: + v4l2_err(&ctx->dev->v4l2_dev, "Invalid control\n"); + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops pxp_ctrl_ops = { + .s_ctrl = pxp_s_ctrl, +}; + +static const struct v4l2_ioctl_ops pxp_ioctl_ops = { + .vidioc_querycap = pxp_querycap, + + .vidioc_enum_fmt_vid_cap = pxp_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = pxp_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = pxp_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = pxp_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = pxp_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = pxp_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = pxp_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = pxp_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * Queue operations + */ +static int pxp_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(vq); + struct pxp_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + size = q_data->sizeimage; + + *nbuffers = count; + + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int pxp_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct pxp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct pxp_dev *dev = ctx->dev; + struct pxp_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) { + dprintk(dev, "%s field isn't supported\n", __func__); + return -EINVAL; + } + } + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void pxp_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct pxp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int pxp_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(q); + struct pxp_q_data *q_data = get_q_data(ctx, q->type); + + q_data->sequence = 0; + return 0; +} + +static void pxp_stop_streaming(struct vb2_queue *q) +{ + struct pxp_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + unsigned long flags; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (vbuf == NULL) + return; + spin_lock_irqsave(&ctx->dev->irqlock, flags); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->dev->irqlock, flags); + } +} + +static const struct vb2_ops pxp_qops = { + .queue_setup = pxp_queue_setup, + .buf_prepare = pxp_buf_prepare, + .buf_queue = pxp_buf_queue, + .start_streaming = pxp_start_streaming, + .stop_streaming = pxp_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct pxp_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &pxp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->dev_mutex; + src_vq->dev = ctx->dev->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &pxp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->dev_mutex; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int pxp_open(struct file *file) +{ + struct pxp_dev *dev = video_drvdata(file); + struct pxp_ctx *ctx = NULL; + struct v4l2_ctrl_handler *hdl; + int rc = 0; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + rc = -ENOMEM; + goto open_unlock; + } + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + hdl = &ctx->hdl; + v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0); + v4l2_ctrl_new_std(hdl, &pxp_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, + 0, 255, 1, 255); + if (hdl->error) { + rc = hdl->error; + v4l2_ctrl_handler_free(hdl); + kfree(ctx); + goto open_unlock; + } + ctx->fh.ctrl_handler = hdl; + v4l2_ctrl_handler_setup(hdl); + + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[0]; + ctx->q_data[V4L2_M2M_SRC].width = 640; + ctx->q_data[V4L2_M2M_SRC].height = 480; + ctx->q_data[V4L2_M2M_SRC].bytesperline = + pxp_bytesperline(&formats[0], 640); + ctx->q_data[V4L2_M2M_SRC].sizeimage = + pxp_sizeimage(&formats[0], 640, 480); + ctx->q_data[V4L2_M2M_DST] = ctx->q_data[V4L2_M2M_SRC]; + ctx->colorspace = V4L2_COLORSPACE_REC709; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + rc = PTR_ERR(ctx->fh.m2m_ctx); + + v4l2_ctrl_handler_free(hdl); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + goto open_unlock; + } + + v4l2_fh_add(&ctx->fh); + atomic_inc(&dev->num_inst); + + dprintk(dev, "Created instance: %p, m2m_ctx: %p\n", + ctx, ctx->fh.m2m_ctx); + +open_unlock: + mutex_unlock(&dev->dev_mutex); + return rc; +} + +static int pxp_release(struct file *file) +{ + struct pxp_dev *dev = video_drvdata(file); + struct pxp_ctx *ctx = file2ctx(file); + + dprintk(dev, "Releasing instance %p\n", ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(&dev->dev_mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&dev->dev_mutex); + kfree(ctx); + + atomic_dec(&dev->num_inst); + + return 0; +} + +static const struct v4l2_file_operations pxp_fops = { + .owner = THIS_MODULE, + .open = pxp_open, + .release = pxp_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device pxp_videodev = { + .name = MEM2MEM_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &pxp_fops, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, + .ioctl_ops = &pxp_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = pxp_device_run, + .job_ready = pxp_job_ready, + .job_abort = pxp_job_abort, +}; + +static int pxp_soft_reset(struct pxp_dev *dev) +{ + int ret; + u32 val; + + writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_CLR); + writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_CLR); + + writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_SET); + + ret = readl_poll_timeout(dev->mmio + HW_PXP_CTRL, val, + val & BM_PXP_CTRL_CLKGATE, 0, 100); + if (ret < 0) + return ret; + + writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_CLR); + writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_CLR); + + return 0; +} + +static int pxp_probe(struct platform_device *pdev) +{ + struct pxp_dev *dev; + struct video_device *vfd; + int irq; + int ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->clk = devm_clk_get(&pdev->dev, "axi"); + if (IS_ERR(dev->clk)) { + ret = PTR_ERR(dev->clk); + dev_err(&pdev->dev, "Failed to get clk: %d\n", ret); + return ret; + } + + dev->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->mmio)) + return PTR_ERR(dev->mmio); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + spin_lock_init(&dev->irqlock); + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, pxp_irq_handler, + IRQF_ONESHOT, dev_name(&pdev->dev), dev); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to request irq: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(dev->clk); + if (ret < 0) + return ret; + + ret = pxp_soft_reset(dev); + if (ret < 0) { + dev_err(&pdev->dev, "PXP reset timeout: %d\n", ret); + goto err_clk; + } + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto err_clk; + + atomic_set(&dev->num_inst, 0); + mutex_init(&dev->dev_mutex); + + dev->vfd = pxp_videodev; + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + video_set_drvdata(vfd, dev); + snprintf(vfd->name, sizeof(vfd->name), "%s", pxp_videodev.name); + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, dev); + + dev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + goto err_v4l2; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto err_m2m; + } + + return 0; + +err_m2m: + v4l2_m2m_release(dev->m2m_dev); +err_v4l2: + v4l2_device_unregister(&dev->v4l2_dev); +err_clk: + clk_disable_unprepare(dev->clk); + + return ret; +} + +static int pxp_remove(struct platform_device *pdev) +{ + struct pxp_dev *dev = platform_get_drvdata(pdev); + + writel(BM_PXP_CTRL_CLKGATE, dev->mmio + HW_PXP_CTRL_SET); + writel(BM_PXP_CTRL_SFTRST, dev->mmio + HW_PXP_CTRL_SET); + + clk_disable_unprepare(dev->clk); + + v4l2_info(&dev->v4l2_dev, "Removing " MEM2MEM_NAME); + video_unregister_device(&dev->vfd); + v4l2_m2m_release(dev->m2m_dev); + v4l2_device_unregister(&dev->v4l2_dev); + + return 0; +} + +static const struct of_device_id pxp_dt_ids[] = { + { .compatible = "fsl,imx6ull-pxp", .data = NULL }, + { }, +}; +MODULE_DEVICE_TABLE(of, pxp_dt_ids); + +static struct platform_driver pxp_driver = { + .probe = pxp_probe, + .remove = pxp_remove, + .driver = { + .name = MEM2MEM_NAME, + .of_match_table = pxp_dt_ids, + }, +}; + +module_platform_driver(pxp_driver); + +MODULE_DESCRIPTION("i.MX PXP mem2mem scaler/CSC/rotator"); +MODULE_AUTHOR("Philipp Zabel "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/nxp/imx-pxp.h b/drivers/media/platform/nxp/imx-pxp.h new file mode 100644 index 000000000..44f95c749 --- /dev/null +++ b/drivers/media/platform/nxp/imx-pxp.h @@ -0,0 +1,1685 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Freescale PXP Register Definitions + * + * based on pxp_dma_v3.h, Xml Revision: 1.77, Template Revision: 1.3 + * + * Copyright 2014-2015 Freescale Semiconductor, Inc. All Rights Reserved. + */ + +#ifndef __IMX_PXP_H__ +#define __IMX_PXP_H__ + +#define HW_PXP_CTRL (0x00000000) +#define HW_PXP_CTRL_SET (0x00000004) +#define HW_PXP_CTRL_CLR (0x00000008) +#define HW_PXP_CTRL_TOG (0x0000000c) + +#define BM_PXP_CTRL_SFTRST 0x80000000 +#define BF_PXP_CTRL_SFTRST(v) \ + (((v) << 31) & BM_PXP_CTRL_SFTRST) +#define BM_PXP_CTRL_CLKGATE 0x40000000 +#define BF_PXP_CTRL_CLKGATE(v) \ + (((v) << 30) & BM_PXP_CTRL_CLKGATE) +#define BM_PXP_CTRL_RSVD4 0x20000000 +#define BF_PXP_CTRL_RSVD4(v) \ + (((v) << 29) & BM_PXP_CTRL_RSVD4) +#define BM_PXP_CTRL_EN_REPEAT 0x10000000 +#define BF_PXP_CTRL_EN_REPEAT(v) \ + (((v) << 28) & BM_PXP_CTRL_EN_REPEAT) +#define BM_PXP_CTRL_ENABLE_ROTATE1 0x08000000 +#define BF_PXP_CTRL_ENABLE_ROTATE1(v) \ + (((v) << 27) & BM_PXP_CTRL_ENABLE_ROTATE1) +#define BM_PXP_CTRL_ENABLE_ROTATE0 0x04000000 +#define BF_PXP_CTRL_ENABLE_ROTATE0(v) \ + (((v) << 26) & BM_PXP_CTRL_ENABLE_ROTATE0) +#define BM_PXP_CTRL_ENABLE_LUT 0x02000000 +#define BF_PXP_CTRL_ENABLE_LUT(v) \ + (((v) << 25) & BM_PXP_CTRL_ENABLE_LUT) +#define BM_PXP_CTRL_ENABLE_CSC2 0x01000000 +#define BF_PXP_CTRL_ENABLE_CSC2(v) \ + (((v) << 24) & BM_PXP_CTRL_ENABLE_CSC2) +#define BM_PXP_CTRL_BLOCK_SIZE 0x00800000 +#define BF_PXP_CTRL_BLOCK_SIZE(v) \ + (((v) << 23) & BM_PXP_CTRL_BLOCK_SIZE) +#define BV_PXP_CTRL_BLOCK_SIZE__8X8 0x0 +#define BV_PXP_CTRL_BLOCK_SIZE__16X16 0x1 +#define BM_PXP_CTRL_RSVD1 0x00400000 +#define BF_PXP_CTRL_RSVD1(v) \ + (((v) << 22) & BM_PXP_CTRL_RSVD1) +#define BM_PXP_CTRL_ENABLE_ALPHA_B 0x00200000 +#define BF_PXP_CTRL_ENABLE_ALPHA_B(v) \ + (((v) << 21) & BM_PXP_CTRL_ENABLE_ALPHA_B) +#define BM_PXP_CTRL_ENABLE_INPUT_FETCH_STORE 0x00100000 +#define BF_PXP_CTRL_ENABLE_INPUT_FETCH_STORE(v) \ + (((v) << 20) & BM_PXP_CTRL_ENABLE_INPUT_FETCH_STORE) +#define BM_PXP_CTRL_ENABLE_WFE_B 0x00080000 +#define BF_PXP_CTRL_ENABLE_WFE_B(v) \ + (((v) << 19) & BM_PXP_CTRL_ENABLE_WFE_B) +#define BM_PXP_CTRL_ENABLE_WFE_A 0x00040000 +#define BF_PXP_CTRL_ENABLE_WFE_A(v) \ + (((v) << 18) & BM_PXP_CTRL_ENABLE_WFE_A) +#define BM_PXP_CTRL_ENABLE_DITHER 0x00020000 +#define BF_PXP_CTRL_ENABLE_DITHER(v) \ + (((v) << 17) & BM_PXP_CTRL_ENABLE_DITHER) +#define BM_PXP_CTRL_ENABLE_PS_AS_OUT 0x00010000 +#define BF_PXP_CTRL_ENABLE_PS_AS_OUT(v) \ + (((v) << 16) & BM_PXP_CTRL_ENABLE_PS_AS_OUT) +#define BM_PXP_CTRL_VFLIP1 0x00008000 +#define BF_PXP_CTRL_VFLIP1(v) \ + (((v) << 15) & BM_PXP_CTRL_VFLIP1) +#define BM_PXP_CTRL_HFLIP1 0x00004000 +#define BF_PXP_CTRL_HFLIP1(v) \ + (((v) << 14) & BM_PXP_CTRL_HFLIP1) +#define BP_PXP_CTRL_ROTATE1 12 +#define BM_PXP_CTRL_ROTATE1 0x00003000 +#define BF_PXP_CTRL_ROTATE1(v) \ + (((v) << 12) & BM_PXP_CTRL_ROTATE1) +#define BV_PXP_CTRL_ROTATE1__ROT_0 0x0 +#define BV_PXP_CTRL_ROTATE1__ROT_90 0x1 +#define BV_PXP_CTRL_ROTATE1__ROT_180 0x2 +#define BV_PXP_CTRL_ROTATE1__ROT_270 0x3 +#define BM_PXP_CTRL_VFLIP0 0x00000800 +#define BF_PXP_CTRL_VFLIP0(v) \ + (((v) << 11) & BM_PXP_CTRL_VFLIP0) +#define BM_PXP_CTRL_HFLIP0 0x00000400 +#define BF_PXP_CTRL_HFLIP0(v) \ + (((v) << 10) & BM_PXP_CTRL_HFLIP0) +#define BP_PXP_CTRL_ROTATE0 8 +#define BM_PXP_CTRL_ROTATE0 0x00000300 +#define BF_PXP_CTRL_ROTATE0(v) \ + (((v) << 8) & BM_PXP_CTRL_ROTATE0) +#define BV_PXP_CTRL_ROTATE0__ROT_0 0x0 +#define BV_PXP_CTRL_ROTATE0__ROT_90 0x1 +#define BV_PXP_CTRL_ROTATE0__ROT_180 0x2 +#define BV_PXP_CTRL_ROTATE0__ROT_270 0x3 +#define BP_PXP_CTRL_RSVD0 6 +#define BM_PXP_CTRL_RSVD0 0x000000C0 +#define BF_PXP_CTRL_RSVD0(v) \ + (((v) << 6) & BM_PXP_CTRL_RSVD0) +#define BM_PXP_CTRL_HANDSHAKE_ABORT_SKIP 0x00000020 +#define BF_PXP_CTRL_HANDSHAKE_ABORT_SKIP(v) \ + (((v) << 5) & BM_PXP_CTRL_HANDSHAKE_ABORT_SKIP) +#define BM_PXP_CTRL_ENABLE_LCD0_HANDSHAKE 0x00000010 +#define BF_PXP_CTRL_ENABLE_LCD0_HANDSHAKE(v) \ + (((v) << 4) & BM_PXP_CTRL_ENABLE_LCD0_HANDSHAKE) +#define BM_PXP_CTRL_LUT_DMA_IRQ_ENABLE 0x00000008 +#define BF_PXP_CTRL_LUT_DMA_IRQ_ENABLE(v) \ + (((v) << 3) & BM_PXP_CTRL_LUT_DMA_IRQ_ENABLE) +#define BM_PXP_CTRL_NEXT_IRQ_ENABLE 0x00000004 +#define BF_PXP_CTRL_NEXT_IRQ_ENABLE(v) \ + (((v) << 2) & BM_PXP_CTRL_NEXT_IRQ_ENABLE) +#define BM_PXP_CTRL_IRQ_ENABLE 0x00000002 +#define BF_PXP_CTRL_IRQ_ENABLE(v) \ + (((v) << 1) & BM_PXP_CTRL_IRQ_ENABLE) +#define BM_PXP_CTRL_ENABLE 0x00000001 +#define BF_PXP_CTRL_ENABLE(v) \ + (((v) << 0) & BM_PXP_CTRL_ENABLE) + +#define HW_PXP_STAT (0x00000010) +#define HW_PXP_STAT_SET (0x00000014) +#define HW_PXP_STAT_CLR (0x00000018) +#define HW_PXP_STAT_TOG (0x0000001c) + +#define BP_PXP_STAT_BLOCKX 24 +#define BM_PXP_STAT_BLOCKX 0xFF000000 +#define BF_PXP_STAT_BLOCKX(v) \ + (((v) << 24) & BM_PXP_STAT_BLOCKX) +#define BP_PXP_STAT_BLOCKY 16 +#define BM_PXP_STAT_BLOCKY 0x00FF0000 +#define BF_PXP_STAT_BLOCKY(v) \ + (((v) << 16) & BM_PXP_STAT_BLOCKY) +#define BP_PXP_STAT_AXI_ERROR_ID_1 12 +#define BM_PXP_STAT_AXI_ERROR_ID_1 0x0000F000 +#define BF_PXP_STAT_AXI_ERROR_ID_1(v) \ + (((v) << 12) & BM_PXP_STAT_AXI_ERROR_ID_1) +#define BM_PXP_STAT_RSVD2 0x00000800 +#define BF_PXP_STAT_RSVD2(v) \ + (((v) << 11) & BM_PXP_STAT_RSVD2) +#define BM_PXP_STAT_AXI_READ_ERROR_1 0x00000400 +#define BF_PXP_STAT_AXI_READ_ERROR_1(v) \ + (((v) << 10) & BM_PXP_STAT_AXI_READ_ERROR_1) +#define BM_PXP_STAT_AXI_WRITE_ERROR_1 0x00000200 +#define BF_PXP_STAT_AXI_WRITE_ERROR_1(v) \ + (((v) << 9) & BM_PXP_STAT_AXI_WRITE_ERROR_1) +#define BM_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ 0x00000100 +#define BF_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ(v) \ + (((v) << 8) & BM_PXP_STAT_LUT_DMA_LOAD_DONE_IRQ) +#define BP_PXP_STAT_AXI_ERROR_ID_0 4 +#define BM_PXP_STAT_AXI_ERROR_ID_0 0x000000F0 +#define BF_PXP_STAT_AXI_ERROR_ID_0(v) \ + (((v) << 4) & BM_PXP_STAT_AXI_ERROR_ID_0) +#define BM_PXP_STAT_NEXT_IRQ 0x00000008 +#define BF_PXP_STAT_NEXT_IRQ(v) \ + (((v) << 3) & BM_PXP_STAT_NEXT_IRQ) +#define BM_PXP_STAT_AXI_READ_ERROR_0 0x00000004 +#define BF_PXP_STAT_AXI_READ_ERROR_0(v) \ + (((v) << 2) & BM_PXP_STAT_AXI_READ_ERROR_0) +#define BM_PXP_STAT_AXI_WRITE_ERROR_0 0x00000002 +#define BF_PXP_STAT_AXI_WRITE_ERROR_0(v) \ + (((v) << 1) & BM_PXP_STAT_AXI_WRITE_ERROR_0) +#define BM_PXP_STAT_IRQ0 0x00000001 +#define BF_PXP_STAT_IRQ0(v) \ + (((v) << 0) & BM_PXP_STAT_IRQ0) + +#define HW_PXP_OUT_CTRL (0x00000020) +#define HW_PXP_OUT_CTRL_SET (0x00000024) +#define HW_PXP_OUT_CTRL_CLR (0x00000028) +#define HW_PXP_OUT_CTRL_TOG (0x0000002c) + +#define BP_PXP_OUT_CTRL_ALPHA 24 +#define BM_PXP_OUT_CTRL_ALPHA 0xFF000000 +#define BF_PXP_OUT_CTRL_ALPHA(v) \ + (((v) << 24) & BM_PXP_OUT_CTRL_ALPHA) +#define BM_PXP_OUT_CTRL_ALPHA_OUTPUT 0x00800000 +#define BF_PXP_OUT_CTRL_ALPHA_OUTPUT(v) \ + (((v) << 23) & BM_PXP_OUT_CTRL_ALPHA_OUTPUT) +#define BP_PXP_OUT_CTRL_RSVD1 10 +#define BM_PXP_OUT_CTRL_RSVD1 0x007FFC00 +#define BF_PXP_OUT_CTRL_RSVD1(v) \ + (((v) << 10) & BM_PXP_OUT_CTRL_RSVD1) +#define BP_PXP_OUT_CTRL_INTERLACED_OUTPUT 8 +#define BM_PXP_OUT_CTRL_INTERLACED_OUTPUT 0x00000300 +#define BF_PXP_OUT_CTRL_INTERLACED_OUTPUT(v) \ + (((v) << 8) & BM_PXP_OUT_CTRL_INTERLACED_OUTPUT) +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__PROGRESSIVE 0x0 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__FIELD0 0x1 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__FIELD1 0x2 +#define BV_PXP_OUT_CTRL_INTERLACED_OUTPUT__INTERLACED 0x3 +#define BP_PXP_OUT_CTRL_RSVD0 5 +#define BM_PXP_OUT_CTRL_RSVD0 0x000000E0 +#define BF_PXP_OUT_CTRL_RSVD0(v) \ + (((v) << 5) & BM_PXP_OUT_CTRL_RSVD0) +#define BP_PXP_OUT_CTRL_FORMAT 0 +#define BM_PXP_OUT_CTRL_FORMAT 0x0000001F +#define BF_PXP_OUT_CTRL_FORMAT(v) \ + (((v) << 0) & BM_PXP_OUT_CTRL_FORMAT) +#define BV_PXP_OUT_CTRL_FORMAT__ARGB8888 0x0 +#define BV_PXP_OUT_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_OUT_CTRL_FORMAT__RGB888P 0x5 +#define BV_PXP_OUT_CTRL_FORMAT__ARGB1555 0x8 +#define BV_PXP_OUT_CTRL_FORMAT__ARGB4444 0x9 +#define BV_PXP_OUT_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_OUT_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_OUT_CTRL_FORMAT__RGB565 0xE +#define BV_PXP_OUT_CTRL_FORMAT__YUV1P444 0x10 +#define BV_PXP_OUT_CTRL_FORMAT__UYVY1P422 0x12 +#define BV_PXP_OUT_CTRL_FORMAT__VYUY1P422 0x13 +#define BV_PXP_OUT_CTRL_FORMAT__Y8 0x14 +#define BV_PXP_OUT_CTRL_FORMAT__Y4 0x15 +#define BV_PXP_OUT_CTRL_FORMAT__YUV2P422 0x18 +#define BV_PXP_OUT_CTRL_FORMAT__YUV2P420 0x19 +#define BV_PXP_OUT_CTRL_FORMAT__YVU2P422 0x1A +#define BV_PXP_OUT_CTRL_FORMAT__YVU2P420 0x1B + +#define HW_PXP_OUT_BUF (0x00000030) + +#define BP_PXP_OUT_BUF_ADDR 0 +#define BM_PXP_OUT_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_OUT_BUF_ADDR(v) (v) + +#define HW_PXP_OUT_BUF2 (0x00000040) + +#define BP_PXP_OUT_BUF2_ADDR 0 +#define BM_PXP_OUT_BUF2_ADDR 0xFFFFFFFF +#define BF_PXP_OUT_BUF2_ADDR(v) (v) + +#define HW_PXP_OUT_PITCH (0x00000050) + +#define BP_PXP_OUT_PITCH_RSVD 16 +#define BM_PXP_OUT_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_OUT_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_OUT_PITCH_RSVD) +#define BP_PXP_OUT_PITCH_PITCH 0 +#define BM_PXP_OUT_PITCH_PITCH 0x0000FFFF +#define BF_PXP_OUT_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_OUT_PITCH_PITCH) + +#define HW_PXP_OUT_LRC (0x00000060) + +#define BP_PXP_OUT_LRC_RSVD1 30 +#define BM_PXP_OUT_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_LRC_RSVD1) +#define BP_PXP_OUT_LRC_X 16 +#define BM_PXP_OUT_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_LRC_X) +#define BP_PXP_OUT_LRC_RSVD0 14 +#define BM_PXP_OUT_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_LRC_RSVD0) +#define BP_PXP_OUT_LRC_Y 0 +#define BM_PXP_OUT_LRC_Y 0x00003FFF +#define BF_PXP_OUT_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_LRC_Y) + +#define HW_PXP_OUT_PS_ULC (0x00000070) + +#define BP_PXP_OUT_PS_ULC_RSVD1 30 +#define BM_PXP_OUT_PS_ULC_RSVD1 0xC0000000 +#define BF_PXP_OUT_PS_ULC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_PS_ULC_RSVD1) +#define BP_PXP_OUT_PS_ULC_X 16 +#define BM_PXP_OUT_PS_ULC_X 0x3FFF0000 +#define BF_PXP_OUT_PS_ULC_X(v) \ + (((v) << 16) & BM_PXP_OUT_PS_ULC_X) +#define BP_PXP_OUT_PS_ULC_RSVD0 14 +#define BM_PXP_OUT_PS_ULC_RSVD0 0x0000C000 +#define BF_PXP_OUT_PS_ULC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_PS_ULC_RSVD0) +#define BP_PXP_OUT_PS_ULC_Y 0 +#define BM_PXP_OUT_PS_ULC_Y 0x00003FFF +#define BF_PXP_OUT_PS_ULC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_PS_ULC_Y) + +#define HW_PXP_OUT_PS_LRC (0x00000080) + +#define BP_PXP_OUT_PS_LRC_RSVD1 30 +#define BM_PXP_OUT_PS_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_PS_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_PS_LRC_RSVD1) +#define BP_PXP_OUT_PS_LRC_X 16 +#define BM_PXP_OUT_PS_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_PS_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_PS_LRC_X) +#define BP_PXP_OUT_PS_LRC_RSVD0 14 +#define BM_PXP_OUT_PS_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_PS_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_PS_LRC_RSVD0) +#define BP_PXP_OUT_PS_LRC_Y 0 +#define BM_PXP_OUT_PS_LRC_Y 0x00003FFF +#define BF_PXP_OUT_PS_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_PS_LRC_Y) + +#define HW_PXP_OUT_AS_ULC (0x00000090) + +#define BP_PXP_OUT_AS_ULC_RSVD1 30 +#define BM_PXP_OUT_AS_ULC_RSVD1 0xC0000000 +#define BF_PXP_OUT_AS_ULC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_AS_ULC_RSVD1) +#define BP_PXP_OUT_AS_ULC_X 16 +#define BM_PXP_OUT_AS_ULC_X 0x3FFF0000 +#define BF_PXP_OUT_AS_ULC_X(v) \ + (((v) << 16) & BM_PXP_OUT_AS_ULC_X) +#define BP_PXP_OUT_AS_ULC_RSVD0 14 +#define BM_PXP_OUT_AS_ULC_RSVD0 0x0000C000 +#define BF_PXP_OUT_AS_ULC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_AS_ULC_RSVD0) +#define BP_PXP_OUT_AS_ULC_Y 0 +#define BM_PXP_OUT_AS_ULC_Y 0x00003FFF +#define BF_PXP_OUT_AS_ULC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_AS_ULC_Y) + +#define HW_PXP_OUT_AS_LRC (0x000000a0) + +#define BP_PXP_OUT_AS_LRC_RSVD1 30 +#define BM_PXP_OUT_AS_LRC_RSVD1 0xC0000000 +#define BF_PXP_OUT_AS_LRC_RSVD1(v) \ + (((v) << 30) & BM_PXP_OUT_AS_LRC_RSVD1) +#define BP_PXP_OUT_AS_LRC_X 16 +#define BM_PXP_OUT_AS_LRC_X 0x3FFF0000 +#define BF_PXP_OUT_AS_LRC_X(v) \ + (((v) << 16) & BM_PXP_OUT_AS_LRC_X) +#define BP_PXP_OUT_AS_LRC_RSVD0 14 +#define BM_PXP_OUT_AS_LRC_RSVD0 0x0000C000 +#define BF_PXP_OUT_AS_LRC_RSVD0(v) \ + (((v) << 14) & BM_PXP_OUT_AS_LRC_RSVD0) +#define BP_PXP_OUT_AS_LRC_Y 0 +#define BM_PXP_OUT_AS_LRC_Y 0x00003FFF +#define BF_PXP_OUT_AS_LRC_Y(v) \ + (((v) << 0) & BM_PXP_OUT_AS_LRC_Y) + +#define HW_PXP_PS_CTRL (0x000000b0) +#define HW_PXP_PS_CTRL_SET (0x000000b4) +#define HW_PXP_PS_CTRL_CLR (0x000000b8) +#define HW_PXP_PS_CTRL_TOG (0x000000bc) + +#define BP_PXP_PS_CTRL_RSVD1 12 +#define BM_PXP_PS_CTRL_RSVD1 0xFFFFF000 +#define BF_PXP_PS_CTRL_RSVD1(v) \ + (((v) << 12) & BM_PXP_PS_CTRL_RSVD1) +#define BP_PXP_PS_CTRL_DECX 10 +#define BM_PXP_PS_CTRL_DECX 0x00000C00 +#define BF_PXP_PS_CTRL_DECX(v) \ + (((v) << 10) & BM_PXP_PS_CTRL_DECX) +#define BV_PXP_PS_CTRL_DECX__DISABLE 0x0 +#define BV_PXP_PS_CTRL_DECX__DECX2 0x1 +#define BV_PXP_PS_CTRL_DECX__DECX4 0x2 +#define BV_PXP_PS_CTRL_DECX__DECX8 0x3 +#define BP_PXP_PS_CTRL_DECY 8 +#define BM_PXP_PS_CTRL_DECY 0x00000300 +#define BF_PXP_PS_CTRL_DECY(v) \ + (((v) << 8) & BM_PXP_PS_CTRL_DECY) +#define BV_PXP_PS_CTRL_DECY__DISABLE 0x0 +#define BV_PXP_PS_CTRL_DECY__DECY2 0x1 +#define BV_PXP_PS_CTRL_DECY__DECY4 0x2 +#define BV_PXP_PS_CTRL_DECY__DECY8 0x3 +#define BM_PXP_PS_CTRL_RSVD0 0x00000080 +#define BF_PXP_PS_CTRL_RSVD0(v) \ + (((v) << 7) & BM_PXP_PS_CTRL_RSVD0) +#define BM_PXP_PS_CTRL_WB_SWAP 0x00000040 +#define BF_PXP_PS_CTRL_WB_SWAP(v) \ + (((v) << 6) & BM_PXP_PS_CTRL_WB_SWAP) +#define BP_PXP_PS_CTRL_FORMAT 0 +#define BM_PXP_PS_CTRL_FORMAT 0x0000003F +#define BF_PXP_PS_CTRL_FORMAT(v) \ + (((v) << 0) & BM_PXP_PS_CTRL_FORMAT) +#define BV_PXP_PS_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_PS_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_PS_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_PS_CTRL_FORMAT__RGB565 0xE +#define BV_PXP_PS_CTRL_FORMAT__YUV1P444 0x10 +#define BV_PXP_PS_CTRL_FORMAT__UYVY1P422 0x12 +#define BV_PXP_PS_CTRL_FORMAT__VYUY1P422 0x13 +#define BV_PXP_PS_CTRL_FORMAT__Y8 0x14 +#define BV_PXP_PS_CTRL_FORMAT__Y4 0x15 +#define BV_PXP_PS_CTRL_FORMAT__YUV2P422 0x18 +#define BV_PXP_PS_CTRL_FORMAT__YUV2P420 0x19 +#define BV_PXP_PS_CTRL_FORMAT__YVU2P422 0x1A +#define BV_PXP_PS_CTRL_FORMAT__YVU2P420 0x1B +#define BV_PXP_PS_CTRL_FORMAT__YUV422 0x1E +#define BV_PXP_PS_CTRL_FORMAT__YUV420 0x1F + +#define HW_PXP_PS_BUF (0x000000c0) + +#define BP_PXP_PS_BUF_ADDR 0 +#define BM_PXP_PS_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_BUF_ADDR(v) (v) + +#define HW_PXP_PS_UBUF (0x000000d0) + +#define BP_PXP_PS_UBUF_ADDR 0 +#define BM_PXP_PS_UBUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_UBUF_ADDR(v) (v) + +#define HW_PXP_PS_VBUF (0x000000e0) + +#define BP_PXP_PS_VBUF_ADDR 0 +#define BM_PXP_PS_VBUF_ADDR 0xFFFFFFFF +#define BF_PXP_PS_VBUF_ADDR(v) (v) + +#define HW_PXP_PS_PITCH (0x000000f0) + +#define BP_PXP_PS_PITCH_RSVD 16 +#define BM_PXP_PS_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_PS_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_PS_PITCH_RSVD) +#define BP_PXP_PS_PITCH_PITCH 0 +#define BM_PXP_PS_PITCH_PITCH 0x0000FFFF +#define BF_PXP_PS_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_PS_PITCH_PITCH) + +#define HW_PXP_PS_BACKGROUND_0 (0x00000100) + +#define BP_PXP_PS_BACKGROUND_0_RSVD 24 +#define BM_PXP_PS_BACKGROUND_0_RSVD 0xFF000000 +#define BF_PXP_PS_BACKGROUND_0_RSVD(v) \ + (((v) << 24) & BM_PXP_PS_BACKGROUND_0_RSVD) +#define BP_PXP_PS_BACKGROUND_0_COLOR 0 +#define BM_PXP_PS_BACKGROUND_0_COLOR 0x00FFFFFF +#define BF_PXP_PS_BACKGROUND_0_COLOR(v) \ + (((v) << 0) & BM_PXP_PS_BACKGROUND_0_COLOR) + +#define HW_PXP_PS_SCALE (0x00000110) + +#define BM_PXP_PS_SCALE_RSVD2 0x80000000 +#define BF_PXP_PS_SCALE_RSVD2(v) \ + (((v) << 31) & BM_PXP_PS_SCALE_RSVD2) +#define BP_PXP_PS_SCALE_YSCALE 16 +#define BM_PXP_PS_SCALE_YSCALE 0x7FFF0000 +#define BF_PXP_PS_SCALE_YSCALE(v) \ + (((v) << 16) & BM_PXP_PS_SCALE_YSCALE) +#define BM_PXP_PS_SCALE_RSVD1 0x00008000 +#define BF_PXP_PS_SCALE_RSVD1(v) \ + (((v) << 15) & BM_PXP_PS_SCALE_RSVD1) +#define BP_PXP_PS_SCALE_XSCALE 0 +#define BM_PXP_PS_SCALE_XSCALE 0x00007FFF +#define BF_PXP_PS_SCALE_XSCALE(v) \ + (((v) << 0) & BM_PXP_PS_SCALE_XSCALE) + +#define HW_PXP_PS_OFFSET (0x00000120) + +#define BP_PXP_PS_OFFSET_RSVD2 28 +#define BM_PXP_PS_OFFSET_RSVD2 0xF0000000 +#define BF_PXP_PS_OFFSET_RSVD2(v) \ + (((v) << 28) & BM_PXP_PS_OFFSET_RSVD2) +#define BP_PXP_PS_OFFSET_YOFFSET 16 +#define BM_PXP_PS_OFFSET_YOFFSET 0x0FFF0000 +#define BF_PXP_PS_OFFSET_YOFFSET(v) \ + (((v) << 16) & BM_PXP_PS_OFFSET_YOFFSET) +#define BP_PXP_PS_OFFSET_RSVD1 12 +#define BM_PXP_PS_OFFSET_RSVD1 0x0000F000 +#define BF_PXP_PS_OFFSET_RSVD1(v) \ + (((v) << 12) & BM_PXP_PS_OFFSET_RSVD1) +#define BP_PXP_PS_OFFSET_XOFFSET 0 +#define BM_PXP_PS_OFFSET_XOFFSET 0x00000FFF +#define BF_PXP_PS_OFFSET_XOFFSET(v) \ + (((v) << 0) & BM_PXP_PS_OFFSET_XOFFSET) + +#define HW_PXP_PS_CLRKEYLOW_0 (0x00000130) + +#define BP_PXP_PS_CLRKEYLOW_0_RSVD1 24 +#define BM_PXP_PS_CLRKEYLOW_0_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYLOW_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYLOW_0_RSVD1) +#define BP_PXP_PS_CLRKEYLOW_0_PIXEL 0 +#define BM_PXP_PS_CLRKEYLOW_0_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYLOW_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYLOW_0_PIXEL) + +#define HW_PXP_PS_CLRKEYHIGH_0 (0x00000140) + +#define BP_PXP_PS_CLRKEYHIGH_0_RSVD1 24 +#define BM_PXP_PS_CLRKEYHIGH_0_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYHIGH_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYHIGH_0_RSVD1) +#define BP_PXP_PS_CLRKEYHIGH_0_PIXEL 0 +#define BM_PXP_PS_CLRKEYHIGH_0_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYHIGH_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYHIGH_0_PIXEL) + +#define HW_PXP_AS_CTRL (0x00000150) + +#define BP_PXP_AS_CTRL_RSVD1 22 +#define BM_PXP_AS_CTRL_RSVD1 0xFFC00000 +#define BF_PXP_AS_CTRL_RSVD1(v) \ + (((v) << 22) & BM_PXP_AS_CTRL_RSVD1) +#define BM_PXP_AS_CTRL_ALPHA1_INVERT 0x00200000 +#define BF_PXP_AS_CTRL_ALPHA1_INVERT(v) \ + (((v) << 21) & BM_PXP_AS_CTRL_ALPHA1_INVERT) +#define BM_PXP_AS_CTRL_ALPHA0_INVERT 0x00100000 +#define BF_PXP_AS_CTRL_ALPHA0_INVERT(v) \ + (((v) << 20) & BM_PXP_AS_CTRL_ALPHA0_INVERT) +#define BP_PXP_AS_CTRL_ROP 16 +#define BM_PXP_AS_CTRL_ROP 0x000F0000 +#define BF_PXP_AS_CTRL_ROP(v) \ + (((v) << 16) & BM_PXP_AS_CTRL_ROP) +#define BV_PXP_AS_CTRL_ROP__MASKAS 0x0 +#define BV_PXP_AS_CTRL_ROP__MASKNOTAS 0x1 +#define BV_PXP_AS_CTRL_ROP__MASKASNOT 0x2 +#define BV_PXP_AS_CTRL_ROP__MERGEAS 0x3 +#define BV_PXP_AS_CTRL_ROP__MERGENOTAS 0x4 +#define BV_PXP_AS_CTRL_ROP__MERGEASNOT 0x5 +#define BV_PXP_AS_CTRL_ROP__NOTCOPYAS 0x6 +#define BV_PXP_AS_CTRL_ROP__NOT 0x7 +#define BV_PXP_AS_CTRL_ROP__NOTMASKAS 0x8 +#define BV_PXP_AS_CTRL_ROP__NOTMERGEAS 0x9 +#define BV_PXP_AS_CTRL_ROP__XORAS 0xA +#define BV_PXP_AS_CTRL_ROP__NOTXORAS 0xB +#define BP_PXP_AS_CTRL_ALPHA 8 +#define BM_PXP_AS_CTRL_ALPHA 0x0000FF00 +#define BF_PXP_AS_CTRL_ALPHA(v) \ + (((v) << 8) & BM_PXP_AS_CTRL_ALPHA) +#define BP_PXP_AS_CTRL_FORMAT 4 +#define BM_PXP_AS_CTRL_FORMAT 0x000000F0 +#define BF_PXP_AS_CTRL_FORMAT(v) \ + (((v) << 4) & BM_PXP_AS_CTRL_FORMAT) +#define BV_PXP_AS_CTRL_FORMAT__ARGB8888 0x0 +#define BV_PXP_AS_CTRL_FORMAT__RGBA8888 0x1 +#define BV_PXP_AS_CTRL_FORMAT__RGB888 0x4 +#define BV_PXP_AS_CTRL_FORMAT__ARGB1555 0x8 +#define BV_PXP_AS_CTRL_FORMAT__ARGB4444 0x9 +#define BV_PXP_AS_CTRL_FORMAT__RGB555 0xC +#define BV_PXP_AS_CTRL_FORMAT__RGB444 0xD +#define BV_PXP_AS_CTRL_FORMAT__RGB565 0xE +#define BM_PXP_AS_CTRL_ENABLE_COLORKEY 0x00000008 +#define BF_PXP_AS_CTRL_ENABLE_COLORKEY(v) \ + (((v) << 3) & BM_PXP_AS_CTRL_ENABLE_COLORKEY) +#define BP_PXP_AS_CTRL_ALPHA_CTRL 1 +#define BM_PXP_AS_CTRL_ALPHA_CTRL 0x00000006 +#define BF_PXP_AS_CTRL_ALPHA_CTRL(v) \ + (((v) << 1) & BM_PXP_AS_CTRL_ALPHA_CTRL) +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Embedded 0x0 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Override 0x1 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__Multiply 0x2 +#define BV_PXP_AS_CTRL_ALPHA_CTRL__ROPs 0x3 +#define BM_PXP_AS_CTRL_RSVD0 0x00000001 +#define BF_PXP_AS_CTRL_RSVD0(v) \ + (((v) << 0) & BM_PXP_AS_CTRL_RSVD0) + +#define HW_PXP_AS_BUF (0x00000160) + +#define BP_PXP_AS_BUF_ADDR 0 +#define BM_PXP_AS_BUF_ADDR 0xFFFFFFFF +#define BF_PXP_AS_BUF_ADDR(v) (v) + +#define HW_PXP_AS_PITCH (0x00000170) + +#define BP_PXP_AS_PITCH_RSVD 16 +#define BM_PXP_AS_PITCH_RSVD 0xFFFF0000 +#define BF_PXP_AS_PITCH_RSVD(v) \ + (((v) << 16) & BM_PXP_AS_PITCH_RSVD) +#define BP_PXP_AS_PITCH_PITCH 0 +#define BM_PXP_AS_PITCH_PITCH 0x0000FFFF +#define BF_PXP_AS_PITCH_PITCH(v) \ + (((v) << 0) & BM_PXP_AS_PITCH_PITCH) + +#define HW_PXP_AS_CLRKEYLOW_0 (0x00000180) + +#define BP_PXP_AS_CLRKEYLOW_0_RSVD1 24 +#define BM_PXP_AS_CLRKEYLOW_0_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYLOW_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYLOW_0_RSVD1) +#define BP_PXP_AS_CLRKEYLOW_0_PIXEL 0 +#define BM_PXP_AS_CLRKEYLOW_0_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYLOW_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYLOW_0_PIXEL) + +#define HW_PXP_AS_CLRKEYHIGH_0 (0x00000190) + +#define BP_PXP_AS_CLRKEYHIGH_0_RSVD1 24 +#define BM_PXP_AS_CLRKEYHIGH_0_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYHIGH_0_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYHIGH_0_RSVD1) +#define BP_PXP_AS_CLRKEYHIGH_0_PIXEL 0 +#define BM_PXP_AS_CLRKEYHIGH_0_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYHIGH_0_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYHIGH_0_PIXEL) + +#define HW_PXP_CSC1_COEF0 (0x000001a0) + +#define BM_PXP_CSC1_COEF0_YCBCR_MODE 0x80000000 +#define BF_PXP_CSC1_COEF0_YCBCR_MODE(v) \ + (((v) << 31) & BM_PXP_CSC1_COEF0_YCBCR_MODE) +#define BM_PXP_CSC1_COEF0_BYPASS 0x40000000 +#define BF_PXP_CSC1_COEF0_BYPASS(v) \ + (((v) << 30) & BM_PXP_CSC1_COEF0_BYPASS) +#define BM_PXP_CSC1_COEF0_RSVD1 0x20000000 +#define BF_PXP_CSC1_COEF0_RSVD1(v) \ + (((v) << 29) & BM_PXP_CSC1_COEF0_RSVD1) +#define BP_PXP_CSC1_COEF0_C0 18 +#define BM_PXP_CSC1_COEF0_C0 0x1FFC0000 +#define BF_PXP_CSC1_COEF0_C0(v) \ + (((v) << 18) & BM_PXP_CSC1_COEF0_C0) +#define BP_PXP_CSC1_COEF0_UV_OFFSET 9 +#define BM_PXP_CSC1_COEF0_UV_OFFSET 0x0003FE00 +#define BF_PXP_CSC1_COEF0_UV_OFFSET(v) \ + (((v) << 9) & BM_PXP_CSC1_COEF0_UV_OFFSET) +#define BP_PXP_CSC1_COEF0_Y_OFFSET 0 +#define BM_PXP_CSC1_COEF0_Y_OFFSET 0x000001FF +#define BF_PXP_CSC1_COEF0_Y_OFFSET(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF0_Y_OFFSET) + +#define HW_PXP_CSC1_COEF1 (0x000001b0) + +#define BP_PXP_CSC1_COEF1_RSVD1 27 +#define BM_PXP_CSC1_COEF1_RSVD1 0xF8000000 +#define BF_PXP_CSC1_COEF1_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC1_COEF1_RSVD1) +#define BP_PXP_CSC1_COEF1_C1 16 +#define BM_PXP_CSC1_COEF1_C1 0x07FF0000 +#define BF_PXP_CSC1_COEF1_C1(v) \ + (((v) << 16) & BM_PXP_CSC1_COEF1_C1) +#define BP_PXP_CSC1_COEF1_RSVD0 11 +#define BM_PXP_CSC1_COEF1_RSVD0 0x0000F800 +#define BF_PXP_CSC1_COEF1_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC1_COEF1_RSVD0) +#define BP_PXP_CSC1_COEF1_C4 0 +#define BM_PXP_CSC1_COEF1_C4 0x000007FF +#define BF_PXP_CSC1_COEF1_C4(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF1_C4) + +#define HW_PXP_CSC1_COEF2 (0x000001c0) + +#define BP_PXP_CSC1_COEF2_RSVD1 27 +#define BM_PXP_CSC1_COEF2_RSVD1 0xF8000000 +#define BF_PXP_CSC1_COEF2_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC1_COEF2_RSVD1) +#define BP_PXP_CSC1_COEF2_C2 16 +#define BM_PXP_CSC1_COEF2_C2 0x07FF0000 +#define BF_PXP_CSC1_COEF2_C2(v) \ + (((v) << 16) & BM_PXP_CSC1_COEF2_C2) +#define BP_PXP_CSC1_COEF2_RSVD0 11 +#define BM_PXP_CSC1_COEF2_RSVD0 0x0000F800 +#define BF_PXP_CSC1_COEF2_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC1_COEF2_RSVD0) +#define BP_PXP_CSC1_COEF2_C3 0 +#define BM_PXP_CSC1_COEF2_C3 0x000007FF +#define BF_PXP_CSC1_COEF2_C3(v) \ + (((v) << 0) & BM_PXP_CSC1_COEF2_C3) + +#define HW_PXP_CSC2_CTRL (0x000001d0) + +#define BP_PXP_CSC2_CTRL_RSVD 3 +#define BM_PXP_CSC2_CTRL_RSVD 0xFFFFFFF8 +#define BF_PXP_CSC2_CTRL_RSVD(v) \ + (((v) << 3) & BM_PXP_CSC2_CTRL_RSVD) +#define BP_PXP_CSC2_CTRL_CSC_MODE 1 +#define BM_PXP_CSC2_CTRL_CSC_MODE 0x00000006 +#define BF_PXP_CSC2_CTRL_CSC_MODE(v) \ + (((v) << 1) & BM_PXP_CSC2_CTRL_CSC_MODE) +#define BV_PXP_CSC2_CTRL_CSC_MODE__YUV2RGB 0x0 +#define BV_PXP_CSC2_CTRL_CSC_MODE__YCbCr2RGB 0x1 +#define BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YUV 0x2 +#define BV_PXP_CSC2_CTRL_CSC_MODE__RGB2YCbCr 0x3 +#define BM_PXP_CSC2_CTRL_BYPASS 0x00000001 +#define BF_PXP_CSC2_CTRL_BYPASS(v) \ + (((v) << 0) & BM_PXP_CSC2_CTRL_BYPASS) + +#define HW_PXP_CSC2_COEF0 (0x000001e0) + +#define BP_PXP_CSC2_COEF0_RSVD1 27 +#define BM_PXP_CSC2_COEF0_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF0_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF0_RSVD1) +#define BP_PXP_CSC2_COEF0_A2 16 +#define BM_PXP_CSC2_COEF0_A2 0x07FF0000 +#define BF_PXP_CSC2_COEF0_A2(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF0_A2) +#define BP_PXP_CSC2_COEF0_RSVD0 11 +#define BM_PXP_CSC2_COEF0_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF0_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF0_RSVD0) +#define BP_PXP_CSC2_COEF0_A1 0 +#define BM_PXP_CSC2_COEF0_A1 0x000007FF +#define BF_PXP_CSC2_COEF0_A1(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF0_A1) + +#define HW_PXP_CSC2_COEF1 (0x000001f0) + +#define BP_PXP_CSC2_COEF1_RSVD1 27 +#define BM_PXP_CSC2_COEF1_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF1_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF1_RSVD1) +#define BP_PXP_CSC2_COEF1_B1 16 +#define BM_PXP_CSC2_COEF1_B1 0x07FF0000 +#define BF_PXP_CSC2_COEF1_B1(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF1_B1) +#define BP_PXP_CSC2_COEF1_RSVD0 11 +#define BM_PXP_CSC2_COEF1_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF1_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF1_RSVD0) +#define BP_PXP_CSC2_COEF1_A3 0 +#define BM_PXP_CSC2_COEF1_A3 0x000007FF +#define BF_PXP_CSC2_COEF1_A3(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF1_A3) + +#define HW_PXP_CSC2_COEF2 (0x00000200) + +#define BP_PXP_CSC2_COEF2_RSVD1 27 +#define BM_PXP_CSC2_COEF2_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF2_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF2_RSVD1) +#define BP_PXP_CSC2_COEF2_B3 16 +#define BM_PXP_CSC2_COEF2_B3 0x07FF0000 +#define BF_PXP_CSC2_COEF2_B3(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF2_B3) +#define BP_PXP_CSC2_COEF2_RSVD0 11 +#define BM_PXP_CSC2_COEF2_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF2_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF2_RSVD0) +#define BP_PXP_CSC2_COEF2_B2 0 +#define BM_PXP_CSC2_COEF2_B2 0x000007FF +#define BF_PXP_CSC2_COEF2_B2(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF2_B2) + +#define HW_PXP_CSC2_COEF3 (0x00000210) + +#define BP_PXP_CSC2_COEF3_RSVD1 27 +#define BM_PXP_CSC2_COEF3_RSVD1 0xF8000000 +#define BF_PXP_CSC2_COEF3_RSVD1(v) \ + (((v) << 27) & BM_PXP_CSC2_COEF3_RSVD1) +#define BP_PXP_CSC2_COEF3_C2 16 +#define BM_PXP_CSC2_COEF3_C2 0x07FF0000 +#define BF_PXP_CSC2_COEF3_C2(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF3_C2) +#define BP_PXP_CSC2_COEF3_RSVD0 11 +#define BM_PXP_CSC2_COEF3_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF3_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF3_RSVD0) +#define BP_PXP_CSC2_COEF3_C1 0 +#define BM_PXP_CSC2_COEF3_C1 0x000007FF +#define BF_PXP_CSC2_COEF3_C1(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF3_C1) + +#define HW_PXP_CSC2_COEF4 (0x00000220) + +#define BP_PXP_CSC2_COEF4_RSVD1 25 +#define BM_PXP_CSC2_COEF4_RSVD1 0xFE000000 +#define BF_PXP_CSC2_COEF4_RSVD1(v) \ + (((v) << 25) & BM_PXP_CSC2_COEF4_RSVD1) +#define BP_PXP_CSC2_COEF4_D1 16 +#define BM_PXP_CSC2_COEF4_D1 0x01FF0000 +#define BF_PXP_CSC2_COEF4_D1(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF4_D1) +#define BP_PXP_CSC2_COEF4_RSVD0 11 +#define BM_PXP_CSC2_COEF4_RSVD0 0x0000F800 +#define BF_PXP_CSC2_COEF4_RSVD0(v) \ + (((v) << 11) & BM_PXP_CSC2_COEF4_RSVD0) +#define BP_PXP_CSC2_COEF4_C3 0 +#define BM_PXP_CSC2_COEF4_C3 0x000007FF +#define BF_PXP_CSC2_COEF4_C3(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF4_C3) + +#define HW_PXP_CSC2_COEF5 (0x00000230) + +#define BP_PXP_CSC2_COEF5_RSVD1 25 +#define BM_PXP_CSC2_COEF5_RSVD1 0xFE000000 +#define BF_PXP_CSC2_COEF5_RSVD1(v) \ + (((v) << 25) & BM_PXP_CSC2_COEF5_RSVD1) +#define BP_PXP_CSC2_COEF5_D3 16 +#define BM_PXP_CSC2_COEF5_D3 0x01FF0000 +#define BF_PXP_CSC2_COEF5_D3(v) \ + (((v) << 16) & BM_PXP_CSC2_COEF5_D3) +#define BP_PXP_CSC2_COEF5_RSVD0 9 +#define BM_PXP_CSC2_COEF5_RSVD0 0x0000FE00 +#define BF_PXP_CSC2_COEF5_RSVD0(v) \ + (((v) << 9) & BM_PXP_CSC2_COEF5_RSVD0) +#define BP_PXP_CSC2_COEF5_D2 0 +#define BM_PXP_CSC2_COEF5_D2 0x000001FF +#define BF_PXP_CSC2_COEF5_D2(v) \ + (((v) << 0) & BM_PXP_CSC2_COEF5_D2) + +#define HW_PXP_LUT_CTRL (0x00000240) + +#define BM_PXP_LUT_CTRL_BYPASS 0x80000000 +#define BF_PXP_LUT_CTRL_BYPASS(v) \ + (((v) << 31) & BM_PXP_LUT_CTRL_BYPASS) +#define BP_PXP_LUT_CTRL_RSVD3 26 +#define BM_PXP_LUT_CTRL_RSVD3 0x7C000000 +#define BF_PXP_LUT_CTRL_RSVD3(v) \ + (((v) << 26) & BM_PXP_LUT_CTRL_RSVD3) +#define BP_PXP_LUT_CTRL_LOOKUP_MODE 24 +#define BM_PXP_LUT_CTRL_LOOKUP_MODE 0x03000000 +#define BF_PXP_LUT_CTRL_LOOKUP_MODE(v) \ + (((v) << 24) & BM_PXP_LUT_CTRL_LOOKUP_MODE) +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__CACHE_RGB565 0x0 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_Y8 0x1 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_RGB444 0x2 +#define BV_PXP_LUT_CTRL_LOOKUP_MODE__DIRECT_RGB454 0x3 +#define BP_PXP_LUT_CTRL_RSVD2 18 +#define BM_PXP_LUT_CTRL_RSVD2 0x00FC0000 +#define BF_PXP_LUT_CTRL_RSVD2(v) \ + (((v) << 18) & BM_PXP_LUT_CTRL_RSVD2) +#define BP_PXP_LUT_CTRL_OUT_MODE 16 +#define BM_PXP_LUT_CTRL_OUT_MODE 0x00030000 +#define BF_PXP_LUT_CTRL_OUT_MODE(v) \ + (((v) << 16) & BM_PXP_LUT_CTRL_OUT_MODE) +#define BV_PXP_LUT_CTRL_OUT_MODE__RESERVED 0x0 +#define BV_PXP_LUT_CTRL_OUT_MODE__Y8 0x1 +#define BV_PXP_LUT_CTRL_OUT_MODE__RGBW4444CFA 0x2 +#define BV_PXP_LUT_CTRL_OUT_MODE__RGB888 0x3 +#define BP_PXP_LUT_CTRL_RSVD1 11 +#define BM_PXP_LUT_CTRL_RSVD1 0x0000F800 +#define BF_PXP_LUT_CTRL_RSVD1(v) \ + (((v) << 11) & BM_PXP_LUT_CTRL_RSVD1) +#define BM_PXP_LUT_CTRL_SEL_8KB 0x00000400 +#define BF_PXP_LUT_CTRL_SEL_8KB(v) \ + (((v) << 10) & BM_PXP_LUT_CTRL_SEL_8KB) +#define BM_PXP_LUT_CTRL_LRU_UPD 0x00000200 +#define BF_PXP_LUT_CTRL_LRU_UPD(v) \ + (((v) << 9) & BM_PXP_LUT_CTRL_LRU_UPD) +#define BM_PXP_LUT_CTRL_INVALID 0x00000100 +#define BF_PXP_LUT_CTRL_INVALID(v) \ + (((v) << 8) & BM_PXP_LUT_CTRL_INVALID) +#define BP_PXP_LUT_CTRL_RSVD0 1 +#define BM_PXP_LUT_CTRL_RSVD0 0x000000FE +#define BF_PXP_LUT_CTRL_RSVD0(v) \ + (((v) << 1) & BM_PXP_LUT_CTRL_RSVD0) +#define BM_PXP_LUT_CTRL_DMA_START 0x00000001 +#define BF_PXP_LUT_CTRL_DMA_START(v) \ + (((v) << 0) & BM_PXP_LUT_CTRL_DMA_START) + +#define HW_PXP_LUT_ADDR (0x00000250) + +#define BM_PXP_LUT_ADDR_RSVD2 0x80000000 +#define BF_PXP_LUT_ADDR_RSVD2(v) \ + (((v) << 31) & BM_PXP_LUT_ADDR_RSVD2) +#define BP_PXP_LUT_ADDR_NUM_BYTES 16 +#define BM_PXP_LUT_ADDR_NUM_BYTES 0x7FFF0000 +#define BF_PXP_LUT_ADDR_NUM_BYTES(v) \ + (((v) << 16) & BM_PXP_LUT_ADDR_NUM_BYTES) +#define BP_PXP_LUT_ADDR_RSVD1 14 +#define BM_PXP_LUT_ADDR_RSVD1 0x0000C000 +#define BF_PXP_LUT_ADDR_RSVD1(v) \ + (((v) << 14) & BM_PXP_LUT_ADDR_RSVD1) +#define BP_PXP_LUT_ADDR_ADDR 0 +#define BM_PXP_LUT_ADDR_ADDR 0x00003FFF +#define BF_PXP_LUT_ADDR_ADDR(v) \ + (((v) << 0) & BM_PXP_LUT_ADDR_ADDR) + +#define HW_PXP_LUT_DATA (0x00000260) + +#define BP_PXP_LUT_DATA_DATA 0 +#define BM_PXP_LUT_DATA_DATA 0xFFFFFFFF +#define BF_PXP_LUT_DATA_DATA(v) (v) + +#define HW_PXP_LUT_EXTMEM (0x00000270) + +#define BP_PXP_LUT_EXTMEM_ADDR 0 +#define BM_PXP_LUT_EXTMEM_ADDR 0xFFFFFFFF +#define BF_PXP_LUT_EXTMEM_ADDR(v) (v) + +#define HW_PXP_CFA (0x00000280) + +#define BP_PXP_CFA_DATA 0 +#define BM_PXP_CFA_DATA 0xFFFFFFFF +#define BF_PXP_CFA_DATA(v) (v) + +#define HW_PXP_ALPHA_A_CTRL (0x00000290) + +#define BP_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA 24 +#define BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA 0xFF000000 +#define BF_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA(v) \ + (((v) << 24) & BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA 16 +#define BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA 0x00FF0000 +#define BF_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA(v) \ + (((v) << 16) & BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_A_CTRL_RSVD0 14 +#define BM_PXP_ALPHA_A_CTRL_RSVD0 0x0000C000 +#define BF_PXP_ALPHA_A_CTRL_RSVD0(v) \ + (((v) << 14) & BM_PXP_ALPHA_A_CTRL_RSVD0) +#define BM_PXP_ALPHA_A_CTRL_S1_COLOR_MODE 0x00002000 +#define BF_PXP_ALPHA_A_CTRL_S1_COLOR_MODE(v) \ + (((v) << 13) & BM_PXP_ALPHA_A_CTRL_S1_COLOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE 0x00001000 +#define BF_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE(v) \ + (((v) << 12) & BM_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE 10 +#define BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE 0x00000C00 +#define BF_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE(v) \ + (((v) << 10) & BM_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__1 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__2 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_GLOBAL_ALPHA_MODE__3 0x0 +#define BP_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE 8 +#define BM_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE 0x00000300 +#define BF_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE(v) \ + (((v) << 8) & BM_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S1_S0_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_A_CTRL_RSVD1 0x00000080 +#define BF_PXP_ALPHA_A_CTRL_RSVD1(v) \ + (((v) << 7) & BM_PXP_ALPHA_A_CTRL_RSVD1) +#define BM_PXP_ALPHA_A_CTRL_S0_COLOR_MODE 0x00000040 +#define BF_PXP_ALPHA_A_CTRL_S0_COLOR_MODE(v) \ + (((v) << 6) & BM_PXP_ALPHA_A_CTRL_S0_COLOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE 0x00000020 +#define BF_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE(v) \ + (((v) << 5) & BM_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE 3 +#define BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE 0x00000018 +#define BF_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE(v) \ + (((v) << 3) & BM_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S0_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE 1 +#define BM_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE 0x00000006 +#define BF_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE(v) \ + (((v) << 1) & BM_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE) +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_A_CTRL_S0_S1_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE 0x00000001 +#define BF_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE) +#define BV_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE__0 0x0 +#define BV_PXP_ALPHA_A_CTRL_POTER_DUFF_ENABLE__1 0x1 + +#define HW_PXP_ALPHA_B_CTRL (0x000002a0) + +#define BP_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA 24 +#define BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA 0xFF000000 +#define BF_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA(v) \ + (((v) << 24) & BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA 16 +#define BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA 0x00FF0000 +#define BF_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA(v) \ + (((v) << 16) & BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA) +#define BP_PXP_ALPHA_B_CTRL_RSVD0 14 +#define BM_PXP_ALPHA_B_CTRL_RSVD0 0x0000C000 +#define BF_PXP_ALPHA_B_CTRL_RSVD0(v) \ + (((v) << 14) & BM_PXP_ALPHA_B_CTRL_RSVD0) +#define BM_PXP_ALPHA_B_CTRL_S1_COLOR_MODE 0x00002000 +#define BF_PXP_ALPHA_B_CTRL_S1_COLOR_MODE(v) \ + (((v) << 13) & BM_PXP_ALPHA_B_CTRL_S1_COLOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE 0x00001000 +#define BF_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE(v) \ + (((v) << 12) & BM_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE 10 +#define BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE 0x00000C00 +#define BF_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE(v) \ + (((v) << 10) & BM_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S1_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE 8 +#define BM_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE 0x00000300 +#define BF_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE(v) \ + (((v) << 8) & BM_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S1_S0_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_B_CTRL_RSVD1 0x00000080 +#define BF_PXP_ALPHA_B_CTRL_RSVD1(v) \ + (((v) << 7) & BM_PXP_ALPHA_B_CTRL_RSVD1) +#define BM_PXP_ALPHA_B_CTRL_S0_COLOR_MODE 0x00000040 +#define BF_PXP_ALPHA_B_CTRL_S0_COLOR_MODE(v) \ + (((v) << 6) & BM_PXP_ALPHA_B_CTRL_S0_COLOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_COLOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_COLOR_MODE__1 0x1 +#define BM_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE 0x00000020 +#define BF_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE(v) \ + (((v) << 5) & BM_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_ALPHA_MODE__1 0x1 +#define BP_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE 3 +#define BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE 0x00000018 +#define BF_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE(v) \ + (((v) << 3) & BM_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S0_GLOBAL_ALPHA_MODE__3 0x3 +#define BP_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE 1 +#define BM_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE 0x00000006 +#define BF_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE(v) \ + (((v) << 1) & BM_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE) +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__1 0x1 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__2 0x2 +#define BV_PXP_ALPHA_B_CTRL_S0_S1_FACTOR_MODE__3 0x3 +#define BM_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE 0x00000001 +#define BF_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE) +#define BV_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE__0 0x0 +#define BV_PXP_ALPHA_B_CTRL_POTER_DUFF_ENABLE__1 0x1 + +#define HW_PXP_ALPHA_B_CTRL_1 (0x000002b0) + +#define BP_PXP_ALPHA_B_CTRL_1_RSVD0 8 +#define BM_PXP_ALPHA_B_CTRL_1_RSVD0 0xFFFFFF00 +#define BF_PXP_ALPHA_B_CTRL_1_RSVD0(v) \ + (((v) << 8) & BM_PXP_ALPHA_B_CTRL_1_RSVD0) +#define BP_PXP_ALPHA_B_CTRL_1_ROP 4 +#define BM_PXP_ALPHA_B_CTRL_1_ROP 0x000000F0 +#define BF_PXP_ALPHA_B_CTRL_1_ROP(v) \ + (((v) << 4) & BM_PXP_ALPHA_B_CTRL_1_ROP) +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKAS 0x0 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKNOTAS 0x1 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MASKASNOT 0x2 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGEAS 0x3 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGENOTAS 0x4 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__MERGEASNOT 0x5 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTCOPYAS 0x6 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOT 0x7 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTMASKAS 0x8 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTMERGEAS 0x9 +#define BV_PXP_ALPHA_B_CTRL_1_ROP__XORAS 0xA +#define BV_PXP_ALPHA_B_CTRL_1_ROP__NOTXORAS 0xB +#define BP_PXP_ALPHA_B_CTRL_1_RSVD1 2 +#define BM_PXP_ALPHA_B_CTRL_1_RSVD1 0x0000000C +#define BF_PXP_ALPHA_B_CTRL_1_RSVD1(v) \ + (((v) << 2) & BM_PXP_ALPHA_B_CTRL_1_RSVD1) +#define BM_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE 0x00000002 +#define BF_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE(v) \ + (((v) << 1) & BM_PXP_ALPHA_B_CTRL_1_OL_CLRKEY_ENABLE) +#define BM_PXP_ALPHA_B_CTRL_1_ROP_ENABLE 0x00000001 +#define BF_PXP_ALPHA_B_CTRL_1_ROP_ENABLE(v) \ + (((v) << 0) & BM_PXP_ALPHA_B_CTRL_1_ROP_ENABLE) + +#define HW_PXP_PS_BACKGROUND_1 (0x000002c0) + +#define BP_PXP_PS_BACKGROUND_1_RSVD 24 +#define BM_PXP_PS_BACKGROUND_1_RSVD 0xFF000000 +#define BF_PXP_PS_BACKGROUND_1_RSVD(v) \ + (((v) << 24) & BM_PXP_PS_BACKGROUND_1_RSVD) +#define BP_PXP_PS_BACKGROUND_1_COLOR 0 +#define BM_PXP_PS_BACKGROUND_1_COLOR 0x00FFFFFF +#define BF_PXP_PS_BACKGROUND_1_COLOR(v) \ + (((v) << 0) & BM_PXP_PS_BACKGROUND_1_COLOR) + +#define HW_PXP_PS_CLRKEYLOW_1 (0x000002d0) + +#define BP_PXP_PS_CLRKEYLOW_1_RSVD1 24 +#define BM_PXP_PS_CLRKEYLOW_1_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYLOW_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYLOW_1_RSVD1) +#define BP_PXP_PS_CLRKEYLOW_1_PIXEL 0 +#define BM_PXP_PS_CLRKEYLOW_1_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYLOW_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYLOW_1_PIXEL) + +#define HW_PXP_PS_CLRKEYHIGH_1 (0x000002e0) + +#define BP_PXP_PS_CLRKEYHIGH_1_RSVD1 24 +#define BM_PXP_PS_CLRKEYHIGH_1_RSVD1 0xFF000000 +#define BF_PXP_PS_CLRKEYHIGH_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_PS_CLRKEYHIGH_1_RSVD1) +#define BP_PXP_PS_CLRKEYHIGH_1_PIXEL 0 +#define BM_PXP_PS_CLRKEYHIGH_1_PIXEL 0x00FFFFFF +#define BF_PXP_PS_CLRKEYHIGH_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_PS_CLRKEYHIGH_1_PIXEL) + +#define HW_PXP_AS_CLRKEYLOW_1 (0x000002f0) + +#define BP_PXP_AS_CLRKEYLOW_1_RSVD1 24 +#define BM_PXP_AS_CLRKEYLOW_1_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYLOW_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYLOW_1_RSVD1) +#define BP_PXP_AS_CLRKEYLOW_1_PIXEL 0 +#define BM_PXP_AS_CLRKEYLOW_1_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYLOW_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYLOW_1_PIXEL) + +#define HW_PXP_AS_CLRKEYHIGH_1 (0x00000300) + +#define BP_PXP_AS_CLRKEYHIGH_1_RSVD1 24 +#define BM_PXP_AS_CLRKEYHIGH_1_RSVD1 0xFF000000 +#define BF_PXP_AS_CLRKEYHIGH_1_RSVD1(v) \ + (((v) << 24) & BM_PXP_AS_CLRKEYHIGH_1_RSVD1) +#define BP_PXP_AS_CLRKEYHIGH_1_PIXEL 0 +#define BM_PXP_AS_CLRKEYHIGH_1_PIXEL 0x00FFFFFF +#define BF_PXP_AS_CLRKEYHIGH_1_PIXEL(v) \ + (((v) << 0) & BM_PXP_AS_CLRKEYHIGH_1_PIXEL) + +#define HW_PXP_CTRL2 (0x00000310) +#define HW_PXP_CTRL2_SET (0x00000314) +#define HW_PXP_CTRL2_CLR (0x00000318) +#define HW_PXP_CTRL2_TOG (0x0000031c) + +#define BP_PXP_CTRL2_RSVD3 28 +#define BM_PXP_CTRL2_RSVD3 0xF0000000 +#define BF_PXP_CTRL2_RSVD3(v) \ + (((v) << 28) & BM_PXP_CTRL2_RSVD3) +#define BM_PXP_CTRL2_ENABLE_ROTATE1 0x08000000 +#define BF_PXP_CTRL2_ENABLE_ROTATE1(v) \ + (((v) << 27) & BM_PXP_CTRL2_ENABLE_ROTATE1) +#define BM_PXP_CTRL2_ENABLE_ROTATE0 0x04000000 +#define BF_PXP_CTRL2_ENABLE_ROTATE0(v) \ + (((v) << 26) & BM_PXP_CTRL2_ENABLE_ROTATE0) +#define BM_PXP_CTRL2_ENABLE_LUT 0x02000000 +#define BF_PXP_CTRL2_ENABLE_LUT(v) \ + (((v) << 25) & BM_PXP_CTRL2_ENABLE_LUT) +#define BM_PXP_CTRL2_ENABLE_CSC2 0x01000000 +#define BF_PXP_CTRL2_ENABLE_CSC2(v) \ + (((v) << 24) & BM_PXP_CTRL2_ENABLE_CSC2) +#define BM_PXP_CTRL2_BLOCK_SIZE 0x00800000 +#define BF_PXP_CTRL2_BLOCK_SIZE(v) \ + (((v) << 23) & BM_PXP_CTRL2_BLOCK_SIZE) +#define BV_PXP_CTRL2_BLOCK_SIZE__8X8 0x0 +#define BV_PXP_CTRL2_BLOCK_SIZE__16X16 0x1 +#define BM_PXP_CTRL2_RSVD2 0x00400000 +#define BF_PXP_CTRL2_RSVD2(v) \ + (((v) << 22) & BM_PXP_CTRL2_RSVD2) +#define BM_PXP_CTRL2_ENABLE_ALPHA_B 0x00200000 +#define BF_PXP_CTRL2_ENABLE_ALPHA_B(v) \ + (((v) << 21) & BM_PXP_CTRL2_ENABLE_ALPHA_B) +#define BM_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE 0x00100000 +#define BF_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE(v) \ + (((v) << 20) & BM_PXP_CTRL2_ENABLE_INPUT_FETCH_STORE) +#define BM_PXP_CTRL2_ENABLE_WFE_B 0x00080000 +#define BF_PXP_CTRL2_ENABLE_WFE_B(v) \ + (((v) << 19) & BM_PXP_CTRL2_ENABLE_WFE_B) +#define BM_PXP_CTRL2_ENABLE_WFE_A 0x00040000 +#define BF_PXP_CTRL2_ENABLE_WFE_A(v) \ + (((v) << 18) & BM_PXP_CTRL2_ENABLE_WFE_A) +#define BM_PXP_CTRL2_ENABLE_DITHER 0x00020000 +#define BF_PXP_CTRL2_ENABLE_DITHER(v) \ + (((v) << 17) & BM_PXP_CTRL2_ENABLE_DITHER) +#define BM_PXP_CTRL2_RSVD1 0x00010000 +#define BF_PXP_CTRL2_RSVD1(v) \ + (((v) << 16) & BM_PXP_CTRL2_RSVD1) +#define BM_PXP_CTRL2_VFLIP1 0x00008000 +#define BF_PXP_CTRL2_VFLIP1(v) \ + (((v) << 15) & BM_PXP_CTRL2_VFLIP1) +#define BM_PXP_CTRL2_HFLIP1 0x00004000 +#define BF_PXP_CTRL2_HFLIP1(v) \ + (((v) << 14) & BM_PXP_CTRL2_HFLIP1) +#define BP_PXP_CTRL2_ROTATE1 12 +#define BM_PXP_CTRL2_ROTATE1 0x00003000 +#define BF_PXP_CTRL2_ROTATE1(v) \ + (((v) << 12) & BM_PXP_CTRL2_ROTATE1) +#define BV_PXP_CTRL2_ROTATE1__ROT_0 0x0 +#define BV_PXP_CTRL2_ROTATE1__ROT_90 0x1 +#define BV_PXP_CTRL2_ROTATE1__ROT_180 0x2 +#define BV_PXP_CTRL2_ROTATE1__ROT_270 0x3 +#define BM_PXP_CTRL2_VFLIP0 0x00000800 +#define BF_PXP_CTRL2_VFLIP0(v) \ + (((v) << 11) & BM_PXP_CTRL2_VFLIP0) +#define BM_PXP_CTRL2_HFLIP0 0x00000400 +#define BF_PXP_CTRL2_HFLIP0(v) \ + (((v) << 10) & BM_PXP_CTRL2_HFLIP0) +#define BP_PXP_CTRL2_ROTATE0 8 +#define BM_PXP_CTRL2_ROTATE0 0x00000300 +#define BF_PXP_CTRL2_ROTATE0(v) \ + (((v) << 8) & BM_PXP_CTRL2_ROTATE0) +#define BV_PXP_CTRL2_ROTATE0__ROT_0 0x0 +#define BV_PXP_CTRL2_ROTATE0__ROT_90 0x1 +#define BV_PXP_CTRL2_ROTATE0__ROT_180 0x2 +#define BV_PXP_CTRL2_ROTATE0__ROT_270 0x3 +#define BP_PXP_CTRL2_RSVD0 1 +#define BM_PXP_CTRL2_RSVD0 0x000000FE +#define BF_PXP_CTRL2_RSVD0(v) \ + (((v) << 1) & BM_PXP_CTRL2_RSVD0) +#define BM_PXP_CTRL2_ENABLE 0x00000001 +#define BF_PXP_CTRL2_ENABLE(v) \ + (((v) << 0) & BM_PXP_CTRL2_ENABLE) + +#define HW_PXP_POWER_REG0 (0x00000320) + +#define BP_PXP_POWER_REG0_CTRL 12 +#define BM_PXP_POWER_REG0_CTRL 0xFFFFF000 +#define BF_PXP_POWER_REG0_CTRL(v) \ + (((v) << 12) & BM_PXP_POWER_REG0_CTRL) +#define BP_PXP_POWER_REG0_ROT0_MEM_LP_STATE 9 +#define BM_PXP_POWER_REG0_ROT0_MEM_LP_STATE 0x00000E00 +#define BF_PXP_POWER_REG0_ROT0_MEM_LP_STATE(v) \ + (((v) << 9) & BM_PXP_POWER_REG0_ROT0_MEM_LP_STATE) +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG0_ROT0_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN 6 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN 0x000001C0 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN(v) \ + (((v) << 6) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY1_BANKN__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN 3 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN 0x00000038 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN(v) \ + (((v) << 3) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANKN__SD 0x4 +#define BP_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0 0 +#define BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0 0x00000007 +#define BF_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0(v) \ + (((v) << 0) & BM_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0) +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__NONE 0x0 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__LS 0x1 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__DS 0x2 +#define BV_PXP_POWER_REG0_LUT_LP_STATE_WAY0_BANK0__SD 0x4 + +#define HW_PXP_POWER_REG1 (0x00000330) + +#define BP_PXP_POWER_REG1_RSVD0 24 +#define BM_PXP_POWER_REG1_RSVD0 0xFF000000 +#define BF_PXP_POWER_REG1_RSVD0(v) \ + (((v) << 24) & BM_PXP_POWER_REG1_RSVD0) +#define BP_PXP_POWER_REG1_ALU_B_MEM_LP_STATE 21 +#define BM_PXP_POWER_REG1_ALU_B_MEM_LP_STATE 0x00E00000 +#define BF_PXP_POWER_REG1_ALU_B_MEM_LP_STATE(v) \ + (((v) << 21) & BM_PXP_POWER_REG1_ALU_B_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ALU_B_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_ALU_A_MEM_LP_STATE 18 +#define BM_PXP_POWER_REG1_ALU_A_MEM_LP_STATE 0x001C0000 +#define BF_PXP_POWER_REG1_ALU_A_MEM_LP_STATE(v) \ + (((v) << 18) & BM_PXP_POWER_REG1_ALU_A_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ALU_A_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE 15 +#define BM_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE 0x00038000 +#define BF_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE(v) \ + (((v) << 15) & BM_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH2_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE 12 +#define BM_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE 0x00007000 +#define BF_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE(v) \ + (((v) << 12) & BM_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH1_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE 9 +#define BM_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE 0x00000E00 +#define BF_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE(v) \ + (((v) << 9) & BM_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_ERR1_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE 6 +#define BM_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE 0x000001C0 +#define BF_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE(v) \ + (((v) << 6) & BM_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_ERR0_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE 3 +#define BM_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE 0x00000038 +#define BF_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE(v) \ + (((v) << 3) & BM_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_DITH0_LUT_MEM_LP_STATE__SD 0x4 +#define BP_PXP_POWER_REG1_ROT1_MEM_LP_STATE 0 +#define BM_PXP_POWER_REG1_ROT1_MEM_LP_STATE 0x00000007 +#define BF_PXP_POWER_REG1_ROT1_MEM_LP_STATE(v) \ + (((v) << 0) & BM_PXP_POWER_REG1_ROT1_MEM_LP_STATE) +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__NONE 0x0 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__LS 0x1 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__DS 0x2 +#define BV_PXP_POWER_REG1_ROT1_MEM_LP_STATE__SD 0x4 + +#define HW_PXP_DATA_PATH_CTRL0 (0x00000340) +#define HW_PXP_DATA_PATH_CTRL0_SET (0x00000344) +#define HW_PXP_DATA_PATH_CTRL0_CLR (0x00000348) +#define HW_PXP_DATA_PATH_CTRL0_TOG (0x0000034c) + +#define BP_PXP_DATA_PATH_CTRL0_MUX15_SEL 30 +#define BM_PXP_DATA_PATH_CTRL0_MUX15_SEL 0xC0000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX15_SEL(v) \ + (((v) << 30) & BM_PXP_DATA_PATH_CTRL0_MUX15_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX15_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX14_SEL 28 +#define BM_PXP_DATA_PATH_CTRL0_MUX14_SEL 0x30000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX14_SEL(v) \ + (((v) << 28) & BM_PXP_DATA_PATH_CTRL0_MUX14_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX14_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX13_SEL 26 +#define BM_PXP_DATA_PATH_CTRL0_MUX13_SEL 0x0C000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX13_SEL(v) \ + (((v) << 26) & BM_PXP_DATA_PATH_CTRL0_MUX13_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX13_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX12_SEL 24 +#define BM_PXP_DATA_PATH_CTRL0_MUX12_SEL 0x03000000 +#define BF_PXP_DATA_PATH_CTRL0_MUX12_SEL(v) \ + (((v) << 24) & BM_PXP_DATA_PATH_CTRL0_MUX12_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX12_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX11_SEL 22 +#define BM_PXP_DATA_PATH_CTRL0_MUX11_SEL 0x00C00000 +#define BF_PXP_DATA_PATH_CTRL0_MUX11_SEL(v) \ + (((v) << 22) & BM_PXP_DATA_PATH_CTRL0_MUX11_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX11_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX10_SEL 20 +#define BM_PXP_DATA_PATH_CTRL0_MUX10_SEL 0x00300000 +#define BF_PXP_DATA_PATH_CTRL0_MUX10_SEL(v) \ + (((v) << 20) & BM_PXP_DATA_PATH_CTRL0_MUX10_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX10_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX9_SEL 18 +#define BM_PXP_DATA_PATH_CTRL0_MUX9_SEL 0x000C0000 +#define BF_PXP_DATA_PATH_CTRL0_MUX9_SEL(v) \ + (((v) << 18) & BM_PXP_DATA_PATH_CTRL0_MUX9_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX9_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX8_SEL 16 +#define BM_PXP_DATA_PATH_CTRL0_MUX8_SEL 0x00030000 +#define BF_PXP_DATA_PATH_CTRL0_MUX8_SEL(v) \ + (((v) << 16) & BM_PXP_DATA_PATH_CTRL0_MUX8_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX8_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX7_SEL 14 +#define BM_PXP_DATA_PATH_CTRL0_MUX7_SEL 0x0000C000 +#define BF_PXP_DATA_PATH_CTRL0_MUX7_SEL(v) \ + (((v) << 14) & BM_PXP_DATA_PATH_CTRL0_MUX7_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX7_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX6_SEL 12 +#define BM_PXP_DATA_PATH_CTRL0_MUX6_SEL 0x00003000 +#define BF_PXP_DATA_PATH_CTRL0_MUX6_SEL(v) \ + (((v) << 12) & BM_PXP_DATA_PATH_CTRL0_MUX6_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX6_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX5_SEL 10 +#define BM_PXP_DATA_PATH_CTRL0_MUX5_SEL 0x00000C00 +#define BF_PXP_DATA_PATH_CTRL0_MUX5_SEL(v) \ + (((v) << 10) & BM_PXP_DATA_PATH_CTRL0_MUX5_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX5_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX4_SEL 8 +#define BM_PXP_DATA_PATH_CTRL0_MUX4_SEL 0x00000300 +#define BF_PXP_DATA_PATH_CTRL0_MUX4_SEL(v) \ + (((v) << 8) & BM_PXP_DATA_PATH_CTRL0_MUX4_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX4_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX3_SEL 6 +#define BM_PXP_DATA_PATH_CTRL0_MUX3_SEL 0x000000C0 +#define BF_PXP_DATA_PATH_CTRL0_MUX3_SEL(v) \ + (((v) << 6) & BM_PXP_DATA_PATH_CTRL0_MUX3_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX3_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX2_SEL 4 +#define BM_PXP_DATA_PATH_CTRL0_MUX2_SEL 0x00000030 +#define BF_PXP_DATA_PATH_CTRL0_MUX2_SEL(v) \ + (((v) << 4) & BM_PXP_DATA_PATH_CTRL0_MUX2_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX2_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX1_SEL 2 +#define BM_PXP_DATA_PATH_CTRL0_MUX1_SEL 0x0000000C +#define BF_PXP_DATA_PATH_CTRL0_MUX1_SEL(v) \ + (((v) << 2) & BM_PXP_DATA_PATH_CTRL0_MUX1_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX1_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL0_MUX0_SEL 0 +#define BM_PXP_DATA_PATH_CTRL0_MUX0_SEL 0x00000003 +#define BF_PXP_DATA_PATH_CTRL0_MUX0_SEL(v) \ + (((v) << 0) & BM_PXP_DATA_PATH_CTRL0_MUX0_SEL) +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL0_MUX0_SEL__3 0x3 + +#define HW_PXP_DATA_PATH_CTRL1 (0x00000350) +#define HW_PXP_DATA_PATH_CTRL1_SET (0x00000354) +#define HW_PXP_DATA_PATH_CTRL1_CLR (0x00000358) +#define HW_PXP_DATA_PATH_CTRL1_TOG (0x0000035c) + +#define BP_PXP_DATA_PATH_CTRL1_RSVD0 4 +#define BM_PXP_DATA_PATH_CTRL1_RSVD0 0xFFFFFFF0 +#define BF_PXP_DATA_PATH_CTRL1_RSVD0(v) \ + (((v) << 4) & BM_PXP_DATA_PATH_CTRL1_RSVD0) +#define BP_PXP_DATA_PATH_CTRL1_MUX17_SEL 2 +#define BM_PXP_DATA_PATH_CTRL1_MUX17_SEL 0x0000000C +#define BF_PXP_DATA_PATH_CTRL1_MUX17_SEL(v) \ + (((v) << 2) & BM_PXP_DATA_PATH_CTRL1_MUX17_SEL) +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL1_MUX17_SEL__3 0x3 +#define BP_PXP_DATA_PATH_CTRL1_MUX16_SEL 0 +#define BM_PXP_DATA_PATH_CTRL1_MUX16_SEL 0x00000003 +#define BF_PXP_DATA_PATH_CTRL1_MUX16_SEL(v) \ + (((v) << 0) & BM_PXP_DATA_PATH_CTRL1_MUX16_SEL) +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__0 0x0 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__1 0x1 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__2 0x2 +#define BV_PXP_DATA_PATH_CTRL1_MUX16_SEL__3 0x3 + +#define HW_PXP_INIT_MEM_CTRL (0x00000360) +#define HW_PXP_INIT_MEM_CTRL_SET (0x00000364) +#define HW_PXP_INIT_MEM_CTRL_CLR (0x00000368) +#define HW_PXP_INIT_MEM_CTRL_TOG (0x0000036c) + +#define BM_PXP_INIT_MEM_CTRL_START 0x80000000 +#define BF_PXP_INIT_MEM_CTRL_START(v) \ + (((v) << 31) & BM_PXP_INIT_MEM_CTRL_START) +#define BP_PXP_INIT_MEM_CTRL_SELECT 27 +#define BM_PXP_INIT_MEM_CTRL_SELECT 0x78000000 +#define BF_PXP_INIT_MEM_CTRL_SELECT(v) \ + (((v) << 27) & BM_PXP_INIT_MEM_CTRL_SELECT) +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_LUT 0x0 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_ERR0 0x1 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER0_ERR1 0x2 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER1_LUT 0x3 +#define BV_PXP_INIT_MEM_CTRL_SELECT__DITHER2_LUT 0x4 +#define BV_PXP_INIT_MEM_CTRL_SELECT__ALU_A 0x5 +#define BV_PXP_INIT_MEM_CTRL_SELECT__ALU_B 0x6 +#define BV_PXP_INIT_MEM_CTRL_SELECT__WFE_A_FETCH 0x7 +#define BV_PXP_INIT_MEM_CTRL_SELECT__WFE_B_FETCH 0x8 +#define BV_PXP_INIT_MEM_CTRL_SELECT__RESERVED 0x15 +#define BP_PXP_INIT_MEM_CTRL_RSVD0 16 +#define BM_PXP_INIT_MEM_CTRL_RSVD0 0x07FF0000 +#define BF_PXP_INIT_MEM_CTRL_RSVD0(v) \ + (((v) << 16) & BM_PXP_INIT_MEM_CTRL_RSVD0) +#define BP_PXP_INIT_MEM_CTRL_ADDR 0 +#define BM_PXP_INIT_MEM_CTRL_ADDR 0x0000FFFF +#define BF_PXP_INIT_MEM_CTRL_ADDR(v) \ + (((v) << 0) & BM_PXP_INIT_MEM_CTRL_ADDR) + +#define HW_PXP_INIT_MEM_DATA (0x00000370) + +#define BP_PXP_INIT_MEM_DATA_DATA 0 +#define BM_PXP_INIT_MEM_DATA_DATA 0xFFFFFFFF +#define BF_PXP_INIT_MEM_DATA_DATA(v) (v) + +#define HW_PXP_INIT_MEM_DATA_HIGH (0x00000380) + +#define BP_PXP_INIT_MEM_DATA_HIGH_DATA 0 +#define BM_PXP_INIT_MEM_DATA_HIGH_DATA 0xFFFFFFFF +#define BF_PXP_INIT_MEM_DATA_HIGH_DATA(v) (v) + +#define HW_PXP_IRQ_MASK (0x00000390) +#define HW_PXP_IRQ_MASK_SET (0x00000394) +#define HW_PXP_IRQ_MASK_CLR (0x00000398) +#define HW_PXP_IRQ_MASK_TOG (0x0000039c) + +#define BM_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN 0x80000000 +#define BF_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN(v) \ + (((v) << 31) & BM_PXP_IRQ_MASK_COMPRESS_DONE_IRQ_EN) +#define BP_PXP_IRQ_MASK_RSVD1 16 +#define BM_PXP_IRQ_MASK_RSVD1 0x7FFF0000 +#define BF_PXP_IRQ_MASK_RSVD1(v) \ + (((v) << 16) & BM_PXP_IRQ_MASK_RSVD1) +#define BM_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN 0x00008000 +#define BF_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN(v) \ + (((v) << 15) & BM_PXP_IRQ_MASK_WFE_B_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN 0x00004000 +#define BF_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN(v) \ + (((v) << 14) & BM_PXP_IRQ_MASK_WFE_A_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN 0x00002000 +#define BF_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN(v) \ + (((v) << 13) & BM_PXP_IRQ_MASK_DITHER_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN 0x00001000 +#define BF_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN(v) \ + (((v) << 12) & BM_PXP_IRQ_MASK_FIRST_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN 0x00000800 +#define BF_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN(v) \ + (((v) << 11) & BM_PXP_IRQ_MASK_WFE_B_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN 0x00000400 +#define BF_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN(v) \ + (((v) << 10) & BM_PXP_IRQ_MASK_WFE_B_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN 0x00000200 +#define BF_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN(v) \ + (((v) << 9) & BM_PXP_IRQ_MASK_WFE_A_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN 0x00000100 +#define BF_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN(v) \ + (((v) << 8) & BM_PXP_IRQ_MASK_WFE_A_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN 0x00000080 +#define BF_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN(v) \ + (((v) << 7) & BM_PXP_IRQ_MASK_DITHER_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN 0x00000040 +#define BF_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN(v) \ + (((v) << 6) & BM_PXP_IRQ_MASK_DITHER_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN 0x00000020 +#define BF_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN(v) \ + (((v) << 5) & BM_PXP_IRQ_MASK_DITHER_CH1_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN 0x00000010 +#define BF_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN(v) \ + (((v) << 4) & BM_PXP_IRQ_MASK_DITHER_CH0_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN 0x00000008 +#define BF_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN(v) \ + (((v) << 3) & BM_PXP_IRQ_MASK_FIRST_CH1_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN 0x00000004 +#define BF_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN(v) \ + (((v) << 2) & BM_PXP_IRQ_MASK_FIRST_CH0_STORE_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN 0x00000002 +#define BF_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN(v) \ + (((v) << 1) & BM_PXP_IRQ_MASK_FIRST_CH1_PREFETCH_IRQ_EN) +#define BM_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN 0x00000001 +#define BF_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN(v) \ + (((v) << 0) & BM_PXP_IRQ_MASK_FIRST_CH0_PREFETCH_IRQ_EN) + +#define HW_PXP_IRQ (0x000003a0) +#define HW_PXP_IRQ_SET (0x000003a4) +#define HW_PXP_IRQ_CLR (0x000003a8) +#define HW_PXP_IRQ_TOG (0x000003ac) + +#define BM_PXP_IRQ_COMPRESS_DONE_IRQ 0x80000000 +#define BF_PXP_IRQ_COMPRESS_DONE_IRQ(v) \ + (((v) << 31) & BM_PXP_IRQ_COMPRESS_DONE_IRQ) +#define BP_PXP_IRQ_RSVD1 16 +#define BM_PXP_IRQ_RSVD1 0x7FFF0000 +#define BF_PXP_IRQ_RSVD1(v) \ + (((v) << 16) & BM_PXP_IRQ_RSVD1) +#define BM_PXP_IRQ_WFE_B_STORE_IRQ 0x00008000 +#define BF_PXP_IRQ_WFE_B_STORE_IRQ(v) \ + (((v) << 15) & BM_PXP_IRQ_WFE_B_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_STORE_IRQ 0x00004000 +#define BF_PXP_IRQ_WFE_A_STORE_IRQ(v) \ + (((v) << 14) & BM_PXP_IRQ_WFE_A_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_STORE_IRQ 0x00002000 +#define BF_PXP_IRQ_DITHER_STORE_IRQ(v) \ + (((v) << 13) & BM_PXP_IRQ_DITHER_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_STORE_IRQ 0x00001000 +#define BF_PXP_IRQ_FIRST_STORE_IRQ(v) \ + (((v) << 12) & BM_PXP_IRQ_FIRST_STORE_IRQ) +#define BM_PXP_IRQ_WFE_B_CH1_STORE_IRQ 0x00000800 +#define BF_PXP_IRQ_WFE_B_CH1_STORE_IRQ(v) \ + (((v) << 11) & BM_PXP_IRQ_WFE_B_CH1_STORE_IRQ) +#define BM_PXP_IRQ_WFE_B_CH0_STORE_IRQ 0x00000400 +#define BF_PXP_IRQ_WFE_B_CH0_STORE_IRQ(v) \ + (((v) << 10) & BM_PXP_IRQ_WFE_B_CH0_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_CH1_STORE_IRQ 0x00000200 +#define BF_PXP_IRQ_WFE_A_CH1_STORE_IRQ(v) \ + (((v) << 9) & BM_PXP_IRQ_WFE_A_CH1_STORE_IRQ) +#define BM_PXP_IRQ_WFE_A_CH0_STORE_IRQ 0x00000100 +#define BF_PXP_IRQ_WFE_A_CH0_STORE_IRQ(v) \ + (((v) << 8) & BM_PXP_IRQ_WFE_A_CH0_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH1_STORE_IRQ 0x00000080 +#define BF_PXP_IRQ_DITHER_CH1_STORE_IRQ(v) \ + (((v) << 7) & BM_PXP_IRQ_DITHER_CH1_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH0_STORE_IRQ 0x00000040 +#define BF_PXP_IRQ_DITHER_CH0_STORE_IRQ(v) \ + (((v) << 6) & BM_PXP_IRQ_DITHER_CH0_STORE_IRQ) +#define BM_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ 0x00000020 +#define BF_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ(v) \ + (((v) << 5) & BM_PXP_IRQ_DITHER_CH1_PREFETCH_IRQ) +#define BM_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ 0x00000010 +#define BF_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ(v) \ + (((v) << 4) & BM_PXP_IRQ_DITHER_CH0_PREFETCH_IRQ) +#define BM_PXP_IRQ_FIRST_CH1_STORE_IRQ 0x00000008 +#define BF_PXP_IRQ_FIRST_CH1_STORE_IRQ(v) \ + (((v) << 3) & BM_PXP_IRQ_FIRST_CH1_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_CH0_STORE_IRQ 0x00000004 +#define BF_PXP_IRQ_FIRST_CH0_STORE_IRQ(v) \ + (((v) << 2) & BM_PXP_IRQ_FIRST_CH0_STORE_IRQ) +#define BM_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ 0x00000002 +#define BF_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ(v) \ + (((v) << 1) & BM_PXP_IRQ_FIRST_CH1_PREFETCH_IRQ) +#define BM_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ 0x00000001 +#define BF_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ(v) \ + (((v) << 0) & BM_PXP_IRQ_FIRST_CH0_PREFETCH_IRQ) + +#define HW_PXP_NEXT (0x00000400) + +#define BP_PXP_NEXT_POINTER 2 +#define BM_PXP_NEXT_POINTER 0xFFFFFFFC +#define BF_PXP_NEXT_POINTER(v) \ + (((v) << 2) & BM_PXP_NEXT_POINTER) +#define BM_PXP_NEXT_RSVD 0x00000002 +#define BF_PXP_NEXT_RSVD(v) \ + (((v) << 1) & BM_PXP_NEXT_RSVD) +#define BM_PXP_NEXT_ENABLED 0x00000001 +#define BF_PXP_NEXT_ENABLED(v) \ + (((v) << 0) & BM_PXP_NEXT_ENABLED) + +#define HW_PXP_DEBUGCTRL (0x00000410) + +#define BP_PXP_DEBUGCTRL_RSVD 12 +#define BM_PXP_DEBUGCTRL_RSVD 0xFFFFF000 +#define BF_PXP_DEBUGCTRL_RSVD(v) \ + (((v) << 12) & BM_PXP_DEBUGCTRL_RSVD) +#define BP_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT 8 +#define BM_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT 0x00000F00 +#define BF_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT(v) \ + (((v) << 8) & BM_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT) +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__NONE 0x0 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__MISS_CNT 0x1 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__HIT_CNT 0x2 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__LAT_CNT 0x4 +#define BV_PXP_DEBUGCTRL_LUT_CLR_STAT_CNT__MAX_LAT 0x8 +#define BP_PXP_DEBUGCTRL_SELECT 0 +#define BM_PXP_DEBUGCTRL_SELECT 0x000000FF +#define BF_PXP_DEBUGCTRL_SELECT(v) \ + (((v) << 0) & BM_PXP_DEBUGCTRL_SELECT) +#define BV_PXP_DEBUGCTRL_SELECT__NONE 0x0 +#define BV_PXP_DEBUGCTRL_SELECT__CTRL 0x1 +#define BV_PXP_DEBUGCTRL_SELECT__PSBUF 0x2 +#define BV_PXP_DEBUGCTRL_SELECT__PSBAX 0x3 +#define BV_PXP_DEBUGCTRL_SELECT__PSBAY 0x4 +#define BV_PXP_DEBUGCTRL_SELECT__ASBUF 0x5 +#define BV_PXP_DEBUGCTRL_SELECT__ROTATION 0x6 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF0 0x7 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF1 0x8 +#define BV_PXP_DEBUGCTRL_SELECT__OUTBUF2 0x9 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_STAT 0x10 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_MISS 0x11 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_HIT 0x12 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_LAT 0x13 +#define BV_PXP_DEBUGCTRL_SELECT__LUT_MAX_LAT 0x14 + +#define HW_PXP_DEBUG (0x00000420) + +#define BP_PXP_DEBUG_DATA 0 +#define BM_PXP_DEBUG_DATA 0xFFFFFFFF +#define BF_PXP_DEBUG_DATA(v) (v) + +#define HW_PXP_VERSION (0x00000430) + +#define BP_PXP_VERSION_MAJOR 24 +#define BM_PXP_VERSION_MAJOR 0xFF000000 +#define BF_PXP_VERSION_MAJOR(v) \ + (((v) << 24) & BM_PXP_VERSION_MAJOR) +#define BP_PXP_VERSION_MINOR 16 +#define BM_PXP_VERSION_MINOR 0x00FF0000 +#define BF_PXP_VERSION_MINOR(v) \ + (((v) << 16) & BM_PXP_VERSION_MINOR) +#define BP_PXP_VERSION_STEP 0 +#define BM_PXP_VERSION_STEP 0x0000FFFF +#define BF_PXP_VERSION_STEP(v) \ + (((v) << 0) & BM_PXP_VERSION_STEP) + +#endif /* __IMX_PXP_H__ */ diff --git a/drivers/media/platform/nxp/mx2_emmaprp.c b/drivers/media/platform/nxp/mx2_emmaprp.c new file mode 100644 index 000000000..3ce84d0f0 --- /dev/null +++ b/drivers/media/platform/nxp/mx2_emmaprp.c @@ -0,0 +1,912 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Support eMMa-PrP through mem2mem framework. + * + * eMMa-PrP is a piece of HW that allows fetching buffers + * from one memory location and do several operations on + * them such as scaling or format conversion giving, as a result + * a new processed buffer in another memory location. + * + * Based on mem2mem_testdev.c by Pawel Osciak. + * + * Copyright (c) 2011 Vista Silicon S.L. + * Javier Martin + */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define EMMAPRP_MODULE_NAME "mem2mem-emmaprp" + +MODULE_DESCRIPTION("Mem-to-mem device which supports eMMa-PrP present in mx2 SoCs"); +MODULE_AUTHOR("Javier Martin v4l2_dev, "%s: " fmt, __func__, ## arg) + +/* EMMA PrP */ +#define PRP_CNTL 0x00 +#define PRP_INTR_CNTL 0x04 +#define PRP_INTRSTATUS 0x08 +#define PRP_SOURCE_Y_PTR 0x0c +#define PRP_SOURCE_CB_PTR 0x10 +#define PRP_SOURCE_CR_PTR 0x14 +#define PRP_DEST_RGB1_PTR 0x18 +#define PRP_DEST_RGB2_PTR 0x1c +#define PRP_DEST_Y_PTR 0x20 +#define PRP_DEST_CB_PTR 0x24 +#define PRP_DEST_CR_PTR 0x28 +#define PRP_SRC_FRAME_SIZE 0x2c +#define PRP_DEST_CH1_LINE_STRIDE 0x30 +#define PRP_SRC_PIXEL_FORMAT_CNTL 0x34 +#define PRP_CH1_PIXEL_FORMAT_CNTL 0x38 +#define PRP_CH1_OUT_IMAGE_SIZE 0x3c +#define PRP_CH2_OUT_IMAGE_SIZE 0x40 +#define PRP_SRC_LINE_STRIDE 0x44 +#define PRP_CSC_COEF_012 0x48 +#define PRP_CSC_COEF_345 0x4c +#define PRP_CSC_COEF_678 0x50 +#define PRP_CH1_RZ_HORI_COEF1 0x54 +#define PRP_CH1_RZ_HORI_COEF2 0x58 +#define PRP_CH1_RZ_HORI_VALID 0x5c +#define PRP_CH1_RZ_VERT_COEF1 0x60 +#define PRP_CH1_RZ_VERT_COEF2 0x64 +#define PRP_CH1_RZ_VERT_VALID 0x68 +#define PRP_CH2_RZ_HORI_COEF1 0x6c +#define PRP_CH2_RZ_HORI_COEF2 0x70 +#define PRP_CH2_RZ_HORI_VALID 0x74 +#define PRP_CH2_RZ_VERT_COEF1 0x78 +#define PRP_CH2_RZ_VERT_COEF2 0x7c +#define PRP_CH2_RZ_VERT_VALID 0x80 + +#define PRP_CNTL_CH1EN (1 << 0) +#define PRP_CNTL_CH2EN (1 << 1) +#define PRP_CNTL_CSIEN (1 << 2) +#define PRP_CNTL_DATA_IN_YUV420 (0 << 3) +#define PRP_CNTL_DATA_IN_YUV422 (1 << 3) +#define PRP_CNTL_DATA_IN_RGB16 (2 << 3) +#define PRP_CNTL_DATA_IN_RGB32 (3 << 3) +#define PRP_CNTL_CH1_OUT_RGB8 (0 << 5) +#define PRP_CNTL_CH1_OUT_RGB16 (1 << 5) +#define PRP_CNTL_CH1_OUT_RGB32 (2 << 5) +#define PRP_CNTL_CH1_OUT_YUV422 (3 << 5) +#define PRP_CNTL_CH2_OUT_YUV420 (0 << 7) +#define PRP_CNTL_CH2_OUT_YUV422 (1 << 7) +#define PRP_CNTL_CH2_OUT_YUV444 (2 << 7) +#define PRP_CNTL_CH1_LEN (1 << 9) +#define PRP_CNTL_CH2_LEN (1 << 10) +#define PRP_CNTL_SKIP_FRAME (1 << 11) +#define PRP_CNTL_SWRST (1 << 12) +#define PRP_CNTL_CLKEN (1 << 13) +#define PRP_CNTL_WEN (1 << 14) +#define PRP_CNTL_CH1BYP (1 << 15) +#define PRP_CNTL_IN_TSKIP(x) ((x) << 16) +#define PRP_CNTL_CH1_TSKIP(x) ((x) << 19) +#define PRP_CNTL_CH2_TSKIP(x) ((x) << 22) +#define PRP_CNTL_INPUT_FIFO_LEVEL(x) ((x) << 25) +#define PRP_CNTL_RZ_FIFO_LEVEL(x) ((x) << 27) +#define PRP_CNTL_CH2B1EN (1 << 29) +#define PRP_CNTL_CH2B2EN (1 << 30) +#define PRP_CNTL_CH2FEN (1UL << 31) + +#define PRP_SIZE_HEIGHT(x) (x) +#define PRP_SIZE_WIDTH(x) ((x) << 16) + +/* IRQ Enable and status register */ +#define PRP_INTR_RDERR (1 << 0) +#define PRP_INTR_CH1WERR (1 << 1) +#define PRP_INTR_CH2WERR (1 << 2) +#define PRP_INTR_CH1FC (1 << 3) +#define PRP_INTR_CH2FC (1 << 5) +#define PRP_INTR_LBOVF (1 << 7) +#define PRP_INTR_CH2OVF (1 << 8) + +#define PRP_INTR_ST_RDERR (1 << 0) +#define PRP_INTR_ST_CH1WERR (1 << 1) +#define PRP_INTR_ST_CH2WERR (1 << 2) +#define PRP_INTR_ST_CH2B2CI (1 << 3) +#define PRP_INTR_ST_CH2B1CI (1 << 4) +#define PRP_INTR_ST_CH1B2CI (1 << 5) +#define PRP_INTR_ST_CH1B1CI (1 << 6) +#define PRP_INTR_ST_LBOVF (1 << 7) +#define PRP_INTR_ST_CH2OVF (1 << 8) + +struct emmaprp_fmt { + u32 fourcc; + /* Types the format can be used for */ + u32 types; +}; + +static struct emmaprp_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUV420, + .types = MEM2MEM_CAPTURE, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .types = MEM2MEM_OUTPUT, + }, +}; + +/* Per-queue, driver-specific private data */ +struct emmaprp_q_data { + unsigned int width; + unsigned int height; + unsigned int sizeimage; + struct emmaprp_fmt *fmt; +}; + +enum { + V4L2_M2M_SRC = 0, + V4L2_M2M_DST = 1, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static struct emmaprp_fmt *find_format(struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + unsigned int k; + + for (k = 0; k < NUM_FORMATS; k++) { + fmt = &formats[k]; + if (fmt->fourcc == f->fmt.pix.pixelformat) + break; + } + + if (k == NUM_FORMATS) + return NULL; + + return &formats[k]; +} + +struct emmaprp_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd; + + struct mutex dev_mutex; + spinlock_t irqlock; + + void __iomem *base_emma; + struct clk *clk_emma_ahb, *clk_emma_ipg; + + struct v4l2_m2m_dev *m2m_dev; +}; + +struct emmaprp_ctx { + struct v4l2_fh fh; + struct emmaprp_dev *dev; + /* Abort requested by m2m */ + int aborting; + struct emmaprp_q_data q_data[2]; +}; + +static struct emmaprp_q_data *get_q_data(struct emmaprp_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &(ctx->q_data[V4L2_M2M_SRC]); + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &(ctx->q_data[V4L2_M2M_DST]); + default: + BUG(); + } + return NULL; +} + +/* + * mem2mem callbacks + */ +static void emmaprp_job_abort(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_dev *pcdev = ctx->dev; + + ctx->aborting = 1; + + dprintk(pcdev, "Aborting task\n"); + + v4l2_m2m_job_finish(pcdev->m2m_dev, ctx->fh.m2m_ctx); +} + +static inline void emmaprp_dump_regs(struct emmaprp_dev *pcdev) +{ + dprintk(pcdev, + "eMMa-PrP Registers:\n" + " SOURCE_Y_PTR = 0x%08X\n" + " SRC_FRAME_SIZE = 0x%08X\n" + " DEST_Y_PTR = 0x%08X\n" + " DEST_CR_PTR = 0x%08X\n" + " DEST_CB_PTR = 0x%08X\n" + " CH2_OUT_IMAGE_SIZE = 0x%08X\n" + " CNTL = 0x%08X\n", + readl(pcdev->base_emma + PRP_SOURCE_Y_PTR), + readl(pcdev->base_emma + PRP_SRC_FRAME_SIZE), + readl(pcdev->base_emma + PRP_DEST_Y_PTR), + readl(pcdev->base_emma + PRP_DEST_CR_PTR), + readl(pcdev->base_emma + PRP_DEST_CB_PTR), + readl(pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE), + readl(pcdev->base_emma + PRP_CNTL)); +} + +static void emmaprp_device_run(void *priv) +{ + struct emmaprp_ctx *ctx = priv; + struct emmaprp_q_data *s_q_data, *d_q_data; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + struct emmaprp_dev *pcdev = ctx->dev; + unsigned int s_width, s_height; + unsigned int d_width, d_height; + unsigned int d_size; + dma_addr_t p_in, p_out; + u32 tmp; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + s_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + s_width = s_q_data->width; + s_height = s_q_data->height; + + d_q_data = get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + d_width = d_q_data->width; + d_height = d_q_data->height; + d_size = d_width * d_height; + + p_in = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + p_out = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + if (!p_in || !p_out) { + v4l2_err(&pcdev->v4l2_dev, + "Acquiring kernel pointers to buffers failed\n"); + return; + } + + /* Input frame parameters */ + writel(p_in, pcdev->base_emma + PRP_SOURCE_Y_PTR); + writel(PRP_SIZE_WIDTH(s_width) | PRP_SIZE_HEIGHT(s_height), + pcdev->base_emma + PRP_SRC_FRAME_SIZE); + + /* Output frame parameters */ + writel(p_out, pcdev->base_emma + PRP_DEST_Y_PTR); + writel(p_out + d_size, pcdev->base_emma + PRP_DEST_CB_PTR); + writel(p_out + d_size + (d_size >> 2), + pcdev->base_emma + PRP_DEST_CR_PTR); + writel(PRP_SIZE_WIDTH(d_width) | PRP_SIZE_HEIGHT(d_height), + pcdev->base_emma + PRP_CH2_OUT_IMAGE_SIZE); + + /* IRQ configuration */ + tmp = readl(pcdev->base_emma + PRP_INTR_CNTL); + writel(tmp | PRP_INTR_RDERR | + PRP_INTR_CH2WERR | + PRP_INTR_CH2FC, + pcdev->base_emma + PRP_INTR_CNTL); + + emmaprp_dump_regs(pcdev); + + /* Enable transfer */ + tmp = readl(pcdev->base_emma + PRP_CNTL); + writel(tmp | PRP_CNTL_CH2_OUT_YUV420 | + PRP_CNTL_DATA_IN_YUV422 | + PRP_CNTL_CH2EN, + pcdev->base_emma + PRP_CNTL); +} + +static irqreturn_t emmaprp_irq(int irq_emma, void *data) +{ + struct emmaprp_dev *pcdev = data; + struct emmaprp_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + unsigned long flags; + u32 irqst; + + /* Check irq flags and clear irq */ + irqst = readl(pcdev->base_emma + PRP_INTRSTATUS); + writel(irqst, pcdev->base_emma + PRP_INTRSTATUS); + dprintk(pcdev, "irqst = 0x%08x\n", irqst); + + curr_ctx = v4l2_m2m_get_curr_priv(pcdev->m2m_dev); + if (curr_ctx == NULL) { + pr_err("Instance released before the end of transaction\n"); + return IRQ_HANDLED; + } + + if (!curr_ctx->aborting) { + if ((irqst & PRP_INTR_ST_RDERR) || + (irqst & PRP_INTR_ST_CH2WERR)) { + pr_err("PrP bus error occurred, this transfer is probably corrupted\n"); + writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); + } else if (irqst & PRP_INTR_ST_CH2B1CI) { /* buffer ready */ + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + dst_vb->flags &= + ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->flags |= + src_vb->flags + & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->timecode = src_vb->timecode; + + spin_lock_irqsave(&pcdev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&pcdev->irqlock, flags); + } + } + + v4l2_m2m_job_finish(pcdev->m2m_dev, curr_ctx->fh.m2m_ctx); + return IRQ_HANDLED; +} + +/* + * video ioctls + */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, MEM2MEM_NAME, sizeof(cap->driver)); + strscpy(cap->card, MEM2MEM_NAME, sizeof(cap->card)); + return 0; +} + +static int enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + int i, num; + struct emmaprp_fmt *fmt; + + num = 0; + + for (i = 0; i < NUM_FORMATS; ++i) { + if (formats[i].types & type) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index */ + ++num; + } + } + + if (i < NUM_FORMATS) { + /* Format found */ + fmt = &formats[i]; + f->pixelformat = fmt->fourcc; + return 0; + } + + /* Format not found */ + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_CAPTURE); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return enum_fmt(f, MEM2MEM_OUTPUT); +} + +static int vidioc_g_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct emmaprp_q_data *q_data; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + + f->fmt.pix.width = q_data->width; + f->fmt.pix.height = q_data->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = q_data->fmt->fourcc; + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) + f->fmt.pix.bytesperline = q_data->width * 3 / 2; + else /* YUYV */ + f->fmt.pix.bytesperline = q_data->width * 2; + f->fmt.pix.sizeimage = q_data->sizeimage; + + return 0; +} + +static int vidioc_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + return vidioc_g_fmt(priv, f); +} + +static int vidioc_try_fmt(struct v4l2_format *f) +{ + enum v4l2_field field; + + + if (!find_format(f)) + return -EINVAL; + + field = f->fmt.pix.field; + if (field == V4L2_FIELD_ANY) + field = V4L2_FIELD_NONE; + else if (V4L2_FIELD_NONE != field) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported */ + f->fmt.pix.field = field; + + if (f->fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420) { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_YUV420, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 3 / 2; + } else { + v4l_bound_align_image(&f->fmt.pix.width, MIN_W, MAX_W, + W_ALIGN_OTHERS, &f->fmt.pix.height, + MIN_H, MAX_H, H_ALIGN, S_ALIGN); + f->fmt.pix.bytesperline = f->fmt.pix.width * 2; + } + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_CAPTURE)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct emmaprp_fmt *fmt; + struct emmaprp_ctx *ctx = priv; + + fmt = find_format(f); + if (!fmt || !(fmt->types & MEM2MEM_OUTPUT)) { + v4l2_err(&ctx->dev->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f); +} + +static int vidioc_s_fmt(struct emmaprp_ctx *ctx, struct v4l2_format *f) +{ + struct emmaprp_q_data *q_data; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + if (!q_data) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = vidioc_try_fmt(f); + if (ret) + return ret; + + q_data->fmt = find_format(f); + q_data->width = f->fmt.pix.width; + q_data->height = f->fmt.pix.height; + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + q_data->sizeimage = q_data->width * q_data->height * 3 / 2; + else /* YUYV */ + q_data->sizeimage = q_data->width * q_data->height * 2; + + dprintk(ctx->dev, + "Setting format for type %d, wxh: %dx%d, fmt: %d\n", + f->type, q_data->width, q_data->height, q_data->fmt->fourcc); + + return 0; +} + +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static int vidioc_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = vidioc_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + return vidioc_s_fmt(priv, f); +} + +static const struct v4l2_ioctl_ops emmaprp_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + + +/* + * Queue operations + */ +static int emmaprp_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vq); + struct emmaprp_q_data *q_data; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + + if (q_data->fmt->fourcc == V4L2_PIX_FMT_YUV420) + size = q_data->width * q_data->height * 3 / 2; + else + size = q_data->width * q_data->height * 2; + + while (size * count > MEM2MEM_VID_MEM_LIMIT) + (count)--; + + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); + + return 0; +} + +static int emmaprp_buf_prepare(struct vb2_buffer *vb) +{ + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct emmaprp_q_data *q_data; + + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); + + q_data = get_q_data(ctx, vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, + "%s data will not fit into plane(%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->sizeimage); + + return 0; +} + +static void emmaprp_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct emmaprp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static const struct vb2_ops emmaprp_qops = { + .queue_setup = emmaprp_queue_setup, + .buf_prepare = emmaprp_buf_prepare, + .buf_queue = emmaprp_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct emmaprp_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &emmaprp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->dev = ctx->dev->v4l2_dev.dev; + src_vq->lock = &ctx->dev->dev_mutex; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &emmaprp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + dst_vq->lock = &ctx->dev->dev_mutex; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int emmaprp_open(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx; + + ctx = kzalloc(sizeof *ctx, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = pcdev; + + if (mutex_lock_interruptible(&pcdev->dev_mutex)) { + kfree(ctx); + return -ERESTARTSYS; + } + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(pcdev->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + int ret = PTR_ERR(ctx->fh.m2m_ctx); + + mutex_unlock(&pcdev->dev_mutex); + kfree(ctx); + return ret; + } + + clk_prepare_enable(pcdev->clk_emma_ipg); + clk_prepare_enable(pcdev->clk_emma_ahb); + ctx->q_data[V4L2_M2M_SRC].fmt = &formats[1]; + ctx->q_data[V4L2_M2M_DST].fmt = &formats[0]; + v4l2_fh_add(&ctx->fh); + mutex_unlock(&pcdev->dev_mutex); + + dprintk(pcdev, "Created instance %p, m2m_ctx: %p\n", ctx, ctx->fh.m2m_ctx); + + return 0; +} + +static int emmaprp_release(struct file *file) +{ + struct emmaprp_dev *pcdev = video_drvdata(file); + struct emmaprp_ctx *ctx = file->private_data; + + dprintk(pcdev, "Releasing instance %p\n", ctx); + + mutex_lock(&pcdev->dev_mutex); + clk_disable_unprepare(pcdev->clk_emma_ahb); + clk_disable_unprepare(pcdev->clk_emma_ipg); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&pcdev->dev_mutex); + kfree(ctx); + + return 0; +} + +static const struct v4l2_file_operations emmaprp_fops = { + .owner = THIS_MODULE, + .open = emmaprp_open, + .release = emmaprp_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device emmaprp_videodev = { + .name = MEM2MEM_NAME, + .fops = &emmaprp_fops, + .ioctl_ops = &emmaprp_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = emmaprp_device_run, + .job_abort = emmaprp_job_abort, +}; + +static int emmaprp_probe(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev; + struct video_device *vfd; + int irq, ret; + + pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) + return -ENOMEM; + + spin_lock_init(&pcdev->irqlock); + + pcdev->clk_emma_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(pcdev->clk_emma_ipg)) { + return PTR_ERR(pcdev->clk_emma_ipg); + } + + pcdev->clk_emma_ahb = devm_clk_get(&pdev->dev, "ahb"); + if (IS_ERR(pcdev->clk_emma_ahb)) + return PTR_ERR(pcdev->clk_emma_ahb); + + pcdev->base_emma = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(pcdev->base_emma)) + return PTR_ERR(pcdev->base_emma); + + ret = v4l2_device_register(&pdev->dev, &pcdev->v4l2_dev); + if (ret) + return ret; + + mutex_init(&pcdev->dev_mutex); + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&pcdev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_dev; + } + + *vfd = emmaprp_videodev; + vfd->lock = &pcdev->dev_mutex; + vfd->v4l2_dev = &pcdev->v4l2_dev; + + video_set_drvdata(vfd, pcdev); + pcdev->vfd = vfd; + v4l2_info(&pcdev->v4l2_dev, EMMAPRP_MODULE_NAME + " Device registered as /dev/video%d\n", vfd->num); + + platform_set_drvdata(pdev, pcdev); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto rel_vdev; + } + + ret = devm_request_irq(&pdev->dev, irq, emmaprp_irq, 0, + dev_name(&pdev->dev), pcdev); + if (ret) + goto rel_vdev; + + pcdev->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(pcdev->m2m_dev)) { + v4l2_err(&pcdev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(pcdev->m2m_dev); + goto rel_vdev; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&pcdev->v4l2_dev, "Failed to register video device\n"); + goto rel_m2m; + } + + return 0; + + +rel_m2m: + v4l2_m2m_release(pcdev->m2m_dev); +rel_vdev: + video_device_release(vfd); +unreg_dev: + v4l2_device_unregister(&pcdev->v4l2_dev); + + mutex_destroy(&pcdev->dev_mutex); + + return ret; +} + +static int emmaprp_remove(struct platform_device *pdev) +{ + struct emmaprp_dev *pcdev = platform_get_drvdata(pdev); + + v4l2_info(&pcdev->v4l2_dev, "Removing " EMMAPRP_MODULE_NAME); + + video_unregister_device(pcdev->vfd); + v4l2_m2m_release(pcdev->m2m_dev); + v4l2_device_unregister(&pcdev->v4l2_dev); + mutex_destroy(&pcdev->dev_mutex); + + return 0; +} + +static struct platform_driver emmaprp_pdrv = { + .probe = emmaprp_probe, + .remove = emmaprp_remove, + .driver = { + .name = MEM2MEM_NAME, + }, +}; +module_platform_driver(emmaprp_pdrv); diff --git a/drivers/media/platform/qcom/Kconfig b/drivers/media/platform/qcom/Kconfig new file mode 100644 index 000000000..cc5799b9e --- /dev/null +++ b/drivers/media/platform/qcom/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Qualcomm media platform drivers" + +source "drivers/media/platform/qcom/camss/Kconfig" +source "drivers/media/platform/qcom/venus/Kconfig" diff --git a/drivers/media/platform/qcom/Makefile b/drivers/media/platform/qcom/Makefile new file mode 100644 index 000000000..4f055c396 --- /dev/null +++ b/drivers/media/platform/qcom/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += camss/ +obj-y += venus/ diff --git a/drivers/media/platform/qcom/camss/Kconfig b/drivers/media/platform/qcom/camss/Kconfig new file mode 100644 index 000000000..4eda48cb1 --- /dev/null +++ b/drivers/media/platform/qcom/camss/Kconfig @@ -0,0 +1,9 @@ +config VIDEO_QCOM_CAMSS + tristate "Qualcomm V4L2 Camera Subsystem driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_SG + select V4L2_FWNODE diff --git a/drivers/media/platform/qcom/camss/Makefile b/drivers/media/platform/qcom/camss/Makefile new file mode 100644 index 000000000..4e2222358 --- /dev/null +++ b/drivers/media/platform/qcom/camss/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for Qualcomm CAMSS driver + +qcom-camss-objs += \ + camss.o \ + camss-csid.o \ + camss-csid-4-1.o \ + camss-csid-4-7.o \ + camss-csid-gen2.o \ + camss-csiphy-2ph-1-0.o \ + camss-csiphy-3ph-1-0.o \ + camss-csiphy.o \ + camss-ispif.o \ + camss-vfe-4-1.o \ + camss-vfe-4-7.o \ + camss-vfe-4-8.o \ + camss-vfe-170.o \ + camss-vfe-480.o \ + camss-vfe-gen1.o \ + camss-vfe.o \ + camss-video.o \ + +obj-$(CONFIG_VIDEO_QCOM_CAMSS) += qcom-camss.o diff --git a/drivers/media/platform/qcom/camss/camss-csid-4-1.c b/drivers/media/platform/qcom/camss/camss-csid-4-1.c new file mode 100644 index 000000000..d2aec0679 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid-4-1.c @@ -0,0 +1,328 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csid-4-1.c + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (C) 2020 Linaro Ltd. + */ + +#include +#include +#include +#include +#include + +#include "camss-csid.h" +#include "camss-csid-gen1.h" +#include "camss.h" + +#define CAMSS_CSID_HW_VERSION 0x0 +#define CAMSS_CSID_CORE_CTRL_0 0x004 +#define CAMSS_CSID_CORE_CTRL_1 0x008 +#define CAMSS_CSID_RST_CMD 0x00c +#define CAMSS_CSID_CID_LUT_VC_n(n) (0x010 + 0x4 * (n)) +#define CAMSS_CSID_CID_n_CFG(n) (0x020 + 0x4 * (n)) +#define CAMSS_CSID_CID_n_CFG_ISPIF_EN BIT(0) +#define CAMSS_CSID_CID_n_CFG_RDI_EN BIT(1) +#define CAMSS_CSID_CID_n_CFG_DECODE_FORMAT_SHIFT 4 +#define CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_8 (PLAIN_FORMAT_PLAIN8 << 8) +#define CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_16 (PLAIN_FORMAT_PLAIN16 << 8) +#define CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_LSB (0 << 9) +#define CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_MSB (1 << 9) +#define CAMSS_CSID_CID_n_CFG_RDI_MODE_RAW_DUMP (0 << 10) +#define CAMSS_CSID_CID_n_CFG_RDI_MODE_PLAIN_PACKING (1 << 10) +#define CAMSS_CSID_IRQ_CLEAR_CMD 0x060 +#define CAMSS_CSID_IRQ_MASK 0x064 +#define CAMSS_CSID_IRQ_STATUS 0x068 +#define CAMSS_CSID_TG_CTRL 0x0a0 +#define CAMSS_CSID_TG_CTRL_DISABLE 0xa06436 +#define CAMSS_CSID_TG_CTRL_ENABLE 0xa06437 +#define CAMSS_CSID_TG_VC_CFG 0x0a4 +#define CAMSS_CSID_TG_VC_CFG_H_BLANKING 0x3ff +#define CAMSS_CSID_TG_VC_CFG_V_BLANKING 0x7f +#define CAMSS_CSID_TG_DT_n_CGG_0(n) (0x0ac + 0xc * (n)) +#define CAMSS_CSID_TG_DT_n_CGG_1(n) (0x0b0 + 0xc * (n)) +#define CAMSS_CSID_TG_DT_n_CGG_2(n) (0x0b4 + 0xc * (n)) + +static const struct csid_format csid_formats[] = { + { + MEDIA_BUS_FMT_UYVY8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_VYUY8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_YUYV8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_YVYU8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_SBGGR8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_Y10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, +}; + +static void csid_configure_stream(struct csid_device *csid, u8 enable) +{ + struct csid_testgen_config *tg = &csid->testgen; + u32 val; + + if (enable) { + struct v4l2_mbus_framefmt *input_format; + const struct csid_format *format; + u8 vc = 0; /* Virtual Channel 0 */ + u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */ + u8 dt_shift; + + if (tg->enabled) { + /* Config Test Generator */ + u32 num_lines, num_bytes_per_line; + + input_format = &csid->fmt[MSM_CSID_PAD_SRC]; + format = csid_get_fmt_entry(csid->formats, csid->nformats, + input_format->code); + num_bytes_per_line = input_format->width * format->bpp * format->spp / 8; + num_lines = input_format->height; + + /* 31:24 V blank, 23:13 H blank, 3:2 num of active DT */ + /* 1:0 VC */ + val = ((CAMSS_CSID_TG_VC_CFG_V_BLANKING & 0xff) << 24) | + ((CAMSS_CSID_TG_VC_CFG_H_BLANKING & 0x7ff) << 13); + writel_relaxed(val, csid->base + CAMSS_CSID_TG_VC_CFG); + + /* 28:16 bytes per lines, 12:0 num of lines */ + val = ((num_bytes_per_line & 0x1fff) << 16) | + (num_lines & 0x1fff); + writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_0(0)); + + /* 5:0 data type */ + val = format->data_type; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_1(0)); + + /* 2:0 output test pattern */ + val = tg->mode - 1; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_2(0)); + } else { + struct csid_phy_config *phy = &csid->phy; + + input_format = &csid->fmt[MSM_CSID_PAD_SINK]; + format = csid_get_fmt_entry(csid->formats, csid->nformats, + input_format->code); + + val = phy->lane_cnt - 1; + val |= phy->lane_assign << 4; + + writel_relaxed(val, csid->base + CAMSS_CSID_CORE_CTRL_0); + + val = phy->csiphy_id << 17; + val |= 0x9; + + writel_relaxed(val, csid->base + CAMSS_CSID_CORE_CTRL_1); + } + + /* Config LUT */ + + dt_shift = (cid % 4) * 8; + val = readl_relaxed(csid->base + CAMSS_CSID_CID_LUT_VC_n(vc)); + val &= ~(0xff << dt_shift); + val |= format->data_type << dt_shift; + writel_relaxed(val, csid->base + CAMSS_CSID_CID_LUT_VC_n(vc)); + + val = CAMSS_CSID_CID_n_CFG_ISPIF_EN; + val |= CAMSS_CSID_CID_n_CFG_RDI_EN; + val |= format->decode_format << CAMSS_CSID_CID_n_CFG_DECODE_FORMAT_SHIFT; + val |= CAMSS_CSID_CID_n_CFG_RDI_MODE_RAW_DUMP; + writel_relaxed(val, csid->base + CAMSS_CSID_CID_n_CFG(cid)); + + if (tg->enabled) { + val = CAMSS_CSID_TG_CTRL_ENABLE; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL); + } + } else { + if (tg->enabled) { + val = CAMSS_CSID_TG_CTRL_DISABLE; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL); + } + } +} + +static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val) +{ + if (val > 0 && val <= csid->testgen.nmodes) + csid->testgen.mode = val; + + return 0; +} + +static u32 csid_hw_version(struct csid_device *csid) +{ + u32 hw_version = readl_relaxed(csid->base + CAMSS_CSID_HW_VERSION); + + dev_dbg(csid->camss->dev, "CSID HW Version = 0x%08x\n", hw_version); + + return hw_version; +} + +static irqreturn_t csid_isr(int irq, void *dev) +{ + struct csid_device *csid = dev; + u32 value; + + value = readl_relaxed(csid->base + CAMSS_CSID_IRQ_STATUS); + writel_relaxed(value, csid->base + CAMSS_CSID_IRQ_CLEAR_CMD); + + if ((value >> 11) & 0x1) + complete(&csid->reset_complete); + + return IRQ_HANDLED; +} + +static int csid_reset(struct csid_device *csid) +{ + unsigned long time; + + reinit_completion(&csid->reset_complete); + + writel_relaxed(0x7fff, csid->base + CAMSS_CSID_RST_CMD); + + time = wait_for_completion_timeout(&csid->reset_complete, + msecs_to_jiffies(CSID_RESET_TIMEOUT_MS)); + if (!time) { + dev_err(csid->camss->dev, "CSID reset timeout\n"); + return -EIO; + } + + return 0; +} + +static u32 csid_src_pad_code(struct csid_device *csid, u32 sink_code, + unsigned int match_format_idx, u32 match_code) +{ + if (match_format_idx > 0) + return 0; + + return sink_code; +} + +static void csid_subdev_init(struct csid_device *csid) +{ + csid->formats = csid_formats; + csid->nformats = ARRAY_SIZE(csid_formats); + csid->testgen.modes = csid_testgen_modes; + csid->testgen.nmodes = CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN1; +} + +const struct csid_hw_ops csid_ops_4_1 = { + .configure_stream = csid_configure_stream, + .configure_testgen_pattern = csid_configure_testgen_pattern, + .hw_version = csid_hw_version, + .isr = csid_isr, + .reset = csid_reset, + .src_pad_code = csid_src_pad_code, + .subdev_init = csid_subdev_init, +}; diff --git a/drivers/media/platform/qcom/camss/camss-csid-4-7.c b/drivers/media/platform/qcom/camss/camss-csid-4-7.c new file mode 100644 index 000000000..e7436ec6d --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid-4-7.c @@ -0,0 +1,404 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csid-4-7.c + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (C) 2020 Linaro Ltd. + */ +#include +#include +#include +#include +#include + +#include "camss-csid.h" +#include "camss-csid-gen1.h" +#include "camss.h" + +#define CAMSS_CSID_HW_VERSION 0x0 +#define CAMSS_CSID_CORE_CTRL_0 0x004 +#define CAMSS_CSID_CORE_CTRL_1 0x008 +#define CAMSS_CSID_RST_CMD 0x010 +#define CAMSS_CSID_CID_LUT_VC_n(n) (0x014 + 0x4 * (n)) +#define CAMSS_CSID_CID_n_CFG(n) (0x024 + 0x4 * (n)) +#define CAMSS_CSID_CID_n_CFG_ISPIF_EN BIT(0) +#define CAMSS_CSID_CID_n_CFG_RDI_EN BIT(1) +#define CAMSS_CSID_CID_n_CFG_DECODE_FORMAT_SHIFT 4 +#define CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_8 (PLAIN_FORMAT_PLAIN8 << 8) +#define CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_16 (PLAIN_FORMAT_PLAIN16 << 8) +#define CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_LSB (0 << 9) +#define CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_MSB (1 << 9) +#define CAMSS_CSID_CID_n_CFG_RDI_MODE_RAW_DUMP (0 << 10) +#define CAMSS_CSID_CID_n_CFG_RDI_MODE_PLAIN_PACKING (1 << 10) +#define CAMSS_CSID_IRQ_CLEAR_CMD 0x064 +#define CAMSS_CSID_IRQ_MASK 0x068 +#define CAMSS_CSID_IRQ_STATUS 0x06c +#define CAMSS_CSID_TG_CTRL 0x0a8 +#define CAMSS_CSID_TG_CTRL_DISABLE 0xa06436 +#define CAMSS_CSID_TG_CTRL_ENABLE 0xa06437 +#define CAMSS_CSID_TG_VC_CFG 0x0ac +#define CAMSS_CSID_TG_VC_CFG_H_BLANKING 0x3ff +#define CAMSS_CSID_TG_VC_CFG_V_BLANKING 0x7f +#define CAMSS_CSID_TG_DT_n_CGG_0(n) (0x0b4 + 0xc * (n)) +#define CAMSS_CSID_TG_DT_n_CGG_1(n) (0x0b8 + 0xc * (n)) +#define CAMSS_CSID_TG_DT_n_CGG_2(n) (0x0bc + 0xc * (n)) + +static const struct csid_format csid_formats[] = { + { + MEDIA_BUS_FMT_UYVY8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_VYUY8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_YUYV8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_YVYU8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_SBGGR8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR14_1X14, + DATA_TYPE_RAW_14BIT, + DECODE_FORMAT_UNCOMPRESSED_14_BIT, + 14, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG14_1X14, + DATA_TYPE_RAW_14BIT, + DECODE_FORMAT_UNCOMPRESSED_14_BIT, + 14, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG14_1X14, + DATA_TYPE_RAW_14BIT, + DECODE_FORMAT_UNCOMPRESSED_14_BIT, + 14, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB14_1X14, + DATA_TYPE_RAW_14BIT, + DECODE_FORMAT_UNCOMPRESSED_14_BIT, + 14, + 1, + }, + { + MEDIA_BUS_FMT_Y10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, +}; + +static void csid_configure_stream(struct csid_device *csid, u8 enable) +{ + struct csid_testgen_config *tg = &csid->testgen; + u32 sink_code = csid->fmt[MSM_CSID_PAD_SINK].code; + u32 src_code = csid->fmt[MSM_CSID_PAD_SRC].code; + u32 val; + + if (enable) { + struct v4l2_mbus_framefmt *input_format; + const struct csid_format *format; + u8 vc = 0; /* Virtual Channel 0 */ + u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */ + u8 dt_shift; + + if (tg->enabled) { + /* Config Test Generator */ + u32 num_bytes_per_line, num_lines; + + input_format = &csid->fmt[MSM_CSID_PAD_SRC]; + format = csid_get_fmt_entry(csid->formats, csid->nformats, + input_format->code); + num_bytes_per_line = input_format->width * format->bpp * format->spp / 8; + num_lines = input_format->height; + + /* 31:24 V blank, 23:13 H blank, 3:2 num of active DT */ + /* 1:0 VC */ + val = ((CAMSS_CSID_TG_VC_CFG_V_BLANKING & 0xff) << 24) | + ((CAMSS_CSID_TG_VC_CFG_H_BLANKING & 0x7ff) << 13); + writel_relaxed(val, csid->base + CAMSS_CSID_TG_VC_CFG); + + /* 28:16 bytes per lines, 12:0 num of lines */ + val = ((num_bytes_per_line & 0x1fff) << 16) | + (num_lines & 0x1fff); + writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_0(0)); + + /* 5:0 data type */ + val = format->data_type; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_1(0)); + + /* 2:0 output test pattern */ + val = tg->mode - 1; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_DT_n_CGG_2(0)); + } else { + struct csid_phy_config *phy = &csid->phy; + + input_format = &csid->fmt[MSM_CSID_PAD_SINK]; + format = csid_get_fmt_entry(csid->formats, csid->nformats, + input_format->code); + + val = phy->lane_cnt - 1; + val |= phy->lane_assign << 4; + + writel_relaxed(val, csid->base + CAMSS_CSID_CORE_CTRL_0); + + val = phy->csiphy_id << 17; + val |= 0x9; + + writel_relaxed(val, csid->base + CAMSS_CSID_CORE_CTRL_1); + } + + /* Config LUT */ + + dt_shift = (cid % 4) * 8; + + val = readl_relaxed(csid->base + CAMSS_CSID_CID_LUT_VC_n(vc)); + val &= ~(0xff << dt_shift); + val |= format->data_type << dt_shift; + writel_relaxed(val, csid->base + CAMSS_CSID_CID_LUT_VC_n(vc)); + + val = CAMSS_CSID_CID_n_CFG_ISPIF_EN; + val |= CAMSS_CSID_CID_n_CFG_RDI_EN; + val |= format->decode_format << CAMSS_CSID_CID_n_CFG_DECODE_FORMAT_SHIFT; + val |= CAMSS_CSID_CID_n_CFG_RDI_MODE_RAW_DUMP; + + if ((sink_code == MEDIA_BUS_FMT_SBGGR10_1X10 && + src_code == MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE) || + (sink_code == MEDIA_BUS_FMT_Y10_1X10 && + src_code == MEDIA_BUS_FMT_Y10_2X8_PADHI_LE)) { + val |= CAMSS_CSID_CID_n_CFG_RDI_MODE_PLAIN_PACKING; + val |= CAMSS_CSID_CID_n_CFG_PLAIN_FORMAT_16; + val |= CAMSS_CSID_CID_n_CFG_PLAIN_ALIGNMENT_LSB; + } + + writel_relaxed(val, csid->base + CAMSS_CSID_CID_n_CFG(cid)); + + if (tg->enabled) { + val = CAMSS_CSID_TG_CTRL_ENABLE; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL); + } + } else { + if (tg->enabled) { + val = CAMSS_CSID_TG_CTRL_DISABLE; + writel_relaxed(val, csid->base + CAMSS_CSID_TG_CTRL); + } + } +} + +static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val) +{ + if (val > 0 && val <= csid->testgen.nmodes) + csid->testgen.mode = val; + + return 0; +} + +static u32 csid_hw_version(struct csid_device *csid) +{ + u32 hw_version = readl_relaxed(csid->base + CAMSS_CSID_HW_VERSION); + + dev_dbg(csid->camss->dev, "CSID HW Version = 0x%08x\n", hw_version); + + return hw_version; +} + +/* + * isr - CSID module interrupt service routine + * @irq: Interrupt line + * @dev: CSID device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t csid_isr(int irq, void *dev) +{ + struct csid_device *csid = dev; + u32 value; + + value = readl_relaxed(csid->base + CAMSS_CSID_IRQ_STATUS); + writel_relaxed(value, csid->base + CAMSS_CSID_IRQ_CLEAR_CMD); + + if ((value >> 11) & 0x1) + complete(&csid->reset_complete); + + return IRQ_HANDLED; +} + +/* + * csid_reset - Trigger reset on CSID module and wait to complete + * @csid: CSID device + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_reset(struct csid_device *csid) +{ + unsigned long time; + + reinit_completion(&csid->reset_complete); + + writel_relaxed(0x7fff, csid->base + CAMSS_CSID_RST_CMD); + + time = wait_for_completion_timeout(&csid->reset_complete, + msecs_to_jiffies(CSID_RESET_TIMEOUT_MS)); + if (!time) { + dev_err(csid->camss->dev, "CSID reset timeout\n"); + return -EIO; + } + + return 0; +} + +static u32 csid_src_pad_code(struct csid_device *csid, u32 sink_code, + unsigned int match_format_idx, u32 match_code) +{ + switch (sink_code) { + case MEDIA_BUS_FMT_SBGGR10_1X10: + { + u32 src_code[] = { + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, + }; + + return csid_find_code(src_code, ARRAY_SIZE(src_code), + match_format_idx, match_code); + } + case MEDIA_BUS_FMT_Y10_1X10: + { + u32 src_code[] = { + MEDIA_BUS_FMT_Y10_1X10, + MEDIA_BUS_FMT_Y10_2X8_PADHI_LE, + }; + + return csid_find_code(src_code, ARRAY_SIZE(src_code), + match_format_idx, match_code); + } + default: + if (match_format_idx > 0) + return 0; + + return sink_code; + } +} + +static void csid_subdev_init(struct csid_device *csid) +{ + csid->formats = csid_formats; + csid->nformats = ARRAY_SIZE(csid_formats); + csid->testgen.modes = csid_testgen_modes; + csid->testgen.nmodes = CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN1; +} + +const struct csid_hw_ops csid_ops_4_7 = { + .configure_stream = csid_configure_stream, + .configure_testgen_pattern = csid_configure_testgen_pattern, + .hw_version = csid_hw_version, + .isr = csid_isr, + .reset = csid_reset, + .src_pad_code = csid_src_pad_code, + .subdev_init = csid_subdev_init, +}; diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen1.h b/drivers/media/platform/qcom/camss/camss-csid-gen1.h new file mode 100644 index 000000000..80a2bc6ef --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid-gen1.h @@ -0,0 +1,27 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-csid-gen1.h + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module Generation 1 + * + * Copyright (C) 2021 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_CSID_GEN1_H +#define QC_MSM_CAMSS_CSID_GEN1_H + +#define DECODE_FORMAT_UNCOMPRESSED_6_BIT 0x0 +#define DECODE_FORMAT_UNCOMPRESSED_8_BIT 0x1 +#define DECODE_FORMAT_UNCOMPRESSED_10_BIT 0x2 +#define DECODE_FORMAT_UNCOMPRESSED_12_BIT 0x3 +#define DECODE_FORMAT_DPCM_10_6_10 0x4 +#define DECODE_FORMAT_DPCM_10_8_10 0x5 +#define DECODE_FORMAT_DPCM_12_6_12 0x6 +#define DECODE_FORMAT_DPCM_12_8_12 0x7 +#define DECODE_FORMAT_UNCOMPRESSED_14_BIT 0x8 +#define DECODE_FORMAT_DPCM_14_8_14 0x9 +#define DECODE_FORMAT_DPCM_14_10_14 0xa + +#define PLAIN_FORMAT_PLAIN8 0x0 /* supports DPCM, UNCOMPRESSED_6/8_BIT */ +#define PLAIN_FORMAT_PLAIN16 0x1 /* supports DPCM, UNCOMPRESSED_10/16_BIT */ + +#endif /* QC_MSM_CAMSS_CSID_GEN1_H */ diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen2.c b/drivers/media/platform/qcom/camss/camss-csid-gen2.c new file mode 100644 index 000000000..0147cc062 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid-gen2.c @@ -0,0 +1,630 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csid-4-7.c + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (C) 2020 Linaro Ltd. + */ +#include +#include +#include +#include +#include + +#include "camss-csid.h" +#include "camss-csid-gen2.h" +#include "camss.h" + +/* The CSID 2 IP-block is different from the others, + * and is of a bare-bones Lite version, with no PIX + * interface support. As a result of that it has an + * alternate register layout. + */ +#define IS_LITE (csid->id >= 2 ? 1 : 0) + +#define CSID_HW_VERSION 0x0 +#define HW_VERSION_STEPPING 0 +#define HW_VERSION_REVISION 16 +#define HW_VERSION_GENERATION 28 + +#define CSID_RST_STROBES 0x10 +#define RST_STROBES 0 + +#define CSID_CSI2_RX_IRQ_STATUS 0x20 +#define CSID_CSI2_RX_IRQ_MASK 0x24 +#define CSID_CSI2_RX_IRQ_CLEAR 0x28 + +#define CSID_CSI2_RDIN_IRQ_STATUS(rdi) ((IS_LITE ? 0x30 : 0x40) \ + + 0x10 * (rdi)) +#define CSID_CSI2_RDIN_IRQ_MASK(rdi) ((IS_LITE ? 0x34 : 0x44) \ + + 0x10 * (rdi)) +#define CSID_CSI2_RDIN_IRQ_CLEAR(rdi) ((IS_LITE ? 0x38 : 0x48) \ + + 0x10 * (rdi)) +#define CSID_CSI2_RDIN_IRQ_SET(rdi) ((IS_LITE ? 0x3C : 0x4C) \ + + 0x10 * (rdi)) + +#define CSID_TOP_IRQ_STATUS 0x70 +#define TOP_IRQ_STATUS_RESET_DONE 0 +#define CSID_TOP_IRQ_MASK 0x74 +#define CSID_TOP_IRQ_CLEAR 0x78 +#define CSID_TOP_IRQ_SET 0x7C +#define CSID_IRQ_CMD 0x80 +#define IRQ_CMD_CLEAR 0 +#define IRQ_CMD_SET 4 + +#define CSID_CSI2_RX_CFG0 0x100 +#define CSI2_RX_CFG0_NUM_ACTIVE_LANES 0 +#define CSI2_RX_CFG0_DL0_INPUT_SEL 4 +#define CSI2_RX_CFG0_DL1_INPUT_SEL 8 +#define CSI2_RX_CFG0_DL2_INPUT_SEL 12 +#define CSI2_RX_CFG0_DL3_INPUT_SEL 16 +#define CSI2_RX_CFG0_PHY_NUM_SEL 20 +#define CSI2_RX_CFG0_PHY_TYPE_SEL 24 + +#define CSID_CSI2_RX_CFG1 0x104 +#define CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN 0 +#define CSI2_RX_CFG1_DE_SCRAMBLE_EN 1 +#define CSI2_RX_CFG1_VC_MODE 2 +#define CSI2_RX_CFG1_COMPLETE_STREAM_EN 4 +#define CSI2_RX_CFG1_COMPLETE_STREAM_FRAME_TIMING 5 +#define CSI2_RX_CFG1_MISR_EN 6 +#define CSI2_RX_CFG1_CGC_MODE 7 +#define CGC_MODE_DYNAMIC_GATING 0 +#define CGC_MODE_ALWAYS_ON 1 + +#define CSID_RDI_CFG0(rdi) ((IS_LITE ? 0x200 : 0x300) \ + + 0x100 * (rdi)) +#define RDI_CFG0_BYTE_CNTR_EN 0 +#define RDI_CFG0_FORMAT_MEASURE_EN 1 +#define RDI_CFG0_TIMESTAMP_EN 2 +#define RDI_CFG0_DROP_H_EN 3 +#define RDI_CFG0_DROP_V_EN 4 +#define RDI_CFG0_CROP_H_EN 5 +#define RDI_CFG0_CROP_V_EN 6 +#define RDI_CFG0_MISR_EN 7 +#define RDI_CFG0_CGC_MODE 8 +#define CGC_MODE_DYNAMIC 0 +#define CGC_MODE_ALWAYS_ON 1 +#define RDI_CFG0_PLAIN_ALIGNMENT 9 +#define PLAIN_ALIGNMENT_LSB 0 +#define PLAIN_ALIGNMENT_MSB 1 +#define RDI_CFG0_PLAIN_FORMAT 10 +#define RDI_CFG0_DECODE_FORMAT 12 +#define RDI_CFG0_DATA_TYPE 16 +#define RDI_CFG0_VIRTUAL_CHANNEL 22 +#define RDI_CFG0_DT_ID 27 +#define RDI_CFG0_EARLY_EOF_EN 29 +#define RDI_CFG0_PACKING_FORMAT 30 +#define RDI_CFG0_ENABLE 31 + +#define CSID_RDI_CFG1(rdi) ((IS_LITE ? 0x204 : 0x304)\ + + 0x100 * (rdi)) +#define RDI_CFG1_TIMESTAMP_STB_SEL 0 + +#define CSID_RDI_CTRL(rdi) ((IS_LITE ? 0x208 : 0x308)\ + + 0x100 * (rdi)) +#define RDI_CTRL_HALT_CMD 0 +#define HALT_CMD_HALT_AT_FRAME_BOUNDARY 0 +#define HALT_CMD_RESUME_AT_FRAME_BOUNDARY 1 +#define RDI_CTRL_HALT_MODE 2 + +#define CSID_RDI_FRM_DROP_PATTERN(rdi) ((IS_LITE ? 0x20C : 0x30C)\ + + 0x100 * (rdi)) +#define CSID_RDI_FRM_DROP_PERIOD(rdi) ((IS_LITE ? 0x210 : 0x310)\ + + 0x100 * (rdi)) +#define CSID_RDI_IRQ_SUBSAMPLE_PATTERN(rdi) ((IS_LITE ? 0x214 : 0x314)\ + + 0x100 * (rdi)) +#define CSID_RDI_IRQ_SUBSAMPLE_PERIOD(rdi) ((IS_LITE ? 0x218 : 0x318)\ + + 0x100 * (rdi)) +#define CSID_RDI_RPP_PIX_DROP_PATTERN(rdi) ((IS_LITE ? 0x224 : 0x324)\ + + 0x100 * (rdi)) +#define CSID_RDI_RPP_PIX_DROP_PERIOD(rdi) ((IS_LITE ? 0x228 : 0x328)\ + + 0x100 * (rdi)) +#define CSID_RDI_RPP_LINE_DROP_PATTERN(rdi) ((IS_LITE ? 0x22C : 0x32C)\ + + 0x100 * (rdi)) +#define CSID_RDI_RPP_LINE_DROP_PERIOD(rdi) ((IS_LITE ? 0x230 : 0x330)\ + + 0x100 * (rdi)) + +#define CSID_TPG_CTRL 0x600 +#define TPG_CTRL_TEST_EN 0 +#define TPG_CTRL_FS_PKT_EN 1 +#define TPG_CTRL_FE_PKT_EN 2 +#define TPG_CTRL_NUM_ACTIVE_LANES 4 +#define TPG_CTRL_CYCLES_BETWEEN_PKTS 8 +#define TPG_CTRL_NUM_TRAIL_BYTES 20 + +#define CSID_TPG_VC_CFG0 0x604 +#define TPG_VC_CFG0_VC_NUM 0 +#define TPG_VC_CFG0_NUM_ACTIVE_SLOTS 8 +#define NUM_ACTIVE_SLOTS_0_ENABLED 0 +#define NUM_ACTIVE_SLOTS_0_1_ENABLED 1 +#define NUM_ACTIVE_SLOTS_0_1_2_ENABLED 2 +#define NUM_ACTIVE_SLOTS_0_1_3_ENABLED 3 +#define TPG_VC_CFG0_LINE_INTERLEAVING_MODE 10 +#define INTELEAVING_MODE_INTERLEAVED 0 +#define INTELEAVING_MODE_ONE_SHOT 1 +#define TPG_VC_CFG0_NUM_FRAMES 16 + +#define CSID_TPG_VC_CFG1 0x608 +#define TPG_VC_CFG1_H_BLANKING_COUNT 0 +#define TPG_VC_CFG1_V_BLANKING_COUNT 12 +#define TPG_VC_CFG1_V_BLANK_FRAME_WIDTH_SEL 24 + +#define CSID_TPG_LFSR_SEED 0x60C + +#define CSID_TPG_DT_n_CFG_0(n) (0x610 + (n) * 0xC) +#define TPG_DT_n_CFG_0_FRAME_HEIGHT 0 +#define TPG_DT_n_CFG_0_FRAME_WIDTH 16 + +#define CSID_TPG_DT_n_CFG_1(n) (0x614 + (n) * 0xC) +#define TPG_DT_n_CFG_1_DATA_TYPE 0 +#define TPG_DT_n_CFG_1_ECC_XOR_MASK 8 +#define TPG_DT_n_CFG_1_CRC_XOR_MASK 16 + +#define CSID_TPG_DT_n_CFG_2(n) (0x618 + (n) * 0xC) +#define TPG_DT_n_CFG_2_PAYLOAD_MODE 0 +#define TPG_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD 4 +#define TPG_DT_n_CFG_2_ENCODE_FORMAT 16 + +#define CSID_TPG_COLOR_BARS_CFG 0x640 +#define TPG_COLOR_BARS_CFG_UNICOLOR_BAR_EN 0 +#define TPG_COLOR_BARS_CFG_UNICOLOR_BAR_SEL 4 +#define TPG_COLOR_BARS_CFG_SPLIT_EN 5 +#define TPG_COLOR_BARS_CFG_ROTATE_PERIOD 8 + +#define CSID_TPG_COLOR_BOX_CFG 0x644 +#define TPG_COLOR_BOX_CFG_MODE 0 +#define TPG_COLOR_BOX_PATTERN_SEL 2 + +static const struct csid_format csid_formats[] = { + { + MEDIA_BUS_FMT_UYVY8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_VYUY8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_YUYV8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_YVYU8_2X8, + DATA_TYPE_YUV422_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 2, + }, + { + MEDIA_BUS_FMT_SBGGR8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_Y8_1X8, + DATA_TYPE_RAW_8BIT, + DECODE_FORMAT_UNCOMPRESSED_8_BIT, + 8, + 1, + }, + { + MEDIA_BUS_FMT_Y10_1X10, + DATA_TYPE_RAW_10BIT, + DECODE_FORMAT_UNCOMPRESSED_10_BIT, + 10, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB12_1X12, + DATA_TYPE_RAW_12BIT, + DECODE_FORMAT_UNCOMPRESSED_12_BIT, + 12, + 1, + }, + { + MEDIA_BUS_FMT_SBGGR14_1X14, + DATA_TYPE_RAW_14BIT, + DECODE_FORMAT_UNCOMPRESSED_14_BIT, + 14, + 1, + }, + { + MEDIA_BUS_FMT_SGBRG14_1X14, + DATA_TYPE_RAW_14BIT, + DECODE_FORMAT_UNCOMPRESSED_14_BIT, + 14, + 1, + }, + { + MEDIA_BUS_FMT_SGRBG14_1X14, + DATA_TYPE_RAW_14BIT, + DECODE_FORMAT_UNCOMPRESSED_14_BIT, + 14, + 1, + }, + { + MEDIA_BUS_FMT_SRGGB14_1X14, + DATA_TYPE_RAW_14BIT, + DECODE_FORMAT_UNCOMPRESSED_14_BIT, + 14, + 1, + }, +}; + +static void __csid_configure_stream(struct csid_device *csid, u8 enable, u8 vc) +{ + struct csid_testgen_config *tg = &csid->testgen; + u32 val; + u32 phy_sel = 0; + u8 lane_cnt = csid->phy.lane_cnt; + /* Source pads matching RDI channels on hardware. Pad 1 -> RDI0, Pad 2 -> RDI1, etc. */ + struct v4l2_mbus_framefmt *input_format = &csid->fmt[MSM_CSID_PAD_FIRST_SRC + vc]; + const struct csid_format *format = csid_get_fmt_entry(csid->formats, csid->nformats, + input_format->code); + + if (!lane_cnt) + lane_cnt = 4; + + if (!tg->enabled) + phy_sel = csid->phy.csiphy_id; + + if (enable) { + /* + * DT_ID is a two bit bitfield that is concatenated with + * the four least significant bits of the five bit VC + * bitfield to generate an internal CID value. + * + * CSID_RDI_CFG0(vc) + * DT_ID : 28:27 + * VC : 26:22 + * DT : 21:16 + * + * CID : VC 3:0 << 2 | DT_ID 1:0 + */ + u8 dt_id = vc & 0x03; + + if (tg->enabled) { + /* configure one DT, infinite frames */ + val = vc << TPG_VC_CFG0_VC_NUM; + val |= INTELEAVING_MODE_ONE_SHOT << TPG_VC_CFG0_LINE_INTERLEAVING_MODE; + val |= 0 << TPG_VC_CFG0_NUM_FRAMES; + writel_relaxed(val, csid->base + CSID_TPG_VC_CFG0); + + val = 0x740 << TPG_VC_CFG1_H_BLANKING_COUNT; + val |= 0x3ff << TPG_VC_CFG1_V_BLANKING_COUNT; + writel_relaxed(val, csid->base + CSID_TPG_VC_CFG1); + + writel_relaxed(0x12345678, csid->base + CSID_TPG_LFSR_SEED); + + val = (input_format->height & 0x1fff) << TPG_DT_n_CFG_0_FRAME_HEIGHT; + val |= (input_format->width & 0x1fff) << TPG_DT_n_CFG_0_FRAME_WIDTH; + writel_relaxed(val, csid->base + CSID_TPG_DT_n_CFG_0(0)); + + val = format->data_type << TPG_DT_n_CFG_1_DATA_TYPE; + writel_relaxed(val, csid->base + CSID_TPG_DT_n_CFG_1(0)); + + val = (tg->mode - 1) << TPG_DT_n_CFG_2_PAYLOAD_MODE; + val |= 0xBE << TPG_DT_n_CFG_2_USER_SPECIFIED_PAYLOAD; + val |= format->decode_format << TPG_DT_n_CFG_2_ENCODE_FORMAT; + writel_relaxed(val, csid->base + CSID_TPG_DT_n_CFG_2(0)); + + writel_relaxed(0, csid->base + CSID_TPG_COLOR_BARS_CFG); + + writel_relaxed(0, csid->base + CSID_TPG_COLOR_BOX_CFG); + } + + val = 1 << RDI_CFG0_BYTE_CNTR_EN; + val |= 1 << RDI_CFG0_FORMAT_MEASURE_EN; + val |= 1 << RDI_CFG0_TIMESTAMP_EN; + /* note: for non-RDI path, this should be format->decode_format */ + val |= DECODE_FORMAT_PAYLOAD_ONLY << RDI_CFG0_DECODE_FORMAT; + val |= format->data_type << RDI_CFG0_DATA_TYPE; + val |= vc << RDI_CFG0_VIRTUAL_CHANNEL; + val |= dt_id << RDI_CFG0_DT_ID; + writel_relaxed(val, csid->base + CSID_RDI_CFG0(vc)); + + /* CSID_TIMESTAMP_STB_POST_IRQ */ + val = 2 << RDI_CFG1_TIMESTAMP_STB_SEL; + writel_relaxed(val, csid->base + CSID_RDI_CFG1(vc)); + + val = 1; + writel_relaxed(val, csid->base + CSID_RDI_FRM_DROP_PERIOD(vc)); + + val = 0; + writel_relaxed(val, csid->base + CSID_RDI_FRM_DROP_PATTERN(vc)); + + val = 1; + writel_relaxed(val, csid->base + CSID_RDI_IRQ_SUBSAMPLE_PERIOD(vc)); + + val = 0; + writel_relaxed(val, csid->base + CSID_RDI_IRQ_SUBSAMPLE_PATTERN(vc)); + + val = 1; + writel_relaxed(val, csid->base + CSID_RDI_RPP_PIX_DROP_PERIOD(vc)); + + val = 0; + writel_relaxed(val, csid->base + CSID_RDI_RPP_PIX_DROP_PATTERN(vc)); + + val = 1; + writel_relaxed(val, csid->base + CSID_RDI_RPP_LINE_DROP_PERIOD(vc)); + + val = 0; + writel_relaxed(val, csid->base + CSID_RDI_RPP_LINE_DROP_PATTERN(vc)); + + val = 0; + writel_relaxed(val, csid->base + CSID_RDI_CTRL(vc)); + + val = readl_relaxed(csid->base + CSID_RDI_CFG0(vc)); + val |= 1 << RDI_CFG0_ENABLE; + writel_relaxed(val, csid->base + CSID_RDI_CFG0(vc)); + } + + if (tg->enabled) { + val = enable << TPG_CTRL_TEST_EN; + val |= 1 << TPG_CTRL_FS_PKT_EN; + val |= 1 << TPG_CTRL_FE_PKT_EN; + val |= (lane_cnt - 1) << TPG_CTRL_NUM_ACTIVE_LANES; + val |= 0x64 << TPG_CTRL_CYCLES_BETWEEN_PKTS; + val |= 0xA << TPG_CTRL_NUM_TRAIL_BYTES; + writel_relaxed(val, csid->base + CSID_TPG_CTRL); + } + + val = (lane_cnt - 1) << CSI2_RX_CFG0_NUM_ACTIVE_LANES; + val |= csid->phy.lane_assign << CSI2_RX_CFG0_DL0_INPUT_SEL; + val |= phy_sel << CSI2_RX_CFG0_PHY_NUM_SEL; + writel_relaxed(val, csid->base + CSID_CSI2_RX_CFG0); + + val = 1 << CSI2_RX_CFG1_PACKET_ECC_CORRECTION_EN; + if (vc > 3) + val |= 1 << CSI2_RX_CFG1_VC_MODE; + val |= 1 << CSI2_RX_CFG1_MISR_EN; + writel_relaxed(val, csid->base + CSID_CSI2_RX_CFG1); + + if (enable) + val = HALT_CMD_RESUME_AT_FRAME_BOUNDARY << RDI_CTRL_HALT_CMD; + else + val = HALT_CMD_HALT_AT_FRAME_BOUNDARY << RDI_CTRL_HALT_CMD; + writel_relaxed(val, csid->base + CSID_RDI_CTRL(vc)); +} + +static void csid_configure_stream(struct csid_device *csid, u8 enable) +{ + u8 i; + /* Loop through all enabled VCs and configure stream for each */ + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) + if (csid->phy.en_vc & BIT(i)) + __csid_configure_stream(csid, enable, i); +} + +static int csid_configure_testgen_pattern(struct csid_device *csid, s32 val) +{ + if (val > 0 && val <= csid->testgen.nmodes) + csid->testgen.mode = val; + + return 0; +} + +/* + * csid_hw_version - CSID hardware version query + * @csid: CSID device + * + * Return HW version or error + */ +static u32 csid_hw_version(struct csid_device *csid) +{ + u32 hw_version; + u32 hw_gen; + u32 hw_rev; + u32 hw_step; + + hw_version = readl_relaxed(csid->base + CSID_HW_VERSION); + hw_gen = (hw_version >> HW_VERSION_GENERATION) & 0xF; + hw_rev = (hw_version >> HW_VERSION_REVISION) & 0xFFF; + hw_step = (hw_version >> HW_VERSION_STEPPING) & 0xFFFF; + dev_dbg(csid->camss->dev, "CSID HW Version = %u.%u.%u\n", + hw_gen, hw_rev, hw_step); + + return hw_version; +} + +/* + * csid_isr - CSID module interrupt service routine + * @irq: Interrupt line + * @dev: CSID device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t csid_isr(int irq, void *dev) +{ + struct csid_device *csid = dev; + u32 val; + u8 reset_done; + int i; + + val = readl_relaxed(csid->base + CSID_TOP_IRQ_STATUS); + writel_relaxed(val, csid->base + CSID_TOP_IRQ_CLEAR); + reset_done = val & BIT(TOP_IRQ_STATUS_RESET_DONE); + + val = readl_relaxed(csid->base + CSID_CSI2_RX_IRQ_STATUS); + writel_relaxed(val, csid->base + CSID_CSI2_RX_IRQ_CLEAR); + + /* Read and clear IRQ status for each enabled RDI channel */ + for (i = 0; i < MSM_CSID_MAX_SRC_STREAMS; i++) + if (csid->phy.en_vc & BIT(i)) { + val = readl_relaxed(csid->base + CSID_CSI2_RDIN_IRQ_STATUS(i)); + writel_relaxed(val, csid->base + CSID_CSI2_RDIN_IRQ_CLEAR(i)); + } + + val = 1 << IRQ_CMD_CLEAR; + writel_relaxed(val, csid->base + CSID_IRQ_CMD); + + if (reset_done) + complete(&csid->reset_complete); + + return IRQ_HANDLED; +} + +/* + * csid_reset - Trigger reset on CSID module and wait to complete + * @csid: CSID device + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_reset(struct csid_device *csid) +{ + unsigned long time; + u32 val; + + reinit_completion(&csid->reset_complete); + + writel_relaxed(1, csid->base + CSID_TOP_IRQ_CLEAR); + writel_relaxed(1, csid->base + CSID_IRQ_CMD); + writel_relaxed(1, csid->base + CSID_TOP_IRQ_MASK); + writel_relaxed(1, csid->base + CSID_IRQ_CMD); + + /* preserve registers */ + val = 0x1e << RST_STROBES; + writel_relaxed(val, csid->base + CSID_RST_STROBES); + + time = wait_for_completion_timeout(&csid->reset_complete, + msecs_to_jiffies(CSID_RESET_TIMEOUT_MS)); + if (!time) { + dev_err(csid->camss->dev, "CSID reset timeout\n"); + return -EIO; + } + + return 0; +} + +static u32 csid_src_pad_code(struct csid_device *csid, u32 sink_code, + unsigned int match_format_idx, u32 match_code) +{ + switch (sink_code) { + case MEDIA_BUS_FMT_SBGGR10_1X10: + { + u32 src_code[] = { + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, + }; + + return csid_find_code(src_code, ARRAY_SIZE(src_code), + match_format_idx, match_code); + } + case MEDIA_BUS_FMT_Y10_1X10: + { + u32 src_code[] = { + MEDIA_BUS_FMT_Y10_1X10, + MEDIA_BUS_FMT_Y10_2X8_PADHI_LE, + }; + + return csid_find_code(src_code, ARRAY_SIZE(src_code), + match_format_idx, match_code); + } + default: + if (match_format_idx > 0) + return 0; + + return sink_code; + } +} + +static void csid_subdev_init(struct csid_device *csid) +{ + csid->formats = csid_formats; + csid->nformats = ARRAY_SIZE(csid_formats); + csid->testgen.modes = csid_testgen_modes; + csid->testgen.nmodes = CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN2; +} + +const struct csid_hw_ops csid_ops_gen2 = { + .configure_stream = csid_configure_stream, + .configure_testgen_pattern = csid_configure_testgen_pattern, + .hw_version = csid_hw_version, + .isr = csid_isr, + .reset = csid_reset, + .src_pad_code = csid_src_pad_code, + .subdev_init = csid_subdev_init, +}; diff --git a/drivers/media/platform/qcom/camss/camss-csid-gen2.h b/drivers/media/platform/qcom/camss/camss-csid-gen2.h new file mode 100644 index 000000000..3a8ad001b --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid-gen2.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-csid-gen1.h + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module Generation 1 + * + * Copyright (C) 2021 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_CSID_GEN2_H +#define QC_MSM_CAMSS_CSID_GEN2_H + +#define DECODE_FORMAT_UNCOMPRESSED_6_BIT 0x0 +#define DECODE_FORMAT_UNCOMPRESSED_8_BIT 0x1 +#define DECODE_FORMAT_UNCOMPRESSED_10_BIT 0x2 +#define DECODE_FORMAT_UNCOMPRESSED_12_BIT 0x3 +#define DECODE_FORMAT_UNCOMPRESSED_14_BIT 0x4 +#define DECODE_FORMAT_UNCOMPRESSED_16_BIT 0x5 +#define DECODE_FORMAT_UNCOMPRESSED_20_BIT 0x6 +#define DECODE_FORMAT_DPCM_10_6_10 0x7 +#define DECODE_FORMAT_DPCM_10_8_10 0x8 +#define DECODE_FORMAT_DPCM_12_6_12 0x9 +#define DECODE_FORMAT_DPCM_12_8_12 0xa +#define DECODE_FORMAT_DPCM_14_8_14 0xb +#define DECODE_FORMAT_DPCM_14_10_14 0xc +#define DECODE_FORMAT_DPCM_12_10_12 0xd +#define DECODE_FORMAT_USER_DEFINED 0xe +#define DECODE_FORMAT_PAYLOAD_ONLY 0xf + +#define ENCODE_FORMAT_RAW_8_BIT 0x1 +#define ENCODE_FORMAT_RAW_10_BIT 0x2 +#define ENCODE_FORMAT_RAW_12_BIT 0x3 +#define ENCODE_FORMAT_RAW_14_BIT 0x4 +#define ENCODE_FORMAT_RAW_16_BIT 0x5 + +#define PLAIN_FORMAT_PLAIN8 0x0 /* supports DPCM, UNCOMPRESSED_6/8_BIT */ +#define PLAIN_FORMAT_PLAIN16 0x1 /* supports DPCM, UNCOMPRESSED_10/16_BIT */ +#define PLAIN_FORMAT_PLAIN32 0x2 /* supports UNCOMPRESSED_20_BIT */ + +#endif /* QC_MSM_CAMSS_CSID_GEN2_H */ diff --git a/drivers/media/platform/qcom/camss/camss-csid.c b/drivers/media/platform/qcom/camss/camss-csid.c new file mode 100644 index 000000000..6360314f0 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid.c @@ -0,0 +1,908 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csid.c + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camss-csid.h" +#include "camss-csid-gen1.h" +#include "camss.h" + +/* offset of CSID registers in VFE region for VFE 480 */ +#define VFE_480_CSID_OFFSET 0x1200 +#define VFE_480_LITE_CSID_OFFSET 0x200 + +#define MSM_CSID_NAME "msm_csid" + +const char * const csid_testgen_modes[] = { + "Disabled", + "Incrementing", + "Alternating 0x55/0xAA", + "All Zeros 0x00", + "All Ones 0xFF", + "Pseudo-random Data", + "User Specified", + "Complex pattern", + "Color box", + "Color bars", + NULL +}; + +u32 csid_find_code(u32 *codes, unsigned int ncodes, + unsigned int match_format_idx, u32 match_code) +{ + int i; + + if (!match_code && (match_format_idx >= ncodes)) + return 0; + + for (i = 0; i < ncodes; i++) + if (match_code) { + if (codes[i] == match_code) + return match_code; + } else { + if (i == match_format_idx) + return codes[i]; + } + + return codes[0]; +} + +const struct csid_format *csid_get_fmt_entry(const struct csid_format *formats, + unsigned int nformats, + u32 code) +{ + unsigned int i; + + for (i = 0; i < nformats; i++) + if (code == formats[i].code) + return &formats[i]; + + WARN(1, "Unknown format\n"); + + return &formats[0]; +} + +/* + * csid_set_clock_rates - Calculate and set clock rates on CSID module + * @csiphy: CSID device + */ +static int csid_set_clock_rates(struct csid_device *csid) +{ + struct device *dev = csid->camss->dev; + const struct csid_format *fmt; + s64 link_freq; + int i, j; + int ret; + + fmt = csid_get_fmt_entry(csid->formats, csid->nformats, + csid->fmt[MSM_CSIPHY_PAD_SINK].code); + link_freq = camss_get_link_freq(&csid->subdev.entity, fmt->bpp, + csid->phy.lane_cnt); + if (link_freq < 0) + link_freq = 0; + + for (i = 0; i < csid->nclocks; i++) { + struct camss_clock *clock = &csid->clock[i]; + + if (!strcmp(clock->name, "csi0") || + !strcmp(clock->name, "csi1") || + !strcmp(clock->name, "csi2") || + !strcmp(clock->name, "csi3")) { + u64 min_rate = link_freq / 4; + long rate; + + camss_add_clock_margin(&min_rate); + + for (j = 0; j < clock->nfreqs; j++) + if (min_rate < clock->freq[j]) + break; + + if (j == clock->nfreqs) { + dev_err(dev, + "Pixel clock is too high for CSID\n"); + return -EINVAL; + } + + /* if sensor pixel clock is not available */ + /* set highest possible CSID clock rate */ + if (min_rate == 0) + j = clock->nfreqs - 1; + + rate = clk_round_rate(clock->clk, clock->freq[j]); + if (rate < 0) { + dev_err(dev, "clk round rate failed: %ld\n", + rate); + return -EINVAL; + } + + ret = clk_set_rate(clock->clk, rate); + if (ret < 0) { + dev_err(dev, "clk set rate failed: %d\n", ret); + return ret; + } + } else if (clock->nfreqs) { + clk_set_rate(clock->clk, clock->freq[0]); + } + } + + return 0; +} + +/* + * csid_set_power - Power on/off CSID module + * @sd: CSID V4L2 subdevice + * @on: Requested power state + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_set_power(struct v4l2_subdev *sd, int on) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct camss *camss = csid->camss; + struct device *dev = camss->dev; + struct vfe_device *vfe = &camss->vfe[csid->id]; + u32 version = camss->version; + int ret = 0; + + if (on) { + if (version == CAMSS_8250 || version == CAMSS_845) { + ret = vfe_get(vfe); + if (ret < 0) + return ret; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + ret = regulator_bulk_enable(csid->num_supplies, + csid->supplies); + if (ret < 0) { + pm_runtime_put_sync(dev); + return ret; + } + + ret = csid_set_clock_rates(csid); + if (ret < 0) { + regulator_bulk_disable(csid->num_supplies, + csid->supplies); + pm_runtime_put_sync(dev); + return ret; + } + + ret = camss_enable_clocks(csid->nclocks, csid->clock, dev); + if (ret < 0) { + regulator_bulk_disable(csid->num_supplies, + csid->supplies); + pm_runtime_put_sync(dev); + return ret; + } + + csid->phy.need_vc_update = true; + + enable_irq(csid->irq); + + ret = csid->ops->reset(csid); + if (ret < 0) { + disable_irq(csid->irq); + camss_disable_clocks(csid->nclocks, csid->clock); + regulator_bulk_disable(csid->num_supplies, + csid->supplies); + pm_runtime_put_sync(dev); + return ret; + } + + csid->ops->hw_version(csid); + } else { + disable_irq(csid->irq); + camss_disable_clocks(csid->nclocks, csid->clock); + regulator_bulk_disable(csid->num_supplies, + csid->supplies); + pm_runtime_put_sync(dev); + if (version == CAMSS_8250 || version == CAMSS_845) + vfe_put(vfe); + } + + return ret; +} + +/* + * csid_set_stream - Enable/disable streaming on CSID module + * @sd: CSID V4L2 subdevice + * @enable: Requested streaming state + * + * Main configuration of CSID module is also done here. + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + int ret; + + if (enable) { + ret = v4l2_ctrl_handler_setup(&csid->ctrls); + if (ret < 0) { + dev_err(csid->camss->dev, + "could not sync v4l2 controls: %d\n", ret); + return ret; + } + + if (!csid->testgen.enabled && + !media_pad_remote_pad_first(&csid->pads[MSM_CSID_PAD_SINK])) + return -ENOLINK; + } + + if (csid->phy.need_vc_update) { + csid->ops->configure_stream(csid, enable); + csid->phy.need_vc_update = false; + } + + return 0; +} + +/* + * __csid_get_format - Get pointer to format structure + * @csid: CSID device + * @cfg: V4L2 subdev pad configuration + * @pad: pad from which format is requested + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE format structure + */ +static struct v4l2_mbus_framefmt * +__csid_get_format(struct csid_device *csid, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&csid->subdev, sd_state, + pad); + + return &csid->fmt[pad]; +} + +/* + * csid_try_format - Handle try format by pad subdev method + * @csid: CSID device + * @cfg: V4L2 subdev pad configuration + * @pad: pad on which format is requested + * @fmt: pointer to v4l2 format structure + * @which: wanted subdev format + */ +static void csid_try_format(struct csid_device *csid, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + + switch (pad) { + case MSM_CSID_PAD_SINK: + /* Set format on sink pad */ + + for (i = 0; i < csid->nformats; i++) + if (fmt->code == csid->formats[i].code) + break; + + /* If not found, use UYVY as default */ + if (i >= csid->nformats) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + break; + + case MSM_CSID_PAD_SRC: + if (csid->testgen_mode->cur.val == 0) { + /* Test generator is disabled, */ + /* keep pad formats in sync */ + u32 code = fmt->code; + + *fmt = *__csid_get_format(csid, sd_state, + MSM_CSID_PAD_SINK, which); + fmt->code = csid->ops->src_pad_code(csid, fmt->code, 0, code); + } else { + /* Test generator is enabled, set format on source */ + /* pad to allow test generator usage */ + + for (i = 0; i < csid->nformats; i++) + if (csid->formats[i].code == fmt->code) + break; + + /* If not found, use UYVY as default */ + if (i >= csid->nformats) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + } + break; + } + + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +/* + * csid_enum_mbus_code - Handle pixel format enumeration + * @sd: CSID V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int csid_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + + if (code->pad == MSM_CSID_PAD_SINK) { + if (code->index >= csid->nformats) + return -EINVAL; + + code->code = csid->formats[code->index].code; + } else { + if (csid->testgen_mode->cur.val == 0) { + struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt = __csid_get_format(csid, sd_state, + MSM_CSID_PAD_SINK, + code->which); + + code->code = csid->ops->src_pad_code(csid, sink_fmt->code, + code->index, 0); + if (!code->code) + return -EINVAL; + } else { + if (code->index >= csid->nformats) + return -EINVAL; + + code->code = csid->formats[code->index].code; + } + } + + return 0; +} + +/* + * csid_enum_frame_size - Handle frame size enumeration + * @sd: CSID V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: pointer to v4l2_subdev_frame_size_enum structure + * return -EINVAL or zero on success + */ +static int csid_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + csid_try_format(csid, sd_state, fse->pad, &format, fse->which); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + csid_try_format(csid, sd_state, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * csid_get_format - Handle get format by pads subdev method + * @sd: CSID V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int csid_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csid_get_format(csid, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +/* + * csid_set_format - Handle set format by pads subdev method + * @sd: CSID V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int csid_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + int i; + + format = __csid_get_format(csid, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + csid_try_format(csid, sd_state, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source pads */ + if (fmt->pad == MSM_CSID_PAD_SINK) { + for (i = MSM_CSID_PAD_FIRST_SRC; i < MSM_CSID_PADS_NUM; ++i) { + format = __csid_get_format(csid, sd_state, i, fmt->which); + + *format = fmt->format; + csid_try_format(csid, sd_state, i, format, fmt->which); + } + } + + return 0; +} + +/* + * csid_init_formats - Initialize formats on all pads + * @sd: CSID V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format = { + .pad = MSM_CSID_PAD_SINK, + .which = fh ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080 + } + }; + + return csid_set_format(sd, fh ? fh->state : NULL, &format); +} + +/* + * csid_set_test_pattern - Set test generator's pattern mode + * @csid: CSID device + * @value: desired test pattern mode + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_set_test_pattern(struct csid_device *csid, s32 value) +{ + struct csid_testgen_config *tg = &csid->testgen; + + /* If CSID is linked to CSIPHY, do not allow to enable test generator */ + if (value && media_pad_remote_pad_first(&csid->pads[MSM_CSID_PAD_SINK])) + return -EBUSY; + + tg->enabled = !!value; + + return csid->ops->configure_testgen_pattern(csid, value); +} + +/* + * csid_s_ctrl - Handle set control subdev method + * @ctrl: pointer to v4l2 control structure + * + * Return 0 on success or a negative error code otherwise + */ +static int csid_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct csid_device *csid = container_of(ctrl->handler, + struct csid_device, ctrls); + int ret = -EINVAL; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + ret = csid_set_test_pattern(csid, ctrl->val); + break; + } + + return ret; +} + +static const struct v4l2_ctrl_ops csid_ctrl_ops = { + .s_ctrl = csid_s_ctrl, +}; + +/* + * msm_csid_subdev_init - Initialize CSID device structure and resources + * @csid: CSID device + * @res: CSID module resources table + * @id: CSID module id + * + * Return 0 on success or a negative error code otherwise + */ +int msm_csid_subdev_init(struct camss *camss, struct csid_device *csid, + const struct resources *res, u8 id) +{ + struct device *dev = camss->dev; + struct platform_device *pdev = to_platform_device(dev); + int i, j; + int ret; + + csid->camss = camss; + csid->id = id; + + if (camss->version == CAMSS_8x16) { + csid->ops = &csid_ops_4_1; + } else if (camss->version == CAMSS_8x96 || + camss->version == CAMSS_660) { + csid->ops = &csid_ops_4_7; + } else if (camss->version == CAMSS_845 || + camss->version == CAMSS_8250) { + csid->ops = &csid_ops_gen2; + } else { + return -EINVAL; + } + csid->ops->subdev_init(csid); + + /* Memory */ + + if (camss->version == CAMSS_8250) { + /* for titan 480, CSID registers are inside the VFE region, + * between the VFE "top" and "bus" registers. this requires + * VFE to be initialized before CSID + */ + if (id >= 2) /* VFE/CSID lite */ + csid->base = camss->vfe[id].base + VFE_480_LITE_CSID_OFFSET; + else + csid->base = camss->vfe[id].base + VFE_480_CSID_OFFSET; + } else { + csid->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]); + if (IS_ERR(csid->base)) + return PTR_ERR(csid->base); + } + + /* Interrupt */ + + ret = platform_get_irq_byname(pdev, res->interrupt[0]); + if (ret < 0) + return ret; + + csid->irq = ret; + snprintf(csid->irq_name, sizeof(csid->irq_name), "%s_%s%d", + dev_name(dev), MSM_CSID_NAME, csid->id); + ret = devm_request_irq(dev, csid->irq, csid->ops->isr, + IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN, + csid->irq_name, csid); + if (ret < 0) { + dev_err(dev, "request_irq failed: %d\n", ret); + return ret; + } + + /* Clocks */ + + csid->nclocks = 0; + while (res->clock[csid->nclocks]) + csid->nclocks++; + + csid->clock = devm_kcalloc(dev, csid->nclocks, sizeof(*csid->clock), + GFP_KERNEL); + if (!csid->clock) + return -ENOMEM; + + for (i = 0; i < csid->nclocks; i++) { + struct camss_clock *clock = &csid->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->name = res->clock[i]; + + clock->nfreqs = 0; + while (res->clock_rate[i][clock->nfreqs]) + clock->nfreqs++; + + if (!clock->nfreqs) { + clock->freq = NULL; + continue; + } + + clock->freq = devm_kcalloc(dev, + clock->nfreqs, + sizeof(*clock->freq), + GFP_KERNEL); + if (!clock->freq) + return -ENOMEM; + + for (j = 0; j < clock->nfreqs; j++) + clock->freq[j] = res->clock_rate[i][j]; + } + + /* Regulator */ + for (i = 0; i < ARRAY_SIZE(res->regulators); i++) { + if (res->regulators[i]) + csid->num_supplies++; + } + + if (csid->num_supplies) { + csid->supplies = devm_kmalloc_array(camss->dev, + csid->num_supplies, + sizeof(*csid->supplies), + GFP_KERNEL); + if (!csid->supplies) + return -ENOMEM; + } + + for (i = 0; i < csid->num_supplies; i++) + csid->supplies[i].supply = res->regulators[i]; + + ret = devm_regulator_bulk_get(camss->dev, csid->num_supplies, + csid->supplies); + if (ret) + return ret; + + init_completion(&csid->reset_complete); + + return 0; +} + +/* + * msm_csid_get_csid_id - Get CSID HW module id + * @entity: Pointer to CSID media entity structure + * @id: Return CSID HW module id here + */ +void msm_csid_get_csid_id(struct media_entity *entity, u8 *id) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct csid_device *csid = v4l2_get_subdevdata(sd); + + *id = csid->id; +} + +/* + * csid_get_lane_assign - Calculate CSI2 lane assign configuration parameter + * @lane_cfg - CSI2 lane configuration + * + * Return lane assign + */ +static u32 csid_get_lane_assign(struct csiphy_lanes_cfg *lane_cfg) +{ + u32 lane_assign = 0; + int i; + + for (i = 0; i < lane_cfg->num_data; i++) + lane_assign |= lane_cfg->data[i].pos << (i * 4); + + return lane_assign; +} + +/* + * csid_link_setup - Setup CSID connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad + * @remote: Pointer to remote pad + * @flags: Link flags + * + * Return 0 on success + */ +static int csid_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (flags & MEDIA_LNK_FL_ENABLED) + if (media_pad_remote_pad_first(local)) + return -EBUSY; + + if ((local->flags & MEDIA_PAD_FL_SINK) && + (flags & MEDIA_LNK_FL_ENABLED)) { + struct v4l2_subdev *sd; + struct csid_device *csid; + struct csiphy_device *csiphy; + struct csiphy_lanes_cfg *lane_cfg; + + sd = media_entity_to_v4l2_subdev(entity); + csid = v4l2_get_subdevdata(sd); + + /* If test generator is enabled */ + /* do not allow a link from CSIPHY to CSID */ + if (csid->testgen_mode->cur.val != 0) + return -EBUSY; + + sd = media_entity_to_v4l2_subdev(remote->entity); + csiphy = v4l2_get_subdevdata(sd); + + /* If a sensor is not linked to CSIPHY */ + /* do no allow a link from CSIPHY to CSID */ + if (!csiphy->cfg.csi2) + return -EPERM; + + csid->phy.csiphy_id = csiphy->id; + + lane_cfg = &csiphy->cfg.csi2->lane_cfg; + csid->phy.lane_cnt = lane_cfg->num_data; + csid->phy.lane_assign = csid_get_lane_assign(lane_cfg); + } + /* Decide which virtual channels to enable based on which source pads are enabled */ + if (local->flags & MEDIA_PAD_FL_SOURCE) { + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct csid_device *csid = v4l2_get_subdevdata(sd); + struct device *dev = csid->camss->dev; + + if (flags & MEDIA_LNK_FL_ENABLED) + csid->phy.en_vc |= BIT(local->index - 1); + else + csid->phy.en_vc &= ~BIT(local->index - 1); + + csid->phy.need_vc_update = true; + + dev_dbg(dev, "%s: Enabled CSID virtual channels mask 0x%x\n", + __func__, csid->phy.en_vc); + } + + return 0; +} + +static const struct v4l2_subdev_core_ops csid_core_ops = { + .s_power = csid_set_power, + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_video_ops csid_video_ops = { + .s_stream = csid_set_stream, +}; + +static const struct v4l2_subdev_pad_ops csid_pad_ops = { + .enum_mbus_code = csid_enum_mbus_code, + .enum_frame_size = csid_enum_frame_size, + .get_fmt = csid_get_format, + .set_fmt = csid_set_format, +}; + +static const struct v4l2_subdev_ops csid_v4l2_ops = { + .core = &csid_core_ops, + .video = &csid_video_ops, + .pad = &csid_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops csid_v4l2_internal_ops = { + .open = csid_init_formats, +}; + +static const struct media_entity_operations csid_media_ops = { + .link_setup = csid_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * msm_csid_register_entity - Register subdev node for CSID module + * @csid: CSID device + * @v4l2_dev: V4L2 device + * + * Return 0 on success or a negative error code otherwise + */ +int msm_csid_register_entity(struct csid_device *csid, + struct v4l2_device *v4l2_dev) +{ + struct v4l2_subdev *sd = &csid->subdev; + struct media_pad *pads = csid->pads; + struct device *dev = csid->camss->dev; + int i; + int ret; + + v4l2_subdev_init(sd, &csid_v4l2_ops); + sd->internal_ops = &csid_v4l2_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", + MSM_CSID_NAME, csid->id); + v4l2_set_subdevdata(sd, csid); + + ret = v4l2_ctrl_handler_init(&csid->ctrls, 1); + if (ret < 0) { + dev_err(dev, "Failed to init ctrl handler: %d\n", ret); + return ret; + } + + csid->testgen_mode = v4l2_ctrl_new_std_menu_items(&csid->ctrls, + &csid_ctrl_ops, V4L2_CID_TEST_PATTERN, + csid->testgen.nmodes, 0, 0, + csid->testgen.modes); + + if (csid->ctrls.error) { + dev_err(dev, "Failed to init ctrl: %d\n", csid->ctrls.error); + ret = csid->ctrls.error; + goto free_ctrl; + } + + csid->subdev.ctrl_handler = &csid->ctrls; + + ret = csid_init_formats(sd, NULL); + if (ret < 0) { + dev_err(dev, "Failed to init format: %d\n", ret); + goto free_ctrl; + } + + pads[MSM_CSID_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + for (i = MSM_CSID_PAD_FIRST_SRC; i < MSM_CSID_PADS_NUM; ++i) + pads[i].flags = MEDIA_PAD_FL_SOURCE; + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &csid_media_ops; + ret = media_entity_pads_init(&sd->entity, MSM_CSID_PADS_NUM, pads); + if (ret < 0) { + dev_err(dev, "Failed to init media entity: %d\n", ret); + goto free_ctrl; + } + + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(dev, "Failed to register subdev: %d\n", ret); + goto media_cleanup; + } + + return 0; + +media_cleanup: + media_entity_cleanup(&sd->entity); +free_ctrl: + v4l2_ctrl_handler_free(&csid->ctrls); + + return ret; +} + +/* + * msm_csid_unregister_entity - Unregister CSID module subdev node + * @csid: CSID device + */ +void msm_csid_unregister_entity(struct csid_device *csid) +{ + v4l2_device_unregister_subdev(&csid->subdev); + media_entity_cleanup(&csid->subdev.entity); + v4l2_ctrl_handler_free(&csid->ctrls); +} diff --git a/drivers/media/platform/qcom/camss/camss-csid.h b/drivers/media/platform/qcom/camss/camss-csid.h new file mode 100644 index 000000000..d4b48432a --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csid.h @@ -0,0 +1,219 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-csid.h + * + * Qualcomm MSM Camera Subsystem - CSID (CSI Decoder) Module + * + * Copyright (c) 2011-2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_CSID_H +#define QC_MSM_CAMSS_CSID_H + +#include +#include +#include +#include +#include +#include +#include + +#define MSM_CSID_PAD_SINK 0 +#define MSM_CSID_PAD_FIRST_SRC 1 +#define MSM_CSID_PADS_NUM 5 + +#define MSM_CSID_PAD_SRC (MSM_CSID_PAD_FIRST_SRC) + +/* CSID hardware can demultiplex up to 4 outputs */ +#define MSM_CSID_MAX_SRC_STREAMS 4 + +#define DATA_TYPE_EMBEDDED_DATA_8BIT 0x12 +#define DATA_TYPE_YUV420_8BIT 0x18 +#define DATA_TYPE_YUV420_10BIT 0x19 +#define DATA_TYPE_YUV420_8BIT_LEGACY 0x1a +#define DATA_TYPE_YUV420_8BIT_SHIFTED 0x1c /* Chroma Shifted Pixel Sampling */ +#define DATA_TYPE_YUV420_10BIT_SHIFTED 0x1d /* Chroma Shifted Pixel Sampling */ +#define DATA_TYPE_YUV422_8BIT 0x1e +#define DATA_TYPE_YUV422_10BIT 0x1f +#define DATA_TYPE_RGB444 0x20 +#define DATA_TYPE_RGB555 0x21 +#define DATA_TYPE_RGB565 0x22 +#define DATA_TYPE_RGB666 0x23 +#define DATA_TYPE_RGB888 0x24 +#define DATA_TYPE_RAW_24BIT 0x27 +#define DATA_TYPE_RAW_6BIT 0x28 +#define DATA_TYPE_RAW_7BIT 0x29 +#define DATA_TYPE_RAW_8BIT 0x2a +#define DATA_TYPE_RAW_10BIT 0x2b +#define DATA_TYPE_RAW_12BIT 0x2c +#define DATA_TYPE_RAW_14BIT 0x2d +#define DATA_TYPE_RAW_16BIT 0x2e +#define DATA_TYPE_RAW_20BIT 0x2f + +#define CSID_RESET_TIMEOUT_MS 500 + +enum csid_testgen_mode { + CSID_PAYLOAD_MODE_DISABLED = 0, + CSID_PAYLOAD_MODE_INCREMENTING = 1, + CSID_PAYLOAD_MODE_ALTERNATING_55_AA = 2, + CSID_PAYLOAD_MODE_ALL_ZEROES = 3, + CSID_PAYLOAD_MODE_ALL_ONES = 4, + CSID_PAYLOAD_MODE_RANDOM = 5, + CSID_PAYLOAD_MODE_USER_SPECIFIED = 6, + CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN1 = 6, /* excluding disabled */ + CSID_PAYLOAD_MODE_COMPLEX_PATTERN = 7, + CSID_PAYLOAD_MODE_COLOR_BOX = 8, + CSID_PAYLOAD_MODE_COLOR_BARS = 9, + CSID_PAYLOAD_MODE_NUM_SUPPORTED_GEN2 = 9, /* excluding disabled */ +}; + +struct csid_format { + u32 code; + u8 data_type; + u8 decode_format; + u8 bpp; + u8 spp; /* bus samples per pixel */ +}; + +struct csid_testgen_config { + enum csid_testgen_mode mode; + const char * const*modes; + u8 nmodes; + u8 enabled; +}; + +struct csid_phy_config { + u8 csiphy_id; + u8 lane_cnt; + u32 lane_assign; + u32 en_vc; + u8 need_vc_update; +}; + +struct csid_device; + +struct csid_hw_ops { + /* + * configure_stream - Configures and starts CSID input stream + * @csid: CSID device + */ + void (*configure_stream)(struct csid_device *csid, u8 enable); + + /* + * configure_testgen_pattern - Validates and configures output pattern mode + * of test pattern generator + * @csid: CSID device + */ + int (*configure_testgen_pattern)(struct csid_device *csid, s32 val); + + /* + * hw_version - Read hardware version register from hardware + * @csid: CSID device + */ + u32 (*hw_version)(struct csid_device *csid); + + /* + * isr - CSID module interrupt service routine + * @irq: Interrupt line + * @dev: CSID device + * + * Return IRQ_HANDLED on success + */ + irqreturn_t (*isr)(int irq, void *dev); + + /* + * reset - Trigger reset on CSID module and wait to complete + * @csid: CSID device + * + * Return 0 on success or a negative error code otherwise + */ + int (*reset)(struct csid_device *csid); + + /* + * src_pad_code - Pick an output/src format based on the input/sink format + * @csid: CSID device + * @sink_code: The sink format of the input + * @match_format_idx: Request preferred index, as defined by subdevice csid_format. + * Set @match_code to 0 if used. + * @match_code: Request preferred code, set @match_format_idx to 0 if used + * + * Return 0 on failure or src format code otherwise + */ + u32 (*src_pad_code)(struct csid_device *csid, u32 sink_code, + unsigned int match_format_idx, u32 match_code); + + /* + * subdev_init - Initialize CSID device according for hardware revision + * @csid: CSID device + */ + void (*subdev_init)(struct csid_device *csid); +}; + +struct csid_device { + struct camss *camss; + u8 id; + struct v4l2_subdev subdev; + struct media_pad pads[MSM_CSID_PADS_NUM]; + void __iomem *base; + u32 irq; + char irq_name[30]; + struct camss_clock *clock; + int nclocks; + struct regulator_bulk_data *supplies; + int num_supplies; + struct completion reset_complete; + struct csid_testgen_config testgen; + struct csid_phy_config phy; + struct v4l2_mbus_framefmt fmt[MSM_CSID_PADS_NUM]; + struct v4l2_ctrl_handler ctrls; + struct v4l2_ctrl *testgen_mode; + const struct csid_format *formats; + unsigned int nformats; + const struct csid_hw_ops *ops; +}; + +struct resources; + +/* + * csid_find_code - Find a format code in an array using array index or format code + * @codes: Array of format codes + * @ncodes: Length of @code array + * @req_format_idx: Request preferred index, as defined by subdevice csid_format. + * Set @match_code to 0 if used. + * @match_code: Request preferred code, set @req_format_idx to 0 if used + * + * Return 0 on failure or format code otherwise + */ +u32 csid_find_code(u32 *codes, unsigned int ncode, + unsigned int match_format_idx, u32 match_code); + +/* + * csid_get_fmt_entry - Find csid_format entry with matching format code + * @formats: Array of format csid_format entries + * @nformats: Length of @nformats array + * @code: Desired format code + * + * Return formats[0] on failure to find code + */ +const struct csid_format *csid_get_fmt_entry(const struct csid_format *formats, + unsigned int nformats, + u32 code); + +int msm_csid_subdev_init(struct camss *camss, struct csid_device *csid, + const struct resources *res, u8 id); + +int msm_csid_register_entity(struct csid_device *csid, + struct v4l2_device *v4l2_dev); + +void msm_csid_unregister_entity(struct csid_device *csid); + +void msm_csid_get_csid_id(struct media_entity *entity, u8 *id); + +extern const char * const csid_testgen_modes[]; + +extern const struct csid_hw_ops csid_ops_4_1; +extern const struct csid_hw_ops csid_ops_4_7; +extern const struct csid_hw_ops csid_ops_gen2; + + +#endif /* QC_MSM_CAMSS_CSID_H */ diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-2ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-2ph-1-0.c new file mode 100644 index 000000000..cd4a8c369 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csiphy-2ph-1-0.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csiphy-2ph-1-0.c + * + * Qualcomm MSM Camera Subsystem - CSIPHY Module 2phase v1.0 + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2016-2018 Linaro Ltd. + */ + +#include "camss-csiphy.h" + +#include +#include +#include + +#define CAMSS_CSI_PHY_LNn_CFG2(n) (0x004 + 0x40 * (n)) +#define CAMSS_CSI_PHY_LNn_CFG3(n) (0x008 + 0x40 * (n)) +#define CAMSS_CSI_PHY_LN_CLK 1 +#define CAMSS_CSI_PHY_GLBL_RESET 0x140 +#define CAMSS_CSI_PHY_GLBL_PWR_CFG 0x144 +#define CAMSS_CSI_PHY_GLBL_IRQ_CMD 0x164 +#define CAMSS_CSI_PHY_HW_VERSION 0x188 +#define CAMSS_CSI_PHY_INTERRUPT_STATUSn(n) (0x18c + 0x4 * (n)) +#define CAMSS_CSI_PHY_INTERRUPT_MASKn(n) (0x1ac + 0x4 * (n)) +#define CAMSS_CSI_PHY_INTERRUPT_CLEARn(n) (0x1cc + 0x4 * (n)) +#define CAMSS_CSI_PHY_GLBL_T_INIT_CFG0 0x1ec +#define CAMSS_CSI_PHY_T_WAKEUP_CFG0 0x1f4 + +static u8 csiphy_get_lane_mask(struct csiphy_lanes_cfg *lane_cfg) +{ + u8 lane_mask; + int i; + + lane_mask = 1 << CAMSS_CSI_PHY_LN_CLK; + + for (i = 0; i < lane_cfg->num_data; i++) + lane_mask |= 1 << lane_cfg->data[i].pos; + + return lane_mask; +} + +static void csiphy_hw_version_read(struct csiphy_device *csiphy, + struct device *dev) +{ + u8 hw_version = readl_relaxed(csiphy->base + + CAMSS_CSI_PHY_HW_VERSION); + + dev_dbg(dev, "CSIPHY HW Version = 0x%02x\n", hw_version); +} + +/* + * csiphy_reset - Perform software reset on CSIPHY module + * @csiphy: CSIPHY device + */ +static void csiphy_reset(struct csiphy_device *csiphy) +{ + writel_relaxed(0x1, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); + usleep_range(5000, 8000); + writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); +} + +/* + * csiphy_settle_cnt_calc - Calculate settle count value + * + * Helper function to calculate settle count value. This is + * based on the CSI2 T_hs_settle parameter which in turn + * is calculated based on the CSI2 transmitter link frequency. + * + * Return settle count value or 0 if the CSI2 link frequency + * is not available + */ +static u8 csiphy_settle_cnt_calc(s64 link_freq, u32 timer_clk_rate) +{ + u32 ui; /* ps */ + u32 timer_period; /* ps */ + u32 t_hs_prepare_max; /* ps */ + u32 t_hs_prepare_zero_min; /* ps */ + u32 t_hs_settle; /* ps */ + u8 settle_cnt; + + if (link_freq <= 0) + return 0; + + ui = div_u64(1000000000000LL, link_freq); + ui /= 2; + t_hs_prepare_max = 85000 + 6 * ui; + t_hs_prepare_zero_min = 145000 + 10 * ui; + t_hs_settle = (t_hs_prepare_max + t_hs_prepare_zero_min) / 2; + + timer_period = div_u64(1000000000000LL, timer_clk_rate); + settle_cnt = t_hs_settle / timer_period - 1; + + return settle_cnt; +} + +static void csiphy_lanes_enable(struct csiphy_device *csiphy, + struct csiphy_config *cfg, + s64 link_freq, u8 lane_mask) +{ + struct csiphy_lanes_cfg *c = &cfg->csi2->lane_cfg; + u8 settle_cnt; + u8 val, l = 0; + int i = 0; + + settle_cnt = csiphy_settle_cnt_calc(link_freq, csiphy->timer_clk_rate); + + writel_relaxed(0x1, csiphy->base + + CAMSS_CSI_PHY_GLBL_T_INIT_CFG0); + writel_relaxed(0x1, csiphy->base + + CAMSS_CSI_PHY_T_WAKEUP_CFG0); + + val = 0x1; + val |= lane_mask << 1; + writel_relaxed(val, csiphy->base + CAMSS_CSI_PHY_GLBL_PWR_CFG); + + val = cfg->combo_mode << 4; + writel_relaxed(val, csiphy->base + CAMSS_CSI_PHY_GLBL_RESET); + + for (i = 0; i <= c->num_data; i++) { + if (i == c->num_data) + l = CAMSS_CSI_PHY_LN_CLK; + else + l = c->data[i].pos; + + writel_relaxed(0x10, csiphy->base + + CAMSS_CSI_PHY_LNn_CFG2(l)); + writel_relaxed(settle_cnt, csiphy->base + + CAMSS_CSI_PHY_LNn_CFG3(l)); + writel_relaxed(0x3f, csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_MASKn(l)); + writel_relaxed(0x3f, csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_CLEARn(l)); + } +} + +static void csiphy_lanes_disable(struct csiphy_device *csiphy, + struct csiphy_config *cfg) +{ + struct csiphy_lanes_cfg *c = &cfg->csi2->lane_cfg; + u8 l = 0; + int i = 0; + + for (i = 0; i <= c->num_data; i++) { + if (i == c->num_data) + l = CAMSS_CSI_PHY_LN_CLK; + else + l = c->data[i].pos; + + writel_relaxed(0x0, csiphy->base + + CAMSS_CSI_PHY_LNn_CFG2(l)); + } + + writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_PWR_CFG); +} + +/* + * csiphy_isr - CSIPHY module interrupt handler + * @irq: Interrupt line + * @dev: CSIPHY device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t csiphy_isr(int irq, void *dev) +{ + struct csiphy_device *csiphy = dev; + u8 i; + + for (i = 0; i < 8; i++) { + u8 val = readl_relaxed(csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_STATUSn(i)); + writel_relaxed(val, csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_CLEARn(i)); + writel_relaxed(0x1, csiphy->base + CAMSS_CSI_PHY_GLBL_IRQ_CMD); + writel_relaxed(0x0, csiphy->base + CAMSS_CSI_PHY_GLBL_IRQ_CMD); + writel_relaxed(0x0, csiphy->base + + CAMSS_CSI_PHY_INTERRUPT_CLEARn(i)); + } + + return IRQ_HANDLED; +} + +const struct csiphy_hw_ops csiphy_ops_2ph_1_0 = { + .get_lane_mask = csiphy_get_lane_mask, + .hw_version_read = csiphy_hw_version_read, + .reset = csiphy_reset, + .lanes_enable = csiphy_lanes_enable, + .lanes_disable = csiphy_lanes_disable, + .isr = csiphy_isr, +}; diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c new file mode 100644 index 000000000..4dba61b8d --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -0,0 +1,521 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csiphy-3ph-1-0.c + * + * Qualcomm MSM Camera Subsystem - CSIPHY Module 3phase v1.0 + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2016-2018 Linaro Ltd. + */ + +#include "camss.h" +#include "camss-csiphy.h" + +#include +#include +#include + +#define CSIPHY_3PH_LNn_CFG1(n) (0x000 + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG1_SWI_REC_DLY_PRG (BIT(7) | BIT(6)) +#define CSIPHY_3PH_LNn_CFG2(n) (0x004 + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG2_LP_REC_EN_INT BIT(3) +#define CSIPHY_3PH_LNn_CFG3(n) (0x008 + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG4(n) (0x00c + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG4_T_HS_CLK_MISS 0xa4 +#define CSIPHY_3PH_LNn_CFG4_T_HS_CLK_MISS_660 0xa5 +#define CSIPHY_3PH_LNn_CFG5(n) (0x010 + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG5_T_HS_DTERM 0x02 +#define CSIPHY_3PH_LNn_CFG5_HS_REC_EQ_FQ_INT 0x50 +#define CSIPHY_3PH_LNn_TEST_IMP(n) (0x01c + 0x100 * (n)) +#define CSIPHY_3PH_LNn_TEST_IMP_HS_TERM_IMP 0xa +#define CSIPHY_3PH_LNn_MISC1(n) (0x028 + 0x100 * (n)) +#define CSIPHY_3PH_LNn_MISC1_IS_CLKLANE BIT(2) +#define CSIPHY_3PH_LNn_CFG6(n) (0x02c + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG6_SWI_FORCE_INIT_EXIT BIT(0) +#define CSIPHY_3PH_LNn_CFG7(n) (0x030 + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG7_SWI_T_INIT 0x2 +#define CSIPHY_3PH_LNn_CFG8(n) (0x034 + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG8_SWI_SKIP_WAKEUP BIT(0) +#define CSIPHY_3PH_LNn_CFG8_SKEW_FILTER_ENABLE BIT(1) +#define CSIPHY_3PH_LNn_CFG9(n) (0x038 + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CFG9_SWI_T_WAKEUP 0x1 +#define CSIPHY_3PH_LNn_CSI_LANE_CTRL15(n) (0x03c + 0x100 * (n)) +#define CSIPHY_3PH_LNn_CSI_LANE_CTRL15_SWI_SOT_SYMBOL 0xb8 + +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(n) (0x800 + 0x4 * (n)) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE BIT(7) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B BIT(0) +#define CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID BIT(1) +#define CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(n) (0x8b0 + 0x4 * (n)) + +#define CSIPHY_DEFAULT_PARAMS 0 +#define CSIPHY_LANE_ENABLE 1 +#define CSIPHY_SETTLE_CNT_LOWER_BYTE 2 +#define CSIPHY_SETTLE_CNT_HIGHER_BYTE 3 +#define CSIPHY_DNP_PARAMS 4 +#define CSIPHY_2PH_REGS 5 +#define CSIPHY_3PH_REGS 6 + +struct csiphy_reg_t { + s32 reg_addr; + s32 reg_data; + s32 delay; + u32 csiphy_param_type; +}; + +/* GEN2 1.0 2PH */ +static const struct +csiphy_reg_t lane_regs_sdm845[5][14] = { + { + {0x0004, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x002C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0034, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x001C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0014, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0028, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x003C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0000, 0x91, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0008, 0x00, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x000c, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0010, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0038, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0060, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0064, 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, + { + {0x0704, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x072C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0734, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x071C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0714, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0728, 0x04, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x073C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0700, 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0708, 0x14, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x070C, 0xA5, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0710, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0738, 0x1F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0760, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0764, 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, + { + {0x0204, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x022C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0234, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x021C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0214, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0228, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x023C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0200, 0x91, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0208, 0x00, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x020C, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0210, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0238, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0260, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0264, 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, + { + {0x0404, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x042C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0434, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x041C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0414, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0428, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x043C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0400, 0x91, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0408, 0x00, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x040C, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0410, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0438, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0460, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0464, 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, + { + {0x0604, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x062C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0634, 0x0F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x061C, 0x0A, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0614, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0628, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x063C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0600, 0x91, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0608, 0x00, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x060C, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0610, 0x52, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0638, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0660, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0664, 0x7F, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, +}; + +/* GEN2 1.2.1 2PH */ +static const struct +csiphy_reg_t lane_regs_sm8250[5][20] = { + { + {0x0030, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0900, 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0908, 0x10, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0904, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0904, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0004, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x002C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0034, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0010, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x001C, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x003C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0008, 0x10, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0000, 0x8D, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x000c, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0038, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0014, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0028, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0024, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0800, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0884, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, + { + {0x0730, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C80, 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C88, 0x10, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C84, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C84, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0704, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x072C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0734, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0710, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x071C, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x073C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0708, 0x10, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0700, 0x80, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x070c, 0xA5, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0738, 0x1F, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0714, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0728, 0x04, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0724, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0800, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0884, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, + { + {0x0230, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0A00, 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0A08, 0x10, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0A04, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0A04, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0204, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x022C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0234, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0210, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x021C, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x023C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0208, 0x10, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0200, 0x8D, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x020c, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0238, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0214, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0228, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0224, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0800, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0884, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, + { + {0x0430, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0B00, 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0B08, 0x10, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0B04, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0B04, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0404, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x042C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0434, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0410, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x041C, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x043C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0408, 0x10, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0400, 0x8D, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x040c, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0438, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0414, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0428, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0424, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0800, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0884, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, + { + {0x0630, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C00, 0x05, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C08, 0x10, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C04, 0x00, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0C04, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0604, 0x0C, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x062C, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0634, 0x07, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0610, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x061C, 0x08, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x063C, 0xB8, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0608, 0x10, 0x00, CSIPHY_SETTLE_CNT_LOWER_BYTE}, + {0x0600, 0x8D, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x060c, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0638, 0xFE, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0614, 0x60, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0628, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0624, 0x00, 0x00, CSIPHY_DNP_PARAMS}, + {0x0800, 0x02, 0x00, CSIPHY_DEFAULT_PARAMS}, + {0x0884, 0x01, 0x00, CSIPHY_DEFAULT_PARAMS}, + }, +}; + +static void csiphy_hw_version_read(struct csiphy_device *csiphy, + struct device *dev) +{ + u32 hw_version; + + writel(CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_SHOW_REV_ID, + csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(6)); + + hw_version = readl_relaxed(csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(12)); + hw_version |= readl_relaxed(csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(13)) << 8; + hw_version |= readl_relaxed(csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(14)) << 16; + hw_version |= readl_relaxed(csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(15)) << 24; + + dev_dbg(dev, "CSIPHY 3PH HW Version = 0x%08x\n", hw_version); +} + +/* + * csiphy_reset - Perform software reset on CSIPHY module + * @csiphy: CSIPHY device + */ +static void csiphy_reset(struct csiphy_device *csiphy) +{ + writel_relaxed(0x1, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(0)); + usleep_range(5000, 8000); + writel_relaxed(0x0, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(0)); +} + +static irqreturn_t csiphy_isr(int irq, void *dev) +{ + struct csiphy_device *csiphy = dev; + int i; + + for (i = 0; i < 11; i++) { + int c = i + 22; + u8 val = readl_relaxed(csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_STATUSn(i)); + + writel_relaxed(val, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(c)); + } + + writel_relaxed(0x1, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(10)); + writel_relaxed(0x0, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(10)); + + for (i = 22; i < 33; i++) + writel_relaxed(0x0, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(i)); + + return IRQ_HANDLED; +} + +/* + * csiphy_settle_cnt_calc - Calculate settle count value + * + * Helper function to calculate settle count value. This is + * based on the CSI2 T_hs_settle parameter which in turn + * is calculated based on the CSI2 transmitter link frequency. + * + * Return settle count value or 0 if the CSI2 link frequency + * is not available + */ +static u8 csiphy_settle_cnt_calc(s64 link_freq, u32 timer_clk_rate) +{ + u32 ui; /* ps */ + u32 timer_period; /* ps */ + u32 t_hs_prepare_max; /* ps */ + u32 t_hs_settle; /* ps */ + u8 settle_cnt; + + if (link_freq <= 0) + return 0; + + ui = div_u64(1000000000000LL, link_freq); + ui /= 2; + t_hs_prepare_max = 85000 + 6 * ui; + t_hs_settle = t_hs_prepare_max; + + timer_period = div_u64(1000000000000LL, timer_clk_rate); + settle_cnt = t_hs_settle / timer_period - 6; + + return settle_cnt; +} + +static void csiphy_gen1_config_lanes(struct csiphy_device *csiphy, + struct csiphy_config *cfg, + u8 settle_cnt) +{ + struct csiphy_lanes_cfg *c = &cfg->csi2->lane_cfg; + int i, l = 0; + u8 val; + + for (i = 0; i <= c->num_data; i++) { + if (i == c->num_data) + l = 7; + else + l = c->data[i].pos * 2; + + val = CSIPHY_3PH_LNn_CFG1_SWI_REC_DLY_PRG; + val |= 0x17; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG1(l)); + + val = CSIPHY_3PH_LNn_CFG2_LP_REC_EN_INT; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG2(l)); + + val = settle_cnt; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG3(l)); + + val = CSIPHY_3PH_LNn_CFG5_T_HS_DTERM | + CSIPHY_3PH_LNn_CFG5_HS_REC_EQ_FQ_INT; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG5(l)); + + val = CSIPHY_3PH_LNn_CFG6_SWI_FORCE_INIT_EXIT; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG6(l)); + + val = CSIPHY_3PH_LNn_CFG7_SWI_T_INIT; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG7(l)); + + val = CSIPHY_3PH_LNn_CFG8_SWI_SKIP_WAKEUP | + CSIPHY_3PH_LNn_CFG8_SKEW_FILTER_ENABLE; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG8(l)); + + val = CSIPHY_3PH_LNn_CFG9_SWI_T_WAKEUP; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG9(l)); + + val = CSIPHY_3PH_LNn_TEST_IMP_HS_TERM_IMP; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_TEST_IMP(l)); + + val = CSIPHY_3PH_LNn_CSI_LANE_CTRL15_SWI_SOT_SYMBOL; + writel_relaxed(val, csiphy->base + + CSIPHY_3PH_LNn_CSI_LANE_CTRL15(l)); + } + + val = CSIPHY_3PH_LNn_CFG1_SWI_REC_DLY_PRG; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG1(l)); + + if (csiphy->camss->version == CAMSS_660) + val = CSIPHY_3PH_LNn_CFG4_T_HS_CLK_MISS_660; + else + val = CSIPHY_3PH_LNn_CFG4_T_HS_CLK_MISS; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_CFG4(l)); + + val = CSIPHY_3PH_LNn_MISC1_IS_CLKLANE; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_LNn_MISC1(l)); +} + +static void csiphy_gen2_config_lanes(struct csiphy_device *csiphy, + u8 settle_cnt) +{ + const struct csiphy_reg_t *r; + int i, l, array_size; + u32 val; + + switch (csiphy->camss->version) { + case CAMSS_845: + r = &lane_regs_sdm845[0][0]; + array_size = ARRAY_SIZE(lane_regs_sdm845[0]); + break; + case CAMSS_8250: + r = &lane_regs_sm8250[0][0]; + array_size = ARRAY_SIZE(lane_regs_sm8250[0]); + break; + default: + WARN(1, "unknown cspi version\n"); + return; + } + + for (l = 0; l < 5; l++) { + for (i = 0; i < array_size; i++, r++) { + switch (r->csiphy_param_type) { + case CSIPHY_SETTLE_CNT_LOWER_BYTE: + val = settle_cnt & 0xff; + break; + case CSIPHY_DNP_PARAMS: + continue; + default: + val = r->reg_data; + break; + } + writel_relaxed(val, csiphy->base + r->reg_addr); + } + } +} + +static u8 csiphy_get_lane_mask(struct csiphy_lanes_cfg *lane_cfg) +{ + u8 lane_mask; + int i; + + lane_mask = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; + + for (i = 0; i < lane_cfg->num_data; i++) + lane_mask |= 1 << lane_cfg->data[i].pos; + + return lane_mask; +} + +static void csiphy_lanes_enable(struct csiphy_device *csiphy, + struct csiphy_config *cfg, + s64 link_freq, u8 lane_mask) +{ + struct csiphy_lanes_cfg *c = &cfg->csi2->lane_cfg; + bool is_gen2 = (csiphy->camss->version == CAMSS_845 || + csiphy->camss->version == CAMSS_8250); + u8 settle_cnt; + u8 val; + int i; + + settle_cnt = csiphy_settle_cnt_calc(link_freq, csiphy->timer_clk_rate); + + val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL5_CLK_ENABLE; + for (i = 0; i < c->num_data; i++) + val |= BIT(c->data[i].pos * 2); + + writel_relaxed(val, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(5)); + + val = CSIPHY_3PH_CMN_CSI_COMMON_CTRL6_COMMON_PWRDN_B; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(6)); + + val = 0x02; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(7)); + + val = 0x00; + writel_relaxed(val, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(0)); + + if (is_gen2) + csiphy_gen2_config_lanes(csiphy, settle_cnt); + else + csiphy_gen1_config_lanes(csiphy, cfg, settle_cnt); + + /* IRQ_MASK registers - disable all interrupts */ + for (i = 11; i < 22; i++) + writel_relaxed(0, csiphy->base + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(i)); +} + +static void csiphy_lanes_disable(struct csiphy_device *csiphy, + struct csiphy_config *cfg) +{ + writel_relaxed(0, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(5)); + + writel_relaxed(0, csiphy->base + + CSIPHY_3PH_CMN_CSI_COMMON_CTRLn(6)); +} + +const struct csiphy_hw_ops csiphy_ops_3ph_1_0 = { + .get_lane_mask = csiphy_get_lane_mask, + .hw_version_read = csiphy_hw_version_read, + .reset = csiphy_reset, + .lanes_enable = csiphy_lanes_enable, + .lanes_disable = csiphy_lanes_disable, + .isr = csiphy_isr, +}; diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c new file mode 100644 index 000000000..3f726a723 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-csiphy.c + * + * Qualcomm MSM Camera Subsystem - CSIPHY Module + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2016-2018 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camss-csiphy.h" +#include "camss.h" + +#define MSM_CSIPHY_NAME "msm_csiphy" + +struct csiphy_format { + u32 code; + u8 bpp; +}; + +static const struct csiphy_format csiphy_formats_8x16[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, 8 }, + { MEDIA_BUS_FMT_YUYV8_2X8, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, 8 }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8 }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8 }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8 }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, + { MEDIA_BUS_FMT_Y10_1X10, 10 }, +}; + +static const struct csiphy_format csiphy_formats_8x96[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, 8 }, + { MEDIA_BUS_FMT_YUYV8_2X8, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, 8 }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8 }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8 }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8 }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, + { MEDIA_BUS_FMT_SBGGR14_1X14, 14 }, + { MEDIA_BUS_FMT_SGBRG14_1X14, 14 }, + { MEDIA_BUS_FMT_SGRBG14_1X14, 14 }, + { MEDIA_BUS_FMT_SRGGB14_1X14, 14 }, + { MEDIA_BUS_FMT_Y10_1X10, 10 }, +}; + +static const struct csiphy_format csiphy_formats_sdm845[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, 8 }, + { MEDIA_BUS_FMT_YUYV8_2X8, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, 8 }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8 }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8 }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8 }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, + { MEDIA_BUS_FMT_SBGGR14_1X14, 14 }, + { MEDIA_BUS_FMT_SGBRG14_1X14, 14 }, + { MEDIA_BUS_FMT_SGRBG14_1X14, 14 }, + { MEDIA_BUS_FMT_SRGGB14_1X14, 14 }, + { MEDIA_BUS_FMT_Y8_1X8, 8 }, + { MEDIA_BUS_FMT_Y10_1X10, 10 }, +}; + +/* + * csiphy_get_bpp - map media bus format to bits per pixel + * @formats: supported media bus formats array + * @nformats: size of @formats array + * @code: media bus format code + * + * Return number of bits per pixel + */ +static u8 csiphy_get_bpp(const struct csiphy_format *formats, + unsigned int nformats, u32 code) +{ + unsigned int i; + + for (i = 0; i < nformats; i++) + if (code == formats[i].code) + return formats[i].bpp; + + WARN(1, "Unknown format\n"); + + return formats[0].bpp; +} + +/* + * csiphy_set_clock_rates - Calculate and set clock rates on CSIPHY module + * @csiphy: CSIPHY device + */ +static int csiphy_set_clock_rates(struct csiphy_device *csiphy) +{ + struct device *dev = csiphy->camss->dev; + s64 link_freq; + int i, j; + int ret; + + u8 bpp = csiphy_get_bpp(csiphy->formats, csiphy->nformats, + csiphy->fmt[MSM_CSIPHY_PAD_SINK].code); + u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data; + + link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes); + if (link_freq < 0) + link_freq = 0; + + for (i = 0; i < csiphy->nclocks; i++) { + struct camss_clock *clock = &csiphy->clock[i]; + + if (csiphy->rate_set[i]) { + u64 min_rate = link_freq / 4; + long round_rate; + + camss_add_clock_margin(&min_rate); + + for (j = 0; j < clock->nfreqs; j++) + if (min_rate < clock->freq[j]) + break; + + if (j == clock->nfreqs) { + dev_err(dev, + "Pixel clock is too high for CSIPHY\n"); + return -EINVAL; + } + + /* if sensor pixel clock is not available */ + /* set highest possible CSIPHY clock rate */ + if (min_rate == 0) + j = clock->nfreqs - 1; + + round_rate = clk_round_rate(clock->clk, clock->freq[j]); + if (round_rate < 0) { + dev_err(dev, "clk round rate failed: %ld\n", + round_rate); + return -EINVAL; + } + + csiphy->timer_clk_rate = round_rate; + + ret = clk_set_rate(clock->clk, csiphy->timer_clk_rate); + if (ret < 0) { + dev_err(dev, "clk set rate failed: %d\n", ret); + return ret; + } + } + } + + return 0; +} + +/* + * csiphy_set_power - Power on/off CSIPHY module + * @sd: CSIPHY V4L2 subdevice + * @on: Requested power state + * + * Return 0 on success or a negative error code otherwise + */ +static int csiphy_set_power(struct v4l2_subdev *sd, int on) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct device *dev = csiphy->camss->dev; + + if (on) { + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + ret = csiphy_set_clock_rates(csiphy); + if (ret < 0) { + pm_runtime_put_sync(dev); + return ret; + } + + ret = camss_enable_clocks(csiphy->nclocks, csiphy->clock, dev); + if (ret < 0) { + pm_runtime_put_sync(dev); + return ret; + } + + enable_irq(csiphy->irq); + + csiphy->ops->reset(csiphy); + + csiphy->ops->hw_version_read(csiphy, dev); + } else { + disable_irq(csiphy->irq); + + camss_disable_clocks(csiphy->nclocks, csiphy->clock); + + pm_runtime_put_sync(dev); + } + + return 0; +} + +/* + * csiphy_stream_on - Enable streaming on CSIPHY module + * @csiphy: CSIPHY device + * + * Helper function to enable streaming on CSIPHY module. + * Main configuration of CSIPHY module is also done here. + * + * Return 0 on success or a negative error code otherwise + */ +static int csiphy_stream_on(struct csiphy_device *csiphy) +{ + struct csiphy_config *cfg = &csiphy->cfg; + s64 link_freq; + u8 lane_mask = csiphy->ops->get_lane_mask(&cfg->csi2->lane_cfg); + u8 bpp = csiphy_get_bpp(csiphy->formats, csiphy->nformats, + csiphy->fmt[MSM_CSIPHY_PAD_SINK].code); + u8 num_lanes = csiphy->cfg.csi2->lane_cfg.num_data; + u8 val; + + link_freq = camss_get_link_freq(&csiphy->subdev.entity, bpp, num_lanes); + + if (link_freq < 0) { + dev_err(csiphy->camss->dev, + "Cannot get CSI2 transmitter's link frequency\n"); + return -EINVAL; + } + + if (csiphy->base_clk_mux) { + val = readl_relaxed(csiphy->base_clk_mux); + if (cfg->combo_mode && (lane_mask & 0x18) == 0x18) { + val &= ~0xf0; + val |= cfg->csid_id << 4; + } else { + val &= ~0xf; + val |= cfg->csid_id; + } + writel_relaxed(val, csiphy->base_clk_mux); + + /* Enforce reg write ordering between clk mux & lane enabling */ + wmb(); + } + + csiphy->ops->lanes_enable(csiphy, cfg, link_freq, lane_mask); + + return 0; +} + +/* + * csiphy_stream_off - Disable streaming on CSIPHY module + * @csiphy: CSIPHY device + * + * Helper function to disable streaming on CSIPHY module + */ +static void csiphy_stream_off(struct csiphy_device *csiphy) +{ + csiphy->ops->lanes_disable(csiphy, &csiphy->cfg); +} + + +/* + * csiphy_set_stream - Enable/disable streaming on CSIPHY module + * @sd: CSIPHY V4L2 subdevice + * @enable: Requested streaming state + * + * Return 0 on success or a negative error code otherwise + */ +static int csiphy_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + int ret = 0; + + if (enable) + ret = csiphy_stream_on(csiphy); + else + csiphy_stream_off(csiphy); + + return ret; +} + +/* + * __csiphy_get_format - Get pointer to format structure + * @csiphy: CSIPHY device + * @cfg: V4L2 subdev pad configuration + * @pad: pad from which format is requested + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE format structure + */ +static struct v4l2_mbus_framefmt * +__csiphy_get_format(struct csiphy_device *csiphy, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&csiphy->subdev, sd_state, + pad); + + return &csiphy->fmt[pad]; +} + +/* + * csiphy_try_format - Handle try format by pad subdev method + * @csiphy: CSIPHY device + * @cfg: V4L2 subdev pad configuration + * @pad: pad on which format is requested + * @fmt: pointer to v4l2 format structure + * @which: wanted subdev format + */ +static void csiphy_try_format(struct csiphy_device *csiphy, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + + switch (pad) { + case MSM_CSIPHY_PAD_SINK: + /* Set format on sink pad */ + + for (i = 0; i < csiphy->nformats; i++) + if (fmt->code == csiphy->formats[i].code) + break; + + /* If not found, use UYVY as default */ + if (i >= csiphy->nformats) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + break; + + case MSM_CSIPHY_PAD_SRC: + /* Set and return a format same as sink pad */ + + *fmt = *__csiphy_get_format(csiphy, sd_state, + MSM_CSID_PAD_SINK, + which); + + break; + } +} + +/* + * csiphy_enum_mbus_code - Handle pixel format enumeration + * @sd: CSIPHY V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int csiphy_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + if (code->pad == MSM_CSIPHY_PAD_SINK) { + if (code->index >= csiphy->nformats) + return -EINVAL; + + code->code = csiphy->formats[code->index].code; + } else { + if (code->index > 0) + return -EINVAL; + + format = __csiphy_get_format(csiphy, sd_state, + MSM_CSIPHY_PAD_SINK, + code->which); + + code->code = format->code; + } + + return 0; +} + +/* + * csiphy_enum_frame_size - Handle frame size enumeration + * @sd: CSIPHY V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: pointer to v4l2_subdev_frame_size_enum structure + * return -EINVAL or zero on success + */ +static int csiphy_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + csiphy_try_format(csiphy, sd_state, fse->pad, &format, fse->which); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + csiphy_try_format(csiphy, sd_state, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * csiphy_get_format - Handle get format by pads subdev method + * @sd: CSIPHY V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int csiphy_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csiphy_get_format(csiphy, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +/* + * csiphy_set_format - Handle set format by pads subdev method + * @sd: CSIPHY V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int csiphy_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct csiphy_device *csiphy = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __csiphy_get_format(csiphy, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + csiphy_try_format(csiphy, sd_state, fmt->pad, &fmt->format, + fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == MSM_CSIPHY_PAD_SINK) { + format = __csiphy_get_format(csiphy, sd_state, + MSM_CSIPHY_PAD_SRC, + fmt->which); + + *format = fmt->format; + csiphy_try_format(csiphy, sd_state, MSM_CSIPHY_PAD_SRC, + format, + fmt->which); + } + + return 0; +} + +/* + * csiphy_init_formats - Initialize formats on all pads + * @sd: CSIPHY V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. + * + * Return 0 on success or a negative error code otherwise + */ +static int csiphy_init_formats(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format = { + .pad = MSM_CSIPHY_PAD_SINK, + .which = fh ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080 + } + }; + + return csiphy_set_format(sd, fh ? fh->state : NULL, &format); +} + +/* + * msm_csiphy_subdev_init - Initialize CSIPHY device structure and resources + * @csiphy: CSIPHY device + * @res: CSIPHY module resources table + * @id: CSIPHY module id + * + * Return 0 on success or a negative error code otherwise + */ +int msm_csiphy_subdev_init(struct camss *camss, + struct csiphy_device *csiphy, + const struct resources *res, u8 id) +{ + struct device *dev = camss->dev; + struct platform_device *pdev = to_platform_device(dev); + int i, j; + int ret; + + csiphy->camss = camss; + csiphy->id = id; + csiphy->cfg.combo_mode = 0; + + if (camss->version == CAMSS_8x16) { + csiphy->ops = &csiphy_ops_2ph_1_0; + csiphy->formats = csiphy_formats_8x16; + csiphy->nformats = ARRAY_SIZE(csiphy_formats_8x16); + } else if (camss->version == CAMSS_8x96 || + camss->version == CAMSS_660) { + csiphy->ops = &csiphy_ops_3ph_1_0; + csiphy->formats = csiphy_formats_8x96; + csiphy->nformats = ARRAY_SIZE(csiphy_formats_8x96); + } else if (camss->version == CAMSS_845 || + camss->version == CAMSS_8250) { + csiphy->ops = &csiphy_ops_3ph_1_0; + csiphy->formats = csiphy_formats_sdm845; + csiphy->nformats = ARRAY_SIZE(csiphy_formats_sdm845); + } else { + return -EINVAL; + } + + /* Memory */ + + csiphy->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]); + if (IS_ERR(csiphy->base)) + return PTR_ERR(csiphy->base); + + if (camss->version == CAMSS_8x16 || + camss->version == CAMSS_8x96) { + csiphy->base_clk_mux = + devm_platform_ioremap_resource_byname(pdev, res->reg[1]); + if (IS_ERR(csiphy->base_clk_mux)) + return PTR_ERR(csiphy->base_clk_mux); + } else { + csiphy->base_clk_mux = NULL; + } + + /* Interrupt */ + + ret = platform_get_irq_byname(pdev, res->interrupt[0]); + if (ret < 0) + return ret; + + csiphy->irq = ret; + snprintf(csiphy->irq_name, sizeof(csiphy->irq_name), "%s_%s%d", + dev_name(dev), MSM_CSIPHY_NAME, csiphy->id); + + ret = devm_request_irq(dev, csiphy->irq, csiphy->ops->isr, + IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN, + csiphy->irq_name, csiphy); + if (ret < 0) { + dev_err(dev, "request_irq failed: %d\n", ret); + return ret; + } + + /* Clocks */ + + csiphy->nclocks = 0; + while (res->clock[csiphy->nclocks]) + csiphy->nclocks++; + + csiphy->clock = devm_kcalloc(dev, + csiphy->nclocks, sizeof(*csiphy->clock), + GFP_KERNEL); + if (!csiphy->clock) + return -ENOMEM; + + csiphy->rate_set = devm_kcalloc(dev, + csiphy->nclocks, + sizeof(*csiphy->rate_set), + GFP_KERNEL); + if (!csiphy->rate_set) + return -ENOMEM; + + for (i = 0; i < csiphy->nclocks; i++) { + struct camss_clock *clock = &csiphy->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->name = res->clock[i]; + + clock->nfreqs = 0; + while (res->clock_rate[i][clock->nfreqs]) + clock->nfreqs++; + + if (!clock->nfreqs) { + clock->freq = NULL; + continue; + } + + clock->freq = devm_kcalloc(dev, + clock->nfreqs, + sizeof(*clock->freq), + GFP_KERNEL); + if (!clock->freq) + return -ENOMEM; + + for (j = 0; j < clock->nfreqs; j++) + clock->freq[j] = res->clock_rate[i][j]; + + if (!strcmp(clock->name, "csiphy0_timer") || + !strcmp(clock->name, "csiphy1_timer") || + !strcmp(clock->name, "csiphy2_timer") || + !strcmp(clock->name, "csiphy3_timer") || + !strcmp(clock->name, "csiphy4_timer") || + !strcmp(clock->name, "csiphy5_timer")) + csiphy->rate_set[i] = true; + + if (camss->version == CAMSS_660 && + (!strcmp(clock->name, "csi0_phy") || + !strcmp(clock->name, "csi1_phy") || + !strcmp(clock->name, "csi2_phy"))) + csiphy->rate_set[i] = true; + } + + return 0; +} + +/* + * csiphy_link_setup - Setup CSIPHY connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad + * @remote: Pointer to remote pad + * @flags: Link flags + * + * Rreturn 0 on success + */ +static int csiphy_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if ((local->flags & MEDIA_PAD_FL_SOURCE) && + (flags & MEDIA_LNK_FL_ENABLED)) { + struct v4l2_subdev *sd; + struct csiphy_device *csiphy; + struct csid_device *csid; + + if (media_pad_remote_pad_first(local)) + return -EBUSY; + + sd = media_entity_to_v4l2_subdev(entity); + csiphy = v4l2_get_subdevdata(sd); + + sd = media_entity_to_v4l2_subdev(remote->entity); + csid = v4l2_get_subdevdata(sd); + + csiphy->cfg.csid_id = csid->id; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops csiphy_core_ops = { + .s_power = csiphy_set_power, +}; + +static const struct v4l2_subdev_video_ops csiphy_video_ops = { + .s_stream = csiphy_set_stream, +}; + +static const struct v4l2_subdev_pad_ops csiphy_pad_ops = { + .enum_mbus_code = csiphy_enum_mbus_code, + .enum_frame_size = csiphy_enum_frame_size, + .get_fmt = csiphy_get_format, + .set_fmt = csiphy_set_format, +}; + +static const struct v4l2_subdev_ops csiphy_v4l2_ops = { + .core = &csiphy_core_ops, + .video = &csiphy_video_ops, + .pad = &csiphy_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops csiphy_v4l2_internal_ops = { + .open = csiphy_init_formats, +}; + +static const struct media_entity_operations csiphy_media_ops = { + .link_setup = csiphy_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * msm_csiphy_register_entity - Register subdev node for CSIPHY module + * @csiphy: CSIPHY device + * @v4l2_dev: V4L2 device + * + * Return 0 on success or a negative error code otherwise + */ +int msm_csiphy_register_entity(struct csiphy_device *csiphy, + struct v4l2_device *v4l2_dev) +{ + struct v4l2_subdev *sd = &csiphy->subdev; + struct media_pad *pads = csiphy->pads; + struct device *dev = csiphy->camss->dev; + int ret; + + v4l2_subdev_init(sd, &csiphy_v4l2_ops); + sd->internal_ops = &csiphy_v4l2_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", + MSM_CSIPHY_NAME, csiphy->id); + v4l2_set_subdevdata(sd, csiphy); + + ret = csiphy_init_formats(sd, NULL); + if (ret < 0) { + dev_err(dev, "Failed to init format: %d\n", ret); + return ret; + } + + pads[MSM_CSIPHY_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[MSM_CSIPHY_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &csiphy_media_ops; + ret = media_entity_pads_init(&sd->entity, MSM_CSIPHY_PADS_NUM, pads); + if (ret < 0) { + dev_err(dev, "Failed to init media entity: %d\n", ret); + return ret; + } + + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(dev, "Failed to register subdev: %d\n", ret); + media_entity_cleanup(&sd->entity); + } + + return ret; +} + +/* + * msm_csiphy_unregister_entity - Unregister CSIPHY module subdev node + * @csiphy: CSIPHY device + */ +void msm_csiphy_unregister_entity(struct csiphy_device *csiphy) +{ + v4l2_device_unregister_subdev(&csiphy->subdev); + media_entity_cleanup(&csiphy->subdev.entity); +} diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.h b/drivers/media/platform/qcom/camss/camss-csiphy.h new file mode 100644 index 000000000..1c14947f9 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-csiphy.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-csiphy.h + * + * Qualcomm MSM Camera Subsystem - CSIPHY Module + * + * Copyright (c) 2011-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2016-2018 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_CSIPHY_H +#define QC_MSM_CAMSS_CSIPHY_H + +#include +#include +#include +#include +#include +#include + +#define MSM_CSIPHY_PAD_SINK 0 +#define MSM_CSIPHY_PAD_SRC 1 +#define MSM_CSIPHY_PADS_NUM 2 + +struct csiphy_lane { + u8 pos; + u8 pol; +}; + +struct csiphy_lanes_cfg { + int num_data; + struct csiphy_lane *data; + struct csiphy_lane clk; +}; + +struct csiphy_csi2_cfg { + struct csiphy_lanes_cfg lane_cfg; +}; + +struct csiphy_config { + u8 combo_mode; + u8 csid_id; + struct csiphy_csi2_cfg *csi2; +}; + +struct csiphy_device; + +struct csiphy_hw_ops { + /* + * csiphy_get_lane_mask - Calculate CSI2 lane mask configuration parameter + * @lane_cfg - CSI2 lane configuration + * + * Return lane mask + */ + u8 (*get_lane_mask)(struct csiphy_lanes_cfg *lane_cfg); + void (*hw_version_read)(struct csiphy_device *csiphy, + struct device *dev); + void (*reset)(struct csiphy_device *csiphy); + void (*lanes_enable)(struct csiphy_device *csiphy, + struct csiphy_config *cfg, + s64 link_freq, u8 lane_mask); + void (*lanes_disable)(struct csiphy_device *csiphy, + struct csiphy_config *cfg); + irqreturn_t (*isr)(int irq, void *dev); +}; + +struct csiphy_device { + struct camss *camss; + u8 id; + struct v4l2_subdev subdev; + struct media_pad pads[MSM_CSIPHY_PADS_NUM]; + void __iomem *base; + void __iomem *base_clk_mux; + u32 irq; + char irq_name[30]; + struct camss_clock *clock; + bool *rate_set; + int nclocks; + u32 timer_clk_rate; + struct csiphy_config cfg; + struct v4l2_mbus_framefmt fmt[MSM_CSIPHY_PADS_NUM]; + const struct csiphy_hw_ops *ops; + const struct csiphy_format *formats; + unsigned int nformats; +}; + +struct resources; + +int msm_csiphy_subdev_init(struct camss *camss, + struct csiphy_device *csiphy, + const struct resources *res, u8 id); + +int msm_csiphy_register_entity(struct csiphy_device *csiphy, + struct v4l2_device *v4l2_dev); + +void msm_csiphy_unregister_entity(struct csiphy_device *csiphy); + +extern const struct csiphy_hw_ops csiphy_ops_2ph_1_0; +extern const struct csiphy_hw_ops csiphy_ops_3ph_1_0; + +#endif /* QC_MSM_CAMSS_CSIPHY_H */ diff --git a/drivers/media/platform/qcom/camss/camss-ispif.c b/drivers/media/platform/qcom/camss/camss-ispif.c new file mode 100644 index 000000000..b713f5b86 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-ispif.c @@ -0,0 +1,1453 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-ispif.c + * + * Qualcomm MSM Camera Subsystem - ISPIF (ISP Interface) Module + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camss-ispif.h" +#include "camss.h" + +#define MSM_ISPIF_NAME "msm_ispif" + +#define ISPIF_RST_CMD_0 0x008 +#define ISPIF_RST_CMD_1 0x00c +#define ISPIF_RST_CMD_0_STROBED_RST_EN (1 << 0) +#define ISPIF_RST_CMD_0_MISC_LOGIC_RST (1 << 1) +#define ISPIF_RST_CMD_0_SW_REG_RST (1 << 2) +#define ISPIF_RST_CMD_0_PIX_INTF_0_CSID_RST (1 << 3) +#define ISPIF_RST_CMD_0_PIX_INTF_0_VFE_RST (1 << 4) +#define ISPIF_RST_CMD_0_PIX_INTF_1_CSID_RST (1 << 5) +#define ISPIF_RST_CMD_0_PIX_INTF_1_VFE_RST (1 << 6) +#define ISPIF_RST_CMD_0_RDI_INTF_0_CSID_RST (1 << 7) +#define ISPIF_RST_CMD_0_RDI_INTF_0_VFE_RST (1 << 8) +#define ISPIF_RST_CMD_0_RDI_INTF_1_CSID_RST (1 << 9) +#define ISPIF_RST_CMD_0_RDI_INTF_1_VFE_RST (1 << 10) +#define ISPIF_RST_CMD_0_RDI_INTF_2_CSID_RST (1 << 11) +#define ISPIF_RST_CMD_0_RDI_INTF_2_VFE_RST (1 << 12) +#define ISPIF_RST_CMD_0_PIX_OUTPUT_0_MISR_RST (1 << 16) +#define ISPIF_RST_CMD_0_RDI_OUTPUT_0_MISR_RST (1 << 17) +#define ISPIF_RST_CMD_0_RDI_OUTPUT_1_MISR_RST (1 << 18) +#define ISPIF_RST_CMD_0_RDI_OUTPUT_2_MISR_RST (1 << 19) +#define ISPIF_IRQ_GLOBAL_CLEAR_CMD 0x01c +#define ISPIF_VFE_m_CTRL_0(m) (0x200 + 0x200 * (m)) +#define ISPIF_VFE_m_CTRL_0_PIX0_LINE_BUF_EN (1 << 6) +#define ISPIF_VFE_m_IRQ_MASK_0(m) (0x208 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE 0x00001249 +#define ISPIF_VFE_m_IRQ_MASK_0_PIX0_MASK 0x00001fff +#define ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE 0x02492000 +#define ISPIF_VFE_m_IRQ_MASK_0_RDI0_MASK 0x03ffe000 +#define ISPIF_VFE_m_IRQ_MASK_1(m) (0x20c + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE 0x00001249 +#define ISPIF_VFE_m_IRQ_MASK_1_PIX1_MASK 0x00001fff +#define ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE 0x02492000 +#define ISPIF_VFE_m_IRQ_MASK_1_RDI1_MASK 0x03ffe000 +#define ISPIF_VFE_m_IRQ_MASK_2(m) (0x210 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE 0x00001249 +#define ISPIF_VFE_m_IRQ_MASK_2_RDI2_MASK 0x00001fff +#define ISPIF_VFE_m_IRQ_STATUS_0(m) (0x21c + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_STATUS_0_PIX0_OVERFLOW (1 << 12) +#define ISPIF_VFE_m_IRQ_STATUS_0_RDI0_OVERFLOW (1 << 25) +#define ISPIF_VFE_m_IRQ_STATUS_1(m) (0x220 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_STATUS_1_PIX1_OVERFLOW (1 << 12) +#define ISPIF_VFE_m_IRQ_STATUS_1_RDI1_OVERFLOW (1 << 25) +#define ISPIF_VFE_m_IRQ_STATUS_2(m) (0x224 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_STATUS_2_RDI2_OVERFLOW (1 << 12) +#define ISPIF_VFE_m_IRQ_CLEAR_0(m) (0x230 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_CLEAR_1(m) (0x234 + 0x200 * (m)) +#define ISPIF_VFE_m_IRQ_CLEAR_2(m) (0x238 + 0x200 * (m)) +#define ISPIF_VFE_m_INTF_INPUT_SEL(m) (0x244 + 0x200 * (m)) +#define ISPIF_VFE_m_INTF_CMD_0(m) (0x248 + 0x200 * (m)) +#define ISPIF_VFE_m_INTF_CMD_1(m) (0x24c + 0x200 * (m)) +#define ISPIF_VFE_m_PIX_INTF_n_CID_MASK(m, n) \ + (0x254 + 0x200 * (m) + 0x4 * (n)) +#define ISPIF_VFE_m_RDI_INTF_n_CID_MASK(m, n) \ + (0x264 + 0x200 * (m) + 0x4 * (n)) +/* PACK_CFG registers are 8x96 only */ +#define ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_0(m, n) \ + (0x270 + 0x200 * (m) + 0x4 * (n)) +#define ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_1(m, n) \ + (0x27c + 0x200 * (m) + 0x4 * (n)) +#define ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_0_CID_c_PLAIN(c) \ + (1 << ((cid % 8) * 4)) +#define ISPIF_VFE_m_PIX_INTF_n_STATUS(m, n) \ + (0x2c0 + 0x200 * (m) + 0x4 * (n)) +#define ISPIF_VFE_m_RDI_INTF_n_STATUS(m, n) \ + (0x2d0 + 0x200 * (m) + 0x4 * (n)) + +#define CSI_PIX_CLK_MUX_SEL 0x000 +#define CSI_RDI_CLK_MUX_SEL 0x008 + +#define ISPIF_TIMEOUT_SLEEP_US 1000 +#define ISPIF_TIMEOUT_ALL_US 1000000 +#define ISPIF_RESET_TIMEOUT_MS 500 + +enum ispif_intf_cmd { + CMD_DISABLE_FRAME_BOUNDARY = 0x0, + CMD_ENABLE_FRAME_BOUNDARY = 0x1, + CMD_DISABLE_IMMEDIATELY = 0x2, + CMD_ALL_DISABLE_IMMEDIATELY = 0xaaaaaaaa, + CMD_ALL_NO_CHANGE = 0xffffffff, +}; + +static const u32 ispif_formats_8x16[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_Y10_1X10, +}; + +static const u32 ispif_formats_8x96[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SBGGR14_1X14, + MEDIA_BUS_FMT_SGBRG14_1X14, + MEDIA_BUS_FMT_SGRBG14_1X14, + MEDIA_BUS_FMT_SRGGB14_1X14, + MEDIA_BUS_FMT_Y10_1X10, + MEDIA_BUS_FMT_Y10_2X8_PADHI_LE, +}; + +/* + * ispif_isr_8x96 - ISPIF module interrupt handler for 8x96 + * @irq: Interrupt line + * @dev: ISPIF device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t ispif_isr_8x96(int irq, void *dev) +{ + struct ispif_device *ispif = dev; + struct camss *camss = ispif->camss; + u32 value0, value1, value2, value3, value4, value5; + + value0 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_0(0)); + value1 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_1(0)); + value2 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_2(0)); + value3 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_0(1)); + value4 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_1(1)); + value5 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_2(1)); + + writel_relaxed(value0, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(0)); + writel_relaxed(value1, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(0)); + writel_relaxed(value2, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_2(0)); + writel_relaxed(value3, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(1)); + writel_relaxed(value4, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(1)); + writel_relaxed(value5, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_2(1)); + + writel(0x1, ispif->base + ISPIF_IRQ_GLOBAL_CLEAR_CMD); + + if ((value0 >> 27) & 0x1) + complete(&ispif->reset_complete[0]); + + if ((value3 >> 27) & 0x1) + complete(&ispif->reset_complete[1]); + + if (unlikely(value0 & ISPIF_VFE_m_IRQ_STATUS_0_PIX0_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 pix0 overflow\n"); + + if (unlikely(value0 & ISPIF_VFE_m_IRQ_STATUS_0_RDI0_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 rdi0 overflow\n"); + + if (unlikely(value1 & ISPIF_VFE_m_IRQ_STATUS_1_PIX1_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 pix1 overflow\n"); + + if (unlikely(value1 & ISPIF_VFE_m_IRQ_STATUS_1_RDI1_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 rdi1 overflow\n"); + + if (unlikely(value2 & ISPIF_VFE_m_IRQ_STATUS_2_RDI2_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 rdi2 overflow\n"); + + if (unlikely(value3 & ISPIF_VFE_m_IRQ_STATUS_0_PIX0_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE1 pix0 overflow\n"); + + if (unlikely(value3 & ISPIF_VFE_m_IRQ_STATUS_0_RDI0_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE1 rdi0 overflow\n"); + + if (unlikely(value4 & ISPIF_VFE_m_IRQ_STATUS_1_PIX1_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE1 pix1 overflow\n"); + + if (unlikely(value4 & ISPIF_VFE_m_IRQ_STATUS_1_RDI1_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE1 rdi1 overflow\n"); + + if (unlikely(value5 & ISPIF_VFE_m_IRQ_STATUS_2_RDI2_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE1 rdi2 overflow\n"); + + return IRQ_HANDLED; +} + +/* + * ispif_isr_8x16 - ISPIF module interrupt handler for 8x16 + * @irq: Interrupt line + * @dev: ISPIF device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t ispif_isr_8x16(int irq, void *dev) +{ + struct ispif_device *ispif = dev; + struct camss *camss = ispif->camss; + u32 value0, value1, value2; + + value0 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_0(0)); + value1 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_1(0)); + value2 = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_STATUS_2(0)); + + writel_relaxed(value0, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(0)); + writel_relaxed(value1, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(0)); + writel_relaxed(value2, ispif->base + ISPIF_VFE_m_IRQ_CLEAR_2(0)); + + writel(0x1, ispif->base + ISPIF_IRQ_GLOBAL_CLEAR_CMD); + + if ((value0 >> 27) & 0x1) + complete(&ispif->reset_complete[0]); + + if (unlikely(value0 & ISPIF_VFE_m_IRQ_STATUS_0_PIX0_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 pix0 overflow\n"); + + if (unlikely(value0 & ISPIF_VFE_m_IRQ_STATUS_0_RDI0_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 rdi0 overflow\n"); + + if (unlikely(value1 & ISPIF_VFE_m_IRQ_STATUS_1_PIX1_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 pix1 overflow\n"); + + if (unlikely(value1 & ISPIF_VFE_m_IRQ_STATUS_1_RDI1_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 rdi1 overflow\n"); + + if (unlikely(value2 & ISPIF_VFE_m_IRQ_STATUS_2_RDI2_OVERFLOW)) + dev_err_ratelimited(camss->dev, "VFE0 rdi2 overflow\n"); + + return IRQ_HANDLED; +} + +static int ispif_vfe_reset(struct ispif_device *ispif, u8 vfe_id) +{ + struct camss *camss = ispif->camss; + + unsigned long time; + u32 val; + + if (vfe_id > (camss->vfe_num - 1)) { + dev_err(camss->dev, + "Error: asked reset for invalid VFE%d\n", vfe_id); + return -ENOENT; + } + + reinit_completion(&ispif->reset_complete[vfe_id]); + + val = ISPIF_RST_CMD_0_STROBED_RST_EN | + ISPIF_RST_CMD_0_MISC_LOGIC_RST | + ISPIF_RST_CMD_0_SW_REG_RST | + ISPIF_RST_CMD_0_PIX_INTF_0_CSID_RST | + ISPIF_RST_CMD_0_PIX_INTF_0_VFE_RST | + ISPIF_RST_CMD_0_PIX_INTF_1_CSID_RST | + ISPIF_RST_CMD_0_PIX_INTF_1_VFE_RST | + ISPIF_RST_CMD_0_RDI_INTF_0_CSID_RST | + ISPIF_RST_CMD_0_RDI_INTF_0_VFE_RST | + ISPIF_RST_CMD_0_RDI_INTF_1_CSID_RST | + ISPIF_RST_CMD_0_RDI_INTF_1_VFE_RST | + ISPIF_RST_CMD_0_RDI_INTF_2_CSID_RST | + ISPIF_RST_CMD_0_RDI_INTF_2_VFE_RST | + ISPIF_RST_CMD_0_PIX_OUTPUT_0_MISR_RST | + ISPIF_RST_CMD_0_RDI_OUTPUT_0_MISR_RST | + ISPIF_RST_CMD_0_RDI_OUTPUT_1_MISR_RST | + ISPIF_RST_CMD_0_RDI_OUTPUT_2_MISR_RST; + + if (vfe_id == 1) + writel_relaxed(val, ispif->base + ISPIF_RST_CMD_1); + else + writel_relaxed(val, ispif->base + ISPIF_RST_CMD_0); + + time = wait_for_completion_timeout(&ispif->reset_complete[vfe_id], + msecs_to_jiffies(ISPIF_RESET_TIMEOUT_MS)); + if (!time) { + dev_err(camss->dev, + "ISPIF for VFE%d reset timeout\n", vfe_id); + return -EIO; + } + + return 0; +} + +/* + * ispif_reset - Trigger reset on ISPIF module and wait to complete + * @ispif: ISPIF device + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_reset(struct ispif_device *ispif, u8 vfe_id) +{ + struct camss *camss = ispif->camss; + int ret; + + ret = camss_pm_domain_on(camss, PM_DOMAIN_VFE0); + if (ret < 0) + return ret; + + ret = camss_pm_domain_on(camss, PM_DOMAIN_VFE1); + if (ret < 0) + return ret; + + ret = camss_enable_clocks(ispif->nclocks_for_reset, + ispif->clock_for_reset, + camss->dev); + if (ret < 0) + return ret; + + ret = ispif_vfe_reset(ispif, vfe_id); + if (ret) + dev_dbg(camss->dev, "ISPIF Reset failed\n"); + + camss_disable_clocks(ispif->nclocks_for_reset, ispif->clock_for_reset); + + camss_pm_domain_off(camss, PM_DOMAIN_VFE0); + camss_pm_domain_off(camss, PM_DOMAIN_VFE1); + + return ret; +} + +/* + * ispif_set_power - Power on/off ISPIF module + * @sd: ISPIF V4L2 subdevice + * @on: Requested power state + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_set_power(struct v4l2_subdev *sd, int on) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct ispif_device *ispif = line->ispif; + struct device *dev = ispif->camss->dev; + int ret = 0; + + mutex_lock(&ispif->power_lock); + + if (on) { + if (ispif->power_count) { + /* Power is already on */ + ispif->power_count++; + goto exit; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto exit; + + ret = camss_enable_clocks(ispif->nclocks, ispif->clock, dev); + if (ret < 0) { + pm_runtime_put_sync(dev); + goto exit; + } + + ret = ispif_reset(ispif, line->vfe_id); + if (ret < 0) { + pm_runtime_put_sync(dev); + camss_disable_clocks(ispif->nclocks, ispif->clock); + goto exit; + } + + ispif->intf_cmd[line->vfe_id].cmd_0 = CMD_ALL_NO_CHANGE; + ispif->intf_cmd[line->vfe_id].cmd_1 = CMD_ALL_NO_CHANGE; + + ispif->power_count++; + } else { + if (ispif->power_count == 0) { + dev_err(dev, "ispif power off on power_count == 0\n"); + goto exit; + } else if (ispif->power_count == 1) { + camss_disable_clocks(ispif->nclocks, ispif->clock); + pm_runtime_put_sync(dev); + } + + ispif->power_count--; + } + +exit: + mutex_unlock(&ispif->power_lock); + + return ret; +} + +/* + * ispif_select_clk_mux - Select clock for PIX/RDI interface + * @ispif: ISPIF device + * @intf: VFE interface + * @csid: CSID HW module id + * @vfe: VFE HW module id + * @enable: enable or disable the selected clock + */ +static void ispif_select_clk_mux(struct ispif_device *ispif, + enum ispif_intf intf, u8 csid, + u8 vfe, u8 enable) +{ + u32 val; + + switch (intf) { + case PIX0: + val = readl_relaxed(ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); + val &= ~(0xf << (vfe * 8)); + if (enable) + val |= (csid << (vfe * 8)); + writel_relaxed(val, ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); + break; + + case RDI0: + val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + val &= ~(0xf << (vfe * 12)); + if (enable) + val |= (csid << (vfe * 12)); + writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + break; + + case PIX1: + val = readl_relaxed(ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); + val &= ~(0xf << (4 + (vfe * 8))); + if (enable) + val |= (csid << (4 + (vfe * 8))); + writel_relaxed(val, ispif->base_clk_mux + CSI_PIX_CLK_MUX_SEL); + break; + + case RDI1: + val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + val &= ~(0xf << (4 + (vfe * 12))); + if (enable) + val |= (csid << (4 + (vfe * 12))); + writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + break; + + case RDI2: + val = readl_relaxed(ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + val &= ~(0xf << (8 + (vfe * 12))); + if (enable) + val |= (csid << (8 + (vfe * 12))); + writel_relaxed(val, ispif->base_clk_mux + CSI_RDI_CLK_MUX_SEL); + break; + } + + mb(); +} + +/* + * ispif_validate_intf_status - Validate current status of PIX/RDI interface + * @ispif: ISPIF device + * @intf: VFE interface + * @vfe: VFE HW module id + * + * Return 0 when interface is idle or -EBUSY otherwise + */ +static int ispif_validate_intf_status(struct ispif_device *ispif, + enum ispif_intf intf, u8 vfe) +{ + int ret = 0; + u32 val = 0; + + switch (intf) { + case PIX0: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 0)); + break; + case RDI0: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 0)); + break; + case PIX1: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 1)); + break; + case RDI1: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 1)); + break; + case RDI2: + val = readl_relaxed(ispif->base + + ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 2)); + break; + } + + if ((val & 0xf) != 0xf) { + dev_err(ispif->camss->dev, "%s: ispif is busy: 0x%x\n", + __func__, val); + ret = -EBUSY; + } + + return ret; +} + +/* + * ispif_wait_for_stop - Wait for PIX/RDI interface to stop + * @ispif: ISPIF device + * @intf: VFE interface + * @vfe: VFE HW module id + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_wait_for_stop(struct ispif_device *ispif, + enum ispif_intf intf, u8 vfe) +{ + u32 addr = 0; + u32 stop_flag = 0; + int ret; + + switch (intf) { + case PIX0: + addr = ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 0); + break; + case RDI0: + addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 0); + break; + case PIX1: + addr = ISPIF_VFE_m_PIX_INTF_n_STATUS(vfe, 1); + break; + case RDI1: + addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 1); + break; + case RDI2: + addr = ISPIF_VFE_m_RDI_INTF_n_STATUS(vfe, 2); + break; + } + + ret = readl_poll_timeout(ispif->base + addr, + stop_flag, + (stop_flag & 0xf) == 0xf, + ISPIF_TIMEOUT_SLEEP_US, + ISPIF_TIMEOUT_ALL_US); + if (ret < 0) + dev_err(ispif->camss->dev, "%s: ispif stop timeout\n", + __func__); + + return ret; +} + +/* + * ispif_select_csid - Select CSID HW module for input from + * @ispif: ISPIF device + * @intf: VFE interface + * @csid: CSID HW module id + * @vfe: VFE HW module id + * @enable: enable or disable the selected input + */ +static void ispif_select_csid(struct ispif_device *ispif, enum ispif_intf intf, + u8 csid, u8 vfe, u8 enable) +{ + u32 val; + + val = readl_relaxed(ispif->base + ISPIF_VFE_m_INTF_INPUT_SEL(vfe)); + switch (intf) { + case PIX0: + val &= ~(BIT(1) | BIT(0)); + if (enable) + val |= csid; + break; + case RDI0: + val &= ~(BIT(5) | BIT(4)); + if (enable) + val |= (csid << 4); + break; + case PIX1: + val &= ~(BIT(9) | BIT(8)); + if (enable) + val |= (csid << 8); + break; + case RDI1: + val &= ~(BIT(13) | BIT(12)); + if (enable) + val |= (csid << 12); + break; + case RDI2: + val &= ~(BIT(21) | BIT(20)); + if (enable) + val |= (csid << 20); + break; + } + + writel(val, ispif->base + ISPIF_VFE_m_INTF_INPUT_SEL(vfe)); +} + +/* + * ispif_select_cid - Enable/disable desired CID + * @ispif: ISPIF device + * @intf: VFE interface + * @cid: desired CID to enable/disable + * @vfe: VFE HW module id + * @enable: enable or disable the desired CID + */ +static void ispif_select_cid(struct ispif_device *ispif, enum ispif_intf intf, + u8 cid, u8 vfe, u8 enable) +{ + u32 cid_mask = 1 << cid; + u32 addr = 0; + u32 val; + + switch (intf) { + case PIX0: + addr = ISPIF_VFE_m_PIX_INTF_n_CID_MASK(vfe, 0); + break; + case RDI0: + addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 0); + break; + case PIX1: + addr = ISPIF_VFE_m_PIX_INTF_n_CID_MASK(vfe, 1); + break; + case RDI1: + addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 1); + break; + case RDI2: + addr = ISPIF_VFE_m_RDI_INTF_n_CID_MASK(vfe, 2); + break; + } + + val = readl_relaxed(ispif->base + addr); + if (enable) + val |= cid_mask; + else + val &= ~cid_mask; + + writel(val, ispif->base + addr); +} + +/* + * ispif_config_irq - Enable/disable interrupts for PIX/RDI interface + * @ispif: ISPIF device + * @intf: VFE interface + * @vfe: VFE HW module id + * @enable: enable or disable + */ +static void ispif_config_irq(struct ispif_device *ispif, enum ispif_intf intf, + u8 vfe, u8 enable) +{ + u32 val; + + switch (intf) { + case PIX0: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_0_PIX0_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_0_PIX0_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(vfe)); + break; + case RDI0: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_0_RDI0_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_0(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_0_RDI0_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_0(vfe)); + break; + case PIX1: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_1_PIX1_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_1_PIX1_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(vfe)); + break; + case RDI1: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_1_RDI1_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_1(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_1_RDI1_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_1(vfe)); + break; + case RDI2: + val = readl_relaxed(ispif->base + ISPIF_VFE_m_IRQ_MASK_2(vfe)); + val &= ~ISPIF_VFE_m_IRQ_MASK_2_RDI2_MASK; + if (enable) + val |= ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE; + writel_relaxed(val, ispif->base + ISPIF_VFE_m_IRQ_MASK_2(vfe)); + writel_relaxed(ISPIF_VFE_m_IRQ_MASK_2_RDI2_ENABLE, + ispif->base + ISPIF_VFE_m_IRQ_CLEAR_2(vfe)); + break; + } + + writel(0x1, ispif->base + ISPIF_IRQ_GLOBAL_CLEAR_CMD); +} + +/* + * ispif_config_pack - Config packing for PRDI mode + * @ispif: ISPIF device + * @code: media bus format code + * @intf: VFE interface + * @cid: desired CID to handle + * @vfe: VFE HW module id + * @enable: enable or disable + */ +static void ispif_config_pack(struct ispif_device *ispif, u32 code, + enum ispif_intf intf, u8 cid, u8 vfe, u8 enable) +{ + u32 addr, val; + + if (code != MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE && + code != MEDIA_BUS_FMT_Y10_2X8_PADHI_LE) + return; + + switch (intf) { + case RDI0: + if (cid < 8) + addr = ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_0(vfe, 0); + else + addr = ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_1(vfe, 0); + break; + case RDI1: + if (cid < 8) + addr = ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_0(vfe, 1); + else + addr = ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_1(vfe, 1); + break; + case RDI2: + if (cid < 8) + addr = ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_0(vfe, 2); + else + addr = ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_1(vfe, 2); + break; + default: + return; + } + + if (enable) + val = ISPIF_VFE_m_RDI_INTF_n_PACK_CFG_0_CID_c_PLAIN(cid); + else + val = 0; + + writel_relaxed(val, ispif->base + addr); +} + +/* + * ispif_set_intf_cmd - Set command to enable/disable interface + * @ispif: ISPIF device + * @cmd: interface command + * @intf: VFE interface + * @vfe: VFE HW module id + * @vc: virtual channel + */ +static void ispif_set_intf_cmd(struct ispif_device *ispif, u8 cmd, + enum ispif_intf intf, u8 vfe, u8 vc) +{ + u32 *val; + + if (intf == RDI2) { + val = &ispif->intf_cmd[vfe].cmd_1; + *val &= ~(0x3 << (vc * 2 + 8)); + *val |= (cmd << (vc * 2 + 8)); + wmb(); + writel_relaxed(*val, ispif->base + ISPIF_VFE_m_INTF_CMD_1(vfe)); + wmb(); + } else { + val = &ispif->intf_cmd[vfe].cmd_0; + *val &= ~(0x3 << (vc * 2 + intf * 8)); + *val |= (cmd << (vc * 2 + intf * 8)); + wmb(); + writel_relaxed(*val, ispif->base + ISPIF_VFE_m_INTF_CMD_0(vfe)); + wmb(); + } +} + +/* + * ispif_set_stream - Enable/disable streaming on ISPIF module + * @sd: ISPIF V4L2 subdevice + * @enable: Requested streaming state + * + * Main configuration of ISPIF module is also done here. + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct ispif_device *ispif = line->ispif; + struct camss *camss = ispif->camss; + enum ispif_intf intf = line->interface; + u8 csid = line->csid_id; + u8 vfe = line->vfe_id; + u8 vc = 0; /* Virtual Channel 0 */ + u8 cid = vc * 4; /* id of Virtual Channel and Data Type set */ + int ret; + + if (enable) { + if (!media_pad_remote_pad_first(&line->pads[MSM_ISPIF_PAD_SINK])) + return -ENOLINK; + + /* Config */ + + mutex_lock(&ispif->config_lock); + ispif_select_clk_mux(ispif, intf, csid, vfe, 1); + + ret = ispif_validate_intf_status(ispif, intf, vfe); + if (ret < 0) { + mutex_unlock(&ispif->config_lock); + return ret; + } + + ispif_select_csid(ispif, intf, csid, vfe, 1); + ispif_select_cid(ispif, intf, cid, vfe, 1); + ispif_config_irq(ispif, intf, vfe, 1); + if (camss->version == CAMSS_8x96 || + camss->version == CAMSS_660) + ispif_config_pack(ispif, + line->fmt[MSM_ISPIF_PAD_SINK].code, + intf, cid, vfe, 1); + ispif_set_intf_cmd(ispif, CMD_ENABLE_FRAME_BOUNDARY, + intf, vfe, vc); + } else { + mutex_lock(&ispif->config_lock); + ispif_set_intf_cmd(ispif, CMD_DISABLE_FRAME_BOUNDARY, + intf, vfe, vc); + mutex_unlock(&ispif->config_lock); + + ret = ispif_wait_for_stop(ispif, intf, vfe); + if (ret < 0) + return ret; + + mutex_lock(&ispif->config_lock); + if (camss->version == CAMSS_8x96 || + camss->version == CAMSS_660) + ispif_config_pack(ispif, + line->fmt[MSM_ISPIF_PAD_SINK].code, + intf, cid, vfe, 0); + ispif_config_irq(ispif, intf, vfe, 0); + ispif_select_cid(ispif, intf, cid, vfe, 0); + ispif_select_csid(ispif, intf, csid, vfe, 0); + ispif_select_clk_mux(ispif, intf, csid, vfe, 0); + } + + mutex_unlock(&ispif->config_lock); + + return 0; +} + +/* + * __ispif_get_format - Get pointer to format structure + * @ispif: ISPIF line + * @cfg: V4L2 subdev pad configuration + * @pad: pad from which format is requested + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE format structure + */ +static struct v4l2_mbus_framefmt * +__ispif_get_format(struct ispif_line *line, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&line->subdev, sd_state, + pad); + + return &line->fmt[pad]; +} + +/* + * ispif_try_format - Handle try format by pad subdev method + * @ispif: ISPIF line + * @cfg: V4L2 subdev pad configuration + * @pad: pad on which format is requested + * @fmt: pointer to v4l2 format structure + * @which: wanted subdev format + */ +static void ispif_try_format(struct ispif_line *line, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + + switch (pad) { + case MSM_ISPIF_PAD_SINK: + /* Set format on sink pad */ + + for (i = 0; i < line->nformats; i++) + if (fmt->code == line->formats[i]) + break; + + /* If not found, use UYVY as default */ + if (i >= line->nformats) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + break; + + case MSM_ISPIF_PAD_SRC: + /* Set and return a format same as sink pad */ + + *fmt = *__ispif_get_format(line, sd_state, MSM_ISPIF_PAD_SINK, + which); + + break; + } + + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +/* + * ispif_enum_mbus_code - Handle pixel format enumeration + * @sd: ISPIF V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * return -EINVAL or zero on success + */ +static int ispif_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + if (code->pad == MSM_ISPIF_PAD_SINK) { + if (code->index >= line->nformats) + return -EINVAL; + + code->code = line->formats[code->index]; + } else { + if (code->index > 0) + return -EINVAL; + + format = __ispif_get_format(line, sd_state, + MSM_ISPIF_PAD_SINK, + code->which); + + code->code = format->code; + } + + return 0; +} + +/* + * ispif_enum_frame_size - Handle frame size enumeration + * @sd: ISPIF V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: pointer to v4l2_subdev_frame_size_enum structure + * return -EINVAL or zero on success + */ +static int ispif_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + ispif_try_format(line, sd_state, fse->pad, &format, fse->which); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + ispif_try_format(line, sd_state, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * ispif_get_format - Handle get format by pads subdev method + * @sd: ISPIF V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int ispif_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ispif_get_format(line, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +/* + * ispif_set_format - Handle set format by pads subdev method + * @sd: ISPIF V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int ispif_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct ispif_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __ispif_get_format(line, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + ispif_try_format(line, sd_state, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + /* Propagate the format from sink to source */ + if (fmt->pad == MSM_ISPIF_PAD_SINK) { + format = __ispif_get_format(line, sd_state, MSM_ISPIF_PAD_SRC, + fmt->which); + + *format = fmt->format; + ispif_try_format(line, sd_state, MSM_ISPIF_PAD_SRC, format, + fmt->which); + } + + return 0; +} + +/* + * ispif_init_formats - Initialize formats on all pads + * @sd: ISPIF V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. + * + * Return 0 on success or a negative error code otherwise + */ +static int ispif_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format = { + .pad = MSM_ISPIF_PAD_SINK, + .which = fh ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080 + } + }; + + return ispif_set_format(sd, fh ? fh->state : NULL, &format); +} + +/* + * msm_ispif_subdev_init - Initialize ISPIF device structure and resources + * @ispif: ISPIF device + * @res: ISPIF module resources table + * + * Return 0 on success or a negative error code otherwise + */ +int msm_ispif_subdev_init(struct camss *camss, + const struct resources_ispif *res) +{ + struct device *dev = camss->dev; + struct ispif_device *ispif = camss->ispif; + struct platform_device *pdev = to_platform_device(dev); + int i; + int ret; + + if (!camss->ispif) + return 0; + + ispif->camss = camss; + + /* Number of ISPIF lines - same as number of CSID hardware modules */ + if (camss->version == CAMSS_8x16) + ispif->line_num = 2; + else if (camss->version == CAMSS_8x96 || + camss->version == CAMSS_660) + ispif->line_num = 4; + else + return -EINVAL; + + ispif->line = devm_kcalloc(dev, ispif->line_num, + sizeof(*ispif->line), GFP_KERNEL); + if (!ispif->line) + return -ENOMEM; + + for (i = 0; i < ispif->line_num; i++) { + ispif->line[i].ispif = ispif; + ispif->line[i].id = i; + + if (camss->version == CAMSS_8x16) { + ispif->line[i].formats = ispif_formats_8x16; + ispif->line[i].nformats = + ARRAY_SIZE(ispif_formats_8x16); + } else if (camss->version == CAMSS_8x96 || + camss->version == CAMSS_660) { + ispif->line[i].formats = ispif_formats_8x96; + ispif->line[i].nformats = + ARRAY_SIZE(ispif_formats_8x96); + } else { + return -EINVAL; + } + } + + /* Memory */ + + ispif->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]); + if (IS_ERR(ispif->base)) + return PTR_ERR(ispif->base); + + ispif->base_clk_mux = devm_platform_ioremap_resource_byname(pdev, res->reg[1]); + if (IS_ERR(ispif->base_clk_mux)) + return PTR_ERR(ispif->base_clk_mux); + + /* Interrupt */ + + ret = platform_get_irq_byname(pdev, res->interrupt); + if (ret < 0) + return ret; + + ispif->irq = ret; + snprintf(ispif->irq_name, sizeof(ispif->irq_name), "%s_%s", + dev_name(dev), MSM_ISPIF_NAME); + if (camss->version == CAMSS_8x16) + ret = devm_request_irq(dev, ispif->irq, ispif_isr_8x16, + IRQF_TRIGGER_RISING, ispif->irq_name, ispif); + else if (camss->version == CAMSS_8x96 || + camss->version == CAMSS_660) + ret = devm_request_irq(dev, ispif->irq, ispif_isr_8x96, + IRQF_TRIGGER_RISING, ispif->irq_name, ispif); + else + ret = -EINVAL; + + if (ret < 0) { + dev_err(dev, "request_irq failed: %d\n", ret); + return ret; + } + + /* Clocks */ + + ispif->nclocks = 0; + while (res->clock[ispif->nclocks]) + ispif->nclocks++; + + ispif->clock = devm_kcalloc(dev, + ispif->nclocks, sizeof(*ispif->clock), + GFP_KERNEL); + if (!ispif->clock) + return -ENOMEM; + + for (i = 0; i < ispif->nclocks; i++) { + struct camss_clock *clock = &ispif->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->freq = NULL; + clock->nfreqs = 0; + } + + ispif->nclocks_for_reset = 0; + while (res->clock_for_reset[ispif->nclocks_for_reset]) + ispif->nclocks_for_reset++; + + ispif->clock_for_reset = devm_kcalloc(dev, + ispif->nclocks_for_reset, + sizeof(*ispif->clock_for_reset), + GFP_KERNEL); + if (!ispif->clock_for_reset) + return -ENOMEM; + + for (i = 0; i < ispif->nclocks_for_reset; i++) { + struct camss_clock *clock = &ispif->clock_for_reset[i]; + + clock->clk = devm_clk_get(dev, res->clock_for_reset[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->freq = NULL; + clock->nfreqs = 0; + } + + mutex_init(&ispif->power_lock); + ispif->power_count = 0; + + mutex_init(&ispif->config_lock); + + for (i = 0; i < MSM_ISPIF_VFE_NUM; i++) + init_completion(&ispif->reset_complete[i]); + + return 0; +} + +/* + * ispif_get_intf - Get ISPIF interface to use by VFE line id + * @line_id: VFE line id that the ISPIF line is connected to + * + * Return ISPIF interface to use + */ +static enum ispif_intf ispif_get_intf(enum vfe_line_id line_id) +{ + switch (line_id) { + case (VFE_LINE_RDI0): + return RDI0; + case (VFE_LINE_RDI1): + return RDI1; + case (VFE_LINE_RDI2): + return RDI2; + case (VFE_LINE_PIX): + return PIX0; + default: + return RDI0; + } +} + +/* + * ispif_get_vfe_id - Get VFE HW module id + * @entity: Pointer to VFE media entity structure + * @id: Return CSID HW module id here + */ +static void ispif_get_vfe_id(struct media_entity *entity, u8 *id) +{ + struct v4l2_subdev *sd; + struct vfe_line *line; + struct vfe_device *vfe; + + sd = media_entity_to_v4l2_subdev(entity); + line = v4l2_get_subdevdata(sd); + vfe = to_vfe(line); + + *id = vfe->id; +} + +/* + * ispif_get_vfe_line_id - Get VFE line id by media entity + * @entity: Pointer to VFE media entity structure + * @id: Return VFE line id here + */ +static void ispif_get_vfe_line_id(struct media_entity *entity, + enum vfe_line_id *id) +{ + struct v4l2_subdev *sd; + struct vfe_line *line; + + sd = media_entity_to_v4l2_subdev(entity); + line = v4l2_get_subdevdata(sd); + + *id = line->id; +} + +/* + * ispif_link_setup - Setup ISPIF connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad + * @remote: Pointer to remote pad + * @flags: Link flags + * + * Return 0 on success + */ +static int ispif_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (flags & MEDIA_LNK_FL_ENABLED) { + if (media_pad_remote_pad_first(local)) + return -EBUSY; + + if (local->flags & MEDIA_PAD_FL_SINK) { + struct v4l2_subdev *sd; + struct ispif_line *line; + + sd = media_entity_to_v4l2_subdev(entity); + line = v4l2_get_subdevdata(sd); + + msm_csid_get_csid_id(remote->entity, &line->csid_id); + } else { /* MEDIA_PAD_FL_SOURCE */ + struct v4l2_subdev *sd; + struct ispif_line *line; + enum vfe_line_id id; + + sd = media_entity_to_v4l2_subdev(entity); + line = v4l2_get_subdevdata(sd); + + ispif_get_vfe_id(remote->entity, &line->vfe_id); + ispif_get_vfe_line_id(remote->entity, &id); + line->interface = ispif_get_intf(id); + } + } + + return 0; +} + +static const struct v4l2_subdev_core_ops ispif_core_ops = { + .s_power = ispif_set_power, +}; + +static const struct v4l2_subdev_video_ops ispif_video_ops = { + .s_stream = ispif_set_stream, +}; + +static const struct v4l2_subdev_pad_ops ispif_pad_ops = { + .enum_mbus_code = ispif_enum_mbus_code, + .enum_frame_size = ispif_enum_frame_size, + .get_fmt = ispif_get_format, + .set_fmt = ispif_set_format, +}; + +static const struct v4l2_subdev_ops ispif_v4l2_ops = { + .core = &ispif_core_ops, + .video = &ispif_video_ops, + .pad = &ispif_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops ispif_v4l2_internal_ops = { + .open = ispif_init_formats, +}; + +static const struct media_entity_operations ispif_media_ops = { + .link_setup = ispif_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * msm_ispif_register_entities - Register subdev node for ISPIF module + * @ispif: ISPIF device + * @v4l2_dev: V4L2 device + * + * Return 0 on success or a negative error code otherwise + */ +int msm_ispif_register_entities(struct ispif_device *ispif, + struct v4l2_device *v4l2_dev) +{ + struct camss *camss; + int ret; + int i; + + if (!ispif) + return 0; + + camss = ispif->camss; + + for (i = 0; i < ispif->line_num; i++) { + struct v4l2_subdev *sd = &ispif->line[i].subdev; + struct media_pad *pads = ispif->line[i].pads; + + v4l2_subdev_init(sd, &ispif_v4l2_ops); + sd->internal_ops = &ispif_v4l2_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d", + MSM_ISPIF_NAME, i); + v4l2_set_subdevdata(sd, &ispif->line[i]); + + ret = ispif_init_formats(sd, NULL); + if (ret < 0) { + dev_err(camss->dev, "Failed to init format: %d\n", ret); + goto error; + } + + pads[MSM_ISPIF_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[MSM_ISPIF_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &ispif_media_ops; + ret = media_entity_pads_init(&sd->entity, MSM_ISPIF_PADS_NUM, + pads); + if (ret < 0) { + dev_err(camss->dev, "Failed to init media entity: %d\n", + ret); + goto error; + } + + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(camss->dev, "Failed to register subdev: %d\n", + ret); + media_entity_cleanup(&sd->entity); + goto error; + } + } + + return 0; + +error: + for (i--; i >= 0; i--) { + struct v4l2_subdev *sd = &ispif->line[i].subdev; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } + + return ret; +} + +/* + * msm_ispif_unregister_entities - Unregister ISPIF module subdev node + * @ispif: ISPIF device + */ +void msm_ispif_unregister_entities(struct ispif_device *ispif) +{ + int i; + + if (!ispif) + return; + + mutex_destroy(&ispif->power_lock); + mutex_destroy(&ispif->config_lock); + + for (i = 0; i < ispif->line_num; i++) { + struct v4l2_subdev *sd = &ispif->line[i].subdev; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } +} diff --git a/drivers/media/platform/qcom/camss/camss-ispif.h b/drivers/media/platform/qcom/camss/camss-ispif.h new file mode 100644 index 000000000..fdf28e68c --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-ispif.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-ispif.h + * + * Qualcomm MSM Camera Subsystem - ISPIF (ISP Interface) Module + * + * Copyright (c) 2013-2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_ISPIF_H +#define QC_MSM_CAMSS_ISPIF_H + +#include +#include +#include +#include + +#define MSM_ISPIF_PAD_SINK 0 +#define MSM_ISPIF_PAD_SRC 1 +#define MSM_ISPIF_PADS_NUM 2 + +#define MSM_ISPIF_VFE_NUM 2 + +enum ispif_intf { + PIX0, + RDI0, + PIX1, + RDI1, + RDI2 +}; + +struct ispif_intf_cmd_reg { + u32 cmd_0; + u32 cmd_1; +}; + +struct ispif_line { + struct ispif_device *ispif; + u8 id; + u8 csid_id; + u8 vfe_id; + enum ispif_intf interface; + struct v4l2_subdev subdev; + struct media_pad pads[MSM_ISPIF_PADS_NUM]; + struct v4l2_mbus_framefmt fmt[MSM_ISPIF_PADS_NUM]; + const u32 *formats; + unsigned int nformats; +}; + +struct ispif_device { + void __iomem *base; + void __iomem *base_clk_mux; + u32 irq; + char irq_name[30]; + struct camss_clock *clock; + int nclocks; + struct camss_clock *clock_for_reset; + int nclocks_for_reset; + struct completion reset_complete[MSM_ISPIF_VFE_NUM]; + int power_count; + struct mutex power_lock; + struct ispif_intf_cmd_reg intf_cmd[MSM_ISPIF_VFE_NUM]; + struct mutex config_lock; + unsigned int line_num; + struct ispif_line *line; + struct camss *camss; +}; + +struct resources_ispif; + +int msm_ispif_subdev_init(struct camss *camss, + const struct resources_ispif *res); + +int msm_ispif_register_entities(struct ispif_device *ispif, + struct v4l2_device *v4l2_dev); + +void msm_ispif_unregister_entities(struct ispif_device *ispif); + +#endif /* QC_MSM_CAMSS_ISPIF_H */ diff --git a/drivers/media/platform/qcom/camss/camss-vfe-170.c b/drivers/media/platform/qcom/camss/camss-vfe-170.c new file mode 100644 index 000000000..f9492b1d1 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe-170.c @@ -0,0 +1,774 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-vfe-170.c + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module v170 + * + * Copyright (C) 2020-2021 Linaro Ltd. + */ + +#include +#include +#include + +#include "camss.h" +#include "camss-vfe.h" + +#define VFE_HW_VERSION (0x000) + +#define VFE_GLOBAL_RESET_CMD (0x018) +#define GLOBAL_RESET_CMD_CORE BIT(0) +#define GLOBAL_RESET_CMD_CAMIF BIT(1) +#define GLOBAL_RESET_CMD_BUS BIT(2) +#define GLOBAL_RESET_CMD_BUS_BDG BIT(3) +#define GLOBAL_RESET_CMD_REGISTER BIT(4) +#define GLOBAL_RESET_CMD_PM BIT(5) +#define GLOBAL_RESET_CMD_BUS_MISR BIT(6) +#define GLOBAL_RESET_CMD_TESTGEN BIT(7) +#define GLOBAL_RESET_CMD_DSP BIT(8) +#define GLOBAL_RESET_CMD_IDLE_CGC BIT(9) +#define GLOBAL_RESET_CMD_RDI0 BIT(10) +#define GLOBAL_RESET_CMD_RDI1 BIT(11) +#define GLOBAL_RESET_CMD_RDI2 BIT(12) +#define GLOBAL_RESET_CMD_RDI3 BIT(13) +#define GLOBAL_RESET_CMD_VFE_DOMAIN BIT(30) +#define GLOBAL_RESET_CMD_RESET_BYPASS BIT(31) + +#define VFE_CORE_CFG (0x050) +#define CFG_PIXEL_PATTERN_YCBYCR (0x4) +#define CFG_PIXEL_PATTERN_YCRYCB (0x5) +#define CFG_PIXEL_PATTERN_CBYCRY (0x6) +#define CFG_PIXEL_PATTERN_CRYCBY (0x7) +#define CFG_COMPOSITE_REG_UPDATE_EN BIT(4) + +#define VFE_IRQ_CMD (0x058) +#define CMD_GLOBAL_CLEAR BIT(0) + +#define VFE_IRQ_MASK_0 (0x05c) +#define MASK_0_CAMIF_SOF BIT(0) +#define MASK_0_CAMIF_EOF BIT(1) +#define MASK_0_RDI_REG_UPDATE(n) BIT((n) + 5) +#define MASK_0_IMAGE_MASTER_n_PING_PONG(n) BIT((n) + 8) +#define MASK_0_IMAGE_COMPOSITE_DONE_n(n) BIT((n) + 25) +#define MASK_0_RESET_ACK BIT(31) + +#define VFE_IRQ_MASK_1 (0x060) +#define MASK_1_CAMIF_ERROR BIT(0) +#define MASK_1_VIOLATION BIT(7) +#define MASK_1_BUS_BDG_HALT_ACK BIT(8) +#define MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(n) BIT((n) + 9) +#define MASK_1_RDI_SOF(n) BIT((n) + 29) + +#define VFE_IRQ_CLEAR_0 (0x064) +#define VFE_IRQ_CLEAR_1 (0x068) + +#define VFE_IRQ_STATUS_0 (0x06c) +#define STATUS_0_CAMIF_SOF BIT(0) +#define STATUS_0_RDI_REG_UPDATE(n) BIT((n) + 5) +#define STATUS_0_IMAGE_MASTER_PING_PONG(n) BIT((n) + 8) +#define STATUS_0_IMAGE_COMPOSITE_DONE(n) BIT((n) + 25) +#define STATUS_0_RESET_ACK BIT(31) + +#define VFE_IRQ_STATUS_1 (0x070) +#define STATUS_1_VIOLATION BIT(7) +#define STATUS_1_BUS_BDG_HALT_ACK BIT(8) +#define STATUS_1_RDI_SOF(n) BIT((n) + 27) + +#define VFE_VIOLATION_STATUS (0x07c) + +#define VFE_CAMIF_CMD (0x478) +#define CMD_CLEAR_CAMIF_STATUS BIT(2) + +#define VFE_CAMIF_CFG (0x47c) +#define CFG_VSYNC_SYNC_EDGE (0) +#define VSYNC_ACTIVE_HIGH (0) +#define VSYNC_ACTIVE_LOW (1) +#define CFG_HSYNC_SYNC_EDGE (1) +#define HSYNC_ACTIVE_HIGH (0) +#define HSYNC_ACTIVE_LOW (1) +#define CFG_VFE_SUBSAMPLE_ENABLE BIT(4) +#define CFG_BUS_SUBSAMPLE_ENABLE BIT(5) +#define CFG_VFE_OUTPUT_EN BIT(6) +#define CFG_BUS_OUTPUT_EN BIT(7) +#define CFG_BINNING_EN BIT(9) +#define CFG_FRAME_BASED_EN BIT(10) +#define CFG_RAW_CROP_EN BIT(22) + +#define VFE_REG_UPDATE_CMD (0x4ac) +#define REG_UPDATE_RDI(n) BIT(1 + (n)) + +#define VFE_BUS_IRQ_MASK(n) (0x2044 + (n) * 4) +#define VFE_BUS_IRQ_CLEAR(n) (0x2050 + (n) * 4) +#define VFE_BUS_IRQ_STATUS(n) (0x205c + (n) * 4) +#define STATUS0_COMP_RESET_DONE BIT(0) +#define STATUS0_COMP_REG_UPDATE0_DONE BIT(1) +#define STATUS0_COMP_REG_UPDATE1_DONE BIT(2) +#define STATUS0_COMP_REG_UPDATE2_DONE BIT(3) +#define STATUS0_COMP_REG_UPDATE3_DONE BIT(4) +#define STATUS0_COMP_REG_UPDATE_DONE(n) BIT((n) + 1) +#define STATUS0_COMP0_BUF_DONE BIT(5) +#define STATUS0_COMP1_BUF_DONE BIT(6) +#define STATUS0_COMP2_BUF_DONE BIT(7) +#define STATUS0_COMP3_BUF_DONE BIT(8) +#define STATUS0_COMP4_BUF_DONE BIT(9) +#define STATUS0_COMP5_BUF_DONE BIT(10) +#define STATUS0_COMP_BUF_DONE(n) BIT((n) + 5) +#define STATUS0_COMP_ERROR BIT(11) +#define STATUS0_COMP_OVERWRITE BIT(12) +#define STATUS0_OVERFLOW BIT(13) +#define STATUS0_VIOLATION BIT(14) +/* WM_CLIENT_BUF_DONE defined for buffers 0:19 */ +#define STATUS1_WM_CLIENT_BUF_DONE(n) BIT(n) +#define STATUS1_EARLY_DONE BIT(24) +#define STATUS2_DUAL_COMP0_BUF_DONE BIT(0) +#define STATUS2_DUAL_COMP1_BUF_DONE BIT(1) +#define STATUS2_DUAL_COMP2_BUF_DONE BIT(2) +#define STATUS2_DUAL_COMP3_BUF_DONE BIT(3) +#define STATUS2_DUAL_COMP4_BUF_DONE BIT(4) +#define STATUS2_DUAL_COMP5_BUF_DONE BIT(5) +#define STATUS2_DUAL_COMP_BUF_DONE(n) BIT(n) +#define STATUS2_DUAL_COMP_ERROR BIT(6) +#define STATUS2_DUAL_COMP_OVERWRITE BIT(7) + +#define VFE_BUS_IRQ_CLEAR_GLOBAL (0x2068) + +#define VFE_BUS_WM_DEBUG_STATUS_CFG (0x226c) +#define DEBUG_STATUS_CFG_STATUS0(n) BIT(n) +#define DEBUG_STATUS_CFG_STATUS1(n) BIT(8 + (n)) + +#define VFE_BUS_WM_ADDR_SYNC_FRAME_HEADER (0x2080) + +#define VFE_BUS_WM_ADDR_SYNC_NO_SYNC (0x2084) +#define BUS_VER2_MAX_CLIENTS (24) +#define WM_ADDR_NO_SYNC_DEFAULT_VAL \ + ((1 << BUS_VER2_MAX_CLIENTS) - 1) + +#define VFE_BUS_WM_CGC_OVERRIDE (0x200c) +#define WM_CGC_OVERRIDE_ALL (0xFFFFF) + +#define VFE_BUS_WM_TEST_BUS_CTRL (0x211c) + +#define VFE_BUS_WM_STATUS0(n) (0x2200 + (n) * 0x100) +#define VFE_BUS_WM_STATUS1(n) (0x2204 + (n) * 0x100) +#define VFE_BUS_WM_CFG(n) (0x2208 + (n) * 0x100) +#define WM_CFG_EN (0) +#define WM_CFG_MODE (1) +#define MODE_QCOM_PLAIN (0) +#define MODE_MIPI_RAW (1) +#define WM_CFG_VIRTUALFRAME (2) +#define VFE_BUS_WM_HEADER_ADDR(n) (0x220c + (n) * 0x100) +#define VFE_BUS_WM_HEADER_CFG(n) (0x2210 + (n) * 0x100) +#define VFE_BUS_WM_IMAGE_ADDR(n) (0x2214 + (n) * 0x100) +#define VFE_BUS_WM_IMAGE_ADDR_OFFSET(n) (0x2218 + (n) * 0x100) +#define VFE_BUS_WM_BUFFER_WIDTH_CFG(n) (0x221c + (n) * 0x100) +#define WM_BUFFER_DEFAULT_WIDTH (0xFF01) + +#define VFE_BUS_WM_BUFFER_HEIGHT_CFG(n) (0x2220 + (n) * 0x100) +#define VFE_BUS_WM_PACKER_CFG(n) (0x2224 + (n) * 0x100) + +#define VFE_BUS_WM_STRIDE(n) (0x2228 + (n) * 0x100) +#define WM_STRIDE_DEFAULT_STRIDE (0xFF01) + +#define VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(n) (0x2248 + (n) * 0x100) +#define VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(n) (0x224c + (n) * 0x100) +#define VFE_BUS_WM_FRAMEDROP_PERIOD(n) (0x2250 + (n) * 0x100) +#define VFE_BUS_WM_FRAMEDROP_PATTERN(n) (0x2254 + (n) * 0x100) +#define VFE_BUS_WM_FRAME_INC(n) (0x2258 + (n) * 0x100) +#define VFE_BUS_WM_BURST_LIMIT(n) (0x225c + (n) * 0x100) + +static u32 vfe_hw_version(struct vfe_device *vfe) +{ + u32 hw_version = readl_relaxed(vfe->base + VFE_HW_VERSION); + + u32 gen = (hw_version >> 28) & 0xF; + u32 rev = (hw_version >> 16) & 0xFFF; + u32 step = hw_version & 0xFFFF; + + dev_dbg(vfe->camss->dev, "VFE HW Version = %u.%u.%u\n", + gen, rev, step); + + return hw_version; +} + +static inline void vfe_reg_set(struct vfe_device *vfe, u32 reg, u32 set_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits | set_bits, vfe->base + reg); +} + +static void vfe_global_reset(struct vfe_device *vfe) +{ + u32 reset_bits = GLOBAL_RESET_CMD_CORE | + GLOBAL_RESET_CMD_CAMIF | + GLOBAL_RESET_CMD_BUS | + GLOBAL_RESET_CMD_BUS_BDG | + GLOBAL_RESET_CMD_REGISTER | + GLOBAL_RESET_CMD_TESTGEN | + GLOBAL_RESET_CMD_DSP | + GLOBAL_RESET_CMD_IDLE_CGC | + GLOBAL_RESET_CMD_RDI0 | + GLOBAL_RESET_CMD_RDI1 | + GLOBAL_RESET_CMD_RDI2; + + writel_relaxed(BIT(31), vfe->base + VFE_IRQ_MASK_0); + + /* Make sure IRQ mask has been written before resetting */ + wmb(); + + writel_relaxed(reset_bits, vfe->base + VFE_GLOBAL_RESET_CMD); +} + +static void vfe_wm_start(struct vfe_device *vfe, u8 wm, struct vfe_line *line) +{ + u32 val; + + /*Set Debug Registers*/ + val = DEBUG_STATUS_CFG_STATUS0(1) | + DEBUG_STATUS_CFG_STATUS0(7); + writel_relaxed(val, vfe->base + VFE_BUS_WM_DEBUG_STATUS_CFG); + + /* BUS_WM_INPUT_IF_ADDR_SYNC_FRAME_HEADER */ + writel_relaxed(0, vfe->base + VFE_BUS_WM_ADDR_SYNC_FRAME_HEADER); + + /* no clock gating at bus input */ + val = WM_CGC_OVERRIDE_ALL; + writel_relaxed(val, vfe->base + VFE_BUS_WM_CGC_OVERRIDE); + + writel_relaxed(0x0, vfe->base + VFE_BUS_WM_TEST_BUS_CTRL); + + /* if addr_no_sync has default value then config the addr no sync reg */ + val = WM_ADDR_NO_SYNC_DEFAULT_VAL; + writel_relaxed(val, vfe->base + VFE_BUS_WM_ADDR_SYNC_NO_SYNC); + + writel_relaxed(0xf, vfe->base + VFE_BUS_WM_BURST_LIMIT(wm)); + + val = WM_BUFFER_DEFAULT_WIDTH; + writel_relaxed(val, vfe->base + VFE_BUS_WM_BUFFER_WIDTH_CFG(wm)); + + val = 0; + writel_relaxed(val, vfe->base + VFE_BUS_WM_BUFFER_HEIGHT_CFG(wm)); + + val = 0; + writel_relaxed(val, vfe->base + VFE_BUS_WM_PACKER_CFG(wm)); // XXX 1 for PLAIN8? + + /* Configure stride for RDIs */ + val = WM_STRIDE_DEFAULT_STRIDE; + writel_relaxed(val, vfe->base + VFE_BUS_WM_STRIDE(wm)); + + /* Enable WM */ + val = 1 << WM_CFG_EN | + MODE_MIPI_RAW << WM_CFG_MODE; + writel_relaxed(val, vfe->base + VFE_BUS_WM_CFG(wm)); +} + +static void vfe_wm_stop(struct vfe_device *vfe, u8 wm) +{ + /* Disable WM */ + writel_relaxed(0, vfe->base + VFE_BUS_WM_CFG(wm)); +} + +static void vfe_wm_update(struct vfe_device *vfe, u8 wm, u32 addr, + struct vfe_line *line) +{ + struct v4l2_pix_format_mplane *pix = + &line->video_out.active_fmt.fmt.pix_mp; + u32 stride = pix->plane_fmt[0].bytesperline; + + writel_relaxed(addr, vfe->base + VFE_BUS_WM_IMAGE_ADDR(wm)); + writel_relaxed(stride * pix->height, vfe->base + VFE_BUS_WM_FRAME_INC(wm)); +} + +static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + vfe->reg_update |= REG_UPDATE_RDI(line_id); + + /* Enforce ordering between previous reg writes and reg update */ + wmb(); + + writel_relaxed(vfe->reg_update, vfe->base + VFE_REG_UPDATE_CMD); + + /* Enforce ordering between reg update and subsequent reg writes */ + wmb(); +} + +static inline void vfe_reg_update_clear(struct vfe_device *vfe, + enum vfe_line_id line_id) +{ + vfe->reg_update &= ~REG_UPDATE_RDI(line_id); +} + +static void vfe_enable_irq_common(struct vfe_device *vfe) +{ + vfe_reg_set(vfe, VFE_IRQ_MASK_0, ~0u); + vfe_reg_set(vfe, VFE_IRQ_MASK_1, ~0u); + + writel_relaxed(~0u, vfe->base + VFE_BUS_IRQ_MASK(0)); + writel_relaxed(~0u, vfe->base + VFE_BUS_IRQ_MASK(1)); + writel_relaxed(~0u, vfe->base + VFE_BUS_IRQ_MASK(2)); +} + +static void vfe_isr_halt_ack(struct vfe_device *vfe) +{ + complete(&vfe->halt_complete); +} + +static void vfe_isr_read(struct vfe_device *vfe, u32 *status0, u32 *status1) +{ + *status0 = readl_relaxed(vfe->base + VFE_IRQ_STATUS_0); + *status1 = readl_relaxed(vfe->base + VFE_IRQ_STATUS_1); + + writel_relaxed(*status0, vfe->base + VFE_IRQ_CLEAR_0); + writel_relaxed(*status1, vfe->base + VFE_IRQ_CLEAR_1); + + /* Enforce ordering between IRQ Clear and Global IRQ Clear */ + wmb(); + writel_relaxed(CMD_GLOBAL_CLEAR, vfe->base + VFE_IRQ_CMD); +} + +static void vfe_violation_read(struct vfe_device *vfe) +{ + u32 violation = readl_relaxed(vfe->base + VFE_VIOLATION_STATUS); + + pr_err_ratelimited("VFE: violation = 0x%08x\n", violation); +} + +/* + * vfe_isr - VFE module interrupt handler + * @irq: Interrupt line + * @dev: VFE device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t vfe_isr(int irq, void *dev) +{ + struct vfe_device *vfe = dev; + u32 status0, status1, vfe_bus_status[3]; + int i, wm; + + status0 = readl_relaxed(vfe->base + VFE_IRQ_STATUS_0); + status1 = readl_relaxed(vfe->base + VFE_IRQ_STATUS_1); + + writel_relaxed(status0, vfe->base + VFE_IRQ_CLEAR_0); + writel_relaxed(status1, vfe->base + VFE_IRQ_CLEAR_1); + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_RDI2; i++) { + vfe_bus_status[i] = readl_relaxed(vfe->base + VFE_BUS_IRQ_STATUS(i)); + writel_relaxed(vfe_bus_status[i], vfe->base + VFE_BUS_IRQ_CLEAR(i)); + } + + /* Enforce ordering between IRQ reading and interpretation */ + wmb(); + + writel_relaxed(CMD_GLOBAL_CLEAR, vfe->base + VFE_IRQ_CMD); + writel_relaxed(1, vfe->base + VFE_BUS_IRQ_CLEAR_GLOBAL); + + if (status0 & STATUS_0_RESET_ACK) + vfe->isr_ops.reset_ack(vfe); + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_RDI2; i++) + if (status0 & STATUS_0_RDI_REG_UPDATE(i)) + vfe->isr_ops.reg_update(vfe, i); + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_RDI2; i++) + if (status0 & STATUS_1_RDI_SOF(i)) + vfe->isr_ops.sof(vfe, i); + + for (i = 0; i < MSM_VFE_COMPOSITE_IRQ_NUM; i++) + if (vfe_bus_status[0] & STATUS0_COMP_BUF_DONE(i)) + vfe->isr_ops.comp_done(vfe, i); + + for (wm = 0; wm < MSM_VFE_IMAGE_MASTERS_NUM; wm++) + if (status0 & BIT(9)) + if (vfe_bus_status[1] & STATUS1_WM_CLIENT_BUF_DONE(wm)) + vfe->isr_ops.wm_done(vfe, wm); + + return IRQ_HANDLED; +} + +/* + * vfe_halt - Trigger halt on VFE module and wait to complete + * @vfe: VFE device + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_halt(struct vfe_device *vfe) +{ + /* rely on vfe_disable_output() to stop the VFE */ + return 0; +} + +static int vfe_get_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output; + unsigned long flags; + int wm_idx; + + spin_lock_irqsave(&vfe->output_lock, flags); + + output = &line->output; + if (output->state != VFE_OUTPUT_OFF) { + dev_err(vfe->camss->dev, "Output is running\n"); + goto error; + } + + output->wm_num = 1; + + wm_idx = vfe_reserve_wm(vfe, line->id); + if (wm_idx < 0) { + dev_err(vfe->camss->dev, "Can not reserve wm\n"); + goto error_get_wm; + } + output->wm_idx[0] = wm_idx; + + output->drop_update_idx = 0; + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; + +error_get_wm: + vfe_release_wm(vfe, output->wm_idx[0]); + output->state = VFE_OUTPUT_OFF; +error: + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return -EINVAL; +} + +static int vfe_enable_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + const struct vfe_hw_ops *ops = vfe->ops; + struct media_entity *sensor; + unsigned long flags; + unsigned int frame_skip = 0; + unsigned int i; + + sensor = camss_find_sensor(&line->subdev.entity); + if (sensor) { + struct v4l2_subdev *subdev = media_entity_to_v4l2_subdev(sensor); + + v4l2_subdev_call(subdev, sensor, g_skip_frames, &frame_skip); + /* Max frame skip is 29 frames */ + if (frame_skip > VFE_FRAME_DROP_VAL - 1) + frame_skip = VFE_FRAME_DROP_VAL - 1; + } + + spin_lock_irqsave(&vfe->output_lock, flags); + + ops->reg_update_clear(vfe, line->id); + + if (output->state != VFE_OUTPUT_OFF) { + dev_err(vfe->camss->dev, "Output is not in reserved state %d\n", + output->state); + spin_unlock_irqrestore(&vfe->output_lock, flags); + return -EINVAL; + } + + WARN_ON(output->gen2.active_num); + + output->state = VFE_OUTPUT_ON; + + output->sequence = 0; + output->wait_reg_update = 0; + reinit_completion(&output->reg_update); + + vfe_wm_start(vfe, output->wm_idx[0], line); + + for (i = 0; i < 2; i++) { + output->buf[i] = vfe_buf_get_pending(output); + if (!output->buf[i]) + break; + output->gen2.active_num++; + vfe_wm_update(vfe, output->wm_idx[0], output->buf[i]->addr[0], line); + } + + ops->reg_update(vfe, line->id); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +static void vfe_disable_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&vfe->output_lock, flags); + for (i = 0; i < output->wm_num; i++) + vfe_wm_stop(vfe, output->wm_idx[i]); + output->gen2.active_num = 0; + spin_unlock_irqrestore(&vfe->output_lock, flags); + + vfe_reset(vfe); +} + +/* + * vfe_enable - Enable streaming on VFE line + * @line: VFE line + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_enable(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + int ret; + + mutex_lock(&vfe->stream_lock); + + if (!vfe->stream_count) + vfe_enable_irq_common(vfe); + + vfe->stream_count++; + + mutex_unlock(&vfe->stream_lock); + + ret = vfe_get_output(line); + if (ret < 0) + goto error_get_output; + + ret = vfe_enable_output(line); + if (ret < 0) + goto error_enable_output; + + vfe->was_streaming = 1; + + return 0; + +error_enable_output: + vfe_put_output(line); + +error_get_output: + mutex_lock(&vfe->stream_lock); + + vfe->stream_count--; + + mutex_unlock(&vfe->stream_lock); + + return ret; +} + +/* + * vfe_disable - Disable streaming on VFE line + * @line: VFE line + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_disable(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + + vfe_disable_output(line); + + vfe_put_output(line); + + mutex_lock(&vfe->stream_lock); + + vfe->stream_count--; + + mutex_unlock(&vfe->stream_lock); + + return 0; +} + +/* + * vfe_isr_sof - Process start of frame interrupt + * @vfe: VFE Device + * @line_id: VFE line + */ +static void vfe_isr_sof(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + /* nop */ +} + +/* + * vfe_isr_reg_update - Process reg update interrupt + * @vfe: VFE Device + * @line_id: VFE line + */ +static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + struct vfe_output *output; + unsigned long flags; + + spin_lock_irqsave(&vfe->output_lock, flags); + vfe->ops->reg_update_clear(vfe, line_id); + + output = &vfe->line[line_id].output; + + if (output->wait_reg_update) { + output->wait_reg_update = 0; + complete(&output->reg_update); + } + + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_isr_wm_done - Process write master done interrupt + * @vfe: VFE Device + * @wm: Write master id + */ +static void vfe_isr_wm_done(struct vfe_device *vfe, u8 wm) +{ + struct vfe_line *line = &vfe->line[vfe->wm_output_map[wm]]; + struct camss_buffer *ready_buf; + struct vfe_output *output; + unsigned long flags; + u32 index; + u64 ts = ktime_get_ns(); + + spin_lock_irqsave(&vfe->output_lock, flags); + + if (vfe->wm_output_map[wm] == VFE_LINE_NONE) { + dev_err_ratelimited(vfe->camss->dev, + "Received wm done for unmapped index\n"); + goto out_unlock; + } + output = &vfe->line[vfe->wm_output_map[wm]].output; + + ready_buf = output->buf[0]; + if (!ready_buf) { + dev_err_ratelimited(vfe->camss->dev, + "Missing ready buf %d!\n", output->state); + goto out_unlock; + } + + ready_buf->vb.vb2_buf.timestamp = ts; + ready_buf->vb.sequence = output->sequence++; + + index = 0; + output->buf[0] = output->buf[1]; + if (output->buf[0]) + index = 1; + + output->buf[index] = vfe_buf_get_pending(output); + + if (output->buf[index]) + vfe_wm_update(vfe, output->wm_idx[0], output->buf[index]->addr[0], line); + else + output->gen2.active_num--; + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + + return; + +out_unlock: + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_pm_domain_off - Disable power domains specific to this VFE. + * @vfe: VFE Device + */ +static void vfe_pm_domain_off(struct vfe_device *vfe) +{ + struct camss *camss = vfe->camss; + + if (vfe->id >= camss->vfe_num) + return; + + device_link_del(camss->genpd_link[vfe->id]); +} + +/* + * vfe_pm_domain_on - Enable power domains specific to this VFE. + * @vfe: VFE Device + */ +static int vfe_pm_domain_on(struct vfe_device *vfe) +{ + struct camss *camss = vfe->camss; + enum vfe_line_id id = vfe->id; + + if (id >= camss->vfe_num) + return 0; + + camss->genpd_link[id] = device_link_add(camss->dev, camss->genpd[id], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[id]) + return -EINVAL; + + return 0; +} + +/* + * vfe_queue_buffer - Add empty buffer + * @vid: Video device structure + * @buf: Buffer to be enqueued + * + * Add an empty buffer - depending on the current number of buffers it will be + * put in pending buffer queue or directly given to the hardware to be filled. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_queue_buffer(struct camss_video *vid, + struct camss_buffer *buf) +{ + struct vfe_line *line = container_of(vid, struct vfe_line, video_out); + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output; + unsigned long flags; + + output = &line->output; + + spin_lock_irqsave(&vfe->output_lock, flags); + + if (output->state == VFE_OUTPUT_ON && output->gen2.active_num < 2) { + output->buf[output->gen2.active_num++] = buf; + vfe_wm_update(vfe, output->wm_idx[0], buf->addr[0], line); + } else { + vfe_buf_add_pending(output, buf); + } + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +static const struct vfe_isr_ops vfe_isr_ops_170 = { + .reset_ack = vfe_isr_reset_ack, + .halt_ack = vfe_isr_halt_ack, + .reg_update = vfe_isr_reg_update, + .sof = vfe_isr_sof, + .comp_done = vfe_isr_comp_done, + .wm_done = vfe_isr_wm_done, +}; + +static const struct camss_video_ops vfe_video_ops_170 = { + .queue_buffer = vfe_queue_buffer, + .flush_buffers = vfe_flush_buffers, +}; + +static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe) +{ + vfe->isr_ops = vfe_isr_ops_170; + vfe->video_ops = vfe_video_ops_170; + + vfe->line_num = VFE_LINE_NUM_GEN2; +} + +const struct vfe_hw_ops vfe_ops_170 = { + .global_reset = vfe_global_reset, + .hw_version = vfe_hw_version, + .isr_read = vfe_isr_read, + .isr = vfe_isr, + .pm_domain_off = vfe_pm_domain_off, + .pm_domain_on = vfe_pm_domain_on, + .reg_update_clear = vfe_reg_update_clear, + .reg_update = vfe_reg_update, + .subdev_init = vfe_subdev_init, + .vfe_disable = vfe_disable, + .vfe_enable = vfe_enable, + .vfe_halt = vfe_halt, + .violation_read = vfe_violation_read, +}; diff --git a/drivers/media/platform/qcom/camss/camss-vfe-4-1.c b/drivers/media/platform/qcom/camss/camss-vfe-4-1.c new file mode 100644 index 000000000..42047b11b --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe-4-1.c @@ -0,0 +1,1013 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-vfe-4-1.c + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module v4.1 + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ + +#include +#include +#include + +#include "camss.h" +#include "camss-vfe.h" +#include "camss-vfe-gen1.h" + +#define VFE_0_HW_VERSION 0x000 + +#define VFE_0_GLOBAL_RESET_CMD 0x00c +#define VFE_0_GLOBAL_RESET_CMD_CORE BIT(0) +#define VFE_0_GLOBAL_RESET_CMD_CAMIF BIT(1) +#define VFE_0_GLOBAL_RESET_CMD_BUS BIT(2) +#define VFE_0_GLOBAL_RESET_CMD_BUS_BDG BIT(3) +#define VFE_0_GLOBAL_RESET_CMD_REGISTER BIT(4) +#define VFE_0_GLOBAL_RESET_CMD_TIMER BIT(5) +#define VFE_0_GLOBAL_RESET_CMD_PM BIT(6) +#define VFE_0_GLOBAL_RESET_CMD_BUS_MISR BIT(7) +#define VFE_0_GLOBAL_RESET_CMD_TESTGEN BIT(8) + +#define VFE_0_MODULE_CFG 0x018 +#define VFE_0_MODULE_CFG_DEMUX BIT(2) +#define VFE_0_MODULE_CFG_CHROMA_UPSAMPLE BIT(3) +#define VFE_0_MODULE_CFG_SCALE_ENC BIT(23) +#define VFE_0_MODULE_CFG_CROP_ENC BIT(27) + +#define VFE_0_CORE_CFG 0x01c +#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR 0x4 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB 0x5 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY 0x6 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY 0x7 + +#define VFE_0_IRQ_CMD 0x024 +#define VFE_0_IRQ_CMD_GLOBAL_CLEAR BIT(0) + +#define VFE_0_IRQ_MASK_0 0x028 +#define VFE_0_IRQ_MASK_0_CAMIF_SOF BIT(0) +#define VFE_0_IRQ_MASK_0_CAMIF_EOF BIT(1) +#define VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n) BIT((n) + 5) +#define VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(n) \ + ((n) == VFE_LINE_PIX ? BIT(4) : VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n)) +#define VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(n) BIT((n) + 8) +#define VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(n) BIT((n) + 25) +#define VFE_0_IRQ_MASK_0_RESET_ACK BIT(31) +#define VFE_0_IRQ_MASK_1 0x02c +#define VFE_0_IRQ_MASK_1_CAMIF_ERROR BIT(0) +#define VFE_0_IRQ_MASK_1_VIOLATION BIT(7) +#define VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK BIT(8) +#define VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(n) BIT((n) + 9) +#define VFE_0_IRQ_MASK_1_RDIn_SOF(n) BIT((n) + 29) + +#define VFE_0_IRQ_CLEAR_0 0x030 +#define VFE_0_IRQ_CLEAR_1 0x034 + +#define VFE_0_IRQ_STATUS_0 0x038 +#define VFE_0_IRQ_STATUS_0_CAMIF_SOF BIT(0) +#define VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n) BIT((n) + 5) +#define VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(n) \ + ((n) == VFE_LINE_PIX ? BIT(4) : VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n)) +#define VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(n) BIT((n) + 8) +#define VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(n) BIT((n) + 25) +#define VFE_0_IRQ_STATUS_0_RESET_ACK BIT(31) +#define VFE_0_IRQ_STATUS_1 0x03c +#define VFE_0_IRQ_STATUS_1_VIOLATION BIT(7) +#define VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK BIT(8) +#define VFE_0_IRQ_STATUS_1_RDIn_SOF(n) BIT((n) + 29) + +#define VFE_0_IRQ_COMPOSITE_MASK_0 0x40 +#define VFE_0_VIOLATION_STATUS 0x48 + +#define VFE_0_BUS_CMD 0x4c +#define VFE_0_BUS_CMD_Mx_RLD_CMD(x) BIT(x) + +#define VFE_0_BUS_CFG 0x050 + +#define VFE_0_BUS_XBAR_CFG_x(x) (0x58 + 0x4 * ((x) / 2)) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN BIT(1) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA (0x3 << 4) +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT 8 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA 0 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 5 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 6 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 7 + +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(n) (0x06c + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT 0 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT 1 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(n) (0x070 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(n) (0x074 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(n) (0x078 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT 2 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK (0x1f << 2) + +#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(n) (0x07c + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT 16 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(n) (0x080 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(n) (0x084 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(n) \ + (0x088 + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(n) \ + (0x08c + 0x24 * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF 0xffffffff + +#define VFE_0_BUS_PING_PONG_STATUS 0x268 + +#define VFE_0_BUS_BDG_CMD 0x2c0 +#define VFE_0_BUS_BDG_CMD_HALT_REQ 1 + +#define VFE_0_BUS_BDG_QOS_CFG_0 0x2c4 +#define VFE_0_BUS_BDG_QOS_CFG_0_CFG 0xaaa5aaa5 +#define VFE_0_BUS_BDG_QOS_CFG_1 0x2c8 +#define VFE_0_BUS_BDG_QOS_CFG_2 0x2cc +#define VFE_0_BUS_BDG_QOS_CFG_3 0x2d0 +#define VFE_0_BUS_BDG_QOS_CFG_4 0x2d4 +#define VFE_0_BUS_BDG_QOS_CFG_5 0x2d8 +#define VFE_0_BUS_BDG_QOS_CFG_6 0x2dc +#define VFE_0_BUS_BDG_QOS_CFG_7 0x2e0 +#define VFE_0_BUS_BDG_QOS_CFG_7_CFG 0x0001aaa5 + +#define VFE_0_RDI_CFG_x(x) (0x2e8 + (0x4 * (x))) +#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT 28 +#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK (0xf << 28) +#define VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT 4 +#define VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK (0xf << 4) +#define VFE_0_RDI_CFG_x_RDI_EN_BIT BIT(2) +#define VFE_0_RDI_CFG_x_MIPI_EN_BITS 0x3 +#define VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(r) BIT(16 + (r)) + +#define VFE_0_CAMIF_CMD 0x2f4 +#define VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY 0 +#define VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY 1 +#define VFE_0_CAMIF_CMD_NO_CHANGE 3 +#define VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS BIT(2) +#define VFE_0_CAMIF_CFG 0x2f8 +#define VFE_0_CAMIF_CFG_VFE_OUTPUT_EN BIT(6) +#define VFE_0_CAMIF_FRAME_CFG 0x300 +#define VFE_0_CAMIF_WINDOW_WIDTH_CFG 0x304 +#define VFE_0_CAMIF_WINDOW_HEIGHT_CFG 0x308 +#define VFE_0_CAMIF_SUBSAMPLE_CFG_0 0x30c +#define VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN 0x314 +#define VFE_0_CAMIF_STATUS 0x31c +#define VFE_0_CAMIF_STATUS_HALT BIT(31) + +#define VFE_0_REG_UPDATE 0x378 +#define VFE_0_REG_UPDATE_RDIn(n) BIT(1 + (n)) +#define VFE_0_REG_UPDATE_line_n(n) \ + ((n) == VFE_LINE_PIX ? 1 : VFE_0_REG_UPDATE_RDIn(n)) + +#define VFE_0_DEMUX_CFG 0x424 +#define VFE_0_DEMUX_CFG_PERIOD 0x3 +#define VFE_0_DEMUX_GAIN_0 0x428 +#define VFE_0_DEMUX_GAIN_0_CH0_EVEN (0x80 << 0) +#define VFE_0_DEMUX_GAIN_0_CH0_ODD (0x80 << 16) +#define VFE_0_DEMUX_GAIN_1 0x42c +#define VFE_0_DEMUX_GAIN_1_CH1 (0x80 << 0) +#define VFE_0_DEMUX_GAIN_1_CH2 (0x80 << 16) +#define VFE_0_DEMUX_EVEN_CFG 0x438 +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV 0x9cac +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU 0xac9c +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY 0xc9ca +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY 0xcac9 +#define VFE_0_DEMUX_ODD_CFG 0x43c +#define VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV 0x9cac +#define VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU 0xac9c +#define VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY 0xc9ca +#define VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY 0xcac9 + +#define VFE_0_SCALE_ENC_Y_CFG 0x75c +#define VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE 0x760 +#define VFE_0_SCALE_ENC_Y_H_PHASE 0x764 +#define VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE 0x76c +#define VFE_0_SCALE_ENC_Y_V_PHASE 0x770 +#define VFE_0_SCALE_ENC_CBCR_CFG 0x778 +#define VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE 0x77c +#define VFE_0_SCALE_ENC_CBCR_H_PHASE 0x780 +#define VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE 0x790 +#define VFE_0_SCALE_ENC_CBCR_V_PHASE 0x794 + +#define VFE_0_CROP_ENC_Y_WIDTH 0x854 +#define VFE_0_CROP_ENC_Y_HEIGHT 0x858 +#define VFE_0_CROP_ENC_CBCR_WIDTH 0x85c +#define VFE_0_CROP_ENC_CBCR_HEIGHT 0x860 + +#define VFE_0_CLAMP_ENC_MAX_CFG 0x874 +#define VFE_0_CLAMP_ENC_MAX_CFG_CH0 (0xff << 0) +#define VFE_0_CLAMP_ENC_MAX_CFG_CH1 (0xff << 8) +#define VFE_0_CLAMP_ENC_MAX_CFG_CH2 (0xff << 16) +#define VFE_0_CLAMP_ENC_MIN_CFG 0x878 +#define VFE_0_CLAMP_ENC_MIN_CFG_CH0 (0x0 << 0) +#define VFE_0_CLAMP_ENC_MIN_CFG_CH1 (0x0 << 8) +#define VFE_0_CLAMP_ENC_MIN_CFG_CH2 (0x0 << 16) + +#define VFE_0_CGC_OVERRIDE_1 0x974 +#define VFE_0_CGC_OVERRIDE_1_IMAGE_Mx_CGC_OVERRIDE(x) BIT(x) + +#define CAMIF_TIMEOUT_SLEEP_US 1000 +#define CAMIF_TIMEOUT_ALL_US 1000000 + +#define MSM_VFE_VFE0_UB_SIZE 1023 +#define MSM_VFE_VFE0_UB_SIZE_RDI (MSM_VFE_VFE0_UB_SIZE / 3) + +static u32 vfe_hw_version(struct vfe_device *vfe) +{ + u32 hw_version = readl_relaxed(vfe->base + VFE_0_HW_VERSION); + + dev_dbg(vfe->camss->dev, "VFE HW Version = 0x%08x\n", hw_version); + + return hw_version; +} + +static u16 vfe_get_ub_size(u8 vfe_id) +{ + if (vfe_id == 0) + return MSM_VFE_VFE0_UB_SIZE_RDI; + + return 0; +} + +static inline void vfe_reg_clr(struct vfe_device *vfe, u32 reg, u32 clr_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits & ~clr_bits, vfe->base + reg); +} + +static inline void vfe_reg_set(struct vfe_device *vfe, u32 reg, u32 set_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits | set_bits, vfe->base + reg); +} + +static void vfe_global_reset(struct vfe_device *vfe) +{ + u32 reset_bits = VFE_0_GLOBAL_RESET_CMD_TESTGEN | + VFE_0_GLOBAL_RESET_CMD_BUS_MISR | + VFE_0_GLOBAL_RESET_CMD_PM | + VFE_0_GLOBAL_RESET_CMD_TIMER | + VFE_0_GLOBAL_RESET_CMD_REGISTER | + VFE_0_GLOBAL_RESET_CMD_BUS_BDG | + VFE_0_GLOBAL_RESET_CMD_BUS | + VFE_0_GLOBAL_RESET_CMD_CAMIF | + VFE_0_GLOBAL_RESET_CMD_CORE; + + writel_relaxed(reset_bits, vfe->base + VFE_0_GLOBAL_RESET_CMD); +} + +static void vfe_halt_request(struct vfe_device *vfe) +{ + writel_relaxed(VFE_0_BUS_BDG_CMD_HALT_REQ, + vfe->base + VFE_0_BUS_BDG_CMD); +} + +static void vfe_halt_clear(struct vfe_device *vfe) +{ + writel_relaxed(0x0, vfe->base + VFE_0_BUS_BDG_CMD); +} + +static void vfe_wm_enable(struct vfe_device *vfe, u8 wm, u8 enable) +{ + if (enable) + vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT); + else + vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT); +} + +static void vfe_wm_frame_based(struct vfe_device *vfe, u8 wm, u8 enable) +{ + if (enable) + vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT); + else + vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_FRM_BASED_SHIFT); +} + +static void vfe_get_wm_sizes(struct v4l2_pix_format_mplane *pix, u8 plane, + u16 *width, u16 *height, u16 *bytesperline) +{ + *width = pix->width; + *height = pix->height; + *bytesperline = pix->plane_fmt[0].bytesperline; + + if (pix->pixelformat == V4L2_PIX_FMT_NV12 || + pix->pixelformat == V4L2_PIX_FMT_NV21) + if (plane == 1) + *height /= 2; +} + +static void vfe_wm_line_based(struct vfe_device *vfe, u32 wm, + struct v4l2_pix_format_mplane *pix, + u8 plane, u32 enable) +{ + u32 reg; + + if (enable) { + u16 width = 0, height = 0, bytesperline = 0, wpl; + + vfe_get_wm_sizes(pix, plane, &width, &height, &bytesperline); + + wpl = vfe_word_per_line(pix->pixelformat, width); + + reg = height - 1; + reg |= ((wpl + 1) / 2 - 1) << 16; + + writel_relaxed(reg, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); + + wpl = vfe_word_per_line(pix->pixelformat, bytesperline); + + reg = 0x3; + reg |= (height - 1) << 4; + reg |= wpl << 16; + + writel_relaxed(reg, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); + } else { + writel_relaxed(0, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); + writel_relaxed(0, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); + } +} + +static void vfe_wm_set_framedrop_period(struct vfe_device *vfe, u8 wm, u8 per) +{ + u32 reg; + + reg = readl_relaxed(vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); + + reg &= ~(VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK); + + reg |= (per << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT) + & VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK; + + writel_relaxed(reg, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); +} + +static void vfe_wm_set_framedrop_pattern(struct vfe_device *vfe, u8 wm, + u32 pattern) +{ + writel_relaxed(pattern, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(wm)); +} + +static void vfe_wm_set_ub_cfg(struct vfe_device *vfe, u8 wm, + u16 offset, u16 depth) +{ + u32 reg; + + reg = (offset << VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT) | + depth; + writel_relaxed(reg, vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(wm)); +} + +static void vfe_bus_reload_wm(struct vfe_device *vfe, u8 wm) +{ + wmb(); + writel_relaxed(VFE_0_BUS_CMD_Mx_RLD_CMD(wm), vfe->base + VFE_0_BUS_CMD); + wmb(); +} + +static void vfe_wm_set_ping_addr(struct vfe_device *vfe, u8 wm, u32 addr) +{ + writel_relaxed(addr, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(wm)); +} + +static void vfe_wm_set_pong_addr(struct vfe_device *vfe, u8 wm, u32 addr) +{ + writel_relaxed(addr, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(wm)); +} + +static int vfe_wm_get_ping_pong_status(struct vfe_device *vfe, u8 wm) +{ + u32 reg; + + reg = readl_relaxed(vfe->base + VFE_0_BUS_PING_PONG_STATUS); + + return (reg >> wm) & 0x1; +} + +static void vfe_bus_enable_wr_if(struct vfe_device *vfe, u8 enable) +{ + if (enable) + writel_relaxed(0x10000009, vfe->base + VFE_0_BUS_CFG); + else + writel_relaxed(0, vfe->base + VFE_0_BUS_CFG); +} + +static void vfe_bus_connect_wm_to_rdi(struct vfe_device *vfe, u8 wm, + enum vfe_line_id id) +{ + u32 reg; + + reg = VFE_0_RDI_CFG_x_MIPI_EN_BITS; + reg |= VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(id); + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), reg); + + reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; + reg |= ((3 * id) << VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT) & + VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), reg); + + switch (id) { + case VFE_LINE_RDI0: + default: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI1: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI2: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + } + + if (wm % 2 == 1) + reg <<= 16; + + vfe_reg_set(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); +} + +static void vfe_wm_set_subsample(struct vfe_device *vfe, u8 wm) +{ + writel_relaxed(VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF, + vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(wm)); +} + +static void vfe_bus_disconnect_wm_from_rdi(struct vfe_device *vfe, u8 wm, + enum vfe_line_id id) +{ + u32 reg; + + reg = VFE_0_RDI_CFG_x_RDI_Mr_FRAME_BASED_EN(id); + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(0), reg); + + reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), reg); + + switch (id) { + case VFE_LINE_RDI0: + default: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI1: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI2: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + } + + if (wm % 2 == 1) + reg <<= 16; + + vfe_reg_clr(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); +} + +static void vfe_set_xbar_cfg(struct vfe_device *vfe, struct vfe_output *output, + u8 enable) +{ + struct vfe_line *line = container_of(output, struct vfe_line, output); + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + unsigned int i; + + for (i = 0; i < output->wm_num; i++) { + if (i == 0) { + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + } else if (i == 1) { + reg = VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV16) + reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA; + } else { + /* On current devices output->wm_num is always <= 2 */ + break; + } + + if (output->wm_idx[i] % 2 == 1) + reg <<= 16; + + if (enable) + vfe_reg_set(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[i]), + reg); + else + vfe_reg_clr(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[i]), + reg); + } +} + +static void vfe_set_realign_cfg(struct vfe_device *vfe, struct vfe_line *line, + u8 enable) +{ + /* empty */ +} +static void vfe_set_rdi_cid(struct vfe_device *vfe, enum vfe_line_id id, u8 cid) +{ + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), + VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK); + + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), + cid << VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT); +} + +static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + vfe->reg_update |= VFE_0_REG_UPDATE_line_n(line_id); + wmb(); + writel_relaxed(vfe->reg_update, vfe->base + VFE_0_REG_UPDATE); + wmb(); +} + +static inline void vfe_reg_update_clear(struct vfe_device *vfe, + enum vfe_line_id line_id) +{ + vfe->reg_update &= ~VFE_0_REG_UPDATE_line_n(line_id); +} + +static void vfe_enable_irq_wm_line(struct vfe_device *vfe, u8 wm, + enum vfe_line_id line_id, u8 enable) +{ + u32 irq_en0 = VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(wm) | + VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); + u32 irq_en1 = VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(wm) | + VFE_0_IRQ_MASK_1_RDIn_SOF(line_id); + + if (enable) { + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); + } else { + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); + } +} + +static void vfe_enable_irq_pix_line(struct vfe_device *vfe, u8 comp, + enum vfe_line_id line_id, u8 enable) +{ + struct vfe_output *output = &vfe->line[line_id].output; + unsigned int i; + u32 irq_en0; + u32 irq_en1; + u32 comp_mask = 0; + + irq_en0 = VFE_0_IRQ_MASK_0_CAMIF_SOF; + irq_en0 |= VFE_0_IRQ_MASK_0_CAMIF_EOF; + irq_en0 |= VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(comp); + irq_en0 |= VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); + irq_en1 = VFE_0_IRQ_MASK_1_CAMIF_ERROR; + for (i = 0; i < output->wm_num; i++) { + irq_en1 |= VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW( + output->wm_idx[i]); + comp_mask |= (1 << output->wm_idx[i]) << comp * 8; + } + + if (enable) { + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); + vfe_reg_set(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); + } else { + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); + vfe_reg_clr(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); + } +} + +static void vfe_enable_irq_common(struct vfe_device *vfe) +{ + u32 irq_en0 = VFE_0_IRQ_MASK_0_RESET_ACK; + u32 irq_en1 = VFE_0_IRQ_MASK_1_VIOLATION | + VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK; + + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); +} + +static void vfe_set_demux_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 val, even_cfg, odd_cfg; + + writel_relaxed(VFE_0_DEMUX_CFG_PERIOD, vfe->base + VFE_0_DEMUX_CFG); + + val = VFE_0_DEMUX_GAIN_0_CH0_EVEN | VFE_0_DEMUX_GAIN_0_CH0_ODD; + writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_0); + + val = VFE_0_DEMUX_GAIN_1_CH1 | VFE_0_DEMUX_GAIN_1_CH2; + writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_1); + + switch (line->fmt[MSM_VFE_PAD_SINK].code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY; + break; + } + + writel_relaxed(even_cfg, vfe->base + VFE_0_DEMUX_EVEN_CFG); + writel_relaxed(odd_cfg, vfe->base + VFE_0_DEMUX_ODD_CFG); +} + +static void vfe_set_scale_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + u16 input, output; + u8 interp_reso; + u32 phase_mult; + + writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_Y_CFG); + + input = line->fmt[MSM_VFE_PAD_SINK].width; + output = line->compose.width; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (13 + interp_reso)) / output; + reg = (interp_reso << 20) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_PHASE); + + input = line->fmt[MSM_VFE_PAD_SINK].height; + output = line->compose.height; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (13 + interp_reso)) / output; + reg = (interp_reso << 20) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_PHASE); + + writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_CBCR_CFG); + + input = line->fmt[MSM_VFE_PAD_SINK].width; + output = line->compose.width / 2; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (13 + interp_reso)) / output; + reg = (interp_reso << 20) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_PHASE); + + input = line->fmt[MSM_VFE_PAD_SINK].height; + output = line->compose.height; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) + output = line->compose.height / 2; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (13 + interp_reso)) / output; + reg = (interp_reso << 20) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_PHASE); +} + +static void vfe_set_crop_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + u16 first, last; + + first = line->crop.left; + last = line->crop.left + line->crop.width - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_WIDTH); + + first = line->crop.top; + last = line->crop.top + line->crop.height - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_HEIGHT); + + first = line->crop.left / 2; + last = line->crop.left / 2 + line->crop.width / 2 - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_WIDTH); + + first = line->crop.top; + last = line->crop.top + line->crop.height - 1; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) { + first = line->crop.top / 2; + last = line->crop.top / 2 + line->crop.height / 2 - 1; + } + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_HEIGHT); +} + +static void vfe_set_clamp_cfg(struct vfe_device *vfe) +{ + u32 val = VFE_0_CLAMP_ENC_MAX_CFG_CH0 | + VFE_0_CLAMP_ENC_MAX_CFG_CH1 | + VFE_0_CLAMP_ENC_MAX_CFG_CH2; + + writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MAX_CFG); + + val = VFE_0_CLAMP_ENC_MIN_CFG_CH0 | + VFE_0_CLAMP_ENC_MIN_CFG_CH1 | + VFE_0_CLAMP_ENC_MIN_CFG_CH2; + + writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MIN_CFG); +} + +static void vfe_set_qos(struct vfe_device *vfe) +{ + u32 val = VFE_0_BUS_BDG_QOS_CFG_0_CFG; + u32 val7 = VFE_0_BUS_BDG_QOS_CFG_7_CFG; + + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_0); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_1); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_2); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_3); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_4); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_5); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_6); + writel_relaxed(val7, vfe->base + VFE_0_BUS_BDG_QOS_CFG_7); +} + +static void vfe_set_ds(struct vfe_device *vfe) +{ + /* empty */ +} + +static void vfe_set_cgc_override(struct vfe_device *vfe, u8 wm, u8 enable) +{ + u32 val = VFE_0_CGC_OVERRIDE_1_IMAGE_Mx_CGC_OVERRIDE(wm); + + if (enable) + vfe_reg_set(vfe, VFE_0_CGC_OVERRIDE_1, val); + else + vfe_reg_clr(vfe, VFE_0_CGC_OVERRIDE_1, val); + + wmb(); +} + +static void vfe_set_camif_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 val; + + switch (line->fmt[MSM_VFE_PAD_SINK].code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY; + break; + } + + writel_relaxed(val, vfe->base + VFE_0_CORE_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].width * 2; + val |= line->fmt[MSM_VFE_PAD_SINK].height << 16; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_FRAME_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].width * 2 - 1; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_WIDTH_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].height - 1; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_HEIGHT_CFG); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_SUBSAMPLE_CFG_0); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN); + + val = VFE_0_RDI_CFG_x_MIPI_EN_BITS; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), val); + + val = VFE_0_CAMIF_CFG_VFE_OUTPUT_EN; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_CFG); +} + +static void vfe_set_camif_cmd(struct vfe_device *vfe, u8 enable) +{ + u32 cmd; + + cmd = VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS | VFE_0_CAMIF_CMD_NO_CHANGE; + writel_relaxed(cmd, vfe->base + VFE_0_CAMIF_CMD); + wmb(); + + if (enable) + cmd = VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY; + else + cmd = VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY; + + writel_relaxed(cmd, vfe->base + VFE_0_CAMIF_CMD); +} + +static void vfe_set_module_cfg(struct vfe_device *vfe, u8 enable) +{ + u32 val = VFE_0_MODULE_CFG_DEMUX | + VFE_0_MODULE_CFG_CHROMA_UPSAMPLE | + VFE_0_MODULE_CFG_SCALE_ENC | + VFE_0_MODULE_CFG_CROP_ENC; + + if (enable) + writel_relaxed(val, vfe->base + VFE_0_MODULE_CFG); + else + writel_relaxed(0x0, vfe->base + VFE_0_MODULE_CFG); +} + +static int vfe_camif_wait_for_stop(struct vfe_device *vfe, struct device *dev) +{ + u32 val; + int ret; + + ret = readl_poll_timeout(vfe->base + VFE_0_CAMIF_STATUS, + val, + (val & VFE_0_CAMIF_STATUS_HALT), + CAMIF_TIMEOUT_SLEEP_US, + CAMIF_TIMEOUT_ALL_US); + if (ret < 0) + dev_err(dev, "%s: camif stop timeout\n", __func__); + + return ret; +} + +static void vfe_isr_read(struct vfe_device *vfe, u32 *value0, u32 *value1) +{ + *value0 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_0); + *value1 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_1); + + writel_relaxed(*value0, vfe->base + VFE_0_IRQ_CLEAR_0); + writel_relaxed(*value1, vfe->base + VFE_0_IRQ_CLEAR_1); + + wmb(); + writel_relaxed(VFE_0_IRQ_CMD_GLOBAL_CLEAR, vfe->base + VFE_0_IRQ_CMD); +} + +static void vfe_violation_read(struct vfe_device *vfe) +{ + u32 violation = readl_relaxed(vfe->base + VFE_0_VIOLATION_STATUS); + + pr_err_ratelimited("VFE: violation = 0x%08x\n", violation); +} + +/* + * vfe_isr - VFE module interrupt handler + * @irq: Interrupt line + * @dev: VFE device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t vfe_isr(int irq, void *dev) +{ + struct vfe_device *vfe = dev; + u32 value0, value1; + int i, j; + + vfe->ops->isr_read(vfe, &value0, &value1); + + dev_dbg(vfe->camss->dev, "VFE: status0 = 0x%08x, status1 = 0x%08x\n", + value0, value1); + + if (value0 & VFE_0_IRQ_STATUS_0_RESET_ACK) + vfe->isr_ops.reset_ack(vfe); + + if (value1 & VFE_0_IRQ_STATUS_1_VIOLATION) + vfe->ops->violation_read(vfe); + + if (value1 & VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK) + vfe->isr_ops.halt_ack(vfe); + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_PIX; i++) + if (value0 & VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(i)) + vfe->isr_ops.reg_update(vfe, i); + + if (value0 & VFE_0_IRQ_STATUS_0_CAMIF_SOF) + vfe->isr_ops.sof(vfe, VFE_LINE_PIX); + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_RDI2; i++) + if (value1 & VFE_0_IRQ_STATUS_1_RDIn_SOF(i)) + vfe->isr_ops.sof(vfe, i); + + for (i = 0; i < MSM_VFE_COMPOSITE_IRQ_NUM; i++) + if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(i)) { + vfe->isr_ops.comp_done(vfe, i); + for (j = 0; j < ARRAY_SIZE(vfe->wm_output_map); j++) + if (vfe->wm_output_map[j] == VFE_LINE_PIX) + value0 &= ~VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(j); + } + + for (i = 0; i < MSM_VFE_IMAGE_MASTERS_NUM; i++) + if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(i)) + vfe->isr_ops.wm_done(vfe, i); + + return IRQ_HANDLED; +} + +/* + * vfe_pm_domain_off - Disable power domains specific to this VFE. + * @vfe: VFE Device + */ +static void vfe_pm_domain_off(struct vfe_device *vfe) +{ + /* nop */ +} + +/* + * vfe_pm_domain_on - Enable power domains specific to this VFE. + * @vfe: VFE Device + */ +static int vfe_pm_domain_on(struct vfe_device *vfe) +{ + return 0; +} + +static const struct vfe_hw_ops_gen1 vfe_ops_gen1_4_1 = { + .bus_connect_wm_to_rdi = vfe_bus_connect_wm_to_rdi, + .bus_disconnect_wm_from_rdi = vfe_bus_disconnect_wm_from_rdi, + .bus_enable_wr_if = vfe_bus_enable_wr_if, + .bus_reload_wm = vfe_bus_reload_wm, + .camif_wait_for_stop = vfe_camif_wait_for_stop, + .enable_irq_common = vfe_enable_irq_common, + .enable_irq_pix_line = vfe_enable_irq_pix_line, + .enable_irq_wm_line = vfe_enable_irq_wm_line, + .get_ub_size = vfe_get_ub_size, + .halt_clear = vfe_halt_clear, + .halt_request = vfe_halt_request, + .set_camif_cfg = vfe_set_camif_cfg, + .set_camif_cmd = vfe_set_camif_cmd, + .set_cgc_override = vfe_set_cgc_override, + .set_clamp_cfg = vfe_set_clamp_cfg, + .set_crop_cfg = vfe_set_crop_cfg, + .set_demux_cfg = vfe_set_demux_cfg, + .set_ds = vfe_set_ds, + .set_module_cfg = vfe_set_module_cfg, + .set_qos = vfe_set_qos, + .set_rdi_cid = vfe_set_rdi_cid, + .set_realign_cfg = vfe_set_realign_cfg, + .set_scale_cfg = vfe_set_scale_cfg, + .set_xbar_cfg = vfe_set_xbar_cfg, + .wm_enable = vfe_wm_enable, + .wm_frame_based = vfe_wm_frame_based, + .wm_get_ping_pong_status = vfe_wm_get_ping_pong_status, + .wm_line_based = vfe_wm_line_based, + .wm_set_framedrop_pattern = vfe_wm_set_framedrop_pattern, + .wm_set_framedrop_period = vfe_wm_set_framedrop_period, + .wm_set_ping_addr = vfe_wm_set_ping_addr, + .wm_set_pong_addr = vfe_wm_set_pong_addr, + .wm_set_subsample = vfe_wm_set_subsample, + .wm_set_ub_cfg = vfe_wm_set_ub_cfg, +}; + +static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe) +{ + vfe->isr_ops = vfe_isr_ops_gen1; + vfe->ops_gen1 = &vfe_ops_gen1_4_1; + vfe->video_ops = vfe_video_ops_gen1; + + vfe->line_num = VFE_LINE_NUM_GEN1; +} + +const struct vfe_hw_ops vfe_ops_4_1 = { + .global_reset = vfe_global_reset, + .hw_version = vfe_hw_version, + .isr_read = vfe_isr_read, + .isr = vfe_isr, + .pm_domain_off = vfe_pm_domain_off, + .pm_domain_on = vfe_pm_domain_on, + .reg_update_clear = vfe_reg_update_clear, + .reg_update = vfe_reg_update, + .subdev_init = vfe_subdev_init, + .vfe_disable = vfe_gen1_disable, + .vfe_enable = vfe_gen1_enable, + .vfe_halt = vfe_gen1_halt, + .violation_read = vfe_violation_read, +}; diff --git a/drivers/media/platform/qcom/camss/camss-vfe-4-7.c b/drivers/media/platform/qcom/camss/camss-vfe-4-7.c new file mode 100644 index 000000000..ab2d57bdf --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe-4-7.c @@ -0,0 +1,1209 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-vfe-4-7.c + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module v4.7 + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ + +#include +#include +#include +#include + +#include "camss.h" +#include "camss-vfe.h" +#include "camss-vfe-gen1.h" + + +#define VFE_0_HW_VERSION 0x000 + +#define VFE_0_GLOBAL_RESET_CMD 0x018 +#define VFE_0_GLOBAL_RESET_CMD_CORE BIT(0) +#define VFE_0_GLOBAL_RESET_CMD_CAMIF BIT(1) +#define VFE_0_GLOBAL_RESET_CMD_BUS BIT(2) +#define VFE_0_GLOBAL_RESET_CMD_BUS_BDG BIT(3) +#define VFE_0_GLOBAL_RESET_CMD_REGISTER BIT(4) +#define VFE_0_GLOBAL_RESET_CMD_PM BIT(5) +#define VFE_0_GLOBAL_RESET_CMD_BUS_MISR BIT(6) +#define VFE_0_GLOBAL_RESET_CMD_TESTGEN BIT(7) +#define VFE_0_GLOBAL_RESET_CMD_DSP BIT(8) +#define VFE_0_GLOBAL_RESET_CMD_IDLE_CGC BIT(9) + +#define VFE_0_MODULE_LENS_EN 0x040 +#define VFE_0_MODULE_LENS_EN_DEMUX BIT(2) +#define VFE_0_MODULE_LENS_EN_CHROMA_UPSAMPLE BIT(3) + +#define VFE_0_MODULE_ZOOM_EN 0x04c +#define VFE_0_MODULE_ZOOM_EN_SCALE_ENC BIT(1) +#define VFE_0_MODULE_ZOOM_EN_CROP_ENC BIT(2) +#define VFE_0_MODULE_ZOOM_EN_REALIGN_BUF BIT(9) + +#define VFE_0_CORE_CFG 0x050 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR 0x4 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB 0x5 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY 0x6 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY 0x7 +#define VFE_0_CORE_CFG_COMPOSITE_REG_UPDATE_EN BIT(4) + +#define VFE_0_IRQ_CMD 0x058 +#define VFE_0_IRQ_CMD_GLOBAL_CLEAR BIT(0) + +#define VFE_0_IRQ_MASK_0 0x05c +#define VFE_0_IRQ_MASK_0_CAMIF_SOF BIT(0) +#define VFE_0_IRQ_MASK_0_CAMIF_EOF BIT(1) +#define VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n) BIT((n) + 5) +#define VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(n) \ + ((n) == VFE_LINE_PIX ? BIT(4) : VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n)) +#define VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(n) BIT((n) + 8) +#define VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(n) BIT((n) + 25) +#define VFE_0_IRQ_MASK_0_RESET_ACK BIT(31) +#define VFE_0_IRQ_MASK_1 0x060 +#define VFE_0_IRQ_MASK_1_CAMIF_ERROR BIT(0) +#define VFE_0_IRQ_MASK_1_VIOLATION BIT(7) +#define VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK BIT(8) +#define VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(n) BIT((n) + 9) +#define VFE_0_IRQ_MASK_1_RDIn_SOF(n) BIT((n) + 29) + +#define VFE_0_IRQ_CLEAR_0 0x064 +#define VFE_0_IRQ_CLEAR_1 0x068 + +#define VFE_0_IRQ_STATUS_0 0x06c +#define VFE_0_IRQ_STATUS_0_CAMIF_SOF BIT(0) +#define VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n) BIT((n) + 5) +#define VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(n) \ + ((n) == VFE_LINE_PIX ? BIT(4) : VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n)) +#define VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(n) BIT((n) + 8) +#define VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(n) BIT((n) + 25) +#define VFE_0_IRQ_STATUS_0_RESET_ACK BIT(31) +#define VFE_0_IRQ_STATUS_1 0x070 +#define VFE_0_IRQ_STATUS_1_VIOLATION BIT(7) +#define VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK BIT(8) +#define VFE_0_IRQ_STATUS_1_RDIn_SOF(n) BIT((n) + 29) + +#define VFE_0_IRQ_COMPOSITE_MASK_0 0x074 +#define VFE_0_VIOLATION_STATUS 0x07c + +#define VFE_0_BUS_CMD 0x80 +#define VFE_0_BUS_CMD_Mx_RLD_CMD(x) BIT(x) + +#define VFE_0_BUS_CFG 0x084 + +#define VFE_0_BUS_XBAR_CFG_x(x) (0x90 + 0x4 * ((x) / 2)) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN BIT(2) +#define VFE_0_BUS_XBAR_CFG_x_M_REALIGN_BUF_EN BIT(3) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTRA (0x1 << 4) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER (0x2 << 4) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA (0x3 << 4) +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT 8 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA 0x0 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 0xc +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 0xd +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 0xe + +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(n) (0x0a0 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT 0 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(n) (0x0a4 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(n) (0x0ac + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(n) (0x0b4 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_BASED_SHIFT 1 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT 2 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK (0x1f << 2) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(n) (0x0b8 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT 16 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(n) (0x0bc + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(n) (0x0c0 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(n) \ + (0x0c4 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(n) \ + (0x0c8 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF 0xffffffff + +#define VFE_0_BUS_PING_PONG_STATUS 0x338 + +#define VFE_0_BUS_BDG_CMD 0x400 +#define VFE_0_BUS_BDG_CMD_HALT_REQ 1 + +#define VFE_0_BUS_BDG_QOS_CFG_0 0x404 +#define VFE_0_BUS_BDG_QOS_CFG_0_CFG 0xaaa9aaa9 +#define VFE_0_BUS_BDG_QOS_CFG_1 0x408 +#define VFE_0_BUS_BDG_QOS_CFG_2 0x40c +#define VFE_0_BUS_BDG_QOS_CFG_3 0x410 +#define VFE_0_BUS_BDG_QOS_CFG_4 0x414 +#define VFE_0_BUS_BDG_QOS_CFG_5 0x418 +#define VFE_0_BUS_BDG_QOS_CFG_6 0x41c +#define VFE_0_BUS_BDG_QOS_CFG_7 0x420 +#define VFE_0_BUS_BDG_QOS_CFG_7_CFG 0x0001aaa9 + +#define VFE48_0_BUS_BDG_QOS_CFG_0_CFG 0xaaa5aaa5 +#define VFE48_0_BUS_BDG_QOS_CFG_3_CFG 0xaa55aaa5 +#define VFE48_0_BUS_BDG_QOS_CFG_4_CFG 0xaa55aa55 +#define VFE48_0_BUS_BDG_QOS_CFG_7_CFG 0x0005aa55 + +#define VFE_0_BUS_BDG_DS_CFG_0 0x424 +#define VFE_0_BUS_BDG_DS_CFG_0_CFG 0xcccc0011 +#define VFE_0_BUS_BDG_DS_CFG_1 0x428 +#define VFE_0_BUS_BDG_DS_CFG_2 0x42c +#define VFE_0_BUS_BDG_DS_CFG_3 0x430 +#define VFE_0_BUS_BDG_DS_CFG_4 0x434 +#define VFE_0_BUS_BDG_DS_CFG_5 0x438 +#define VFE_0_BUS_BDG_DS_CFG_6 0x43c +#define VFE_0_BUS_BDG_DS_CFG_7 0x440 +#define VFE_0_BUS_BDG_DS_CFG_8 0x444 +#define VFE_0_BUS_BDG_DS_CFG_9 0x448 +#define VFE_0_BUS_BDG_DS_CFG_10 0x44c +#define VFE_0_BUS_BDG_DS_CFG_11 0x450 +#define VFE_0_BUS_BDG_DS_CFG_12 0x454 +#define VFE_0_BUS_BDG_DS_CFG_13 0x458 +#define VFE_0_BUS_BDG_DS_CFG_14 0x45c +#define VFE_0_BUS_BDG_DS_CFG_15 0x460 +#define VFE_0_BUS_BDG_DS_CFG_16 0x464 +#define VFE_0_BUS_BDG_DS_CFG_16_CFG 0x40000103 + +#define VFE48_0_BUS_BDG_DS_CFG_0_CFG 0xcccc1111 +#define VFE48_0_BUS_BDG_DS_CFG_16_CFG 0x00000110 + +#define VFE_0_RDI_CFG_x(x) (0x46c + (0x4 * (x))) +#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT 28 +#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK (0xf << 28) +#define VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT 4 +#define VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK (0xf << 4) +#define VFE_0_RDI_CFG_x_RDI_EN_BIT BIT(2) +#define VFE_0_RDI_CFG_x_MIPI_EN_BITS 0x3 + +#define VFE_0_CAMIF_CMD 0x478 +#define VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY 0 +#define VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY 1 +#define VFE_0_CAMIF_CMD_NO_CHANGE 3 +#define VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS BIT(2) +#define VFE_0_CAMIF_CFG 0x47c +#define VFE_0_CAMIF_CFG_VFE_OUTPUT_EN BIT(6) +#define VFE_0_CAMIF_FRAME_CFG 0x484 +#define VFE_0_CAMIF_WINDOW_WIDTH_CFG 0x488 +#define VFE_0_CAMIF_WINDOW_HEIGHT_CFG 0x48c +#define VFE_0_CAMIF_SUBSAMPLE_CFG 0x490 +#define VFE_0_CAMIF_IRQ_FRAMEDROP_PATTERN 0x498 +#define VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN 0x49c +#define VFE_0_CAMIF_STATUS 0x4a4 +#define VFE_0_CAMIF_STATUS_HALT BIT(31) + +#define VFE_0_REG_UPDATE 0x4ac +#define VFE_0_REG_UPDATE_RDIn(n) BIT(1 + (n)) +#define VFE_0_REG_UPDATE_line_n(n) \ + ((n) == VFE_LINE_PIX ? 1 : VFE_0_REG_UPDATE_RDIn(n)) + +#define VFE_0_DEMUX_CFG 0x560 +#define VFE_0_DEMUX_CFG_PERIOD 0x3 +#define VFE_0_DEMUX_GAIN_0 0x564 +#define VFE_0_DEMUX_GAIN_0_CH0_EVEN (0x80 << 0) +#define VFE_0_DEMUX_GAIN_0_CH0_ODD (0x80 << 16) +#define VFE_0_DEMUX_GAIN_1 0x568 +#define VFE_0_DEMUX_GAIN_1_CH1 (0x80 << 0) +#define VFE_0_DEMUX_GAIN_1_CH2 (0x80 << 16) +#define VFE_0_DEMUX_EVEN_CFG 0x574 +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV 0x9cac +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU 0xac9c +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY 0xc9ca +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY 0xcac9 +#define VFE_0_DEMUX_ODD_CFG 0x578 +#define VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV 0x9cac +#define VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU 0xac9c +#define VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY 0xc9ca +#define VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY 0xcac9 + +#define VFE_0_SCALE_ENC_Y_CFG 0x91c +#define VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE 0x920 +#define VFE_0_SCALE_ENC_Y_H_PHASE 0x924 +#define VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE 0x934 +#define VFE_0_SCALE_ENC_Y_V_PHASE 0x938 +#define VFE_0_SCALE_ENC_CBCR_CFG 0x948 +#define VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE 0x94c +#define VFE_0_SCALE_ENC_CBCR_H_PHASE 0x950 +#define VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE 0x960 +#define VFE_0_SCALE_ENC_CBCR_V_PHASE 0x964 + +#define VFE_0_CROP_ENC_Y_WIDTH 0x974 +#define VFE_0_CROP_ENC_Y_HEIGHT 0x978 +#define VFE_0_CROP_ENC_CBCR_WIDTH 0x97c +#define VFE_0_CROP_ENC_CBCR_HEIGHT 0x980 + +#define VFE_0_CLAMP_ENC_MAX_CFG 0x984 +#define VFE_0_CLAMP_ENC_MAX_CFG_CH0 (0xff << 0) +#define VFE_0_CLAMP_ENC_MAX_CFG_CH1 (0xff << 8) +#define VFE_0_CLAMP_ENC_MAX_CFG_CH2 (0xff << 16) +#define VFE_0_CLAMP_ENC_MIN_CFG 0x988 +#define VFE_0_CLAMP_ENC_MIN_CFG_CH0 (0x0 << 0) +#define VFE_0_CLAMP_ENC_MIN_CFG_CH1 (0x0 << 8) +#define VFE_0_CLAMP_ENC_MIN_CFG_CH2 (0x0 << 16) + +#define VFE_0_REALIGN_BUF_CFG 0xaac +#define VFE_0_REALIGN_BUF_CFG_CB_ODD_PIXEL BIT(2) +#define VFE_0_REALIGN_BUF_CFG_CR_ODD_PIXEL BIT(3) +#define VFE_0_REALIGN_BUF_CFG_HSUB_ENABLE BIT(4) + +#define VFE48_0_BUS_IMAGE_MASTER_CMD 0xcec +#define VFE48_0_BUS_IMAGE_MASTER_n_SHIFT(x) (2 * (x)) + +#define CAMIF_TIMEOUT_SLEEP_US 1000 +#define CAMIF_TIMEOUT_ALL_US 1000000 + +#define MSM_VFE_VFE0_UB_SIZE 2047 +#define MSM_VFE_VFE0_UB_SIZE_RDI (MSM_VFE_VFE0_UB_SIZE / 3) +#define MSM_VFE_VFE1_UB_SIZE 1535 +#define MSM_VFE_VFE1_UB_SIZE_RDI (MSM_VFE_VFE1_UB_SIZE / 3) + +static u32 vfe_hw_version(struct vfe_device *vfe) +{ + u32 hw_version = readl_relaxed(vfe->base + VFE_0_HW_VERSION); + + dev_dbg(vfe->camss->dev, "VFE HW Version = 0x%08x\n", hw_version); + + return hw_version; +} + +static u16 vfe_get_ub_size(u8 vfe_id) +{ + if (vfe_id == 0) + return MSM_VFE_VFE0_UB_SIZE_RDI; + else if (vfe_id == 1) + return MSM_VFE_VFE1_UB_SIZE_RDI; + + return 0; +} + +static inline void vfe_reg_clr(struct vfe_device *vfe, u32 reg, u32 clr_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits & ~clr_bits, vfe->base + reg); +} + +static inline void vfe_reg_set(struct vfe_device *vfe, u32 reg, u32 set_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits | set_bits, vfe->base + reg); +} + +static void vfe_global_reset(struct vfe_device *vfe) +{ + u32 reset_bits = VFE_0_GLOBAL_RESET_CMD_IDLE_CGC | + VFE_0_GLOBAL_RESET_CMD_DSP | + VFE_0_GLOBAL_RESET_CMD_TESTGEN | + VFE_0_GLOBAL_RESET_CMD_BUS_MISR | + VFE_0_GLOBAL_RESET_CMD_PM | + VFE_0_GLOBAL_RESET_CMD_REGISTER | + VFE_0_GLOBAL_RESET_CMD_BUS_BDG | + VFE_0_GLOBAL_RESET_CMD_BUS | + VFE_0_GLOBAL_RESET_CMD_CAMIF | + VFE_0_GLOBAL_RESET_CMD_CORE; + + writel_relaxed(BIT(31), vfe->base + VFE_0_IRQ_MASK_0); + + /* Enforce barrier between IRQ mask setup and global reset */ + wmb(); + writel_relaxed(reset_bits, vfe->base + VFE_0_GLOBAL_RESET_CMD); +} + +static void vfe_halt_request(struct vfe_device *vfe) +{ + writel_relaxed(VFE_0_BUS_BDG_CMD_HALT_REQ, + vfe->base + VFE_0_BUS_BDG_CMD); +} + +static void vfe_halt_clear(struct vfe_device *vfe) +{ + writel_relaxed(0x0, vfe->base + VFE_0_BUS_BDG_CMD); +} + +static void vfe_wm_enable(struct vfe_device *vfe, u8 wm, u8 enable) +{ + if (enable) + vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT); + else + vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT); +} + +static void vfe_wm_frame_based(struct vfe_device *vfe, u8 wm, u8 enable) +{ + if (enable) + vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_BASED_SHIFT); + else + vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_BASED_SHIFT); +} + +#define CALC_WORD(width, M, N) (((width) * (M) + (N) - 1) / (N)) + +static int vfe_word_per_line_by_pixel(u32 format, u32 pixel_per_line) +{ + int val = 0; + + switch (format) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + val = CALC_WORD(pixel_per_line, 1, 8); + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + val = CALC_WORD(pixel_per_line, 2, 8); + break; + } + + return val; +} + +static int vfe_word_per_line_by_bytes(u32 bytes_per_line) +{ + return CALC_WORD(bytes_per_line, 1, 8); +} + +static void vfe_get_wm_sizes(struct v4l2_pix_format_mplane *pix, u8 plane, + u16 *width, u16 *height, u16 *bytesperline) +{ + *width = pix->width; + *height = pix->height; + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + *bytesperline = pix->plane_fmt[0].bytesperline; + if (plane == 1) + *height /= 2; + break; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + *bytesperline = pix->plane_fmt[0].bytesperline; + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_UYVY: + *bytesperline = pix->plane_fmt[plane].bytesperline; + break; + } +} + +static void vfe_wm_line_based(struct vfe_device *vfe, u32 wm, + struct v4l2_pix_format_mplane *pix, + u8 plane, u32 enable) +{ + u32 reg; + + if (enable) { + u16 width = 0, height = 0, bytesperline = 0, wpl; + + vfe_get_wm_sizes(pix, plane, &width, &height, &bytesperline); + + wpl = vfe_word_per_line_by_pixel(pix->pixelformat, width); + + reg = height - 1; + reg |= ((wpl + 3) / 4 - 1) << 16; + + writel_relaxed(reg, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); + + wpl = vfe_word_per_line_by_bytes(bytesperline); + + reg = 0x3; + reg |= (height - 1) << 2; + reg |= ((wpl + 1) / 2) << 16; + + writel_relaxed(reg, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); + } else { + writel_relaxed(0, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); + writel_relaxed(0, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); + } +} + +static void vfe_wm_set_framedrop_period(struct vfe_device *vfe, u8 wm, u8 per) +{ + u32 reg; + + reg = readl_relaxed(vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); + + reg &= ~(VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK); + + reg |= (per << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT) + & VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK; + + writel_relaxed(reg, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); +} + +static void vfe_wm_set_framedrop_pattern(struct vfe_device *vfe, u8 wm, + u32 pattern) +{ + writel_relaxed(pattern, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(wm)); +} + +static void vfe_wm_set_ub_cfg(struct vfe_device *vfe, u8 wm, + u16 offset, u16 depth) +{ + u32 reg; + + reg = (offset << VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT) | + depth; + writel_relaxed(reg, vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(wm)); +} + +static void vfe_bus_reload_wm(struct vfe_device *vfe, u8 wm) +{ + /* Enforce barrier between any outstanding register write */ + wmb(); + + writel_relaxed(VFE_0_BUS_CMD_Mx_RLD_CMD(wm), vfe->base + VFE_0_BUS_CMD); + + /* Use barrier to make sure bus reload is issued before anything else */ + wmb(); +} + +static void vfe_wm_set_ping_addr(struct vfe_device *vfe, u8 wm, u32 addr) +{ + writel_relaxed(addr, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(wm)); +} + +static void vfe_wm_set_pong_addr(struct vfe_device *vfe, u8 wm, u32 addr) +{ + writel_relaxed(addr, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(wm)); +} + +static int vfe_wm_get_ping_pong_status(struct vfe_device *vfe, u8 wm) +{ + u32 reg; + + reg = readl_relaxed(vfe->base + VFE_0_BUS_PING_PONG_STATUS); + + return (reg >> wm) & 0x1; +} + +static void vfe_bus_enable_wr_if(struct vfe_device *vfe, u8 enable) +{ + if (enable) + writel_relaxed(0x101, vfe->base + VFE_0_BUS_CFG); + else + writel_relaxed(0, vfe->base + VFE_0_BUS_CFG); +} + +static void vfe_bus_connect_wm_to_rdi(struct vfe_device *vfe, u8 wm, + enum vfe_line_id id) +{ + u32 reg; + + reg = VFE_0_RDI_CFG_x_MIPI_EN_BITS; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), reg); + + reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; + reg |= ((3 * id) << VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT) & + VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), reg); + + switch (id) { + case VFE_LINE_RDI0: + default: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI1: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI2: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + } + + if (wm % 2 == 1) + reg <<= 16; + + vfe_reg_set(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); +} + +static void vfe_wm_set_subsample(struct vfe_device *vfe, u8 wm) +{ + writel_relaxed(VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF, + vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(wm)); +} + +static void vfe_bus_disconnect_wm_from_rdi(struct vfe_device *vfe, u8 wm, + enum vfe_line_id id) +{ + u32 reg; + + reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), reg); + + switch (id) { + case VFE_LINE_RDI0: + default: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI1: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI2: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + } + + if (wm % 2 == 1) + reg <<= 16; + + vfe_reg_clr(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); +} + +static void vfe_set_xbar_cfg(struct vfe_device *vfe, struct vfe_output *output, + u8 enable) +{ + struct vfe_line *line = container_of(output, struct vfe_line, output); + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + + switch (p) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + + if (output->wm_idx[0] % 2 == 1) + reg <<= 16; + + if (enable) + vfe_reg_set(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[0]), + reg); + else + vfe_reg_clr(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[0]), + reg); + + reg = VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV16) + reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA; + + if (output->wm_idx[1] % 2 == 1) + reg <<= 16; + + if (enable) + vfe_reg_set(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[1]), + reg); + else + vfe_reg_clr(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[1]), + reg); + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_UYVY: + reg = VFE_0_BUS_XBAR_CFG_x_M_REALIGN_BUF_EN; + reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN; + + if (p == V4L2_PIX_FMT_YUYV || p == V4L2_PIX_FMT_YVYU) + reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA; + + if (output->wm_idx[0] % 2 == 1) + reg <<= 16; + + if (enable) + vfe_reg_set(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[0]), + reg); + else + vfe_reg_clr(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[0]), + reg); + break; + default: + break; + } +} + +static void vfe_set_realign_cfg(struct vfe_device *vfe, struct vfe_line *line, + u8 enable) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 val = VFE_0_MODULE_ZOOM_EN_REALIGN_BUF; + + if (p != V4L2_PIX_FMT_YUYV && p != V4L2_PIX_FMT_YVYU && + p != V4L2_PIX_FMT_VYUY && p != V4L2_PIX_FMT_UYVY) + return; + + if (enable) { + vfe_reg_set(vfe, VFE_0_MODULE_ZOOM_EN, val); + } else { + vfe_reg_clr(vfe, VFE_0_MODULE_ZOOM_EN, val); + return; + } + + val = VFE_0_REALIGN_BUF_CFG_HSUB_ENABLE; + + if (p == V4L2_PIX_FMT_UYVY || p == V4L2_PIX_FMT_YUYV) + val |= VFE_0_REALIGN_BUF_CFG_CR_ODD_PIXEL; + else + val |= VFE_0_REALIGN_BUF_CFG_CB_ODD_PIXEL; + + writel_relaxed(val, vfe->base + VFE_0_REALIGN_BUF_CFG); +} + +static void vfe_set_rdi_cid(struct vfe_device *vfe, enum vfe_line_id id, u8 cid) +{ + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), + VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK); + + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), + cid << VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT); +} + +static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + vfe->reg_update |= VFE_0_REG_UPDATE_line_n(line_id); + + /* Enforce barrier between line update and commit */ + wmb(); + writel_relaxed(vfe->reg_update, vfe->base + VFE_0_REG_UPDATE); + + /* Make sure register update is issued before further reg writes */ + wmb(); +} + +static inline void vfe_reg_update_clear(struct vfe_device *vfe, + enum vfe_line_id line_id) +{ + vfe->reg_update &= ~VFE_0_REG_UPDATE_line_n(line_id); +} + +static void vfe_enable_irq_wm_line(struct vfe_device *vfe, u8 wm, + enum vfe_line_id line_id, u8 enable) +{ + u32 irq_en0 = VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(wm) | + VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); + u32 irq_en1 = VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(wm) | + VFE_0_IRQ_MASK_1_RDIn_SOF(line_id); + + if (enable) { + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); + } else { + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); + } +} + +static void vfe_enable_irq_pix_line(struct vfe_device *vfe, u8 comp, + enum vfe_line_id line_id, u8 enable) +{ + struct vfe_output *output = &vfe->line[line_id].output; + unsigned int i; + u32 irq_en0; + u32 irq_en1; + u32 comp_mask = 0; + + irq_en0 = VFE_0_IRQ_MASK_0_CAMIF_SOF; + irq_en0 |= VFE_0_IRQ_MASK_0_CAMIF_EOF; + irq_en0 |= VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(comp); + irq_en0 |= VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); + irq_en1 = VFE_0_IRQ_MASK_1_CAMIF_ERROR; + for (i = 0; i < output->wm_num; i++) { + irq_en1 |= VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW( + output->wm_idx[i]); + comp_mask |= (1 << output->wm_idx[i]) << comp * 8; + } + + if (enable) { + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); + vfe_reg_set(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); + } else { + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); + vfe_reg_clr(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); + } +} + +static void vfe_enable_irq_common(struct vfe_device *vfe) +{ + u32 irq_en0 = VFE_0_IRQ_MASK_0_RESET_ACK; + u32 irq_en1 = VFE_0_IRQ_MASK_1_VIOLATION | + VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK; + + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); +} + +static void vfe_set_demux_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 val, even_cfg, odd_cfg; + + writel_relaxed(VFE_0_DEMUX_CFG_PERIOD, vfe->base + VFE_0_DEMUX_CFG); + + val = VFE_0_DEMUX_GAIN_0_CH0_EVEN | VFE_0_DEMUX_GAIN_0_CH0_ODD; + writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_0); + + val = VFE_0_DEMUX_GAIN_1_CH1 | VFE_0_DEMUX_GAIN_1_CH2; + writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_1); + + switch (line->fmt[MSM_VFE_PAD_SINK].code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY; + break; + } + + writel_relaxed(even_cfg, vfe->base + VFE_0_DEMUX_EVEN_CFG); + writel_relaxed(odd_cfg, vfe->base + VFE_0_DEMUX_ODD_CFG); +} + +static void vfe_set_scale_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + u16 input, output; + u8 interp_reso; + u32 phase_mult; + + writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_Y_CFG); + + input = line->fmt[MSM_VFE_PAD_SINK].width - 1; + output = line->compose.width - 1; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (14 + interp_reso)) / output; + reg = (interp_reso << 28) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_PHASE); + + input = line->fmt[MSM_VFE_PAD_SINK].height - 1; + output = line->compose.height - 1; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (14 + interp_reso)) / output; + reg = (interp_reso << 28) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_PHASE); + + writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_CBCR_CFG); + + input = line->fmt[MSM_VFE_PAD_SINK].width - 1; + output = line->compose.width / 2 - 1; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (14 + interp_reso)) / output; + reg = (interp_reso << 28) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_PHASE); + + input = line->fmt[MSM_VFE_PAD_SINK].height - 1; + output = line->compose.height - 1; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) + output = line->compose.height / 2 - 1; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (14 + interp_reso)) / output; + reg = (interp_reso << 28) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_PHASE); +} + +static void vfe_set_crop_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + u16 first, last; + + first = line->crop.left; + last = line->crop.left + line->crop.width - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_WIDTH); + + first = line->crop.top; + last = line->crop.top + line->crop.height - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_HEIGHT); + + first = line->crop.left / 2; + last = line->crop.left / 2 + line->crop.width / 2 - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_WIDTH); + + first = line->crop.top; + last = line->crop.top + line->crop.height - 1; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) { + first = line->crop.top / 2; + last = line->crop.top / 2 + line->crop.height / 2 - 1; + } + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_HEIGHT); +} + +static void vfe_set_clamp_cfg(struct vfe_device *vfe) +{ + u32 val = VFE_0_CLAMP_ENC_MAX_CFG_CH0 | + VFE_0_CLAMP_ENC_MAX_CFG_CH1 | + VFE_0_CLAMP_ENC_MAX_CFG_CH2; + + writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MAX_CFG); + + val = VFE_0_CLAMP_ENC_MIN_CFG_CH0 | + VFE_0_CLAMP_ENC_MIN_CFG_CH1 | + VFE_0_CLAMP_ENC_MIN_CFG_CH2; + + writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MIN_CFG); +} + +static void vfe_set_qos(struct vfe_device *vfe) +{ + u32 val = VFE_0_BUS_BDG_QOS_CFG_0_CFG; + u32 val7 = VFE_0_BUS_BDG_QOS_CFG_7_CFG; + + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_0); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_1); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_2); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_3); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_4); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_5); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_6); + writel_relaxed(val7, vfe->base + VFE_0_BUS_BDG_QOS_CFG_7); +} + +static void vfe_set_ds(struct vfe_device *vfe) +{ + u32 val = VFE_0_BUS_BDG_DS_CFG_0_CFG; + u32 val16 = VFE_0_BUS_BDG_DS_CFG_16_CFG; + + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_0); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_1); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_2); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_3); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_4); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_5); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_6); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_7); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_8); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_9); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_10); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_11); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_12); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_13); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_14); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_15); + writel_relaxed(val16, vfe->base + VFE_0_BUS_BDG_DS_CFG_16); +} + +static void vfe_set_cgc_override(struct vfe_device *vfe, u8 wm, u8 enable) +{ + /* empty */ +} + +static void vfe_set_camif_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 val; + + switch (line->fmt[MSM_VFE_PAD_SINK].code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY; + break; + } + + val |= VFE_0_CORE_CFG_COMPOSITE_REG_UPDATE_EN; + writel_relaxed(val, vfe->base + VFE_0_CORE_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].width * 2 - 1; + val |= (line->fmt[MSM_VFE_PAD_SINK].height - 1) << 16; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_FRAME_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].width * 2 - 1; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_WIDTH_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].height - 1; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_HEIGHT_CFG); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_SUBSAMPLE_CFG); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_IRQ_FRAMEDROP_PATTERN); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN); + + val = VFE_0_RDI_CFG_x_MIPI_EN_BITS; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), val); + + val = VFE_0_CAMIF_CFG_VFE_OUTPUT_EN; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_CFG); +} + +static void vfe_set_camif_cmd(struct vfe_device *vfe, u8 enable) +{ + u32 cmd; + + cmd = VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS | VFE_0_CAMIF_CMD_NO_CHANGE; + writel_relaxed(cmd, vfe->base + VFE_0_CAMIF_CMD); + + /* Make sure camif command is issued written before it is changed again */ + wmb(); + + if (enable) + cmd = VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY; + else + cmd = VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY; + + writel_relaxed(cmd, vfe->base + VFE_0_CAMIF_CMD); +} + +static void vfe_set_module_cfg(struct vfe_device *vfe, u8 enable) +{ + u32 val_lens = VFE_0_MODULE_LENS_EN_DEMUX | + VFE_0_MODULE_LENS_EN_CHROMA_UPSAMPLE; + u32 val_zoom = VFE_0_MODULE_ZOOM_EN_SCALE_ENC | + VFE_0_MODULE_ZOOM_EN_CROP_ENC; + + if (enable) { + vfe_reg_set(vfe, VFE_0_MODULE_LENS_EN, val_lens); + vfe_reg_set(vfe, VFE_0_MODULE_ZOOM_EN, val_zoom); + } else { + vfe_reg_clr(vfe, VFE_0_MODULE_LENS_EN, val_lens); + vfe_reg_clr(vfe, VFE_0_MODULE_ZOOM_EN, val_zoom); + } +} + +static int vfe_camif_wait_for_stop(struct vfe_device *vfe, struct device *dev) +{ + u32 val; + int ret; + + ret = readl_poll_timeout(vfe->base + VFE_0_CAMIF_STATUS, + val, + (val & VFE_0_CAMIF_STATUS_HALT), + CAMIF_TIMEOUT_SLEEP_US, + CAMIF_TIMEOUT_ALL_US); + if (ret < 0) + dev_err(dev, "%s: camif stop timeout\n", __func__); + + return ret; +} + + + +/* + * vfe_isr - VFE module interrupt handler + * @irq: Interrupt line + * @dev: VFE device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t vfe_isr(int irq, void *dev) +{ + struct vfe_device *vfe = dev; + u32 value0, value1; + int i, j; + + vfe->ops->isr_read(vfe, &value0, &value1); + + dev_dbg(vfe->camss->dev, "VFE: status0 = 0x%08x, status1 = 0x%08x\n", + value0, value1); + + if (value0 & VFE_0_IRQ_STATUS_0_RESET_ACK) + vfe->isr_ops.reset_ack(vfe); + + if (value1 & VFE_0_IRQ_STATUS_1_VIOLATION) + vfe->ops->violation_read(vfe); + + if (value1 & VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK) + vfe->isr_ops.halt_ack(vfe); + + for (i = VFE_LINE_RDI0; i < vfe->line_num; i++) + if (value0 & VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(i)) + vfe->isr_ops.reg_update(vfe, i); + + if (value0 & VFE_0_IRQ_STATUS_0_CAMIF_SOF) + vfe->isr_ops.sof(vfe, VFE_LINE_PIX); + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_RDI2; i++) + if (value1 & VFE_0_IRQ_STATUS_1_RDIn_SOF(i)) + vfe->isr_ops.sof(vfe, i); + + for (i = 0; i < MSM_VFE_COMPOSITE_IRQ_NUM; i++) + if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(i)) { + vfe->isr_ops.comp_done(vfe, i); + for (j = 0; j < ARRAY_SIZE(vfe->wm_output_map); j++) + if (vfe->wm_output_map[j] == VFE_LINE_PIX) + value0 &= ~VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(j); + } + + for (i = 0; i < MSM_VFE_IMAGE_MASTERS_NUM; i++) + if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(i)) + vfe->isr_ops.wm_done(vfe, i); + + return IRQ_HANDLED; +} + +static void vfe_isr_read(struct vfe_device *vfe, u32 *value0, u32 *value1) +{ + *value0 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_0); + *value1 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_1); + + writel_relaxed(*value0, vfe->base + VFE_0_IRQ_CLEAR_0); + writel_relaxed(*value1, vfe->base + VFE_0_IRQ_CLEAR_1); + + /* Enforce barrier between local & global IRQ clear */ + wmb(); + writel_relaxed(VFE_0_IRQ_CMD_GLOBAL_CLEAR, vfe->base + VFE_0_IRQ_CMD); +} + +/* + * vfe_pm_domain_off - Disable power domains specific to this VFE. + * @vfe: VFE Device + */ +static void vfe_pm_domain_off(struct vfe_device *vfe) +{ + struct camss *camss; + + if (!vfe) + return; + + camss = vfe->camss; + + device_link_del(camss->genpd_link[vfe->id]); +} + +/* + * vfe_pm_domain_on - Enable power domains specific to this VFE. + * @vfe: VFE Device + */ +static int vfe_pm_domain_on(struct vfe_device *vfe) +{ + struct camss *camss = vfe->camss; + enum vfe_line_id id = vfe->id; + + camss->genpd_link[id] = device_link_add(camss->dev, camss->genpd[id], DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE); + + if (!camss->genpd_link[id]) { + dev_err(vfe->camss->dev, "Failed to add VFE#%d to power domain\n", id); + return -EINVAL; + } + + return 0; +} + +static void vfe_violation_read(struct vfe_device *vfe) +{ + u32 violation = readl_relaxed(vfe->base + VFE_0_VIOLATION_STATUS); + + pr_err_ratelimited("VFE: violation = 0x%08x\n", violation); +} + +static const struct vfe_hw_ops_gen1 vfe_ops_gen1_4_7 = { + .bus_connect_wm_to_rdi = vfe_bus_connect_wm_to_rdi, + .bus_disconnect_wm_from_rdi = vfe_bus_disconnect_wm_from_rdi, + .bus_enable_wr_if = vfe_bus_enable_wr_if, + .bus_reload_wm = vfe_bus_reload_wm, + .camif_wait_for_stop = vfe_camif_wait_for_stop, + .enable_irq_common = vfe_enable_irq_common, + .enable_irq_pix_line = vfe_enable_irq_pix_line, + .enable_irq_wm_line = vfe_enable_irq_wm_line, + .get_ub_size = vfe_get_ub_size, + .halt_clear = vfe_halt_clear, + .halt_request = vfe_halt_request, + .set_camif_cfg = vfe_set_camif_cfg, + .set_camif_cmd = vfe_set_camif_cmd, + .set_cgc_override = vfe_set_cgc_override, + .set_clamp_cfg = vfe_set_clamp_cfg, + .set_crop_cfg = vfe_set_crop_cfg, + .set_demux_cfg = vfe_set_demux_cfg, + .set_ds = vfe_set_ds, + .set_module_cfg = vfe_set_module_cfg, + .set_qos = vfe_set_qos, + .set_rdi_cid = vfe_set_rdi_cid, + .set_realign_cfg = vfe_set_realign_cfg, + .set_scale_cfg = vfe_set_scale_cfg, + .set_xbar_cfg = vfe_set_xbar_cfg, + .wm_enable = vfe_wm_enable, + .wm_frame_based = vfe_wm_frame_based, + .wm_get_ping_pong_status = vfe_wm_get_ping_pong_status, + .wm_line_based = vfe_wm_line_based, + .wm_set_framedrop_pattern = vfe_wm_set_framedrop_pattern, + .wm_set_framedrop_period = vfe_wm_set_framedrop_period, + .wm_set_ping_addr = vfe_wm_set_ping_addr, + .wm_set_pong_addr = vfe_wm_set_pong_addr, + .wm_set_subsample = vfe_wm_set_subsample, + .wm_set_ub_cfg = vfe_wm_set_ub_cfg, +}; + +static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe) +{ + vfe->isr_ops = vfe_isr_ops_gen1; + vfe->ops_gen1 = &vfe_ops_gen1_4_7; + vfe->video_ops = vfe_video_ops_gen1; + + vfe->line_num = VFE_LINE_NUM_GEN1; +} + +const struct vfe_hw_ops vfe_ops_4_7 = { + .global_reset = vfe_global_reset, + .hw_version = vfe_hw_version, + .isr_read = vfe_isr_read, + .isr = vfe_isr, + .pm_domain_off = vfe_pm_domain_off, + .pm_domain_on = vfe_pm_domain_on, + .reg_update_clear = vfe_reg_update_clear, + .reg_update = vfe_reg_update, + .subdev_init = vfe_subdev_init, + .vfe_disable = vfe_gen1_disable, + .vfe_enable = vfe_gen1_enable, + .vfe_halt = vfe_gen1_halt, + .violation_read = vfe_violation_read, +}; diff --git a/drivers/media/platform/qcom/camss/camss-vfe-4-8.c b/drivers/media/platform/qcom/camss/camss-vfe-4-8.c new file mode 100644 index 000000000..7e6b62c93 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe-4-8.c @@ -0,0 +1,1194 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-vfe-4-8.c + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module v4.8 + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2021 Linaro Ltd. + */ + +#include +#include +#include +#include + +#include "camss.h" +#include "camss-vfe.h" +#include "camss-vfe-gen1.h" + +#define VFE_0_HW_VERSION 0x000 + +#define VFE_0_GLOBAL_RESET_CMD 0x018 +#define VFE_0_GLOBAL_RESET_CMD_CORE BIT(0) +#define VFE_0_GLOBAL_RESET_CMD_CAMIF BIT(1) +#define VFE_0_GLOBAL_RESET_CMD_BUS BIT(2) +#define VFE_0_GLOBAL_RESET_CMD_BUS_BDG BIT(3) +#define VFE_0_GLOBAL_RESET_CMD_REGISTER BIT(4) +#define VFE_0_GLOBAL_RESET_CMD_PM BIT(5) +#define VFE_0_GLOBAL_RESET_CMD_BUS_MISR BIT(6) +#define VFE_0_GLOBAL_RESET_CMD_TESTGEN BIT(7) +#define VFE_0_GLOBAL_RESET_CMD_DSP BIT(8) +#define VFE_0_GLOBAL_RESET_CMD_IDLE_CGC BIT(9) + +#define VFE_0_MODULE_LENS_EN 0x040 +#define VFE_0_MODULE_LENS_EN_DEMUX BIT(2) +#define VFE_0_MODULE_LENS_EN_CHROMA_UPSAMPLE BIT(3) + +#define VFE_0_MODULE_ZOOM_EN 0x04c +#define VFE_0_MODULE_ZOOM_EN_SCALE_ENC BIT(1) +#define VFE_0_MODULE_ZOOM_EN_CROP_ENC BIT(2) +#define VFE_0_MODULE_ZOOM_EN_REALIGN_BUF BIT(9) + +#define VFE_0_CORE_CFG 0x050 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR 0x4 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB 0x5 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY 0x6 +#define VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY 0x7 +#define VFE_0_CORE_CFG_COMPOSITE_REG_UPDATE_EN BIT(4) + +#define VFE_0_IRQ_CMD 0x058 +#define VFE_0_IRQ_CMD_GLOBAL_CLEAR BIT(0) + +#define VFE_0_IRQ_MASK_0 0x05c +#define VFE_0_IRQ_MASK_0_CAMIF_SOF BIT(0) +#define VFE_0_IRQ_MASK_0_CAMIF_EOF BIT(1) +#define VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n) BIT((n) + 5) +#define VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(n) \ + ((n) == VFE_LINE_PIX ? BIT(4) : VFE_0_IRQ_MASK_0_RDIn_REG_UPDATE(n)) +#define VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(n) BIT((n) + 8) +#define VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(n) BIT((n) + 25) +#define VFE_0_IRQ_MASK_0_RESET_ACK BIT(31) +#define VFE_0_IRQ_MASK_1 0x060 +#define VFE_0_IRQ_MASK_1_CAMIF_ERROR BIT(0) +#define VFE_0_IRQ_MASK_1_VIOLATION BIT(7) +#define VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK BIT(8) +#define VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(n) BIT((n) + 9) +#define VFE_0_IRQ_MASK_1_RDIn_SOF(n) BIT((n) + 29) + +#define VFE_0_IRQ_CLEAR_0 0x064 +#define VFE_0_IRQ_CLEAR_1 0x068 + +#define VFE_0_IRQ_STATUS_0 0x06c +#define VFE_0_IRQ_STATUS_0_CAMIF_SOF BIT(0) +#define VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n) BIT((n) + 5) +#define VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(n) \ + ((n) == VFE_LINE_PIX ? BIT(4) : VFE_0_IRQ_STATUS_0_RDIn_REG_UPDATE(n)) +#define VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(n) BIT((n) + 8) +#define VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(n) BIT((n) + 25) +#define VFE_0_IRQ_STATUS_0_RESET_ACK BIT(31) +#define VFE_0_IRQ_STATUS_1 0x070 +#define VFE_0_IRQ_STATUS_1_VIOLATION BIT(7) +#define VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK BIT(8) +#define VFE_0_IRQ_STATUS_1_RDIn_SOF(n) BIT((n) + 29) + +#define VFE_0_IRQ_COMPOSITE_MASK_0 0x074 +#define VFE_0_VIOLATION_STATUS 0x07c + +#define VFE_0_BUS_CMD 0x80 +#define VFE_0_BUS_CMD_Mx_RLD_CMD(x) BIT(x) + +#define VFE_0_BUS_CFG 0x084 + +#define VFE_0_BUS_XBAR_CFG_x(x) (0x90 + 0x4 * ((x) / 2)) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN BIT(2) +#define VFE_0_BUS_XBAR_CFG_x_M_REALIGN_BUF_EN BIT(3) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTRA (0x1 << 4) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER (0x2 << 4) +#define VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA (0x3 << 4) +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT 8 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA 0x0 +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 0xc +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 0xd +#define VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 0xe + +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG(n) (0x0a0 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_CFG_WR_PATH_SHIFT 0 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(n) (0x0a4 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(n) (0x0ac + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(n) (0x0b4 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_BASED_SHIFT 1 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT 2 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK (0x1f << 2) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(n) (0x0b8 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT 16 +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(n) (0x0bc + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(n) (0x0c0 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(n) \ + (0x0c4 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(n) \ + (0x0c8 + 0x2c * (n)) +#define VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF 0xffffffff + +#define VFE_0_BUS_PING_PONG_STATUS 0x338 + +#define VFE_0_BUS_BDG_CMD 0x400 +#define VFE_0_BUS_BDG_CMD_HALT_REQ 1 + +#define VFE_0_BUS_BDG_QOS_CFG_0 0x404 +#define VFE_0_BUS_BDG_QOS_CFG_0_CFG 0xaaa5aaa5 +#define VFE_0_BUS_BDG_QOS_CFG_1 0x408 +#define VFE_0_BUS_BDG_QOS_CFG_2 0x40c +#define VFE_0_BUS_BDG_QOS_CFG_3 0x410 +#define VFE_0_BUS_BDG_QOS_CFG_3_CFG 0xaa55aaa5 +#define VFE_0_BUS_BDG_QOS_CFG_4 0x414 +#define VFE_0_BUS_BDG_QOS_CFG_4_CFG 0xaa55aa55 +#define VFE_0_BUS_BDG_QOS_CFG_5 0x418 +#define VFE_0_BUS_BDG_QOS_CFG_6 0x41c +#define VFE_0_BUS_BDG_QOS_CFG_7 0x420 +#define VFE_0_BUS_BDG_QOS_CFG_7_CFG 0x0005aa55 + +#define VFE_0_BUS_BDG_DS_CFG_0 0x424 +#define VFE_0_BUS_BDG_DS_CFG_0_CFG 0xcccc1111 +#define VFE_0_BUS_BDG_DS_CFG_1 0x428 +#define VFE_0_BUS_BDG_DS_CFG_2 0x42c +#define VFE_0_BUS_BDG_DS_CFG_3 0x430 +#define VFE_0_BUS_BDG_DS_CFG_4 0x434 +#define VFE_0_BUS_BDG_DS_CFG_5 0x438 +#define VFE_0_BUS_BDG_DS_CFG_6 0x43c +#define VFE_0_BUS_BDG_DS_CFG_7 0x440 +#define VFE_0_BUS_BDG_DS_CFG_8 0x444 +#define VFE_0_BUS_BDG_DS_CFG_9 0x448 +#define VFE_0_BUS_BDG_DS_CFG_10 0x44c +#define VFE_0_BUS_BDG_DS_CFG_11 0x450 +#define VFE_0_BUS_BDG_DS_CFG_12 0x454 +#define VFE_0_BUS_BDG_DS_CFG_13 0x458 +#define VFE_0_BUS_BDG_DS_CFG_14 0x45c +#define VFE_0_BUS_BDG_DS_CFG_15 0x460 +#define VFE_0_BUS_BDG_DS_CFG_16 0x464 +#define VFE_0_BUS_BDG_DS_CFG_16_CFG 0x00000110 + +#define VFE_0_RDI_CFG_x(x) (0x46c + (0x4 * (x))) +#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT 28 +#define VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK (0xf << 28) +#define VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT 4 +#define VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK (0xf << 4) +#define VFE_0_RDI_CFG_x_RDI_EN_BIT BIT(2) +#define VFE_0_RDI_CFG_x_MIPI_EN_BITS 0x3 + +#define VFE_0_CAMIF_CMD 0x478 +#define VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY 0 +#define VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY 1 +#define VFE_0_CAMIF_CMD_NO_CHANGE 3 +#define VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS BIT(2) +#define VFE_0_CAMIF_CFG 0x47c +#define VFE_0_CAMIF_CFG_VFE_OUTPUT_EN BIT(6) +#define VFE_0_CAMIF_FRAME_CFG 0x484 +#define VFE_0_CAMIF_WINDOW_WIDTH_CFG 0x488 +#define VFE_0_CAMIF_WINDOW_HEIGHT_CFG 0x48c +#define VFE_0_CAMIF_SUBSAMPLE_CFG 0x490 +#define VFE_0_CAMIF_IRQ_FRAMEDROP_PATTERN 0x498 +#define VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN 0x49c +#define VFE_0_CAMIF_STATUS 0x4a4 +#define VFE_0_CAMIF_STATUS_HALT BIT(31) + +#define VFE_0_REG_UPDATE 0x4ac +#define VFE_0_REG_UPDATE_RDIn(n) BIT(1 + (n)) +#define VFE_0_REG_UPDATE_line_n(n) \ + ((n) == VFE_LINE_PIX ? 1 : VFE_0_REG_UPDATE_RDIn(n)) + +#define VFE_0_DEMUX_CFG 0x560 +#define VFE_0_DEMUX_CFG_PERIOD 0x3 +#define VFE_0_DEMUX_GAIN_0 0x564 +#define VFE_0_DEMUX_GAIN_0_CH0_EVEN (0x80 << 0) +#define VFE_0_DEMUX_GAIN_0_CH0_ODD (0x80 << 16) +#define VFE_0_DEMUX_GAIN_1 0x568 +#define VFE_0_DEMUX_GAIN_1_CH1 (0x80 << 0) +#define VFE_0_DEMUX_GAIN_1_CH2 (0x80 << 16) +#define VFE_0_DEMUX_EVEN_CFG 0x574 +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV 0x9cac +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU 0xac9c +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY 0xc9ca +#define VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY 0xcac9 +#define VFE_0_DEMUX_ODD_CFG 0x578 +#define VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV 0x9cac +#define VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU 0xac9c +#define VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY 0xc9ca +#define VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY 0xcac9 + +#define VFE_0_SCALE_ENC_Y_CFG 0x91c +#define VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE 0x920 +#define VFE_0_SCALE_ENC_Y_H_PHASE 0x924 +#define VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE 0x934 +#define VFE_0_SCALE_ENC_Y_V_PHASE 0x938 +#define VFE_0_SCALE_ENC_CBCR_CFG 0x948 +#define VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE 0x94c +#define VFE_0_SCALE_ENC_CBCR_H_PHASE 0x950 +#define VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE 0x960 +#define VFE_0_SCALE_ENC_CBCR_V_PHASE 0x964 + +#define VFE_0_CROP_ENC_Y_WIDTH 0x974 +#define VFE_0_CROP_ENC_Y_HEIGHT 0x978 +#define VFE_0_CROP_ENC_CBCR_WIDTH 0x97c +#define VFE_0_CROP_ENC_CBCR_HEIGHT 0x980 + +#define VFE_0_CLAMP_ENC_MAX_CFG 0x984 +#define VFE_0_CLAMP_ENC_MAX_CFG_CH0 (0xff << 0) +#define VFE_0_CLAMP_ENC_MAX_CFG_CH1 (0xff << 8) +#define VFE_0_CLAMP_ENC_MAX_CFG_CH2 (0xff << 16) +#define VFE_0_CLAMP_ENC_MIN_CFG 0x988 +#define VFE_0_CLAMP_ENC_MIN_CFG_CH0 (0x0 << 0) +#define VFE_0_CLAMP_ENC_MIN_CFG_CH1 (0x0 << 8) +#define VFE_0_CLAMP_ENC_MIN_CFG_CH2 (0x0 << 16) + +#define VFE_0_REALIGN_BUF_CFG 0xaac +#define VFE_0_REALIGN_BUF_CFG_CB_ODD_PIXEL BIT(2) +#define VFE_0_REALIGN_BUF_CFG_CR_ODD_PIXEL BIT(3) +#define VFE_0_REALIGN_BUF_CFG_HSUB_ENABLE BIT(4) + +#define VFE_0_BUS_IMAGE_MASTER_CMD 0xcec +#define VFE_0_BUS_IMAGE_MASTER_n_SHIFT(x) (2 * (x)) + +#define CAMIF_TIMEOUT_SLEEP_US 1000 +#define CAMIF_TIMEOUT_ALL_US 1000000 + +#define MSM_VFE_VFE0_UB_SIZE 2047 +#define MSM_VFE_VFE0_UB_SIZE_RDI (MSM_VFE_VFE0_UB_SIZE / 3) +#define MSM_VFE_VFE1_UB_SIZE 1535 +#define MSM_VFE_VFE1_UB_SIZE_RDI (MSM_VFE_VFE1_UB_SIZE / 3) + +static u32 vfe_hw_version(struct vfe_device *vfe) +{ + u32 hw_version = readl_relaxed(vfe->base + VFE_0_HW_VERSION); + + dev_dbg(vfe->camss->dev, "VFE HW Version = 0x%08x\n", hw_version); + + return hw_version; +} + +static inline void vfe_reg_clr(struct vfe_device *vfe, u32 reg, u32 clr_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits & ~clr_bits, vfe->base + reg); +} + +static inline void vfe_reg_set(struct vfe_device *vfe, u32 reg, u32 set_bits) +{ + u32 bits = readl_relaxed(vfe->base + reg); + + writel_relaxed(bits | set_bits, vfe->base + reg); +} + +static void vfe_global_reset(struct vfe_device *vfe) +{ + u32 reset_bits = VFE_0_GLOBAL_RESET_CMD_IDLE_CGC | + VFE_0_GLOBAL_RESET_CMD_DSP | + VFE_0_GLOBAL_RESET_CMD_TESTGEN | + VFE_0_GLOBAL_RESET_CMD_BUS_MISR | + VFE_0_GLOBAL_RESET_CMD_PM | + VFE_0_GLOBAL_RESET_CMD_REGISTER | + VFE_0_GLOBAL_RESET_CMD_BUS_BDG | + VFE_0_GLOBAL_RESET_CMD_BUS | + VFE_0_GLOBAL_RESET_CMD_CAMIF | + VFE_0_GLOBAL_RESET_CMD_CORE; + + writel_relaxed(BIT(31), vfe->base + VFE_0_IRQ_MASK_0); + + /* Enforce barrier between IRQ mask setup and global reset */ + wmb(); + writel_relaxed(reset_bits, vfe->base + VFE_0_GLOBAL_RESET_CMD); +} + +static void vfe_halt_request(struct vfe_device *vfe) +{ + writel_relaxed(VFE_0_BUS_BDG_CMD_HALT_REQ, + vfe->base + VFE_0_BUS_BDG_CMD); +} + +static void vfe_halt_clear(struct vfe_device *vfe) +{ + writel_relaxed(0x0, vfe->base + VFE_0_BUS_BDG_CMD); +} + +static void vfe_wm_frame_based(struct vfe_device *vfe, u8 wm, u8 enable) +{ + if (enable) + vfe_reg_set(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_BASED_SHIFT); + else + vfe_reg_clr(vfe, VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm), + 1 << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_BASED_SHIFT); +} + +#define CALC_WORD(width, M, N) (((width) * (M) + (N) - 1) / (N)) + +static int vfe_word_per_line_by_pixel(u32 format, u32 pixel_per_line) +{ + int val = 0; + + switch (format) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + val = CALC_WORD(pixel_per_line, 1, 8); + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + val = CALC_WORD(pixel_per_line, 2, 8); + break; + } + + return val; +} + +static int vfe_word_per_line_by_bytes(u32 bytes_per_line) +{ + return CALC_WORD(bytes_per_line, 1, 8); +} + +static void vfe_get_wm_sizes(struct v4l2_pix_format_mplane *pix, u8 plane, + u16 *width, u16 *height, u16 *bytesperline) +{ + *width = pix->width; + *height = pix->height; + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + *bytesperline = pix->plane_fmt[0].bytesperline; + if (plane == 1) + *height /= 2; + break; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + *bytesperline = pix->plane_fmt[0].bytesperline; + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_UYVY: + *bytesperline = pix->plane_fmt[plane].bytesperline; + break; + } +} + +static void vfe_wm_line_based(struct vfe_device *vfe, u32 wm, + struct v4l2_pix_format_mplane *pix, + u8 plane, u32 enable) +{ + u32 reg; + + if (enable) { + u16 width = 0, height = 0, bytesperline = 0, wpl; + + vfe_get_wm_sizes(pix, plane, &width, &height, &bytesperline); + + wpl = vfe_word_per_line_by_pixel(pix->pixelformat, width); + + reg = height - 1; + reg |= ((wpl + 3) / 4 - 1) << 16; + + writel_relaxed(reg, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); + + wpl = vfe_word_per_line_by_bytes(bytesperline); + + reg = 0x3; + reg |= (height - 1) << 2; + reg |= ((wpl + 1) / 2) << 16; + + writel_relaxed(reg, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); + } else { + writel_relaxed(0, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_IMAGE_SIZE(wm)); + writel_relaxed(0, vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_BUFFER_CFG(wm)); + } +} + +static void vfe_wm_set_framedrop_period(struct vfe_device *vfe, u8 wm, u8 per) +{ + u32 reg; + + reg = readl_relaxed(vfe->base + + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); + + reg &= ~(VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK); + + reg |= (per << VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_SHIFT) + & VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG_FRM_DROP_PER_MASK; + + writel_relaxed(reg, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_ADDR_CFG(wm)); +} + +static void vfe_wm_set_framedrop_pattern(struct vfe_device *vfe, u8 wm, + u32 pattern) +{ + writel_relaxed(pattern, vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_FRAMEDROP_PATTERN(wm)); +} + +static void vfe_wm_set_ub_cfg(struct vfe_device *vfe, u8 wm, + u16 offset, u16 depth) +{ + u32 reg; + + reg = (offset << VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG_OFFSET_SHIFT) | + depth; + writel_relaxed(reg, vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_UB_CFG(wm)); +} + +static void vfe_bus_reload_wm(struct vfe_device *vfe, u8 wm) +{ + /* Enforce barrier between any outstanding register write */ + wmb(); + + writel_relaxed(VFE_0_BUS_CMD_Mx_RLD_CMD(wm), vfe->base + VFE_0_BUS_CMD); + + /* Use barrier to make sure bus reload is issued before anything else */ + wmb(); +} + +static void vfe_wm_set_ping_addr(struct vfe_device *vfe, u8 wm, u32 addr) +{ + writel_relaxed(addr, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PING_ADDR(wm)); +} + +static void vfe_wm_set_pong_addr(struct vfe_device *vfe, u8 wm, u32 addr) +{ + writel_relaxed(addr, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_PONG_ADDR(wm)); +} + +static int vfe_wm_get_ping_pong_status(struct vfe_device *vfe, u8 wm) +{ + u32 reg; + + reg = readl_relaxed(vfe->base + VFE_0_BUS_PING_PONG_STATUS); + + return (reg >> wm) & 0x1; +} + +static void vfe_bus_enable_wr_if(struct vfe_device *vfe, u8 enable) +{ + if (enable) + writel_relaxed(0x101, vfe->base + VFE_0_BUS_CFG); + else + writel_relaxed(0, vfe->base + VFE_0_BUS_CFG); +} + +static void vfe_bus_connect_wm_to_rdi(struct vfe_device *vfe, u8 wm, + enum vfe_line_id id) +{ + u32 reg; + + reg = VFE_0_RDI_CFG_x_MIPI_EN_BITS; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), reg); + + reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; + reg |= ((3 * id) << VFE_0_RDI_CFG_x_RDI_STREAM_SEL_SHIFT) & + VFE_0_RDI_CFG_x_RDI_STREAM_SEL_MASK; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), reg); + + switch (id) { + case VFE_LINE_RDI0: + default: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI1: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI2: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + } + + if (wm % 2 == 1) + reg <<= 16; + + vfe_reg_set(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); +} + +static void vfe_wm_set_subsample(struct vfe_device *vfe, u8 wm) +{ + writel_relaxed(VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN_DEF, + vfe->base + VFE_0_BUS_IMAGE_MASTER_n_WR_IRQ_SUBSAMPLE_PATTERN(wm)); +} + +static void vfe_bus_disconnect_wm_from_rdi(struct vfe_device *vfe, u8 wm, + enum vfe_line_id id) +{ + u32 reg; + + reg = VFE_0_RDI_CFG_x_RDI_EN_BIT; + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), reg); + + switch (id) { + case VFE_LINE_RDI0: + default: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI0 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI1: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI1 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + case VFE_LINE_RDI2: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_VAL_RDI2 << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + break; + } + + if (wm % 2 == 1) + reg <<= 16; + + vfe_reg_clr(vfe, VFE_0_BUS_XBAR_CFG_x(wm), reg); +} + +static void vfe_set_xbar_cfg(struct vfe_device *vfe, struct vfe_output *output, + u8 enable) +{ + struct vfe_line *line = container_of(output, struct vfe_line, output); + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + + switch (p) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + reg = VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_LUMA << + VFE_0_BUS_XBAR_CFG_x_M_SINGLE_STREAM_SEL_SHIFT; + + if (output->wm_idx[0] % 2 == 1) + reg <<= 16; + + if (enable) + vfe_reg_set(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[0]), + reg); + else + vfe_reg_clr(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[0]), + reg); + + reg = VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV16) + reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA; + + if (output->wm_idx[1] % 2 == 1) + reg <<= 16; + + if (enable) + vfe_reg_set(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[1]), + reg); + else + vfe_reg_clr(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[1]), + reg); + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_UYVY: + reg = VFE_0_BUS_XBAR_CFG_x_M_REALIGN_BUF_EN; + reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_EN; + + if (p == V4L2_PIX_FMT_YUYV || p == V4L2_PIX_FMT_YVYU) + reg |= VFE_0_BUS_XBAR_CFG_x_M_PAIR_STREAM_SWAP_INTER_INTRA; + + if (output->wm_idx[0] % 2 == 1) + reg <<= 16; + + if (enable) + vfe_reg_set(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[0]), + reg); + else + vfe_reg_clr(vfe, + VFE_0_BUS_XBAR_CFG_x(output->wm_idx[0]), + reg); + break; + default: + break; + } +} + +static void vfe_set_realign_cfg(struct vfe_device *vfe, struct vfe_line *line, + u8 enable) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 val = VFE_0_MODULE_ZOOM_EN_REALIGN_BUF; + + if (p != V4L2_PIX_FMT_YUYV && p != V4L2_PIX_FMT_YVYU && + p != V4L2_PIX_FMT_VYUY && p != V4L2_PIX_FMT_UYVY) + return; + + if (enable) { + vfe_reg_set(vfe, VFE_0_MODULE_ZOOM_EN, val); + } else { + vfe_reg_clr(vfe, VFE_0_MODULE_ZOOM_EN, val); + return; + } + + val = VFE_0_REALIGN_BUF_CFG_HSUB_ENABLE; + + if (p == V4L2_PIX_FMT_UYVY || p == V4L2_PIX_FMT_YUYV) + val |= VFE_0_REALIGN_BUF_CFG_CR_ODD_PIXEL; + else + val |= VFE_0_REALIGN_BUF_CFG_CB_ODD_PIXEL; + + writel_relaxed(val, vfe->base + VFE_0_REALIGN_BUF_CFG); +} + +static void vfe_set_rdi_cid(struct vfe_device *vfe, enum vfe_line_id id, u8 cid) +{ + vfe_reg_clr(vfe, VFE_0_RDI_CFG_x(id), + VFE_0_RDI_CFG_x_RDI_M0_SEL_MASK); + + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(id), + cid << VFE_0_RDI_CFG_x_RDI_M0_SEL_SHIFT); +} + +static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + vfe->reg_update |= VFE_0_REG_UPDATE_line_n(line_id); + + /* Enforce barrier between line update and commit */ + wmb(); + + writel_relaxed(vfe->reg_update, vfe->base + VFE_0_REG_UPDATE); + + /* Make sure register update is issued before further reg writes */ + wmb(); +} + +static inline void vfe_reg_update_clear(struct vfe_device *vfe, + enum vfe_line_id line_id) +{ + vfe->reg_update &= ~VFE_0_REG_UPDATE_line_n(line_id); +} + +static void vfe_enable_irq_wm_line(struct vfe_device *vfe, u8 wm, + enum vfe_line_id line_id, u8 enable) +{ + u32 irq_en0 = VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(wm) | + VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); + u32 irq_en1 = VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(wm) | + VFE_0_IRQ_MASK_1_RDIn_SOF(line_id); + + if (enable) { + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); + } else { + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); + } +} + +static void vfe_enable_irq_pix_line(struct vfe_device *vfe, u8 comp, + enum vfe_line_id line_id, u8 enable) +{ + struct vfe_output *output = &vfe->line[line_id].output; + unsigned int i; + u32 irq_en0; + u32 irq_en1; + u32 comp_mask = 0; + + irq_en0 = VFE_0_IRQ_MASK_0_CAMIF_SOF; + irq_en0 |= VFE_0_IRQ_MASK_0_CAMIF_EOF; + irq_en0 |= VFE_0_IRQ_MASK_0_IMAGE_COMPOSITE_DONE_n(comp); + irq_en0 |= VFE_0_IRQ_MASK_0_line_n_REG_UPDATE(line_id); + irq_en1 = VFE_0_IRQ_MASK_1_CAMIF_ERROR; + for (i = 0; i < output->wm_num; i++) { + irq_en1 |= VFE_0_IRQ_MASK_1_IMAGE_MASTER_n_BUS_OVERFLOW(output->wm_idx[i]); + comp_mask |= (1 << output->wm_idx[i]) << comp * 8; + } + + if (enable) { + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); + vfe_reg_set(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); + } else { + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_clr(vfe, VFE_0_IRQ_MASK_1, irq_en1); + vfe_reg_clr(vfe, VFE_0_IRQ_COMPOSITE_MASK_0, comp_mask); + } +} + +static void vfe_enable_irq_common(struct vfe_device *vfe) +{ + u32 irq_en0 = VFE_0_IRQ_MASK_0_RESET_ACK; + u32 irq_en1 = VFE_0_IRQ_MASK_1_VIOLATION | + VFE_0_IRQ_MASK_1_BUS_BDG_HALT_ACK; + + vfe_reg_set(vfe, VFE_0_IRQ_MASK_0, irq_en0); + vfe_reg_set(vfe, VFE_0_IRQ_MASK_1, irq_en1); +} + +static void vfe_set_demux_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 val, even_cfg, odd_cfg; + + writel_relaxed(VFE_0_DEMUX_CFG_PERIOD, vfe->base + VFE_0_DEMUX_CFG); + + val = VFE_0_DEMUX_GAIN_0_CH0_EVEN | VFE_0_DEMUX_GAIN_0_CH0_ODD; + writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_0); + + val = VFE_0_DEMUX_GAIN_1_CH1 | VFE_0_DEMUX_GAIN_1_CH2; + writel_relaxed(val, vfe->base + VFE_0_DEMUX_GAIN_1); + + switch (line->fmt[MSM_VFE_PAD_SINK].code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YUYV; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YUYV; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_YVYU; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_YVYU; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_UYVY; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_UYVY; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + even_cfg = VFE_0_DEMUX_EVEN_CFG_PATTERN_VYUY; + odd_cfg = VFE_0_DEMUX_ODD_CFG_PATTERN_VYUY; + break; + } + + writel_relaxed(even_cfg, vfe->base + VFE_0_DEMUX_EVEN_CFG); + writel_relaxed(odd_cfg, vfe->base + VFE_0_DEMUX_ODD_CFG); +} + +static void vfe_set_scale_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + u16 input, output; + u8 interp_reso; + u32 phase_mult; + + writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_Y_CFG); + + input = line->fmt[MSM_VFE_PAD_SINK].width - 1; + output = line->compose.width - 1; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (14 + interp_reso)) / output; + reg = (interp_reso << 28) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_H_PHASE); + + input = line->fmt[MSM_VFE_PAD_SINK].height - 1; + output = line->compose.height - 1; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (14 + interp_reso)) / output; + reg = (interp_reso << 28) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_Y_V_PHASE); + + writel_relaxed(0x3, vfe->base + VFE_0_SCALE_ENC_CBCR_CFG); + + input = line->fmt[MSM_VFE_PAD_SINK].width - 1; + output = line->compose.width / 2 - 1; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (14 + interp_reso)) / output; + reg = (interp_reso << 28) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_H_PHASE); + + input = line->fmt[MSM_VFE_PAD_SINK].height - 1; + output = line->compose.height - 1; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) + output = line->compose.height / 2 - 1; + reg = (output << 16) | input; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_IMAGE_SIZE); + + interp_reso = vfe_calc_interp_reso(input, output); + phase_mult = input * (1 << (14 + interp_reso)) / output; + reg = (interp_reso << 28) | phase_mult; + writel_relaxed(reg, vfe->base + VFE_0_SCALE_ENC_CBCR_V_PHASE); +} + +static void vfe_set_crop_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 p = line->video_out.active_fmt.fmt.pix_mp.pixelformat; + u32 reg; + u16 first, last; + + first = line->crop.left; + last = line->crop.left + line->crop.width - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_WIDTH); + + first = line->crop.top; + last = line->crop.top + line->crop.height - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_Y_HEIGHT); + + first = line->crop.left / 2; + last = line->crop.left / 2 + line->crop.width / 2 - 1; + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_WIDTH); + + first = line->crop.top; + last = line->crop.top + line->crop.height - 1; + if (p == V4L2_PIX_FMT_NV12 || p == V4L2_PIX_FMT_NV21) { + first = line->crop.top / 2; + last = line->crop.top / 2 + line->crop.height / 2 - 1; + } + reg = (first << 16) | last; + writel_relaxed(reg, vfe->base + VFE_0_CROP_ENC_CBCR_HEIGHT); +} + +static void vfe_set_clamp_cfg(struct vfe_device *vfe) +{ + u32 val = VFE_0_CLAMP_ENC_MAX_CFG_CH0 | + VFE_0_CLAMP_ENC_MAX_CFG_CH1 | + VFE_0_CLAMP_ENC_MAX_CFG_CH2; + + writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MAX_CFG); + + val = VFE_0_CLAMP_ENC_MIN_CFG_CH0 | + VFE_0_CLAMP_ENC_MIN_CFG_CH1 | + VFE_0_CLAMP_ENC_MIN_CFG_CH2; + + writel_relaxed(val, vfe->base + VFE_0_CLAMP_ENC_MIN_CFG); +} + +static void vfe_set_cgc_override(struct vfe_device *vfe, u8 wm, u8 enable) +{ + /* empty */ +} + +static void vfe_set_camif_cfg(struct vfe_device *vfe, struct vfe_line *line) +{ + u32 val; + + switch (line->fmt[MSM_VFE_PAD_SINK].code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCBYCR; + break; + case MEDIA_BUS_FMT_YVYU8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_YCRYCB; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + default: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_CBYCRY; + break; + case MEDIA_BUS_FMT_VYUY8_2X8: + val = VFE_0_CORE_CFG_PIXEL_PATTERN_CRYCBY; + break; + } + + val |= VFE_0_CORE_CFG_COMPOSITE_REG_UPDATE_EN; + writel_relaxed(val, vfe->base + VFE_0_CORE_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].width * 2 - 1; + val |= (line->fmt[MSM_VFE_PAD_SINK].height - 1) << 16; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_FRAME_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].width * 2 - 1; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_WIDTH_CFG); + + val = line->fmt[MSM_VFE_PAD_SINK].height - 1; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_WINDOW_HEIGHT_CFG); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_SUBSAMPLE_CFG); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_IRQ_FRAMEDROP_PATTERN); + + val = 0xffffffff; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_IRQ_SUBSAMPLE_PATTERN); + + val = VFE_0_RDI_CFG_x_MIPI_EN_BITS; + vfe_reg_set(vfe, VFE_0_RDI_CFG_x(0), val); + + val = VFE_0_CAMIF_CFG_VFE_OUTPUT_EN; + writel_relaxed(val, vfe->base + VFE_0_CAMIF_CFG); +} + +static void vfe_set_camif_cmd(struct vfe_device *vfe, u8 enable) +{ + u32 cmd; + + cmd = VFE_0_CAMIF_CMD_CLEAR_CAMIF_STATUS | VFE_0_CAMIF_CMD_NO_CHANGE; + writel_relaxed(cmd, vfe->base + VFE_0_CAMIF_CMD); + + /* Make sure camif command is issued written before it is changed again */ + wmb(); + + if (enable) + cmd = VFE_0_CAMIF_CMD_ENABLE_FRAME_BOUNDARY; + else + cmd = VFE_0_CAMIF_CMD_DISABLE_FRAME_BOUNDARY; + + writel_relaxed(cmd, vfe->base + VFE_0_CAMIF_CMD); +} + +static void vfe_set_module_cfg(struct vfe_device *vfe, u8 enable) +{ + u32 val_lens = VFE_0_MODULE_LENS_EN_DEMUX | + VFE_0_MODULE_LENS_EN_CHROMA_UPSAMPLE; + u32 val_zoom = VFE_0_MODULE_ZOOM_EN_SCALE_ENC | + VFE_0_MODULE_ZOOM_EN_CROP_ENC; + + if (enable) { + vfe_reg_set(vfe, VFE_0_MODULE_LENS_EN, val_lens); + vfe_reg_set(vfe, VFE_0_MODULE_ZOOM_EN, val_zoom); + } else { + vfe_reg_clr(vfe, VFE_0_MODULE_LENS_EN, val_lens); + vfe_reg_clr(vfe, VFE_0_MODULE_ZOOM_EN, val_zoom); + } +} + +static int vfe_camif_wait_for_stop(struct vfe_device *vfe, struct device *dev) +{ + u32 val; + int ret; + + ret = readl_poll_timeout(vfe->base + VFE_0_CAMIF_STATUS, + val, + (val & VFE_0_CAMIF_STATUS_HALT), + CAMIF_TIMEOUT_SLEEP_US, + CAMIF_TIMEOUT_ALL_US); + if (ret < 0) + dev_err(dev, "%s: camif stop timeout\n", __func__); + + return ret; +} + +/* + * vfe_isr - VFE module interrupt handler + * @irq: Interrupt line + * @dev: VFE device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t vfe_isr(int irq, void *dev) +{ + struct vfe_device *vfe = dev; + u32 value0, value1; + int i, j; + + vfe->ops->isr_read(vfe, &value0, &value1); + + dev_dbg(vfe->camss->dev, "VFE: status0 = 0x%08x, status1 = 0x%08x\n", + value0, value1); + + if (value0 & VFE_0_IRQ_STATUS_0_RESET_ACK) + vfe->isr_ops.reset_ack(vfe); + + if (value1 & VFE_0_IRQ_STATUS_1_VIOLATION) + vfe->ops->violation_read(vfe); + + if (value1 & VFE_0_IRQ_STATUS_1_BUS_BDG_HALT_ACK) + vfe->isr_ops.halt_ack(vfe); + + for (i = VFE_LINE_RDI0; i < vfe->line_num; i++) + if (value0 & VFE_0_IRQ_STATUS_0_line_n_REG_UPDATE(i)) + vfe->isr_ops.reg_update(vfe, i); + + if (value0 & VFE_0_IRQ_STATUS_0_CAMIF_SOF) + vfe->isr_ops.sof(vfe, VFE_LINE_PIX); + + for (i = VFE_LINE_RDI0; i <= VFE_LINE_RDI2; i++) + if (value1 & VFE_0_IRQ_STATUS_1_RDIn_SOF(i)) + vfe->isr_ops.sof(vfe, i); + + for (i = 0; i < MSM_VFE_COMPOSITE_IRQ_NUM; i++) + if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_COMPOSITE_DONE_n(i)) { + vfe->isr_ops.comp_done(vfe, i); + for (j = 0; j < ARRAY_SIZE(vfe->wm_output_map); j++) + if (vfe->wm_output_map[j] == VFE_LINE_PIX) + value0 &= ~VFE_0_IRQ_MASK_0_IMAGE_MASTER_n_PING_PONG(j); + } + + for (i = 0; i < MSM_VFE_IMAGE_MASTERS_NUM; i++) + if (value0 & VFE_0_IRQ_STATUS_0_IMAGE_MASTER_n_PING_PONG(i)) + vfe->isr_ops.wm_done(vfe, i); + + return IRQ_HANDLED; +} + +static u16 vfe_get_ub_size(u8 vfe_id) +{ + /* On VFE4.8 the ub-size is the same on both instances */ + return MSM_VFE_VFE0_UB_SIZE_RDI; +} + +static void vfe_wm_enable(struct vfe_device *vfe, u8 wm, u8 enable) +{ + if (enable) + writel_relaxed(2 << VFE_0_BUS_IMAGE_MASTER_n_SHIFT(wm), + vfe->base + VFE_0_BUS_IMAGE_MASTER_CMD); + else + writel_relaxed(1 << VFE_0_BUS_IMAGE_MASTER_n_SHIFT(wm), + vfe->base + VFE_0_BUS_IMAGE_MASTER_CMD); + + /* The WM must be enabled before sending other commands */ + wmb(); +} + +static void vfe_set_qos(struct vfe_device *vfe) +{ + u32 val = VFE_0_BUS_BDG_QOS_CFG_0_CFG; + u32 val3 = VFE_0_BUS_BDG_QOS_CFG_3_CFG; + u32 val4 = VFE_0_BUS_BDG_QOS_CFG_4_CFG; + u32 val7 = VFE_0_BUS_BDG_QOS_CFG_7_CFG; + + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_0); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_1); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_QOS_CFG_2); + writel_relaxed(val3, vfe->base + VFE_0_BUS_BDG_QOS_CFG_3); + writel_relaxed(val4, vfe->base + VFE_0_BUS_BDG_QOS_CFG_4); + writel_relaxed(val4, vfe->base + VFE_0_BUS_BDG_QOS_CFG_5); + writel_relaxed(val4, vfe->base + VFE_0_BUS_BDG_QOS_CFG_6); + writel_relaxed(val7, vfe->base + VFE_0_BUS_BDG_QOS_CFG_7); +} + +static void vfe_set_ds(struct vfe_device *vfe) +{ + u32 val = VFE_0_BUS_BDG_DS_CFG_0_CFG; + u32 val16 = VFE_0_BUS_BDG_DS_CFG_16_CFG; + + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_0); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_1); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_2); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_3); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_4); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_5); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_6); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_7); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_8); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_9); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_10); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_11); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_12); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_13); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_14); + writel_relaxed(val, vfe->base + VFE_0_BUS_BDG_DS_CFG_15); + writel_relaxed(val16, vfe->base + VFE_0_BUS_BDG_DS_CFG_16); +} + +static void vfe_isr_read(struct vfe_device *vfe, u32 *value0, u32 *value1) +{ + *value0 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_0); + *value1 = readl_relaxed(vfe->base + VFE_0_IRQ_STATUS_1); + + writel_relaxed(*value0, vfe->base + VFE_0_IRQ_CLEAR_0); + writel_relaxed(*value1, vfe->base + VFE_0_IRQ_CLEAR_1); + + /* Enforce barrier between local & global IRQ clear */ + wmb(); + writel_relaxed(VFE_0_IRQ_CMD_GLOBAL_CLEAR, vfe->base + VFE_0_IRQ_CMD); +} + +/* + * vfe_pm_domain_off - Disable power domains specific to this VFE. + * @vfe: VFE Device + */ +static void vfe_pm_domain_off(struct vfe_device *vfe) +{ + struct camss *camss = vfe->camss; + + device_link_del(camss->genpd_link[vfe->id]); +} + +/* + * vfe_pm_domain_on - Enable power domains specific to this VFE. + * @vfe: VFE Device + */ +static int vfe_pm_domain_on(struct vfe_device *vfe) +{ + struct camss *camss = vfe->camss; + enum vfe_line_id id = vfe->id; + + camss->genpd_link[id] = device_link_add(camss->dev, camss->genpd[id], DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE); + + if (!camss->genpd_link[id]) { + dev_err(vfe->camss->dev, "Failed to add VFE#%d to power domain\n", id); + return -EINVAL; + } + + return 0; +} + +static void vfe_violation_read(struct vfe_device *vfe) +{ + u32 violation = readl_relaxed(vfe->base + VFE_0_VIOLATION_STATUS); + + pr_err_ratelimited("VFE: violation = 0x%08x\n", violation); +} + +static const struct vfe_hw_ops_gen1 vfe_ops_gen1_4_8 = { + .bus_connect_wm_to_rdi = vfe_bus_connect_wm_to_rdi, + .bus_disconnect_wm_from_rdi = vfe_bus_disconnect_wm_from_rdi, + .bus_enable_wr_if = vfe_bus_enable_wr_if, + .bus_reload_wm = vfe_bus_reload_wm, + .camif_wait_for_stop = vfe_camif_wait_for_stop, + .enable_irq_common = vfe_enable_irq_common, + .enable_irq_pix_line = vfe_enable_irq_pix_line, + .enable_irq_wm_line = vfe_enable_irq_wm_line, + .get_ub_size = vfe_get_ub_size, + .halt_clear = vfe_halt_clear, + .halt_request = vfe_halt_request, + .set_camif_cfg = vfe_set_camif_cfg, + .set_camif_cmd = vfe_set_camif_cmd, + .set_cgc_override = vfe_set_cgc_override, + .set_clamp_cfg = vfe_set_clamp_cfg, + .set_crop_cfg = vfe_set_crop_cfg, + .set_demux_cfg = vfe_set_demux_cfg, + .set_ds = vfe_set_ds, + .set_module_cfg = vfe_set_module_cfg, + .set_qos = vfe_set_qos, + .set_rdi_cid = vfe_set_rdi_cid, + .set_realign_cfg = vfe_set_realign_cfg, + .set_scale_cfg = vfe_set_scale_cfg, + .set_xbar_cfg = vfe_set_xbar_cfg, + .wm_enable = vfe_wm_enable, + .wm_frame_based = vfe_wm_frame_based, + .wm_get_ping_pong_status = vfe_wm_get_ping_pong_status, + .wm_line_based = vfe_wm_line_based, + .wm_set_framedrop_pattern = vfe_wm_set_framedrop_pattern, + .wm_set_framedrop_period = vfe_wm_set_framedrop_period, + .wm_set_ping_addr = vfe_wm_set_ping_addr, + .wm_set_pong_addr = vfe_wm_set_pong_addr, + .wm_set_subsample = vfe_wm_set_subsample, + .wm_set_ub_cfg = vfe_wm_set_ub_cfg, +}; + +static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe) +{ + vfe->isr_ops = vfe_isr_ops_gen1; + vfe->ops_gen1 = &vfe_ops_gen1_4_8; + vfe->video_ops = vfe_video_ops_gen1; + + vfe->line_num = VFE_LINE_NUM_GEN1; +} + +const struct vfe_hw_ops vfe_ops_4_8 = { + .global_reset = vfe_global_reset, + .hw_version = vfe_hw_version, + .isr_read = vfe_isr_read, + .isr = vfe_isr, + .pm_domain_off = vfe_pm_domain_off, + .pm_domain_on = vfe_pm_domain_on, + .reg_update_clear = vfe_reg_update_clear, + .reg_update = vfe_reg_update, + .subdev_init = vfe_subdev_init, + .vfe_disable = vfe_gen1_disable, + .vfe_enable = vfe_gen1_enable, + .vfe_halt = vfe_gen1_halt, + .violation_read = vfe_violation_read, +}; diff --git a/drivers/media/platform/qcom/camss/camss-vfe-480.c b/drivers/media/platform/qcom/camss/camss-vfe-480.c new file mode 100644 index 000000000..72f5cfeeb --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe-480.c @@ -0,0 +1,566 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-vfe-480.c + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module v480 (SM8250) + * + * Copyright (C) 2020-2021 Linaro Ltd. + * Copyright (C) 2021 Jonathan Marek + */ + +#include +#include +#include + +#include "camss.h" +#include "camss-vfe.h" + +/* VFE 2/3 are lite and have a different register layout */ +#define IS_LITE (vfe->id >= 2 ? 1 : 0) + +#define VFE_HW_VERSION (0x00) + +#define VFE_GLOBAL_RESET_CMD (IS_LITE ? 0x0c : 0x1c) +#define GLOBAL_RESET_HW_AND_REG (IS_LITE ? BIT(1) : BIT(0)) + +#define VFE_REG_UPDATE_CMD (IS_LITE ? 0x20 : 0x34) +static inline int reg_update_rdi(struct vfe_device *vfe, int n) +{ + return IS_LITE ? BIT(n) : BIT(1 + (n)); +} + +#define REG_UPDATE_RDI reg_update_rdi +#define VFE_IRQ_CMD (IS_LITE ? 0x24 : 0x38) +#define IRQ_CMD_GLOBAL_CLEAR BIT(0) + +#define VFE_IRQ_MASK(n) ((IS_LITE ? 0x28 : 0x3c) + (n) * 4) +#define IRQ_MASK_0_RESET_ACK (IS_LITE ? BIT(17) : BIT(0)) +#define IRQ_MASK_0_BUS_TOP_IRQ (IS_LITE ? BIT(4) : BIT(7)) +#define VFE_IRQ_CLEAR(n) ((IS_LITE ? 0x34 : 0x48) + (n) * 4) +#define VFE_IRQ_STATUS(n) ((IS_LITE ? 0x40 : 0x54) + (n) * 4) + +#define BUS_REG_BASE (IS_LITE ? 0x1a00 : 0xaa00) + +#define VFE_BUS_WM_CGC_OVERRIDE (BUS_REG_BASE + 0x08) +#define WM_CGC_OVERRIDE_ALL (0x3FFFFFF) + +#define VFE_BUS_WM_TEST_BUS_CTRL (BUS_REG_BASE + 0xdc) + +#define VFE_BUS_IRQ_MASK(n) (BUS_REG_BASE + 0x18 + (n) * 4) +static inline int bus_irq_mask_0_rdi_rup(struct vfe_device *vfe, int n) +{ + return IS_LITE ? BIT(n) : BIT(3 + (n)); +} + +#define BUS_IRQ_MASK_0_RDI_RUP bus_irq_mask_0_rdi_rup +static inline int bus_irq_mask_0_comp_done(struct vfe_device *vfe, int n) +{ + return IS_LITE ? BIT(4 + (n)) : BIT(6 + (n)); +} + +#define BUS_IRQ_MASK_0_COMP_DONE bus_irq_mask_0_comp_done +#define VFE_BUS_IRQ_CLEAR(n) (BUS_REG_BASE + 0x20 + (n) * 4) +#define VFE_BUS_IRQ_STATUS(n) (BUS_REG_BASE + 0x28 + (n) * 4) +#define VFE_BUS_IRQ_CLEAR_GLOBAL (BUS_REG_BASE + 0x30) + +#define VFE_BUS_WM_CFG(n) (BUS_REG_BASE + 0x200 + (n) * 0x100) +#define WM_CFG_EN (0) +#define WM_CFG_MODE (16) +#define MODE_QCOM_PLAIN (0) +#define MODE_MIPI_RAW (1) +#define VFE_BUS_WM_IMAGE_ADDR(n) (BUS_REG_BASE + 0x204 + (n) * 0x100) +#define VFE_BUS_WM_FRAME_INCR(n) (BUS_REG_BASE + 0x208 + (n) * 0x100) +#define VFE_BUS_WM_IMAGE_CFG_0(n) (BUS_REG_BASE + 0x20c + (n) * 0x100) +#define WM_IMAGE_CFG_0_DEFAULT_WIDTH (0xFFFF) +#define VFE_BUS_WM_IMAGE_CFG_1(n) (BUS_REG_BASE + 0x210 + (n) * 0x100) +#define VFE_BUS_WM_IMAGE_CFG_2(n) (BUS_REG_BASE + 0x214 + (n) * 0x100) +#define VFE_BUS_WM_PACKER_CFG(n) (BUS_REG_BASE + 0x218 + (n) * 0x100) +#define VFE_BUS_WM_HEADER_ADDR(n) (BUS_REG_BASE + 0x220 + (n) * 0x100) +#define VFE_BUS_WM_HEADER_INCR(n) (BUS_REG_BASE + 0x224 + (n) * 0x100) +#define VFE_BUS_WM_HEADER_CFG(n) (BUS_REG_BASE + 0x228 + (n) * 0x100) + +#define VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(n) (BUS_REG_BASE + 0x230 + (n) * 0x100) +#define VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(n) (BUS_REG_BASE + 0x234 + (n) * 0x100) +#define VFE_BUS_WM_FRAMEDROP_PERIOD(n) (BUS_REG_BASE + 0x238 + (n) * 0x100) +#define VFE_BUS_WM_FRAMEDROP_PATTERN(n) (BUS_REG_BASE + 0x23c + (n) * 0x100) + +#define VFE_BUS_WM_SYSTEM_CACHE_CFG(n) (BUS_REG_BASE + 0x260 + (n) * 0x100) +#define VFE_BUS_WM_BURST_LIMIT(n) (BUS_REG_BASE + 0x264 + (n) * 0x100) + +/* for titan 480, each bus client is hardcoded to a specific path + * and each bus client is part of a hardcoded "comp group" + */ +#define RDI_WM(n) ((IS_LITE ? 0 : 23) + (n)) +#define RDI_COMP_GROUP(n) ((IS_LITE ? 0 : 11) + (n)) + +static u32 vfe_hw_version(struct vfe_device *vfe) +{ + u32 hw_version = readl_relaxed(vfe->base + VFE_HW_VERSION); + + u32 gen = (hw_version >> 28) & 0xF; + u32 rev = (hw_version >> 16) & 0xFFF; + u32 step = hw_version & 0xFFFF; + + dev_dbg(vfe->camss->dev, "VFE HW Version = %u.%u.%u\n", gen, rev, step); + + return hw_version; +} + +static void vfe_global_reset(struct vfe_device *vfe) +{ + writel_relaxed(IRQ_MASK_0_RESET_ACK, vfe->base + VFE_IRQ_MASK(0)); + writel_relaxed(GLOBAL_RESET_HW_AND_REG, vfe->base + VFE_GLOBAL_RESET_CMD); +} + +static void vfe_wm_start(struct vfe_device *vfe, u8 wm, struct vfe_line *line) +{ + struct v4l2_pix_format_mplane *pix = + &line->video_out.active_fmt.fmt.pix_mp; + + wm = RDI_WM(wm); /* map to actual WM used (from wm=RDI index) */ + + /* no clock gating at bus input */ + writel_relaxed(WM_CGC_OVERRIDE_ALL, vfe->base + VFE_BUS_WM_CGC_OVERRIDE); + + writel_relaxed(0x0, vfe->base + VFE_BUS_WM_TEST_BUS_CTRL); + + writel_relaxed(pix->plane_fmt[0].bytesperline * pix->height, + vfe->base + VFE_BUS_WM_FRAME_INCR(wm)); + writel_relaxed(0xf, vfe->base + VFE_BUS_WM_BURST_LIMIT(wm)); + writel_relaxed(WM_IMAGE_CFG_0_DEFAULT_WIDTH, + vfe->base + VFE_BUS_WM_IMAGE_CFG_0(wm)); + writel_relaxed(pix->plane_fmt[0].bytesperline, + vfe->base + VFE_BUS_WM_IMAGE_CFG_2(wm)); + writel_relaxed(0, vfe->base + VFE_BUS_WM_PACKER_CFG(wm)); + + /* no dropped frames, one irq per frame */ + writel_relaxed(0, vfe->base + VFE_BUS_WM_FRAMEDROP_PERIOD(wm)); + writel_relaxed(1, vfe->base + VFE_BUS_WM_FRAMEDROP_PATTERN(wm)); + writel_relaxed(0, vfe->base + VFE_BUS_WM_IRQ_SUBSAMPLE_PERIOD(wm)); + writel_relaxed(1, vfe->base + VFE_BUS_WM_IRQ_SUBSAMPLE_PATTERN(wm)); + + writel_relaxed(1 << WM_CFG_EN | MODE_MIPI_RAW << WM_CFG_MODE, + vfe->base + VFE_BUS_WM_CFG(wm)); +} + +static void vfe_wm_stop(struct vfe_device *vfe, u8 wm) +{ + wm = RDI_WM(wm); /* map to actual WM used (from wm=RDI index) */ + writel_relaxed(0, vfe->base + VFE_BUS_WM_CFG(wm)); +} + +static void vfe_wm_update(struct vfe_device *vfe, u8 wm, u32 addr, + struct vfe_line *line) +{ + wm = RDI_WM(wm); /* map to actual WM used (from wm=RDI index) */ + writel_relaxed(addr, vfe->base + VFE_BUS_WM_IMAGE_ADDR(wm)); +} + +static void vfe_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + vfe->reg_update |= REG_UPDATE_RDI(vfe, line_id); + writel_relaxed(vfe->reg_update, vfe->base + VFE_REG_UPDATE_CMD); +} + +static inline void vfe_reg_update_clear(struct vfe_device *vfe, + enum vfe_line_id line_id) +{ + vfe->reg_update &= ~REG_UPDATE_RDI(vfe, line_id); +} + +static void vfe_enable_irq_common(struct vfe_device *vfe) +{ + /* enable only the IRQs used: rup and comp_done irqs for RDI0 */ + writel_relaxed(IRQ_MASK_0_RESET_ACK | IRQ_MASK_0_BUS_TOP_IRQ, + vfe->base + VFE_IRQ_MASK(0)); + writel_relaxed(BUS_IRQ_MASK_0_RDI_RUP(vfe, 0) | + BUS_IRQ_MASK_0_COMP_DONE(vfe, RDI_COMP_GROUP(0)), + vfe->base + VFE_BUS_IRQ_MASK(0)); +} + +static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id); +static void vfe_isr_wm_done(struct vfe_device *vfe, u8 wm); + +/* + * vfe_isr - VFE module interrupt handler + * @irq: Interrupt line + * @dev: VFE device + * + * Return IRQ_HANDLED on success + */ +static irqreturn_t vfe_isr(int irq, void *dev) +{ + struct vfe_device *vfe = dev; + u32 status; + + status = readl_relaxed(vfe->base + VFE_IRQ_STATUS(0)); + writel_relaxed(status, vfe->base + VFE_IRQ_CLEAR(0)); + writel_relaxed(IRQ_CMD_GLOBAL_CLEAR, vfe->base + VFE_IRQ_CMD); + + if (status & IRQ_MASK_0_RESET_ACK) + vfe_isr_reset_ack(vfe); + + if (status & IRQ_MASK_0_BUS_TOP_IRQ) { + u32 status = readl_relaxed(vfe->base + VFE_BUS_IRQ_STATUS(0)); + + writel_relaxed(status, vfe->base + VFE_BUS_IRQ_CLEAR(0)); + writel_relaxed(1, vfe->base + VFE_BUS_IRQ_CLEAR_GLOBAL); + + if (status & BUS_IRQ_MASK_0_RDI_RUP(vfe, 0)) + vfe_isr_reg_update(vfe, 0); + + if (status & BUS_IRQ_MASK_0_COMP_DONE(vfe, RDI_COMP_GROUP(0))) + vfe_isr_wm_done(vfe, 0); + } + + return IRQ_HANDLED; +} + +/* + * vfe_halt - Trigger halt on VFE module and wait to complete + * @vfe: VFE device + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_halt(struct vfe_device *vfe) +{ + /* rely on vfe_disable_output() to stop the VFE */ + return 0; +} + +static int vfe_get_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output; + unsigned long flags; + int wm_idx; + + spin_lock_irqsave(&vfe->output_lock, flags); + + output = &line->output; + if (output->state != VFE_OUTPUT_OFF) { + dev_err(vfe->camss->dev, "Output is running\n"); + goto error; + } + + output->wm_num = 1; + + wm_idx = vfe_reserve_wm(vfe, line->id); + if (wm_idx < 0) { + dev_err(vfe->camss->dev, "Can not reserve wm\n"); + goto error_get_wm; + } + output->wm_idx[0] = wm_idx; + + output->drop_update_idx = 0; + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; + +error_get_wm: + vfe_release_wm(vfe, output->wm_idx[0]); + output->state = VFE_OUTPUT_OFF; +error: + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return -EINVAL; +} + +static int vfe_enable_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&vfe->output_lock, flags); + + vfe_reg_update_clear(vfe, line->id); + + if (output->state != VFE_OUTPUT_OFF) { + dev_err(vfe->camss->dev, "Output is not in reserved state %d\n", + output->state); + spin_unlock_irqrestore(&vfe->output_lock, flags); + return -EINVAL; + } + + WARN_ON(output->gen2.active_num); + + output->state = VFE_OUTPUT_ON; + + output->sequence = 0; + output->wait_reg_update = 0; + reinit_completion(&output->reg_update); + + vfe_wm_start(vfe, output->wm_idx[0], line); + + for (i = 0; i < 2; i++) { + output->buf[i] = vfe_buf_get_pending(output); + if (!output->buf[i]) + break; + output->gen2.active_num++; + vfe_wm_update(vfe, output->wm_idx[0], output->buf[i]->addr[0], line); + } + + vfe_reg_update(vfe, line->id); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +static void vfe_disable_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&vfe->output_lock, flags); + for (i = 0; i < output->wm_num; i++) + vfe_wm_stop(vfe, output->wm_idx[i]); + output->gen2.active_num = 0; + spin_unlock_irqrestore(&vfe->output_lock, flags); + + vfe_reset(vfe); +} + +/* + * vfe_enable - Enable streaming on VFE line + * @line: VFE line + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_enable(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + int ret; + + mutex_lock(&vfe->stream_lock); + + if (!vfe->stream_count) + vfe_enable_irq_common(vfe); + + vfe->stream_count++; + + mutex_unlock(&vfe->stream_lock); + + ret = vfe_get_output(line); + if (ret < 0) + goto error_get_output; + + ret = vfe_enable_output(line); + if (ret < 0) + goto error_enable_output; + + vfe->was_streaming = 1; + + return 0; + +error_enable_output: + vfe_put_output(line); + +error_get_output: + mutex_lock(&vfe->stream_lock); + + vfe->stream_count--; + + mutex_unlock(&vfe->stream_lock); + + return ret; +} + +/* + * vfe_disable - Disable streaming on VFE line + * @line: VFE line + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_disable(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + + vfe_disable_output(line); + + vfe_put_output(line); + + mutex_lock(&vfe->stream_lock); + + vfe->stream_count--; + + mutex_unlock(&vfe->stream_lock); + + return 0; +} + +/* + * vfe_isr_reg_update - Process reg update interrupt + * @vfe: VFE Device + * @line_id: VFE line + */ +static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + struct vfe_output *output; + unsigned long flags; + + spin_lock_irqsave(&vfe->output_lock, flags); + vfe_reg_update_clear(vfe, line_id); + + output = &vfe->line[line_id].output; + + if (output->wait_reg_update) { + output->wait_reg_update = 0; + complete(&output->reg_update); + } + + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_isr_wm_done - Process write master done interrupt + * @vfe: VFE Device + * @wm: Write master id + */ +static void vfe_isr_wm_done(struct vfe_device *vfe, u8 wm) +{ + struct vfe_line *line = &vfe->line[vfe->wm_output_map[wm]]; + struct camss_buffer *ready_buf; + struct vfe_output *output; + unsigned long flags; + u32 index; + u64 ts = ktime_get_ns(); + + spin_lock_irqsave(&vfe->output_lock, flags); + + if (vfe->wm_output_map[wm] == VFE_LINE_NONE) { + dev_err_ratelimited(vfe->camss->dev, + "Received wm done for unmapped index\n"); + goto out_unlock; + } + output = &vfe->line[vfe->wm_output_map[wm]].output; + + ready_buf = output->buf[0]; + if (!ready_buf) { + dev_err_ratelimited(vfe->camss->dev, + "Missing ready buf %d!\n", output->state); + goto out_unlock; + } + + ready_buf->vb.vb2_buf.timestamp = ts; + ready_buf->vb.sequence = output->sequence++; + + index = 0; + output->buf[0] = output->buf[1]; + if (output->buf[0]) + index = 1; + + output->buf[index] = vfe_buf_get_pending(output); + + if (output->buf[index]) + vfe_wm_update(vfe, output->wm_idx[0], output->buf[index]->addr[0], line); + else + output->gen2.active_num--; + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + + return; + +out_unlock: + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_pm_domain_off - Disable power domains specific to this VFE. + * @vfe: VFE Device + */ +static void vfe_pm_domain_off(struct vfe_device *vfe) +{ + struct camss *camss = vfe->camss; + + if (vfe->id >= camss->vfe_num) + return; + + device_link_del(camss->genpd_link[vfe->id]); +} + +/* + * vfe_pm_domain_on - Enable power domains specific to this VFE. + * @vfe: VFE Device + */ +static int vfe_pm_domain_on(struct vfe_device *vfe) +{ + struct camss *camss = vfe->camss; + enum vfe_line_id id = vfe->id; + + if (id >= camss->vfe_num) + return 0; + + camss->genpd_link[id] = device_link_add(camss->dev, camss->genpd[id], + DL_FLAG_STATELESS | + DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[id]) + return -EINVAL; + + return 0; +} + +/* + * vfe_queue_buffer - Add empty buffer + * @vid: Video device structure + * @buf: Buffer to be enqueued + * + * Add an empty buffer - depending on the current number of buffers it will be + * put in pending buffer queue or directly given to the hardware to be filled. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_queue_buffer(struct camss_video *vid, + struct camss_buffer *buf) +{ + struct vfe_line *line = container_of(vid, struct vfe_line, video_out); + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output; + unsigned long flags; + + output = &line->output; + + spin_lock_irqsave(&vfe->output_lock, flags); + + if (output->state == VFE_OUTPUT_ON && output->gen2.active_num < 2) { + output->buf[output->gen2.active_num++] = buf; + vfe_wm_update(vfe, output->wm_idx[0], buf->addr[0], line); + } else { + vfe_buf_add_pending(output, buf); + } + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +static const struct camss_video_ops vfe_video_ops_480 = { + .queue_buffer = vfe_queue_buffer, + .flush_buffers = vfe_flush_buffers, +}; + +static void vfe_subdev_init(struct device *dev, struct vfe_device *vfe) +{ + vfe->video_ops = vfe_video_ops_480; + vfe->line_num = 1; +} + +const struct vfe_hw_ops vfe_ops_480 = { + .global_reset = vfe_global_reset, + .hw_version = vfe_hw_version, + .isr = vfe_isr, + .pm_domain_off = vfe_pm_domain_off, + .pm_domain_on = vfe_pm_domain_on, + .subdev_init = vfe_subdev_init, + .vfe_disable = vfe_disable, + .vfe_enable = vfe_enable, + .vfe_halt = vfe_halt, +}; diff --git a/drivers/media/platform/qcom/camss/camss-vfe-gen1.c b/drivers/media/platform/qcom/camss/camss-vfe-gen1.c new file mode 100644 index 000000000..4fd265d01 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe-gen1.c @@ -0,0 +1,742 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-vfe-gen1.c + * + * Qualcomm MSM Camera Subsystem - VFE Common functionality for Gen 1 versions of hw (4.1, 4.7..) + * + * Copyright (C) 2020 Linaro Ltd. + */ + +#include "camss.h" +#include "camss-vfe.h" +#include "camss-vfe-gen1.h" + +/* Max number of frame drop updates per frame */ +#define VFE_FRAME_DROP_UPDATES 2 +#define VFE_NEXT_SOF_MS 500 + +int vfe_gen1_halt(struct vfe_device *vfe) +{ + unsigned long time; + + reinit_completion(&vfe->halt_complete); + + vfe->ops_gen1->halt_request(vfe); + + time = wait_for_completion_timeout(&vfe->halt_complete, + msecs_to_jiffies(VFE_HALT_TIMEOUT_MS)); + if (!time) { + dev_err(vfe->camss->dev, "VFE halt timeout\n"); + return -EIO; + } + + return 0; +} + +static int vfe_disable_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + const struct vfe_hw_ops *ops = vfe->ops; + unsigned long flags; + unsigned long time; + unsigned int i; + + spin_lock_irqsave(&vfe->output_lock, flags); + + output->gen1.wait_sof = 1; + spin_unlock_irqrestore(&vfe->output_lock, flags); + + time = wait_for_completion_timeout(&output->sof, msecs_to_jiffies(VFE_NEXT_SOF_MS)); + if (!time) + dev_err(vfe->camss->dev, "VFE sof timeout\n"); + + spin_lock_irqsave(&vfe->output_lock, flags); + for (i = 0; i < output->wm_num; i++) + vfe->ops_gen1->wm_enable(vfe, output->wm_idx[i], 0); + + ops->reg_update(vfe, line->id); + output->wait_reg_update = 1; + spin_unlock_irqrestore(&vfe->output_lock, flags); + + time = wait_for_completion_timeout(&output->reg_update, msecs_to_jiffies(VFE_NEXT_SOF_MS)); + if (!time) + dev_err(vfe->camss->dev, "VFE reg update timeout\n"); + + spin_lock_irqsave(&vfe->output_lock, flags); + + if (line->id != VFE_LINE_PIX) { + vfe->ops_gen1->wm_frame_based(vfe, output->wm_idx[0], 0); + vfe->ops_gen1->bus_disconnect_wm_from_rdi(vfe, output->wm_idx[0], line->id); + vfe->ops_gen1->enable_irq_wm_line(vfe, output->wm_idx[0], line->id, 0); + vfe->ops_gen1->set_cgc_override(vfe, output->wm_idx[0], 0); + spin_unlock_irqrestore(&vfe->output_lock, flags); + } else { + for (i = 0; i < output->wm_num; i++) { + vfe->ops_gen1->wm_line_based(vfe, output->wm_idx[i], NULL, i, 0); + vfe->ops_gen1->set_cgc_override(vfe, output->wm_idx[i], 0); + } + + vfe->ops_gen1->enable_irq_pix_line(vfe, 0, line->id, 0); + vfe->ops_gen1->set_module_cfg(vfe, 0); + vfe->ops_gen1->set_realign_cfg(vfe, line, 0); + vfe->ops_gen1->set_xbar_cfg(vfe, output, 0); + vfe->ops_gen1->set_camif_cmd(vfe, 0); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + vfe->ops_gen1->camif_wait_for_stop(vfe, vfe->camss->dev); + } + + return 0; +} + +/* + * vfe_gen1_disable - Disable streaming on VFE line + * @line: VFE line + * + * Return 0 on success or a negative error code otherwise + */ +int vfe_gen1_disable(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + + vfe_disable_output(line); + + vfe_put_output(line); + + mutex_lock(&vfe->stream_lock); + + if (vfe->stream_count == 1) + vfe->ops_gen1->bus_enable_wr_if(vfe, 0); + + vfe->stream_count--; + + mutex_unlock(&vfe->stream_lock); + + return 0; +} + +static void vfe_output_init_addrs(struct vfe_device *vfe, + struct vfe_output *output, u8 sync, + struct vfe_line *line) +{ + u32 ping_addr; + u32 pong_addr; + unsigned int i; + + output->gen1.active_buf = 0; + + for (i = 0; i < output->wm_num; i++) { + if (output->buf[0]) + ping_addr = output->buf[0]->addr[i]; + else + ping_addr = 0; + + if (output->buf[1]) + pong_addr = output->buf[1]->addr[i]; + else + pong_addr = ping_addr; + + vfe->ops_gen1->wm_set_ping_addr(vfe, output->wm_idx[i], ping_addr); + vfe->ops_gen1->wm_set_pong_addr(vfe, output->wm_idx[i], pong_addr); + if (sync) + vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[i]); + } +} + +static void vfe_output_frame_drop(struct vfe_device *vfe, + struct vfe_output *output, + u32 drop_pattern) +{ + u8 drop_period; + unsigned int i; + + /* We need to toggle update period to be valid on next frame */ + output->drop_update_idx++; + output->drop_update_idx %= VFE_FRAME_DROP_UPDATES; + drop_period = VFE_FRAME_DROP_VAL + output->drop_update_idx; + + for (i = 0; i < output->wm_num; i++) { + vfe->ops_gen1->wm_set_framedrop_period(vfe, output->wm_idx[i], drop_period); + vfe->ops_gen1->wm_set_framedrop_pattern(vfe, output->wm_idx[i], drop_pattern); + } + + vfe->ops->reg_update(vfe, container_of(output, struct vfe_line, output)->id); +} + +static int vfe_enable_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + const struct vfe_hw_ops *ops = vfe->ops; + struct media_entity *sensor; + unsigned long flags; + unsigned int frame_skip = 0; + unsigned int i; + u16 ub_size; + + ub_size = vfe->ops_gen1->get_ub_size(vfe->id); + if (!ub_size) + return -EINVAL; + + sensor = camss_find_sensor(&line->subdev.entity); + if (sensor) { + struct v4l2_subdev *subdev = media_entity_to_v4l2_subdev(sensor); + + v4l2_subdev_call(subdev, sensor, g_skip_frames, &frame_skip); + /* Max frame skip is 29 frames */ + if (frame_skip > VFE_FRAME_DROP_VAL - 1) + frame_skip = VFE_FRAME_DROP_VAL - 1; + } + + spin_lock_irqsave(&vfe->output_lock, flags); + + ops->reg_update_clear(vfe, line->id); + + if (output->state != VFE_OUTPUT_RESERVED) { + dev_err(vfe->camss->dev, "Output is not in reserved state %d\n", output->state); + spin_unlock_irqrestore(&vfe->output_lock, flags); + return -EINVAL; + } + output->state = VFE_OUTPUT_IDLE; + + output->buf[0] = vfe_buf_get_pending(output); + output->buf[1] = vfe_buf_get_pending(output); + + if (!output->buf[0] && output->buf[1]) { + output->buf[0] = output->buf[1]; + output->buf[1] = NULL; + } + + if (output->buf[0]) + output->state = VFE_OUTPUT_SINGLE; + + if (output->buf[1]) + output->state = VFE_OUTPUT_CONTINUOUS; + + switch (output->state) { + case VFE_OUTPUT_SINGLE: + vfe_output_frame_drop(vfe, output, 1 << frame_skip); + break; + case VFE_OUTPUT_CONTINUOUS: + vfe_output_frame_drop(vfe, output, 3 << frame_skip); + break; + default: + vfe_output_frame_drop(vfe, output, 0); + break; + } + + output->sequence = 0; + output->gen1.wait_sof = 0; + output->wait_reg_update = 0; + reinit_completion(&output->sof); + reinit_completion(&output->reg_update); + + vfe_output_init_addrs(vfe, output, 0, line); + + if (line->id != VFE_LINE_PIX) { + vfe->ops_gen1->set_cgc_override(vfe, output->wm_idx[0], 1); + vfe->ops_gen1->enable_irq_wm_line(vfe, output->wm_idx[0], line->id, 1); + vfe->ops_gen1->bus_connect_wm_to_rdi(vfe, output->wm_idx[0], line->id); + vfe->ops_gen1->wm_set_subsample(vfe, output->wm_idx[0]); + vfe->ops_gen1->set_rdi_cid(vfe, line->id, 0); + vfe->ops_gen1->wm_set_ub_cfg(vfe, output->wm_idx[0], + (ub_size + 1) * output->wm_idx[0], ub_size); + vfe->ops_gen1->wm_frame_based(vfe, output->wm_idx[0], 1); + vfe->ops_gen1->wm_enable(vfe, output->wm_idx[0], 1); + vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[0]); + } else { + ub_size /= output->wm_num; + for (i = 0; i < output->wm_num; i++) { + vfe->ops_gen1->set_cgc_override(vfe, output->wm_idx[i], 1); + vfe->ops_gen1->wm_set_subsample(vfe, output->wm_idx[i]); + vfe->ops_gen1->wm_set_ub_cfg(vfe, output->wm_idx[i], + (ub_size + 1) * output->wm_idx[i], ub_size); + vfe->ops_gen1->wm_line_based(vfe, output->wm_idx[i], + &line->video_out.active_fmt.fmt.pix_mp, i, 1); + vfe->ops_gen1->wm_enable(vfe, output->wm_idx[i], 1); + vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[i]); + } + vfe->ops_gen1->enable_irq_pix_line(vfe, 0, line->id, 1); + vfe->ops_gen1->set_module_cfg(vfe, 1); + vfe->ops_gen1->set_camif_cfg(vfe, line); + vfe->ops_gen1->set_realign_cfg(vfe, line, 1); + vfe->ops_gen1->set_xbar_cfg(vfe, output, 1); + vfe->ops_gen1->set_demux_cfg(vfe, line); + vfe->ops_gen1->set_scale_cfg(vfe, line); + vfe->ops_gen1->set_crop_cfg(vfe, line); + vfe->ops_gen1->set_clamp_cfg(vfe); + vfe->ops_gen1->set_camif_cmd(vfe, 1); + } + + ops->reg_update(vfe, line->id); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +static int vfe_get_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output; + struct v4l2_format *f = &line->video_out.active_fmt; + unsigned long flags; + int i; + int wm_idx; + + spin_lock_irqsave(&vfe->output_lock, flags); + + output = &line->output; + if (output->state != VFE_OUTPUT_OFF) { + dev_err(vfe->camss->dev, "Output is running\n"); + goto error; + } + output->state = VFE_OUTPUT_RESERVED; + + output->gen1.active_buf = 0; + + switch (f->fmt.pix_mp.pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + output->wm_num = 2; + break; + default: + output->wm_num = 1; + break; + } + + for (i = 0; i < output->wm_num; i++) { + wm_idx = vfe_reserve_wm(vfe, line->id); + if (wm_idx < 0) { + dev_err(vfe->camss->dev, "Can not reserve wm\n"); + goto error_get_wm; + } + output->wm_idx[i] = wm_idx; + } + + output->drop_update_idx = 0; + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; + +error_get_wm: + for (i--; i >= 0; i--) + vfe_release_wm(vfe, output->wm_idx[i]); + output->state = VFE_OUTPUT_OFF; +error: + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return -EINVAL; +} + +int vfe_gen1_enable(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + int ret; + + mutex_lock(&vfe->stream_lock); + + if (!vfe->stream_count) { + vfe->ops_gen1->enable_irq_common(vfe); + vfe->ops_gen1->bus_enable_wr_if(vfe, 1); + vfe->ops_gen1->set_qos(vfe); + vfe->ops_gen1->set_ds(vfe); + } + + vfe->stream_count++; + + mutex_unlock(&vfe->stream_lock); + + ret = vfe_get_output(line); + if (ret < 0) + goto error_get_output; + + ret = vfe_enable_output(line); + if (ret < 0) + goto error_enable_output; + + vfe->was_streaming = 1; + + return 0; + +error_enable_output: + vfe_put_output(line); + +error_get_output: + mutex_lock(&vfe->stream_lock); + + if (vfe->stream_count == 1) + vfe->ops_gen1->bus_enable_wr_if(vfe, 0); + + vfe->stream_count--; + + mutex_unlock(&vfe->stream_lock); + + return ret; +} + +static void vfe_output_update_ping_addr(struct vfe_device *vfe, + struct vfe_output *output, u8 sync, + struct vfe_line *line) +{ + u32 addr; + unsigned int i; + + for (i = 0; i < output->wm_num; i++) { + if (output->buf[0]) + addr = output->buf[0]->addr[i]; + else + addr = 0; + + vfe->ops_gen1->wm_set_ping_addr(vfe, output->wm_idx[i], addr); + if (sync) + vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[i]); + } +} + +static void vfe_output_update_pong_addr(struct vfe_device *vfe, + struct vfe_output *output, u8 sync, + struct vfe_line *line) +{ + u32 addr; + unsigned int i; + + for (i = 0; i < output->wm_num; i++) { + if (output->buf[1]) + addr = output->buf[1]->addr[i]; + else + addr = 0; + + vfe->ops_gen1->wm_set_pong_addr(vfe, output->wm_idx[i], addr); + if (sync) + vfe->ops_gen1->bus_reload_wm(vfe, output->wm_idx[i]); + } +} + +static void vfe_buf_update_wm_on_next(struct vfe_device *vfe, + struct vfe_output *output) +{ + switch (output->state) { + case VFE_OUTPUT_CONTINUOUS: + vfe_output_frame_drop(vfe, output, 3); + break; + case VFE_OUTPUT_SINGLE: + default: + dev_err_ratelimited(vfe->camss->dev, + "Next buf in wrong state! %d\n", + output->state); + break; + } +} + +static void vfe_buf_update_wm_on_last(struct vfe_device *vfe, + struct vfe_output *output) +{ + switch (output->state) { + case VFE_OUTPUT_CONTINUOUS: + output->state = VFE_OUTPUT_SINGLE; + vfe_output_frame_drop(vfe, output, 1); + break; + case VFE_OUTPUT_SINGLE: + output->state = VFE_OUTPUT_STOPPING; + vfe_output_frame_drop(vfe, output, 0); + break; + default: + dev_err_ratelimited(vfe->camss->dev, + "Last buff in wrong state! %d\n", + output->state); + break; + } +} + +static void vfe_buf_update_wm_on_new(struct vfe_device *vfe, + struct vfe_output *output, + struct camss_buffer *new_buf, + struct vfe_line *line) +{ + int inactive_idx; + + switch (output->state) { + case VFE_OUTPUT_SINGLE: + inactive_idx = !output->gen1.active_buf; + + if (!output->buf[inactive_idx]) { + output->buf[inactive_idx] = new_buf; + + if (inactive_idx) + vfe_output_update_pong_addr(vfe, output, 0, line); + else + vfe_output_update_ping_addr(vfe, output, 0, line); + + vfe_output_frame_drop(vfe, output, 3); + output->state = VFE_OUTPUT_CONTINUOUS; + } else { + vfe_buf_add_pending(output, new_buf); + dev_err_ratelimited(vfe->camss->dev, + "Inactive buffer is busy\n"); + } + break; + + case VFE_OUTPUT_IDLE: + if (!output->buf[0]) { + output->buf[0] = new_buf; + + vfe_output_init_addrs(vfe, output, 1, line); + vfe_output_frame_drop(vfe, output, 1); + + output->state = VFE_OUTPUT_SINGLE; + } else { + vfe_buf_add_pending(output, new_buf); + dev_err_ratelimited(vfe->camss->dev, + "Output idle with buffer set!\n"); + } + break; + + case VFE_OUTPUT_CONTINUOUS: + default: + vfe_buf_add_pending(output, new_buf); + break; + } +} + +/* + * vfe_isr_halt_ack - Process halt ack + * @vfe: VFE Device + */ +static void vfe_isr_halt_ack(struct vfe_device *vfe) +{ + complete(&vfe->halt_complete); + vfe->ops_gen1->halt_clear(vfe); +} + +/* + * vfe_isr_sof - Process start of frame interrupt + * @vfe: VFE Device + * @line_id: VFE line + */ +static void vfe_isr_sof(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + struct vfe_output *output; + unsigned long flags; + + spin_lock_irqsave(&vfe->output_lock, flags); + output = &vfe->line[line_id].output; + if (output->gen1.wait_sof) { + output->gen1.wait_sof = 0; + complete(&output->sof); + } + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_isr_reg_update - Process reg update interrupt + * @vfe: VFE Device + * @line_id: VFE line + */ +static void vfe_isr_reg_update(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + struct vfe_output *output; + struct vfe_line *line = &vfe->line[line_id]; + unsigned long flags; + + spin_lock_irqsave(&vfe->output_lock, flags); + vfe->ops->reg_update_clear(vfe, line_id); + + output = &line->output; + + if (output->wait_reg_update) { + output->wait_reg_update = 0; + complete(&output->reg_update); + spin_unlock_irqrestore(&vfe->output_lock, flags); + return; + } + + if (output->state == VFE_OUTPUT_STOPPING) { + /* Release last buffer when hw is idle */ + if (output->last_buffer) { + vb2_buffer_done(&output->last_buffer->vb.vb2_buf, + VB2_BUF_STATE_DONE); + output->last_buffer = NULL; + } + output->state = VFE_OUTPUT_IDLE; + + /* Buffers received in stopping state are queued in */ + /* dma pending queue, start next capture here */ + + output->buf[0] = vfe_buf_get_pending(output); + output->buf[1] = vfe_buf_get_pending(output); + + if (!output->buf[0] && output->buf[1]) { + output->buf[0] = output->buf[1]; + output->buf[1] = NULL; + } + + if (output->buf[0]) + output->state = VFE_OUTPUT_SINGLE; + + if (output->buf[1]) + output->state = VFE_OUTPUT_CONTINUOUS; + + switch (output->state) { + case VFE_OUTPUT_SINGLE: + vfe_output_frame_drop(vfe, output, 2); + break; + case VFE_OUTPUT_CONTINUOUS: + vfe_output_frame_drop(vfe, output, 3); + break; + default: + vfe_output_frame_drop(vfe, output, 0); + break; + } + + vfe_output_init_addrs(vfe, output, 1, &vfe->line[line_id]); + } + + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_isr_wm_done - Process write master done interrupt + * @vfe: VFE Device + * @wm: Write master id + */ +static void vfe_isr_wm_done(struct vfe_device *vfe, u8 wm) +{ + struct camss_buffer *ready_buf; + struct vfe_output *output; + dma_addr_t *new_addr; + unsigned long flags; + u32 active_index; + u64 ts = ktime_get_ns(); + unsigned int i; + + active_index = vfe->ops_gen1->wm_get_ping_pong_status(vfe, wm); + + spin_lock_irqsave(&vfe->output_lock, flags); + + if (vfe->wm_output_map[wm] == VFE_LINE_NONE) { + dev_err_ratelimited(vfe->camss->dev, + "Received wm done for unmapped index\n"); + goto out_unlock; + } + output = &vfe->line[vfe->wm_output_map[wm]].output; + + if (output->gen1.active_buf == active_index && 0) { + dev_err_ratelimited(vfe->camss->dev, + "Active buffer mismatch!\n"); + goto out_unlock; + } + output->gen1.active_buf = active_index; + + ready_buf = output->buf[!active_index]; + if (!ready_buf) { + dev_err_ratelimited(vfe->camss->dev, + "Missing ready buf %d %d!\n", + !active_index, output->state); + goto out_unlock; + } + + ready_buf->vb.vb2_buf.timestamp = ts; + ready_buf->vb.sequence = output->sequence++; + + /* Get next buffer */ + output->buf[!active_index] = vfe_buf_get_pending(output); + if (!output->buf[!active_index]) { + /* No next buffer - set same address */ + new_addr = ready_buf->addr; + vfe_buf_update_wm_on_last(vfe, output); + } else { + new_addr = output->buf[!active_index]->addr; + vfe_buf_update_wm_on_next(vfe, output); + } + + if (active_index) + for (i = 0; i < output->wm_num; i++) + vfe->ops_gen1->wm_set_ping_addr(vfe, output->wm_idx[i], new_addr[i]); + else + for (i = 0; i < output->wm_num; i++) + vfe->ops_gen1->wm_set_pong_addr(vfe, output->wm_idx[i], new_addr[i]); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + if (output->state == VFE_OUTPUT_STOPPING) + output->last_buffer = ready_buf; + else + vb2_buffer_done(&ready_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + + return; + +out_unlock: + spin_unlock_irqrestore(&vfe->output_lock, flags); +} + +/* + * vfe_queue_buffer - Add empty buffer + * @vid: Video device structure + * @buf: Buffer to be enqueued + * + * Add an empty buffer - depending on the current number of buffers it will be + * put in pending buffer queue or directly given to the hardware to be filled. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_queue_buffer(struct camss_video *vid, struct camss_buffer *buf) +{ + struct vfe_line *line = container_of(vid, struct vfe_line, video_out); + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output; + unsigned long flags; + + output = &line->output; + + spin_lock_irqsave(&vfe->output_lock, flags); + + vfe_buf_update_wm_on_new(vfe, output, buf, line); + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +#define CALC_WORD(width, M, N) (((width) * (M) + (N) - 1) / (N)) + +int vfe_word_per_line(u32 format, u32 width) +{ + int val = 0; + + switch (format) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + val = CALC_WORD(width, 1, 8); + break; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + val = CALC_WORD(width, 2, 8); + break; + } + + return val; +} + +const struct vfe_isr_ops vfe_isr_ops_gen1 = { + .reset_ack = vfe_isr_reset_ack, + .halt_ack = vfe_isr_halt_ack, + .reg_update = vfe_isr_reg_update, + .sof = vfe_isr_sof, + .comp_done = vfe_isr_comp_done, + .wm_done = vfe_isr_wm_done, +}; + +const struct camss_video_ops vfe_video_ops_gen1 = { + .queue_buffer = vfe_queue_buffer, + .flush_buffers = vfe_flush_buffers, +}; diff --git a/drivers/media/platform/qcom/camss/camss-vfe-gen1.h b/drivers/media/platform/qcom/camss/camss-vfe-gen1.h new file mode 100644 index 000000000..6d5f96565 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe-gen1.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-vfe.h + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_VFE_GEN1_H +#define QC_MSM_CAMSS_VFE_GEN1_H + +#include "camss-vfe.h" + +enum vfe_line_id; +struct vfe_device; +struct vfe_line; +struct vfe_output; + +struct vfe_hw_ops_gen1 { + void (*bus_connect_wm_to_rdi)(struct vfe_device *vfe, u8 wm, enum vfe_line_id id); + void (*bus_disconnect_wm_from_rdi)(struct vfe_device *vfe, u8 wm, enum vfe_line_id id); + void (*bus_enable_wr_if)(struct vfe_device *vfe, u8 enable); + void (*bus_reload_wm)(struct vfe_device *vfe, u8 wm); + int (*camif_wait_for_stop)(struct vfe_device *vfe, struct device *dev); + void (*enable_irq_common)(struct vfe_device *vfe); + void (*enable_irq_wm_line)(struct vfe_device *vfe, u8 wm, enum vfe_line_id line_id, + u8 enable); + void (*enable_irq_pix_line)(struct vfe_device *vfe, u8 comp, enum vfe_line_id line_id, + u8 enable); + u16 (*get_ub_size)(u8 vfe_id); + void (*halt_clear)(struct vfe_device *vfe); + void (*halt_request)(struct vfe_device *vfe); + void (*set_camif_cfg)(struct vfe_device *vfe, struct vfe_line *line); + void (*set_camif_cmd)(struct vfe_device *vfe, u8 enable); + void (*set_cgc_override)(struct vfe_device *vfe, u8 wm, u8 enable); + void (*set_clamp_cfg)(struct vfe_device *vfe); + void (*set_crop_cfg)(struct vfe_device *vfe, struct vfe_line *line); + void (*set_demux_cfg)(struct vfe_device *vfe, struct vfe_line *line); + void (*set_ds)(struct vfe_device *vfe); + void (*set_module_cfg)(struct vfe_device *vfe, u8 enable); + void (*set_scale_cfg)(struct vfe_device *vfe, struct vfe_line *line); + void (*set_rdi_cid)(struct vfe_device *vfe, enum vfe_line_id id, u8 cid); + void (*set_realign_cfg)(struct vfe_device *vfe, struct vfe_line *line, u8 enable); + void (*set_qos)(struct vfe_device *vfe); + void (*set_xbar_cfg)(struct vfe_device *vfe, struct vfe_output *output, u8 enable); + void (*wm_frame_based)(struct vfe_device *vfe, u8 wm, u8 enable); + void (*wm_line_based)(struct vfe_device *vfe, u32 wm, struct v4l2_pix_format_mplane *pix, + u8 plane, u32 enable); + void (*wm_set_ub_cfg)(struct vfe_device *vfe, u8 wm, u16 offset, u16 depth); + void (*wm_set_subsample)(struct vfe_device *vfe, u8 wm); + void (*wm_set_framedrop_period)(struct vfe_device *vfe, u8 wm, u8 per); + void (*wm_set_framedrop_pattern)(struct vfe_device *vfe, u8 wm, u32 pattern); + void (*wm_set_ping_addr)(struct vfe_device *vfe, u8 wm, u32 addr); + void (*wm_set_pong_addr)(struct vfe_device *vfe, u8 wm, u32 addr); + int (*wm_get_ping_pong_status)(struct vfe_device *vfe, u8 wm); + void (*wm_enable)(struct vfe_device *vfe, u8 wm, u8 enable); +}; + +/* + * vfe_calc_interp_reso - Calculate interpolation mode + * @input: Input resolution + * @output: Output resolution + * + * Return interpolation mode + */ +static inline u8 vfe_calc_interp_reso(u16 input, u16 output) +{ + if (input / output >= 16) + return 0; + + if (input / output >= 8) + return 1; + + if (input / output >= 4) + return 2; + + return 3; +} + +/* + * vfe_gen1_disable - Disable streaming on VFE line + * @line: VFE line + * + * Return 0 on success or a negative error code otherwise + */ +int vfe_gen1_disable(struct vfe_line *line); + +/* + * vfe_gen1_enable - Enable VFE module + * @line: VFE line + * + * Return 0 on success + */ +int vfe_gen1_enable(struct vfe_line *line); + +/* + * vfe_gen1_enable - Halt VFE module + * @vfe: VFE device + * + * Return 0 on success + */ +int vfe_gen1_halt(struct vfe_device *vfe); + +/* + * vfe_word_per_line - Calculate number of words per frame width + * @format: V4L2 format + * @width: Frame width + * + * Return number of words per frame width + */ +int vfe_word_per_line(u32 format, u32 width); + +extern const struct vfe_isr_ops vfe_isr_ops_gen1; +extern const struct camss_video_ops vfe_video_ops_gen1; + +#endif /* QC_MSM_CAMSS_VFE_GEN1_H */ diff --git a/drivers/media/platform/qcom/camss/camss-vfe.c b/drivers/media/platform/qcom/camss/camss-vfe.c new file mode 100644 index 000000000..ee4d7dcce --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe.c @@ -0,0 +1,1619 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-vfe.c + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "camss-vfe.h" +#include "camss.h" + +#define MSM_VFE_NAME "msm_vfe" + +/* VFE reset timeout */ +#define VFE_RESET_TIMEOUT_MS 50 + +#define SCALER_RATIO_MAX 16 + +struct vfe_format { + u32 code; + u8 bpp; +}; + +static const struct vfe_format formats_rdi_8x16[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, 8 }, + { MEDIA_BUS_FMT_YUYV8_2X8, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, 8 }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8 }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8 }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8 }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, + { MEDIA_BUS_FMT_Y10_1X10, 10 }, +}; + +static const struct vfe_format formats_pix_8x16[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, 8 }, + { MEDIA_BUS_FMT_YUYV8_2X8, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, 8 }, +}; + +static const struct vfe_format formats_rdi_8x96[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, 8 }, + { MEDIA_BUS_FMT_YUYV8_2X8, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, 8 }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8 }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8 }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8 }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, 16 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, + { MEDIA_BUS_FMT_SBGGR14_1X14, 14 }, + { MEDIA_BUS_FMT_SGBRG14_1X14, 14 }, + { MEDIA_BUS_FMT_SGRBG14_1X14, 14 }, + { MEDIA_BUS_FMT_SRGGB14_1X14, 14 }, + { MEDIA_BUS_FMT_Y10_1X10, 10 }, + { MEDIA_BUS_FMT_Y10_2X8_PADHI_LE, 16 }, +}; + +static const struct vfe_format formats_pix_8x96[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, 8 }, + { MEDIA_BUS_FMT_YUYV8_2X8, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, 8 }, +}; + +static const struct vfe_format formats_rdi_845[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, 8 }, + { MEDIA_BUS_FMT_YUYV8_2X8, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, 8 }, + { MEDIA_BUS_FMT_SBGGR8_1X8, 8 }, + { MEDIA_BUS_FMT_SGBRG8_1X8, 8 }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 8 }, + { MEDIA_BUS_FMT_SRGGB8_1X8, 8 }, + { MEDIA_BUS_FMT_SBGGR10_1X10, 10 }, + { MEDIA_BUS_FMT_SGBRG10_1X10, 10 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 10 }, + { MEDIA_BUS_FMT_SRGGB10_1X10, 10 }, + { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, 16 }, + { MEDIA_BUS_FMT_SBGGR12_1X12, 12 }, + { MEDIA_BUS_FMT_SGBRG12_1X12, 12 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 12 }, + { MEDIA_BUS_FMT_SRGGB12_1X12, 12 }, + { MEDIA_BUS_FMT_SBGGR14_1X14, 14 }, + { MEDIA_BUS_FMT_SGBRG14_1X14, 14 }, + { MEDIA_BUS_FMT_SGRBG14_1X14, 14 }, + { MEDIA_BUS_FMT_SRGGB14_1X14, 14 }, + { MEDIA_BUS_FMT_Y8_1X8, 8 }, + { MEDIA_BUS_FMT_Y10_1X10, 10 }, + { MEDIA_BUS_FMT_Y10_2X8_PADHI_LE, 16 }, +}; + +/* + * vfe_get_bpp - map media bus format to bits per pixel + * @formats: supported media bus formats array + * @nformats: size of @formats array + * @code: media bus format code + * + * Return number of bits per pixel + */ +static u8 vfe_get_bpp(const struct vfe_format *formats, + unsigned int nformats, u32 code) +{ + unsigned int i; + + for (i = 0; i < nformats; i++) + if (code == formats[i].code) + return formats[i].bpp; + + WARN(1, "Unknown format\n"); + + return formats[0].bpp; +} + +static u32 vfe_find_code(u32 *code, unsigned int n_code, + unsigned int index, u32 req_code) +{ + int i; + + if (!req_code && (index >= n_code)) + return 0; + + for (i = 0; i < n_code; i++) + if (req_code) { + if (req_code == code[i]) + return req_code; + } else { + if (i == index) + return code[i]; + } + + return code[0]; +} + +static u32 vfe_src_pad_code(struct vfe_line *line, u32 sink_code, + unsigned int index, u32 src_req_code) +{ + struct vfe_device *vfe = to_vfe(line); + + if (vfe->camss->version == CAMSS_8x16) + switch (sink_code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + { + u32 src_code[] = { + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YUYV8_1_5X8, + }; + + return vfe_find_code(src_code, ARRAY_SIZE(src_code), + index, src_req_code); + } + case MEDIA_BUS_FMT_YVYU8_2X8: + { + u32 src_code[] = { + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_YVYU8_1_5X8, + }; + + return vfe_find_code(src_code, ARRAY_SIZE(src_code), + index, src_req_code); + } + case MEDIA_BUS_FMT_UYVY8_2X8: + { + u32 src_code[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_UYVY8_1_5X8, + }; + + return vfe_find_code(src_code, ARRAY_SIZE(src_code), + index, src_req_code); + } + case MEDIA_BUS_FMT_VYUY8_2X8: + { + u32 src_code[] = { + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_VYUY8_1_5X8, + }; + + return vfe_find_code(src_code, ARRAY_SIZE(src_code), + index, src_req_code); + } + default: + if (index > 0) + return 0; + + return sink_code; + } + else if (vfe->camss->version == CAMSS_8x96 || + vfe->camss->version == CAMSS_660 || + vfe->camss->version == CAMSS_845 || + vfe->camss->version == CAMSS_8250) + switch (sink_code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + { + u32 src_code[] = { + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_YUYV8_1_5X8, + }; + + return vfe_find_code(src_code, ARRAY_SIZE(src_code), + index, src_req_code); + } + case MEDIA_BUS_FMT_YVYU8_2X8: + { + u32 src_code[] = { + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_YVYU8_1_5X8, + }; + + return vfe_find_code(src_code, ARRAY_SIZE(src_code), + index, src_req_code); + } + case MEDIA_BUS_FMT_UYVY8_2X8: + { + u32 src_code[] = { + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_UYVY8_1_5X8, + }; + + return vfe_find_code(src_code, ARRAY_SIZE(src_code), + index, src_req_code); + } + case MEDIA_BUS_FMT_VYUY8_2X8: + { + u32 src_code[] = { + MEDIA_BUS_FMT_VYUY8_2X8, + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_VYUY8_1_5X8, + }; + + return vfe_find_code(src_code, ARRAY_SIZE(src_code), + index, src_req_code); + } + default: + if (index > 0) + return 0; + + return sink_code; + } + else + return 0; +} + +int vfe_reset(struct vfe_device *vfe) +{ + unsigned long time; + + reinit_completion(&vfe->reset_complete); + + vfe->ops->global_reset(vfe); + + time = wait_for_completion_timeout(&vfe->reset_complete, + msecs_to_jiffies(VFE_RESET_TIMEOUT_MS)); + if (!time) { + dev_err(vfe->camss->dev, "VFE reset timeout\n"); + return -EIO; + } + + return 0; +} + +static void vfe_init_outputs(struct vfe_device *vfe) +{ + int i; + + for (i = 0; i < vfe->line_num; i++) { + struct vfe_output *output = &vfe->line[i].output; + + output->state = VFE_OUTPUT_OFF; + output->buf[0] = NULL; + output->buf[1] = NULL; + INIT_LIST_HEAD(&output->pending_bufs); + } +} + +static void vfe_reset_output_maps(struct vfe_device *vfe) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) + vfe->wm_output_map[i] = VFE_LINE_NONE; +} + +int vfe_reserve_wm(struct vfe_device *vfe, enum vfe_line_id line_id) +{ + int ret = -EBUSY; + int i; + + for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) { + if (vfe->wm_output_map[i] == VFE_LINE_NONE) { + vfe->wm_output_map[i] = line_id; + ret = i; + break; + } + } + + return ret; +} + +int vfe_release_wm(struct vfe_device *vfe, u8 wm) +{ + if (wm >= ARRAY_SIZE(vfe->wm_output_map)) + return -EINVAL; + + vfe->wm_output_map[wm] = VFE_LINE_NONE; + + return 0; +} + +struct camss_buffer *vfe_buf_get_pending(struct vfe_output *output) +{ + struct camss_buffer *buffer = NULL; + + if (!list_empty(&output->pending_bufs)) { + buffer = list_first_entry(&output->pending_bufs, + struct camss_buffer, + queue); + list_del(&buffer->queue); + } + + return buffer; +} + +void vfe_buf_add_pending(struct vfe_output *output, + struct camss_buffer *buffer) +{ + INIT_LIST_HEAD(&buffer->queue); + list_add_tail(&buffer->queue, &output->pending_bufs); +} + +/* + * vfe_buf_flush_pending - Flush all pending buffers. + * @output: VFE output + * @state: vb2 buffer state + */ +static void vfe_buf_flush_pending(struct vfe_output *output, + enum vb2_buffer_state state) +{ + struct camss_buffer *buf; + struct camss_buffer *t; + + list_for_each_entry_safe(buf, t, &output->pending_bufs, queue) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->queue); + } +} + +int vfe_put_output(struct vfe_line *line) +{ + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output = &line->output; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&vfe->output_lock, flags); + + for (i = 0; i < output->wm_num; i++) + vfe_release_wm(vfe, output->wm_idx[i]); + + output->state = VFE_OUTPUT_OFF; + + spin_unlock_irqrestore(&vfe->output_lock, flags); + return 0; +} + +/** + * vfe_isr_comp_done() - Process composite image done interrupt + * @vfe: VFE Device + * @comp: Composite image id + */ +void vfe_isr_comp_done(struct vfe_device *vfe, u8 comp) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(vfe->wm_output_map); i++) + if (vfe->wm_output_map[i] == VFE_LINE_PIX) { + vfe->isr_ops.wm_done(vfe, i); + break; + } +} + +void vfe_isr_reset_ack(struct vfe_device *vfe) +{ + complete(&vfe->reset_complete); +} + +/* + * vfe_set_clock_rates - Calculate and set clock rates on VFE module + * @vfe: VFE device + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_set_clock_rates(struct vfe_device *vfe) +{ + struct device *dev = vfe->camss->dev; + u64 pixel_clock[VFE_LINE_NUM_MAX]; + int i, j; + int ret; + + for (i = VFE_LINE_RDI0; i < vfe->line_num; i++) { + ret = camss_get_pixel_clock(&vfe->line[i].subdev.entity, + &pixel_clock[i]); + if (ret) + pixel_clock[i] = 0; + } + + for (i = 0; i < vfe->nclocks; i++) { + struct camss_clock *clock = &vfe->clock[i]; + + if (!strcmp(clock->name, "vfe0") || + !strcmp(clock->name, "vfe1") || + !strcmp(clock->name, "vfe_lite")) { + u64 min_rate = 0; + long rate; + + for (j = VFE_LINE_RDI0; j < vfe->line_num; j++) { + u32 tmp; + u8 bpp; + + if (j == VFE_LINE_PIX) { + tmp = pixel_clock[j]; + } else { + struct vfe_line *l = &vfe->line[j]; + + bpp = vfe_get_bpp(l->formats, + l->nformats, + l->fmt[MSM_VFE_PAD_SINK].code); + tmp = pixel_clock[j] * bpp / 64; + } + + if (min_rate < tmp) + min_rate = tmp; + } + + camss_add_clock_margin(&min_rate); + + for (j = 0; j < clock->nfreqs; j++) + if (min_rate < clock->freq[j]) + break; + + if (j == clock->nfreqs) { + dev_err(dev, + "Pixel clock is too high for VFE"); + return -EINVAL; + } + + /* if sensor pixel clock is not available */ + /* set highest possible VFE clock rate */ + if (min_rate == 0) + j = clock->nfreqs - 1; + + rate = clk_round_rate(clock->clk, clock->freq[j]); + if (rate < 0) { + dev_err(dev, "clk round rate failed: %ld\n", + rate); + return -EINVAL; + } + + ret = clk_set_rate(clock->clk, rate); + if (ret < 0) { + dev_err(dev, "clk set rate failed: %d\n", ret); + return ret; + } + } + } + + return 0; +} + +/* + * vfe_check_clock_rates - Check current clock rates on VFE module + * @vfe: VFE device + * + * Return 0 if current clock rates are suitable for a new pipeline + * or a negative error code otherwise + */ +static int vfe_check_clock_rates(struct vfe_device *vfe) +{ + u64 pixel_clock[VFE_LINE_NUM_MAX]; + int i, j; + int ret; + + for (i = VFE_LINE_RDI0; i < vfe->line_num; i++) { + ret = camss_get_pixel_clock(&vfe->line[i].subdev.entity, + &pixel_clock[i]); + if (ret) + pixel_clock[i] = 0; + } + + for (i = 0; i < vfe->nclocks; i++) { + struct camss_clock *clock = &vfe->clock[i]; + + if (!strcmp(clock->name, "vfe0") || + !strcmp(clock->name, "vfe1") || + !strcmp(clock->name, "vfe_lite")) { + u64 min_rate = 0; + unsigned long rate; + + for (j = VFE_LINE_RDI0; j < vfe->line_num; j++) { + u32 tmp; + u8 bpp; + + if (j == VFE_LINE_PIX) { + tmp = pixel_clock[j]; + } else { + struct vfe_line *l = &vfe->line[j]; + + bpp = vfe_get_bpp(l->formats, + l->nformats, + l->fmt[MSM_VFE_PAD_SINK].code); + tmp = pixel_clock[j] * bpp / 64; + } + + if (min_rate < tmp) + min_rate = tmp; + } + + camss_add_clock_margin(&min_rate); + + rate = clk_get_rate(clock->clk); + if (rate < min_rate) + return -EBUSY; + } + } + + return 0; +} + +/* + * vfe_get - Power up and reset VFE module + * @vfe: VFE Device + * + * Return 0 on success or a negative error code otherwise + */ +int vfe_get(struct vfe_device *vfe) +{ + int ret; + + mutex_lock(&vfe->power_lock); + + if (vfe->power_count == 0) { + ret = vfe->ops->pm_domain_on(vfe); + if (ret < 0) + goto error_pm_domain; + + ret = pm_runtime_resume_and_get(vfe->camss->dev); + if (ret < 0) + goto error_domain_off; + + ret = vfe_set_clock_rates(vfe); + if (ret < 0) + goto error_pm_runtime_get; + + ret = camss_enable_clocks(vfe->nclocks, vfe->clock, + vfe->camss->dev); + if (ret < 0) + goto error_pm_runtime_get; + + ret = vfe_reset(vfe); + if (ret < 0) + goto error_reset; + + vfe_reset_output_maps(vfe); + + vfe_init_outputs(vfe); + + vfe->ops->hw_version(vfe); + } else { + ret = vfe_check_clock_rates(vfe); + if (ret < 0) + goto error_pm_domain; + } + vfe->power_count++; + + mutex_unlock(&vfe->power_lock); + + return 0; + +error_reset: + camss_disable_clocks(vfe->nclocks, vfe->clock); + +error_pm_runtime_get: + pm_runtime_put_sync(vfe->camss->dev); +error_domain_off: + vfe->ops->pm_domain_off(vfe); + +error_pm_domain: + mutex_unlock(&vfe->power_lock); + + return ret; +} + +/* + * vfe_put - Power down VFE module + * @vfe: VFE Device + */ +void vfe_put(struct vfe_device *vfe) +{ + mutex_lock(&vfe->power_lock); + + if (vfe->power_count == 0) { + dev_err(vfe->camss->dev, "vfe power off on power_count == 0\n"); + goto exit; + } else if (vfe->power_count == 1) { + if (vfe->was_streaming) { + vfe->was_streaming = 0; + vfe->ops->vfe_halt(vfe); + } + camss_disable_clocks(vfe->nclocks, vfe->clock); + pm_runtime_put_sync(vfe->camss->dev); + vfe->ops->pm_domain_off(vfe); + } + + vfe->power_count--; + +exit: + mutex_unlock(&vfe->power_lock); +} + +/* + * vfe_flush_buffers - Return all vb2 buffers + * @vid: Video device structure + * @state: vb2 buffer state of the returned buffers + * + * Return all buffers to vb2. This includes queued pending buffers (still + * unused) and any buffers given to the hardware but again still not used. + * + * Return 0 on success or a negative error code otherwise + */ +int vfe_flush_buffers(struct camss_video *vid, + enum vb2_buffer_state state) +{ + struct vfe_line *line = container_of(vid, struct vfe_line, video_out); + struct vfe_device *vfe = to_vfe(line); + struct vfe_output *output; + unsigned long flags; + + output = &line->output; + + spin_lock_irqsave(&vfe->output_lock, flags); + + vfe_buf_flush_pending(output, state); + + if (output->buf[0]) + vb2_buffer_done(&output->buf[0]->vb.vb2_buf, state); + + if (output->buf[1]) + vb2_buffer_done(&output->buf[1]->vb.vb2_buf, state); + + if (output->last_buffer) { + vb2_buffer_done(&output->last_buffer->vb.vb2_buf, state); + output->last_buffer = NULL; + } + + spin_unlock_irqrestore(&vfe->output_lock, flags); + + return 0; +} + +/* + * vfe_set_power - Power on/off VFE module + * @sd: VFE V4L2 subdevice + * @on: Requested power state + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_set_power(struct v4l2_subdev *sd, int on) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct vfe_device *vfe = to_vfe(line); + int ret; + + if (on) { + ret = vfe_get(vfe); + if (ret < 0) + return ret; + } else { + vfe_put(vfe); + } + + return 0; +} + +/* + * vfe_set_stream - Enable/disable streaming on VFE module + * @sd: VFE V4L2 subdevice + * @enable: Requested streaming state + * + * Main configuration of VFE module is triggered here. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_set_stream(struct v4l2_subdev *sd, int enable) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct vfe_device *vfe = to_vfe(line); + int ret; + + if (enable) { + ret = vfe->ops->vfe_enable(line); + if (ret < 0) + dev_err(vfe->camss->dev, + "Failed to enable vfe outputs\n"); + } else { + ret = vfe->ops->vfe_disable(line); + if (ret < 0) + dev_err(vfe->camss->dev, + "Failed to disable vfe outputs\n"); + } + + return ret; +} + +/* + * __vfe_get_format - Get pointer to format structure + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @pad: pad from which format is requested + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE format structure + */ +static struct v4l2_mbus_framefmt * +__vfe_get_format(struct vfe_line *line, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&line->subdev, sd_state, + pad); + + return &line->fmt[pad]; +} + +/* + * __vfe_get_compose - Get pointer to compose selection structure + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE compose rectangle structure + */ +static struct v4l2_rect * +__vfe_get_compose(struct vfe_line *line, + struct v4l2_subdev_state *sd_state, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_compose(&line->subdev, sd_state, + MSM_VFE_PAD_SINK); + + return &line->compose; +} + +/* + * __vfe_get_crop - Get pointer to crop selection structure + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @which: TRY or ACTIVE format + * + * Return pointer to TRY or ACTIVE crop rectangle structure + */ +static struct v4l2_rect * +__vfe_get_crop(struct vfe_line *line, + struct v4l2_subdev_state *sd_state, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&line->subdev, sd_state, + MSM_VFE_PAD_SRC); + + return &line->crop; +} + +/* + * vfe_try_format - Handle try format by pad subdev method + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @pad: pad on which format is requested + * @fmt: pointer to v4l2 format structure + * @which: wanted subdev format + */ +static void vfe_try_format(struct vfe_line *line, + struct v4l2_subdev_state *sd_state, + unsigned int pad, + struct v4l2_mbus_framefmt *fmt, + enum v4l2_subdev_format_whence which) +{ + unsigned int i; + u32 code; + + switch (pad) { + case MSM_VFE_PAD_SINK: + /* Set format on sink pad */ + + for (i = 0; i < line->nformats; i++) + if (fmt->code == line->formats[i].code) + break; + + /* If not found, use UYVY as default */ + if (i >= line->nformats) + fmt->code = MEDIA_BUS_FMT_UYVY8_2X8; + + fmt->width = clamp_t(u32, fmt->width, 1, 8191); + fmt->height = clamp_t(u32, fmt->height, 1, 8191); + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + + break; + + case MSM_VFE_PAD_SRC: + /* Set and return a format same as sink pad */ + code = fmt->code; + + *fmt = *__vfe_get_format(line, sd_state, MSM_VFE_PAD_SINK, + which); + + fmt->code = vfe_src_pad_code(line, fmt->code, 0, code); + + if (line->id == VFE_LINE_PIX) { + struct v4l2_rect *rect; + + rect = __vfe_get_crop(line, sd_state, which); + + fmt->width = rect->width; + fmt->height = rect->height; + } + + break; + } + + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +/* + * vfe_try_compose - Handle try compose selection by pad subdev method + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @rect: pointer to v4l2 rect structure + * @which: wanted subdev format + */ +static void vfe_try_compose(struct vfe_line *line, + struct v4l2_subdev_state *sd_state, + struct v4l2_rect *rect, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = __vfe_get_format(line, sd_state, MSM_VFE_PAD_SINK, which); + + if (rect->width > fmt->width) + rect->width = fmt->width; + + if (rect->height > fmt->height) + rect->height = fmt->height; + + if (fmt->width > rect->width * SCALER_RATIO_MAX) + rect->width = (fmt->width + SCALER_RATIO_MAX - 1) / + SCALER_RATIO_MAX; + + rect->width &= ~0x1; + + if (fmt->height > rect->height * SCALER_RATIO_MAX) + rect->height = (fmt->height + SCALER_RATIO_MAX - 1) / + SCALER_RATIO_MAX; + + if (rect->width < 16) + rect->width = 16; + + if (rect->height < 4) + rect->height = 4; +} + +/* + * vfe_try_crop - Handle try crop selection by pad subdev method + * @line: VFE line + * @cfg: V4L2 subdev pad configuration + * @rect: pointer to v4l2 rect structure + * @which: wanted subdev format + */ +static void vfe_try_crop(struct vfe_line *line, + struct v4l2_subdev_state *sd_state, + struct v4l2_rect *rect, + enum v4l2_subdev_format_whence which) +{ + struct v4l2_rect *compose; + + compose = __vfe_get_compose(line, sd_state, which); + + if (rect->width > compose->width) + rect->width = compose->width; + + if (rect->width + rect->left > compose->width) + rect->left = compose->width - rect->width; + + if (rect->height > compose->height) + rect->height = compose->height; + + if (rect->height + rect->top > compose->height) + rect->top = compose->height - rect->height; + + /* wm in line based mode writes multiple of 16 horizontally */ + rect->left += (rect->width & 0xf) >> 1; + rect->width &= ~0xf; + + if (rect->width < 16) { + rect->left = 0; + rect->width = 16; + } + + if (rect->height < 4) { + rect->top = 0; + rect->height = 4; + } +} + +/* + * vfe_enum_mbus_code - Handle pixel format enumeration + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: pointer to v4l2_subdev_mbus_code_enum structure + * + * return -EINVAL or zero on success + */ +static int vfe_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + + if (code->pad == MSM_VFE_PAD_SINK) { + if (code->index >= line->nformats) + return -EINVAL; + + code->code = line->formats[code->index].code; + } else { + struct v4l2_mbus_framefmt *sink_fmt; + + sink_fmt = __vfe_get_format(line, sd_state, MSM_VFE_PAD_SINK, + code->which); + + code->code = vfe_src_pad_code(line, sink_fmt->code, + code->index, 0); + if (!code->code) + return -EINVAL; + } + + return 0; +} + +/* + * vfe_enum_frame_size - Handle frame size enumeration + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: pointer to v4l2_subdev_frame_size_enum structure + * + * Return -EINVAL or zero on success + */ +static int vfe_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt format; + + if (fse->index != 0) + return -EINVAL; + + format.code = fse->code; + format.width = 1; + format.height = 1; + vfe_try_format(line, sd_state, fse->pad, &format, fse->which); + fse->min_width = format.width; + fse->min_height = format.height; + + if (format.code != fse->code) + return -EINVAL; + + format.code = fse->code; + format.width = -1; + format.height = -1; + vfe_try_format(line, sd_state, fse->pad, &format, fse->which); + fse->max_width = format.width; + fse->max_height = format.height; + + return 0; +} + +/* + * vfe_get_format - Handle get format by pads subdev method + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int vfe_get_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __vfe_get_format(line, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + fmt->format = *format; + + return 0; +} + +static int vfe_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel); + +/* + * vfe_set_format - Handle set format by pads subdev method + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: pointer to v4l2 subdev format structure + * + * Return -EINVAL or zero on success + */ +static int vfe_set_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *format; + + format = __vfe_get_format(line, sd_state, fmt->pad, fmt->which); + if (format == NULL) + return -EINVAL; + + vfe_try_format(line, sd_state, fmt->pad, &fmt->format, fmt->which); + *format = fmt->format; + + if (fmt->pad == MSM_VFE_PAD_SINK) { + struct v4l2_subdev_selection sel = { 0 }; + int ret; + + /* Propagate the format from sink to source */ + format = __vfe_get_format(line, sd_state, MSM_VFE_PAD_SRC, + fmt->which); + + *format = fmt->format; + vfe_try_format(line, sd_state, MSM_VFE_PAD_SRC, format, + fmt->which); + + if (line->id != VFE_LINE_PIX) + return 0; + + /* Reset sink pad compose selection */ + sel.which = fmt->which; + sel.pad = MSM_VFE_PAD_SINK; + sel.target = V4L2_SEL_TGT_COMPOSE; + sel.r.width = fmt->format.width; + sel.r.height = fmt->format.height; + ret = vfe_set_selection(sd, sd_state, &sel); + if (ret < 0) + return ret; + } + + return 0; +} + +/* + * vfe_get_selection - Handle get selection by pads subdev method + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @sel: pointer to v4l2 subdev selection structure + * + * Return -EINVAL or zero on success + */ +static int vfe_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_subdev_format fmt = { 0 }; + struct v4l2_rect *rect; + int ret; + + if (line->id != VFE_LINE_PIX) + return -EINVAL; + + if (sel->pad == MSM_VFE_PAD_SINK) + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + fmt.pad = sel->pad; + fmt.which = sel->which; + ret = vfe_get_format(sd, sd_state, &fmt); + if (ret < 0) + return ret; + + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = fmt.format.width; + sel->r.height = fmt.format.height; + break; + case V4L2_SEL_TGT_COMPOSE: + rect = __vfe_get_compose(line, sd_state, sel->which); + if (rect == NULL) + return -EINVAL; + + sel->r = *rect; + break; + default: + return -EINVAL; + } + else if (sel->pad == MSM_VFE_PAD_SRC) + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + rect = __vfe_get_compose(line, sd_state, sel->which); + if (rect == NULL) + return -EINVAL; + + sel->r.left = rect->left; + sel->r.top = rect->top; + sel->r.width = rect->width; + sel->r.height = rect->height; + break; + case V4L2_SEL_TGT_CROP: + rect = __vfe_get_crop(line, sd_state, sel->which); + if (rect == NULL) + return -EINVAL; + + sel->r = *rect; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * vfe_set_selection - Handle set selection by pads subdev method + * @sd: VFE V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @sel: pointer to v4l2 subdev selection structure + * + * Return -EINVAL or zero on success + */ +static int vfe_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vfe_line *line = v4l2_get_subdevdata(sd); + struct v4l2_rect *rect; + int ret; + + if (line->id != VFE_LINE_PIX) + return -EINVAL; + + if (sel->target == V4L2_SEL_TGT_COMPOSE && + sel->pad == MSM_VFE_PAD_SINK) { + struct v4l2_subdev_selection crop = { 0 }; + + rect = __vfe_get_compose(line, sd_state, sel->which); + if (rect == NULL) + return -EINVAL; + + vfe_try_compose(line, sd_state, &sel->r, sel->which); + *rect = sel->r; + + /* Reset source crop selection */ + crop.which = sel->which; + crop.pad = MSM_VFE_PAD_SRC; + crop.target = V4L2_SEL_TGT_CROP; + crop.r = *rect; + ret = vfe_set_selection(sd, sd_state, &crop); + } else if (sel->target == V4L2_SEL_TGT_CROP && + sel->pad == MSM_VFE_PAD_SRC) { + struct v4l2_subdev_format fmt = { 0 }; + + rect = __vfe_get_crop(line, sd_state, sel->which); + if (rect == NULL) + return -EINVAL; + + vfe_try_crop(line, sd_state, &sel->r, sel->which); + *rect = sel->r; + + /* Reset source pad format width and height */ + fmt.which = sel->which; + fmt.pad = MSM_VFE_PAD_SRC; + ret = vfe_get_format(sd, sd_state, &fmt); + if (ret < 0) + return ret; + + fmt.format.width = rect->width; + fmt.format.height = rect->height; + ret = vfe_set_format(sd, sd_state, &fmt); + } else { + ret = -EINVAL; + } + + return ret; +} + +/* + * vfe_init_formats - Initialize formats on all pads + * @sd: VFE V4L2 subdevice + * @fh: V4L2 subdev file handle + * + * Initialize all pad formats with default values. + * + * Return 0 on success or a negative error code otherwise + */ +static int vfe_init_formats(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +{ + struct v4l2_subdev_format format = { + .pad = MSM_VFE_PAD_SINK, + .which = fh ? V4L2_SUBDEV_FORMAT_TRY : + V4L2_SUBDEV_FORMAT_ACTIVE, + .format = { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .width = 1920, + .height = 1080 + } + }; + + return vfe_set_format(sd, fh ? fh->state : NULL, &format); +} + +/* + * msm_vfe_subdev_init - Initialize VFE device structure and resources + * @vfe: VFE device + * @res: VFE module resources table + * + * Return 0 on success or a negative error code otherwise + */ +int msm_vfe_subdev_init(struct camss *camss, struct vfe_device *vfe, + const struct resources *res, u8 id) +{ + struct device *dev = camss->dev; + struct platform_device *pdev = to_platform_device(dev); + int i, j; + int ret; + + switch (camss->version) { + case CAMSS_8x16: + vfe->ops = &vfe_ops_4_1; + break; + case CAMSS_8x96: + vfe->ops = &vfe_ops_4_7; + break; + case CAMSS_660: + vfe->ops = &vfe_ops_4_8; + break; + case CAMSS_845: + vfe->ops = &vfe_ops_170; + break; + case CAMSS_8250: + vfe->ops = &vfe_ops_480; + break; + default: + return -EINVAL; + } + vfe->ops->subdev_init(dev, vfe); + + /* Memory */ + + vfe->base = devm_platform_ioremap_resource_byname(pdev, res->reg[0]); + if (IS_ERR(vfe->base)) { + dev_err(dev, "could not map memory\n"); + return PTR_ERR(vfe->base); + } + + /* Interrupt */ + + ret = platform_get_irq_byname(pdev, res->interrupt[0]); + if (ret < 0) + return ret; + + vfe->irq = ret; + snprintf(vfe->irq_name, sizeof(vfe->irq_name), "%s_%s%d", + dev_name(dev), MSM_VFE_NAME, id); + ret = devm_request_irq(dev, vfe->irq, vfe->ops->isr, + IRQF_TRIGGER_RISING, vfe->irq_name, vfe); + if (ret < 0) { + dev_err(dev, "request_irq failed: %d\n", ret); + return ret; + } + + /* Clocks */ + + vfe->nclocks = 0; + while (res->clock[vfe->nclocks]) + vfe->nclocks++; + + vfe->clock = devm_kcalloc(dev, vfe->nclocks, sizeof(*vfe->clock), + GFP_KERNEL); + if (!vfe->clock) + return -ENOMEM; + + for (i = 0; i < vfe->nclocks; i++) { + struct camss_clock *clock = &vfe->clock[i]; + + clock->clk = devm_clk_get(dev, res->clock[i]); + if (IS_ERR(clock->clk)) + return PTR_ERR(clock->clk); + + clock->name = res->clock[i]; + + clock->nfreqs = 0; + while (res->clock_rate[i][clock->nfreqs]) + clock->nfreqs++; + + if (!clock->nfreqs) { + clock->freq = NULL; + continue; + } + + clock->freq = devm_kcalloc(dev, + clock->nfreqs, + sizeof(*clock->freq), + GFP_KERNEL); + if (!clock->freq) + return -ENOMEM; + + for (j = 0; j < clock->nfreqs; j++) + clock->freq[j] = res->clock_rate[i][j]; + } + + mutex_init(&vfe->power_lock); + vfe->power_count = 0; + + mutex_init(&vfe->stream_lock); + vfe->stream_count = 0; + + spin_lock_init(&vfe->output_lock); + + vfe->camss = camss; + vfe->id = id; + vfe->reg_update = 0; + + for (i = VFE_LINE_RDI0; i < vfe->line_num; i++) { + struct vfe_line *l = &vfe->line[i]; + + l->video_out.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + l->video_out.camss = camss; + l->id = i; + init_completion(&l->output.sof); + init_completion(&l->output.reg_update); + + if (camss->version == CAMSS_8x16) { + if (i == VFE_LINE_PIX) { + l->formats = formats_pix_8x16; + l->nformats = ARRAY_SIZE(formats_pix_8x16); + } else { + l->formats = formats_rdi_8x16; + l->nformats = ARRAY_SIZE(formats_rdi_8x16); + } + } else if (camss->version == CAMSS_8x96 || + camss->version == CAMSS_660) { + if (i == VFE_LINE_PIX) { + l->formats = formats_pix_8x96; + l->nformats = ARRAY_SIZE(formats_pix_8x96); + } else { + l->formats = formats_rdi_8x96; + l->nformats = ARRAY_SIZE(formats_rdi_8x96); + } + } else if (camss->version == CAMSS_845 || + camss->version == CAMSS_8250) { + l->formats = formats_rdi_845; + l->nformats = ARRAY_SIZE(formats_rdi_845); + } else { + return -EINVAL; + } + } + + init_completion(&vfe->reset_complete); + init_completion(&vfe->halt_complete); + + return 0; +} + +/* + * vfe_link_setup - Setup VFE connections + * @entity: Pointer to media entity structure + * @local: Pointer to local pad + * @remote: Pointer to remote pad + * @flags: Link flags + * + * Return 0 on success + */ +static int vfe_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (flags & MEDIA_LNK_FL_ENABLED) + if (media_pad_remote_pad_first(local)) + return -EBUSY; + + return 0; +} + +static const struct v4l2_subdev_core_ops vfe_core_ops = { + .s_power = vfe_set_power, +}; + +static const struct v4l2_subdev_video_ops vfe_video_ops = { + .s_stream = vfe_set_stream, +}; + +static const struct v4l2_subdev_pad_ops vfe_pad_ops = { + .enum_mbus_code = vfe_enum_mbus_code, + .enum_frame_size = vfe_enum_frame_size, + .get_fmt = vfe_get_format, + .set_fmt = vfe_set_format, + .get_selection = vfe_get_selection, + .set_selection = vfe_set_selection, +}; + +static const struct v4l2_subdev_ops vfe_v4l2_ops = { + .core = &vfe_core_ops, + .video = &vfe_video_ops, + .pad = &vfe_pad_ops, +}; + +static const struct v4l2_subdev_internal_ops vfe_v4l2_internal_ops = { + .open = vfe_init_formats, +}; + +static const struct media_entity_operations vfe_media_ops = { + .link_setup = vfe_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +/* + * msm_vfe_register_entities - Register subdev node for VFE module + * @vfe: VFE device + * @v4l2_dev: V4L2 device + * + * Initialize and register a subdev node for the VFE module. Then + * call msm_video_register() to register the video device node which + * will be connected to this subdev node. Then actually create the + * media link between them. + * + * Return 0 on success or a negative error code otherwise + */ +int msm_vfe_register_entities(struct vfe_device *vfe, + struct v4l2_device *v4l2_dev) +{ + struct device *dev = vfe->camss->dev; + struct v4l2_subdev *sd; + struct media_pad *pads; + struct camss_video *video_out; + int ret; + int i; + + for (i = 0; i < vfe->line_num; i++) { + char name[32]; + + sd = &vfe->line[i].subdev; + pads = vfe->line[i].pads; + video_out = &vfe->line[i].video_out; + + v4l2_subdev_init(sd, &vfe_v4l2_ops); + sd->internal_ops = &vfe_v4l2_internal_ops; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + if (i == VFE_LINE_PIX) + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s", + MSM_VFE_NAME, vfe->id, "pix"); + else + snprintf(sd->name, ARRAY_SIZE(sd->name), "%s%d_%s%d", + MSM_VFE_NAME, vfe->id, "rdi", i); + + v4l2_set_subdevdata(sd, &vfe->line[i]); + + ret = vfe_init_formats(sd, NULL); + if (ret < 0) { + dev_err(dev, "Failed to init format: %d\n", ret); + goto error_init; + } + + pads[MSM_VFE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[MSM_VFE_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE; + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->entity.ops = &vfe_media_ops; + ret = media_entity_pads_init(&sd->entity, MSM_VFE_PADS_NUM, + pads); + if (ret < 0) { + dev_err(dev, "Failed to init media entity: %d\n", ret); + goto error_init; + } + + ret = v4l2_device_register_subdev(v4l2_dev, sd); + if (ret < 0) { + dev_err(dev, "Failed to register subdev: %d\n", ret); + goto error_reg_subdev; + } + + video_out->ops = &vfe->video_ops; + if (vfe->camss->version == CAMSS_845 || + vfe->camss->version == CAMSS_8250) + video_out->bpl_alignment = 16; + else + video_out->bpl_alignment = 8; + video_out->line_based = 0; + if (i == VFE_LINE_PIX) { + video_out->bpl_alignment = 16; + video_out->line_based = 1; + } + snprintf(name, ARRAY_SIZE(name), "%s%d_%s%d", + MSM_VFE_NAME, vfe->id, "video", i); + ret = msm_video_register(video_out, v4l2_dev, name, + i == VFE_LINE_PIX ? 1 : 0); + if (ret < 0) { + dev_err(dev, "Failed to register video node: %d\n", + ret); + goto error_reg_video; + } + + ret = media_create_pad_link( + &sd->entity, MSM_VFE_PAD_SRC, + &video_out->vdev.entity, 0, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret < 0) { + dev_err(dev, "Failed to link %s->%s entities: %d\n", + sd->entity.name, video_out->vdev.entity.name, + ret); + goto error_link; + } + } + + return 0; + +error_link: + msm_video_unregister(video_out); + +error_reg_video: + v4l2_device_unregister_subdev(sd); + +error_reg_subdev: + media_entity_cleanup(&sd->entity); + +error_init: + for (i--; i >= 0; i--) { + sd = &vfe->line[i].subdev; + video_out = &vfe->line[i].video_out; + + msm_video_unregister(video_out); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } + + return ret; +} + +/* + * msm_vfe_unregister_entities - Unregister VFE module subdev node + * @vfe: VFE device + */ +void msm_vfe_unregister_entities(struct vfe_device *vfe) +{ + int i; + + mutex_destroy(&vfe->power_lock); + mutex_destroy(&vfe->stream_lock); + + for (i = 0; i < vfe->line_num; i++) { + struct v4l2_subdev *sd = &vfe->line[i].subdev; + struct camss_video *video_out = &vfe->line[i].video_out; + + msm_video_unregister(video_out); + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + } +} diff --git a/drivers/media/platform/qcom/camss/camss-vfe.h b/drivers/media/platform/qcom/camss/camss-vfe.h new file mode 100644 index 000000000..cbc314c4e --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-vfe.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-vfe.h + * + * Qualcomm MSM Camera Subsystem - VFE (Video Front End) Module + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_VFE_H +#define QC_MSM_CAMSS_VFE_H + +#include +#include +#include +#include +#include + +#include "camss-video.h" +#include "camss-vfe-gen1.h" + +#define MSM_VFE_PAD_SINK 0 +#define MSM_VFE_PAD_SRC 1 +#define MSM_VFE_PADS_NUM 2 + +#define MSM_VFE_IMAGE_MASTERS_NUM 7 +#define MSM_VFE_COMPOSITE_IRQ_NUM 4 + +/* VFE halt timeout */ +#define VFE_HALT_TIMEOUT_MS 100 +/* Frame drop value. VAL + UPDATES - 1 should not exceed 31 */ +#define VFE_FRAME_DROP_VAL 30 + +#define vfe_line_array(ptr_line) \ + ((const struct vfe_line (*)[]) &(ptr_line)[-(ptr_line)->id]) + +#define to_vfe(ptr_line) \ + container_of(vfe_line_array(ptr_line), struct vfe_device, line) + +enum vfe_output_state { + VFE_OUTPUT_OFF, + VFE_OUTPUT_RESERVED, + VFE_OUTPUT_SINGLE, + VFE_OUTPUT_CONTINUOUS, + VFE_OUTPUT_IDLE, + VFE_OUTPUT_STOPPING, + VFE_OUTPUT_ON, +}; + +enum vfe_line_id { + VFE_LINE_NONE = -1, + VFE_LINE_RDI0 = 0, + VFE_LINE_RDI1 = 1, + VFE_LINE_RDI2 = 2, + VFE_LINE_NUM_GEN2 = 3, + VFE_LINE_PIX = 3, + VFE_LINE_NUM_GEN1 = 4, + VFE_LINE_NUM_MAX = 4 +}; + +struct vfe_output { + u8 wm_num; + u8 wm_idx[3]; + + struct camss_buffer *buf[2]; + struct camss_buffer *last_buffer; + struct list_head pending_bufs; + + unsigned int drop_update_idx; + + union { + struct { + int active_buf; + int wait_sof; + } gen1; + struct { + int active_num; + } gen2; + }; + enum vfe_output_state state; + unsigned int sequence; + + int wait_reg_update; + struct completion sof; + struct completion reg_update; +}; + +struct vfe_line { + enum vfe_line_id id; + struct v4l2_subdev subdev; + struct media_pad pads[MSM_VFE_PADS_NUM]; + struct v4l2_mbus_framefmt fmt[MSM_VFE_PADS_NUM]; + struct v4l2_rect compose; + struct v4l2_rect crop; + struct camss_video video_out; + struct vfe_output output; + const struct vfe_format *formats; + unsigned int nformats; +}; + +struct vfe_device; + +struct vfe_hw_ops { + void (*enable_irq_common)(struct vfe_device *vfe); + void (*global_reset)(struct vfe_device *vfe); + u32 (*hw_version)(struct vfe_device *vfe); + irqreturn_t (*isr)(int irq, void *dev); + void (*isr_read)(struct vfe_device *vfe, u32 *value0, u32 *value1); + void (*pm_domain_off)(struct vfe_device *vfe); + int (*pm_domain_on)(struct vfe_device *vfe); + void (*reg_update)(struct vfe_device *vfe, enum vfe_line_id line_id); + void (*reg_update_clear)(struct vfe_device *vfe, + enum vfe_line_id line_id); + void (*subdev_init)(struct device *dev, struct vfe_device *vfe); + int (*vfe_disable)(struct vfe_line *line); + int (*vfe_enable)(struct vfe_line *line); + int (*vfe_halt)(struct vfe_device *vfe); + void (*violation_read)(struct vfe_device *vfe); +}; + +struct vfe_isr_ops { + void (*reset_ack)(struct vfe_device *vfe); + void (*halt_ack)(struct vfe_device *vfe); + void (*reg_update)(struct vfe_device *vfe, enum vfe_line_id line_id); + void (*sof)(struct vfe_device *vfe, enum vfe_line_id line_id); + void (*comp_done)(struct vfe_device *vfe, u8 comp); + void (*wm_done)(struct vfe_device *vfe, u8 wm); +}; + +struct vfe_device { + struct camss *camss; + u8 id; + void __iomem *base; + u32 irq; + char irq_name[30]; + struct camss_clock *clock; + int nclocks; + struct completion reset_complete; + struct completion halt_complete; + struct mutex power_lock; + int power_count; + struct mutex stream_lock; + int stream_count; + spinlock_t output_lock; + enum vfe_line_id wm_output_map[MSM_VFE_IMAGE_MASTERS_NUM]; + struct vfe_line line[VFE_LINE_NUM_MAX]; + u8 line_num; + u32 reg_update; + u8 was_streaming; + const struct vfe_hw_ops *ops; + const struct vfe_hw_ops_gen1 *ops_gen1; + struct vfe_isr_ops isr_ops; + struct camss_video_ops video_ops; +}; + +struct resources; + +int msm_vfe_subdev_init(struct camss *camss, struct vfe_device *vfe, + const struct resources *res, u8 id); + +int msm_vfe_register_entities(struct vfe_device *vfe, + struct v4l2_device *v4l2_dev); + +void msm_vfe_unregister_entities(struct vfe_device *vfe); + +/* + * vfe_buf_add_pending - Add output buffer to list of pending + * @output: VFE output + * @buffer: Video buffer + */ +void vfe_buf_add_pending(struct vfe_output *output, struct camss_buffer *buffer); + +struct camss_buffer *vfe_buf_get_pending(struct vfe_output *output); + +int vfe_flush_buffers(struct camss_video *vid, enum vb2_buffer_state state); + +/* + * vfe_isr_comp_done - Process composite image done interrupt + * @vfe: VFE Device + * @comp: Composite image id + */ +void vfe_isr_comp_done(struct vfe_device *vfe, u8 comp); + +void vfe_isr_reset_ack(struct vfe_device *vfe); +int vfe_put_output(struct vfe_line *line); +int vfe_release_wm(struct vfe_device *vfe, u8 wm); +int vfe_reserve_wm(struct vfe_device *vfe, enum vfe_line_id line_id); + +/* + * vfe_reset - Trigger reset on VFE module and wait to complete + * @vfe: VFE device + * + * Return 0 on success or a negative error code otherwise + */ +int vfe_reset(struct vfe_device *vfe); + +extern const struct vfe_hw_ops vfe_ops_4_1; +extern const struct vfe_hw_ops vfe_ops_4_7; +extern const struct vfe_hw_ops vfe_ops_4_8; +extern const struct vfe_hw_ops vfe_ops_170; +extern const struct vfe_hw_ops vfe_ops_480; + +int vfe_get(struct vfe_device *vfe); +void vfe_put(struct vfe_device *vfe); + +#endif /* QC_MSM_CAMSS_VFE_H */ diff --git a/drivers/media/platform/qcom/camss/camss-video.c b/drivers/media/platform/qcom/camss/camss-video.c new file mode 100644 index 000000000..41deda232 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-video.c @@ -0,0 +1,1063 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss-video.c + * + * Qualcomm MSM Camera Subsystem - V4L2 device node + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include + +#include "camss-video.h" +#include "camss.h" + +#define CAMSS_FRAME_MIN_WIDTH 1 +#define CAMSS_FRAME_MAX_WIDTH 8191 +#define CAMSS_FRAME_MIN_HEIGHT 1 +#define CAMSS_FRAME_MAX_HEIGHT_RDI 8191 +#define CAMSS_FRAME_MAX_HEIGHT_PIX 4096 + +struct fract { + u8 numerator; + u8 denominator; +}; + +/* + * struct camss_format_info - ISP media bus format information + * @code: V4L2 media bus format code + * @pixelformat: V4L2 pixel format FCC identifier + * @planes: Number of planes + * @hsub: Horizontal subsampling (for each plane) + * @vsub: Vertical subsampling (for each plane) + * @bpp: Bits per pixel when stored in memory (for each plane) + */ +struct camss_format_info { + u32 code; + u32 pixelformat; + u8 planes; + struct fract hsub[3]; + struct fract vsub[3]; + unsigned int bpp[3]; +}; + +static const struct camss_format_info formats_rdi_8x16[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_UYVY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_VYUY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_YUYV, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_YVYU, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_PIX_FMT_SBGGR8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_PIX_FMT_SGBRG8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_PIX_FMT_SGRBG8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_PIX_FMT_SRGGB8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_PIX_FMT_SBGGR10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_PIX_FMT_SGBRG10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_PIX_FMT_SGRBG10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_PIX_FMT_SRGGB10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SBGGR12_1X12, V4L2_PIX_FMT_SBGGR12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SGBRG12_1X12, V4L2_PIX_FMT_SGBRG12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SGRBG12_1X12, V4L2_PIX_FMT_SGRBG12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SRGGB12_1X12, V4L2_PIX_FMT_SRGGB12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_Y10_1X10, V4L2_PIX_FMT_Y10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, +}; + +static const struct camss_format_info formats_rdi_8x96[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_UYVY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_VYUY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_YUYV, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_YVYU, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_PIX_FMT_SBGGR8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_PIX_FMT_SGBRG8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_PIX_FMT_SGRBG8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_PIX_FMT_SRGGB8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_PIX_FMT_SBGGR10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_PIX_FMT_SGBRG10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_PIX_FMT_SGRBG10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_PIX_FMT_SRGGB10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, V4L2_PIX_FMT_SBGGR10, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_SBGGR12_1X12, V4L2_PIX_FMT_SBGGR12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SGBRG12_1X12, V4L2_PIX_FMT_SGBRG12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SGRBG12_1X12, V4L2_PIX_FMT_SGRBG12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SRGGB12_1X12, V4L2_PIX_FMT_SRGGB12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SBGGR14_1X14, V4L2_PIX_FMT_SBGGR14P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 14 } }, + { MEDIA_BUS_FMT_SGBRG14_1X14, V4L2_PIX_FMT_SGBRG14P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 14 } }, + { MEDIA_BUS_FMT_SGRBG14_1X14, V4L2_PIX_FMT_SGRBG14P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 14 } }, + { MEDIA_BUS_FMT_SRGGB14_1X14, V4L2_PIX_FMT_SRGGB14P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 14 } }, + { MEDIA_BUS_FMT_Y10_1X10, V4L2_PIX_FMT_Y10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_Y10_2X8_PADHI_LE, V4L2_PIX_FMT_Y10, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, +}; + +static const struct camss_format_info formats_rdi_845[] = { + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_UYVY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_VYUY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_YUYV, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_YVYU, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_PIX_FMT_SBGGR8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_PIX_FMT_SGBRG8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_PIX_FMT_SGRBG8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_PIX_FMT_SRGGB8, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_SBGGR10_1X10, V4L2_PIX_FMT_SBGGR10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGBRG10_1X10, V4L2_PIX_FMT_SGBRG10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SGRBG10_1X10, V4L2_PIX_FMT_SGRBG10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SRGGB10_1X10, V4L2_PIX_FMT_SRGGB10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_SBGGR10_2X8_PADHI_LE, V4L2_PIX_FMT_SBGGR10, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_SBGGR12_1X12, V4L2_PIX_FMT_SBGGR12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SGBRG12_1X12, V4L2_PIX_FMT_SGBRG12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SGRBG12_1X12, V4L2_PIX_FMT_SGRBG12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SRGGB12_1X12, V4L2_PIX_FMT_SRGGB12P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 12 } }, + { MEDIA_BUS_FMT_SBGGR14_1X14, V4L2_PIX_FMT_SBGGR14P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 14 } }, + { MEDIA_BUS_FMT_SGBRG14_1X14, V4L2_PIX_FMT_SGBRG14P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 14 } }, + { MEDIA_BUS_FMT_SGRBG14_1X14, V4L2_PIX_FMT_SGRBG14P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 14 } }, + { MEDIA_BUS_FMT_SRGGB14_1X14, V4L2_PIX_FMT_SRGGB14P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 14 } }, + { MEDIA_BUS_FMT_Y8_1X8, V4L2_PIX_FMT_GREY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 8 } }, + { MEDIA_BUS_FMT_Y10_1X10, V4L2_PIX_FMT_Y10P, 1, + { { 1, 1 } }, { { 1, 1 } }, { 10 } }, + { MEDIA_BUS_FMT_Y10_2X8_PADHI_LE, V4L2_PIX_FMT_Y10, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, +}; + +static const struct camss_format_info formats_pix_8x16[] = { + { MEDIA_BUS_FMT_YUYV8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, +}; + +static const struct camss_format_info formats_pix_8x96[] = { + { MEDIA_BUS_FMT_YUYV8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_1_5X8, V4L2_PIX_FMT_NV12, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_1_5X8, V4L2_PIX_FMT_NV21, 1, + { { 1, 1 } }, { { 2, 3 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_NV16, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_NV61, 1, + { { 1, 1 } }, { { 1, 2 } }, { 8 } }, + { MEDIA_BUS_FMT_UYVY8_2X8, V4L2_PIX_FMT_UYVY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_VYUY8_2X8, V4L2_PIX_FMT_VYUY, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YUYV8_2X8, V4L2_PIX_FMT_YUYV, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, + { MEDIA_BUS_FMT_YVYU8_2X8, V4L2_PIX_FMT_YVYU, 1, + { { 1, 1 } }, { { 1, 1 } }, { 16 } }, +}; + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static int video_find_format(u32 code, u32 pixelformat, + const struct camss_format_info *formats, + unsigned int nformats) +{ + int i; + + for (i = 0; i < nformats; i++) { + if (formats[i].code == code && + formats[i].pixelformat == pixelformat) + return i; + } + + for (i = 0; i < nformats; i++) + if (formats[i].code == code) + return i; + + WARN_ON(1); + + return -EINVAL; +} + +/* + * video_mbus_to_pix_mp - Convert v4l2_mbus_framefmt to v4l2_pix_format_mplane + * @mbus: v4l2_mbus_framefmt format (input) + * @pix: v4l2_pix_format_mplane format (output) + * @f: a pointer to formats array element to be used for the conversion + * @alignment: bytesperline alignment value + * + * Fill the output pix structure with information from the input mbus format. + * + * Return 0 on success or a negative error code otherwise + */ +static int video_mbus_to_pix_mp(const struct v4l2_mbus_framefmt *mbus, + struct v4l2_pix_format_mplane *pix, + const struct camss_format_info *f, + unsigned int alignment) +{ + unsigned int i; + u32 bytesperline; + + memset(pix, 0, sizeof(*pix)); + v4l2_fill_pix_format_mplane(pix, mbus); + pix->pixelformat = f->pixelformat; + pix->num_planes = f->planes; + for (i = 0; i < pix->num_planes; i++) { + bytesperline = pix->width / f->hsub[i].numerator * + f->hsub[i].denominator * f->bpp[i] / 8; + bytesperline = ALIGN(bytesperline, alignment); + pix->plane_fmt[i].bytesperline = bytesperline; + pix->plane_fmt[i].sizeimage = pix->height / + f->vsub[i].numerator * f->vsub[i].denominator * + bytesperline; + } + + return 0; +} + +static struct v4l2_subdev *video_remote_subdev(struct camss_video *video, + u32 *pad) +{ + struct media_pad *remote; + + remote = media_pad_remote_pad_first(&video->pad); + + if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) + return NULL; + + if (pad) + *pad = remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +static int video_get_subdev_format(struct camss_video *video, + struct v4l2_format *format) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + u32 pad; + int ret; + + subdev = video_remote_subdev(video, &pad); + if (subdev == NULL) + return -EPIPE; + + fmt.pad = pad; + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + ret = video_find_format(fmt.format.code, + format->fmt.pix_mp.pixelformat, + video->formats, video->nformats); + if (ret < 0) + return ret; + + format->type = video->type; + + return video_mbus_to_pix_mp(&fmt.format, &format->fmt.pix_mp, + &video->formats[ret], video->bpl_alignment); +} + +/* ----------------------------------------------------------------------------- + * Video queue operations + */ + +static int video_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct camss_video *video = vb2_get_drv_priv(q); + const struct v4l2_pix_format_mplane *format = + &video->active_fmt.fmt.pix_mp; + unsigned int i; + + if (*num_planes) { + if (*num_planes != format->num_planes) + return -EINVAL; + + for (i = 0; i < *num_planes; i++) + if (sizes[i] < format->plane_fmt[i].sizeimage) + return -EINVAL; + + return 0; + } + + *num_planes = format->num_planes; + + for (i = 0; i < *num_planes; i++) + sizes[i] = format->plane_fmt[i].sizeimage; + + return 0; +} + +static int video_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer, + vb); + const struct v4l2_pix_format_mplane *format = + &video->active_fmt.fmt.pix_mp; + struct sg_table *sgt; + unsigned int i; + + for (i = 0; i < format->num_planes; i++) { + sgt = vb2_dma_sg_plane_desc(vb, i); + if (!sgt) + return -EFAULT; + + buffer->addr[i] = sg_dma_address(sgt->sgl); + } + + if (format->pixelformat == V4L2_PIX_FMT_NV12 || + format->pixelformat == V4L2_PIX_FMT_NV21 || + format->pixelformat == V4L2_PIX_FMT_NV16 || + format->pixelformat == V4L2_PIX_FMT_NV61) + buffer->addr[1] = buffer->addr[0] + + format->plane_fmt[0].bytesperline * + format->height; + + return 0; +} + +static int video_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); + const struct v4l2_pix_format_mplane *format = + &video->active_fmt.fmt.pix_mp; + unsigned int i; + + for (i = 0; i < format->num_planes; i++) { + if (format->plane_fmt[i].sizeimage > vb2_plane_size(vb, i)) + return -EINVAL; + + vb2_set_plane_payload(vb, i, format->plane_fmt[i].sizeimage); + } + + vbuf->field = V4L2_FIELD_NONE; + + return 0; +} + +static void video_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct camss_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct camss_buffer *buffer = container_of(vbuf, struct camss_buffer, + vb); + + video->ops->queue_buffer(video, buffer); +} + +static int video_check_format(struct camss_video *video) +{ + struct v4l2_pix_format_mplane *pix = &video->active_fmt.fmt.pix_mp; + struct v4l2_format format; + struct v4l2_pix_format_mplane *sd_pix = &format.fmt.pix_mp; + int ret; + + sd_pix->pixelformat = pix->pixelformat; + ret = video_get_subdev_format(video, &format); + if (ret < 0) + return ret; + + if (pix->pixelformat != sd_pix->pixelformat || + pix->height != sd_pix->height || + pix->width != sd_pix->width || + pix->num_planes != sd_pix->num_planes || + pix->field != format.fmt.pix_mp.field) + return -EPIPE; + + return 0; +} + +static int video_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct camss_video *video = vb2_get_drv_priv(q); + struct video_device *vdev = &video->vdev; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + int ret; + + ret = video_device_pipeline_start(vdev, &video->pipe); + if (ret < 0) + goto flush_buffers; + + ret = video_check_format(video); + if (ret < 0) + goto error; + + entity = &vdev->entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto error; + } + + return 0; + +error: + video_device_pipeline_stop(vdev); + +flush_buffers: + video->ops->flush_buffers(video, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void video_stop_streaming(struct vb2_queue *q) +{ + struct camss_video *video = vb2_get_drv_priv(q); + struct video_device *vdev = &video->vdev; + struct media_entity *entity; + struct media_pad *pad; + struct v4l2_subdev *subdev; + + entity = &vdev->entity; + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + v4l2_subdev_call(subdev, video, s_stream, 0); + } + + video_device_pipeline_stop(vdev); + + video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops msm_video_vb2_q_ops = { + .queue_setup = video_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_init = video_buf_init, + .buf_prepare = video_buf_prepare, + .buf_queue = video_buf_queue, + .start_streaming = video_start_streaming, + .stop_streaming = video_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int video_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, "qcom-camss", sizeof(cap->driver)); + strscpy(cap->card, "Qualcomm Camera Subsystem", sizeof(cap->card)); + + return 0; +} + +static int video_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct camss_video *video = video_drvdata(file); + int i, j, k; + u32 mcode = f->mbus_code; + + if (f->type != video->type) + return -EINVAL; + + if (f->index >= video->nformats) + return -EINVAL; + + /* + * Find index "i" of "k"th unique pixelformat in formats array. + * + * If f->mbus_code passed to video_enum_fmt() is not zero, a device + * with V4L2_CAP_IO_MC capability restricts enumeration to only the + * pixel formats that can be produced from that media bus code. + * This is implemented by skipping video->formats[] entries with + * code != f->mbus_code (if f->mbus_code is not zero). + * If the f->mbus_code passed to video_enum_fmt() is not supported, + * -EINVAL is returned. + * If f->mbus_code is zero, all the pixel formats are enumerated. + */ + k = -1; + for (i = 0; i < video->nformats; i++) { + if (mcode != 0 && video->formats[i].code != mcode) + continue; + + for (j = 0; j < i; j++) { + if (mcode != 0 && video->formats[j].code != mcode) + continue; + if (video->formats[i].pixelformat == + video->formats[j].pixelformat) + break; + } + + if (j == i) + k++; + + if (k == f->index) + break; + } + + if (k == -1 || k < f->index) + /* + * All the unique pixel formats matching the arguments + * have been enumerated (k >= 0 and f->index > 0), or + * no pixel formats match the non-zero f->mbus_code (k == -1). + */ + return -EINVAL; + + f->pixelformat = video->formats[i].pixelformat; + + return 0; +} + +static int video_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct camss_video *video = video_drvdata(file); + int i; + + if (fsize->index) + return -EINVAL; + + /* Only accept pixel format present in the formats[] table */ + for (i = 0; i < video->nformats; i++) { + if (video->formats[i].pixelformat == fsize->pixel_format) + break; + } + + if (i == video->nformats) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; + fsize->stepwise.min_width = CAMSS_FRAME_MIN_WIDTH; + fsize->stepwise.max_width = CAMSS_FRAME_MAX_WIDTH; + fsize->stepwise.min_height = CAMSS_FRAME_MIN_HEIGHT; + fsize->stepwise.max_height = (video->line_based) ? + CAMSS_FRAME_MAX_HEIGHT_PIX : CAMSS_FRAME_MAX_HEIGHT_RDI; + fsize->stepwise.step_width = 1; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int video_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct camss_video *video = video_drvdata(file); + + *f = video->active_fmt; + + return 0; +} + +static int __video_try_fmt(struct camss_video *video, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pix_mp; + const struct camss_format_info *fi; + struct v4l2_plane_pix_format *p; + u32 bytesperline[3] = { 0 }; + u32 sizeimage[3] = { 0 }; + u32 width, height; + u32 bpl, lines; + int i, j; + + pix_mp = &f->fmt.pix_mp; + + if (video->line_based) + for (i = 0; i < pix_mp->num_planes && i < 3; i++) { + p = &pix_mp->plane_fmt[i]; + bytesperline[i] = clamp_t(u32, p->bytesperline, + 1, 65528); + sizeimage[i] = clamp_t(u32, p->sizeimage, + bytesperline[i], + bytesperline[i] * CAMSS_FRAME_MAX_HEIGHT_PIX); + } + + for (j = 0; j < video->nformats; j++) + if (pix_mp->pixelformat == video->formats[j].pixelformat) + break; + + if (j == video->nformats) + j = 0; /* default format */ + + fi = &video->formats[j]; + width = pix_mp->width; + height = pix_mp->height; + + memset(pix_mp, 0, sizeof(*pix_mp)); + + pix_mp->pixelformat = fi->pixelformat; + pix_mp->width = clamp_t(u32, width, 1, CAMSS_FRAME_MAX_WIDTH); + pix_mp->height = clamp_t(u32, height, 1, CAMSS_FRAME_MAX_HEIGHT_RDI); + pix_mp->num_planes = fi->planes; + for (i = 0; i < pix_mp->num_planes; i++) { + bpl = pix_mp->width / fi->hsub[i].numerator * + fi->hsub[i].denominator * fi->bpp[i] / 8; + bpl = ALIGN(bpl, video->bpl_alignment); + pix_mp->plane_fmt[i].bytesperline = bpl; + pix_mp->plane_fmt[i].sizeimage = pix_mp->height / + fi->vsub[i].numerator * fi->vsub[i].denominator * bpl; + } + + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->colorspace = V4L2_COLORSPACE_SRGB; + pix_mp->flags = 0; + pix_mp->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix_mp->colorspace); + pix_mp->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, + pix_mp->colorspace, pix_mp->ycbcr_enc); + pix_mp->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix_mp->colorspace); + + if (video->line_based) + for (i = 0; i < pix_mp->num_planes; i++) { + p = &pix_mp->plane_fmt[i]; + p->bytesperline = clamp_t(u32, p->bytesperline, + 1, 65528); + p->sizeimage = clamp_t(u32, p->sizeimage, + p->bytesperline, + p->bytesperline * CAMSS_FRAME_MAX_HEIGHT_PIX); + lines = p->sizeimage / p->bytesperline; + + if (p->bytesperline < bytesperline[i]) + p->bytesperline = ALIGN(bytesperline[i], 8); + + if (p->sizeimage < p->bytesperline * lines) + p->sizeimage = p->bytesperline * lines; + + if (p->sizeimage < sizeimage[i]) + p->sizeimage = sizeimage[i]; + } + + return 0; +} + +static int video_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct camss_video *video = video_drvdata(file); + + return __video_try_fmt(video, f); +} + +static int video_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct camss_video *video = video_drvdata(file); + int ret; + + if (vb2_is_busy(&video->vb2_q)) + return -EBUSY; + + ret = __video_try_fmt(video, f); + if (ret < 0) + return ret; + + video->active_fmt = *f; + + return 0; +} + +static int video_enum_input(struct file *file, void *fh, + struct v4l2_input *input) +{ + if (input->index > 0) + return -EINVAL; + + strscpy(input->name, "camera", sizeof(input->name)); + input->type = V4L2_INPUT_TYPE_CAMERA; + + return 0; +} + +static int video_g_input(struct file *file, void *fh, unsigned int *input) +{ + *input = 0; + + return 0; +} + +static int video_s_input(struct file *file, void *fh, unsigned int input) +{ + return input == 0 ? 0 : -EINVAL; +} + +static const struct v4l2_ioctl_ops msm_vid_ioctl_ops = { + .vidioc_querycap = video_querycap, + .vidioc_enum_fmt_vid_cap = video_enum_fmt, + .vidioc_enum_framesizes = video_enum_framesizes, + .vidioc_g_fmt_vid_cap_mplane = video_g_fmt, + .vidioc_s_fmt_vid_cap_mplane = video_s_fmt, + .vidioc_try_fmt_vid_cap_mplane = video_try_fmt, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_input = video_enum_input, + .vidioc_g_input = video_g_input, + .vidioc_s_input = video_s_input, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 file operations + */ + +static int video_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct camss_video *video = video_drvdata(file); + struct v4l2_fh *vfh; + int ret; + + mutex_lock(&video->lock); + + vfh = kzalloc(sizeof(*vfh), GFP_KERNEL); + if (vfh == NULL) { + ret = -ENOMEM; + goto error_alloc; + } + + v4l2_fh_init(vfh, vdev); + v4l2_fh_add(vfh); + + file->private_data = vfh; + + ret = v4l2_pipeline_pm_get(&vdev->entity); + if (ret < 0) { + dev_err(video->camss->dev, "Failed to power up pipeline: %d\n", + ret); + goto error_pm_use; + } + + mutex_unlock(&video->lock); + + return 0; + +error_pm_use: + v4l2_fh_release(file); + +error_alloc: + mutex_unlock(&video->lock); + + return ret; +} + +static int video_release(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + + vb2_fop_release(file); + + v4l2_pipeline_pm_put(&vdev->entity); + + file->private_data = NULL; + + return 0; +} + +static const struct v4l2_file_operations msm_vid_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = video_open, + .release = video_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +/* ----------------------------------------------------------------------------- + * CAMSS video core + */ + +static void msm_video_release(struct video_device *vdev) +{ + struct camss_video *video = video_get_drvdata(vdev); + + media_entity_cleanup(&vdev->entity); + + mutex_destroy(&video->q_lock); + mutex_destroy(&video->lock); + + if (atomic_dec_and_test(&video->camss->ref_count)) + camss_delete(video->camss); +} + +/* + * msm_video_init_format - Helper function to initialize format + * @video: struct camss_video + * + * Initialize pad format with default value. + * + * Return 0 on success or a negative error code otherwise + */ +static int msm_video_init_format(struct camss_video *video) +{ + int ret; + struct v4l2_format format = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .fmt.pix_mp = { + .width = 1920, + .height = 1080, + .pixelformat = video->formats[0].pixelformat, + }, + }; + + ret = __video_try_fmt(video, &format); + if (ret < 0) + return ret; + + video->active_fmt = format; + + return 0; +} + +/* + * msm_video_register - Register a video device node + * @video: struct camss_video + * @v4l2_dev: V4L2 device + * @name: name to be used for the video device node + * + * Initialize and register a video device node to a V4L2 device. Also + * initialize the vb2 queue. + * + * Return 0 on success or a negative error code otherwise + */ + +int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev, + const char *name, int is_pix) +{ + struct media_pad *pad = &video->pad; + struct video_device *vdev; + struct vb2_queue *q; + int ret; + + vdev = &video->vdev; + + mutex_init(&video->q_lock); + + q = &video->vb2_q; + q->drv_priv = video; + q->mem_ops = &vb2_dma_sg_memops; + q->ops = &msm_video_vb2_q_ops; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_DMABUF | VB2_MMAP | VB2_READ; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->buf_struct_size = sizeof(struct camss_buffer); + q->dev = video->camss->dev; + q->lock = &video->q_lock; + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to init vb2 queue: %d\n", ret); + goto error_vb2_init; + } + + pad->flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, pad); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to init video entity: %d\n", + ret); + goto error_vb2_init; + } + + mutex_init(&video->lock); + + if (video->camss->version == CAMSS_8x16) { + if (is_pix) { + video->formats = formats_pix_8x16; + video->nformats = ARRAY_SIZE(formats_pix_8x16); + } else { + video->formats = formats_rdi_8x16; + video->nformats = ARRAY_SIZE(formats_rdi_8x16); + } + } else if (video->camss->version == CAMSS_8x96 || + video->camss->version == CAMSS_660) { + if (is_pix) { + video->formats = formats_pix_8x96; + video->nformats = ARRAY_SIZE(formats_pix_8x96); + } else { + video->formats = formats_rdi_8x96; + video->nformats = ARRAY_SIZE(formats_rdi_8x96); + } + } else if (video->camss->version == CAMSS_845 || + video->camss->version == CAMSS_8250) { + video->formats = formats_rdi_845; + video->nformats = ARRAY_SIZE(formats_rdi_845); + } else { + ret = -EINVAL; + goto error_video_register; + } + + ret = msm_video_init_format(video); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to init format: %d\n", ret); + goto error_video_register; + } + + vdev->fops = &msm_vid_fops; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING + | V4L2_CAP_READWRITE | V4L2_CAP_IO_MC; + vdev->ioctl_ops = &msm_vid_ioctl_ops; + vdev->release = msm_video_release; + vdev->v4l2_dev = v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->queue = &video->vb2_q; + vdev->lock = &video->lock; + strscpy(vdev->name, name, sizeof(vdev->name)); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + dev_err(v4l2_dev->dev, "Failed to register video device: %d\n", + ret); + goto error_video_register; + } + + video_set_drvdata(vdev, video); + atomic_inc(&video->camss->ref_count); + + return 0; + +error_video_register: + media_entity_cleanup(&vdev->entity); + mutex_destroy(&video->lock); +error_vb2_init: + mutex_destroy(&video->q_lock); + + return ret; +} + +void msm_video_unregister(struct camss_video *video) +{ + atomic_inc(&video->camss->ref_count); + vb2_video_unregister_device(&video->vdev); + atomic_dec(&video->camss->ref_count); +} diff --git a/drivers/media/platform/qcom/camss/camss-video.h b/drivers/media/platform/qcom/camss/camss-video.h new file mode 100644 index 000000000..bdbae8424 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss-video.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss-video.h + * + * Qualcomm MSM Camera Subsystem - V4L2 device node + * + * Copyright (c) 2013-2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_VIDEO_H +#define QC_MSM_CAMSS_VIDEO_H + +#include +#include +#include +#include +#include +#include +#include +#include + +struct camss_buffer { + struct vb2_v4l2_buffer vb; + dma_addr_t addr[3]; + struct list_head queue; +}; + +struct camss_video; + +struct camss_video_ops { + int (*queue_buffer)(struct camss_video *vid, struct camss_buffer *buf); + int (*flush_buffers)(struct camss_video *vid, + enum vb2_buffer_state state); +}; + +struct camss_format_info; + +struct camss_video { + struct camss *camss; + struct vb2_queue vb2_q; + struct video_device vdev; + struct media_pad pad; + struct v4l2_format active_fmt; + enum v4l2_buf_type type; + struct media_pipeline pipe; + const struct camss_video_ops *ops; + struct mutex lock; + struct mutex q_lock; + unsigned int bpl_alignment; + unsigned int line_based; + const struct camss_format_info *formats; + unsigned int nformats; +}; + +int msm_video_register(struct camss_video *video, struct v4l2_device *v4l2_dev, + const char *name, int is_pix); + +void msm_video_unregister(struct camss_video *video); + +#endif /* QC_MSM_CAMSS_VIDEO_H */ diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c new file mode 100644 index 000000000..a30461de3 --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss.c @@ -0,0 +1,1816 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * camss.c + * + * Qualcomm MSM Camera Subsystem - Core + * + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "camss.h" + +#define CAMSS_CLOCK_MARGIN_NUMERATOR 105 +#define CAMSS_CLOCK_MARGIN_DENOMINATOR 100 + +static const struct resources csiphy_res_8x16[] = { + /* CSIPHY0 */ + { + .regulators = {}, + .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy0_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000 } }, + .reg = { "csiphy0", "csiphy0_clk_mux" }, + .interrupt = { "csiphy0" } + }, + + /* CSIPHY1 */ + { + .regulators = {}, + .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy1_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000 } }, + .reg = { "csiphy1", "csiphy1_clk_mux" }, + .interrupt = { "csiphy1" } + } +}; + +static const struct resources csid_res_8x16[] = { + /* CSID0 */ + { + .regulators = { "vdda" }, + .clock = { "top_ahb", "ispif_ahb", "csi0_ahb", "ahb", + "csi0", "csi0_phy", "csi0_pix", "csi0_rdi" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" } + }, + + /* CSID1 */ + { + .regulators = { "vdda" }, + .clock = { "top_ahb", "ispif_ahb", "csi1_ahb", "ahb", + "csi1", "csi1_phy", "csi1_pix", "csi1_rdi" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" } + }, +}; + +static const struct resources_ispif ispif_res_8x16 = { + /* ISPIF */ + .clock = { "top_ahb", "ahb", "ispif_ahb", + "csi0", "csi0_pix", "csi0_rdi", + "csi1", "csi1_pix", "csi1_rdi" }, + .clock_for_reset = { "vfe0", "csi_vfe0" }, + .reg = { "ispif", "csi_clk_mux" }, + .interrupt = "ispif" + +}; + +static const struct resources vfe_res_8x16[] = { + /* VFE0 */ + { + .regulators = {}, + .clock = { "top_ahb", "vfe0", "csi_vfe0", + "vfe_ahb", "vfe_axi", "ahb" }, + .clock_rate = { { 0 }, + { 50000000, 80000000, 100000000, 160000000, + 177780000, 200000000, 266670000, 320000000, + 400000000, 465000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" } + } +}; + +static const struct resources csiphy_res_8x96[] = { + /* CSIPHY0 */ + { + .regulators = {}, + .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy0_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 266666667 } }, + .reg = { "csiphy0", "csiphy0_clk_mux" }, + .interrupt = { "csiphy0" } + }, + + /* CSIPHY1 */ + { + .regulators = {}, + .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy1_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 266666667 } }, + .reg = { "csiphy1", "csiphy1_clk_mux" }, + .interrupt = { "csiphy1" } + }, + + /* CSIPHY2 */ + { + .regulators = {}, + .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy2_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 266666667 } }, + .reg = { "csiphy2", "csiphy2_clk_mux" }, + .interrupt = { "csiphy2" } + } +}; + +static const struct resources csid_res_8x96[] = { + /* CSID0 */ + { + .regulators = { "vdda" }, + .clock = { "top_ahb", "ispif_ahb", "csi0_ahb", "ahb", + "csi0", "csi0_phy", "csi0_pix", "csi0_rdi" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 266666667 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" } + }, + + /* CSID1 */ + { + .regulators = { "vdda" }, + .clock = { "top_ahb", "ispif_ahb", "csi1_ahb", "ahb", + "csi1", "csi1_phy", "csi1_pix", "csi1_rdi" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 266666667 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" } + }, + + /* CSID2 */ + { + .regulators = { "vdda" }, + .clock = { "top_ahb", "ispif_ahb", "csi2_ahb", "ahb", + "csi2", "csi2_phy", "csi2_pix", "csi2_rdi" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 266666667 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid2" }, + .interrupt = { "csid2" } + }, + + /* CSID3 */ + { + .regulators = { "vdda" }, + .clock = { "top_ahb", "ispif_ahb", "csi3_ahb", "ahb", + "csi3", "csi3_phy", "csi3_pix", "csi3_rdi" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 266666667 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid3" }, + .interrupt = { "csid3" } + } +}; + +static const struct resources_ispif ispif_res_8x96 = { + /* ISPIF */ + .clock = { "top_ahb", "ahb", "ispif_ahb", + "csi0", "csi0_pix", "csi0_rdi", + "csi1", "csi1_pix", "csi1_rdi", + "csi2", "csi2_pix", "csi2_rdi", + "csi3", "csi3_pix", "csi3_rdi" }, + .clock_for_reset = { "vfe0", "csi_vfe0", "vfe1", "csi_vfe1" }, + .reg = { "ispif", "csi_clk_mux" }, + .interrupt = "ispif" +}; + +static const struct resources vfe_res_8x96[] = { + /* VFE0 */ + { + .regulators = {}, + .clock = { "top_ahb", "ahb", "vfe0", "csi_vfe0", "vfe_ahb", + "vfe0_ahb", "vfe_axi", "vfe0_stream"}, + .clock_rate = { { 0 }, + { 0 }, + { 75000000, 100000000, 300000000, + 320000000, 480000000, 600000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" } + }, + + /* VFE1 */ + { + .regulators = {}, + .clock = { "top_ahb", "ahb", "vfe1", "csi_vfe1", "vfe_ahb", + "vfe1_ahb", "vfe_axi", "vfe1_stream"}, + .clock_rate = { { 0 }, + { 0 }, + { 75000000, 100000000, 300000000, + 320000000, 480000000, 600000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "vfe1" }, + .interrupt = { "vfe1" } + } +}; + +static const struct resources csiphy_res_660[] = { + /* CSIPHY0 */ + { + .regulators = {}, + .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy0_timer", + "csi0_phy", "csiphy_ahb2crif" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 269333333 }, + { 0 } }, + .reg = { "csiphy0", "csiphy0_clk_mux" }, + .interrupt = { "csiphy0" } + }, + + /* CSIPHY1 */ + { + .regulators = {}, + .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy1_timer", + "csi1_phy", "csiphy_ahb2crif" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 269333333 }, + { 0 } }, + .reg = { "csiphy1", "csiphy1_clk_mux" }, + .interrupt = { "csiphy1" } + }, + + /* CSIPHY2 */ + { + .regulators = {}, + .clock = { "top_ahb", "ispif_ahb", "ahb", "csiphy2_timer", + "csi2_phy", "csiphy_ahb2crif" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 269333333 }, + { 0 } }, + .reg = { "csiphy2", "csiphy2_clk_mux" }, + .interrupt = { "csiphy2" } + } +}; + +static const struct resources csid_res_660[] = { + /* CSID0 */ + { + .regulators = { "vdda", "vdd_sec" }, + .clock = { "top_ahb", "ispif_ahb", "csi0_ahb", "ahb", + "csi0", "csi0_phy", "csi0_pix", "csi0_rdi", + "cphy_csid0" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 310000000, + 404000000, 465000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" } + }, + + /* CSID1 */ + { + .regulators = { "vdda", "vdd_sec" }, + .clock = { "top_ahb", "ispif_ahb", "csi1_ahb", "ahb", + "csi1", "csi1_phy", "csi1_pix", "csi1_rdi", + "cphy_csid1" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 310000000, + 404000000, 465000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" } + }, + + /* CSID2 */ + { + .regulators = { "vdda", "vdd_sec" }, + .clock = { "top_ahb", "ispif_ahb", "csi2_ahb", "ahb", + "csi2", "csi2_phy", "csi2_pix", "csi2_rdi", + "cphy_csid2" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 310000000, + 404000000, 465000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid2" }, + .interrupt = { "csid2" } + }, + + /* CSID3 */ + { + .regulators = { "vdda", "vdd_sec" }, + .clock = { "top_ahb", "ispif_ahb", "csi3_ahb", "ahb", + "csi3", "csi3_phy", "csi3_pix", "csi3_rdi", + "cphy_csid3" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 310000000, + 404000000, 465000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "csid3" }, + .interrupt = { "csid3" } + } +}; + +static const struct resources_ispif ispif_res_660 = { + /* ISPIF */ + .clock = { "top_ahb", "ahb", "ispif_ahb", + "csi0", "csi0_pix", "csi0_rdi", + "csi1", "csi1_pix", "csi1_rdi", + "csi2", "csi2_pix", "csi2_rdi", + "csi3", "csi3_pix", "csi3_rdi" }, + .clock_for_reset = { "vfe0", "csi_vfe0", "vfe1", "csi_vfe1" }, + .reg = { "ispif", "csi_clk_mux" }, + .interrupt = "ispif" +}; + +static const struct resources vfe_res_660[] = { + /* VFE0 */ + { + .regulators = {}, + .clock = { "throttle_axi", "top_ahb", "ahb", "vfe0", + "csi_vfe0", "vfe_ahb", "vfe0_ahb", "vfe_axi", + "vfe0_stream"}, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 120000000, 200000000, 256000000, + 300000000, 404000000, 480000000, + 540000000, 576000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" } + }, + + /* VFE1 */ + { + .regulators = {}, + .clock = { "throttle_axi", "top_ahb", "ahb", "vfe1", + "csi_vfe1", "vfe_ahb", "vfe1_ahb", "vfe_axi", + "vfe1_stream"}, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 120000000, 200000000, 256000000, + 300000000, 404000000, 480000000, + 540000000, 576000000 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 } }, + .reg = { "vfe1" }, + .interrupt = { "vfe1" } + } +}; + +static const struct resources csiphy_res_845[] = { + /* CSIPHY0 */ + { + .regulators = {}, + .clock = { "camnoc_axi", "soc_ahb", "slow_ahb_src", + "cpas_ahb", "cphy_rx_src", "csiphy0", + "csiphy0_timer_src", "csiphy0_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 19200000, 240000000, 269333333 } }, + .reg = { "csiphy0" }, + .interrupt = { "csiphy0" } + }, + + /* CSIPHY1 */ + { + .regulators = {}, + .clock = { "camnoc_axi", "soc_ahb", "slow_ahb_src", + "cpas_ahb", "cphy_rx_src", "csiphy1", + "csiphy1_timer_src", "csiphy1_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 19200000, 240000000, 269333333 } }, + .reg = { "csiphy1" }, + .interrupt = { "csiphy1" } + }, + + /* CSIPHY2 */ + { + .regulators = {}, + .clock = { "camnoc_axi", "soc_ahb", "slow_ahb_src", + "cpas_ahb", "cphy_rx_src", "csiphy2", + "csiphy2_timer_src", "csiphy2_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 19200000, 240000000, 269333333 } }, + .reg = { "csiphy2" }, + .interrupt = { "csiphy2" } + }, + + /* CSIPHY3 */ + { + .regulators = {}, + .clock = { "camnoc_axi", "soc_ahb", "slow_ahb_src", + "cpas_ahb", "cphy_rx_src", "csiphy3", + "csiphy3_timer_src", "csiphy3_timer" }, + .clock_rate = { { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 0 }, + { 19200000, 240000000, 269333333 } }, + .reg = { "csiphy3" }, + .interrupt = { "csiphy3" } + } +}; + +static const struct resources csid_res_845[] = { + /* CSID0 */ + { + .regulators = { "vdda-phy", "vdda-pll" }, + .clock = { "cpas_ahb", "cphy_rx_src", "slow_ahb_src", + "soc_ahb", "vfe0", "vfe0_src", + "vfe0_cphy_rx", "csi0", + "csi0_src" }, + .clock_rate = { { 0 }, + { 384000000 }, + { 80000000 }, + { 0 }, + { 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 }, + { 320000000 }, + { 0 }, + { 19200000, 75000000, 384000000, 538666667 }, + { 384000000 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" } + }, + + /* CSID1 */ + { + .regulators = { "vdda-phy", "vdda-pll" }, + .clock = { "cpas_ahb", "cphy_rx_src", "slow_ahb_src", + "soc_ahb", "vfe1", "vfe1_src", + "vfe1_cphy_rx", "csi1", + "csi1_src" }, + .clock_rate = { { 0 }, + { 384000000 }, + { 80000000 }, + { 0 }, + { 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 }, + { 320000000 }, + { 0 }, + { 19200000, 75000000, 384000000, 538666667 }, + { 384000000 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" } + }, + + /* CSID2 */ + { + .regulators = { "vdda-phy", "vdda-pll" }, + .clock = { "cpas_ahb", "cphy_rx_src", "slow_ahb_src", + "soc_ahb", "vfe_lite", "vfe_lite_src", + "vfe_lite_cphy_rx", "csi2", + "csi2_src" }, + .clock_rate = { { 0 }, + { 384000000 }, + { 80000000 }, + { 0 }, + { 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 }, + { 320000000 }, + { 0 }, + { 19200000, 75000000, 384000000, 538666667 }, + { 384000000 } }, + .reg = { "csid2" }, + .interrupt = { "csid2" } + } +}; + +static const struct resources vfe_res_845[] = { + /* VFE0 */ + { + .regulators = {}, + .clock = { "camnoc_axi", "cpas_ahb", "slow_ahb_src", + "soc_ahb", "vfe0", "vfe0_axi", + "vfe0_src", "csi0", + "csi0_src"}, + .clock_rate = { { 0 }, + { 0 }, + { 80000000 }, + { 0 }, + { 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 }, + { 0 }, + { 320000000 }, + { 19200000, 75000000, 384000000, 538666667 }, + { 384000000 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" } + }, + + /* VFE1 */ + { + .regulators = {}, + .clock = { "camnoc_axi", "cpas_ahb", "slow_ahb_src", + "soc_ahb", "vfe1", "vfe1_axi", + "vfe1_src", "csi1", + "csi1_src"}, + .clock_rate = { { 0 }, + { 0 }, + { 80000000 }, + { 0 }, + { 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 }, + { 0 }, + { 320000000 }, + { 19200000, 75000000, 384000000, 538666667 }, + { 384000000 } }, + .reg = { "vfe1" }, + .interrupt = { "vfe1" } + }, + + /* VFE-lite */ + { + .regulators = {}, + .clock = { "camnoc_axi", "cpas_ahb", "slow_ahb_src", + "soc_ahb", "vfe_lite", + "vfe_lite_src", "csi2", + "csi2_src"}, + .clock_rate = { { 0 }, + { 0 }, + { 80000000 }, + { 0 }, + { 19200000, 100000000, 320000000, 404000000, 480000000, 600000000 }, + { 320000000 }, + { 19200000, 75000000, 384000000, 538666667 }, + { 384000000 } }, + .reg = { "vfe_lite" }, + .interrupt = { "vfe_lite" } + } +}; + +static const struct resources csiphy_res_8250[] = { + /* CSIPHY0 */ + { + .regulators = {}, + .clock = { "csiphy0", "csiphy0_timer" }, + .clock_rate = { { 400000000 }, + { 300000000 } }, + .reg = { "csiphy0" }, + .interrupt = { "csiphy0" } + }, + /* CSIPHY1 */ + { + .regulators = {}, + .clock = { "csiphy1", "csiphy1_timer" }, + .clock_rate = { { 400000000 }, + { 300000000 } }, + .reg = { "csiphy1" }, + .interrupt = { "csiphy1" } + }, + /* CSIPHY2 */ + { + .regulators = {}, + .clock = { "csiphy2", "csiphy2_timer" }, + .clock_rate = { { 400000000 }, + { 300000000 } }, + .reg = { "csiphy2" }, + .interrupt = { "csiphy2" } + }, + /* CSIPHY3 */ + { + .regulators = {}, + .clock = { "csiphy3", "csiphy3_timer" }, + .clock_rate = { { 400000000 }, + { 300000000 } }, + .reg = { "csiphy3" }, + .interrupt = { "csiphy3" } + }, + /* CSIPHY4 */ + { + .regulators = {}, + .clock = { "csiphy4", "csiphy4_timer" }, + .clock_rate = { { 400000000 }, + { 300000000 } }, + .reg = { "csiphy4" }, + .interrupt = { "csiphy4" } + }, + /* CSIPHY5 */ + { + .regulators = {}, + .clock = { "csiphy5", "csiphy5_timer" }, + .clock_rate = { { 400000000 }, + { 300000000 } }, + .reg = { "csiphy5" }, + .interrupt = { "csiphy5" } + } +}; + +static const struct resources csid_res_8250[] = { + /* CSID0 */ + { + .regulators = { "vdda-phy", "vdda-pll" }, + .clock = { "vfe0_csid", "vfe0_cphy_rx", "vfe0", "vfe0_areg", "vfe0_ahb" }, + .clock_rate = { { 400000000 }, + { 400000000 }, + { 350000000, 475000000, 576000000, 720000000 }, + { 100000000, 200000000, 300000000, 400000000 }, + { 0 } }, + .reg = { "csid0" }, + .interrupt = { "csid0" } + }, + /* CSID1 */ + { + .regulators = { "vdda-phy", "vdda-pll" }, + .clock = { "vfe1_csid", "vfe1_cphy_rx", "vfe1", "vfe1_areg", "vfe1_ahb" }, + .clock_rate = { { 400000000 }, + { 400000000 }, + { 350000000, 475000000, 576000000, 720000000 }, + { 100000000, 200000000, 300000000, 400000000 }, + { 0 } }, + .reg = { "csid1" }, + .interrupt = { "csid1" } + }, + /* CSID2 */ + { + .regulators = { "vdda-phy", "vdda-pll" }, + .clock = { "vfe_lite_csid", "vfe_lite_cphy_rx", "vfe_lite", "vfe_lite_ahb" }, + .clock_rate = { { 400000000 }, + { 400000000 }, + { 400000000, 480000000 }, + { 0 } }, + .reg = { "csid2" }, + .interrupt = { "csid2" } + }, + /* CSID3 */ + { + .regulators = { "vdda-phy", "vdda-pll" }, + .clock = { "vfe_lite_csid", "vfe_lite_cphy_rx", "vfe_lite", "vfe_lite_ahb" }, + .clock_rate = { { 400000000 }, + { 400000000 }, + { 400000000, 480000000 }, + { 0 } }, + .reg = { "csid3" }, + .interrupt = { "csid3" } + } +}; + +static const struct resources vfe_res_8250[] = { + /* VFE0 */ + { + .regulators = {}, + .clock = { "camnoc_axi_src", "slow_ahb_src", "cpas_ahb", + "camnoc_axi", "vfe0_ahb", "vfe0_areg", "vfe0", + "vfe0_axi", "cam_hf_axi" }, + .clock_rate = { { 19200000, 300000000, 400000000, 480000000 }, + { 19200000, 80000000 }, + { 19200000 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 300000000, 400000000 }, + { 350000000, 475000000, 576000000, 720000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe0" }, + .interrupt = { "vfe0" } + }, + /* VFE1 */ + { + .regulators = {}, + .clock = { "camnoc_axi_src", "slow_ahb_src", "cpas_ahb", + "camnoc_axi", "vfe1_ahb", "vfe1_areg", "vfe1", + "vfe1_axi", "cam_hf_axi" }, + .clock_rate = { { 19200000, 300000000, 400000000, 480000000 }, + { 19200000, 80000000 }, + { 19200000 }, + { 0 }, + { 0 }, + { 100000000, 200000000, 300000000, 400000000 }, + { 350000000, 475000000, 576000000, 720000000 }, + { 0 }, + { 0 } }, + .reg = { "vfe1" }, + .interrupt = { "vfe1" } + }, + /* VFE2 (lite) */ + { + .regulators = {}, + .clock = { "camnoc_axi_src", "slow_ahb_src", "cpas_ahb", + "camnoc_axi", "vfe_lite_ahb", "vfe_lite_axi", + "vfe_lite", "cam_hf_axi" }, + .clock_rate = { { 19200000, 300000000, 400000000, 480000000 }, + { 19200000, 80000000 }, + { 19200000 }, + { 0 }, + { 0 }, + { 0 }, + { 400000000, 480000000 }, + { 0 } }, + .reg = { "vfe_lite0" }, + .interrupt = { "vfe_lite0" } + }, + /* VFE3 (lite) */ + { + .regulators = {}, + .clock = { "camnoc_axi_src", "slow_ahb_src", "cpas_ahb", + "camnoc_axi", "vfe_lite_ahb", "vfe_lite_axi", + "vfe_lite", "cam_hf_axi" }, + .clock_rate = { { 19200000, 300000000, 400000000, 480000000 }, + { 19200000, 80000000 }, + { 19200000 }, + { 0 }, + { 0 }, + { 0 }, + { 400000000, 480000000 }, + { 0 } }, + .reg = { "vfe_lite1" }, + .interrupt = { "vfe_lite1" } + }, +}; + +static const struct resources_icc icc_res_sm8250[] = { + { + .name = "cam_ahb", + .icc_bw_tbl.avg = 38400, + .icc_bw_tbl.peak = 76800, + }, + { + .name = "cam_hf_0_mnoc", + .icc_bw_tbl.avg = 2097152, + .icc_bw_tbl.peak = 2097152, + }, + { + .name = "cam_sf_0_mnoc", + .icc_bw_tbl.avg = 0, + .icc_bw_tbl.peak = 2097152, + }, + { + .name = "cam_sf_icp_mnoc", + .icc_bw_tbl.avg = 2097152, + .icc_bw_tbl.peak = 2097152, + }, +}; + +/* + * camss_add_clock_margin - Add margin to clock frequency rate + * @rate: Clock frequency rate + * + * When making calculations with physical clock frequency values + * some safety margin must be added. Add it. + */ +inline void camss_add_clock_margin(u64 *rate) +{ + *rate *= CAMSS_CLOCK_MARGIN_NUMERATOR; + *rate = div_u64(*rate, CAMSS_CLOCK_MARGIN_DENOMINATOR); +} + +/* + * camss_enable_clocks - Enable multiple clocks + * @nclocks: Number of clocks in clock array + * @clock: Clock array + * @dev: Device + * + * Return 0 on success or a negative error code otherwise + */ +int camss_enable_clocks(int nclocks, struct camss_clock *clock, + struct device *dev) +{ + int ret; + int i; + + for (i = 0; i < nclocks; i++) { + ret = clk_prepare_enable(clock[i].clk); + if (ret) { + dev_err(dev, "clock enable failed: %d\n", ret); + goto error; + } + } + + return 0; + +error: + for (i--; i >= 0; i--) + clk_disable_unprepare(clock[i].clk); + + return ret; +} + +/* + * camss_disable_clocks - Disable multiple clocks + * @nclocks: Number of clocks in clock array + * @clock: Clock array + */ +void camss_disable_clocks(int nclocks, struct camss_clock *clock) +{ + int i; + + for (i = nclocks - 1; i >= 0; i--) + clk_disable_unprepare(clock[i].clk); +} + +/* + * camss_find_sensor - Find a linked media entity which represents a sensor + * @entity: Media entity to start searching from + * + * Return a pointer to sensor media entity or NULL if not found + */ +struct media_entity *camss_find_sensor(struct media_entity *entity) +{ + struct media_pad *pad; + + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + return NULL; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + return NULL; + + entity = pad->entity; + + if (entity->function == MEDIA_ENT_F_CAM_SENSOR) + return entity; + } +} + +/** + * camss_get_link_freq - Get link frequency from sensor + * @entity: Media entity in the current pipeline + * @bpp: Number of bits per pixel for the current format + * @lanes: Number of lanes in the link to the sensor + * + * Return link frequency on success or a negative error code otherwise + */ +s64 camss_get_link_freq(struct media_entity *entity, unsigned int bpp, + unsigned int lanes) +{ + struct media_entity *sensor; + struct v4l2_subdev *subdev; + + sensor = camss_find_sensor(entity); + if (!sensor) + return -ENODEV; + + subdev = media_entity_to_v4l2_subdev(sensor); + + return v4l2_get_link_freq(subdev->ctrl_handler, bpp, 2 * lanes); +} + +/* + * camss_get_pixel_clock - Get pixel clock rate from sensor + * @entity: Media entity in the current pipeline + * @pixel_clock: Received pixel clock value + * + * Return 0 on success or a negative error code otherwise + */ +int camss_get_pixel_clock(struct media_entity *entity, u64 *pixel_clock) +{ + struct media_entity *sensor; + struct v4l2_subdev *subdev; + struct v4l2_ctrl *ctrl; + + sensor = camss_find_sensor(entity); + if (!sensor) + return -ENODEV; + + subdev = media_entity_to_v4l2_subdev(sensor); + + ctrl = v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_PIXEL_RATE); + + if (!ctrl) + return -EINVAL; + + *pixel_clock = v4l2_ctrl_g_ctrl_int64(ctrl); + + return 0; +} + +int camss_pm_domain_on(struct camss *camss, int id) +{ + int ret = 0; + + if (id < camss->vfe_num) { + struct vfe_device *vfe = &camss->vfe[id]; + + ret = vfe->ops->pm_domain_on(vfe); + } + + return ret; +} + +void camss_pm_domain_off(struct camss *camss, int id) +{ + if (id < camss->vfe_num) { + struct vfe_device *vfe = &camss->vfe[id]; + + vfe->ops->pm_domain_off(vfe); + } +} + +/* + * camss_of_parse_endpoint_node - Parse port endpoint node + * @dev: Device + * @node: Device node to be parsed + * @csd: Parsed data from port endpoint node + * + * Return 0 on success or a negative error code on failure + */ +static int camss_of_parse_endpoint_node(struct device *dev, + struct device_node *node, + struct camss_async_subdev *csd) +{ + struct csiphy_lanes_cfg *lncfg = &csd->interface.csi2.lane_cfg; + struct v4l2_mbus_config_mipi_csi2 *mipi_csi2; + struct v4l2_fwnode_endpoint vep = { { 0 } }; + unsigned int i; + + v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &vep); + + csd->interface.csiphy_id = vep.base.port; + + mipi_csi2 = &vep.bus.mipi_csi2; + lncfg->clk.pos = mipi_csi2->clock_lane; + lncfg->clk.pol = mipi_csi2->lane_polarities[0]; + lncfg->num_data = mipi_csi2->num_data_lanes; + + lncfg->data = devm_kcalloc(dev, + lncfg->num_data, sizeof(*lncfg->data), + GFP_KERNEL); + if (!lncfg->data) + return -ENOMEM; + + for (i = 0; i < lncfg->num_data; i++) { + lncfg->data[i].pos = mipi_csi2->data_lanes[i]; + lncfg->data[i].pol = mipi_csi2->lane_polarities[i + 1]; + } + + return 0; +} + +/* + * camss_of_parse_ports - Parse ports node + * @dev: Device + * @notifier: v4l2_device notifier data + * + * Return number of "port" nodes found in "ports" node + */ +static int camss_of_parse_ports(struct camss *camss) +{ + struct device *dev = camss->dev; + struct device_node *node = NULL; + struct device_node *remote = NULL; + int ret, num_subdevs = 0; + + for_each_endpoint_of_node(dev->of_node, node) { + struct camss_async_subdev *csd; + + if (!of_device_is_available(node)) + continue; + + remote = of_graph_get_remote_port_parent(node); + if (!remote) { + dev_err(dev, "Cannot get remote parent\n"); + ret = -EINVAL; + goto err_cleanup; + } + + csd = v4l2_async_nf_add_fwnode(&camss->notifier, + of_fwnode_handle(remote), + struct camss_async_subdev); + of_node_put(remote); + if (IS_ERR(csd)) { + ret = PTR_ERR(csd); + goto err_cleanup; + } + + ret = camss_of_parse_endpoint_node(dev, node, csd); + if (ret < 0) + goto err_cleanup; + + num_subdevs++; + } + + return num_subdevs; + +err_cleanup: + of_node_put(node); + return ret; +} + +/* + * camss_init_subdevices - Initialize subdev structures and resources + * @camss: CAMSS device + * + * Return 0 on success or a negative error code on failure + */ +static int camss_init_subdevices(struct camss *camss) +{ + const struct resources *csiphy_res; + const struct resources *csid_res; + const struct resources_ispif *ispif_res; + const struct resources *vfe_res; + unsigned int i; + int ret; + + if (camss->version == CAMSS_8x16) { + csiphy_res = csiphy_res_8x16; + csid_res = csid_res_8x16; + ispif_res = &ispif_res_8x16; + vfe_res = vfe_res_8x16; + } else if (camss->version == CAMSS_8x96) { + csiphy_res = csiphy_res_8x96; + csid_res = csid_res_8x96; + ispif_res = &ispif_res_8x96; + vfe_res = vfe_res_8x96; + } else if (camss->version == CAMSS_660) { + csiphy_res = csiphy_res_660; + csid_res = csid_res_660; + ispif_res = &ispif_res_660; + vfe_res = vfe_res_660; + } else if (camss->version == CAMSS_845) { + csiphy_res = csiphy_res_845; + csid_res = csid_res_845; + /* Titan VFEs don't have an ISPIF */ + ispif_res = NULL; + vfe_res = vfe_res_845; + } else if (camss->version == CAMSS_8250) { + csiphy_res = csiphy_res_8250; + csid_res = csid_res_8250; + /* Titan VFEs don't have an ISPIF */ + ispif_res = NULL; + vfe_res = vfe_res_8250; + } else { + return -EINVAL; + } + + for (i = 0; i < camss->csiphy_num; i++) { + ret = msm_csiphy_subdev_init(camss, &camss->csiphy[i], + &csiphy_res[i], i); + if (ret < 0) { + dev_err(camss->dev, + "Failed to init csiphy%d sub-device: %d\n", + i, ret); + return ret; + } + } + + /* note: SM8250 requires VFE to be initialized before CSID */ + for (i = 0; i < camss->vfe_num; i++) { + ret = msm_vfe_subdev_init(camss, &camss->vfe[i], + &vfe_res[i], i); + if (ret < 0) { + dev_err(camss->dev, + "Fail to init vfe%d sub-device: %d\n", i, ret); + return ret; + } + } + + for (i = 0; i < camss->csid_num; i++) { + ret = msm_csid_subdev_init(camss, &camss->csid[i], + &csid_res[i], i); + if (ret < 0) { + dev_err(camss->dev, + "Failed to init csid%d sub-device: %d\n", + i, ret); + return ret; + } + } + + ret = msm_ispif_subdev_init(camss, ispif_res); + if (ret < 0) { + dev_err(camss->dev, "Failed to init ispif sub-device: %d\n", + ret); + return ret; + } + + return 0; +} + +/* + * camss_register_entities - Register subdev nodes and create links + * @camss: CAMSS device + * + * Return 0 on success or a negative error code on failure + */ +static int camss_register_entities(struct camss *camss) +{ + int i, j, k; + int ret; + + for (i = 0; i < camss->csiphy_num; i++) { + ret = msm_csiphy_register_entity(&camss->csiphy[i], + &camss->v4l2_dev); + if (ret < 0) { + dev_err(camss->dev, + "Failed to register csiphy%d entity: %d\n", + i, ret); + goto err_reg_csiphy; + } + } + + for (i = 0; i < camss->csid_num; i++) { + ret = msm_csid_register_entity(&camss->csid[i], + &camss->v4l2_dev); + if (ret < 0) { + dev_err(camss->dev, + "Failed to register csid%d entity: %d\n", + i, ret); + goto err_reg_csid; + } + } + + ret = msm_ispif_register_entities(camss->ispif, + &camss->v4l2_dev); + if (ret < 0) { + dev_err(camss->dev, "Failed to register ispif entities: %d\n", + ret); + goto err_reg_ispif; + } + + for (i = 0; i < camss->vfe_num; i++) { + ret = msm_vfe_register_entities(&camss->vfe[i], + &camss->v4l2_dev); + if (ret < 0) { + dev_err(camss->dev, + "Failed to register vfe%d entities: %d\n", + i, ret); + goto err_reg_vfe; + } + } + + for (i = 0; i < camss->csiphy_num; i++) { + for (j = 0; j < camss->csid_num; j++) { + ret = media_create_pad_link( + &camss->csiphy[i].subdev.entity, + MSM_CSIPHY_PAD_SRC, + &camss->csid[j].subdev.entity, + MSM_CSID_PAD_SINK, + 0); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + camss->csiphy[i].subdev.entity.name, + camss->csid[j].subdev.entity.name, + ret); + goto err_link; + } + } + } + + if (camss->ispif) { + for (i = 0; i < camss->csid_num; i++) { + for (j = 0; j < camss->ispif->line_num; j++) { + ret = media_create_pad_link( + &camss->csid[i].subdev.entity, + MSM_CSID_PAD_SRC, + &camss->ispif->line[j].subdev.entity, + MSM_ISPIF_PAD_SINK, + 0); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + camss->csid[i].subdev.entity.name, + camss->ispif->line[j].subdev.entity.name, + ret); + goto err_link; + } + } + } + + for (i = 0; i < camss->ispif->line_num; i++) + for (k = 0; k < camss->vfe_num; k++) + for (j = 0; j < camss->vfe[k].line_num; j++) { + struct v4l2_subdev *ispif = &camss->ispif->line[i].subdev; + struct v4l2_subdev *vfe = &camss->vfe[k].line[j].subdev; + + ret = media_create_pad_link(&ispif->entity, + MSM_ISPIF_PAD_SRC, + &vfe->entity, + MSM_VFE_PAD_SINK, + 0); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + ispif->entity.name, + vfe->entity.name, + ret); + goto err_link; + } + } + } else { + for (i = 0; i < camss->csid_num; i++) + for (k = 0; k < camss->vfe_num; k++) + for (j = 0; j < camss->vfe[k].line_num; j++) { + struct v4l2_subdev *csid = &camss->csid[i].subdev; + struct v4l2_subdev *vfe = &camss->vfe[k].line[j].subdev; + + ret = media_create_pad_link(&csid->entity, + MSM_CSID_PAD_SRC, + &vfe->entity, + MSM_VFE_PAD_SINK, + 0); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + csid->entity.name, + vfe->entity.name, + ret); + goto err_link; + } + } + } + + return 0; + +err_link: + i = camss->vfe_num; +err_reg_vfe: + for (i--; i >= 0; i--) + msm_vfe_unregister_entities(&camss->vfe[i]); + +err_reg_ispif: + msm_ispif_unregister_entities(camss->ispif); + + i = camss->csid_num; +err_reg_csid: + for (i--; i >= 0; i--) + msm_csid_unregister_entity(&camss->csid[i]); + + i = camss->csiphy_num; +err_reg_csiphy: + for (i--; i >= 0; i--) + msm_csiphy_unregister_entity(&camss->csiphy[i]); + + return ret; +} + +/* + * camss_unregister_entities - Unregister subdev nodes + * @camss: CAMSS device + * + * Return 0 on success or a negative error code on failure + */ +static void camss_unregister_entities(struct camss *camss) +{ + unsigned int i; + + for (i = 0; i < camss->csiphy_num; i++) + msm_csiphy_unregister_entity(&camss->csiphy[i]); + + for (i = 0; i < camss->csid_num; i++) + msm_csid_unregister_entity(&camss->csid[i]); + + msm_ispif_unregister_entities(camss->ispif); + + for (i = 0; i < camss->vfe_num; i++) + msm_vfe_unregister_entities(&camss->vfe[i]); +} + +static int camss_subdev_notifier_bound(struct v4l2_async_notifier *async, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct camss *camss = container_of(async, struct camss, notifier); + struct camss_async_subdev *csd = + container_of(asd, struct camss_async_subdev, asd); + u8 id = csd->interface.csiphy_id; + struct csiphy_device *csiphy = &camss->csiphy[id]; + + csiphy->cfg.csi2 = &csd->interface.csi2; + subdev->host_priv = csiphy; + + return 0; +} + +static int camss_subdev_notifier_complete(struct v4l2_async_notifier *async) +{ + struct camss *camss = container_of(async, struct camss, notifier); + struct v4l2_device *v4l2_dev = &camss->v4l2_dev; + struct v4l2_subdev *sd; + int ret; + + list_for_each_entry(sd, &v4l2_dev->subdevs, list) { + if (sd->host_priv) { + struct media_entity *sensor = &sd->entity; + struct csiphy_device *csiphy = + (struct csiphy_device *) sd->host_priv; + struct media_entity *input = &csiphy->subdev.entity; + unsigned int i; + + for (i = 0; i < sensor->num_pads; i++) { + if (sensor->pads[i].flags & MEDIA_PAD_FL_SOURCE) + break; + } + if (i == sensor->num_pads) { + dev_err(camss->dev, + "No source pad in external entity\n"); + return -EINVAL; + } + + ret = media_create_pad_link(sensor, i, + input, MSM_CSIPHY_PAD_SINK, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret < 0) { + dev_err(camss->dev, + "Failed to link %s->%s entities: %d\n", + sensor->name, input->name, ret); + return ret; + } + } + } + + ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev); + if (ret < 0) + return ret; + + return media_device_register(&camss->media_dev); +} + +static const struct v4l2_async_notifier_operations camss_subdev_notifier_ops = { + .bound = camss_subdev_notifier_bound, + .complete = camss_subdev_notifier_complete, +}; + +static const struct media_device_ops camss_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +static int camss_configure_pd(struct camss *camss) +{ + struct device *dev = camss->dev; + int i; + int ret; + + camss->genpd_num = of_count_phandle_with_args(dev->of_node, + "power-domains", + "#power-domain-cells"); + if (camss->genpd_num < 0) { + dev_err(dev, "Power domains are not defined for camss\n"); + return camss->genpd_num; + } + + /* + * If a platform device has just one power domain, then it is attached + * at platform_probe() level, thus there shall be no need and even no + * option to attach it again, this is the case for CAMSS on MSM8916. + */ + if (camss->genpd_num == 1) + return 0; + + camss->genpd = devm_kmalloc_array(dev, camss->genpd_num, + sizeof(*camss->genpd), GFP_KERNEL); + if (!camss->genpd) + return -ENOMEM; + + camss->genpd_link = devm_kmalloc_array(dev, camss->genpd_num, + sizeof(*camss->genpd_link), + GFP_KERNEL); + if (!camss->genpd_link) + return -ENOMEM; + + /* + * VFE power domains are in the beginning of the list, and while all + * power domains should be attached, only if TITAN_TOP power domain is + * found in the list, it should be linked over here. + */ + for (i = 0; i < camss->genpd_num; i++) { + camss->genpd[i] = dev_pm_domain_attach_by_id(camss->dev, i); + if (IS_ERR(camss->genpd[i])) { + ret = PTR_ERR(camss->genpd[i]); + goto fail_pm; + } + } + + if (i > camss->vfe_num) { + camss->genpd_link[i - 1] = device_link_add(camss->dev, camss->genpd[i - 1], + DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | + DL_FLAG_RPM_ACTIVE); + if (!camss->genpd_link[i - 1]) { + ret = -EINVAL; + goto fail_pm; + } + } + + return 0; + +fail_pm: + for (--i ; i >= 0; i--) + dev_pm_domain_detach(camss->genpd[i], true); + + return ret; +} + +static int camss_icc_get(struct camss *camss) +{ + const struct resources_icc *icc_res; + int nbr_icc_paths = 0; + int i; + + if (camss->version == CAMSS_8250) { + icc_res = &icc_res_sm8250[0]; + nbr_icc_paths = ICC_SM8250_COUNT; + } + + for (i = 0; i < nbr_icc_paths; i++) { + camss->icc_path[i] = devm_of_icc_get(camss->dev, + icc_res[i].name); + if (IS_ERR(camss->icc_path[i])) + return PTR_ERR(camss->icc_path[i]); + + camss->icc_bw_tbl[i] = icc_res[i].icc_bw_tbl; + } + + return 0; +} + +static void camss_genpd_cleanup(struct camss *camss) +{ + int i; + + if (camss->genpd_num == 1) + return; + + if (camss->genpd_num > camss->vfe_num) + device_link_del(camss->genpd_link[camss->genpd_num - 1]); + + for (i = 0; i < camss->genpd_num; i++) + dev_pm_domain_detach(camss->genpd[i], true); +} + +/* + * camss_probe - Probe CAMSS platform device + * @pdev: Pointer to CAMSS platform device + * + * Return 0 on success or a negative error code on failure + */ +static int camss_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct camss *camss; + int num_subdevs, ret; + + camss = devm_kzalloc(dev, sizeof(*camss), GFP_KERNEL); + if (!camss) + return -ENOMEM; + + atomic_set(&camss->ref_count, 0); + camss->dev = dev; + platform_set_drvdata(pdev, camss); + + if (of_device_is_compatible(dev->of_node, "qcom,msm8916-camss")) { + camss->version = CAMSS_8x16; + camss->csiphy_num = 2; + camss->csid_num = 2; + camss->vfe_num = 1; + } else if (of_device_is_compatible(dev->of_node, + "qcom,msm8996-camss")) { + camss->version = CAMSS_8x96; + camss->csiphy_num = 3; + camss->csid_num = 4; + camss->vfe_num = 2; + } else if (of_device_is_compatible(dev->of_node, + "qcom,sdm660-camss")) { + camss->version = CAMSS_660; + camss->csiphy_num = 3; + camss->csid_num = 4; + camss->vfe_num = 2; + } else if (of_device_is_compatible(dev->of_node, + "qcom,sdm845-camss")) { + camss->version = CAMSS_845; + camss->csiphy_num = 4; + camss->csid_num = 3; + camss->vfe_num = 3; + } else if (of_device_is_compatible(dev->of_node, + "qcom,sm8250-camss")) { + camss->version = CAMSS_8250; + camss->csiphy_num = 6; + camss->csid_num = 4; + camss->vfe_num = 4; + } else { + return -EINVAL; + } + + camss->csiphy = devm_kcalloc(dev, camss->csiphy_num, + sizeof(*camss->csiphy), GFP_KERNEL); + if (!camss->csiphy) + return -ENOMEM; + + camss->csid = devm_kcalloc(dev, camss->csid_num, sizeof(*camss->csid), + GFP_KERNEL); + if (!camss->csid) + return -ENOMEM; + + if (camss->version == CAMSS_8x16 || + camss->version == CAMSS_8x96) { + camss->ispif = devm_kcalloc(dev, 1, sizeof(*camss->ispif), GFP_KERNEL); + if (!camss->ispif) + return -ENOMEM; + } + + camss->vfe = devm_kcalloc(dev, camss->vfe_num, sizeof(*camss->vfe), + GFP_KERNEL); + if (!camss->vfe) + return -ENOMEM; + + ret = camss_icc_get(camss); + if (ret < 0) + return ret; + + ret = camss_configure_pd(camss); + if (ret < 0) { + dev_err(dev, "Failed to configure power domains: %d\n", ret); + return ret; + } + + ret = camss_init_subdevices(camss); + if (ret < 0) + goto err_genpd_cleanup; + + ret = dma_set_mask_and_coherent(dev, 0xffffffff); + if (ret) + goto err_genpd_cleanup; + + camss->media_dev.dev = camss->dev; + strscpy(camss->media_dev.model, "Qualcomm Camera Subsystem", + sizeof(camss->media_dev.model)); + camss->media_dev.ops = &camss_media_ops; + media_device_init(&camss->media_dev); + + camss->v4l2_dev.mdev = &camss->media_dev; + ret = v4l2_device_register(camss->dev, &camss->v4l2_dev); + if (ret < 0) { + dev_err(dev, "Failed to register V4L2 device: %d\n", ret); + goto err_genpd_cleanup; + } + + v4l2_async_nf_init(&camss->notifier); + + num_subdevs = camss_of_parse_ports(camss); + if (num_subdevs < 0) { + ret = num_subdevs; + goto err_v4l2_device_unregister; + } + + ret = camss_register_entities(camss); + if (ret < 0) + goto err_v4l2_device_unregister; + + if (num_subdevs) { + camss->notifier.ops = &camss_subdev_notifier_ops; + + ret = v4l2_async_nf_register(&camss->v4l2_dev, + &camss->notifier); + if (ret) { + dev_err(dev, + "Failed to register async subdev nodes: %d\n", + ret); + goto err_register_subdevs; + } + } else { + ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev); + if (ret < 0) { + dev_err(dev, "Failed to register subdev nodes: %d\n", + ret); + goto err_register_subdevs; + } + + ret = media_device_register(&camss->media_dev); + if (ret < 0) { + dev_err(dev, "Failed to register media device: %d\n", + ret); + goto err_register_subdevs; + } + } + + pm_runtime_enable(dev); + + return 0; + +err_register_subdevs: + camss_unregister_entities(camss); +err_v4l2_device_unregister: + v4l2_device_unregister(&camss->v4l2_dev); + v4l2_async_nf_cleanup(&camss->notifier); +err_genpd_cleanup: + camss_genpd_cleanup(camss); + + return ret; +} + +void camss_delete(struct camss *camss) +{ + v4l2_device_unregister(&camss->v4l2_dev); + media_device_unregister(&camss->media_dev); + media_device_cleanup(&camss->media_dev); + + pm_runtime_disable(camss->dev); +} + +/* + * camss_remove - Remove CAMSS platform device + * @pdev: Pointer to CAMSS platform device + * + * Always returns 0. + */ +static void camss_remove(struct platform_device *pdev) +{ + struct camss *camss = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&camss->notifier); + v4l2_async_nf_cleanup(&camss->notifier); + camss_unregister_entities(camss); + + if (atomic_read(&camss->ref_count) == 0) + camss_delete(camss); + + camss_genpd_cleanup(camss); +} + +static const struct of_device_id camss_dt_match[] = { + { .compatible = "qcom,msm8916-camss" }, + { .compatible = "qcom,msm8996-camss" }, + { .compatible = "qcom,sdm660-camss" }, + { .compatible = "qcom,sdm845-camss" }, + { .compatible = "qcom,sm8250-camss" }, + { } +}; + +MODULE_DEVICE_TABLE(of, camss_dt_match); + +static int __maybe_unused camss_runtime_suspend(struct device *dev) +{ + struct camss *camss = dev_get_drvdata(dev); + int nbr_icc_paths = 0; + int i; + int ret; + + if (camss->version == CAMSS_8250) + nbr_icc_paths = ICC_SM8250_COUNT; + + for (i = 0; i < nbr_icc_paths; i++) { + ret = icc_set_bw(camss->icc_path[i], 0, 0); + if (ret) + return ret; + } + + return 0; +} + +static int __maybe_unused camss_runtime_resume(struct device *dev) +{ + struct camss *camss = dev_get_drvdata(dev); + int nbr_icc_paths = 0; + int i; + int ret; + + if (camss->version == CAMSS_8250) + nbr_icc_paths = ICC_SM8250_COUNT; + + for (i = 0; i < nbr_icc_paths; i++) { + ret = icc_set_bw(camss->icc_path[i], + camss->icc_bw_tbl[i].avg, + camss->icc_bw_tbl[i].peak); + if (ret) + return ret; + } + + return 0; +} + +static const struct dev_pm_ops camss_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(camss_runtime_suspend, camss_runtime_resume, NULL) +}; + +static struct platform_driver qcom_camss_driver = { + .probe = camss_probe, + .remove_new = camss_remove, + .driver = { + .name = "qcom-camss", + .of_match_table = camss_dt_match, + .pm = &camss_pm_ops, + }, +}; + +module_platform_driver(qcom_camss_driver); + +MODULE_ALIAS("platform:qcom-camss"); +MODULE_DESCRIPTION("Qualcomm Camera Subsystem driver"); +MODULE_AUTHOR("Todor Tomov "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/camss/camss.h b/drivers/media/platform/qcom/camss/camss.h new file mode 100644 index 000000000..0db80cadb --- /dev/null +++ b/drivers/media/platform/qcom/camss/camss.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * camss.h + * + * Qualcomm MSM Camera Subsystem - Core + * + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (C) 2015-2018 Linaro Ltd. + */ +#ifndef QC_MSM_CAMSS_H +#define QC_MSM_CAMSS_H + +#include +#include +#include +#include +#include +#include +#include + +#include "camss-csid.h" +#include "camss-csiphy.h" +#include "camss-ispif.h" +#include "camss-vfe.h" + +#define to_camss(ptr_module) \ + container_of(ptr_module, struct camss, ptr_module) + +#define to_device(ptr_module) \ + (to_camss(ptr_module)->dev) + +#define module_pointer(ptr_module, index) \ + ((const struct ptr_module##_device (*)[]) &(ptr_module[-(index)])) + +#define to_camss_index(ptr_module, index) \ + container_of(module_pointer(ptr_module, index), \ + struct camss, ptr_module) + +#define to_device_index(ptr_module, index) \ + (to_camss_index(ptr_module, index)->dev) + +#define CAMSS_RES_MAX 17 + +struct resources { + char *regulators[CAMSS_RES_MAX]; + char *clock[CAMSS_RES_MAX]; + u32 clock_rate[CAMSS_RES_MAX][CAMSS_RES_MAX]; + char *reg[CAMSS_RES_MAX]; + char *interrupt[CAMSS_RES_MAX]; +}; + +struct resources_ispif { + char *clock[CAMSS_RES_MAX]; + char *clock_for_reset[CAMSS_RES_MAX]; + char *reg[CAMSS_RES_MAX]; + char *interrupt; +}; + +struct icc_bw_tbl { + u32 avg; + u32 peak; +}; + +struct resources_icc { + char *name; + struct icc_bw_tbl icc_bw_tbl; +}; + +enum pm_domain { + PM_DOMAIN_VFE0 = 0, + PM_DOMAIN_VFE1 = 1, + PM_DOMAIN_VFELITE = 2, /* VFELITE / TOP GDSC */ +}; + +enum camss_version { + CAMSS_8x16, + CAMSS_8x96, + CAMSS_660, + CAMSS_845, + CAMSS_8250, +}; + +enum icc_count { + ICC_DEFAULT_COUNT = 0, + ICC_SM8250_COUNT = 4, +}; + +struct camss { + enum camss_version version; + struct v4l2_device v4l2_dev; + struct v4l2_async_notifier notifier; + struct media_device media_dev; + struct device *dev; + int csiphy_num; + struct csiphy_device *csiphy; + int csid_num; + struct csid_device *csid; + struct ispif_device *ispif; + int vfe_num; + struct vfe_device *vfe; + atomic_t ref_count; + int genpd_num; + struct device **genpd; + struct device_link **genpd_link; + struct icc_path *icc_path[ICC_SM8250_COUNT]; + struct icc_bw_tbl icc_bw_tbl[ICC_SM8250_COUNT]; +}; + +struct camss_camera_interface { + u8 csiphy_id; + struct csiphy_csi2_cfg csi2; +}; + +struct camss_async_subdev { + struct v4l2_async_subdev asd; /* must be first */ + struct camss_camera_interface interface; +}; + +struct camss_clock { + struct clk *clk; + const char *name; + u32 *freq; + u32 nfreqs; +}; + +void camss_add_clock_margin(u64 *rate); +int camss_enable_clocks(int nclocks, struct camss_clock *clock, + struct device *dev); +void camss_disable_clocks(int nclocks, struct camss_clock *clock); +struct media_entity *camss_find_sensor(struct media_entity *entity); +s64 camss_get_link_freq(struct media_entity *entity, unsigned int bpp, + unsigned int lanes); +int camss_get_pixel_clock(struct media_entity *entity, u64 *pixel_clock); +int camss_pm_domain_on(struct camss *camss, int id); +void camss_pm_domain_off(struct camss *camss, int id); +void camss_delete(struct camss *camss); + +#endif /* QC_MSM_CAMSS_H */ diff --git a/drivers/media/platform/qcom/venus/Kconfig b/drivers/media/platform/qcom/venus/Kconfig new file mode 100644 index 000000000..bfd50e8f3 --- /dev/null +++ b/drivers/media/platform/qcom/venus/Kconfig @@ -0,0 +1,14 @@ +config VIDEO_QCOM_VENUS + tristate "Qualcomm Venus V4L2 encoder/decoder driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV && QCOM_SMEM + depends on (ARCH_QCOM && IOMMU_DMA) || COMPILE_TEST + select QCOM_MDT_LOADER if ARCH_QCOM + select QCOM_SCM + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a V4L2 driver for Qualcomm Venus video accelerator + hardware. It accelerates encoding and decoding operations + on various Qualcomm SoCs. + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/qcom/venus/Makefile b/drivers/media/platform/qcom/venus/Makefile new file mode 100644 index 000000000..91ee6be10 --- /dev/null +++ b/drivers/media/platform/qcom/venus/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for Qualcomm Venus driver + +venus-core-objs += core.o helpers.o firmware.o \ + hfi_venus.o hfi_msgs.o hfi_cmds.o hfi.o \ + hfi_parser.o pm_helpers.o dbgfs.o \ + hfi_platform.o hfi_platform_v4.o \ + hfi_platform_v6.o hfi_plat_bufs_v6.o \ + +venus-dec-objs += vdec.o vdec_ctrls.o +venus-enc-objs += venc.o venc_ctrls.o + +obj-$(CONFIG_VIDEO_QCOM_VENUS) += venus-core.o +obj-$(CONFIG_VIDEO_QCOM_VENUS) += venus-dec.o +obj-$(CONFIG_VIDEO_QCOM_VENUS) += venus-enc.o diff --git a/drivers/media/platform/qcom/venus/core.c b/drivers/media/platform/qcom/venus/core.c new file mode 100644 index 000000000..990a1519f --- /dev/null +++ b/drivers/media/platform/qcom/venus/core.c @@ -0,0 +1,906 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "firmware.h" +#include "pm_helpers.h" +#include "hfi_venus_io.h" + +static void venus_coredump(struct venus_core *core) +{ + struct device *dev; + phys_addr_t mem_phys; + size_t mem_size; + void *mem_va; + void *data; + + dev = core->dev; + mem_phys = core->fw.mem_phys; + mem_size = core->fw.mem_size; + + mem_va = memremap(mem_phys, mem_size, MEMREMAP_WC); + if (!mem_va) + return; + + data = vmalloc(mem_size); + if (!data) { + memunmap(mem_va); + return; + } + + memcpy(data, mem_va, mem_size); + memunmap(mem_va); + dev_coredumpv(dev, data, mem_size, GFP_KERNEL); +} + +static void venus_event_notify(struct venus_core *core, u32 event) +{ + struct venus_inst *inst; + + switch (event) { + case EVT_SYS_WATCHDOG_TIMEOUT: + case EVT_SYS_ERROR: + break; + default: + return; + } + + mutex_lock(&core->lock); + set_bit(0, &core->sys_error); + list_for_each_entry(inst, &core->instances, list) + inst->ops->event_notify(inst, EVT_SESSION_ERROR, NULL); + mutex_unlock(&core->lock); + + disable_irq_nosync(core->irq); + schedule_delayed_work(&core->work, msecs_to_jiffies(10)); +} + +static const struct hfi_core_ops venus_core_ops = { + .event_notify = venus_event_notify, +}; + +#define RPM_WAIT_FOR_IDLE_MAX_ATTEMPTS 10 + +static void venus_sys_error_handler(struct work_struct *work) +{ + struct venus_core *core = + container_of(work, struct venus_core, work.work); + int ret, i, max_attempts = RPM_WAIT_FOR_IDLE_MAX_ATTEMPTS; + const char *err_msg = ""; + bool failed = false; + + ret = pm_runtime_get_sync(core->dev); + if (ret < 0) { + err_msg = "resume runtime PM"; + max_attempts = 0; + failed = true; + } + + core->ops->core_deinit(core); + core->state = CORE_UNINIT; + + for (i = 0; i < max_attempts; i++) { + if (!pm_runtime_active(core->dev_dec) && !pm_runtime_active(core->dev_enc)) + break; + msleep(10); + } + + mutex_lock(&core->lock); + + venus_shutdown(core); + + venus_coredump(core); + + pm_runtime_put_sync(core->dev); + + for (i = 0; i < max_attempts; i++) { + if (!core->pmdomains[0] || !pm_runtime_active(core->pmdomains[0])) + break; + usleep_range(1000, 1500); + } + + hfi_reinit(core); + + ret = pm_runtime_get_sync(core->dev); + if (ret < 0) { + err_msg = "resume runtime PM"; + failed = true; + } + + ret = venus_boot(core); + if (ret && !failed) { + err_msg = "boot Venus"; + failed = true; + } + + ret = hfi_core_resume(core, true); + if (ret && !failed) { + err_msg = "resume HFI"; + failed = true; + } + + enable_irq(core->irq); + + mutex_unlock(&core->lock); + + ret = hfi_core_init(core); + if (ret && !failed) { + err_msg = "init HFI"; + failed = true; + } + + pm_runtime_put_sync(core->dev); + + if (failed) { + disable_irq_nosync(core->irq); + dev_warn_ratelimited(core->dev, + "System error has occurred, recovery failed to %s\n", + err_msg); + schedule_delayed_work(&core->work, msecs_to_jiffies(10)); + return; + } + + dev_warn(core->dev, "system error has occurred (recovered)\n"); + + mutex_lock(&core->lock); + clear_bit(0, &core->sys_error); + wake_up_all(&core->sys_err_done); + mutex_unlock(&core->lock); +} + +static u32 to_v4l2_codec_type(u32 codec) +{ + switch (codec) { + case HFI_VIDEO_CODEC_H264: + return V4L2_PIX_FMT_H264; + case HFI_VIDEO_CODEC_H263: + return V4L2_PIX_FMT_H263; + case HFI_VIDEO_CODEC_MPEG1: + return V4L2_PIX_FMT_MPEG1; + case HFI_VIDEO_CODEC_MPEG2: + return V4L2_PIX_FMT_MPEG2; + case HFI_VIDEO_CODEC_MPEG4: + return V4L2_PIX_FMT_MPEG4; + case HFI_VIDEO_CODEC_VC1: + return V4L2_PIX_FMT_VC1_ANNEX_G; + case HFI_VIDEO_CODEC_VP8: + return V4L2_PIX_FMT_VP8; + case HFI_VIDEO_CODEC_VP9: + return V4L2_PIX_FMT_VP9; + case HFI_VIDEO_CODEC_DIVX: + case HFI_VIDEO_CODEC_DIVX_311: + return V4L2_PIX_FMT_XVID; + default: + return 0; + } +} + +static int venus_enumerate_codecs(struct venus_core *core, u32 type) +{ + const struct hfi_inst_ops dummy_ops = {}; + struct venus_inst *inst; + u32 codec, codecs; + unsigned int i; + int ret; + + if (core->res->hfi_version != HFI_VERSION_1XX) + return 0; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + mutex_init(&inst->lock); + inst->core = core; + inst->session_type = type; + if (type == VIDC_SESSION_TYPE_DEC) + codecs = core->dec_codecs; + else + codecs = core->enc_codecs; + + ret = hfi_session_create(inst, &dummy_ops); + if (ret) + goto err; + + for (i = 0; i < MAX_CODEC_NUM; i++) { + codec = (1UL << i) & codecs; + if (!codec) + continue; + + ret = hfi_session_init(inst, to_v4l2_codec_type(codec)); + if (ret) + goto done; + + ret = hfi_session_deinit(inst); + if (ret) + goto done; + } + +done: + hfi_session_destroy(inst); +err: + mutex_destroy(&inst->lock); + kfree(inst); + + return ret; +} + +static void venus_assign_register_offsets(struct venus_core *core) +{ + if (IS_V6(core)) { + core->vbif_base = core->base + VBIF_BASE; + core->cpu_base = core->base + CPU_BASE_V6; + core->cpu_cs_base = core->base + CPU_CS_BASE_V6; + core->cpu_ic_base = core->base + CPU_IC_BASE_V6; + core->wrapper_base = core->base + WRAPPER_BASE_V6; + core->wrapper_tz_base = core->base + WRAPPER_TZ_BASE_V6; + core->aon_base = core->base + AON_BASE_V6; + } else { + core->vbif_base = core->base + VBIF_BASE; + core->cpu_base = core->base + CPU_BASE; + core->cpu_cs_base = core->base + CPU_CS_BASE; + core->cpu_ic_base = core->base + CPU_IC_BASE; + core->wrapper_base = core->base + WRAPPER_BASE; + core->wrapper_tz_base = NULL; + core->aon_base = NULL; + } +} + +static irqreturn_t venus_isr_thread(int irq, void *dev_id) +{ + struct venus_core *core = dev_id; + irqreturn_t ret; + + ret = hfi_isr_thread(irq, dev_id); + + if (ret == IRQ_HANDLED && venus_fault_inject_ssr()) + hfi_core_trigger_ssr(core, HFI_TEST_SSR_SW_ERR_FATAL); + + return ret; +} + +static int venus_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct venus_core *core; + int ret; + + core = devm_kzalloc(dev, sizeof(*core), GFP_KERNEL); + if (!core) + return -ENOMEM; + + core->dev = dev; + + core->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(core->base)) + return PTR_ERR(core->base); + + core->video_path = devm_of_icc_get(dev, "video-mem"); + if (IS_ERR(core->video_path)) + return PTR_ERR(core->video_path); + + core->cpucfg_path = devm_of_icc_get(dev, "cpu-cfg"); + if (IS_ERR(core->cpucfg_path)) + return PTR_ERR(core->cpucfg_path); + + core->irq = platform_get_irq(pdev, 0); + if (core->irq < 0) + return core->irq; + + core->res = of_device_get_match_data(dev); + if (!core->res) + return -ENODEV; + + mutex_init(&core->pm_lock); + + core->pm_ops = venus_pm_get(core->res->hfi_version); + if (!core->pm_ops) + return -ENODEV; + + if (core->pm_ops->core_get) { + ret = core->pm_ops->core_get(core); + if (ret) + return ret; + } + + ret = dma_set_mask_and_coherent(dev, core->res->dma_mask); + if (ret) + goto err_core_put; + + dma_set_max_seg_size(dev, UINT_MAX); + + INIT_LIST_HEAD(&core->instances); + mutex_init(&core->lock); + INIT_DELAYED_WORK(&core->work, venus_sys_error_handler); + init_waitqueue_head(&core->sys_err_done); + + ret = devm_request_threaded_irq(dev, core->irq, hfi_isr, venus_isr_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "venus", core); + if (ret) + goto err_core_put; + + ret = hfi_create(core, &venus_core_ops); + if (ret) + goto err_core_put; + + venus_assign_register_offsets(core); + + ret = v4l2_device_register(dev, &core->v4l2_dev); + if (ret) + goto err_core_deinit; + + platform_set_drvdata(pdev, core); + + pm_runtime_enable(dev); + + ret = pm_runtime_get_sync(dev); + if (ret < 0) + goto err_runtime_disable; + + ret = of_platform_populate(dev->of_node, NULL, NULL, dev); + if (ret) + goto err_runtime_disable; + + ret = venus_firmware_init(core); + if (ret) + goto err_of_depopulate; + + ret = venus_boot(core); + if (ret) + goto err_firmware_deinit; + + ret = hfi_core_resume(core, true); + if (ret) + goto err_venus_shutdown; + + ret = hfi_core_init(core); + if (ret) + goto err_venus_shutdown; + + ret = venus_enumerate_codecs(core, VIDC_SESSION_TYPE_DEC); + if (ret) + goto err_venus_shutdown; + + ret = venus_enumerate_codecs(core, VIDC_SESSION_TYPE_ENC); + if (ret) + goto err_venus_shutdown; + + ret = pm_runtime_put_sync(dev); + if (ret) { + pm_runtime_get_noresume(dev); + goto err_dev_unregister; + } + + venus_dbgfs_init(core); + + return 0; + +err_dev_unregister: + v4l2_device_unregister(&core->v4l2_dev); +err_venus_shutdown: + venus_shutdown(core); +err_firmware_deinit: + venus_firmware_deinit(core); +err_of_depopulate: + of_platform_depopulate(dev); +err_runtime_disable: + pm_runtime_put_noidle(dev); + pm_runtime_set_suspended(dev); + pm_runtime_disable(dev); + hfi_destroy(core); +err_core_deinit: + hfi_core_deinit(core, false); +err_core_put: + if (core->pm_ops->core_put) + core->pm_ops->core_put(core); + return ret; +} + +static int venus_remove(struct platform_device *pdev) +{ + struct venus_core *core = platform_get_drvdata(pdev); + const struct venus_pm_ops *pm_ops = core->pm_ops; + struct device *dev = core->dev; + int ret; + + ret = pm_runtime_get_sync(dev); + WARN_ON(ret < 0); + + ret = hfi_core_deinit(core, true); + WARN_ON(ret); + + venus_shutdown(core); + of_platform_depopulate(dev); + + venus_firmware_deinit(core); + + pm_runtime_put_sync(dev); + pm_runtime_disable(dev); + + if (pm_ops->core_put) + pm_ops->core_put(core); + + v4l2_device_unregister(&core->v4l2_dev); + + hfi_destroy(core); + + mutex_destroy(&core->pm_lock); + mutex_destroy(&core->lock); + venus_dbgfs_deinit(core); + + return ret; +} + +static void venus_core_shutdown(struct platform_device *pdev) +{ + struct venus_core *core = platform_get_drvdata(pdev); + + pm_runtime_get_sync(core->dev); + venus_shutdown(core); + venus_firmware_deinit(core); + pm_runtime_put_sync(core->dev); +} + +static __maybe_unused int venus_runtime_suspend(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + const struct venus_pm_ops *pm_ops = core->pm_ops; + int ret; + + ret = hfi_core_suspend(core); + if (ret) + return ret; + + if (pm_ops->core_power) { + ret = pm_ops->core_power(core, POWER_OFF); + if (ret) + return ret; + } + + ret = icc_set_bw(core->cpucfg_path, 0, 0); + if (ret) + goto err_cpucfg_path; + + ret = icc_set_bw(core->video_path, 0, 0); + if (ret) + goto err_video_path; + + return ret; + +err_video_path: + icc_set_bw(core->cpucfg_path, kbps_to_icc(1000), 0); +err_cpucfg_path: + if (pm_ops->core_power) + pm_ops->core_power(core, POWER_ON); + + return ret; +} + +static __maybe_unused int venus_runtime_resume(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + const struct venus_pm_ops *pm_ops = core->pm_ops; + int ret; + + ret = icc_set_bw(core->video_path, kbps_to_icc(20000), 0); + if (ret) + return ret; + + ret = icc_set_bw(core->cpucfg_path, kbps_to_icc(1000), 0); + if (ret) + return ret; + + if (pm_ops->core_power) { + ret = pm_ops->core_power(core, POWER_ON); + if (ret) + return ret; + } + + return hfi_core_resume(core, false); +} + +static const struct dev_pm_ops venus_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(venus_runtime_suspend, venus_runtime_resume, NULL) +}; + +static const struct freq_tbl msm8916_freq_table[] = { + { 352800, 228570000 }, /* 1920x1088 @ 30 + 1280x720 @ 30 */ + { 244800, 160000000 }, /* 1920x1088 @ 30 */ + { 108000, 100000000 }, /* 1280x720 @ 30 */ +}; + +static const struct reg_val msm8916_reg_preset[] = { + { 0xe0020, 0x05555556 }, + { 0xe0024, 0x05555556 }, + { 0x80124, 0x00000003 }, +}; + +static const struct venus_resources msm8916_res = { + .freq_tbl = msm8916_freq_table, + .freq_tbl_size = ARRAY_SIZE(msm8916_freq_table), + .reg_tbl = msm8916_reg_preset, + .reg_tbl_size = ARRAY_SIZE(msm8916_reg_preset), + .clks = { "core", "iface", "bus", }, + .clks_num = 3, + .max_load = 352800, /* 720p@30 + 1080p@30 */ + .hfi_version = HFI_VERSION_1XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xddc00000 - 1, + .fwname = "qcom/venus-1.8/venus.mdt", +}; + +static const struct freq_tbl msm8996_freq_table[] = { + { 1944000, 520000000 }, /* 4k UHD @ 60 (decode only) */ + { 972000, 520000000 }, /* 4k UHD @ 30 */ + { 489600, 346666667 }, /* 1080p @ 60 */ + { 244800, 150000000 }, /* 1080p @ 30 */ + { 108000, 75000000 }, /* 720p @ 30 */ +}; + +static const struct reg_val msm8996_reg_preset[] = { + { 0x80010, 0xffffffff }, + { 0x80018, 0x00001556 }, + { 0x8001C, 0x00001556 }, +}; + +static const struct venus_resources msm8996_res = { + .freq_tbl = msm8996_freq_table, + .freq_tbl_size = ARRAY_SIZE(msm8996_freq_table), + .reg_tbl = msm8996_reg_preset, + .reg_tbl_size = ARRAY_SIZE(msm8996_reg_preset), + .clks = {"core", "iface", "bus", "mbus" }, + .clks_num = 4, + .vcodec0_clks = { "core" }, + .vcodec1_clks = { "core" }, + .vcodec_clks_num = 1, + .max_load = 2563200, + .hfi_version = HFI_VERSION_3XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xddc00000 - 1, + .fwname = "qcom/venus-4.2/venus.mdt", +}; + +static const struct freq_tbl sdm660_freq_table[] = { + { 979200, 518400000 }, + { 489600, 441600000 }, + { 432000, 404000000 }, + { 244800, 320000000 }, + { 216000, 269330000 }, + { 108000, 133330000 }, +}; + +static const struct reg_val sdm660_reg_preset[] = { + { 0x80010, 0x001f001f }, + { 0x80018, 0x00000156 }, + { 0x8001c, 0x00000156 }, +}; + +static const struct bw_tbl sdm660_bw_table_enc[] = { + { 979200, 1044000, 0, 2446336, 0 }, /* 4k UHD @ 30 */ + { 864000, 887000, 0, 2108416, 0 }, /* 720p @ 240 */ + { 489600, 666000, 0, 1207296, 0 }, /* 1080p @ 60 */ + { 432000, 578000, 0, 1058816, 0 }, /* 720p @ 120 */ + { 244800, 346000, 0, 616448, 0 }, /* 1080p @ 30 */ + { 216000, 293000, 0, 534528, 0 }, /* 720p @ 60 */ + { 108000, 151000, 0, 271360, 0 }, /* 720p @ 30 */ +}; + +static const struct bw_tbl sdm660_bw_table_dec[] = { + { 979200, 2365000, 0, 1892000, 0 }, /* 4k UHD @ 30 */ + { 864000, 1978000, 0, 1554000, 0 }, /* 720p @ 240 */ + { 489600, 1133000, 0, 895000, 0 }, /* 1080p @ 60 */ + { 432000, 994000, 0, 781000, 0 }, /* 720p @ 120 */ + { 244800, 580000, 0, 460000, 0 }, /* 1080p @ 30 */ + { 216000, 501000, 0, 301000, 0 }, /* 720p @ 60 */ + { 108000, 255000, 0, 202000, 0 }, /* 720p @ 30 */ +}; + +static const struct venus_resources sdm660_res = { + .freq_tbl = sdm660_freq_table, + .freq_tbl_size = ARRAY_SIZE(sdm660_freq_table), + .reg_tbl = sdm660_reg_preset, + .reg_tbl_size = ARRAY_SIZE(sdm660_reg_preset), + .bw_tbl_enc = sdm660_bw_table_enc, + .bw_tbl_enc_size = ARRAY_SIZE(sdm660_bw_table_enc), + .bw_tbl_dec = sdm660_bw_table_dec, + .bw_tbl_dec_size = ARRAY_SIZE(sdm660_bw_table_dec), + .clks = {"core", "iface", "bus", "bus_throttle" }, + .clks_num = 4, + .vcodec0_clks = { "vcodec0_core" }, + .vcodec1_clks = { "vcodec0_core" }, + .vcodec_clks_num = 1, + .vcodec_num = 1, + .max_load = 1036800, + .hfi_version = HFI_VERSION_3XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .cp_start = 0, + .cp_size = 0x79000000, + .cp_nonpixel_start = 0x1000000, + .cp_nonpixel_size = 0x28000000, + .dma_mask = 0xd9000000 - 1, + .fwname = "qcom/venus-4.4/venus.mdt", +}; + +static const struct freq_tbl sdm845_freq_table[] = { + { 3110400, 533000000 }, /* 4096x2160@90 */ + { 2073600, 444000000 }, /* 4096x2160@60 */ + { 1944000, 404000000 }, /* 3840x2160@60 */ + { 972000, 330000000 }, /* 3840x2160@30 */ + { 489600, 200000000 }, /* 1920x1080@60 */ + { 244800, 100000000 }, /* 1920x1080@30 */ +}; + +static const struct bw_tbl sdm845_bw_table_enc[] = { + { 1944000, 1612000, 0, 2416000, 0 }, /* 3840x2160@60 */ + { 972000, 951000, 0, 1434000, 0 }, /* 3840x2160@30 */ + { 489600, 723000, 0, 973000, 0 }, /* 1920x1080@60 */ + { 244800, 370000, 0, 495000, 0 }, /* 1920x1080@30 */ +}; + +static const struct bw_tbl sdm845_bw_table_dec[] = { + { 2073600, 3929000, 0, 5551000, 0 }, /* 4096x2160@60 */ + { 1036800, 1987000, 0, 2797000, 0 }, /* 4096x2160@30 */ + { 489600, 1040000, 0, 1298000, 0 }, /* 1920x1080@60 */ + { 244800, 530000, 0, 659000, 0 }, /* 1920x1080@30 */ +}; + +static const struct venus_resources sdm845_res = { + .freq_tbl = sdm845_freq_table, + .freq_tbl_size = ARRAY_SIZE(sdm845_freq_table), + .bw_tbl_enc = sdm845_bw_table_enc, + .bw_tbl_enc_size = ARRAY_SIZE(sdm845_bw_table_enc), + .bw_tbl_dec = sdm845_bw_table_dec, + .bw_tbl_dec_size = ARRAY_SIZE(sdm845_bw_table_dec), + .clks = {"core", "iface", "bus" }, + .clks_num = 3, + .vcodec0_clks = { "core", "bus" }, + .vcodec1_clks = { "core", "bus" }, + .vcodec_clks_num = 2, + .max_load = 3110400, /* 4096x2160@90 */ + .hfi_version = HFI_VERSION_4XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xe0000000 - 1, + .fwname = "qcom/venus-5.2/venus.mdt", +}; + +static const struct venus_resources sdm845_res_v2 = { + .freq_tbl = sdm845_freq_table, + .freq_tbl_size = ARRAY_SIZE(sdm845_freq_table), + .bw_tbl_enc = sdm845_bw_table_enc, + .bw_tbl_enc_size = ARRAY_SIZE(sdm845_bw_table_enc), + .bw_tbl_dec = sdm845_bw_table_dec, + .bw_tbl_dec_size = ARRAY_SIZE(sdm845_bw_table_dec), + .clks = {"core", "iface", "bus" }, + .clks_num = 3, + .vcodec0_clks = { "vcodec0_core", "vcodec0_bus" }, + .vcodec1_clks = { "vcodec1_core", "vcodec1_bus" }, + .vcodec_clks_num = 2, + .vcodec_pmdomains = { "venus", "vcodec0", "vcodec1" }, + .vcodec_pmdomains_num = 3, + .opp_pmdomain = (const char *[]) { "cx", NULL }, + .vcodec_num = 2, + .max_load = 3110400, /* 4096x2160@90 */ + .hfi_version = HFI_VERSION_4XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xe0000000 - 1, + .cp_start = 0, + .cp_size = 0x70800000, + .cp_nonpixel_start = 0x1000000, + .cp_nonpixel_size = 0x24800000, + .fwname = "qcom/venus-5.2/venus.mdt", +}; + +static const struct freq_tbl sc7180_freq_table[] = { + { 0, 500000000 }, + { 0, 434000000 }, + { 0, 340000000 }, + { 0, 270000000 }, + { 0, 150000000 }, +}; + +static const struct bw_tbl sc7180_bw_table_enc[] = { + { 972000, 750000, 0, 0, 0 }, /* 3840x2160@30 */ + { 489600, 451000, 0, 0, 0 }, /* 1920x1080@60 */ + { 244800, 234000, 0, 0, 0 }, /* 1920x1080@30 */ +}; + +static const struct bw_tbl sc7180_bw_table_dec[] = { + { 1036800, 1386000, 0, 1875000, 0 }, /* 4096x2160@30 */ + { 489600, 865000, 0, 1146000, 0 }, /* 1920x1080@60 */ + { 244800, 530000, 0, 583000, 0 }, /* 1920x1080@30 */ +}; + +static const struct venus_resources sc7180_res = { + .freq_tbl = sc7180_freq_table, + .freq_tbl_size = ARRAY_SIZE(sc7180_freq_table), + .bw_tbl_enc = sc7180_bw_table_enc, + .bw_tbl_enc_size = ARRAY_SIZE(sc7180_bw_table_enc), + .bw_tbl_dec = sc7180_bw_table_dec, + .bw_tbl_dec_size = ARRAY_SIZE(sc7180_bw_table_dec), + .clks = {"core", "iface", "bus" }, + .clks_num = 3, + .vcodec0_clks = { "vcodec0_core", "vcodec0_bus" }, + .vcodec_clks_num = 2, + .vcodec_pmdomains = { "venus", "vcodec0" }, + .vcodec_pmdomains_num = 2, + .opp_pmdomain = (const char *[]) { "cx", NULL }, + .vcodec_num = 1, + .hfi_version = HFI_VERSION_4XX, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xe0000000 - 1, + .fwname = "qcom/venus-5.4/venus.mdt", +}; + +static const struct freq_tbl sm8250_freq_table[] = { + { 0, 444000000 }, + { 0, 366000000 }, + { 0, 338000000 }, + { 0, 240000000 }, +}; + +static const struct bw_tbl sm8250_bw_table_enc[] = { + { 1944000, 1954000, 0, 3711000, 0 }, /* 3840x2160@60 */ + { 972000, 996000, 0, 1905000, 0 }, /* 3840x2160@30 */ + { 489600, 645000, 0, 977000, 0 }, /* 1920x1080@60 */ + { 244800, 332000, 0, 498000, 0 }, /* 1920x1080@30 */ +}; + +static const struct bw_tbl sm8250_bw_table_dec[] = { + { 2073600, 2403000, 0, 4113000, 0 }, /* 4096x2160@60 */ + { 1036800, 1224000, 0, 2079000, 0 }, /* 4096x2160@30 */ + { 489600, 812000, 0, 998000, 0 }, /* 1920x1080@60 */ + { 244800, 416000, 0, 509000, 0 }, /* 1920x1080@30 */ +}; + +static const struct reg_val sm8250_reg_preset[] = { + { 0xb0088, 0 }, +}; + +static const struct venus_resources sm8250_res = { + .freq_tbl = sm8250_freq_table, + .freq_tbl_size = ARRAY_SIZE(sm8250_freq_table), + .reg_tbl = sm8250_reg_preset, + .reg_tbl_size = ARRAY_SIZE(sm8250_reg_preset), + .bw_tbl_enc = sm8250_bw_table_enc, + .bw_tbl_enc_size = ARRAY_SIZE(sm8250_bw_table_enc), + .bw_tbl_dec = sm8250_bw_table_dec, + .bw_tbl_dec_size = ARRAY_SIZE(sm8250_bw_table_dec), + .clks = {"core", "iface"}, + .clks_num = 2, + .resets = { "bus", "core" }, + .resets_num = 2, + .vcodec0_clks = { "vcodec0_core" }, + .vcodec_clks_num = 1, + .vcodec_pmdomains = { "venus", "vcodec0" }, + .vcodec_pmdomains_num = 2, + .opp_pmdomain = (const char *[]) { "mx", NULL }, + .vcodec_num = 1, + .max_load = 7833600, + .hfi_version = HFI_VERSION_6XX, + .num_vpp_pipes = 4, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xe0000000 - 1, + .fwname = "qcom/vpu-1.0/venus.mdt", +}; + +static const struct freq_tbl sc7280_freq_table[] = { + { 0, 460000000 }, + { 0, 424000000 }, + { 0, 335000000 }, + { 0, 240000000 }, + { 0, 133333333 }, +}; + +static const struct bw_tbl sc7280_bw_table_enc[] = { + { 1944000, 1896000, 0, 3657000, 0 }, /* 3840x2160@60 */ + { 972000, 968000, 0, 1848000, 0 }, /* 3840x2160@30 */ + { 489600, 618000, 0, 941000, 0 }, /* 1920x1080@60 */ + { 244800, 318000, 0, 480000, 0 }, /* 1920x1080@30 */ +}; + +static const struct bw_tbl sc7280_bw_table_dec[] = { + { 2073600, 2128000, 0, 3831000, 0 }, /* 4096x2160@60 */ + { 1036800, 1085000, 0, 1937000, 0 }, /* 4096x2160@30 */ + { 489600, 779000, 0, 998000, 0 }, /* 1920x1080@60 */ + { 244800, 400000, 0, 509000, 0 }, /* 1920x1080@30 */ +}; + +static const struct reg_val sm7280_reg_preset[] = { + { 0xb0088, 0 }, +}; + +static const struct hfi_ubwc_config sc7280_ubwc_config = { + 0, 0, {1, 1, 1, 0, 0, 0}, 8, 32, 14, 0, 0, {0, 0} +}; + +static const struct venus_resources sc7280_res = { + .freq_tbl = sc7280_freq_table, + .freq_tbl_size = ARRAY_SIZE(sc7280_freq_table), + .reg_tbl = sm7280_reg_preset, + .reg_tbl_size = ARRAY_SIZE(sm7280_reg_preset), + .bw_tbl_enc = sc7280_bw_table_enc, + .bw_tbl_enc_size = ARRAY_SIZE(sc7280_bw_table_enc), + .bw_tbl_dec = sc7280_bw_table_dec, + .bw_tbl_dec_size = ARRAY_SIZE(sc7280_bw_table_dec), + .ubwc_conf = &sc7280_ubwc_config, + .clks = {"core", "bus", "iface"}, + .clks_num = 3, + .vcodec0_clks = {"vcodec_core", "vcodec_bus"}, + .vcodec_clks_num = 2, + .vcodec_pmdomains = { "venus", "vcodec0" }, + .vcodec_pmdomains_num = 2, + .opp_pmdomain = (const char *[]) { "cx", NULL }, + .vcodec_num = 1, + .hfi_version = HFI_VERSION_6XX, + .num_vpp_pipes = 1, + .vmem_id = VIDC_RESOURCE_NONE, + .vmem_size = 0, + .vmem_addr = 0, + .dma_mask = 0xe0000000 - 1, + .fwname = "qcom/vpu-2.0/venus.mbn", +}; + +static const struct of_device_id venus_dt_match[] = { + { .compatible = "qcom,msm8916-venus", .data = &msm8916_res, }, + { .compatible = "qcom,msm8996-venus", .data = &msm8996_res, }, + { .compatible = "qcom,sdm660-venus", .data = &sdm660_res, }, + { .compatible = "qcom,sdm845-venus", .data = &sdm845_res, }, + { .compatible = "qcom,sdm845-venus-v2", .data = &sdm845_res_v2, }, + { .compatible = "qcom,sc7180-venus", .data = &sc7180_res, }, + { .compatible = "qcom,sc7280-venus", .data = &sc7280_res, }, + { .compatible = "qcom,sm8250-venus", .data = &sm8250_res, }, + { } +}; +MODULE_DEVICE_TABLE(of, venus_dt_match); + +static struct platform_driver qcom_venus_driver = { + .probe = venus_probe, + .remove = venus_remove, + .driver = { + .name = "qcom-venus", + .of_match_table = venus_dt_match, + .pm = &venus_pm_ops, + }, + .shutdown = venus_core_shutdown, +}; +module_platform_driver(qcom_venus_driver); + +MODULE_ALIAS("platform:qcom-venus"); +MODULE_DESCRIPTION("Qualcomm Venus video encoder and decoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/venus/core.h b/drivers/media/platform/qcom/venus/core.h new file mode 100644 index 000000000..32551c260 --- /dev/null +++ b/drivers/media/platform/qcom/venus/core.h @@ -0,0 +1,503 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ + +#ifndef __VENUS_CORE_H_ +#define __VENUS_CORE_H_ + +#include +#include +#include +#include +#include + +#include "dbgfs.h" +#include "hfi.h" +#include "hfi_platform.h" +#include "hfi_helper.h" + +#define VDBGL "VenusLow : " +#define VDBGM "VenusMed : " +#define VDBGH "VenusHigh: " +#define VDBGFW "VenusFW : " + +#define VIDC_CLKS_NUM_MAX 4 +#define VIDC_VCODEC_CLKS_NUM_MAX 2 +#define VIDC_PMDOMAINS_NUM_MAX 3 +#define VIDC_RESETS_NUM_MAX 2 + +extern int venus_fw_debug; + +struct freq_tbl { + unsigned int load; + unsigned long freq; +}; + +struct reg_val { + u32 reg; + u32 value; +}; + +struct bw_tbl { + u32 mbs_per_sec; + u32 avg; + u32 peak; + u32 avg_10bit; + u32 peak_10bit; +}; + +struct venus_resources { + u64 dma_mask; + const struct freq_tbl *freq_tbl; + unsigned int freq_tbl_size; + const struct bw_tbl *bw_tbl_enc; + unsigned int bw_tbl_enc_size; + const struct bw_tbl *bw_tbl_dec; + unsigned int bw_tbl_dec_size; + const struct reg_val *reg_tbl; + unsigned int reg_tbl_size; + const struct hfi_ubwc_config *ubwc_conf; + const char * const clks[VIDC_CLKS_NUM_MAX]; + unsigned int clks_num; + const char * const vcodec0_clks[VIDC_VCODEC_CLKS_NUM_MAX]; + const char * const vcodec1_clks[VIDC_VCODEC_CLKS_NUM_MAX]; + unsigned int vcodec_clks_num; + const char * const vcodec_pmdomains[VIDC_PMDOMAINS_NUM_MAX]; + unsigned int vcodec_pmdomains_num; + const char **opp_pmdomain; + unsigned int vcodec_num; + const char * const resets[VIDC_RESETS_NUM_MAX]; + unsigned int resets_num; + enum hfi_version hfi_version; + u8 num_vpp_pipes; + u32 max_load; + unsigned int vmem_id; + u32 vmem_size; + u32 vmem_addr; + u32 cp_start; + u32 cp_size; + u32 cp_nonpixel_start; + u32 cp_nonpixel_size; + const char *fwname; +}; + +struct venus_format { + u32 pixfmt; + unsigned int num_planes; + u32 type; + u32 flags; +}; + +/** + * struct venus_core - holds core parameters valid for all instances + * + * @base: IO memory base address + * @vbif_base: IO memory vbif base address + * @cpu_base: IO memory cpu base address + * @cpu_cs_base: IO memory cpu_cs base address + * @cpu_ic_base: IO memory cpu_ic base address + * @wrapper_base: IO memory wrapper base address + * @wrapper_tz_base: IO memory wrapper TZ base address + * @aon_base: AON base address + * @irq: Venus irq + * @clks: an array of struct clk pointers + * @vcodec0_clks: an array of vcodec0 struct clk pointers + * @vcodec1_clks: an array of vcodec1 struct clk pointers + * @video_path: an interconnect handle to video to/from memory path + * @cpucfg_path: an interconnect handle to cpu configuration path + * @opp_table: an device OPP table handle + * @has_opp_table: does OPP table exist + * @pmdomains: an array of pmdomains struct device pointers + * @opp_dl_venus: an device-link for device OPP + * @opp_pmdomain: an OPP power-domain + * @resets: an array of reset signals + * @vdev_dec: a reference to video device structure for decoder instances + * @vdev_enc: a reference to video device structure for encoder instances + * @v4l2_dev: a holder for v4l2 device structure + * @res: a reference to venus resources structure + * @dev: convenience struct device pointer + * @dev_dec: convenience struct device pointer for decoder device + * @dev_enc: convenience struct device pointer for encoder device + * @use_tz: a flag that suggests presence of trustzone + * @fw: structure of firmware parameters + * @lock: a lock for this strucure + * @instances: a list_head of all instances + * @insts_count: num of instances + * @state: the state of the venus core + * @done: a completion for sync HFI operations + * @error: an error returned during last HFI sync operations + * @sys_error: an error flag that signal system error event + * @sys_err_done: a waitqueue to wait for system error recovery end + * @core_ops: the core operations + * @pm_ops: a pointer to pm operations + * @pm_lock: a lock for PM operations + * @enc_codecs: encoders supported by this core + * @dec_codecs: decoders supported by this core + * @max_sessions_supported: holds the maximum number of sessions + * @priv: a private filed for HFI operations + * @ops: the core HFI operations + * @work: a delayed work for handling system fatal error + * @caps: an array of supported HFI capabilities + * @codecs_count: platform codecs count + * @core0_usage_count: usage counter for core0 + * @core1_usage_count: usage counter for core1 + * @root: debugfs root directory + */ +struct venus_core { + void __iomem *base; + void __iomem *vbif_base; + void __iomem *cpu_base; + void __iomem *cpu_cs_base; + void __iomem *cpu_ic_base; + void __iomem *wrapper_base; + void __iomem *wrapper_tz_base; + void __iomem *aon_base; + int irq; + struct clk *clks[VIDC_CLKS_NUM_MAX]; + struct clk *vcodec0_clks[VIDC_VCODEC_CLKS_NUM_MAX]; + struct clk *vcodec1_clks[VIDC_VCODEC_CLKS_NUM_MAX]; + struct icc_path *video_path; + struct icc_path *cpucfg_path; + bool has_opp_table; + struct device *pmdomains[VIDC_PMDOMAINS_NUM_MAX]; + struct device_link *opp_dl_venus; + struct device *opp_pmdomain; + struct reset_control *resets[VIDC_RESETS_NUM_MAX]; + struct video_device *vdev_dec; + struct video_device *vdev_enc; + struct v4l2_device v4l2_dev; + const struct venus_resources *res; + struct device *dev; + struct device *dev_dec; + struct device *dev_enc; + unsigned int use_tz; + struct video_firmware { + struct device *dev; + struct iommu_domain *iommu_domain; + size_t mapped_mem_size; + phys_addr_t mem_phys; + size_t mem_size; + } fw; + struct mutex lock; + struct list_head instances; + atomic_t insts_count; + unsigned int state; + struct completion done; + unsigned int error; + unsigned long sys_error; + wait_queue_head_t sys_err_done; + const struct hfi_core_ops *core_ops; + const struct venus_pm_ops *pm_ops; + struct mutex pm_lock; + unsigned long enc_codecs; + unsigned long dec_codecs; + unsigned int max_sessions_supported; + void *priv; + const struct hfi_ops *ops; + struct delayed_work work; + struct hfi_plat_caps caps[MAX_CODEC_NUM]; + unsigned int codecs_count; + unsigned int core0_usage_count; + unsigned int core1_usage_count; + struct dentry *root; +}; + +struct vdec_controls { + u32 post_loop_deb_mode; + u32 profile; + u32 level; + u32 display_delay; + u32 display_delay_enable; + u64 conceal_color; +}; + +struct venc_controls { + u16 gop_size; + u32 num_p_frames; + u32 num_b_frames; + u32 bitrate_mode; + u32 bitrate; + u32 bitrate_peak; + u32 rc_enable; + u32 const_quality; + u32 frame_skip_mode; + + u32 h264_i_period; + u32 h264_entropy_mode; + u32 h264_i_qp; + u32 h264_p_qp; + u32 h264_b_qp; + u32 h264_min_qp; + u32 h264_max_qp; + u32 h264_i_min_qp; + u32 h264_i_max_qp; + u32 h264_p_min_qp; + u32 h264_p_max_qp; + u32 h264_b_min_qp; + u32 h264_b_max_qp; + u32 h264_loop_filter_mode; + s32 h264_loop_filter_alpha; + s32 h264_loop_filter_beta; + u32 h264_8x8_transform; + + u32 hevc_i_qp; + u32 hevc_p_qp; + u32 hevc_b_qp; + u32 hevc_min_qp; + u32 hevc_max_qp; + u32 hevc_i_min_qp; + u32 hevc_i_max_qp; + u32 hevc_p_min_qp; + u32 hevc_p_max_qp; + u32 hevc_b_min_qp; + u32 hevc_b_max_qp; + + u32 vp8_min_qp; + u32 vp8_max_qp; + + u32 multi_slice_mode; + u32 multi_slice_max_bytes; + u32 multi_slice_max_mb; + + u32 header_mode; + bool aud_enable; + u32 intra_refresh_type; + u32 intra_refresh_period; + + struct { + u32 h264; + u32 mpeg4; + u32 hevc; + u32 vp8; + u32 vp9; + } profile; + struct { + u32 h264; + u32 mpeg4; + u32 hevc; + u32 vp9; + } level; + + u32 base_priority_id; + u32 ltr_count; + struct v4l2_ctrl_hdr10_cll_info cll; + struct v4l2_ctrl_hdr10_mastering_display mastering; +}; + +struct venus_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; + dma_addr_t dma_addr; + u32 size; + struct list_head reg_list; + u32 flags; + struct list_head ref_list; +}; + +struct clock_data { + u32 core_id; + unsigned long freq; + unsigned long vpp_freq; + unsigned long vsp_freq; + unsigned long low_power_freq; +}; + +#define to_venus_buffer(ptr) container_of(ptr, struct venus_buffer, vb) + +enum venus_dec_state { + VENUS_DEC_STATE_DEINIT = 0, + VENUS_DEC_STATE_INIT = 1, + VENUS_DEC_STATE_CAPTURE_SETUP = 2, + VENUS_DEC_STATE_STOPPED = 3, + VENUS_DEC_STATE_SEEK = 4, + VENUS_DEC_STATE_DRAIN = 5, + VENUS_DEC_STATE_DECODING = 6, + VENUS_DEC_STATE_DRC = 7, +}; + +struct venus_ts_metadata { + bool used; + u64 ts_ns; + u64 ts_us; + u32 flags; + struct v4l2_timecode tc; +}; + +enum venus_inst_modes { + VENUS_LOW_POWER = BIT(0), +}; + +/** + * struct venus_inst - holds per instance parameters + * + * @list: used for attach an instance to the core + * @lock: instance lock + * @core: a reference to the core struct + * @clk_data: clock data per core ID + * @dpbbufs: a list of decoded picture buffers + * @internalbufs: a list of internal bufferes + * @registeredbufs: a list of registered capture bufferes + * @delayed_process: a list of delayed buffers + * @delayed_process_work: a work_struct for process delayed buffers + * @nonblock: nonblocking flag + * @ctrl_handler: v4l control handler + * @controls: a union of decoder and encoder control parameters + * @fh: a holder of v4l file handle structure + * @streamon_cap: stream on flag for capture queue + * @streamon_out: stream on flag for output queue + * @width: current capture width + * @height: current capture height + * @crop: current crop rectangle + * @fw_min_cnt: firmware minimum buffer count + * @out_width: current output width + * @out_height: current output height + * @colorspace: current color space + * @ycbcr_enc: current YCbCr encoding + * @quantization: current quantization + * @xfer_func: current xfer function + * @codec_state: current codec API state (see DEC/ENC_STATE_) + * @reconf_wait: wait queue for resolution change event + * @subscriptions: used to hold current events subscriptions + * @buf_count: used to count number of buffers (reqbuf(0)) + * @tss: timestamp metadata + * @payloads: cache plane payload to use it for clock/BW scaling + * @fps: holds current FPS + * @timeperframe: holds current time per frame structure + * @fmt_out: a reference to output format structure + * @fmt_cap: a reference to capture format structure + * @num_input_bufs: holds number of input buffers + * @num_output_bufs: holds number of output buffers + * @input_buf_size: holds input buffer size + * @output_buf_size: holds output buffer size + * @output2_buf_size: holds secondary decoder output buffer size + * @dpb_buftype: decoded picture buffer type + * @dpb_fmt: decoded picture buffer raw format + * @opb_buftype: output picture buffer type + * @opb_fmt: output picture buffer raw format + * @reconfig: a flag raised by decoder when the stream resolution changed + * @hfi_codec: current codec for this instance in HFI space + * @sequence_cap: a sequence counter for capture queue + * @sequence_out: a sequence counter for output queue + * @m2m_dev: a reference to m2m device structure + * @m2m_ctx: a reference to m2m context structure + * @state: current state of the instance + * @done: a completion for sync HFI operation + * @error: an error returned during last HFI sync operation + * @session_error: a flag rised by HFI interface in case of session error + * @ops: HFI operations + * @priv: a private for HFI operations callbacks + * @session_type: the type of the session (decoder or encoder) + * @hprop: a union used as a holder by get property + * @core_acquired: the Core has been acquired + * @bit_depth: current bitstream bit-depth + * @pic_struct: bitstream progressive vs interlaced + * @next_buf_last: a flag to mark next queued capture buffer as last + * @drain_active: Drain sequence is in progress + * @flags: bitmask flags describing current instance mode + * @dpb_ids: DPB buffer ID's + */ +struct venus_inst { + struct list_head list; + struct mutex lock; + struct venus_core *core; + struct clock_data clk_data; + struct list_head dpbbufs; + struct list_head internalbufs; + struct list_head registeredbufs; + struct list_head delayed_process; + struct work_struct delayed_process_work; + bool nonblock; + + struct v4l2_ctrl_handler ctrl_handler; + union { + struct vdec_controls dec; + struct venc_controls enc; + } controls; + struct v4l2_fh fh; + unsigned int streamon_cap, streamon_out; + u32 width; + u32 height; + struct v4l2_rect crop; + u32 fw_min_cnt; + u32 out_width; + u32 out_height; + u32 colorspace; + u8 ycbcr_enc; + u8 quantization; + u8 xfer_func; + enum venus_dec_state codec_state; + wait_queue_head_t reconf_wait; + unsigned int subscriptions; + int buf_count; + struct venus_ts_metadata tss[VIDEO_MAX_FRAME]; + unsigned long payloads[VIDEO_MAX_FRAME]; + u64 fps; + struct v4l2_fract timeperframe; + const struct venus_format *fmt_out; + const struct venus_format *fmt_cap; + unsigned int num_input_bufs; + unsigned int num_output_bufs; + unsigned int input_buf_size; + unsigned int output_buf_size; + unsigned int output2_buf_size; + u32 dpb_buftype; + u32 dpb_fmt; + u32 opb_buftype; + u32 opb_fmt; + bool reconfig; + u32 hfi_codec; + u32 sequence_cap; + u32 sequence_out; + struct v4l2_m2m_dev *m2m_dev; + struct v4l2_m2m_ctx *m2m_ctx; + unsigned int state; + struct completion done; + unsigned int error; + bool session_error; + const struct hfi_inst_ops *ops; + u32 session_type; + union hfi_get_property hprop; + unsigned int core_acquired: 1; + unsigned int bit_depth; + unsigned int pic_struct; + bool next_buf_last; + bool drain_active; + enum venus_inst_modes flags; + struct ida dpb_ids; +}; + +#define IS_V1(core) ((core)->res->hfi_version == HFI_VERSION_1XX) +#define IS_V3(core) ((core)->res->hfi_version == HFI_VERSION_3XX) +#define IS_V4(core) ((core)->res->hfi_version == HFI_VERSION_4XX) +#define IS_V6(core) ((core)->res->hfi_version == HFI_VERSION_6XX) + +#define ctrl_to_inst(ctrl) \ + container_of((ctrl)->handler, struct venus_inst, ctrl_handler) + +static inline struct venus_inst *to_inst(struct file *filp) +{ + return container_of(filp->private_data, struct venus_inst, fh); +} + +static inline void *to_hfi_priv(struct venus_core *core) +{ + return core->priv; +} + +static inline struct hfi_plat_caps * +venus_caps_by_codec(struct venus_core *core, u32 codec, u32 domain) +{ + unsigned int c; + + for (c = 0; c < core->codecs_count; c++) { + if (core->caps[c].codec == codec && + core->caps[c].domain == domain) + return &core->caps[c]; + } + + return NULL; +} + +#endif diff --git a/drivers/media/platform/qcom/venus/dbgfs.c b/drivers/media/platform/qcom/venus/dbgfs.c new file mode 100644 index 000000000..726f4b730 --- /dev/null +++ b/drivers/media/platform/qcom/venus/dbgfs.c @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2020 Linaro Ltd. + */ + +#include +#include + +#include "core.h" + +#ifdef CONFIG_FAULT_INJECTION +DECLARE_FAULT_ATTR(venus_ssr_attr); +#endif + +void venus_dbgfs_init(struct venus_core *core) +{ + core->root = debugfs_create_dir("venus", NULL); + debugfs_create_x32("fw_level", 0644, core->root, &venus_fw_debug); + +#ifdef CONFIG_FAULT_INJECTION + fault_create_debugfs_attr("fail_ssr", core->root, &venus_ssr_attr); +#endif +} + +void venus_dbgfs_deinit(struct venus_core *core) +{ + debugfs_remove_recursive(core->root); +} diff --git a/drivers/media/platform/qcom/venus/dbgfs.h b/drivers/media/platform/qcom/venus/dbgfs.h new file mode 100644 index 000000000..c87c1355d --- /dev/null +++ b/drivers/media/platform/qcom/venus/dbgfs.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020 Linaro Ltd. */ + +#ifndef __VENUS_DBGFS_H__ +#define __VENUS_DBGFS_H__ + +#include + +struct venus_core; + +#ifdef CONFIG_FAULT_INJECTION +extern struct fault_attr venus_ssr_attr; +static inline bool venus_fault_inject_ssr(void) +{ + return should_fail(&venus_ssr_attr, 1); +} +#else +static inline bool venus_fault_inject_ssr(void) { return false; } +#endif + + +void venus_dbgfs_init(struct venus_core *core); +void venus_dbgfs_deinit(struct venus_core *core); + +#endif diff --git a/drivers/media/platform/qcom/venus/firmware.c b/drivers/media/platform/qcom/venus/firmware.c new file mode 100644 index 000000000..14b6f1d05 --- /dev/null +++ b/drivers/media/platform/qcom/venus/firmware.c @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "firmware.h" +#include "hfi_venus_io.h" + +#define VENUS_PAS_ID 9 +#define VENUS_FW_MEM_SIZE (6 * SZ_1M) +#define VENUS_FW_START_ADDR 0x0 + +static void venus_reset_cpu(struct venus_core *core) +{ + u32 fw_size = core->fw.mapped_mem_size; + void __iomem *wrapper_base; + + if (IS_V6(core)) + wrapper_base = core->wrapper_tz_base; + else + wrapper_base = core->wrapper_base; + + writel(0, wrapper_base + WRAPPER_FW_START_ADDR); + writel(fw_size, wrapper_base + WRAPPER_FW_END_ADDR); + writel(0, wrapper_base + WRAPPER_CPA_START_ADDR); + writel(fw_size, wrapper_base + WRAPPER_CPA_END_ADDR); + writel(fw_size, wrapper_base + WRAPPER_NONPIX_START_ADDR); + writel(fw_size, wrapper_base + WRAPPER_NONPIX_END_ADDR); + + if (IS_V6(core)) { + /* Bring XTSS out of reset */ + writel(0, wrapper_base + WRAPPER_TZ_XTSS_SW_RESET); + } else { + writel(0x0, wrapper_base + WRAPPER_CPU_CGC_DIS); + writel(0x0, wrapper_base + WRAPPER_CPU_CLOCK_CONFIG); + + /* Bring ARM9 out of reset */ + writel(0, wrapper_base + WRAPPER_A9SS_SW_RESET); + } +} + +int venus_set_hw_state(struct venus_core *core, bool resume) +{ + int ret; + + if (core->use_tz) { + ret = qcom_scm_set_remote_state(resume, 0); + if (resume && ret == -EINVAL) + ret = 0; + return ret; + } + + if (resume) { + venus_reset_cpu(core); + } else { + if (IS_V6(core)) + writel(1, core->wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + else + writel(1, core->wrapper_base + WRAPPER_A9SS_SW_RESET); + } + + return 0; +} + +static int venus_load_fw(struct venus_core *core, const char *fwname, + phys_addr_t *mem_phys, size_t *mem_size) +{ + const struct firmware *mdt; + struct device_node *node; + struct device *dev; + struct resource r; + ssize_t fw_size; + void *mem_va; + int ret; + + *mem_phys = 0; + *mem_size = 0; + + dev = core->dev; + node = of_parse_phandle(dev->of_node, "memory-region", 0); + if (!node) { + dev_err(dev, "no memory-region specified\n"); + return -EINVAL; + } + + ret = of_address_to_resource(node, 0, &r); + if (ret) + goto err_put_node; + + ret = request_firmware(&mdt, fwname, dev); + if (ret < 0) + goto err_put_node; + + fw_size = qcom_mdt_get_size(mdt); + if (fw_size < 0) { + ret = fw_size; + goto err_release_fw; + } + + *mem_phys = r.start; + *mem_size = resource_size(&r); + + if (*mem_size < fw_size || fw_size > VENUS_FW_MEM_SIZE) { + ret = -EINVAL; + goto err_release_fw; + } + + mem_va = memremap(r.start, *mem_size, MEMREMAP_WC); + if (!mem_va) { + dev_err(dev, "unable to map memory region: %pR\n", &r); + ret = -ENOMEM; + goto err_release_fw; + } + + if (core->use_tz) + ret = qcom_mdt_load(dev, mdt, fwname, VENUS_PAS_ID, + mem_va, *mem_phys, *mem_size, NULL); + else + ret = qcom_mdt_load_no_init(dev, mdt, fwname, VENUS_PAS_ID, + mem_va, *mem_phys, *mem_size, NULL); + + memunmap(mem_va); +err_release_fw: + release_firmware(mdt); +err_put_node: + of_node_put(node); + return ret; +} + +static int venus_boot_no_tz(struct venus_core *core, phys_addr_t mem_phys, + size_t mem_size) +{ + struct iommu_domain *iommu; + struct device *dev; + int ret; + + dev = core->fw.dev; + if (!dev) + return -EPROBE_DEFER; + + iommu = core->fw.iommu_domain; + core->fw.mapped_mem_size = mem_size; + + ret = iommu_map(iommu, VENUS_FW_START_ADDR, mem_phys, mem_size, + IOMMU_READ | IOMMU_WRITE | IOMMU_PRIV); + if (ret) { + dev_err(dev, "could not map video firmware region\n"); + return ret; + } + + venus_reset_cpu(core); + + return 0; +} + +static int venus_shutdown_no_tz(struct venus_core *core) +{ + const size_t mapped = core->fw.mapped_mem_size; + struct iommu_domain *iommu; + size_t unmapped; + u32 reg; + struct device *dev = core->fw.dev; + void __iomem *wrapper_base = core->wrapper_base; + void __iomem *wrapper_tz_base = core->wrapper_tz_base; + + if (IS_V6(core)) { + /* Assert the reset to XTSS */ + reg = readl_relaxed(wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + reg |= WRAPPER_XTSS_SW_RESET_BIT; + writel_relaxed(reg, wrapper_tz_base + WRAPPER_TZ_XTSS_SW_RESET); + } else { + /* Assert the reset to ARM9 */ + reg = readl_relaxed(wrapper_base + WRAPPER_A9SS_SW_RESET); + reg |= WRAPPER_A9SS_SW_RESET_BIT; + writel_relaxed(reg, wrapper_base + WRAPPER_A9SS_SW_RESET); + } + /* Make sure reset is asserted before the mapping is removed */ + mb(); + + iommu = core->fw.iommu_domain; + + if (core->fw.mapped_mem_size && iommu) { + unmapped = iommu_unmap(iommu, VENUS_FW_START_ADDR, mapped); + + if (unmapped != mapped) + dev_err(dev, "failed to unmap firmware\n"); + else + core->fw.mapped_mem_size = 0; + } + + return 0; +} + +int venus_boot(struct venus_core *core) +{ + struct device *dev = core->dev; + const struct venus_resources *res = core->res; + const char *fwpath = NULL; + phys_addr_t mem_phys; + size_t mem_size; + int ret; + + if (!IS_ENABLED(CONFIG_QCOM_MDT_LOADER) || + (core->use_tz && !qcom_scm_is_available())) + return -EPROBE_DEFER; + + ret = of_property_read_string_index(dev->of_node, "firmware-name", 0, + &fwpath); + if (ret) + fwpath = core->res->fwname; + + ret = venus_load_fw(core, fwpath, &mem_phys, &mem_size); + if (ret) { + dev_err(dev, "fail to load video firmware\n"); + return -EINVAL; + } + + core->fw.mem_size = mem_size; + core->fw.mem_phys = mem_phys; + + if (core->use_tz) + ret = qcom_scm_pas_auth_and_reset(VENUS_PAS_ID); + else + ret = venus_boot_no_tz(core, mem_phys, mem_size); + + if (ret) + return ret; + + if (core->use_tz && res->cp_size) { + ret = qcom_scm_mem_protect_video_var(res->cp_start, + res->cp_size, + res->cp_nonpixel_start, + res->cp_nonpixel_size); + if (ret) { + qcom_scm_pas_shutdown(VENUS_PAS_ID); + dev_err(dev, "set virtual address ranges fail (%d)\n", + ret); + return ret; + } + } + + return 0; +} + +int venus_shutdown(struct venus_core *core) +{ + int ret; + + if (core->use_tz) + ret = qcom_scm_pas_shutdown(VENUS_PAS_ID); + else + ret = venus_shutdown_no_tz(core); + + return ret; +} + +int venus_firmware_init(struct venus_core *core) +{ + struct platform_device_info info; + struct iommu_domain *iommu_dom; + struct platform_device *pdev; + struct device_node *np; + int ret; + + np = of_get_child_by_name(core->dev->of_node, "video-firmware"); + if (!np) { + core->use_tz = true; + return 0; + } + + memset(&info, 0, sizeof(info)); + info.fwnode = &np->fwnode; + info.parent = core->dev; + info.name = np->name; + info.dma_mask = DMA_BIT_MASK(32); + + pdev = platform_device_register_full(&info); + if (IS_ERR(pdev)) { + of_node_put(np); + return PTR_ERR(pdev); + } + + pdev->dev.of_node = np; + + ret = of_dma_configure(&pdev->dev, np, true); + if (ret) { + dev_err(core->dev, "dma configure fail\n"); + goto err_unregister; + } + + core->fw.dev = &pdev->dev; + + iommu_dom = iommu_domain_alloc(&platform_bus_type); + if (!iommu_dom) { + dev_err(core->fw.dev, "Failed to allocate iommu domain\n"); + ret = -ENOMEM; + goto err_unregister; + } + + ret = iommu_attach_device(iommu_dom, core->fw.dev); + if (ret) { + dev_err(core->fw.dev, "could not attach device\n"); + goto err_iommu_free; + } + + core->fw.iommu_domain = iommu_dom; + + of_node_put(np); + + return 0; + +err_iommu_free: + iommu_domain_free(iommu_dom); +err_unregister: + platform_device_unregister(pdev); + of_node_put(np); + return ret; +} + +void venus_firmware_deinit(struct venus_core *core) +{ + struct iommu_domain *iommu; + + if (!core->fw.dev) + return; + + iommu = core->fw.iommu_domain; + + iommu_detach_device(iommu, core->fw.dev); + + if (core->fw.iommu_domain) { + iommu_domain_free(iommu); + core->fw.iommu_domain = NULL; + } + + platform_device_unregister(to_platform_device(core->fw.dev)); +} diff --git a/drivers/media/platform/qcom/venus/firmware.h b/drivers/media/platform/qcom/venus/firmware.h new file mode 100644 index 000000000..aaccd847f --- /dev/null +++ b/drivers/media/platform/qcom/venus/firmware.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_FIRMWARE_H__ +#define __VENUS_FIRMWARE_H__ + +struct device; + +int venus_firmware_init(struct venus_core *core); +void venus_firmware_deinit(struct venus_core *core); +int venus_boot(struct venus_core *core); +int venus_shutdown(struct venus_core *core); +int venus_set_hw_state(struct venus_core *core, bool suspend); + +static inline int venus_set_hw_state_suspend(struct venus_core *core) +{ + return venus_set_hw_state(core, false); +} + +static inline int venus_set_hw_state_resume(struct venus_core *core) +{ + return venus_set_hw_state(core, true); +} + +#endif diff --git a/drivers/media/platform/qcom/venus/helpers.c b/drivers/media/platform/qcom/venus/helpers.c new file mode 100644 index 000000000..ca6555bdc --- /dev/null +++ b/drivers/media/platform/qcom/venus/helpers.c @@ -0,0 +1,1838 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "helpers.h" +#include "hfi_helper.h" +#include "pm_helpers.h" +#include "hfi_platform.h" +#include "hfi_parser.h" + +#define NUM_MBS_720P (((ALIGN(1280, 16)) >> 4) * ((ALIGN(736, 16)) >> 4)) +#define NUM_MBS_4K (((ALIGN(4096, 16)) >> 4) * ((ALIGN(2304, 16)) >> 4)) + +enum dpb_buf_owner { + DRIVER, + FIRMWARE, +}; + +struct intbuf { + struct list_head list; + u32 type; + size_t size; + void *va; + dma_addr_t da; + unsigned long attrs; + enum dpb_buf_owner owned_by; + u32 dpb_out_tag; +}; + +bool venus_helper_check_codec(struct venus_inst *inst, u32 v4l2_pixfmt) +{ + struct venus_core *core = inst->core; + u32 session_type = inst->session_type; + u32 codec; + + switch (v4l2_pixfmt) { + case V4L2_PIX_FMT_H264: + codec = HFI_VIDEO_CODEC_H264; + break; + case V4L2_PIX_FMT_H263: + codec = HFI_VIDEO_CODEC_H263; + break; + case V4L2_PIX_FMT_MPEG1: + codec = HFI_VIDEO_CODEC_MPEG1; + break; + case V4L2_PIX_FMT_MPEG2: + codec = HFI_VIDEO_CODEC_MPEG2; + break; + case V4L2_PIX_FMT_MPEG4: + codec = HFI_VIDEO_CODEC_MPEG4; + break; + case V4L2_PIX_FMT_VC1_ANNEX_G: + case V4L2_PIX_FMT_VC1_ANNEX_L: + codec = HFI_VIDEO_CODEC_VC1; + break; + case V4L2_PIX_FMT_VP8: + codec = HFI_VIDEO_CODEC_VP8; + break; + case V4L2_PIX_FMT_VP9: + codec = HFI_VIDEO_CODEC_VP9; + break; + case V4L2_PIX_FMT_XVID: + codec = HFI_VIDEO_CODEC_DIVX; + break; + case V4L2_PIX_FMT_HEVC: + codec = HFI_VIDEO_CODEC_HEVC; + break; + default: + return false; + } + + if (session_type == VIDC_SESSION_TYPE_ENC && core->enc_codecs & codec) + return true; + + if (session_type == VIDC_SESSION_TYPE_DEC && core->dec_codecs & codec) + return true; + + return false; +} +EXPORT_SYMBOL_GPL(venus_helper_check_codec); + +static void free_dpb_buf(struct venus_inst *inst, struct intbuf *buf) +{ + ida_free(&inst->dpb_ids, buf->dpb_out_tag); + + list_del_init(&buf->list); + dma_free_attrs(inst->core->dev, buf->size, buf->va, buf->da, + buf->attrs); + kfree(buf); +} + +int venus_helper_queue_dpb_bufs(struct venus_inst *inst) +{ + struct intbuf *buf, *next; + unsigned int dpb_size = 0; + int ret = 0; + + if (inst->dpb_buftype == HFI_BUFFER_OUTPUT) + dpb_size = inst->output_buf_size; + else if (inst->dpb_buftype == HFI_BUFFER_OUTPUT2) + dpb_size = inst->output2_buf_size; + + list_for_each_entry_safe(buf, next, &inst->dpbbufs, list) { + struct hfi_frame_data fdata; + + memset(&fdata, 0, sizeof(fdata)); + fdata.alloc_len = buf->size; + fdata.device_addr = buf->da; + fdata.buffer_type = buf->type; + + if (buf->owned_by == FIRMWARE) + continue; + + /* free buffer from previous sequence which was released later */ + if (dpb_size > buf->size) { + free_dpb_buf(inst, buf); + continue; + } + + fdata.clnt_data = buf->dpb_out_tag; + + ret = hfi_session_process_buf(inst, &fdata); + if (ret) + goto fail; + + buf->owned_by = FIRMWARE; + } + +fail: + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_queue_dpb_bufs); + +int venus_helper_free_dpb_bufs(struct venus_inst *inst) +{ + struct intbuf *buf, *n; + + list_for_each_entry_safe(buf, n, &inst->dpbbufs, list) { + if (buf->owned_by == FIRMWARE) + continue; + free_dpb_buf(inst, buf); + } + + if (list_empty(&inst->dpbbufs)) + INIT_LIST_HEAD(&inst->dpbbufs); + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_free_dpb_bufs); + +int venus_helper_alloc_dpb_bufs(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev; + enum hfi_version ver = core->res->hfi_version; + struct hfi_buffer_requirements bufreq; + u32 buftype = inst->dpb_buftype; + unsigned int dpb_size = 0; + struct intbuf *buf; + unsigned int i; + u32 count; + int ret; + int id; + + /* no need to allocate dpb buffers */ + if (!inst->dpb_fmt) + return 0; + + if (inst->dpb_buftype == HFI_BUFFER_OUTPUT) + dpb_size = inst->output_buf_size; + else if (inst->dpb_buftype == HFI_BUFFER_OUTPUT2) + dpb_size = inst->output2_buf_size; + + if (!dpb_size) + return 0; + + ret = venus_helper_get_bufreq(inst, buftype, &bufreq); + if (ret) + return ret; + + count = HFI_BUFREQ_COUNT_MIN(&bufreq, ver); + + for (i = 0; i < count; i++) { + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto fail; + } + + buf->type = buftype; + buf->size = dpb_size; + buf->attrs = DMA_ATTR_WRITE_COMBINE | + DMA_ATTR_NO_KERNEL_MAPPING; + buf->va = dma_alloc_attrs(dev, buf->size, &buf->da, GFP_KERNEL, + buf->attrs); + if (!buf->va) { + ret = -ENOMEM; + goto fail; + } + buf->owned_by = DRIVER; + + id = ida_alloc_min(&inst->dpb_ids, VB2_MAX_FRAME, GFP_KERNEL); + if (id < 0) { + ret = id; + goto fail; + } + + buf->dpb_out_tag = id; + + list_add_tail(&buf->list, &inst->dpbbufs); + } + + return 0; + +fail: + kfree(buf); + venus_helper_free_dpb_bufs(inst); + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_alloc_dpb_bufs); + +static int intbufs_set_buffer(struct venus_inst *inst, u32 type) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev; + struct hfi_buffer_requirements bufreq; + struct hfi_buffer_desc bd; + struct intbuf *buf; + unsigned int i; + int ret; + + ret = venus_helper_get_bufreq(inst, type, &bufreq); + if (ret) + return 0; + + if (!bufreq.size) + return 0; + + for (i = 0; i < bufreq.count_actual; i++) { + buf = kzalloc(sizeof(*buf), GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto fail; + } + + buf->type = bufreq.type; + buf->size = bufreq.size; + buf->attrs = DMA_ATTR_WRITE_COMBINE | + DMA_ATTR_NO_KERNEL_MAPPING; + buf->va = dma_alloc_attrs(dev, buf->size, &buf->da, GFP_KERNEL, + buf->attrs); + if (!buf->va) { + ret = -ENOMEM; + goto fail; + } + + memset(&bd, 0, sizeof(bd)); + bd.buffer_size = buf->size; + bd.buffer_type = buf->type; + bd.num_buffers = 1; + bd.device_addr = buf->da; + + ret = hfi_session_set_buffers(inst, &bd); + if (ret) { + dev_err(dev, "set session buffers failed\n"); + goto dma_free; + } + + list_add_tail(&buf->list, &inst->internalbufs); + } + + return 0; + +dma_free: + dma_free_attrs(dev, buf->size, buf->va, buf->da, buf->attrs); +fail: + kfree(buf); + return ret; +} + +static int intbufs_unset_buffers(struct venus_inst *inst) +{ + struct hfi_buffer_desc bd = {0}; + struct intbuf *buf, *n; + int ret = 0; + + list_for_each_entry_safe(buf, n, &inst->internalbufs, list) { + bd.buffer_size = buf->size; + bd.buffer_type = buf->type; + bd.num_buffers = 1; + bd.device_addr = buf->da; + bd.response_required = true; + + ret = hfi_session_unset_buffers(inst, &bd); + + list_del_init(&buf->list); + dma_free_attrs(inst->core->dev, buf->size, buf->va, buf->da, + buf->attrs); + kfree(buf); + } + + return ret; +} + +static const unsigned int intbuf_types_1xx[] = { + HFI_BUFFER_INTERNAL_SCRATCH(HFI_VERSION_1XX), + HFI_BUFFER_INTERNAL_SCRATCH_1(HFI_VERSION_1XX), + HFI_BUFFER_INTERNAL_SCRATCH_2(HFI_VERSION_1XX), + HFI_BUFFER_INTERNAL_PERSIST, + HFI_BUFFER_INTERNAL_PERSIST_1, +}; + +static const unsigned int intbuf_types_4xx[] = { + HFI_BUFFER_INTERNAL_SCRATCH(HFI_VERSION_4XX), + HFI_BUFFER_INTERNAL_SCRATCH_1(HFI_VERSION_4XX), + HFI_BUFFER_INTERNAL_SCRATCH_2(HFI_VERSION_4XX), + HFI_BUFFER_INTERNAL_PERSIST, + HFI_BUFFER_INTERNAL_PERSIST_1, +}; + +static const unsigned int intbuf_types_6xx[] = { + HFI_BUFFER_INTERNAL_SCRATCH(HFI_VERSION_6XX), + HFI_BUFFER_INTERNAL_SCRATCH_1(HFI_VERSION_6XX), + HFI_BUFFER_INTERNAL_SCRATCH_2(HFI_VERSION_6XX), + HFI_BUFFER_INTERNAL_PERSIST, + HFI_BUFFER_INTERNAL_PERSIST_1, +}; + +int venus_helper_intbufs_alloc(struct venus_inst *inst) +{ + const unsigned int *intbuf; + size_t arr_sz, i; + int ret; + + if (IS_V6(inst->core)) { + arr_sz = ARRAY_SIZE(intbuf_types_6xx); + intbuf = intbuf_types_6xx; + } else if (IS_V4(inst->core)) { + arr_sz = ARRAY_SIZE(intbuf_types_4xx); + intbuf = intbuf_types_4xx; + } else { + arr_sz = ARRAY_SIZE(intbuf_types_1xx); + intbuf = intbuf_types_1xx; + } + + for (i = 0; i < arr_sz; i++) { + ret = intbufs_set_buffer(inst, intbuf[i]); + if (ret) + goto error; + } + + return 0; + +error: + intbufs_unset_buffers(inst); + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_intbufs_alloc); + +int venus_helper_intbufs_free(struct venus_inst *inst) +{ + return intbufs_unset_buffers(inst); +} +EXPORT_SYMBOL_GPL(venus_helper_intbufs_free); + +int venus_helper_intbufs_realloc(struct venus_inst *inst) +{ + enum hfi_version ver = inst->core->res->hfi_version; + struct hfi_buffer_desc bd; + struct intbuf *buf, *n; + int ret; + + list_for_each_entry_safe(buf, n, &inst->internalbufs, list) { + if (buf->type == HFI_BUFFER_INTERNAL_PERSIST || + buf->type == HFI_BUFFER_INTERNAL_PERSIST_1) + continue; + + memset(&bd, 0, sizeof(bd)); + bd.buffer_size = buf->size; + bd.buffer_type = buf->type; + bd.num_buffers = 1; + bd.device_addr = buf->da; + bd.response_required = true; + + ret = hfi_session_unset_buffers(inst, &bd); + + dma_free_attrs(inst->core->dev, buf->size, buf->va, buf->da, + buf->attrs); + + list_del_init(&buf->list); + kfree(buf); + } + + ret = intbufs_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH(ver)); + if (ret) + goto err; + + ret = intbufs_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH_1(ver)); + if (ret) + goto err; + + ret = intbufs_set_buffer(inst, HFI_BUFFER_INTERNAL_SCRATCH_2(ver)); + if (ret) + goto err; + + return 0; +err: + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_intbufs_realloc); + +static void fill_buffer_desc(const struct venus_buffer *buf, + struct hfi_buffer_desc *bd, bool response) +{ + memset(bd, 0, sizeof(*bd)); + bd->buffer_type = HFI_BUFFER_OUTPUT; + bd->buffer_size = buf->size; + bd->num_buffers = 1; + bd->device_addr = buf->dma_addr; + bd->response_required = response; +} + +static void return_buf_error(struct venus_inst *inst, + struct vb2_v4l2_buffer *vbuf) +{ + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + + if (vbuf->vb2_buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + v4l2_m2m_src_buf_remove_by_buf(m2m_ctx, vbuf); + else + v4l2_m2m_dst_buf_remove_by_buf(m2m_ctx, vbuf); + + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); +} + +static void +put_ts_metadata(struct venus_inst *inst, struct vb2_v4l2_buffer *vbuf) +{ + struct vb2_buffer *vb = &vbuf->vb2_buf; + unsigned int i; + int slot = -1; + u64 ts_us = vb->timestamp; + + for (i = 0; i < ARRAY_SIZE(inst->tss); i++) { + if (!inst->tss[i].used) { + slot = i; + break; + } + } + + if (slot == -1) { + dev_dbg(inst->core->dev, VDBGL "no free slot\n"); + return; + } + + do_div(ts_us, NSEC_PER_USEC); + + inst->tss[slot].used = true; + inst->tss[slot].flags = vbuf->flags; + inst->tss[slot].tc = vbuf->timecode; + inst->tss[slot].ts_us = ts_us; + inst->tss[slot].ts_ns = vb->timestamp; +} + +void venus_helper_get_ts_metadata(struct venus_inst *inst, u64 timestamp_us, + struct vb2_v4l2_buffer *vbuf) +{ + struct vb2_buffer *vb = &vbuf->vb2_buf; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(inst->tss); ++i) { + if (!inst->tss[i].used) + continue; + + if (inst->tss[i].ts_us != timestamp_us) + continue; + + inst->tss[i].used = false; + vbuf->flags |= inst->tss[i].flags; + vbuf->timecode = inst->tss[i].tc; + vb->timestamp = inst->tss[i].ts_ns; + break; + } +} +EXPORT_SYMBOL_GPL(venus_helper_get_ts_metadata); + +static int +session_process_buf(struct venus_inst *inst, struct vb2_v4l2_buffer *vbuf) +{ + struct venus_buffer *buf = to_venus_buffer(vbuf); + struct vb2_buffer *vb = &vbuf->vb2_buf; + unsigned int type = vb->type; + struct hfi_frame_data fdata; + int ret; + + memset(&fdata, 0, sizeof(fdata)); + fdata.alloc_len = buf->size; + fdata.device_addr = buf->dma_addr; + fdata.timestamp = vb->timestamp; + do_div(fdata.timestamp, NSEC_PER_USEC); + fdata.flags = 0; + fdata.clnt_data = vbuf->vb2_buf.index; + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + fdata.buffer_type = HFI_BUFFER_INPUT; + fdata.filled_len = vb2_get_plane_payload(vb, 0); + fdata.offset = vb->planes[0].data_offset; + + if (vbuf->flags & V4L2_BUF_FLAG_LAST || !fdata.filled_len) + fdata.flags |= HFI_BUFFERFLAG_EOS; + + if (inst->session_type == VIDC_SESSION_TYPE_DEC) + put_ts_metadata(inst, vbuf); + + venus_pm_load_scale(inst); + } else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (inst->session_type == VIDC_SESSION_TYPE_ENC) + fdata.buffer_type = HFI_BUFFER_OUTPUT; + else + fdata.buffer_type = inst->opb_buftype; + fdata.filled_len = 0; + fdata.offset = 0; + } + + ret = hfi_session_process_buf(inst, &fdata); + if (ret) + return ret; + + return 0; +} + +static bool is_dynamic_bufmode(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct hfi_plat_caps *caps; + + /* + * v4 doesn't send BUFFER_ALLOC_MODE_SUPPORTED property and supports + * dynamic buffer mode by default for HFI_BUFFER_OUTPUT/OUTPUT2. + */ + if (IS_V4(core) || IS_V6(core)) + return true; + + caps = venus_caps_by_codec(core, inst->hfi_codec, inst->session_type); + if (!caps) + return false; + + return caps->cap_bufs_mode_dynamic; +} + +int venus_helper_unregister_bufs(struct venus_inst *inst) +{ + struct venus_buffer *buf, *n; + struct hfi_buffer_desc bd; + int ret = 0; + + if (is_dynamic_bufmode(inst)) + return 0; + + list_for_each_entry_safe(buf, n, &inst->registeredbufs, reg_list) { + fill_buffer_desc(buf, &bd, true); + ret = hfi_session_unset_buffers(inst, &bd); + list_del_init(&buf->reg_list); + } + + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_unregister_bufs); + +static int session_register_bufs(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev; + struct hfi_buffer_desc bd; + struct venus_buffer *buf; + int ret = 0; + + if (is_dynamic_bufmode(inst)) + return 0; + + list_for_each_entry(buf, &inst->registeredbufs, reg_list) { + fill_buffer_desc(buf, &bd, false); + ret = hfi_session_set_buffers(inst, &bd); + if (ret) { + dev_err(dev, "%s: set buffer failed\n", __func__); + break; + } + } + + return ret; +} + +static u32 to_hfi_raw_fmt(u32 v4l2_fmt) +{ + switch (v4l2_fmt) { + case V4L2_PIX_FMT_NV12: + return HFI_COLOR_FORMAT_NV12; + case V4L2_PIX_FMT_NV21: + return HFI_COLOR_FORMAT_NV21; + case V4L2_PIX_FMT_QC08C: + return HFI_COLOR_FORMAT_NV12_UBWC; + case V4L2_PIX_FMT_QC10C: + return HFI_COLOR_FORMAT_YUV420_TP10_UBWC; + default: + break; + } + + return 0; +} + +static int platform_get_bufreq(struct venus_inst *inst, u32 buftype, + struct hfi_buffer_requirements *req) +{ + enum hfi_version version = inst->core->res->hfi_version; + const struct hfi_platform *hfi_plat; + struct hfi_plat_buffers_params params; + bool is_dec = inst->session_type == VIDC_SESSION_TYPE_DEC; + struct venc_controls *enc_ctr = &inst->controls.enc; + + hfi_plat = hfi_platform_get(version); + + if (!hfi_plat || !hfi_plat->bufreq) + return -EINVAL; + + params.version = version; + params.num_vpp_pipes = inst->core->res->num_vpp_pipes; + + if (is_dec) { + params.width = inst->width; + params.height = inst->height; + params.codec = inst->fmt_out->pixfmt; + params.hfi_color_fmt = to_hfi_raw_fmt(inst->fmt_cap->pixfmt); + params.dec.max_mbs_per_frame = mbs_per_frame_max(inst); + params.dec.buffer_size_limit = 0; + params.dec.is_secondary_output = + inst->opb_buftype == HFI_BUFFER_OUTPUT2; + params.dec.is_interlaced = + inst->pic_struct != HFI_INTERLACE_FRAME_PROGRESSIVE; + } else { + params.width = inst->out_width; + params.height = inst->out_height; + params.codec = inst->fmt_cap->pixfmt; + params.hfi_color_fmt = to_hfi_raw_fmt(inst->fmt_out->pixfmt); + params.enc.work_mode = VIDC_WORK_MODE_2; + params.enc.rc_type = HFI_RATE_CONTROL_OFF; + if (enc_ctr->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CQ) + params.enc.rc_type = HFI_RATE_CONTROL_CQ; + params.enc.num_b_frames = enc_ctr->num_b_frames; + params.enc.is_tenbit = inst->bit_depth == VIDC_BITDEPTH_10; + } + + return hfi_plat->bufreq(¶ms, inst->session_type, buftype, req); +} + +int venus_helper_get_bufreq(struct venus_inst *inst, u32 type, + struct hfi_buffer_requirements *req) +{ + u32 ptype = HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS; + union hfi_get_property hprop; + unsigned int i; + int ret; + + memset(req, 0, sizeof(*req)); + + if (type == HFI_BUFFER_OUTPUT || type == HFI_BUFFER_OUTPUT2) + req->count_min = inst->fw_min_cnt; + + ret = platform_get_bufreq(inst, type, req); + if (!ret) { + if (type == HFI_BUFFER_OUTPUT || type == HFI_BUFFER_OUTPUT2) + inst->fw_min_cnt = req->count_min; + return 0; + } + + ret = hfi_session_get_property(inst, ptype, &hprop); + if (ret) + return ret; + + ret = -EINVAL; + + for (i = 0; i < HFI_BUFFER_TYPE_MAX; i++) { + if (hprop.bufreq[i].type != type) + continue; + + memcpy(req, &hprop.bufreq[i], sizeof(*req)); + ret = 0; + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_get_bufreq); + +struct id_mapping { + u32 hfi_id; + u32 v4l2_id; +}; + +static const struct id_mapping mpeg4_profiles[] = { + { HFI_MPEG4_PROFILE_SIMPLE, V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE }, + { HFI_MPEG4_PROFILE_ADVANCEDSIMPLE, V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE }, +}; + +static const struct id_mapping mpeg4_levels[] = { + { HFI_MPEG4_LEVEL_0, V4L2_MPEG_VIDEO_MPEG4_LEVEL_0 }, + { HFI_MPEG4_LEVEL_0b, V4L2_MPEG_VIDEO_MPEG4_LEVEL_0B }, + { HFI_MPEG4_LEVEL_1, V4L2_MPEG_VIDEO_MPEG4_LEVEL_1 }, + { HFI_MPEG4_LEVEL_2, V4L2_MPEG_VIDEO_MPEG4_LEVEL_2 }, + { HFI_MPEG4_LEVEL_3, V4L2_MPEG_VIDEO_MPEG4_LEVEL_3 }, + { HFI_MPEG4_LEVEL_4, V4L2_MPEG_VIDEO_MPEG4_LEVEL_4 }, + { HFI_MPEG4_LEVEL_5, V4L2_MPEG_VIDEO_MPEG4_LEVEL_5 }, +}; + +static const struct id_mapping mpeg2_profiles[] = { + { HFI_MPEG2_PROFILE_SIMPLE, V4L2_MPEG_VIDEO_MPEG2_PROFILE_SIMPLE }, + { HFI_MPEG2_PROFILE_MAIN, V4L2_MPEG_VIDEO_MPEG2_PROFILE_MAIN }, + { HFI_MPEG2_PROFILE_SNR, V4L2_MPEG_VIDEO_MPEG2_PROFILE_SNR_SCALABLE }, + { HFI_MPEG2_PROFILE_SPATIAL, V4L2_MPEG_VIDEO_MPEG2_PROFILE_SPATIALLY_SCALABLE }, + { HFI_MPEG2_PROFILE_HIGH, V4L2_MPEG_VIDEO_MPEG2_PROFILE_HIGH }, +}; + +static const struct id_mapping mpeg2_levels[] = { + { HFI_MPEG2_LEVEL_LL, V4L2_MPEG_VIDEO_MPEG2_LEVEL_LOW }, + { HFI_MPEG2_LEVEL_ML, V4L2_MPEG_VIDEO_MPEG2_LEVEL_MAIN }, + { HFI_MPEG2_LEVEL_H14, V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH_1440 }, + { HFI_MPEG2_LEVEL_HL, V4L2_MPEG_VIDEO_MPEG2_LEVEL_HIGH }, +}; + +static const struct id_mapping h264_profiles[] = { + { HFI_H264_PROFILE_BASELINE, V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE }, + { HFI_H264_PROFILE_MAIN, V4L2_MPEG_VIDEO_H264_PROFILE_MAIN }, + { HFI_H264_PROFILE_HIGH, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH }, + { HFI_H264_PROFILE_STEREO_HIGH, V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH }, + { HFI_H264_PROFILE_MULTIVIEW_HIGH, V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH }, + { HFI_H264_PROFILE_CONSTRAINED_BASE, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE }, + { HFI_H264_PROFILE_CONSTRAINED_HIGH, V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_HIGH }, +}; + +static const struct id_mapping h264_levels[] = { + { HFI_H264_LEVEL_1, V4L2_MPEG_VIDEO_H264_LEVEL_1_0 }, + { HFI_H264_LEVEL_1b, V4L2_MPEG_VIDEO_H264_LEVEL_1B }, + { HFI_H264_LEVEL_11, V4L2_MPEG_VIDEO_H264_LEVEL_1_1 }, + { HFI_H264_LEVEL_12, V4L2_MPEG_VIDEO_H264_LEVEL_1_2 }, + { HFI_H264_LEVEL_13, V4L2_MPEG_VIDEO_H264_LEVEL_1_3 }, + { HFI_H264_LEVEL_2, V4L2_MPEG_VIDEO_H264_LEVEL_2_0 }, + { HFI_H264_LEVEL_21, V4L2_MPEG_VIDEO_H264_LEVEL_2_1 }, + { HFI_H264_LEVEL_22, V4L2_MPEG_VIDEO_H264_LEVEL_2_2 }, + { HFI_H264_LEVEL_3, V4L2_MPEG_VIDEO_H264_LEVEL_3_0 }, + { HFI_H264_LEVEL_31, V4L2_MPEG_VIDEO_H264_LEVEL_3_1 }, + { HFI_H264_LEVEL_32, V4L2_MPEG_VIDEO_H264_LEVEL_3_2 }, + { HFI_H264_LEVEL_4, V4L2_MPEG_VIDEO_H264_LEVEL_4_0 }, + { HFI_H264_LEVEL_41, V4L2_MPEG_VIDEO_H264_LEVEL_4_1 }, + { HFI_H264_LEVEL_42, V4L2_MPEG_VIDEO_H264_LEVEL_4_2 }, + { HFI_H264_LEVEL_5, V4L2_MPEG_VIDEO_H264_LEVEL_5_0 }, + { HFI_H264_LEVEL_51, V4L2_MPEG_VIDEO_H264_LEVEL_5_1 }, + { HFI_H264_LEVEL_52, V4L2_MPEG_VIDEO_H264_LEVEL_5_1 }, +}; + +static const struct id_mapping hevc_profiles[] = { + { HFI_HEVC_PROFILE_MAIN, V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN }, + { HFI_HEVC_PROFILE_MAIN_STILL_PIC, V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE }, + { HFI_HEVC_PROFILE_MAIN10, V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10 }, +}; + +static const struct id_mapping hevc_levels[] = { + { HFI_HEVC_LEVEL_1, V4L2_MPEG_VIDEO_HEVC_LEVEL_1 }, + { HFI_HEVC_LEVEL_2, V4L2_MPEG_VIDEO_HEVC_LEVEL_2 }, + { HFI_HEVC_LEVEL_21, V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1 }, + { HFI_HEVC_LEVEL_3, V4L2_MPEG_VIDEO_HEVC_LEVEL_3 }, + { HFI_HEVC_LEVEL_31, V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1 }, + { HFI_HEVC_LEVEL_4, V4L2_MPEG_VIDEO_HEVC_LEVEL_4 }, + { HFI_HEVC_LEVEL_41, V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1 }, + { HFI_HEVC_LEVEL_5, V4L2_MPEG_VIDEO_HEVC_LEVEL_5 }, + { HFI_HEVC_LEVEL_51, V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1 }, + { HFI_HEVC_LEVEL_52, V4L2_MPEG_VIDEO_HEVC_LEVEL_5_2 }, + { HFI_HEVC_LEVEL_6, V4L2_MPEG_VIDEO_HEVC_LEVEL_6 }, + { HFI_HEVC_LEVEL_61, V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1 }, + { HFI_HEVC_LEVEL_62, V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2 }, +}; + +static const struct id_mapping vp8_profiles[] = { + { HFI_VPX_PROFILE_VERSION_0, V4L2_MPEG_VIDEO_VP8_PROFILE_0 }, + { HFI_VPX_PROFILE_VERSION_1, V4L2_MPEG_VIDEO_VP8_PROFILE_1 }, + { HFI_VPX_PROFILE_VERSION_2, V4L2_MPEG_VIDEO_VP8_PROFILE_2 }, + { HFI_VPX_PROFILE_VERSION_3, V4L2_MPEG_VIDEO_VP8_PROFILE_3 }, +}; + +static const struct id_mapping vp9_profiles[] = { + { HFI_VP9_PROFILE_P0, V4L2_MPEG_VIDEO_VP9_PROFILE_0 }, + { HFI_VP9_PROFILE_P2_10B, V4L2_MPEG_VIDEO_VP9_PROFILE_2 }, +}; + +static const struct id_mapping vp9_levels[] = { + { HFI_VP9_LEVEL_1, V4L2_MPEG_VIDEO_VP9_LEVEL_1_0 }, + { HFI_VP9_LEVEL_11, V4L2_MPEG_VIDEO_VP9_LEVEL_1_1 }, + { HFI_VP9_LEVEL_2, V4L2_MPEG_VIDEO_VP9_LEVEL_2_0}, + { HFI_VP9_LEVEL_21, V4L2_MPEG_VIDEO_VP9_LEVEL_2_1 }, + { HFI_VP9_LEVEL_3, V4L2_MPEG_VIDEO_VP9_LEVEL_3_0}, + { HFI_VP9_LEVEL_31, V4L2_MPEG_VIDEO_VP9_LEVEL_3_1 }, + { HFI_VP9_LEVEL_4, V4L2_MPEG_VIDEO_VP9_LEVEL_4_0 }, + { HFI_VP9_LEVEL_41, V4L2_MPEG_VIDEO_VP9_LEVEL_4_1 }, + { HFI_VP9_LEVEL_5, V4L2_MPEG_VIDEO_VP9_LEVEL_5_0 }, + { HFI_VP9_LEVEL_51, V4L2_MPEG_VIDEO_VP9_LEVEL_5_1 }, + { HFI_VP9_LEVEL_6, V4L2_MPEG_VIDEO_VP9_LEVEL_6_0 }, + { HFI_VP9_LEVEL_61, V4L2_MPEG_VIDEO_VP9_LEVEL_6_1 }, +}; + +static u32 find_v4l2_id(u32 hfi_id, const struct id_mapping *array, unsigned int array_sz) +{ + unsigned int i; + + if (!array || !array_sz) + return 0; + + for (i = 0; i < array_sz; i++) + if (hfi_id == array[i].hfi_id) + return array[i].v4l2_id; + + return 0; +} + +static u32 find_hfi_id(u32 v4l2_id, const struct id_mapping *array, unsigned int array_sz) +{ + unsigned int i; + + if (!array || !array_sz) + return 0; + + for (i = 0; i < array_sz; i++) + if (v4l2_id == array[i].v4l2_id) + return array[i].hfi_id; + + return 0; +} + +static void +v4l2_id_profile_level(u32 hfi_codec, struct hfi_profile_level *pl, u32 *profile, u32 *level) +{ + u32 hfi_pf = pl->profile; + u32 hfi_lvl = pl->level; + + switch (hfi_codec) { + case HFI_VIDEO_CODEC_H264: + *profile = find_v4l2_id(hfi_pf, h264_profiles, ARRAY_SIZE(h264_profiles)); + *level = find_v4l2_id(hfi_lvl, h264_levels, ARRAY_SIZE(h264_levels)); + break; + case HFI_VIDEO_CODEC_MPEG2: + *profile = find_v4l2_id(hfi_pf, mpeg2_profiles, ARRAY_SIZE(mpeg2_profiles)); + *level = find_v4l2_id(hfi_lvl, mpeg2_levels, ARRAY_SIZE(mpeg2_levels)); + break; + case HFI_VIDEO_CODEC_MPEG4: + *profile = find_v4l2_id(hfi_pf, mpeg4_profiles, ARRAY_SIZE(mpeg4_profiles)); + *level = find_v4l2_id(hfi_lvl, mpeg4_levels, ARRAY_SIZE(mpeg4_levels)); + break; + case HFI_VIDEO_CODEC_VP8: + *profile = find_v4l2_id(hfi_pf, vp8_profiles, ARRAY_SIZE(vp8_profiles)); + *level = 0; + break; + case HFI_VIDEO_CODEC_VP9: + *profile = find_v4l2_id(hfi_pf, vp9_profiles, ARRAY_SIZE(vp9_profiles)); + *level = find_v4l2_id(hfi_lvl, vp9_levels, ARRAY_SIZE(vp9_levels)); + break; + case HFI_VIDEO_CODEC_HEVC: + *profile = find_v4l2_id(hfi_pf, hevc_profiles, ARRAY_SIZE(hevc_profiles)); + *level = find_v4l2_id(hfi_lvl, hevc_levels, ARRAY_SIZE(hevc_levels)); + break; + default: + break; + } +} + +static void +hfi_id_profile_level(u32 hfi_codec, u32 v4l2_pf, u32 v4l2_lvl, struct hfi_profile_level *pl) +{ + switch (hfi_codec) { + case HFI_VIDEO_CODEC_H264: + pl->profile = find_hfi_id(v4l2_pf, h264_profiles, ARRAY_SIZE(h264_profiles)); + pl->level = find_hfi_id(v4l2_lvl, h264_levels, ARRAY_SIZE(h264_levels)); + break; + case HFI_VIDEO_CODEC_MPEG2: + pl->profile = find_hfi_id(v4l2_pf, mpeg2_profiles, ARRAY_SIZE(mpeg2_profiles)); + pl->level = find_hfi_id(v4l2_lvl, mpeg2_levels, ARRAY_SIZE(mpeg2_levels)); + break; + case HFI_VIDEO_CODEC_MPEG4: + pl->profile = find_hfi_id(v4l2_pf, mpeg4_profiles, ARRAY_SIZE(mpeg4_profiles)); + pl->level = find_hfi_id(v4l2_lvl, mpeg4_levels, ARRAY_SIZE(mpeg4_levels)); + break; + case HFI_VIDEO_CODEC_VP8: + pl->profile = find_hfi_id(v4l2_pf, vp8_profiles, ARRAY_SIZE(vp8_profiles)); + pl->level = 0; + break; + case HFI_VIDEO_CODEC_VP9: + pl->profile = find_hfi_id(v4l2_pf, vp9_profiles, ARRAY_SIZE(vp9_profiles)); + pl->level = find_hfi_id(v4l2_lvl, vp9_levels, ARRAY_SIZE(vp9_levels)); + break; + case HFI_VIDEO_CODEC_HEVC: + pl->profile = find_hfi_id(v4l2_pf, hevc_profiles, ARRAY_SIZE(hevc_profiles)); + pl->level = find_hfi_id(v4l2_lvl, hevc_levels, ARRAY_SIZE(hevc_levels)); + break; + default: + break; + } +} + +int venus_helper_get_profile_level(struct venus_inst *inst, u32 *profile, u32 *level) +{ + const u32 ptype = HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT; + union hfi_get_property hprop; + int ret; + + ret = hfi_session_get_property(inst, ptype, &hprop); + if (ret) + return ret; + + v4l2_id_profile_level(inst->hfi_codec, &hprop.profile_level, profile, level); + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_get_profile_level); + +int venus_helper_set_profile_level(struct venus_inst *inst, u32 profile, u32 level) +{ + const u32 ptype = HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT; + struct hfi_profile_level pl; + + hfi_id_profile_level(inst->hfi_codec, profile, level, &pl); + + return hfi_session_set_property(inst, ptype, &pl); +} +EXPORT_SYMBOL_GPL(venus_helper_set_profile_level); + +static u32 get_framesize_raw_nv12(u32 width, u32 height) +{ + u32 y_stride, uv_stride, y_plane; + u32 y_sclines, uv_sclines, uv_plane; + u32 size; + + y_stride = ALIGN(width, 128); + uv_stride = ALIGN(width, 128); + y_sclines = ALIGN(height, 32); + uv_sclines = ALIGN(((height + 1) >> 1), 16); + + y_plane = y_stride * y_sclines; + uv_plane = uv_stride * uv_sclines + SZ_4K; + size = y_plane + uv_plane + SZ_8K; + + return ALIGN(size, SZ_4K); +} + +static u32 get_framesize_raw_nv12_ubwc(u32 width, u32 height) +{ + u32 y_meta_stride, y_meta_plane; + u32 y_stride, y_plane; + u32 uv_meta_stride, uv_meta_plane; + u32 uv_stride, uv_plane; + u32 extradata = SZ_16K; + + y_meta_stride = ALIGN(DIV_ROUND_UP(width, 32), 64); + y_meta_plane = y_meta_stride * ALIGN(DIV_ROUND_UP(height, 8), 16); + y_meta_plane = ALIGN(y_meta_plane, SZ_4K); + + y_stride = ALIGN(width, 128); + y_plane = ALIGN(y_stride * ALIGN(height, 32), SZ_4K); + + uv_meta_stride = ALIGN(DIV_ROUND_UP(width / 2, 16), 64); + uv_meta_plane = uv_meta_stride * ALIGN(DIV_ROUND_UP(height / 2, 8), 16); + uv_meta_plane = ALIGN(uv_meta_plane, SZ_4K); + + uv_stride = ALIGN(width, 128); + uv_plane = ALIGN(uv_stride * ALIGN(height / 2, 32), SZ_4K); + + return ALIGN(y_meta_plane + y_plane + uv_meta_plane + uv_plane + + max(extradata, y_stride * 48), SZ_4K); +} + +static u32 get_framesize_raw_p010(u32 width, u32 height) +{ + u32 y_plane, uv_plane, y_stride, uv_stride, y_sclines, uv_sclines; + + y_stride = ALIGN(width * 2, 256); + uv_stride = ALIGN(width * 2, 256); + y_sclines = ALIGN(height, 32); + uv_sclines = ALIGN((height + 1) >> 1, 16); + y_plane = y_stride * y_sclines; + uv_plane = uv_stride * uv_sclines; + + return ALIGN((y_plane + uv_plane), SZ_4K); +} + +static u32 get_framesize_raw_p010_ubwc(u32 width, u32 height) +{ + u32 y_stride, uv_stride, y_sclines, uv_sclines; + u32 y_ubwc_plane, uv_ubwc_plane; + u32 y_meta_stride, y_meta_scanlines; + u32 uv_meta_stride, uv_meta_scanlines; + u32 y_meta_plane, uv_meta_plane; + u32 size; + + y_stride = ALIGN(width * 2, 256); + uv_stride = ALIGN(width * 2, 256); + y_sclines = ALIGN(height, 16); + uv_sclines = ALIGN((height + 1) >> 1, 16); + + y_ubwc_plane = ALIGN(y_stride * y_sclines, SZ_4K); + uv_ubwc_plane = ALIGN(uv_stride * uv_sclines, SZ_4K); + y_meta_stride = ALIGN(DIV_ROUND_UP(width, 32), 64); + y_meta_scanlines = ALIGN(DIV_ROUND_UP(height, 4), 16); + y_meta_plane = ALIGN(y_meta_stride * y_meta_scanlines, SZ_4K); + uv_meta_stride = ALIGN(DIV_ROUND_UP((width + 1) >> 1, 16), 64); + uv_meta_scanlines = ALIGN(DIV_ROUND_UP((height + 1) >> 1, 4), 16); + uv_meta_plane = ALIGN(uv_meta_stride * uv_meta_scanlines, SZ_4K); + + size = y_ubwc_plane + uv_ubwc_plane + y_meta_plane + uv_meta_plane; + + return ALIGN(size, SZ_4K); +} + +static u32 get_framesize_raw_yuv420_tp10_ubwc(u32 width, u32 height) +{ + u32 y_stride, uv_stride, y_sclines, uv_sclines; + u32 y_ubwc_plane, uv_ubwc_plane; + u32 y_meta_stride, y_meta_scanlines; + u32 uv_meta_stride, uv_meta_scanlines; + u32 y_meta_plane, uv_meta_plane; + u32 extradata = SZ_16K; + u32 size; + + y_stride = ALIGN(width * 4 / 3, 256); + uv_stride = ALIGN(width * 4 / 3, 256); + y_sclines = ALIGN(height, 16); + uv_sclines = ALIGN((height + 1) >> 1, 16); + + y_ubwc_plane = ALIGN(y_stride * y_sclines, SZ_4K); + uv_ubwc_plane = ALIGN(uv_stride * uv_sclines, SZ_4K); + y_meta_stride = ALIGN(DIV_ROUND_UP(width, 48), 64); + y_meta_scanlines = ALIGN(DIV_ROUND_UP(height, 4), 16); + y_meta_plane = ALIGN(y_meta_stride * y_meta_scanlines, SZ_4K); + uv_meta_stride = ALIGN(DIV_ROUND_UP((width + 1) >> 1, 24), 64); + uv_meta_scanlines = ALIGN(DIV_ROUND_UP((height + 1) >> 1, 4), 16); + uv_meta_plane = ALIGN(uv_meta_stride * uv_meta_scanlines, SZ_4K); + + size = y_ubwc_plane + uv_ubwc_plane + y_meta_plane + uv_meta_plane; + size += max(extradata + SZ_8K, y_stride * 48); + + return ALIGN(size, SZ_4K); +} + +u32 venus_helper_get_framesz_raw(u32 hfi_fmt, u32 width, u32 height) +{ + switch (hfi_fmt) { + case HFI_COLOR_FORMAT_NV12: + case HFI_COLOR_FORMAT_NV21: + return get_framesize_raw_nv12(width, height); + case HFI_COLOR_FORMAT_NV12_UBWC: + return get_framesize_raw_nv12_ubwc(width, height); + case HFI_COLOR_FORMAT_P010: + return get_framesize_raw_p010(width, height); + case HFI_COLOR_FORMAT_P010_UBWC: + return get_framesize_raw_p010_ubwc(width, height); + case HFI_COLOR_FORMAT_YUV420_TP10_UBWC: + return get_framesize_raw_yuv420_tp10_ubwc(width, height); + default: + return 0; + } +} +EXPORT_SYMBOL_GPL(venus_helper_get_framesz_raw); + +u32 venus_helper_get_framesz(u32 v4l2_fmt, u32 width, u32 height) +{ + u32 hfi_fmt, sz; + bool compressed; + + switch (v4l2_fmt) { + case V4L2_PIX_FMT_MPEG: + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_H264_NO_SC: + case V4L2_PIX_FMT_H264_MVC: + case V4L2_PIX_FMT_H263: + case V4L2_PIX_FMT_MPEG1: + case V4L2_PIX_FMT_MPEG2: + case V4L2_PIX_FMT_MPEG4: + case V4L2_PIX_FMT_XVID: + case V4L2_PIX_FMT_VC1_ANNEX_G: + case V4L2_PIX_FMT_VC1_ANNEX_L: + case V4L2_PIX_FMT_VP8: + case V4L2_PIX_FMT_VP9: + case V4L2_PIX_FMT_HEVC: + compressed = true; + break; + default: + compressed = false; + break; + } + + if (compressed) { + sz = ALIGN(height, 32) * ALIGN(width, 32) * 3 / 2 / 2; + if (width < 1280 || height < 720) + sz *= 8; + return ALIGN(sz, SZ_4K); + } + + hfi_fmt = to_hfi_raw_fmt(v4l2_fmt); + if (!hfi_fmt) + return 0; + + return venus_helper_get_framesz_raw(hfi_fmt, width, height); +} +EXPORT_SYMBOL_GPL(venus_helper_get_framesz); + +int venus_helper_set_input_resolution(struct venus_inst *inst, + unsigned int width, unsigned int height) +{ + u32 ptype = HFI_PROPERTY_PARAM_FRAME_SIZE; + struct hfi_framesize fs; + + fs.buffer_type = HFI_BUFFER_INPUT; + fs.width = width; + fs.height = height; + + return hfi_session_set_property(inst, ptype, &fs); +} +EXPORT_SYMBOL_GPL(venus_helper_set_input_resolution); + +int venus_helper_set_output_resolution(struct venus_inst *inst, + unsigned int width, unsigned int height, + u32 buftype) +{ + u32 ptype = HFI_PROPERTY_PARAM_FRAME_SIZE; + struct hfi_framesize fs; + + fs.buffer_type = buftype; + fs.width = width; + fs.height = height; + + return hfi_session_set_property(inst, ptype, &fs); +} +EXPORT_SYMBOL_GPL(venus_helper_set_output_resolution); + +static u32 venus_helper_get_work_mode(struct venus_inst *inst) +{ + u32 mode; + u32 num_mbs; + + mode = VIDC_WORK_MODE_2; + if (inst->session_type == VIDC_SESSION_TYPE_DEC) { + num_mbs = (ALIGN(inst->height, 16) * ALIGN(inst->width, 16)) / 256; + if (inst->hfi_codec == HFI_VIDEO_CODEC_MPEG2 || + inst->pic_struct != HFI_INTERLACE_FRAME_PROGRESSIVE || + num_mbs <= NUM_MBS_720P) + mode = VIDC_WORK_MODE_1; + } else { + num_mbs = (ALIGN(inst->out_height, 16) * ALIGN(inst->out_width, 16)) / 256; + if (inst->hfi_codec == HFI_VIDEO_CODEC_VP8 && + num_mbs <= NUM_MBS_4K) + mode = VIDC_WORK_MODE_1; + } + + return mode; +} + +int venus_helper_set_work_mode(struct venus_inst *inst) +{ + const u32 ptype = HFI_PROPERTY_PARAM_WORK_MODE; + struct hfi_video_work_mode wm; + u32 mode; + + if (!IS_V4(inst->core) && !IS_V6(inst->core)) + return 0; + + mode = venus_helper_get_work_mode(inst); + wm.video_work_mode = mode; + return hfi_session_set_property(inst, ptype, &wm); +} +EXPORT_SYMBOL_GPL(venus_helper_set_work_mode); + +int venus_helper_set_format_constraints(struct venus_inst *inst) +{ + const u32 ptype = HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO; + struct hfi_uncompressed_plane_actual_constraints_info pconstraint; + + if (!IS_V6(inst->core)) + return 0; + + if (inst->opb_fmt == HFI_COLOR_FORMAT_NV12_UBWC || + inst->opb_fmt == HFI_COLOR_FORMAT_YUV420_TP10_UBWC) + return 0; + + pconstraint.buffer_type = HFI_BUFFER_OUTPUT2; + pconstraint.num_planes = 2; + pconstraint.plane_format[0].stride_multiples = 128; + pconstraint.plane_format[0].max_stride = 8192; + pconstraint.plane_format[0].min_plane_buffer_height_multiple = 32; + pconstraint.plane_format[0].buffer_alignment = 256; + + pconstraint.plane_format[1].stride_multiples = 128; + pconstraint.plane_format[1].max_stride = 8192; + pconstraint.plane_format[1].min_plane_buffer_height_multiple = 16; + pconstraint.plane_format[1].buffer_alignment = 256; + + return hfi_session_set_property(inst, ptype, &pconstraint); +} +EXPORT_SYMBOL_GPL(venus_helper_set_format_constraints); + +int venus_helper_set_num_bufs(struct venus_inst *inst, unsigned int input_bufs, + unsigned int output_bufs, + unsigned int output2_bufs) +{ + u32 ptype = HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL; + struct hfi_buffer_count_actual buf_count; + int ret; + + buf_count.type = HFI_BUFFER_INPUT; + buf_count.count_actual = input_bufs; + + ret = hfi_session_set_property(inst, ptype, &buf_count); + if (ret) + return ret; + + buf_count.type = HFI_BUFFER_OUTPUT; + buf_count.count_actual = output_bufs; + + ret = hfi_session_set_property(inst, ptype, &buf_count); + if (ret) + return ret; + + if (output2_bufs) { + buf_count.type = HFI_BUFFER_OUTPUT2; + buf_count.count_actual = output2_bufs; + + ret = hfi_session_set_property(inst, ptype, &buf_count); + } + + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_set_num_bufs); + +int venus_helper_set_raw_format(struct venus_inst *inst, u32 hfi_format, + u32 buftype) +{ + const u32 ptype = HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT; + struct hfi_uncompressed_format_select fmt; + + fmt.buffer_type = buftype; + fmt.format = hfi_format; + + return hfi_session_set_property(inst, ptype, &fmt); +} +EXPORT_SYMBOL_GPL(venus_helper_set_raw_format); + +int venus_helper_set_color_format(struct venus_inst *inst, u32 pixfmt) +{ + u32 hfi_format, buftype; + + if (inst->session_type == VIDC_SESSION_TYPE_DEC) + buftype = HFI_BUFFER_OUTPUT; + else if (inst->session_type == VIDC_SESSION_TYPE_ENC) + buftype = HFI_BUFFER_INPUT; + else + return -EINVAL; + + hfi_format = to_hfi_raw_fmt(pixfmt); + if (!hfi_format) + return -EINVAL; + + return venus_helper_set_raw_format(inst, hfi_format, buftype); +} +EXPORT_SYMBOL_GPL(venus_helper_set_color_format); + +int venus_helper_set_multistream(struct venus_inst *inst, bool out_en, + bool out2_en) +{ + struct hfi_multi_stream multi = {0}; + u32 ptype = HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM; + int ret; + + multi.buffer_type = HFI_BUFFER_OUTPUT; + multi.enable = out_en; + + ret = hfi_session_set_property(inst, ptype, &multi); + if (ret) + return ret; + + multi.buffer_type = HFI_BUFFER_OUTPUT2; + multi.enable = out2_en; + + return hfi_session_set_property(inst, ptype, &multi); +} +EXPORT_SYMBOL_GPL(venus_helper_set_multistream); + +int venus_helper_set_dyn_bufmode(struct venus_inst *inst) +{ + const u32 ptype = HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE; + struct hfi_buffer_alloc_mode mode; + int ret; + + if (!is_dynamic_bufmode(inst)) + return 0; + + mode.type = HFI_BUFFER_OUTPUT; + mode.mode = HFI_BUFFER_MODE_DYNAMIC; + + ret = hfi_session_set_property(inst, ptype, &mode); + if (ret) + return ret; + + mode.type = HFI_BUFFER_OUTPUT2; + + return hfi_session_set_property(inst, ptype, &mode); +} +EXPORT_SYMBOL_GPL(venus_helper_set_dyn_bufmode); + +int venus_helper_set_bufsize(struct venus_inst *inst, u32 bufsize, u32 buftype) +{ + const u32 ptype = HFI_PROPERTY_PARAM_BUFFER_SIZE_ACTUAL; + struct hfi_buffer_size_actual bufsz; + + bufsz.type = buftype; + bufsz.size = bufsize; + + return hfi_session_set_property(inst, ptype, &bufsz); +} +EXPORT_SYMBOL_GPL(venus_helper_set_bufsize); + +unsigned int venus_helper_get_opb_size(struct venus_inst *inst) +{ + /* the encoder has only one output */ + if (inst->session_type == VIDC_SESSION_TYPE_ENC) + return inst->output_buf_size; + + if (inst->opb_buftype == HFI_BUFFER_OUTPUT) + return inst->output_buf_size; + else if (inst->opb_buftype == HFI_BUFFER_OUTPUT2) + return inst->output2_buf_size; + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_get_opb_size); + +static void delayed_process_buf_func(struct work_struct *work) +{ + struct venus_buffer *buf, *n; + struct venus_inst *inst; + int ret; + + inst = container_of(work, struct venus_inst, delayed_process_work); + + mutex_lock(&inst->lock); + + if (!(inst->streamon_out & inst->streamon_cap)) + goto unlock; + + list_for_each_entry_safe(buf, n, &inst->delayed_process, ref_list) { + if (buf->flags & HFI_BUFFERFLAG_READONLY) + continue; + + ret = session_process_buf(inst, &buf->vb); + if (ret) + return_buf_error(inst, &buf->vb); + + list_del_init(&buf->ref_list); + } +unlock: + mutex_unlock(&inst->lock); +} + +void venus_helper_release_buf_ref(struct venus_inst *inst, unsigned int idx) +{ + struct venus_buffer *buf; + + list_for_each_entry(buf, &inst->registeredbufs, reg_list) { + if (buf->vb.vb2_buf.index == idx) { + buf->flags &= ~HFI_BUFFERFLAG_READONLY; + schedule_work(&inst->delayed_process_work); + break; + } + } +} +EXPORT_SYMBOL_GPL(venus_helper_release_buf_ref); + +void venus_helper_acquire_buf_ref(struct vb2_v4l2_buffer *vbuf) +{ + struct venus_buffer *buf = to_venus_buffer(vbuf); + + buf->flags |= HFI_BUFFERFLAG_READONLY; +} +EXPORT_SYMBOL_GPL(venus_helper_acquire_buf_ref); + +static int is_buf_refed(struct venus_inst *inst, struct vb2_v4l2_buffer *vbuf) +{ + struct venus_buffer *buf = to_venus_buffer(vbuf); + + if (buf->flags & HFI_BUFFERFLAG_READONLY) { + list_add_tail(&buf->ref_list, &inst->delayed_process); + schedule_work(&inst->delayed_process_work); + return 1; + } + + return 0; +} + +struct vb2_v4l2_buffer * +venus_helper_find_buf(struct venus_inst *inst, unsigned int type, u32 idx) +{ + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return v4l2_m2m_src_buf_remove_by_idx(m2m_ctx, idx); + else + return v4l2_m2m_dst_buf_remove_by_idx(m2m_ctx, idx); +} +EXPORT_SYMBOL_GPL(venus_helper_find_buf); + +void venus_helper_change_dpb_owner(struct venus_inst *inst, + struct vb2_v4l2_buffer *vbuf, unsigned int type, + unsigned int buf_type, u32 tag) +{ + struct intbuf *dpb_buf; + + if (!V4L2_TYPE_IS_CAPTURE(type) || + buf_type != inst->dpb_buftype) + return; + + list_for_each_entry(dpb_buf, &inst->dpbbufs, list) + if (dpb_buf->dpb_out_tag == tag) { + dpb_buf->owned_by = DRIVER; + break; + } +} +EXPORT_SYMBOL_GPL(venus_helper_change_dpb_owner); + +int venus_helper_vb2_buf_init(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct venus_buffer *buf = to_venus_buffer(vbuf); + + buf->size = vb2_plane_size(vb, 0); + buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + list_add_tail(&buf->reg_list, &inst->registeredbufs); + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_buf_init); + +int venus_helper_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + unsigned int out_buf_size = venus_helper_get_opb_size(inst); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) { + dev_err(inst->core->dev, "%s field isn't supported\n", + __func__); + return -EINVAL; + } + } + + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + vb2_plane_size(vb, 0) < out_buf_size) + return -EINVAL; + if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + vb2_plane_size(vb, 0) < inst->input_buf_size) + return -EINVAL; + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_buf_prepare); + +static void cache_payload(struct venus_inst *inst, struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + unsigned int idx = vbuf->vb2_buf.index; + + if (vbuf->vb2_buf.type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->payloads[idx] = vb2_get_plane_payload(vb, 0); +} + +void venus_helper_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + int ret; + + v4l2_m2m_buf_queue(m2m_ctx, vbuf); + + /* Skip processing queued capture buffers after LAST flag */ + if (inst->session_type == VIDC_SESSION_TYPE_DEC && + V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type) && + inst->codec_state == VENUS_DEC_STATE_DRC) + return; + + cache_payload(inst, vb); + + if (inst->session_type == VIDC_SESSION_TYPE_ENC && + !(inst->streamon_out && inst->streamon_cap)) + return; + + if (vb2_start_streaming_called(vb->vb2_queue)) { + ret = is_buf_refed(inst, vbuf); + if (ret) + return; + + ret = session_process_buf(inst, vbuf); + if (ret) + return_buf_error(inst, vbuf); + } +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_buf_queue); + +void venus_helper_buffers_done(struct venus_inst *inst, unsigned int type, + enum vb2_buffer_state state) +{ + struct vb2_v4l2_buffer *buf; + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + while ((buf = v4l2_m2m_src_buf_remove(inst->m2m_ctx))) + v4l2_m2m_buf_done(buf, state); + } else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + while ((buf = v4l2_m2m_dst_buf_remove(inst->m2m_ctx))) + v4l2_m2m_buf_done(buf, state); + } +} +EXPORT_SYMBOL_GPL(venus_helper_buffers_done); + +void venus_helper_vb2_stop_streaming(struct vb2_queue *q) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + struct venus_core *core = inst->core; + int ret; + + mutex_lock(&inst->lock); + + if (inst->streamon_out & inst->streamon_cap) { + ret = hfi_session_stop(inst); + ret |= hfi_session_unload_res(inst); + ret |= venus_helper_unregister_bufs(inst); + ret |= venus_helper_intbufs_free(inst); + ret |= hfi_session_deinit(inst); + + if (inst->session_error || test_bit(0, &core->sys_error)) + ret = -EIO; + + if (ret) + hfi_session_abort(inst); + + venus_helper_free_dpb_bufs(inst); + + venus_pm_load_scale(inst); + INIT_LIST_HEAD(&inst->registeredbufs); + } + + venus_helper_buffers_done(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + VB2_BUF_STATE_ERROR); + venus_helper_buffers_done(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + VB2_BUF_STATE_ERROR); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 0; + else + inst->streamon_cap = 0; + + venus_pm_release_core(inst); + + inst->session_error = 0; + + mutex_unlock(&inst->lock); +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_stop_streaming); + +void venus_helper_vb2_queue_error(struct venus_inst *inst) +{ + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + struct vb2_queue *q; + + q = v4l2_m2m_get_src_vq(m2m_ctx); + vb2_queue_error(q); + q = v4l2_m2m_get_dst_vq(m2m_ctx); + vb2_queue_error(q); +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_queue_error); + +int venus_helper_process_initial_cap_bufs(struct venus_inst *inst) +{ + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + struct v4l2_m2m_buffer *buf, *n; + int ret; + + v4l2_m2m_for_each_dst_buf_safe(m2m_ctx, buf, n) { + ret = session_process_buf(inst, &buf->vb); + if (ret) { + return_buf_error(inst, &buf->vb); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_process_initial_cap_bufs); + +int venus_helper_process_initial_out_bufs(struct venus_inst *inst) +{ + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + struct v4l2_m2m_buffer *buf, *n; + int ret; + + v4l2_m2m_for_each_src_buf_safe(m2m_ctx, buf, n) { + ret = session_process_buf(inst, &buf->vb); + if (ret) { + return_buf_error(inst, &buf->vb); + return ret; + } + } + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_process_initial_out_bufs); + +int venus_helper_vb2_start_streaming(struct venus_inst *inst) +{ + int ret; + + ret = venus_helper_intbufs_alloc(inst); + if (ret) + return ret; + + ret = session_register_bufs(inst); + if (ret) + goto err_bufs_free; + + venus_pm_load_scale(inst); + + ret = hfi_session_load_res(inst); + if (ret) + goto err_unreg_bufs; + + ret = hfi_session_start(inst); + if (ret) + goto err_unload_res; + + return 0; + +err_unload_res: + hfi_session_unload_res(inst); +err_unreg_bufs: + venus_helper_unregister_bufs(inst); +err_bufs_free: + venus_helper_intbufs_free(inst); + return ret; +} +EXPORT_SYMBOL_GPL(venus_helper_vb2_start_streaming); + +void venus_helper_m2m_device_run(void *priv) +{ + struct venus_inst *inst = priv; + struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + struct v4l2_m2m_buffer *buf, *n; + int ret; + + mutex_lock(&inst->lock); + + v4l2_m2m_for_each_dst_buf_safe(m2m_ctx, buf, n) { + ret = session_process_buf(inst, &buf->vb); + if (ret) + return_buf_error(inst, &buf->vb); + } + + v4l2_m2m_for_each_src_buf_safe(m2m_ctx, buf, n) { + ret = session_process_buf(inst, &buf->vb); + if (ret) + return_buf_error(inst, &buf->vb); + } + + mutex_unlock(&inst->lock); +} +EXPORT_SYMBOL_GPL(venus_helper_m2m_device_run); + +void venus_helper_m2m_job_abort(void *priv) +{ + struct venus_inst *inst = priv; + + v4l2_m2m_job_finish(inst->m2m_dev, inst->m2m_ctx); +} +EXPORT_SYMBOL_GPL(venus_helper_m2m_job_abort); + +int venus_helper_session_init(struct venus_inst *inst) +{ + enum hfi_version version = inst->core->res->hfi_version; + u32 session_type = inst->session_type; + u32 codec; + int ret; + + codec = inst->session_type == VIDC_SESSION_TYPE_DEC ? + inst->fmt_out->pixfmt : inst->fmt_cap->pixfmt; + + ret = hfi_session_init(inst, codec); + if (ret) + return ret; + + inst->clk_data.vpp_freq = hfi_platform_get_codec_vpp_freq(version, codec, + session_type); + inst->clk_data.vsp_freq = hfi_platform_get_codec_vsp_freq(version, codec, + session_type); + inst->clk_data.low_power_freq = hfi_platform_get_codec_lp_freq(version, codec, + session_type); + + return 0; +} +EXPORT_SYMBOL_GPL(venus_helper_session_init); + +void venus_helper_init_instance(struct venus_inst *inst) +{ + if (inst->session_type == VIDC_SESSION_TYPE_DEC) { + INIT_LIST_HEAD(&inst->delayed_process); + INIT_WORK(&inst->delayed_process_work, + delayed_process_buf_func); + } +} +EXPORT_SYMBOL_GPL(venus_helper_init_instance); + +static bool find_fmt_from_caps(struct hfi_plat_caps *caps, u32 buftype, u32 fmt) +{ + unsigned int i; + + for (i = 0; i < caps->num_fmts; i++) { + if (caps->fmts[i].buftype == buftype && + caps->fmts[i].fmt == fmt) + return true; + } + + return false; +} + +int venus_helper_get_out_fmts(struct venus_inst *inst, u32 v4l2_fmt, + u32 *out_fmt, u32 *out2_fmt, bool ubwc) +{ + struct venus_core *core = inst->core; + struct hfi_plat_caps *caps; + u32 ubwc_fmt, fmt = to_hfi_raw_fmt(v4l2_fmt); + bool found, found_ubwc; + + *out_fmt = *out2_fmt = 0; + + if (!fmt) + return -EINVAL; + + caps = venus_caps_by_codec(core, inst->hfi_codec, inst->session_type); + if (!caps) + return -EINVAL; + + if (ubwc) { + ubwc_fmt = fmt | HFI_COLOR_FORMAT_UBWC_BASE; + found_ubwc = find_fmt_from_caps(caps, HFI_BUFFER_OUTPUT, + ubwc_fmt); + found = find_fmt_from_caps(caps, HFI_BUFFER_OUTPUT2, fmt); + + if (found_ubwc && found) { + *out_fmt = ubwc_fmt; + *out2_fmt = fmt; + return 0; + } + } + + found = find_fmt_from_caps(caps, HFI_BUFFER_OUTPUT, fmt); + if (found) { + *out_fmt = fmt; + *out2_fmt = 0; + return 0; + } + + found = find_fmt_from_caps(caps, HFI_BUFFER_OUTPUT2, fmt); + if (found) { + *out_fmt = 0; + *out2_fmt = fmt; + return 0; + } + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(venus_helper_get_out_fmts); + +bool venus_helper_check_format(struct venus_inst *inst, u32 v4l2_pixfmt) +{ + struct venus_core *core = inst->core; + u32 fmt = to_hfi_raw_fmt(v4l2_pixfmt); + struct hfi_plat_caps *caps; + bool found; + + if (!fmt) + return false; + + caps = venus_caps_by_codec(core, inst->hfi_codec, inst->session_type); + if (!caps) + return false; + + found = find_fmt_from_caps(caps, HFI_BUFFER_OUTPUT, fmt); + if (found) + goto done; + + found = find_fmt_from_caps(caps, HFI_BUFFER_OUTPUT2, fmt); +done: + return found; +} +EXPORT_SYMBOL_GPL(venus_helper_check_format); + +int venus_helper_set_stride(struct venus_inst *inst, + unsigned int width, unsigned int height) +{ + const u32 ptype = HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO; + + struct hfi_uncompressed_plane_actual_info plane_actual_info; + + plane_actual_info.buffer_type = HFI_BUFFER_INPUT; + plane_actual_info.num_planes = 2; + plane_actual_info.plane_format[0].actual_stride = width; + plane_actual_info.plane_format[0].actual_plane_buffer_height = height; + plane_actual_info.plane_format[1].actual_stride = width; + plane_actual_info.plane_format[1].actual_plane_buffer_height = height / 2; + + return hfi_session_set_property(inst, ptype, &plane_actual_info); +} +EXPORT_SYMBOL_GPL(venus_helper_set_stride); diff --git a/drivers/media/platform/qcom/venus/helpers.h b/drivers/media/platform/qcom/venus/helpers.h new file mode 100644 index 000000000..358e4f39c --- /dev/null +++ b/drivers/media/platform/qcom/venus/helpers.h @@ -0,0 +1,74 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_HELPERS_H__ +#define __VENUS_HELPERS_H__ + +#include + +struct venus_inst; +struct venus_core; + +bool venus_helper_check_codec(struct venus_inst *inst, u32 v4l2_pixfmt); +struct vb2_v4l2_buffer *venus_helper_find_buf(struct venus_inst *inst, + unsigned int type, u32 idx); +void venus_helper_change_dpb_owner(struct venus_inst *inst, + struct vb2_v4l2_buffer *vbuf, unsigned int type, + unsigned int buf_type, u32 idx); +void venus_helper_buffers_done(struct venus_inst *inst, unsigned int type, + enum vb2_buffer_state state); +int venus_helper_vb2_buf_init(struct vb2_buffer *vb); +int venus_helper_vb2_buf_prepare(struct vb2_buffer *vb); +void venus_helper_vb2_buf_queue(struct vb2_buffer *vb); +void venus_helper_vb2_stop_streaming(struct vb2_queue *q); +int venus_helper_vb2_start_streaming(struct venus_inst *inst); +void venus_helper_vb2_queue_error(struct venus_inst *inst); +void venus_helper_m2m_device_run(void *priv); +void venus_helper_m2m_job_abort(void *priv); +int venus_helper_get_bufreq(struct venus_inst *inst, u32 type, + struct hfi_buffer_requirements *req); +u32 venus_helper_get_framesz_raw(u32 hfi_fmt, u32 width, u32 height); +u32 venus_helper_get_framesz(u32 v4l2_fmt, u32 width, u32 height); +int venus_helper_set_input_resolution(struct venus_inst *inst, + unsigned int width, unsigned int height); +int venus_helper_set_output_resolution(struct venus_inst *inst, + unsigned int width, unsigned int height, + u32 buftype); +int venus_helper_set_work_mode(struct venus_inst *inst); +int venus_helper_set_format_constraints(struct venus_inst *inst); +int venus_helper_set_num_bufs(struct venus_inst *inst, unsigned int input_bufs, + unsigned int output_bufs, + unsigned int output2_bufs); +int venus_helper_set_raw_format(struct venus_inst *inst, u32 hfi_format, + u32 buftype); +int venus_helper_set_color_format(struct venus_inst *inst, u32 fmt); +int venus_helper_set_dyn_bufmode(struct venus_inst *inst); +int venus_helper_set_bufsize(struct venus_inst *inst, u32 bufsize, u32 buftype); +int venus_helper_set_multistream(struct venus_inst *inst, bool out_en, + bool out2_en); +unsigned int venus_helper_get_opb_size(struct venus_inst *inst); +void venus_helper_acquire_buf_ref(struct vb2_v4l2_buffer *vbuf); +void venus_helper_release_buf_ref(struct venus_inst *inst, unsigned int idx); +void venus_helper_init_instance(struct venus_inst *inst); +int venus_helper_session_init(struct venus_inst *inst); +int venus_helper_get_out_fmts(struct venus_inst *inst, u32 fmt, u32 *out_fmt, + u32 *out2_fmt, bool ubwc); +bool venus_helper_check_format(struct venus_inst *inst, u32 v4l2_pixfmt); +int venus_helper_alloc_dpb_bufs(struct venus_inst *inst); +int venus_helper_free_dpb_bufs(struct venus_inst *inst); +int venus_helper_intbufs_alloc(struct venus_inst *inst); +int venus_helper_intbufs_free(struct venus_inst *inst); +int venus_helper_intbufs_realloc(struct venus_inst *inst); +int venus_helper_queue_dpb_bufs(struct venus_inst *inst); +int venus_helper_unregister_bufs(struct venus_inst *inst); +int venus_helper_process_initial_cap_bufs(struct venus_inst *inst); +int venus_helper_process_initial_out_bufs(struct venus_inst *inst); +void venus_helper_get_ts_metadata(struct venus_inst *inst, u64 timestamp_us, + struct vb2_v4l2_buffer *vbuf); +int venus_helper_get_profile_level(struct venus_inst *inst, u32 *profile, u32 *level); +int venus_helper_set_profile_level(struct venus_inst *inst, u32 profile, u32 level); +int venus_helper_set_stride(struct venus_inst *inst, unsigned int aligned_width, + unsigned int aligned_height); +#endif diff --git a/drivers/media/platform/qcom/venus/hfi.c b/drivers/media/platform/qcom/venus/hfi.c new file mode 100644 index 000000000..e00aedb41 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi.c @@ -0,0 +1,592 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "hfi.h" +#include "hfi_cmds.h" +#include "hfi_venus.h" + +#define TIMEOUT msecs_to_jiffies(1000) + +static u32 to_codec_type(u32 pixfmt) +{ + switch (pixfmt) { + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_H264_NO_SC: + return HFI_VIDEO_CODEC_H264; + case V4L2_PIX_FMT_H263: + return HFI_VIDEO_CODEC_H263; + case V4L2_PIX_FMT_MPEG1: + return HFI_VIDEO_CODEC_MPEG1; + case V4L2_PIX_FMT_MPEG2: + return HFI_VIDEO_CODEC_MPEG2; + case V4L2_PIX_FMT_MPEG4: + return HFI_VIDEO_CODEC_MPEG4; + case V4L2_PIX_FMT_VC1_ANNEX_G: + case V4L2_PIX_FMT_VC1_ANNEX_L: + return HFI_VIDEO_CODEC_VC1; + case V4L2_PIX_FMT_VP8: + return HFI_VIDEO_CODEC_VP8; + case V4L2_PIX_FMT_VP9: + return HFI_VIDEO_CODEC_VP9; + case V4L2_PIX_FMT_XVID: + return HFI_VIDEO_CODEC_DIVX; + case V4L2_PIX_FMT_HEVC: + return HFI_VIDEO_CODEC_HEVC; + default: + return 0; + } +} + +int hfi_core_init(struct venus_core *core) +{ + int ret = 0; + + mutex_lock(&core->lock); + + if (core->state >= CORE_INIT) + goto unlock; + + reinit_completion(&core->done); + + ret = core->ops->core_init(core); + if (ret) + goto unlock; + + ret = wait_for_completion_timeout(&core->done, TIMEOUT); + if (!ret) { + ret = -ETIMEDOUT; + goto unlock; + } + + ret = 0; + + if (core->error != HFI_ERR_NONE) { + ret = -EIO; + goto unlock; + } + + core->state = CORE_INIT; +unlock: + mutex_unlock(&core->lock); + return ret; +} + +int hfi_core_deinit(struct venus_core *core, bool blocking) +{ + int ret = 0, empty; + + mutex_lock(&core->lock); + + if (core->state == CORE_UNINIT) + goto unlock; + + empty = list_empty(&core->instances); + + if (!empty && !blocking) { + ret = -EBUSY; + goto unlock; + } + + if (!empty) { + mutex_unlock(&core->lock); + wait_var_event(&core->insts_count, + !atomic_read(&core->insts_count)); + mutex_lock(&core->lock); + } + + if (!core->ops) + goto unlock; + + ret = core->ops->core_deinit(core); + + if (!ret) + core->state = CORE_UNINIT; + +unlock: + mutex_unlock(&core->lock); + return ret; +} + +int hfi_core_suspend(struct venus_core *core) +{ + if (core->state != CORE_INIT) + return 0; + + return core->ops->suspend(core); +} + +int hfi_core_resume(struct venus_core *core, bool force) +{ + if (!force && core->state != CORE_INIT) + return 0; + + return core->ops->resume(core); +} + +int hfi_core_trigger_ssr(struct venus_core *core, u32 type) +{ + return core->ops->core_trigger_ssr(core, type); +} + +int hfi_core_ping(struct venus_core *core) +{ + int ret; + + mutex_lock(&core->lock); + + ret = core->ops->core_ping(core, 0xbeef); + if (ret) + goto unlock; + + ret = wait_for_completion_timeout(&core->done, TIMEOUT); + if (!ret) { + ret = -ETIMEDOUT; + goto unlock; + } + ret = 0; + if (core->error != HFI_ERR_NONE) + ret = -ENODEV; +unlock: + mutex_unlock(&core->lock); + return ret; +} + +static int wait_session_msg(struct venus_inst *inst) +{ + int ret; + + ret = wait_for_completion_timeout(&inst->done, TIMEOUT); + if (!ret) + return -ETIMEDOUT; + + if (inst->error != HFI_ERR_NONE) + return -EIO; + + return 0; +} + +int hfi_session_create(struct venus_inst *inst, const struct hfi_inst_ops *ops) +{ + struct venus_core *core = inst->core; + bool max; + int ret; + + if (!ops) + return -EINVAL; + + inst->state = INST_UNINIT; + init_completion(&inst->done); + inst->ops = ops; + + mutex_lock(&core->lock); + + if (test_bit(0, &inst->core->sys_error)) { + ret = -EIO; + goto unlock; + } + + max = atomic_add_unless(&core->insts_count, 1, + core->max_sessions_supported); + if (!max) { + ret = -EAGAIN; + } else { + list_add_tail(&inst->list, &core->instances); + ret = 0; + } + +unlock: + mutex_unlock(&core->lock); + + return ret; +} +EXPORT_SYMBOL_GPL(hfi_session_create); + +int hfi_session_init(struct venus_inst *inst, u32 pixfmt) +{ + struct venus_core *core = inst->core; + const struct hfi_ops *ops = core->ops; + int ret; + + /* + * If core shutdown is in progress or if we are in system + * recovery, return an error as during system error recovery + * session_init() can't pass successfully + */ + mutex_lock(&core->lock); + if (!core->ops || test_bit(0, &inst->core->sys_error)) { + mutex_unlock(&core->lock); + return -EIO; + } + mutex_unlock(&core->lock); + + if (inst->state != INST_UNINIT) + return -EALREADY; + + inst->hfi_codec = to_codec_type(pixfmt); + reinit_completion(&inst->done); + + ret = ops->session_init(inst, inst->session_type, inst->hfi_codec); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_INIT; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_init); + +void hfi_session_destroy(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + + mutex_lock(&core->lock); + list_del_init(&inst->list); + if (atomic_dec_and_test(&core->insts_count)) + wake_up_var(&core->insts_count); + mutex_unlock(&core->lock); +} +EXPORT_SYMBOL_GPL(hfi_session_destroy); + +int hfi_session_deinit(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (inst->state == INST_UNINIT) + return 0; + + if (inst->state < INST_INIT) + return -EINVAL; + + if (test_bit(0, &inst->core->sys_error)) + goto done; + + reinit_completion(&inst->done); + + ret = ops->session_end(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + +done: + inst->state = INST_UNINIT; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_deinit); + +int hfi_session_start(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + if (inst->state != INST_LOAD_RESOURCES) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_start(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_START; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_start); + +int hfi_session_stop(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + if (inst->state != INST_START) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_stop(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_STOP; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_stop); + +int hfi_session_continue(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + if (core->res->hfi_version == HFI_VERSION_1XX) + return 0; + + return core->ops->session_continue(inst); +} +EXPORT_SYMBOL_GPL(hfi_session_continue); + +int hfi_session_abort(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + reinit_completion(&inst->done); + + ret = ops->session_abort(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_abort); + +int hfi_session_load_res(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + if (inst->state != INST_INIT) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_load_res(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_LOAD_RESOURCES; + + return 0; +} + +int hfi_session_unload_res(struct venus_inst *inst) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + if (inst->state != INST_STOP) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_release_res(inst); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + inst->state = INST_RELEASE_RESOURCES; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_unload_res); + +int hfi_session_flush(struct venus_inst *inst, u32 type, bool block) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + reinit_completion(&inst->done); + + ret = ops->session_flush(inst, type); + if (ret) + return ret; + + if (block) { + ret = wait_session_msg(inst); + if (ret) + return ret; + } + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_flush); + +int hfi_session_set_buffers(struct venus_inst *inst, struct hfi_buffer_desc *bd) +{ + const struct hfi_ops *ops = inst->core->ops; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + return ops->session_set_buffers(inst, bd); +} + +int hfi_session_unset_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + reinit_completion(&inst->done); + + ret = ops->session_unset_buffers(inst, bd); + if (ret) + return ret; + + if (!bd->response_required) + return 0; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + return 0; +} + +int hfi_session_get_property(struct venus_inst *inst, u32 ptype, + union hfi_get_property *hprop) +{ + const struct hfi_ops *ops = inst->core->ops; + int ret; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + if (inst->state < INST_INIT || inst->state >= INST_STOP) + return -EINVAL; + + reinit_completion(&inst->done); + + ret = ops->session_get_property(inst, ptype); + if (ret) + return ret; + + ret = wait_session_msg(inst); + if (ret) + return ret; + + *hprop = inst->hprop; + + return 0; +} +EXPORT_SYMBOL_GPL(hfi_session_get_property); + +int hfi_session_set_property(struct venus_inst *inst, u32 ptype, void *pdata) +{ + const struct hfi_ops *ops = inst->core->ops; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + if (inst->state < INST_INIT || inst->state >= INST_STOP) + return -EINVAL; + + return ops->session_set_property(inst, ptype, pdata); +} +EXPORT_SYMBOL_GPL(hfi_session_set_property); + +int hfi_session_process_buf(struct venus_inst *inst, struct hfi_frame_data *fd) +{ + const struct hfi_ops *ops = inst->core->ops; + + if (test_bit(0, &inst->core->sys_error)) + return -EIO; + + if (fd->buffer_type == HFI_BUFFER_INPUT) + return ops->session_etb(inst, fd); + else if (fd->buffer_type == HFI_BUFFER_OUTPUT || + fd->buffer_type == HFI_BUFFER_OUTPUT2) + return ops->session_ftb(inst, fd); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(hfi_session_process_buf); + +irqreturn_t hfi_isr_thread(int irq, void *dev_id) +{ + struct venus_core *core = dev_id; + + return core->ops->isr_thread(core); +} + +irqreturn_t hfi_isr(int irq, void *dev) +{ + struct venus_core *core = dev; + + return core->ops->isr(core); +} + +int hfi_create(struct venus_core *core, const struct hfi_core_ops *ops) +{ + if (!ops) + return -EINVAL; + + atomic_set(&core->insts_count, 0); + core->core_ops = ops; + core->state = CORE_UNINIT; + init_completion(&core->done); + pkt_set_version(core->res->hfi_version); + + return venus_hfi_create(core); +} + +void hfi_destroy(struct venus_core *core) +{ + venus_hfi_destroy(core); +} + +void hfi_reinit(struct venus_core *core) +{ + venus_hfi_queues_reinit(core); +} diff --git a/drivers/media/platform/qcom/venus/hfi.h b/drivers/media/platform/qcom/venus/hfi.h new file mode 100644 index 000000000..f25d412d6 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __HFI_H__ +#define __HFI_H__ + +#include + +#include "hfi_helper.h" + +#define VIDC_SESSION_TYPE_VPE 0 +#define VIDC_SESSION_TYPE_ENC 1 +#define VIDC_SESSION_TYPE_DEC 2 + +#define VIDC_RESOURCE_NONE 0 +#define VIDC_RESOURCE_OCMEM 1 +#define VIDC_RESOURCE_VMEM 2 + +struct hfi_buffer_desc { + u32 buffer_type; + u32 buffer_size; + u32 num_buffers; + u32 device_addr; + u32 extradata_addr; + u32 extradata_size; + u32 response_required; +}; + +struct hfi_frame_data { + u32 buffer_type; + u32 device_addr; + u32 extradata_addr; + u64 timestamp; + u32 flags; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 mark_target; + u32 mark_data; + u32 clnt_data; + u32 extradata_size; +}; + +union hfi_get_property { + struct hfi_profile_level profile_level; + struct hfi_buffer_requirements bufreq[HFI_BUFFER_TYPE_MAX]; +}; + +/* HFI events */ +#define EVT_SYS_EVENT_CHANGE 1 +#define EVT_SYS_WATCHDOG_TIMEOUT 2 +#define EVT_SYS_ERROR 3 +#define EVT_SESSION_ERROR 4 + +/* HFI event callback structure */ +struct hfi_event_data { + u32 error; + u32 height; + u32 width; + u32 event_type; + u32 packet_buffer; + u32 extradata_buffer; + u32 tag; + u32 profile; + u32 level; + /* the following properties start appear from v4 onwards */ + u32 bit_depth; + u32 pic_struct; + u32 colour_space; + u32 entropy_mode; + u32 buf_count; + struct { + u32 left, top; + u32 width, height; + } input_crop; +}; + +/* define core states */ +#define CORE_UNINIT 0 +#define CORE_INIT 1 + +/* define instance states */ +#define INST_UNINIT 2 +#define INST_INIT 3 +#define INST_LOAD_RESOURCES 4 +#define INST_START 5 +#define INST_STOP 6 +#define INST_RELEASE_RESOURCES 7 + +struct venus_core; +struct venus_inst; + +struct hfi_core_ops { + void (*event_notify)(struct venus_core *core, u32 event); +}; + +struct hfi_inst_ops { + void (*buf_done)(struct venus_inst *inst, unsigned int buf_type, + u32 tag, u32 bytesused, u32 data_offset, u32 flags, + u32 hfi_flags, u64 timestamp_us); + void (*event_notify)(struct venus_inst *inst, u32 event, + struct hfi_event_data *data); + void (*flush_done)(struct venus_inst *inst); +}; + +struct hfi_ops { + int (*core_init)(struct venus_core *core); + int (*core_deinit)(struct venus_core *core); + int (*core_ping)(struct venus_core *core, u32 cookie); + int (*core_trigger_ssr)(struct venus_core *core, u32 trigger_type); + + int (*session_init)(struct venus_inst *inst, u32 session_type, + u32 codec); + int (*session_end)(struct venus_inst *inst); + int (*session_abort)(struct venus_inst *inst); + int (*session_flush)(struct venus_inst *inst, u32 flush_mode); + int (*session_start)(struct venus_inst *inst); + int (*session_stop)(struct venus_inst *inst); + int (*session_continue)(struct venus_inst *inst); + int (*session_etb)(struct venus_inst *inst, struct hfi_frame_data *fd); + int (*session_ftb)(struct venus_inst *inst, struct hfi_frame_data *fd); + int (*session_set_buffers)(struct venus_inst *inst, + struct hfi_buffer_desc *bd); + int (*session_unset_buffers)(struct venus_inst *inst, + struct hfi_buffer_desc *bd); + int (*session_load_res)(struct venus_inst *inst); + int (*session_release_res)(struct venus_inst *inst); + int (*session_parse_seq_hdr)(struct venus_inst *inst, u32 seq_hdr, + u32 seq_hdr_len); + int (*session_get_seq_hdr)(struct venus_inst *inst, u32 seq_hdr, + u32 seq_hdr_len); + int (*session_set_property)(struct venus_inst *inst, u32 ptype, + void *pdata); + int (*session_get_property)(struct venus_inst *inst, u32 ptype); + + int (*resume)(struct venus_core *core); + int (*suspend)(struct venus_core *core); + + /* interrupt operations */ + irqreturn_t (*isr)(struct venus_core *core); + irqreturn_t (*isr_thread)(struct venus_core *core); +}; + +int hfi_create(struct venus_core *core, const struct hfi_core_ops *ops); +void hfi_destroy(struct venus_core *core); +void hfi_reinit(struct venus_core *core); + +int hfi_core_init(struct venus_core *core); +int hfi_core_deinit(struct venus_core *core, bool blocking); +int hfi_core_suspend(struct venus_core *core); +int hfi_core_resume(struct venus_core *core, bool force); +int hfi_core_trigger_ssr(struct venus_core *core, u32 type); +int hfi_core_ping(struct venus_core *core); +int hfi_session_create(struct venus_inst *inst, const struct hfi_inst_ops *ops); +void hfi_session_destroy(struct venus_inst *inst); +int hfi_session_init(struct venus_inst *inst, u32 pixfmt); +int hfi_session_deinit(struct venus_inst *inst); +int hfi_session_start(struct venus_inst *inst); +int hfi_session_stop(struct venus_inst *inst); +int hfi_session_continue(struct venus_inst *inst); +int hfi_session_abort(struct venus_inst *inst); +int hfi_session_load_res(struct venus_inst *inst); +int hfi_session_unload_res(struct venus_inst *inst); +int hfi_session_flush(struct venus_inst *inst, u32 type, bool block); +int hfi_session_set_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd); +int hfi_session_unset_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd); +int hfi_session_get_property(struct venus_inst *inst, u32 ptype, + union hfi_get_property *hprop); +int hfi_session_set_property(struct venus_inst *inst, u32 ptype, void *pdata); +int hfi_session_process_buf(struct venus_inst *inst, struct hfi_frame_data *f); +irqreturn_t hfi_isr_thread(int irq, void *dev_id); +irqreturn_t hfi_isr(int irq, void *dev); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_cmds.c b/drivers/media/platform/qcom/venus/hfi_cmds.c new file mode 100644 index 000000000..930b743f2 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_cmds.c @@ -0,0 +1,1354 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include +#include + +#include "hfi_cmds.h" + +static enum hfi_version hfi_ver; + +void pkt_sys_init(struct hfi_sys_init_pkt *pkt, u32 arch_type) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_INIT; + pkt->arch_type = arch_type; +} + +void pkt_sys_pc_prep(struct hfi_sys_pc_prep_pkt *pkt) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_PC_PREP; +} + +void pkt_sys_idle_indicator(struct hfi_sys_set_property_pkt *pkt, u32 enable) +{ + struct hfi_enable *hfi = (struct hfi_enable *)&pkt->data[1]; + + pkt->hdr.size = struct_size(pkt, data, 1) + sizeof(*hfi); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_IDLE_INDICATOR; + hfi->enable = enable; +} + +void pkt_sys_debug_config(struct hfi_sys_set_property_pkt *pkt, u32 mode, + u32 config) +{ + struct hfi_debug_config *hfi; + + pkt->hdr.size = struct_size(pkt, data, 1) + sizeof(*hfi); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_DEBUG_CONFIG; + hfi = (struct hfi_debug_config *)&pkt->data[1]; + hfi->config = config; + hfi->mode = mode; +} + +void pkt_sys_coverage_config(struct hfi_sys_set_property_pkt *pkt, u32 mode) +{ + pkt->hdr.size = struct_size(pkt, data, 2); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_CONFIG_COVERAGE; + pkt->data[1] = mode; +} + +void pkt_sys_ubwc_config(struct hfi_sys_set_property_pkt *pkt, const struct hfi_ubwc_config *hfi) +{ + pkt->hdr.size = struct_size(pkt, data, 1) + sizeof(*hfi); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_UBWC_CONFIG; + memcpy(&pkt->data[1], hfi, sizeof(*hfi)); +} + +int pkt_sys_set_resource(struct hfi_sys_set_resource_pkt *pkt, u32 id, u32 size, + u32 addr, void *cookie) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_RESOURCE; + pkt->resource_handle = hash32_ptr(cookie); + + switch (id) { + case VIDC_RESOURCE_OCMEM: + case VIDC_RESOURCE_VMEM: { + struct hfi_resource_ocmem *res = + (struct hfi_resource_ocmem *)&pkt->resource_data[0]; + + res->size = size; + res->mem = addr; + pkt->resource_type = HFI_RESOURCE_OCMEM; + pkt->hdr.size += sizeof(*res) - sizeof(u32); + break; + } + case VIDC_RESOURCE_NONE: + default: + return -ENOTSUPP; + } + + return 0; +} + +int pkt_sys_unset_resource(struct hfi_sys_release_resource_pkt *pkt, u32 id, + u32 size, void *cookie) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_RELEASE_RESOURCE; + pkt->resource_handle = hash32_ptr(cookie); + + switch (id) { + case VIDC_RESOURCE_OCMEM: + case VIDC_RESOURCE_VMEM: + pkt->resource_type = HFI_RESOURCE_OCMEM; + break; + case VIDC_RESOURCE_NONE: + break; + default: + return -ENOTSUPP; + } + + return 0; +} + +void pkt_sys_ping(struct hfi_sys_ping_pkt *pkt, u32 cookie) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_PING; + pkt->client_data = cookie; +} + +void pkt_sys_power_control(struct hfi_sys_set_property_pkt *pkt, u32 enable) +{ + struct hfi_enable *hfi = (struct hfi_enable *)&pkt->data[1]; + + pkt->hdr.size = struct_size(pkt, data, 1) + sizeof(*hfi); + pkt->hdr.pkt_type = HFI_CMD_SYS_SET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_CODEC_POWER_PLANE_CTRL; + hfi->enable = enable; +} + +int pkt_sys_ssr_cmd(struct hfi_sys_test_ssr_pkt *pkt, u32 trigger_type) +{ + switch (trigger_type) { + case HFI_TEST_SSR_SW_ERR_FATAL: + case HFI_TEST_SSR_SW_DIV_BY_ZERO: + case HFI_TEST_SSR_HW_WDOG_IRQ: + break; + default: + return -EINVAL; + } + + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_TEST_SSR; + pkt->trigger_type = trigger_type; + + return 0; +} + +void pkt_sys_image_version(struct hfi_sys_get_property_pkt *pkt) +{ + pkt->hdr.size = sizeof(*pkt); + pkt->hdr.pkt_type = HFI_CMD_SYS_GET_PROPERTY; + pkt->num_properties = 1; + pkt->data[0] = HFI_PROPERTY_SYS_IMAGE_VERSION; +} + +int pkt_session_init(struct hfi_session_init_pkt *pkt, void *cookie, + u32 session_type, u32 codec) +{ + if (!pkt || !cookie || !codec) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SYS_SESSION_INIT; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->session_domain = session_type; + pkt->session_codec = codec; + + return 0; +} + +void pkt_session_cmd(struct hfi_session_pkt *pkt, u32 pkt_type, void *cookie) +{ + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = pkt_type; + pkt->shdr.session_id = hash32_ptr(cookie); +} + +int pkt_session_set_buffers(struct hfi_session_set_buffers_pkt *pkt, + void *cookie, struct hfi_buffer_desc *bd) +{ + unsigned int i; + + if (!cookie || !pkt || !bd) + return -EINVAL; + + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_SET_BUFFERS; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->buffer_size = bd->buffer_size; + pkt->min_buffer_size = bd->buffer_size; + pkt->num_buffers = bd->num_buffers; + + if (bd->buffer_type == HFI_BUFFER_OUTPUT || + bd->buffer_type == HFI_BUFFER_OUTPUT2) { + struct hfi_buffer_info *bi; + + pkt->extradata_size = bd->extradata_size; + pkt->shdr.hdr.size = sizeof(*pkt) - sizeof(u32) + + (bd->num_buffers * sizeof(*bi)); + bi = (struct hfi_buffer_info *)pkt->buffer_info; + for (i = 0; i < pkt->num_buffers; i++) { + bi->buffer_addr = bd->device_addr; + bi->extradata_addr = bd->extradata_addr; + } + } else { + pkt->extradata_size = 0; + pkt->shdr.hdr.size = sizeof(*pkt) + + ((bd->num_buffers - 1) * sizeof(u32)); + for (i = 0; i < pkt->num_buffers; i++) + pkt->buffer_info[i] = bd->device_addr; + } + + pkt->buffer_type = bd->buffer_type; + + return 0; +} + +int pkt_session_unset_buffers(struct hfi_session_release_buffer_pkt *pkt, + void *cookie, struct hfi_buffer_desc *bd) +{ + unsigned int i; + + if (!cookie || !pkt || !bd) + return -EINVAL; + + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_RELEASE_BUFFERS; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->buffer_size = bd->buffer_size; + pkt->num_buffers = bd->num_buffers; + + if (bd->buffer_type == HFI_BUFFER_OUTPUT || + bd->buffer_type == HFI_BUFFER_OUTPUT2) { + struct hfi_buffer_info *bi; + + bi = (struct hfi_buffer_info *)pkt->buffer_info; + for (i = 0; i < pkt->num_buffers; i++) { + bi->buffer_addr = bd->device_addr; + bi->extradata_addr = bd->extradata_addr; + } + pkt->shdr.hdr.size = + sizeof(struct hfi_session_set_buffers_pkt) - + sizeof(u32) + (bd->num_buffers * sizeof(*bi)); + } else { + for (i = 0; i < pkt->num_buffers; i++) + pkt->buffer_info[i] = bd->device_addr; + + pkt->extradata_size = 0; + pkt->shdr.hdr.size = + sizeof(struct hfi_session_set_buffers_pkt) + + ((bd->num_buffers - 1) * sizeof(u32)); + } + + pkt->response_req = bd->response_required; + pkt->buffer_type = bd->buffer_type; + + return 0; +} + +int pkt_session_etb_decoder(struct hfi_session_empty_buffer_compressed_pkt *pkt, + void *cookie, struct hfi_frame_data *in_frame) +{ + if (!cookie) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_EMPTY_BUFFER; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->time_stamp_hi = upper_32_bits(in_frame->timestamp); + pkt->time_stamp_lo = lower_32_bits(in_frame->timestamp); + pkt->flags = in_frame->flags; + pkt->mark_target = in_frame->mark_target; + pkt->mark_data = in_frame->mark_data; + pkt->offset = in_frame->offset; + pkt->alloc_len = in_frame->alloc_len; + pkt->filled_len = in_frame->filled_len; + pkt->input_tag = in_frame->clnt_data; + pkt->packet_buffer = in_frame->device_addr; + + return 0; +} + +int pkt_session_etb_encoder( + struct hfi_session_empty_buffer_uncompressed_plane0_pkt *pkt, + void *cookie, struct hfi_frame_data *in_frame) +{ + if (!cookie || !in_frame->device_addr) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_EMPTY_BUFFER; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->view_id = 0; + pkt->time_stamp_hi = upper_32_bits(in_frame->timestamp); + pkt->time_stamp_lo = lower_32_bits(in_frame->timestamp); + pkt->flags = in_frame->flags; + pkt->mark_target = in_frame->mark_target; + pkt->mark_data = in_frame->mark_data; + pkt->offset = in_frame->offset; + pkt->alloc_len = in_frame->alloc_len; + pkt->filled_len = in_frame->filled_len; + pkt->input_tag = in_frame->clnt_data; + pkt->packet_buffer = in_frame->device_addr; + pkt->extradata_buffer = in_frame->extradata_addr; + + return 0; +} + +int pkt_session_ftb(struct hfi_session_fill_buffer_pkt *pkt, void *cookie, + struct hfi_frame_data *out_frame) +{ + if (!cookie || !out_frame || !out_frame->device_addr) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_FILL_BUFFER; + pkt->shdr.session_id = hash32_ptr(cookie); + + if (out_frame->buffer_type == HFI_BUFFER_OUTPUT) + pkt->stream_id = 0; + else if (out_frame->buffer_type == HFI_BUFFER_OUTPUT2) + pkt->stream_id = 1; + + pkt->output_tag = out_frame->clnt_data; + pkt->packet_buffer = out_frame->device_addr; + pkt->extradata_buffer = out_frame->extradata_addr; + pkt->alloc_len = out_frame->alloc_len; + pkt->filled_len = out_frame->filled_len; + pkt->offset = out_frame->offset; + pkt->data[0] = out_frame->extradata_size; + + return 0; +} + +int pkt_session_parse_seq_header( + struct hfi_session_parse_sequence_header_pkt *pkt, + void *cookie, u32 seq_hdr, u32 seq_hdr_len) +{ + if (!cookie || !seq_hdr || !seq_hdr_len) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_PARSE_SEQUENCE_HEADER; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->header_len = seq_hdr_len; + pkt->packet_buffer = seq_hdr; + + return 0; +} + +int pkt_session_get_seq_hdr(struct hfi_session_get_sequence_header_pkt *pkt, + void *cookie, u32 seq_hdr, u32 seq_hdr_len) +{ + if (!cookie || !seq_hdr || !seq_hdr_len) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_GET_SEQUENCE_HEADER; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->buffer_len = seq_hdr_len; + pkt->packet_buffer = seq_hdr; + + return 0; +} + +int pkt_session_flush(struct hfi_session_flush_pkt *pkt, void *cookie, u32 type) +{ + switch (type) { + case HFI_FLUSH_INPUT: + case HFI_FLUSH_OUTPUT: + case HFI_FLUSH_OUTPUT2: + case HFI_FLUSH_ALL: + break; + default: + return -EINVAL; + } + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_FLUSH; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->flush_type = type; + + return 0; +} + +static int pkt_session_get_property_1x(struct hfi_session_get_property_pkt *pkt, + void *cookie, u32 ptype) +{ + switch (ptype) { + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: + case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: + break; + default: + return -EINVAL; + } + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_GET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + pkt->data[0] = ptype; + + return 0; +} + +static int pkt_session_set_property_1x(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata) +{ + void *prop_data; + int ret = 0; + + if (!pkt || !cookie || !pdata) + return -EINVAL; + + prop_data = &pkt->data[1]; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_SET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + pkt->data[0] = ptype; + + switch (ptype) { + case HFI_PROPERTY_CONFIG_FRAME_RATE: { + struct hfi_framerate *in = pdata, *frate = prop_data; + + frate->buffer_type = in->buffer_type; + frate->framerate = in->framerate; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*frate); + break; + } + case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT: { + struct hfi_uncompressed_format_select *in = pdata; + struct hfi_uncompressed_format_select *hfi = prop_data; + + hfi->buffer_type = in->buffer_type; + hfi->format = in->format; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hfi); + break; + } + case HFI_PROPERTY_PARAM_FRAME_SIZE: { + struct hfi_framesize *in = pdata, *fsize = prop_data; + + fsize->buffer_type = in->buffer_type; + fsize->height = in->height; + fsize->width = in->width; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*fsize); + break; + } + case HFI_PROPERTY_CONFIG_REALTIME: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL: { + struct hfi_buffer_count_actual *in = pdata, *count = prop_data; + + count->count_actual = in->count_actual; + count->type = in->type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*count); + break; + } + case HFI_PROPERTY_PARAM_BUFFER_SIZE_ACTUAL: { + struct hfi_buffer_size_actual *in = pdata, *sz = prop_data; + + sz->size = in->size; + sz->type = in->type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*sz); + break; + } + case HFI_PROPERTY_PARAM_BUFFER_DISPLAY_HOLD_COUNT_ACTUAL: { + struct hfi_buffer_display_hold_count_actual *in = pdata; + struct hfi_buffer_display_hold_count_actual *count = prop_data; + + count->hold_count = in->hold_count; + count->type = in->type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*count); + break; + } + case HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SELECT: { + struct hfi_nal_stream_format_select *in = pdata; + struct hfi_nal_stream_format_select *fmt = prop_data; + + fmt->format = in->format; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*fmt); + break; + } + case HFI_PROPERTY_PARAM_VDEC_OUTPUT_ORDER: { + u32 *in = pdata; + + switch (*in) { + case HFI_OUTPUT_ORDER_DECODE: + case HFI_OUTPUT_ORDER_DISPLAY: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VDEC_PICTURE_TYPE_DECODE: { + struct hfi_enable_picture *in = pdata, *en = prop_data; + + en->picture_type = in->picture_type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER: { + struct hfi_enable *in = pdata; + struct hfi_enable *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM: { + struct hfi_multi_stream *in = pdata, *multi = prop_data; + + multi->buffer_type = in->buffer_type; + multi->enable = in->enable; + multi->width = in->width; + multi->height = in->height; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*multi); + break; + } + case HFI_PROPERTY_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT: { + struct hfi_display_picture_buffer_count *in = pdata; + struct hfi_display_picture_buffer_count *count = prop_data; + + count->count = in->count; + count->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*count); + break; + } + case HFI_PROPERTY_PARAM_DIVX_FORMAT: { + u32 *in = pdata; + + switch (*in) { + case HFI_DIVX_FORMAT_4: + case HFI_DIVX_FORMAT_5: + case HFI_DIVX_FORMAT_6: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP_REPORTING: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_THUMBNAIL_MODE: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_CONFIG_VENC_SYNC_FRAME_SEQUENCE_HEADER: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_CONFIG_VENC_REQUEST_SYNC_FRAME: + pkt->shdr.hdr.size += sizeof(u32); + break; + case HFI_PROPERTY_PARAM_VENC_MPEG4_SHORT_HEADER: + break; + case HFI_PROPERTY_PARAM_VENC_MPEG4_AC_PREDICTION: + break; + case HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE: { + struct hfi_bitrate *in = pdata, *brate = prop_data; + + brate->bitrate = in->bitrate; + brate->layer_id = in->layer_id; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*brate); + break; + } + case HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE: { + struct hfi_bitrate *in = pdata, *hfi = prop_data; + + hfi->bitrate = in->bitrate; + hfi->layer_id = in->layer_id; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hfi); + break; + } + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: { + struct hfi_profile_level *in = pdata, *pl = prop_data; + + pl->level = in->level; + pl->profile = in->profile; + if (pl->profile <= 0) + /* Profile not supported, falling back to high */ + pl->profile = HFI_H264_PROFILE_HIGH; + + if (!pl->level) + /* Level not supported, falling back to 1 */ + pl->level = 1; + + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*pl); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_ENTROPY_CONTROL: { + struct hfi_h264_entropy_control *in = pdata, *hfi = prop_data; + + hfi->entropy_mode = in->entropy_mode; + if (hfi->entropy_mode == HFI_H264_ENTROPY_CABAC) + hfi->cabac_model = in->cabac_model; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hfi); + break; + } + case HFI_PROPERTY_PARAM_VENC_RATE_CONTROL: { + u32 *in = pdata; + + switch (*in) { + case HFI_RATE_CONTROL_OFF: + case HFI_RATE_CONTROL_CBR_CFR: + case HFI_RATE_CONTROL_CBR_VFR: + case HFI_RATE_CONTROL_VBR_CFR: + case HFI_RATE_CONTROL_VBR_VFR: + case HFI_RATE_CONTROL_CQ: + break; + default: + ret = -EINVAL; + break; + } + + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VENC_MPEG4_TIME_RESOLUTION: { + struct hfi_mpeg4_time_resolution *in = pdata, *res = prop_data; + + res->time_increment_resolution = in->time_increment_resolution; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*res); + break; + } + case HFI_PROPERTY_PARAM_VENC_MPEG4_HEADER_EXTENSION: { + struct hfi_mpeg4_header_extension *in = pdata, *ext = prop_data; + + ext->header_extension = in->header_extension; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ext); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_DEBLOCK_CONTROL: { + struct hfi_h264_db_control *in = pdata, *db = prop_data; + + switch (in->mode) { + case HFI_H264_DB_MODE_DISABLE: + case HFI_H264_DB_MODE_SKIP_SLICE_BOUNDARY: + case HFI_H264_DB_MODE_ALL_BOUNDARY: + break; + default: + ret = -EINVAL; + break; + } + + db->mode = in->mode; + db->slice_alpha_offset = in->slice_alpha_offset; + db->slice_beta_offset = in->slice_beta_offset; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*db); + break; + } + case HFI_PROPERTY_PARAM_VENC_SESSION_QP: { + struct hfi_quantization *in = pdata, *quant = prop_data; + + quant->qp_i = in->qp_i; + quant->qp_p = in->qp_p; + quant->qp_b = in->qp_b; + quant->layer_id = in->layer_id; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*quant); + break; + } + case HFI_PROPERTY_PARAM_VENC_SESSION_QP_RANGE: { + struct hfi_quantization_range *in = pdata, *range = prop_data; + u32 min_qp, max_qp; + + min_qp = in->min_qp; + max_qp = in->max_qp; + + /* We'll be packing in the qp, so make sure we + * won't be losing data when masking + */ + if (min_qp > 0xff || max_qp > 0xff) { + ret = -ERANGE; + break; + } + + /* When creating the packet, pack the qp value as + * 0xiippbb, where ii = qp range for I-frames, + * pp = qp range for P-frames, etc. + */ + range->min_qp = min_qp | min_qp << 8 | min_qp << 16; + range->max_qp = max_qp | max_qp << 8 | max_qp << 16; + range->layer_id = in->layer_id; + + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*range); + break; + } + case HFI_PROPERTY_PARAM_VENC_VC1_PERF_CFG: { + struct hfi_vc1e_perf_cfg_type *in = pdata, *perf = prop_data; + + memcpy(perf->search_range_x_subsampled, + in->search_range_x_subsampled, + sizeof(perf->search_range_x_subsampled)); + memcpy(perf->search_range_y_subsampled, + in->search_range_y_subsampled, + sizeof(perf->search_range_y_subsampled)); + + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*perf); + break; + } + case HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES: { + struct hfi_max_num_b_frames *bframes = prop_data; + u32 *in = pdata; + + bframes->max_num_b_frames = *in; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*bframes); + break; + } + case HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD: { + struct hfi_intra_period *in = pdata, *intra = prop_data; + + intra->pframes = in->pframes; + intra->bframes = in->bframes; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*intra); + break; + } + case HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD: { + struct hfi_idr_period *in = pdata, *idr = prop_data; + + idr->idr_period = in->idr_period; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*idr); + break; + } + case HFI_PROPERTY_PARAM_VDEC_CONCEAL_COLOR: { + struct hfi_conceal_color *color = prop_data; + u32 *in = pdata; + + color->conceal_color = *in & 0xff; + color->conceal_color |= ((*in >> 10) & 0xff) << 8; + color->conceal_color |= ((*in >> 20) & 0xff) << 16; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*color); + break; + } + case HFI_PROPERTY_CONFIG_VPE_OPERATIONS: { + struct hfi_operations_type *in = pdata, *ops = prop_data; + + switch (in->rotation) { + case HFI_ROTATE_NONE: + case HFI_ROTATE_90: + case HFI_ROTATE_180: + case HFI_ROTATE_270: + break; + default: + ret = -EINVAL; + break; + } + + switch (in->flip) { + case HFI_FLIP_NONE: + case HFI_FLIP_HORIZONTAL: + case HFI_FLIP_VERTICAL: + break; + default: + ret = -EINVAL; + break; + } + + ops->rotation = in->rotation; + ops->flip = in->flip; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ops); + break; + } + case HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH: { + struct hfi_intra_refresh *in = pdata, *intra = prop_data; + + switch (in->mode) { + case HFI_INTRA_REFRESH_NONE: + case HFI_INTRA_REFRESH_ADAPTIVE: + case HFI_INTRA_REFRESH_CYCLIC: + case HFI_INTRA_REFRESH_CYCLIC_ADAPTIVE: + case HFI_INTRA_REFRESH_RANDOM: + break; + default: + ret = -EINVAL; + break; + } + + intra->mode = in->mode; + intra->air_mbs = in->air_mbs; + intra->air_ref = in->air_ref; + intra->cir_mbs = in->cir_mbs; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*intra); + break; + } + case HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_CONTROL: { + struct hfi_multi_slice_control *in = pdata, *multi = prop_data; + + switch (in->multi_slice) { + case HFI_MULTI_SLICE_OFF: + case HFI_MULTI_SLICE_GOB: + case HFI_MULTI_SLICE_BY_MB_COUNT: + case HFI_MULTI_SLICE_BY_BYTE_COUNT: + break; + default: + ret = -EINVAL; + break; + } + + multi->multi_slice = in->multi_slice; + multi->slice_size = in->slice_size; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*multi); + break; + } + case HFI_PROPERTY_PARAM_VENC_SLICE_DELIVERY_MODE: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO: { + struct hfi_h264_vui_timing_info *in = pdata, *vui = prop_data; + + vui->enable = in->enable; + vui->fixed_framerate = in->fixed_framerate; + vui->time_scale = in->time_scale; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*vui); + break; + } + case HFI_PROPERTY_CONFIG_VPE_DEINTERLACE: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_GENERATE_AUDNAL: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE: { + struct hfi_buffer_alloc_mode *in = pdata, *mode = prop_data; + + mode->type = in->type; + mode->mode = in->mode; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*mode); + break; + } + case HFI_PROPERTY_PARAM_VDEC_FRAME_ASSEMBLY: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_VUI_BITSTREAM_RESTRC: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_PRESERVE_TEXT_QUALITY: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VDEC_SCS_THRESHOLD: { + struct hfi_scs_threshold *thres = prop_data; + u32 *in = pdata; + + thres->threshold_value = *in; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*thres); + break; + } + case HFI_PROPERTY_PARAM_MVC_BUFFER_LAYOUT: { + struct hfi_mvc_buffer_layout_descp_type *in = pdata; + struct hfi_mvc_buffer_layout_descp_type *mvc = prop_data; + + switch (in->layout_type) { + case HFI_MVC_BUFFER_LAYOUT_TOP_BOTTOM: + case HFI_MVC_BUFFER_LAYOUT_SEQ: + break; + default: + ret = -EINVAL; + break; + } + + mvc->layout_type = in->layout_type; + mvc->bright_view_first = in->bright_view_first; + mvc->ngap = in->ngap; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*mvc); + break; + } + case HFI_PROPERTY_PARAM_VENC_LTRMODE: { + struct hfi_ltr_mode *in = pdata, *ltr = prop_data; + + switch (in->ltr_mode) { + case HFI_LTR_MODE_DISABLE: + case HFI_LTR_MODE_MANUAL: + case HFI_LTR_MODE_PERIODIC: + break; + default: + ret = -EINVAL; + break; + } + + ltr->ltr_mode = in->ltr_mode; + ltr->ltr_count = in->ltr_count; + ltr->trust_mode = in->trust_mode; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ltr); + break; + } + case HFI_PROPERTY_CONFIG_VENC_USELTRFRAME: { + struct hfi_ltr_use *in = pdata, *ltr_use = prop_data; + + ltr_use->frames = in->frames; + ltr_use->ref_ltr = in->ref_ltr; + ltr_use->use_constrnt = in->use_constrnt; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ltr_use); + break; + } + case HFI_PROPERTY_CONFIG_VENC_MARKLTRFRAME: { + struct hfi_ltr_mark *in = pdata, *ltr_mark = prop_data; + + ltr_mark->mark_frame = in->mark_frame; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*ltr_mark); + break; + } + case HFI_PROPERTY_PARAM_VENC_HIER_P_MAX_NUM_ENH_LAYER: { + u32 *in = pdata; + + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_CONFIG_VENC_HIER_P_ENH_LAYER: { + u32 *in = pdata; + + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VENC_DISABLE_RC_TIMESTAMP: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_INITIAL_QP: { + struct hfi_initial_quantization *in = pdata, *quant = prop_data; + + quant->init_qp_enable = in->init_qp_enable; + quant->qp_i = in->qp_i; + quant->qp_p = in->qp_p; + quant->qp_b = in->qp_b; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*quant); + break; + } + case HFI_PROPERTY_PARAM_VPE_COLOR_SPACE_CONVERSION: { + struct hfi_vpe_color_space_conversion *in = pdata; + struct hfi_vpe_color_space_conversion *csc = prop_data; + + memcpy(csc->csc_matrix, in->csc_matrix, + sizeof(csc->csc_matrix)); + memcpy(csc->csc_bias, in->csc_bias, sizeof(csc->csc_bias)); + memcpy(csc->csc_limit, in->csc_limit, sizeof(csc->csc_limit)); + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*csc); + break; + } + case HFI_PROPERTY_PARAM_VENC_VPX_ERROR_RESILIENCE_MODE: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_H264_NAL_SVC_EXT: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_CONFIG_VENC_PERF_MODE: { + u32 *in = pdata; + + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VENC_HIER_B_MAX_NUM_ENH_LAYER: { + u32 *in = pdata; + + pkt->data[1] = *in; + pkt->shdr.hdr.size += sizeof(u32) * 2; + break; + } + case HFI_PROPERTY_PARAM_VDEC_NONCP_OUTPUT2: { + struct hfi_enable *in = pdata, *en = prop_data; + + en->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*en); + break; + } + case HFI_PROPERTY_PARAM_VENC_HIER_P_HYBRID_MODE: { + struct hfi_hybrid_hierp *in = pdata, *hierp = prop_data; + + hierp->layers = in->layers; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hierp); + break; + } + case HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO: { + struct hfi_uncompressed_plane_actual_info *in = pdata; + struct hfi_uncompressed_plane_actual_info *info = prop_data; + + info->buffer_type = in->buffer_type; + info->num_planes = in->num_planes; + info->plane_format[0] = in->plane_format[0]; + if (in->num_planes > 1) + info->plane_format[1] = in->plane_format[1]; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*info); + break; + } + case HFI_PROPERTY_PARAM_VENC_HDR10_PQ_SEI: + return -ENOTSUPP; + + /* FOLLOWING PROPERTIES ARE NOT IMPLEMENTED IN CORE YET */ + case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: + case HFI_PROPERTY_CONFIG_PRIORITY: + case HFI_PROPERTY_CONFIG_BATCH_INFO: + case HFI_PROPERTY_SYS_IDLE_INDICATOR: + case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED: + case HFI_PROPERTY_PARAM_INTERLACE_FORMAT_SUPPORTED: + case HFI_PROPERTY_PARAM_CHROMA_SITE: + case HFI_PROPERTY_PARAM_PROPERTIES_SUPPORTED: + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED: + case HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED: + case HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SUPPORTED: + case HFI_PROPERTY_PARAM_MULTI_VIEW_FORMAT: + case HFI_PROPERTY_PARAM_MAX_SEQUENCE_HEADER_SIZE: + case HFI_PROPERTY_PARAM_CODEC_SUPPORTED: + case HFI_PROPERTY_PARAM_VDEC_MULTI_VIEW_SELECT: + case HFI_PROPERTY_PARAM_VDEC_MB_QUANTIZATION: + case HFI_PROPERTY_PARAM_VDEC_NUM_CONCEALED_MB: + case HFI_PROPERTY_PARAM_VDEC_H264_ENTROPY_SWITCHING: + case HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_INFO: + default: + return -EINVAL; + } + + return ret; +} + +static int +pkt_session_get_property_3xx(struct hfi_session_get_property_pkt *pkt, + void *cookie, u32 ptype) +{ + int ret = 0; + + if (!pkt || !cookie) + return -EINVAL; + + pkt->shdr.hdr.size = sizeof(struct hfi_session_get_property_pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_GET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + + switch (ptype) { + case HFI_PROPERTY_CONFIG_VDEC_ENTROPY: + pkt->data[0] = HFI_PROPERTY_CONFIG_VDEC_ENTROPY; + break; + default: + ret = pkt_session_get_property_1x(pkt, cookie, ptype); + break; + } + + return ret; +} + +static int +pkt_session_set_property_3xx(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata) +{ + void *prop_data; + int ret = 0; + + if (!pkt || !cookie || !pdata) + return -EINVAL; + + prop_data = &pkt->data[1]; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_SET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + pkt->data[0] = ptype; + + /* + * Any session set property which is different in 3XX packetization + * should be added as a new case below. All unchanged session set + * properties will be handled in the default case. + */ + switch (ptype) { + case HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM: { + struct hfi_multi_stream *in = pdata; + struct hfi_multi_stream_3x *multi = prop_data; + + multi->buffer_type = in->buffer_type; + multi->enable = in->enable; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*multi); + break; + } + case HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH: { + struct hfi_intra_refresh *in = pdata; + struct hfi_intra_refresh_3x *intra = prop_data; + + switch (in->mode) { + case HFI_INTRA_REFRESH_NONE: + case HFI_INTRA_REFRESH_ADAPTIVE: + case HFI_INTRA_REFRESH_CYCLIC: + case HFI_INTRA_REFRESH_CYCLIC_ADAPTIVE: + case HFI_INTRA_REFRESH_RANDOM: + break; + default: + ret = -EINVAL; + break; + } + + intra->mode = in->mode; + intra->mbs = in->cir_mbs; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*intra); + break; + } + case HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER: + /* for 3xx fw version session_continue is used */ + break; + default: + ret = pkt_session_set_property_1x(pkt, cookie, ptype, pdata); + break; + } + + return ret; +} + +static int +pkt_session_set_property_4xx(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata) +{ + void *prop_data; + + if (!pkt || !cookie || !pdata) + return -EINVAL; + + prop_data = &pkt->data[1]; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_SET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + pkt->data[0] = ptype; + + /* + * Any session set property which is different in 3XX packetization + * should be added as a new case below. All unchanged session set + * properties will be handled in the default case. + */ + switch (ptype) { + case HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL: { + struct hfi_buffer_count_actual *in = pdata; + struct hfi_buffer_count_actual_4xx *count = prop_data; + + count->count_actual = in->count_actual; + count->type = in->type; + count->count_min_host = in->count_actual; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*count); + break; + } + case HFI_PROPERTY_PARAM_WORK_MODE: { + struct hfi_video_work_mode *in = pdata, *wm = prop_data; + + wm->video_work_mode = in->video_work_mode; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*wm); + break; + } + case HFI_PROPERTY_CONFIG_VIDEOCORES_USAGE: { + struct hfi_videocores_usage_type *in = pdata, *cu = prop_data; + + cu->video_core_enable_mask = in->video_core_enable_mask; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*cu); + break; + } + case HFI_PROPERTY_PARAM_VENC_HDR10_PQ_SEI: { + struct hfi_hdr10_pq_sei *in = pdata, *hdr10 = prop_data; + + memcpy(hdr10, in, sizeof(*hdr10)); + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*hdr10); + break; + } + case HFI_PROPERTY_PARAM_VDEC_CONCEAL_COLOR: { + struct hfi_conceal_color_v4 *color = prop_data; + u32 *in = pdata; + + color->conceal_color_8bit = *in & 0xff; + color->conceal_color_8bit |= ((*in >> 10) & 0xff) << 8; + color->conceal_color_8bit |= ((*in >> 20) & 0xff) << 16; + color->conceal_color_10bit = *in; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*color); + break; + } + + case HFI_PROPERTY_PARAM_VENC_H264_TRANSFORM_8X8: { + struct hfi_h264_8x8_transform *in = pdata, *tm = prop_data; + + tm->enable_type = in->enable_type; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*tm); + break; + } + + case HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE: + case HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER: + case HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE: + case HFI_PROPERTY_PARAM_VENC_SESSION_QP: + case HFI_PROPERTY_PARAM_VENC_SESSION_QP_RANGE: + /* not implemented on Venus 4xx */ + return -ENOTSUPP; + default: + return pkt_session_set_property_3xx(pkt, cookie, ptype, pdata); + } + + return 0; +} + +static int +pkt_session_set_property_6xx(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata) +{ + void *prop_data; + + if (!pkt || !cookie || !pdata) + return -EINVAL; + + prop_data = &pkt->data[1]; + + pkt->shdr.hdr.size = sizeof(*pkt); + pkt->shdr.hdr.pkt_type = HFI_CMD_SESSION_SET_PROPERTY; + pkt->shdr.session_id = hash32_ptr(cookie); + pkt->num_properties = 1; + pkt->data[0] = ptype; + + switch (ptype) { + case HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO: { + struct hfi_uncompressed_plane_actual_constraints_info *in = pdata; + struct hfi_uncompressed_plane_actual_constraints_info *info = prop_data; + + info->buffer_type = in->buffer_type; + info->num_planes = in->num_planes; + info->plane_format[0] = in->plane_format[0]; + if (in->num_planes > 1) + info->plane_format[1] = in->plane_format[1]; + + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*info); + break; + } + case HFI_PROPERTY_CONFIG_HEIC_FRAME_QUALITY: { + struct hfi_heic_frame_quality *in = pdata, *cq = prop_data; + + cq->frame_quality = in->frame_quality; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*cq); + break; + } + case HFI_PROPERTY_PARAM_WORK_ROUTE: { + struct hfi_video_work_route *in = pdata, *wr = prop_data; + + wr->video_work_route = in->video_work_route; + pkt->shdr.hdr.size += sizeof(u32) + sizeof(*wr); + break; + } + default: + return pkt_session_set_property_4xx(pkt, cookie, ptype, pdata); + } + + return 0; +} + +int pkt_session_get_property(struct hfi_session_get_property_pkt *pkt, + void *cookie, u32 ptype) +{ + if (hfi_ver == HFI_VERSION_1XX) + return pkt_session_get_property_1x(pkt, cookie, ptype); + + return pkt_session_get_property_3xx(pkt, cookie, ptype); +} + +int pkt_session_set_property(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata) +{ + if (hfi_ver == HFI_VERSION_1XX) + return pkt_session_set_property_1x(pkt, cookie, ptype, pdata); + + if (hfi_ver == HFI_VERSION_3XX) + return pkt_session_set_property_3xx(pkt, cookie, ptype, pdata); + + if (hfi_ver == HFI_VERSION_4XX) + return pkt_session_set_property_4xx(pkt, cookie, ptype, pdata); + + return pkt_session_set_property_6xx(pkt, cookie, ptype, pdata); +} + +void pkt_set_version(enum hfi_version version) +{ + hfi_ver = version; +} diff --git a/drivers/media/platform/qcom/venus/hfi_cmds.h b/drivers/media/platform/qcom/venus/hfi_cmds.h new file mode 100644 index 000000000..99bc0b6db --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_cmds.h @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_HFI_CMDS_H__ +#define __VENUS_HFI_CMDS_H__ + +#include "hfi.h" + +/* commands */ +#define HFI_CMD_SYS_INIT 0x10001 +#define HFI_CMD_SYS_PC_PREP 0x10002 +#define HFI_CMD_SYS_SET_RESOURCE 0x10003 +#define HFI_CMD_SYS_RELEASE_RESOURCE 0x10004 +#define HFI_CMD_SYS_SET_PROPERTY 0x10005 +#define HFI_CMD_SYS_GET_PROPERTY 0x10006 +#define HFI_CMD_SYS_SESSION_INIT 0x10007 +#define HFI_CMD_SYS_SESSION_END 0x10008 +#define HFI_CMD_SYS_SET_BUFFERS 0x10009 +#define HFI_CMD_SYS_TEST_SSR 0x10101 + +#define HFI_CMD_SESSION_SET_PROPERTY 0x11001 +#define HFI_CMD_SESSION_SET_BUFFERS 0x11002 +#define HFI_CMD_SESSION_GET_SEQUENCE_HEADER 0x11003 + +#define HFI_CMD_SYS_SESSION_ABORT 0x210001 +#define HFI_CMD_SYS_PING 0x210002 + +#define HFI_CMD_SESSION_LOAD_RESOURCES 0x211001 +#define HFI_CMD_SESSION_START 0x211002 +#define HFI_CMD_SESSION_STOP 0x211003 +#define HFI_CMD_SESSION_EMPTY_BUFFER 0x211004 +#define HFI_CMD_SESSION_FILL_BUFFER 0x211005 +#define HFI_CMD_SESSION_SUSPEND 0x211006 +#define HFI_CMD_SESSION_RESUME 0x211007 +#define HFI_CMD_SESSION_FLUSH 0x211008 +#define HFI_CMD_SESSION_GET_PROPERTY 0x211009 +#define HFI_CMD_SESSION_PARSE_SEQUENCE_HEADER 0x21100a +#define HFI_CMD_SESSION_RELEASE_BUFFERS 0x21100b +#define HFI_CMD_SESSION_RELEASE_RESOURCES 0x21100c +#define HFI_CMD_SESSION_CONTINUE 0x21100d +#define HFI_CMD_SESSION_SYNC 0x21100e + +/* command packets */ +struct hfi_sys_init_pkt { + struct hfi_pkt_hdr hdr; + u32 arch_type; +}; + +struct hfi_sys_pc_prep_pkt { + struct hfi_pkt_hdr hdr; +}; + +struct hfi_sys_set_resource_pkt { + struct hfi_pkt_hdr hdr; + u32 resource_handle; + u32 resource_type; + u32 resource_data[1]; +}; + +struct hfi_sys_release_resource_pkt { + struct hfi_pkt_hdr hdr; + u32 resource_type; + u32 resource_handle; +}; + +struct hfi_sys_set_property_pkt { + struct hfi_pkt_hdr hdr; + u32 num_properties; + u32 data[]; +}; + +struct hfi_sys_get_property_pkt { + struct hfi_pkt_hdr hdr; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_sys_set_buffers_pkt { + struct hfi_pkt_hdr hdr; + u32 buffer_type; + u32 buffer_size; + u32 num_buffers; + u32 buffer_addr[1]; +}; + +struct hfi_sys_ping_pkt { + struct hfi_pkt_hdr hdr; + u32 client_data; +}; + +struct hfi_session_init_pkt { + struct hfi_session_hdr_pkt shdr; + u32 session_domain; + u32 session_codec; +}; + +struct hfi_session_end_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_abort_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_set_property_pkt { + struct hfi_session_hdr_pkt shdr; + u32 num_properties; + u32 data[]; +}; + +struct hfi_session_set_buffers_pkt { + struct hfi_session_hdr_pkt shdr; + u32 buffer_type; + u32 buffer_size; + u32 extradata_size; + u32 min_buffer_size; + u32 num_buffers; + u32 buffer_info[1]; +}; + +struct hfi_session_get_sequence_header_pkt { + struct hfi_session_hdr_pkt shdr; + u32 buffer_len; + u32 packet_buffer; +}; + +struct hfi_session_load_resources_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_start_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_stop_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_empty_buffer_compressed_pkt { + struct hfi_session_hdr_pkt shdr; + u32 time_stamp_hi; + u32 time_stamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 input_tag; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[1]; +}; + +struct hfi_session_empty_buffer_uncompressed_plane0_pkt { + struct hfi_session_hdr_pkt shdr; + u32 view_id; + u32 time_stamp_hi; + u32 time_stamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 input_tag; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[1]; +}; + +struct hfi_session_empty_buffer_uncompressed_plane1_pkt { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 packet_buffer2; + u32 data[1]; +}; + +struct hfi_session_empty_buffer_uncompressed_plane2_pkt { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 packet_buffer3; + u32 data[1]; +}; + +struct hfi_session_fill_buffer_pkt { + struct hfi_session_hdr_pkt shdr; + u32 stream_id; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 output_tag; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[1]; +}; + +struct hfi_session_flush_pkt { + struct hfi_session_hdr_pkt shdr; + u32 flush_type; +}; + +struct hfi_session_suspend_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_resume_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_get_property_pkt { + struct hfi_session_hdr_pkt shdr; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_session_release_buffer_pkt { + struct hfi_session_hdr_pkt shdr; + u32 buffer_type; + u32 buffer_size; + u32 extradata_size; + u32 response_req; + u32 num_buffers; + u32 buffer_info[1]; +}; + +struct hfi_session_release_resources_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +struct hfi_session_parse_sequence_header_pkt { + struct hfi_session_hdr_pkt shdr; + u32 header_len; + u32 packet_buffer; +}; + +struct hfi_sfr { + u32 buf_size; + u8 data[1]; +}; + +struct hfi_sys_test_ssr_pkt { + struct hfi_pkt_hdr hdr; + u32 trigger_type; +}; + +void pkt_set_version(enum hfi_version version); + +void pkt_sys_init(struct hfi_sys_init_pkt *pkt, u32 arch_type); +void pkt_sys_pc_prep(struct hfi_sys_pc_prep_pkt *pkt); +void pkt_sys_idle_indicator(struct hfi_sys_set_property_pkt *pkt, u32 enable); +void pkt_sys_power_control(struct hfi_sys_set_property_pkt *pkt, u32 enable); +void pkt_sys_ubwc_config(struct hfi_sys_set_property_pkt *pkt, const struct hfi_ubwc_config *hfi); +int pkt_sys_set_resource(struct hfi_sys_set_resource_pkt *pkt, u32 id, u32 size, + u32 addr, void *cookie); +int pkt_sys_unset_resource(struct hfi_sys_release_resource_pkt *pkt, u32 id, + u32 size, void *cookie); +void pkt_sys_debug_config(struct hfi_sys_set_property_pkt *pkt, u32 mode, + u32 config); +void pkt_sys_coverage_config(struct hfi_sys_set_property_pkt *pkt, u32 mode); +void pkt_sys_ping(struct hfi_sys_ping_pkt *pkt, u32 cookie); +void pkt_sys_image_version(struct hfi_sys_get_property_pkt *pkt); +int pkt_sys_ssr_cmd(struct hfi_sys_test_ssr_pkt *pkt, u32 trigger_type); +int pkt_session_init(struct hfi_session_init_pkt *pkt, void *cookie, + u32 session_type, u32 codec); +void pkt_session_cmd(struct hfi_session_pkt *pkt, u32 pkt_type, void *cookie); +int pkt_session_set_buffers(struct hfi_session_set_buffers_pkt *pkt, + void *cookie, struct hfi_buffer_desc *bd); +int pkt_session_unset_buffers(struct hfi_session_release_buffer_pkt *pkt, + void *cookie, struct hfi_buffer_desc *bd); +int pkt_session_etb_decoder(struct hfi_session_empty_buffer_compressed_pkt *pkt, + void *cookie, struct hfi_frame_data *input_frame); +int pkt_session_etb_encoder( + struct hfi_session_empty_buffer_uncompressed_plane0_pkt *pkt, + void *cookie, struct hfi_frame_data *input_frame); +int pkt_session_ftb(struct hfi_session_fill_buffer_pkt *pkt, + void *cookie, struct hfi_frame_data *output_frame); +int pkt_session_parse_seq_header( + struct hfi_session_parse_sequence_header_pkt *pkt, + void *cookie, u32 seq_hdr, u32 seq_hdr_len); +int pkt_session_get_seq_hdr(struct hfi_session_get_sequence_header_pkt *pkt, + void *cookie, u32 seq_hdr, u32 seq_hdr_len); +int pkt_session_flush(struct hfi_session_flush_pkt *pkt, void *cookie, + u32 flush_mode); +int pkt_session_get_property(struct hfi_session_get_property_pkt *pkt, + void *cookie, u32 ptype); +int pkt_session_set_property(struct hfi_session_set_property_pkt *pkt, + void *cookie, u32 ptype, void *pdata); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_helper.h b/drivers/media/platform/qcom/venus/hfi_helper.h new file mode 100644 index 000000000..d2d6719a2 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_helper.h @@ -0,0 +1,1237 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_HFI_HELPER_H__ +#define __VENUS_HFI_HELPER_H__ + +#define HFI_DOMAIN_BASE_COMMON 0 + +#define HFI_DOMAIN_BASE_VDEC 0x1000000 +#define HFI_DOMAIN_BASE_VENC 0x2000000 +#define HFI_DOMAIN_BASE_VPE 0x3000000 + +#define HFI_VIDEO_ARCH_OX 0x1 + +#define HFI_ARCH_COMMON_OFFSET 0 +#define HFI_ARCH_OX_OFFSET 0x200000 + +#define HFI_OX_BASE 0x1000000 + +#define HFI_CMD_START_OFFSET 0x10000 +#define HFI_MSG_START_OFFSET 0x20000 + +#define HFI_ERR_NONE 0x0 +#define HFI_ERR_SYS_FATAL 0x1 +#define HFI_ERR_SYS_INVALID_PARAMETER 0x2 +#define HFI_ERR_SYS_VERSION_MISMATCH 0x3 +#define HFI_ERR_SYS_INSUFFICIENT_RESOURCES 0x4 +#define HFI_ERR_SYS_MAX_SESSIONS_REACHED 0x5 +#define HFI_ERR_SYS_UNSUPPORTED_CODEC 0x6 +#define HFI_ERR_SYS_SESSION_IN_USE 0x7 +#define HFI_ERR_SYS_SESSION_ID_OUT_OF_RANGE 0x8 +#define HFI_ERR_SYS_UNSUPPORTED_DOMAIN 0x9 + +#define HFI_ERR_SESSION_FATAL 0x1001 +#define HFI_ERR_SESSION_INVALID_PARAMETER 0x1002 +#define HFI_ERR_SESSION_BAD_POINTER 0x1003 +#define HFI_ERR_SESSION_INVALID_SESSION_ID 0x1004 +#define HFI_ERR_SESSION_INVALID_STREAM_ID 0x1005 +#define HFI_ERR_SESSION_INCORRECT_STATE_OPERATION 0x1006 +#define HFI_ERR_SESSION_UNSUPPORTED_PROPERTY 0x1007 +#define HFI_ERR_SESSION_UNSUPPORTED_SETTING 0x1008 +#define HFI_ERR_SESSION_INSUFFICIENT_RESOURCES 0x1009 +#define HFI_ERR_SESSION_STREAM_CORRUPT_OUTPUT_STALLED 0x100a +#define HFI_ERR_SESSION_STREAM_CORRUPT 0x100b +#define HFI_ERR_SESSION_ENC_OVERFLOW 0x100c +#define HFI_ERR_SESSION_UNSUPPORTED_STREAM 0x100d +#define HFI_ERR_SESSION_CMDSIZE 0x100e +#define HFI_ERR_SESSION_UNSUPPORT_CMD 0x100f +#define HFI_ERR_SESSION_UNSUPPORT_BUFFERTYPE 0x1010 +#define HFI_ERR_SESSION_BUFFERCOUNT_TOOSMALL 0x1011 +#define HFI_ERR_SESSION_INVALID_SCALE_FACTOR 0x1012 +#define HFI_ERR_SESSION_UPSCALE_NOT_SUPPORTED 0x1013 + +#define HFI_EVENT_SYS_ERROR 0x1 +#define HFI_EVENT_SESSION_ERROR 0x2 + +#define HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES 0x1000001 +#define HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES 0x1000002 +#define HFI_EVENT_SESSION_SEQUENCE_CHANGED 0x1000003 +#define HFI_EVENT_SESSION_PROPERTY_CHANGED 0x1000004 +#define HFI_EVENT_SESSION_LTRUSE_FAILED 0x1000005 +#define HFI_EVENT_RELEASE_BUFFER_REFERENCE 0x1000006 + +#define HFI_BUFFERFLAG_EOS 0x00000001 +#define HFI_BUFFERFLAG_STARTTIME 0x00000002 +#define HFI_BUFFERFLAG_DECODEONLY 0x00000004 +#define HFI_BUFFERFLAG_DATACORRUPT 0x00000008 +#define HFI_BUFFERFLAG_ENDOFFRAME 0x00000010 +#define HFI_BUFFERFLAG_SYNCFRAME 0x00000020 +#define HFI_BUFFERFLAG_EXTRADATA 0x00000040 +#define HFI_BUFFERFLAG_CODECCONFIG 0x00000080 +#define HFI_BUFFERFLAG_TIMESTAMPINVALID 0x00000100 +#define HFI_BUFFERFLAG_READONLY 0x00000200 +#define HFI_BUFFERFLAG_ENDOFSUBFRAME 0x00000400 +#define HFI_BUFFERFLAG_EOSEQ 0x00200000 +#define HFI_BUFFERFLAG_MBAFF 0x08000000 +#define HFI_BUFFERFLAG_VPE_YUV_601_709_CSC_CLAMP 0x10000000 +#define HFI_BUFFERFLAG_DROP_FRAME 0x20000000 +#define HFI_BUFFERFLAG_TEI 0x40000000 +#define HFI_BUFFERFLAG_DISCONTINUITY 0x80000000 + +#define HFI_ERR_SESSION_EMPTY_BUFFER_DONE_OUTPUT_PENDING 0x1001001 +#define HFI_ERR_SESSION_SAME_STATE_OPERATION 0x1001002 +#define HFI_ERR_SESSION_SYNC_FRAME_NOT_DETECTED 0x1001003 +#define HFI_ERR_SESSION_START_CODE_NOT_FOUND 0x1001004 + +#define HFI_FLUSH_INPUT 0x1000001 +#define HFI_FLUSH_OUTPUT 0x1000002 +#define HFI_FLUSH_OUTPUT2 0x1000003 +#define HFI_FLUSH_ALL 0x1000004 + +#define HFI_EXTRADATA_NONE 0x00000000 +#define HFI_EXTRADATA_MB_QUANTIZATION 0x00000001 +#define HFI_EXTRADATA_INTERLACE_VIDEO 0x00000002 +#define HFI_EXTRADATA_VC1_FRAMEDISP 0x00000003 +#define HFI_EXTRADATA_VC1_SEQDISP 0x00000004 +#define HFI_EXTRADATA_TIMESTAMP 0x00000005 +#define HFI_EXTRADATA_S3D_FRAME_PACKING 0x00000006 +#define HFI_EXTRADATA_FRAME_RATE 0x00000007 +#define HFI_EXTRADATA_PANSCAN_WINDOW 0x00000008 +#define HFI_EXTRADATA_RECOVERY_POINT_SEI 0x00000009 +#define HFI_EXTRADATA_MPEG2_SEQDISP 0x0000000d +#define HFI_EXTRADATA_STREAM_USERDATA 0x0000000e +#define HFI_EXTRADATA_FRAME_QP 0x0000000f +#define HFI_EXTRADATA_FRAME_BITS_INFO 0x00000010 +#define HFI_EXTRADATA_MULTISLICE_INFO 0x7f100000 +#define HFI_EXTRADATA_NUM_CONCEALED_MB 0x7f100001 +#define HFI_EXTRADATA_INDEX 0x7f100002 +#define HFI_EXTRADATA_METADATA_LTR 0x7f100004 +#define HFI_EXTRADATA_METADATA_FILLER 0x7fe00002 + +#define HFI_INDEX_EXTRADATA_INPUT_CROP 0x0700000e +#define HFI_INDEX_EXTRADATA_OUTPUT_CROP 0x0700000f +#define HFI_INDEX_EXTRADATA_DIGITAL_ZOOM 0x07000010 +#define HFI_INDEX_EXTRADATA_ASPECT_RATIO 0x7f100003 + +#define HFI_INTERLACE_FRAME_PROGRESSIVE 0x01 +#define HFI_INTERLACE_INTERLEAVE_FRAME_TOPFIELDFIRST 0x02 +#define HFI_INTERLACE_INTERLEAVE_FRAME_BOTTOMFIELDFIRST 0x04 +#define HFI_INTERLACE_FRAME_TOPFIELDFIRST 0x08 +#define HFI_INTERLACE_FRAME_BOTTOMFIELDFIRST 0x10 + +/* + * HFI_PROPERTY_PARAM_OX_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_OX_OFFSET + 0x1000 + */ +#define HFI_PROPERTY_PARAM_BUFFER_COUNT_ACTUAL 0x201001 +#define HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO 0x201002 +#define HFI_PROPERTY_PARAM_INTERLACE_FORMAT_SUPPORTED 0x201003 +#define HFI_PROPERTY_PARAM_CHROMA_SITE 0x201004 +#define HFI_PROPERTY_PARAM_EXTRA_DATA_HEADER_CONFIG 0x201005 +#define HFI_PROPERTY_PARAM_INDEX_EXTRADATA 0x201006 +#define HFI_PROPERTY_PARAM_DIVX_FORMAT 0x201007 +#define HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE 0x201008 +#define HFI_PROPERTY_PARAM_S3D_FRAME_PACKING_EXTRADATA 0x201009 +#define HFI_PROPERTY_PARAM_ERR_DETECTION_CODE_EXTRADATA 0x20100a +#define HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE_SUPPORTED 0x20100b +#define HFI_PROPERTY_PARAM_BUFFER_SIZE_ACTUAL 0x20100c +#define HFI_PROPERTY_PARAM_BUFFER_DISPLAY_HOLD_COUNT_ACTUAL 0x20100d + +/* + * HFI_PROPERTY_CONFIG_OX_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_OX_OFFSET + 0x2000 + */ +#define HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS 0x202001 +#define HFI_PROPERTY_CONFIG_REALTIME 0x202002 +#define HFI_PROPERTY_CONFIG_PRIORITY 0x202003 +#define HFI_PROPERTY_CONFIG_BATCH_INFO 0x202004 + +/* + * HFI_PROPERTY_PARAM_VDEC_OX_START \ + * HFI_DOMAIN_BASE_VDEC + HFI_ARCH_OX_OFFSET + 0x3000 + */ +#define HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER 0x1203001 +#define HFI_PROPERTY_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT 0x1203002 +#define HFI_PROPERTY_PARAM_VDEC_MULTI_VIEW_SELECT 0x1203003 +#define HFI_PROPERTY_PARAM_VDEC_PICTURE_TYPE_DECODE 0x1203004 +#define HFI_PROPERTY_PARAM_VDEC_OUTPUT_ORDER 0x1203005 +#define HFI_PROPERTY_PARAM_VDEC_MB_QUANTIZATION 0x1203006 +#define HFI_PROPERTY_PARAM_VDEC_NUM_CONCEALED_MB 0x1203007 +#define HFI_PROPERTY_PARAM_VDEC_H264_ENTROPY_SWITCHING 0x1203008 +#define HFI_PROPERTY_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO 0x1203009 +#define HFI_PROPERTY_PARAM_VDEC_FRAME_RATE_EXTRADATA 0x120300a +#define HFI_PROPERTY_PARAM_VDEC_PANSCAN_WNDW_EXTRADATA 0x120300b +#define HFI_PROPERTY_PARAM_VDEC_RECOVERY_POINT_SEI_EXTRADATA 0x120300c +#define HFI_PROPERTY_PARAM_VDEC_THUMBNAIL_MODE 0x120300d +#define HFI_PROPERTY_PARAM_VDEC_FRAME_ASSEMBLY 0x120300e +#define HFI_PROPERTY_PARAM_VDEC_DPB_COUNTS 0x120300e +#define HFI_PROPERTY_PARAM_VDEC_VC1_FRAMEDISP_EXTRADATA 0x1203011 +#define HFI_PROPERTY_PARAM_VDEC_VC1_SEQDISP_EXTRADATA 0x1203012 +#define HFI_PROPERTY_PARAM_VDEC_TIMESTAMP_EXTRADATA 0x1203013 +#define HFI_PROPERTY_PARAM_VDEC_INTERLACE_VIDEO_EXTRADATA 0x1203014 +#define HFI_PROPERTY_PARAM_VDEC_AVC_SESSION_SELECT 0x1203015 +#define HFI_PROPERTY_PARAM_VDEC_MPEG2_SEQDISP_EXTRADATA 0x1203016 +#define HFI_PROPERTY_PARAM_VDEC_STREAM_USERDATA_EXTRADATA 0x1203017 +#define HFI_PROPERTY_PARAM_VDEC_FRAME_QP_EXTRADATA 0x1203018 +#define HFI_PROPERTY_PARAM_VDEC_FRAME_BITS_INFO_EXTRADATA 0x1203019 +#define HFI_PROPERTY_PARAM_VDEC_SCS_THRESHOLD 0x120301a + +/* + * HFI_PROPERTY_CONFIG_VDEC_OX_START + * HFI_DOMAIN_BASE_VDEC + HFI_ARCH_OX_OFFSET + 0x0000 + */ +#define HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER 0x1200001 +#define HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP_REPORTING 0x1200002 +#define HFI_PROPERTY_CONFIG_VDEC_MB_ERROR_MAP 0x1200003 + +#define HFI_PROPERTY_CONFIG_VDEC_ENTROPY 0x1204004 + +/* + * HFI_PROPERTY_PARAM_VENC_OX_START + * HFI_DOMAIN_BASE_VENC + HFI_ARCH_OX_OFFSET + 0x5000 + */ +#define HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_INFO 0x2205001 +#define HFI_PROPERTY_PARAM_VENC_H264_IDR_S3D_FRAME_PACKING_NAL 0x2205002 +#define HFI_PROPERTY_PARAM_VENC_LTR_INFO 0x2205003 +#define HFI_PROPERTY_PARAM_VENC_MBI_DUMPING 0x2205005 + +/* + * HFI_PROPERTY_CONFIG_VENC_OX_START + * HFI_DOMAIN_BASE_VENC + HFI_ARCH_OX_OFFSET + 0x6000 + */ +#define HFI_PROPERTY_CONFIG_VENC_FRAME_QP 0x2206001 + +/* + * HFI_PROPERTY_PARAM_VPE_OX_START + * HFI_DOMAIN_BASE_VPE + HFI_ARCH_OX_OFFSET + 0x7000 + */ +#define HFI_PROPERTY_PARAM_VPE_COLOR_SPACE_CONVERSION 0x3207001 + +#define HFI_PROPERTY_CONFIG_VPE_OX_START \ + (HFI_DOMAIN_BASE_VPE + HFI_ARCH_OX_OFFSET + 0x8000) + +#define HFI_CHROMA_SITE_0 0x1000001 +#define HFI_CHROMA_SITE_1 0x1000002 +#define HFI_CHROMA_SITE_2 0x1000003 +#define HFI_CHROMA_SITE_3 0x1000004 +#define HFI_CHROMA_SITE_4 0x1000005 +#define HFI_CHROMA_SITE_5 0x1000006 + +#define HFI_PRIORITY_LOW 10 +#define HFI_PRIOIRTY_MEDIUM 20 +#define HFI_PRIORITY_HIGH 30 + +#define HFI_OUTPUT_ORDER_DISPLAY 0x1000001 +#define HFI_OUTPUT_ORDER_DECODE 0x1000002 + +#define HFI_RATE_CONTROL_OFF 0x1000001 +#define HFI_RATE_CONTROL_VBR_VFR 0x1000002 +#define HFI_RATE_CONTROL_VBR_CFR 0x1000003 +#define HFI_RATE_CONTROL_CBR_VFR 0x1000004 +#define HFI_RATE_CONTROL_CBR_CFR 0x1000005 +#define HFI_RATE_CONTROL_CQ 0x1000008 + +#define HFI_VIDEO_CODEC_H264 0x00000002 +#define HFI_VIDEO_CODEC_H263 0x00000004 +#define HFI_VIDEO_CODEC_MPEG1 0x00000008 +#define HFI_VIDEO_CODEC_MPEG2 0x00000010 +#define HFI_VIDEO_CODEC_MPEG4 0x00000020 +#define HFI_VIDEO_CODEC_DIVX_311 0x00000040 +#define HFI_VIDEO_CODEC_DIVX 0x00000080 +#define HFI_VIDEO_CODEC_VC1 0x00000100 +#define HFI_VIDEO_CODEC_SPARK 0x00000200 +#define HFI_VIDEO_CODEC_VP8 0x00001000 +#define HFI_VIDEO_CODEC_HEVC 0x00002000 +#define HFI_VIDEO_CODEC_VP9 0x00004000 +#define HFI_VIDEO_CODEC_HEVC_HYBRID 0x80000000 + +#define HFI_H264_PROFILE_BASELINE 0x00000001 +#define HFI_H264_PROFILE_MAIN 0x00000002 +#define HFI_H264_PROFILE_HIGH 0x00000004 +#define HFI_H264_PROFILE_STEREO_HIGH 0x00000008 +#define HFI_H264_PROFILE_MULTIVIEW_HIGH 0x00000010 +#define HFI_H264_PROFILE_CONSTRAINED_BASE 0x00000020 +#define HFI_H264_PROFILE_CONSTRAINED_HIGH 0x00000040 + +#define HFI_H264_LEVEL_1 0x00000001 +#define HFI_H264_LEVEL_1b 0x00000002 +#define HFI_H264_LEVEL_11 0x00000004 +#define HFI_H264_LEVEL_12 0x00000008 +#define HFI_H264_LEVEL_13 0x00000010 +#define HFI_H264_LEVEL_2 0x00000020 +#define HFI_H264_LEVEL_21 0x00000040 +#define HFI_H264_LEVEL_22 0x00000080 +#define HFI_H264_LEVEL_3 0x00000100 +#define HFI_H264_LEVEL_31 0x00000200 +#define HFI_H264_LEVEL_32 0x00000400 +#define HFI_H264_LEVEL_4 0x00000800 +#define HFI_H264_LEVEL_41 0x00001000 +#define HFI_H264_LEVEL_42 0x00002000 +#define HFI_H264_LEVEL_5 0x00004000 +#define HFI_H264_LEVEL_51 0x00008000 +#define HFI_H264_LEVEL_52 0x00010000 + +#define HFI_H263_PROFILE_BASELINE 0x00000001 + +#define HFI_H263_LEVEL_10 0x00000001 +#define HFI_H263_LEVEL_20 0x00000002 +#define HFI_H263_LEVEL_30 0x00000004 +#define HFI_H263_LEVEL_40 0x00000008 +#define HFI_H263_LEVEL_45 0x00000010 +#define HFI_H263_LEVEL_50 0x00000020 +#define HFI_H263_LEVEL_60 0x00000040 +#define HFI_H263_LEVEL_70 0x00000080 + +#define HFI_MPEG2_PROFILE_SIMPLE 0x00000001 +#define HFI_MPEG2_PROFILE_MAIN 0x00000002 +#define HFI_MPEG2_PROFILE_422 0x00000004 +#define HFI_MPEG2_PROFILE_SNR 0x00000008 +#define HFI_MPEG2_PROFILE_SPATIAL 0x00000010 +#define HFI_MPEG2_PROFILE_HIGH 0x00000020 + +#define HFI_MPEG2_LEVEL_LL 0x00000001 +#define HFI_MPEG2_LEVEL_ML 0x00000002 +#define HFI_MPEG2_LEVEL_H14 0x00000004 +#define HFI_MPEG2_LEVEL_HL 0x00000008 + +#define HFI_MPEG4_PROFILE_SIMPLE 0x00000001 +#define HFI_MPEG4_PROFILE_ADVANCEDSIMPLE 0x00000002 + +#define HFI_MPEG4_LEVEL_0 0x00000001 +#define HFI_MPEG4_LEVEL_0b 0x00000002 +#define HFI_MPEG4_LEVEL_1 0x00000004 +#define HFI_MPEG4_LEVEL_2 0x00000008 +#define HFI_MPEG4_LEVEL_3 0x00000010 +#define HFI_MPEG4_LEVEL_4 0x00000020 +#define HFI_MPEG4_LEVEL_4a 0x00000040 +#define HFI_MPEG4_LEVEL_5 0x00000080 +#define HFI_MPEG4_LEVEL_6 0x00000100 +#define HFI_MPEG4_LEVEL_7 0x00000200 +#define HFI_MPEG4_LEVEL_8 0x00000400 +#define HFI_MPEG4_LEVEL_9 0x00000800 +#define HFI_MPEG4_LEVEL_3b 0x00001000 + +#define HFI_VC1_PROFILE_SIMPLE 0x00000001 +#define HFI_VC1_PROFILE_MAIN 0x00000002 +#define HFI_VC1_PROFILE_ADVANCED 0x00000004 + +#define HFI_VC1_LEVEL_LOW 0x00000001 +#define HFI_VC1_LEVEL_MEDIUM 0x00000002 +#define HFI_VC1_LEVEL_HIGH 0x00000004 +#define HFI_VC1_LEVEL_0 0x00000008 +#define HFI_VC1_LEVEL_1 0x00000010 +#define HFI_VC1_LEVEL_2 0x00000020 +#define HFI_VC1_LEVEL_3 0x00000040 +#define HFI_VC1_LEVEL_4 0x00000080 + +#define HFI_VPX_PROFILE_SIMPLE 0x00000001 +#define HFI_VPX_PROFILE_ADVANCED 0x00000002 +#define HFI_VPX_PROFILE_VERSION_0 0x00000004 +#define HFI_VPX_PROFILE_VERSION_1 0x00000008 +#define HFI_VPX_PROFILE_VERSION_2 0x00000010 +#define HFI_VPX_PROFILE_VERSION_3 0x00000020 + +#define HFI_DIVX_FORMAT_4 0x1 +#define HFI_DIVX_FORMAT_5 0x2 +#define HFI_DIVX_FORMAT_6 0x3 + +#define HFI_DIVX_PROFILE_QMOBILE 0x00000001 +#define HFI_DIVX_PROFILE_MOBILE 0x00000002 +#define HFI_DIVX_PROFILE_MT 0x00000004 +#define HFI_DIVX_PROFILE_HT 0x00000008 +#define HFI_DIVX_PROFILE_HD 0x00000010 + +#define HFI_HEVC_PROFILE_MAIN 0x00000001 +#define HFI_HEVC_PROFILE_MAIN10 0x00000002 +#define HFI_HEVC_PROFILE_MAIN_STILL_PIC 0x00000004 + +#define HFI_HEVC_LEVEL_1 0x00000001 +#define HFI_HEVC_LEVEL_2 0x00000002 +#define HFI_HEVC_LEVEL_21 0x00000004 +#define HFI_HEVC_LEVEL_3 0x00000008 +#define HFI_HEVC_LEVEL_31 0x00000010 +#define HFI_HEVC_LEVEL_4 0x00000020 +#define HFI_HEVC_LEVEL_41 0x00000040 +#define HFI_HEVC_LEVEL_5 0x00000080 +#define HFI_HEVC_LEVEL_51 0x00000100 +#define HFI_HEVC_LEVEL_52 0x00000200 +#define HFI_HEVC_LEVEL_6 0x00000400 +#define HFI_HEVC_LEVEL_61 0x00000800 +#define HFI_HEVC_LEVEL_62 0x00001000 + +#define HFI_HEVC_TIER_MAIN 0x1 +#define HFI_HEVC_TIER_HIGH0 0x2 + +#define HFI_VPX_PROFILE_MAIN 0x00000001 + +#define HFI_VPX_LEVEL_VERSION_0 0x00000001 +#define HFI_VPX_LEVEL_VERSION_1 0x00000002 +#define HFI_VPX_LEVEL_VERSION_2 0x00000004 +#define HFI_VPX_LEVEL_VERSION_3 0x00000008 + +/* VP9 Profile 0, 8-bit */ +#define HFI_VP9_PROFILE_P0 0x00000001 +/* VP9 Profile 2, 10-bit */ +#define HFI_VP9_PROFILE_P2_10B 0x00000004 + +#define HFI_VP9_LEVEL_1 0x00000001 +#define HFI_VP9_LEVEL_11 0x00000002 +#define HFI_VP9_LEVEL_2 0x00000004 +#define HFI_VP9_LEVEL_21 0x00000008 +#define HFI_VP9_LEVEL_3 0x00000010 +#define HFI_VP9_LEVEL_31 0x00000020 +#define HFI_VP9_LEVEL_4 0x00000040 +#define HFI_VP9_LEVEL_41 0x00000080 +#define HFI_VP9_LEVEL_5 0x00000100 +#define HFI_VP9_LEVEL_51 0x00000200 +#define HFI_VP9_LEVEL_6 0x00000400 +#define HFI_VP9_LEVEL_61 0x00000800 + +#define HFI_BUFFER_INPUT 0x1 +#define HFI_BUFFER_OUTPUT 0x2 +#define HFI_BUFFER_OUTPUT2 0x3 +#define HFI_BUFFER_INTERNAL_PERSIST 0x4 +#define HFI_BUFFER_INTERNAL_PERSIST_1 0x5 +#define HFI_BUFFER_INTERNAL_SCRATCH(ver) \ + (((ver) == HFI_VERSION_4XX || \ + (ver) == HFI_VERSION_6XX) ? 0x6 : 0x1000001) +#define HFI_BUFFER_INTERNAL_SCRATCH_1(ver) \ + (((ver) == HFI_VERSION_4XX || \ + (ver) == HFI_VERSION_6XX) ? 0x7 : 0x1000005) +#define HFI_BUFFER_INTERNAL_SCRATCH_2(ver) \ + (((ver) == HFI_VERSION_4XX || \ + (ver) == HFI_VERSION_6XX) ? 0x8 : 0x1000006) +#define HFI_BUFFER_EXTRADATA_INPUT(ver) \ + (((ver) == HFI_VERSION_4XX) ? 0xc : 0x1000002) +#define HFI_BUFFER_EXTRADATA_OUTPUT(ver) \ + (((ver) == HFI_VERSION_4XX) ? 0xa : 0x1000003) +#define HFI_BUFFER_EXTRADATA_OUTPUT2(ver) \ + (((ver) == HFI_VERSION_4XX) ? 0xb : 0x1000004) +#define HFI_BUFFER_TYPE_MAX 11 + +#define HFI_BUFFER_MODE_STATIC 0x1000001 +#define HFI_BUFFER_MODE_RING 0x1000002 +#define HFI_BUFFER_MODE_DYNAMIC 0x1000003 + +/* + * HFI_PROPERTY_SYS_COMMON_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_COMMON_OFFSET + 0x0000 + */ +#define HFI_PROPERTY_SYS_DEBUG_CONFIG 0x1 +#define HFI_PROPERTY_SYS_RESOURCE_OCMEM_REQUIREMENT_INFO 0x2 +#define HFI_PROPERTY_SYS_CONFIG_VCODEC_CLKFREQ 0x3 +#define HFI_PROPERTY_SYS_IDLE_INDICATOR 0x4 +#define HFI_PROPERTY_SYS_CODEC_POWER_PLANE_CTRL 0x5 +#define HFI_PROPERTY_SYS_IMAGE_VERSION 0x6 +#define HFI_PROPERTY_SYS_CONFIG_COVERAGE 0x7 +#define HFI_PROPERTY_SYS_UBWC_CONFIG 0x8 + +/* + * HFI_PROPERTY_PARAM_COMMON_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_COMMON_OFFSET + 0x1000 + */ +#define HFI_PROPERTY_PARAM_FRAME_SIZE 0x1001 +#define HFI_PROPERTY_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO 0x1002 +#define HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SELECT 0x1003 +#define HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED 0x1004 +#define HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT 0x1005 +#define HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED 0x1006 +#define HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED 0x1007 +#define HFI_PROPERTY_PARAM_PROPERTIES_SUPPORTED 0x1008 +#define HFI_PROPERTY_PARAM_CODEC_SUPPORTED 0x1009 +#define HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SUPPORTED 0x100a +#define HFI_PROPERTY_PARAM_NAL_STREAM_FORMAT_SELECT 0x100b +#define HFI_PROPERTY_PARAM_MULTI_VIEW_FORMAT 0x100c +#define HFI_PROPERTY_PARAM_MAX_SEQUENCE_HEADER_SIZE 0x100d +#define HFI_PROPERTY_PARAM_CODEC_MASK_SUPPORTED 0x100e +#define HFI_PROPERTY_PARAM_MVC_BUFFER_LAYOUT 0x100f +#define HFI_PROPERTY_PARAM_MAX_SESSIONS_SUPPORTED 0x1010 +#define HFI_PROPERTY_PARAM_WORK_MODE 0x1015 +#define HFI_PROPERTY_PARAM_WORK_ROUTE 0x1017 + +/* + * HFI_PROPERTY_CONFIG_COMMON_START + * HFI_DOMAIN_BASE_COMMON + HFI_ARCH_COMMON_OFFSET + 0x2000 + */ +#define HFI_PROPERTY_CONFIG_FRAME_RATE 0x2001 +#define HFI_PROPERTY_CONFIG_VIDEOCORES_USAGE 0x2002 + +/* + * HFI_PROPERTY_PARAM_VDEC_COMMON_START + * HFI_DOMAIN_BASE_VDEC + HFI_ARCH_COMMON_OFFSET + 0x3000 + */ +#define HFI_PROPERTY_PARAM_VDEC_MULTI_STREAM 0x1003001 +#define HFI_PROPERTY_PARAM_VDEC_CONCEAL_COLOR 0x1003002 +#define HFI_PROPERTY_PARAM_VDEC_NONCP_OUTPUT2 0x1003003 +#define HFI_PROPERTY_PARAM_VDEC_PIXEL_BITDEPTH 0x1003007 +#define HFI_PROPERTY_PARAM_VDEC_PIC_STRUCT 0x1003009 +#define HFI_PROPERTY_PARAM_VDEC_COLOUR_SPACE 0x100300a + +/* + * HFI_PROPERTY_CONFIG_VDEC_COMMON_START + * HFI_DOMAIN_BASE_VDEC + HFI_ARCH_COMMON_OFFSET + 0x4000 + */ + +/* + * HFI_PROPERTY_PARAM_VENC_COMMON_START + * HFI_DOMAIN_BASE_VENC + HFI_ARCH_COMMON_OFFSET + 0x5000 + */ +#define HFI_PROPERTY_PARAM_VENC_SLICE_DELIVERY_MODE 0x2005001 +#define HFI_PROPERTY_PARAM_VENC_H264_ENTROPY_CONTROL 0x2005002 +#define HFI_PROPERTY_PARAM_VENC_H264_DEBLOCK_CONTROL 0x2005003 +#define HFI_PROPERTY_PARAM_VENC_RATE_CONTROL 0x2005004 +#define HFI_PROPERTY_PARAM_VENC_H264_PICORDER_CNT_TYPE 0x2005005 +#define HFI_PROPERTY_PARAM_VENC_SESSION_QP 0x2005006 +#define HFI_PROPERTY_PARAM_VENC_MPEG4_AC_PREDICTION 0x2005007 +#define HFI_PROPERTY_PARAM_VENC_SESSION_QP_RANGE 0x2005008 +#define HFI_PROPERTY_PARAM_VENC_MPEG4_TIME_RESOLUTION 0x2005009 +#define HFI_PROPERTY_PARAM_VENC_MPEG4_SHORT_HEADER 0x200500a +#define HFI_PROPERTY_PARAM_VENC_MPEG4_HEADER_EXTENSION 0x200500b +#define HFI_PROPERTY_PARAM_VENC_OPEN_GOP 0x200500c +#define HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH 0x200500d +#define HFI_PROPERTY_PARAM_VENC_MULTI_SLICE_CONTROL 0x200500e +#define HFI_PROPERTY_PARAM_VENC_VBV_HRD_BUF_SIZE 0x200500f +#define HFI_PROPERTY_PARAM_VENC_QUALITY_VS_SPEED 0x2005010 +#define HFI_PROPERTY_PARAM_VENC_ADVANCED 0x2005012 +#define HFI_PROPERTY_PARAM_VENC_H264_SPS_ID 0x2005014 +#define HFI_PROPERTY_PARAM_VENC_H264_PPS_ID 0x2005015 +#define HFI_PROPERTY_PARAM_VENC_H264_GENERATE_AUDNAL 0x2005016 +#define HFI_PROPERTY_PARAM_VENC_ASPECT_RATIO 0x2005017 +#define HFI_PROPERTY_PARAM_VENC_NUMREF 0x2005018 +#define HFI_PROPERTY_PARAM_VENC_MULTIREF_P 0x2005019 +#define HFI_PROPERTY_PARAM_VENC_H264_NAL_SVC_EXT 0x200501b +#define HFI_PROPERTY_PARAM_VENC_LTRMODE 0x200501c +#define HFI_PROPERTY_PARAM_VENC_VIDEO_FULL_RANGE 0x200501d +#define HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO 0x200501e +#define HFI_PROPERTY_PARAM_VENC_VC1_PERF_CFG 0x200501f +#define HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES 0x2005020 +#define HFI_PROPERTY_PARAM_VENC_H264_VUI_BITSTREAM_RESTRC 0x2005021 +#define HFI_PROPERTY_PARAM_VENC_PRESERVE_TEXT_QUALITY 0x2005023 +#define HFI_PROPERTY_PARAM_VENC_H264_TRANSFORM_8X8 0x2005025 +#define HFI_PROPERTY_PARAM_VENC_HIER_P_MAX_NUM_ENH_LAYER 0x2005026 +#define HFI_PROPERTY_PARAM_VENC_DISABLE_RC_TIMESTAMP 0x2005027 +#define HFI_PROPERTY_PARAM_VENC_INITIAL_QP 0x2005028 +#define HFI_PROPERTY_PARAM_VENC_VPX_ERROR_RESILIENCE_MODE 0x2005029 +#define HFI_PROPERTY_PARAM_VENC_HIER_B_MAX_NUM_ENH_LAYER 0x200502c +#define HFI_PROPERTY_PARAM_VENC_HIER_P_HYBRID_MODE 0x200502f +#define HFI_PROPERTY_PARAM_VENC_HDR10_PQ_SEI 0x2005036 + +/* + * HFI_PROPERTY_CONFIG_VENC_COMMON_START + * HFI_DOMAIN_BASE_VENC + HFI_ARCH_COMMON_OFFSET + 0x6000 + */ +#define HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE 0x2006001 +#define HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD 0x2006002 +#define HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD 0x2006003 +#define HFI_PROPERTY_CONFIG_VENC_REQUEST_SYNC_FRAME 0x2006004 +#define HFI_PROPERTY_CONFIG_VENC_SLICE_SIZE 0x2006005 +#define HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE 0x2006007 +#define HFI_PROPERTY_CONFIG_VENC_SYNC_FRAME_SEQUENCE_HEADER 0x2006008 +#define HFI_PROPERTY_CONFIG_VENC_MARKLTRFRAME 0x2006009 +#define HFI_PROPERTY_CONFIG_VENC_USELTRFRAME 0x200600a +#define HFI_PROPERTY_CONFIG_VENC_HIER_P_ENH_LAYER 0x200600b +#define HFI_PROPERTY_CONFIG_VENC_LTRPERIOD 0x200600c +#define HFI_PROPERTY_CONFIG_VENC_PERF_MODE 0x200600e +#define HFI_PROPERTY_CONFIG_HEIC_FRAME_QUALITY 0x2006014 + +/* + * HFI_PROPERTY_PARAM_VPE_COMMON_START + * HFI_DOMAIN_BASE_VPE + HFI_ARCH_COMMON_OFFSET + 0x7000 + */ + +/* + * HFI_PROPERTY_CONFIG_VPE_COMMON_START + * HFI_DOMAIN_BASE_VPE + HFI_ARCH_COMMON_OFFSET + 0x8000 + */ +#define HFI_PROPERTY_CONFIG_VPE_DEINTERLACE 0x3008001 +#define HFI_PROPERTY_CONFIG_VPE_OPERATIONS 0x3008002 + +enum hfi_version { + HFI_VERSION_1XX, + HFI_VERSION_3XX, + HFI_VERSION_4XX, + HFI_VERSION_6XX, +}; + +struct hfi_buffer_info { + u32 buffer_addr; + u32 extradata_addr; +}; + +struct hfi_bitrate { + u32 bitrate; + u32 layer_id; +}; + +struct hfi_h264_8x8_transform { + u32 enable_type; +}; + +#define HFI_CAPABILITY_FRAME_WIDTH 0x01 +#define HFI_CAPABILITY_FRAME_HEIGHT 0x02 +#define HFI_CAPABILITY_MBS_PER_FRAME 0x03 +#define HFI_CAPABILITY_MBS_PER_SECOND 0x04 +#define HFI_CAPABILITY_FRAMERATE 0x05 +#define HFI_CAPABILITY_SCALE_X 0x06 +#define HFI_CAPABILITY_SCALE_Y 0x07 +#define HFI_CAPABILITY_BITRATE 0x08 +#define HFI_CAPABILITY_BFRAME 0x09 +#define HFI_CAPABILITY_PEAKBITRATE 0x0a +#define HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS 0x10 +#define HFI_CAPABILITY_ENC_LTR_COUNT 0x11 +#define HFI_CAPABILITY_CP_OUTPUT2_THRESH 0x12 +#define HFI_CAPABILITY_HIER_B_NUM_ENH_LAYERS 0x13 +#define HFI_CAPABILITY_LCU_SIZE 0x14 +#define HFI_CAPABILITY_HIER_P_HYBRID_NUM_ENH_LAYERS 0x15 +#define HFI_CAPABILITY_MBS_PER_SECOND_POWERSAVE 0x16 +#define HFI_CAPABILITY_I_FRAME_QP 0x20 +#define HFI_CAPABILITY_P_FRAME_QP 0x21 +#define HFI_CAPABILITY_B_FRAME_QP 0x22 +#define HFI_CAPABILITY_RATE_CONTROL_MODES 0x23 +#define HFI_CAPABILITY_BLUR_WIDTH 0x24 +#define HFI_CAPABILITY_BLUR_HEIGHT 0x25 +#define HFI_CAPABILITY_SLICE_BYTE 0x27 +#define HFI_CAPABILITY_SLICE_MB 0x28 +#define HFI_CAPABILITY_MAX_VIDEOCORES 0x2b +#define HFI_CAPABILITY_MAX_WORKMODES 0x2c +#define HFI_CAPABILITY_ROTATION 0x2f +#define HFI_CAPABILITY_COLOR_SPACE_CONVERSION 0x30 + +struct hfi_capability { + u32 capability_type; + u32 min; + u32 max; + u32 step_size; +}; + +struct hfi_capabilities { + u32 num_capabilities; + struct hfi_capability data[]; +}; + +#define HFI_DEBUG_MSG_LOW 0x01 +#define HFI_DEBUG_MSG_MEDIUM 0x02 +#define HFI_DEBUG_MSG_HIGH 0x04 +#define HFI_DEBUG_MSG_ERROR 0x08 +#define HFI_DEBUG_MSG_FATAL 0x10 +#define HFI_DEBUG_MSG_PERF 0x20 + +#define HFI_DEBUG_MODE_QUEUE 0x01 +#define HFI_DEBUG_MODE_QDSS 0x02 + +struct hfi_debug_config { + u32 config; + u32 mode; +}; + +struct hfi_ubwc_config { + u32 size; + u32 packet_type; + struct { + u32 max_channel_override : 1; + u32 mal_length_override : 1; + u32 hb_override : 1; + u32 bank_swzl_level_override : 1; + u32 bank_spreading_override : 1; + u32 reserved : 27; + } override_bit_info; + u32 max_channels; + u32 mal_length; + u32 highest_bank_bit; + u32 bank_swzl_level; + u32 bank_spreading; + u32 reserved[2]; +}; + +struct hfi_enable { + u32 enable; +}; + +#define HFI_H264_DB_MODE_DISABLE 0x1 +#define HFI_H264_DB_MODE_SKIP_SLICE_BOUNDARY 0x2 +#define HFI_H264_DB_MODE_ALL_BOUNDARY 0x3 + +struct hfi_h264_db_control { + u32 mode; + s32 slice_alpha_offset; + s32 slice_beta_offset; +}; + +#define HFI_H264_ENTROPY_CAVLC 0x1 +#define HFI_H264_ENTROPY_CABAC 0x2 + +#define HFI_H264_CABAC_MODEL_0 0x1 +#define HFI_H264_CABAC_MODEL_1 0x2 +#define HFI_H264_CABAC_MODEL_2 0x3 + +struct hfi_h264_entropy_control { + u32 entropy_mode; + u32 cabac_model; +}; + +struct hfi_framerate { + u32 buffer_type; + u32 framerate; +}; + +#define HFI_INTRA_REFRESH_NONE 0x1 +#define HFI_INTRA_REFRESH_CYCLIC 0x2 +#define HFI_INTRA_REFRESH_ADAPTIVE 0x3 +#define HFI_INTRA_REFRESH_CYCLIC_ADAPTIVE 0x4 +#define HFI_INTRA_REFRESH_RANDOM 0x5 + +struct hfi_intra_refresh { + u32 mode; + u32 air_mbs; + u32 air_ref; + u32 cir_mbs; +}; + +struct hfi_intra_refresh_3x { + u32 mode; + u32 mbs; +}; + +struct hfi_idr_period { + u32 idr_period; +}; + +struct hfi_operations_type { + u32 rotation; + u32 flip; +}; + +struct hfi_max_num_b_frames { + u32 max_num_b_frames; +}; + +struct hfi_vc1e_perf_cfg_type { + u32 search_range_x_subsampled[3]; + u32 search_range_y_subsampled[3]; +}; + +/* + * 0 - 7bit -> Luma (def: 16) + * 8 - 15bit -> Chroma (def: 128) + * format is valid up to v4 + */ +struct hfi_conceal_color { + u32 conceal_color; +}; + +struct hfi_conceal_color_v4 { + u32 conceal_color_8bit; + u32 conceal_color_10bit; +}; + +struct hfi_intra_period { + u32 pframes; + u32 bframes; +}; + +struct hfi_mpeg4_header_extension { + u32 header_extension; +}; + +struct hfi_mpeg4_time_resolution { + u32 time_increment_resolution; +}; + +struct hfi_multi_stream { + u32 buffer_type; + u32 enable; + u32 width; + u32 height; +}; + +struct hfi_multi_stream_3x { + u32 buffer_type; + u32 enable; +}; + +struct hfi_multi_view_format { + u32 views; + u32 view_order[1]; +}; + +#define HFI_MULTI_SLICE_OFF 0x1 +#define HFI_MULTI_SLICE_BY_MB_COUNT 0x2 +#define HFI_MULTI_SLICE_BY_BYTE_COUNT 0x3 +#define HFI_MULTI_SLICE_GOB 0x4 + +struct hfi_multi_slice_control { + u32 multi_slice; + u32 slice_size; +}; + +#define HFI_NAL_FORMAT_STARTCODES 0x01 +#define HFI_NAL_FORMAT_ONE_NAL_PER_BUFFER 0x02 +#define HFI_NAL_FORMAT_ONE_BYTE_LENGTH 0x04 +#define HFI_NAL_FORMAT_TWO_BYTE_LENGTH 0x08 +#define HFI_NAL_FORMAT_FOUR_BYTE_LENGTH 0x10 + +struct hfi_nal_stream_format { + u32 format; +}; + +struct hfi_nal_stream_format_select { + u32 format; +}; + +#define HFI_PICTURE_TYPE_I 0x01 +#define HFI_PICTURE_TYPE_P 0x02 +#define HFI_PICTURE_TYPE_B 0x04 +#define HFI_PICTURE_TYPE_IDR 0x08 + +struct hfi_profile_level { + u32 profile; + u32 level; +}; + +#define HFI_MAX_PROFILE_COUNT 16 + +struct hfi_profile_level_supported { + u32 profile_count; + struct hfi_profile_level profile_level[]; +}; + +struct hfi_quality_vs_speed { + u32 quality_vs_speed; +}; + +struct hfi_heic_frame_quality { + u32 frame_quality; + u32 reserved[3]; +}; + +struct hfi_quantization { + u32 qp_i; + u32 qp_p; + u32 qp_b; + u32 layer_id; +}; + +struct hfi_initial_quantization { + u32 qp_i; + u32 qp_p; + u32 qp_b; + u32 init_qp_enable; +}; + +struct hfi_quantization_range { + u32 min_qp; + u32 max_qp; + u32 layer_id; +}; + +#define HFI_LTR_MODE_DISABLE 0x0 +#define HFI_LTR_MODE_MANUAL 0x1 +#define HFI_LTR_MODE_PERIODIC 0x2 + +struct hfi_ltr_mode { + u32 ltr_mode; + u32 ltr_count; + u32 trust_mode; +}; + +struct hfi_ltr_use { + u32 ref_ltr; + u32 use_constrnt; + u32 frames; +}; + +struct hfi_ltr_mark { + u32 mark_frame; +}; + +struct hfi_mastering_display_colour_sei_payload { + u32 display_primaries_x[3]; + u32 display_primaries_y[3]; + u32 white_point_x; + u32 white_point_y; + u32 max_display_mastering_luminance; + u32 min_display_mastering_luminance; +}; + +struct hfi_content_light_level_sei_payload { + u32 max_content_light; + u32 max_pic_average_light; +}; + +struct hfi_hdr10_pq_sei { + struct hfi_mastering_display_colour_sei_payload mastering; + struct hfi_content_light_level_sei_payload cll; +}; + +struct hfi_framesize { + u32 buffer_type; + u32 width; + u32 height; +}; + +#define HFI_VENC_PERFMODE_MAX_QUALITY 0x1 +#define HFI_VENC_PERFMODE_POWER_SAVE 0x2 + +struct hfi_perf_mode { + u32 video_perf_mode; +}; + +#define VIDC_CORE_ID_DEFAULT 0 +#define VIDC_CORE_ID_1 1 +#define VIDC_CORE_ID_2 2 +#define VIDC_CORE_ID_3 3 + +struct hfi_videocores_usage_type { + u32 video_core_enable_mask; +}; + +#define VIDC_WORK_MODE_1 1 +#define VIDC_WORK_MODE_2 2 + +struct hfi_video_work_mode { + u32 video_work_mode; +}; + +struct hfi_video_work_route { + u32 video_work_route; +}; + +struct hfi_h264_vui_timing_info { + u32 enable; + u32 fixed_framerate; + u32 time_scale; +}; + +#define VIDC_BITDEPTH_8 0x00000 +#define VIDC_BITDEPTH_10 0x20002 + +struct hfi_bit_depth { + u32 buffer_type; + u32 bit_depth; +}; + +struct hfi_picture_type { + u32 is_sync_frame; + u32 picture_type; +}; + +struct hfi_pic_struct { + u32 progressive_only; +}; + +struct hfi_colour_space { + u32 colour_space; +}; + +struct hfi_extradata_input_crop { + u32 size; + u32 version; + u32 port_index; + u32 left; + u32 top; + u32 width; + u32 height; +}; + +struct hfi_dpb_counts { + u32 max_dpb_count; + u32 max_ref_frames; + u32 max_dec_buffering; + u32 max_reorder_frames; + u32 fw_min_cnt; +}; + +#define HFI_COLOR_FORMAT_MONOCHROME 0x01 +#define HFI_COLOR_FORMAT_NV12 0x02 +#define HFI_COLOR_FORMAT_NV21 0x03 +#define HFI_COLOR_FORMAT_NV12_4x4TILE 0x04 +#define HFI_COLOR_FORMAT_NV21_4x4TILE 0x05 +#define HFI_COLOR_FORMAT_YUYV 0x06 +#define HFI_COLOR_FORMAT_YVYU 0x07 +#define HFI_COLOR_FORMAT_UYVY 0x08 +#define HFI_COLOR_FORMAT_VYUY 0x09 +#define HFI_COLOR_FORMAT_RGB565 0x0a +#define HFI_COLOR_FORMAT_BGR565 0x0b +#define HFI_COLOR_FORMAT_RGB888 0x0c +#define HFI_COLOR_FORMAT_BGR888 0x0d +#define HFI_COLOR_FORMAT_YUV444 0x0e +#define HFI_COLOR_FORMAT_RGBA8888 0x10 + +#define HFI_COLOR_FORMAT_UBWC_BASE 0x8000 +#define HFI_COLOR_FORMAT_10_BIT_BASE 0x4000 + +#define HFI_COLOR_FORMAT_YUV420_TP10 0x4002 +#define HFI_COLOR_FORMAT_P010 0x4003 +#define HFI_COLOR_FORMAT_NV12_UBWC 0x8002 +#define HFI_COLOR_FORMAT_YUV420_TP10_UBWC 0xc002 +#define HFI_COLOR_FORMAT_P010_UBWC 0xc003 +#define HFI_COLOR_FORMAT_RGBA8888_UBWC 0x8010 + +struct hfi_uncompressed_format_select { + u32 buffer_type; + u32 format; +}; + +struct hfi_uncompressed_plane_constraints { + u32 stride_multiples; + u32 max_stride; + u32 min_plane_buffer_height_multiple; + u32 buffer_alignment; +}; + +struct hfi_uncompressed_plane_info { + u32 format; + u32 num_planes; + struct hfi_uncompressed_plane_constraints plane_constraints[1]; +}; + +struct hfi_uncompressed_format_supported { + u32 buffer_type; + u32 format_entries; + struct hfi_uncompressed_plane_info plane_info[1]; +}; + +struct hfi_uncompressed_plane_actual { + int actual_stride; + u32 actual_plane_buffer_height; +}; + +struct hfi_uncompressed_plane_actual_info { + u32 buffer_type; + u32 num_planes; + struct hfi_uncompressed_plane_actual plane_format[2]; +}; + +struct hfi_uncompressed_plane_actual_constraints_info { + u32 buffer_type; + u32 num_planes; + struct hfi_uncompressed_plane_constraints plane_format[2]; +}; + +struct hfi_codec_supported { + u32 dec_codecs; + u32 enc_codecs; +}; + +struct hfi_properties_supported { + u32 num_properties; + u32 properties[1]; +}; + +struct hfi_max_sessions_supported { + u32 max_sessions; +}; + +#define HFI_MAX_MATRIX_COEFFS 9 +#define HFI_MAX_BIAS_COEFFS 3 +#define HFI_MAX_LIMIT_COEFFS 6 + +struct hfi_vpe_color_space_conversion { + u32 csc_matrix[HFI_MAX_MATRIX_COEFFS]; + u32 csc_bias[HFI_MAX_BIAS_COEFFS]; + u32 csc_limit[HFI_MAX_LIMIT_COEFFS]; +}; + +#define HFI_ROTATE_NONE 0x1 +#define HFI_ROTATE_90 0x2 +#define HFI_ROTATE_180 0x3 +#define HFI_ROTATE_270 0x4 + +#define HFI_FLIP_NONE 0x1 +#define HFI_FLIP_HORIZONTAL 0x2 +#define HFI_FLIP_VERTICAL 0x3 + +struct hfi_operations { + u32 rotate; + u32 flip; +}; + +#define HFI_RESOURCE_OCMEM 0x1 + +struct hfi_resource_ocmem { + u32 size; + u32 mem; +}; + +struct hfi_resource_ocmem_requirement { + u32 session_domain; + u32 width; + u32 height; + u32 size; +}; + +struct hfi_resource_ocmem_requirement_info { + u32 num_entries; + struct hfi_resource_ocmem_requirement requirements[1]; +}; + +struct hfi_property_sys_image_version_info_type { + u32 string_size; + u8 str_image_version[1]; +}; + +struct hfi_codec_mask_supported { + u32 codecs; + u32 video_domains; +}; + +struct hfi_seq_header_info { + u32 max_hader_len; +}; + +struct hfi_aspect_ratio { + u32 aspect_width; + u32 aspect_height; +}; + +#define HFI_MVC_BUFFER_LAYOUT_TOP_BOTTOM 0 +#define HFI_MVC_BUFFER_LAYOUT_SIDEBYSIDE 1 +#define HFI_MVC_BUFFER_LAYOUT_SEQ 2 + +struct hfi_mvc_buffer_layout_descp_type { + u32 layout_type; + u32 bright_view_first; + u32 ngap; +}; + +struct hfi_scs_threshold { + u32 threshold_value; +}; + +#define HFI_TEST_SSR_SW_ERR_FATAL 0x1 +#define HFI_TEST_SSR_SW_DIV_BY_ZERO 0x2 +#define HFI_TEST_SSR_HW_WDOG_IRQ 0x3 + +struct hfi_buffer_alloc_mode { + u32 type; + u32 mode; +}; + +struct hfi_index_extradata_config { + u32 enable; + u32 index_extra_data_id; +}; + +struct hfi_extradata_header { + u32 size; + u32 version; + u32 port_index; + u32 type; + u32 data_size; + u8 data[1]; +}; + +struct hfi_batch_info { + u32 input_batch_count; + u32 output_batch_count; +}; + +struct hfi_buffer_count_actual { + u32 type; + u32 count_actual; +}; + +struct hfi_buffer_count_actual_4xx { + u32 type; + u32 count_actual; + u32 count_min_host; +}; + +struct hfi_buffer_size_actual { + u32 type; + u32 size; +}; + +struct hfi_buffer_display_hold_count_actual { + u32 type; + u32 hold_count; +}; + +/* HFI 4XX reorder the fields, use these macros */ +#define HFI_BUFREQ_HOLD_COUNT(bufreq, ver) \ + ((ver) == HFI_VERSION_4XX ? 0 : (bufreq)->hold_count) +#define HFI_BUFREQ_COUNT_MIN(bufreq, ver) \ + ((ver) == HFI_VERSION_4XX ? (bufreq)->hold_count : (bufreq)->count_min) +#define HFI_BUFREQ_COUNT_MIN_HOST(bufreq, ver) \ + ((ver) == HFI_VERSION_4XX ? (bufreq)->count_min : 0) + +struct hfi_buffer_requirements { + u32 type; + u32 size; + u32 region_size; + u32 hold_count; + u32 count_min; + u32 count_actual; + u32 contiguous; + u32 alignment; +}; + +struct hfi_data_payload { + u32 size; + u8 data[1]; +}; + +struct hfi_enable_picture { + u32 picture_type; +}; + +struct hfi_display_picture_buffer_count { + int enable; + u32 count; +}; + +struct hfi_extra_data_header_config { + u32 type; + u32 buffer_type; + u32 version; + u32 port_index; + u32 client_extra_data_id; +}; + +struct hfi_interlace_format_supported { + u32 buffer_type; + u32 format; +}; + +struct hfi_buffer_alloc_mode_supported { + u32 buffer_type; + u32 num_entries; + u32 data[1]; +}; + +struct hfi_mb_error_map { + u32 error_map_size; + u8 error_map[1]; +}; + +struct hfi_metadata_pass_through { + int enable; + u32 size; +}; + +struct hfi_multi_view_select { + u32 view_index; +}; + +struct hfi_hybrid_hierp { + u32 layers; +}; + +struct hfi_pkt_hdr { + u32 size; + u32 pkt_type; +}; + +struct hfi_session_hdr_pkt { + struct hfi_pkt_hdr hdr; + u32 session_id; +}; + +struct hfi_session_pkt { + struct hfi_session_hdr_pkt shdr; +}; + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_msgs.c b/drivers/media/platform/qcom/venus/hfi_msgs.c new file mode 100644 index 000000000..1c5cc5a5f --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_msgs.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include +#include +#include +#include + +#include "core.h" +#include "hfi.h" +#include "hfi_helper.h" +#include "hfi_msgs.h" +#include "hfi_parser.h" + +#define SMEM_IMG_VER_TBL 469 +#define VER_STR_SZ 128 +#define SMEM_IMG_OFFSET_VENUS (14 * 128) + +static void event_seq_changed(struct venus_core *core, struct venus_inst *inst, + struct hfi_msg_event_notify_pkt *pkt) +{ + enum hfi_version ver = core->res->hfi_version; + struct hfi_event_data event = {0}; + int num_properties_changed; + struct hfi_framesize *frame_sz; + struct hfi_profile_level *profile_level; + struct hfi_bit_depth *pixel_depth; + struct hfi_pic_struct *pic_struct; + struct hfi_colour_space *colour_info; + struct hfi_buffer_requirements *bufreq; + struct hfi_extradata_input_crop *crop; + struct hfi_dpb_counts *dpb_count; + u8 *data_ptr; + u32 ptype; + + inst->error = HFI_ERR_NONE; + + switch (pkt->event_data1) { + case HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES: + case HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES: + break; + default: + inst->error = HFI_ERR_SESSION_INVALID_PARAMETER; + goto done; + } + + event.event_type = pkt->event_data1; + + num_properties_changed = pkt->event_data2; + if (!num_properties_changed) { + inst->error = HFI_ERR_SESSION_INSUFFICIENT_RESOURCES; + goto done; + } + + data_ptr = (u8 *)&pkt->ext_event_data[0]; + do { + ptype = *((u32 *)data_ptr); + switch (ptype) { + case HFI_PROPERTY_PARAM_FRAME_SIZE: + data_ptr += sizeof(u32); + frame_sz = (struct hfi_framesize *)data_ptr; + event.width = frame_sz->width; + event.height = frame_sz->height; + data_ptr += sizeof(*frame_sz); + break; + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: + data_ptr += sizeof(u32); + profile_level = (struct hfi_profile_level *)data_ptr; + event.profile = profile_level->profile; + event.level = profile_level->level; + data_ptr += sizeof(*profile_level); + break; + case HFI_PROPERTY_PARAM_VDEC_PIXEL_BITDEPTH: + data_ptr += sizeof(u32); + pixel_depth = (struct hfi_bit_depth *)data_ptr; + event.bit_depth = pixel_depth->bit_depth; + data_ptr += sizeof(*pixel_depth); + break; + case HFI_PROPERTY_PARAM_VDEC_PIC_STRUCT: + data_ptr += sizeof(u32); + pic_struct = (struct hfi_pic_struct *)data_ptr; + event.pic_struct = pic_struct->progressive_only; + data_ptr += sizeof(*pic_struct); + break; + case HFI_PROPERTY_PARAM_VDEC_COLOUR_SPACE: + data_ptr += sizeof(u32); + colour_info = (struct hfi_colour_space *)data_ptr; + event.colour_space = colour_info->colour_space; + data_ptr += sizeof(*colour_info); + break; + case HFI_PROPERTY_CONFIG_VDEC_ENTROPY: + data_ptr += sizeof(u32); + event.entropy_mode = *(u32 *)data_ptr; + data_ptr += sizeof(u32); + break; + case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: + data_ptr += sizeof(u32); + bufreq = (struct hfi_buffer_requirements *)data_ptr; + event.buf_count = HFI_BUFREQ_COUNT_MIN(bufreq, ver); + data_ptr += sizeof(*bufreq); + break; + case HFI_INDEX_EXTRADATA_INPUT_CROP: + data_ptr += sizeof(u32); + crop = (struct hfi_extradata_input_crop *)data_ptr; + event.input_crop.left = crop->left; + event.input_crop.top = crop->top; + event.input_crop.width = crop->width; + event.input_crop.height = crop->height; + data_ptr += sizeof(*crop); + break; + case HFI_PROPERTY_PARAM_VDEC_DPB_COUNTS: + data_ptr += sizeof(u32); + dpb_count = (struct hfi_dpb_counts *)data_ptr; + event.buf_count = dpb_count->fw_min_cnt; + data_ptr += sizeof(*dpb_count); + break; + default: + break; + } + num_properties_changed--; + } while (num_properties_changed > 0); + +done: + inst->ops->event_notify(inst, EVT_SYS_EVENT_CHANGE, &event); +} + +static void event_release_buffer_ref(struct venus_core *core, + struct venus_inst *inst, + struct hfi_msg_event_notify_pkt *pkt) +{ + struct hfi_event_data event = {0}; + struct hfi_msg_event_release_buffer_ref_pkt *data; + + data = (struct hfi_msg_event_release_buffer_ref_pkt *) + pkt->ext_event_data; + + event.event_type = HFI_EVENT_RELEASE_BUFFER_REFERENCE; + event.packet_buffer = data->packet_buffer; + event.extradata_buffer = data->extradata_buffer; + event.tag = data->output_tag; + + inst->error = HFI_ERR_NONE; + inst->ops->event_notify(inst, EVT_SYS_EVENT_CHANGE, &event); +} + +static void event_sys_error(struct venus_core *core, u32 event, + struct hfi_msg_event_notify_pkt *pkt) +{ + if (pkt) + dev_dbg(core->dev, VDBGH + "sys error (session id:%x, data1:%x, data2:%x)\n", + pkt->shdr.session_id, pkt->event_data1, + pkt->event_data2); + + core->core_ops->event_notify(core, event); +} + +static void +event_session_error(struct venus_core *core, struct venus_inst *inst, + struct hfi_msg_event_notify_pkt *pkt) +{ + struct device *dev = core->dev; + + dev_dbg(dev, VDBGH "session error: event id:%x, session id:%x\n", + pkt->event_data1, pkt->shdr.session_id); + + if (!inst) + return; + + switch (pkt->event_data1) { + /* non fatal session errors */ + case HFI_ERR_SESSION_INVALID_SCALE_FACTOR: + case HFI_ERR_SESSION_UNSUPPORT_BUFFERTYPE: + case HFI_ERR_SESSION_UNSUPPORTED_SETTING: + case HFI_ERR_SESSION_UPSCALE_NOT_SUPPORTED: + inst->error = HFI_ERR_NONE; + break; + default: + dev_err(dev, "session error: event id:%x (%x), session id:%x\n", + pkt->event_data1, pkt->event_data2, + pkt->shdr.session_id); + + inst->error = pkt->event_data1; + inst->ops->event_notify(inst, EVT_SESSION_ERROR, NULL); + break; + } +} + +static void hfi_event_notify(struct venus_core *core, struct venus_inst *inst, + void *packet) +{ + struct hfi_msg_event_notify_pkt *pkt = packet; + + if (!packet) + return; + + switch (pkt->event_id) { + case HFI_EVENT_SYS_ERROR: + event_sys_error(core, EVT_SYS_ERROR, pkt); + break; + case HFI_EVENT_SESSION_ERROR: + event_session_error(core, inst, pkt); + break; + case HFI_EVENT_SESSION_SEQUENCE_CHANGED: + event_seq_changed(core, inst, pkt); + break; + case HFI_EVENT_RELEASE_BUFFER_REFERENCE: + event_release_buffer_ref(core, inst, pkt); + break; + case HFI_EVENT_SESSION_PROPERTY_CHANGED: + break; + default: + break; + } +} + +static void hfi_sys_init_done(struct venus_core *core, struct venus_inst *inst, + void *packet) +{ + struct hfi_msg_sys_init_done_pkt *pkt = packet; + int rem_bytes; + u32 error; + + error = pkt->error_type; + if (error != HFI_ERR_NONE) + goto done; + + if (!pkt->num_properties) { + error = HFI_ERR_SYS_INVALID_PARAMETER; + goto done; + } + + rem_bytes = pkt->hdr.size - sizeof(*pkt) + sizeof(u32); + if (rem_bytes <= 0) { + /* missing property data */ + error = HFI_ERR_SYS_INSUFFICIENT_RESOURCES; + goto done; + } + + error = hfi_parser(core, inst, pkt->data, rem_bytes); + +done: + core->error = error; + complete(&core->done); +} + +static void +sys_get_prop_image_version(struct device *dev, + struct hfi_msg_sys_property_info_pkt *pkt) +{ + u8 *smem_tbl_ptr; + u8 *img_ver; + int req_bytes; + size_t smem_blk_sz; + + req_bytes = pkt->hdr.size - sizeof(*pkt); + + if (req_bytes < VER_STR_SZ || !pkt->data[0] || pkt->num_properties > 1) + /* bad packet */ + return; + + img_ver = pkt->data; + + dev_dbg(dev, VDBGL "F/W version: %s\n", img_ver); + + smem_tbl_ptr = qcom_smem_get(QCOM_SMEM_HOST_ANY, + SMEM_IMG_VER_TBL, &smem_blk_sz); + if (!IS_ERR(smem_tbl_ptr) && smem_blk_sz >= SMEM_IMG_OFFSET_VENUS + VER_STR_SZ) + memcpy(smem_tbl_ptr + SMEM_IMG_OFFSET_VENUS, + img_ver, VER_STR_SZ); +} + +static void hfi_sys_property_info(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_sys_property_info_pkt *pkt = packet; + struct device *dev = core->dev; + + if (!pkt->num_properties) { + dev_dbg(dev, VDBGL "no properties\n"); + return; + } + + switch (pkt->property) { + case HFI_PROPERTY_SYS_IMAGE_VERSION: + sys_get_prop_image_version(dev, pkt); + break; + default: + dev_dbg(dev, VDBGL "unknown property data\n"); + break; + } +} + +static void hfi_sys_rel_resource_done(struct venus_core *core, + struct venus_inst *inst, + void *packet) +{ + struct hfi_msg_sys_release_resource_done_pkt *pkt = packet; + + core->error = pkt->error_type; + complete(&core->done); +} + +static void hfi_sys_ping_done(struct venus_core *core, struct venus_inst *inst, + void *packet) +{ + struct hfi_msg_sys_ping_ack_pkt *pkt = packet; + + core->error = HFI_ERR_NONE; + + if (pkt->client_data != 0xbeef) + core->error = HFI_ERR_SYS_FATAL; + + complete(&core->done); +} + +static void hfi_sys_idle_done(struct venus_core *core, struct venus_inst *inst, + void *packet) +{ + dev_dbg(core->dev, VDBGL "sys idle\n"); +} + +static void hfi_sys_pc_prepare_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_sys_pc_prep_done_pkt *pkt = packet; + + dev_dbg(core->dev, VDBGL "pc prepare done (error %x)\n", + pkt->error_type); +} + +static unsigned int +session_get_prop_profile_level(struct hfi_msg_session_property_info_pkt *pkt, + struct hfi_profile_level *profile_level) +{ + struct hfi_profile_level *hfi; + u32 req_bytes; + + req_bytes = pkt->shdr.hdr.size - sizeof(*pkt); + + if (!req_bytes || req_bytes % sizeof(struct hfi_profile_level)) + /* bad packet */ + return HFI_ERR_SESSION_INVALID_PARAMETER; + + hfi = (struct hfi_profile_level *)&pkt->data[0]; + profile_level->profile = hfi->profile; + profile_level->level = hfi->level; + + return HFI_ERR_NONE; +} + +static unsigned int +session_get_prop_buf_req(struct hfi_msg_session_property_info_pkt *pkt, + struct hfi_buffer_requirements *bufreq) +{ + struct hfi_buffer_requirements *buf_req; + u32 req_bytes; + unsigned int idx = 0; + + req_bytes = pkt->shdr.hdr.size - sizeof(*pkt); + + if (!req_bytes || req_bytes % sizeof(*buf_req) || !pkt->data[0]) + /* bad packet */ + return HFI_ERR_SESSION_INVALID_PARAMETER; + + buf_req = (struct hfi_buffer_requirements *)&pkt->data[0]; + if (!buf_req) + return HFI_ERR_SESSION_INVALID_PARAMETER; + + while (req_bytes) { + memcpy(&bufreq[idx], buf_req, sizeof(*bufreq)); + idx++; + + if (idx >= HFI_BUFFER_TYPE_MAX) + return HFI_ERR_SESSION_INVALID_PARAMETER; + + req_bytes -= sizeof(struct hfi_buffer_requirements); + buf_req++; + } + + return HFI_ERR_NONE; +} + +static void hfi_session_prop_info(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_property_info_pkt *pkt = packet; + struct device *dev = core->dev; + union hfi_get_property *hprop = &inst->hprop; + unsigned int error = HFI_ERR_NONE; + + if (!pkt->num_properties) { + error = HFI_ERR_SESSION_INVALID_PARAMETER; + dev_err(dev, "%s: no properties\n", __func__); + goto done; + } + + switch (pkt->property) { + case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: + memset(hprop->bufreq, 0, sizeof(hprop->bufreq)); + error = session_get_prop_buf_req(pkt, hprop->bufreq); + break; + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: + memset(&hprop->profile_level, 0, sizeof(hprop->profile_level)); + error = session_get_prop_profile_level(pkt, + &hprop->profile_level); + break; + case HFI_PROPERTY_CONFIG_VDEC_ENTROPY: + break; + default: + dev_dbg(dev, VDBGM "unknown property id:%x\n", pkt->property); + return; + } + +done: + inst->error = error; + complete(&inst->done); +} + +static void hfi_session_init_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_init_done_pkt *pkt = packet; + int rem_bytes; + u32 error; + + error = pkt->error_type; + if (error != HFI_ERR_NONE) + goto done; + + if (!IS_V1(core)) + goto done; + + rem_bytes = pkt->shdr.hdr.size - sizeof(*pkt) + sizeof(u32); + if (rem_bytes <= 0) { + error = HFI_ERR_SESSION_INSUFFICIENT_RESOURCES; + goto done; + } + + error = hfi_parser(core, inst, pkt->data, rem_bytes); +done: + inst->error = error; + complete(&inst->done); +} + +static void hfi_session_load_res_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_load_resources_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_flush_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_flush_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); + if (inst->ops->flush_done) + inst->ops->flush_done(inst); +} + +static void hfi_session_etb_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_empty_buffer_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + inst->ops->buf_done(inst, HFI_BUFFER_INPUT, pkt->input_tag, + pkt->filled_len, pkt->offset, 0, 0, 0); +} + +static void hfi_session_ftb_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + u32 session_type = inst->session_type; + u64 timestamp_us = 0; + u32 timestamp_hi = 0, timestamp_lo = 0; + unsigned int error; + u32 flags = 0, hfi_flags = 0, offset = 0, filled_len = 0; + u32 pic_type = 0, buffer_type = 0, output_tag = -1; + + if (session_type == VIDC_SESSION_TYPE_ENC) { + struct hfi_msg_session_fbd_compressed_pkt *pkt = packet; + + timestamp_hi = pkt->time_stamp_hi; + timestamp_lo = pkt->time_stamp_lo; + hfi_flags = pkt->flags; + offset = pkt->offset; + filled_len = pkt->filled_len; + pic_type = pkt->picture_type; + output_tag = pkt->output_tag; + buffer_type = HFI_BUFFER_OUTPUT; + + error = pkt->error_type; + } else if (session_type == VIDC_SESSION_TYPE_DEC) { + struct hfi_msg_session_fbd_uncompressed_plane0_pkt *pkt = + packet; + + timestamp_hi = pkt->time_stamp_hi; + timestamp_lo = pkt->time_stamp_lo; + hfi_flags = pkt->flags; + offset = pkt->offset; + filled_len = pkt->filled_len; + pic_type = pkt->picture_type; + output_tag = pkt->output_tag; + + if (pkt->stream_id == 0) + buffer_type = HFI_BUFFER_OUTPUT; + else if (pkt->stream_id == 1) + buffer_type = HFI_BUFFER_OUTPUT2; + + error = pkt->error_type; + } else { + error = HFI_ERR_SESSION_INVALID_PARAMETER; + } + + if (buffer_type != HFI_BUFFER_OUTPUT && + buffer_type != HFI_BUFFER_OUTPUT2) + goto done; + + if (hfi_flags & HFI_BUFFERFLAG_EOS) + flags |= V4L2_BUF_FLAG_LAST; + + switch (pic_type) { + case HFI_PICTURE_IDR: + case HFI_PICTURE_I: + flags |= V4L2_BUF_FLAG_KEYFRAME; + break; + case HFI_PICTURE_P: + flags |= V4L2_BUF_FLAG_PFRAME; + break; + case HFI_PICTURE_B: + flags |= V4L2_BUF_FLAG_BFRAME; + break; + case HFI_FRAME_NOTCODED: + case HFI_UNUSED_PICT: + case HFI_FRAME_YUV: + default: + break; + } + + if (!(hfi_flags & HFI_BUFFERFLAG_TIMESTAMPINVALID) && filled_len) { + timestamp_us = timestamp_hi; + timestamp_us = (timestamp_us << 32) | timestamp_lo; + } + +done: + inst->error = error; + inst->ops->buf_done(inst, buffer_type, output_tag, filled_len, + offset, flags, hfi_flags, timestamp_us); +} + +static void hfi_session_start_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_start_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_stop_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_stop_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_rel_res_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_release_resources_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_rel_buf_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_release_buffers_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_end_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_end_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_abort_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_sys_session_abort_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +static void hfi_session_get_seq_hdr_done(struct venus_core *core, + struct venus_inst *inst, void *packet) +{ + struct hfi_msg_session_get_sequence_hdr_done_pkt *pkt = packet; + + inst->error = pkt->error_type; + complete(&inst->done); +} + +struct hfi_done_handler { + u32 pkt; + u32 pkt_sz; + u32 pkt_sz2; + void (*done)(struct venus_core *, struct venus_inst *, void *); + bool is_sys_pkt; +}; + +static const struct hfi_done_handler handlers[] = { + {.pkt = HFI_MSG_EVENT_NOTIFY, + .pkt_sz = sizeof(struct hfi_msg_event_notify_pkt), + .done = hfi_event_notify, + }, + {.pkt = HFI_MSG_SYS_INIT, + .pkt_sz = sizeof(struct hfi_msg_sys_init_done_pkt), + .done = hfi_sys_init_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_PROPERTY_INFO, + .pkt_sz = sizeof(struct hfi_msg_sys_property_info_pkt), + .done = hfi_sys_property_info, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_RELEASE_RESOURCE, + .pkt_sz = sizeof(struct hfi_msg_sys_release_resource_done_pkt), + .done = hfi_sys_rel_resource_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_PING_ACK, + .pkt_sz = sizeof(struct hfi_msg_sys_ping_ack_pkt), + .done = hfi_sys_ping_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_IDLE, + .pkt_sz = sizeof(struct hfi_msg_sys_idle_pkt), + .done = hfi_sys_idle_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_PC_PREP, + .pkt_sz = sizeof(struct hfi_msg_sys_pc_prep_done_pkt), + .done = hfi_sys_pc_prepare_done, + .is_sys_pkt = true, + }, + {.pkt = HFI_MSG_SYS_SESSION_INIT, + .pkt_sz = sizeof(struct hfi_msg_session_init_done_pkt), + .done = hfi_session_init_done, + }, + {.pkt = HFI_MSG_SYS_SESSION_END, + .pkt_sz = sizeof(struct hfi_msg_session_end_done_pkt), + .done = hfi_session_end_done, + }, + {.pkt = HFI_MSG_SESSION_LOAD_RESOURCES, + .pkt_sz = sizeof(struct hfi_msg_session_load_resources_done_pkt), + .done = hfi_session_load_res_done, + }, + {.pkt = HFI_MSG_SESSION_START, + .pkt_sz = sizeof(struct hfi_msg_session_start_done_pkt), + .done = hfi_session_start_done, + }, + {.pkt = HFI_MSG_SESSION_STOP, + .pkt_sz = sizeof(struct hfi_msg_session_stop_done_pkt), + .done = hfi_session_stop_done, + }, + {.pkt = HFI_MSG_SYS_SESSION_ABORT, + .pkt_sz = sizeof(struct hfi_msg_sys_session_abort_done_pkt), + .done = hfi_session_abort_done, + }, + {.pkt = HFI_MSG_SESSION_EMPTY_BUFFER, + .pkt_sz = sizeof(struct hfi_msg_session_empty_buffer_done_pkt), + .done = hfi_session_etb_done, + }, + {.pkt = HFI_MSG_SESSION_FILL_BUFFER, + .pkt_sz = sizeof(struct hfi_msg_session_fbd_uncompressed_plane0_pkt), + .pkt_sz2 = sizeof(struct hfi_msg_session_fbd_compressed_pkt), + .done = hfi_session_ftb_done, + }, + {.pkt = HFI_MSG_SESSION_FLUSH, + .pkt_sz = sizeof(struct hfi_msg_session_flush_done_pkt), + .done = hfi_session_flush_done, + }, + {.pkt = HFI_MSG_SESSION_PROPERTY_INFO, + .pkt_sz = sizeof(struct hfi_msg_session_property_info_pkt), + .done = hfi_session_prop_info, + }, + {.pkt = HFI_MSG_SESSION_RELEASE_RESOURCES, + .pkt_sz = sizeof(struct hfi_msg_session_release_resources_done_pkt), + .done = hfi_session_rel_res_done, + }, + {.pkt = HFI_MSG_SESSION_GET_SEQUENCE_HEADER, + .pkt_sz = sizeof(struct hfi_msg_session_get_sequence_hdr_done_pkt), + .done = hfi_session_get_seq_hdr_done, + }, + {.pkt = HFI_MSG_SESSION_RELEASE_BUFFERS, + .pkt_sz = sizeof(struct hfi_msg_session_release_buffers_done_pkt), + .done = hfi_session_rel_buf_done, + }, +}; + +void hfi_process_watchdog_timeout(struct venus_core *core) +{ + event_sys_error(core, EVT_SYS_WATCHDOG_TIMEOUT, NULL); +} + +static struct venus_inst *to_instance(struct venus_core *core, u32 session_id) +{ + struct venus_inst *inst; + + mutex_lock(&core->lock); + list_for_each_entry(inst, &core->instances, list) + if (hash32_ptr(inst) == session_id) { + mutex_unlock(&core->lock); + return inst; + } + mutex_unlock(&core->lock); + + return NULL; +} + +u32 hfi_process_msg_packet(struct venus_core *core, struct hfi_pkt_hdr *hdr) +{ + const struct hfi_done_handler *handler; + struct device *dev = core->dev; + struct venus_inst *inst; + bool found = false; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + handler = &handlers[i]; + if (handler->pkt != hdr->pkt_type) + continue; + found = true; + break; + } + + if (!found) + return hdr->pkt_type; + + if (hdr->size && hdr->size < handler->pkt_sz && + hdr->size < handler->pkt_sz2) { + dev_err(dev, "bad packet size (%d should be %d, pkt type:%x)\n", + hdr->size, handler->pkt_sz, hdr->pkt_type); + + return hdr->pkt_type; + } + + if (handler->is_sys_pkt) { + inst = NULL; + } else { + struct hfi_session_pkt *pkt; + + pkt = (struct hfi_session_pkt *)hdr; + inst = to_instance(core, pkt->shdr.session_id); + + if (!inst) + dev_warn(dev, "no valid instance(pkt session_id:%x, pkt:%x)\n", + pkt->shdr.session_id, + handler ? handler->pkt : 0); + + /* + * Event of type HFI_EVENT_SYS_ERROR will not have any session + * associated with it + */ + if (!inst && hdr->pkt_type != HFI_MSG_EVENT_NOTIFY) { + dev_err(dev, "got invalid session id:%x\n", + pkt->shdr.session_id); + goto invalid_session; + } + } + + handler->done(core, inst, hdr); + +invalid_session: + return hdr->pkt_type; +} diff --git a/drivers/media/platform/qcom/venus/hfi_msgs.h b/drivers/media/platform/qcom/venus/hfi_msgs.h new file mode 100644 index 000000000..510513697 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_msgs.h @@ -0,0 +1,276 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_HFI_MSGS_H__ +#define __VENUS_HFI_MSGS_H__ + +/* message calls */ +#define HFI_MSG_SYS_INIT 0x20001 +#define HFI_MSG_SYS_PC_PREP 0x20002 +#define HFI_MSG_SYS_RELEASE_RESOURCE 0x20003 +#define HFI_MSG_SYS_DEBUG 0x20004 +#define HFI_MSG_SYS_SESSION_INIT 0x20006 +#define HFI_MSG_SYS_SESSION_END 0x20007 +#define HFI_MSG_SYS_IDLE 0x20008 +#define HFI_MSG_SYS_COV 0x20009 +#define HFI_MSG_SYS_PROPERTY_INFO 0x2000a + +#define HFI_MSG_EVENT_NOTIFY 0x21001 +#define HFI_MSG_SESSION_GET_SEQUENCE_HEADER 0x21002 + +#define HFI_MSG_SYS_PING_ACK 0x220002 +#define HFI_MSG_SYS_SESSION_ABORT 0x220004 + +#define HFI_MSG_SESSION_LOAD_RESOURCES 0x221001 +#define HFI_MSG_SESSION_START 0x221002 +#define HFI_MSG_SESSION_STOP 0x221003 +#define HFI_MSG_SESSION_SUSPEND 0x221004 +#define HFI_MSG_SESSION_RESUME 0x221005 +#define HFI_MSG_SESSION_FLUSH 0x221006 +#define HFI_MSG_SESSION_EMPTY_BUFFER 0x221007 +#define HFI_MSG_SESSION_FILL_BUFFER 0x221008 +#define HFI_MSG_SESSION_PROPERTY_INFO 0x221009 +#define HFI_MSG_SESSION_RELEASE_RESOURCES 0x22100a +#define HFI_MSG_SESSION_PARSE_SEQUENCE_HEADER 0x22100b +#define HFI_MSG_SESSION_RELEASE_BUFFERS 0x22100c + +#define HFI_PICTURE_I 0x00000001 +#define HFI_PICTURE_P 0x00000002 +#define HFI_PICTURE_B 0x00000004 +#define HFI_PICTURE_IDR 0x00000008 +#define HFI_FRAME_NOTCODED 0x7f002000 +#define HFI_FRAME_YUV 0x7f004000 +#define HFI_UNUSED_PICT 0x10000000 + +/* message packets */ +struct hfi_msg_event_notify_pkt { + struct hfi_session_hdr_pkt shdr; + u32 event_id; + u32 event_data1; + u32 event_data2; + u32 ext_event_data[1]; +}; + +struct hfi_msg_event_release_buffer_ref_pkt { + u32 packet_buffer; + u32 extradata_buffer; + u32 output_tag; +}; + +struct hfi_msg_sys_init_done_pkt { + struct hfi_pkt_hdr hdr; + u32 error_type; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_msg_sys_pc_prep_done_pkt { + struct hfi_pkt_hdr hdr; + u32 error_type; +}; + +struct hfi_msg_sys_release_resource_done_pkt { + struct hfi_pkt_hdr hdr; + u32 resource_handle; + u32 error_type; +}; + +struct hfi_msg_session_init_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_msg_session_end_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_get_sequence_hdr_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 header_len; + u32 sequence_header; +}; + +struct hfi_msg_sys_session_abort_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_sys_idle_pkt { + struct hfi_pkt_hdr hdr; +}; + +struct hfi_msg_sys_ping_ack_pkt { + struct hfi_pkt_hdr hdr; + u32 client_data; +}; + +struct hfi_msg_sys_property_info_pkt { + struct hfi_pkt_hdr hdr; + u32 num_properties; + u32 property; + u8 data[]; +}; + +struct hfi_msg_session_load_resources_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_start_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_stop_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_suspend_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_resume_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_flush_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 flush_type; +}; + +struct hfi_msg_session_empty_buffer_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 offset; + u32 filled_len; + u32 input_tag; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[]; +}; + +struct hfi_msg_session_fbd_compressed_pkt { + struct hfi_session_hdr_pkt shdr; + u32 time_stamp_hi; + u32 time_stamp_lo; + u32 error_type; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 stats; + u32 offset; + u32 alloc_len; + u32 filled_len; + u32 input_tag; + u32 output_tag; + u32 picture_type; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[]; +}; + +struct hfi_msg_session_fbd_uncompressed_plane0_pkt { + struct hfi_session_hdr_pkt shdr; + u32 stream_id; + u32 view_id; + u32 error_type; + u32 time_stamp_hi; + u32 time_stamp_lo; + u32 flags; + u32 mark_target; + u32 mark_data; + u32 stats; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 frame_width; + u32 frame_height; + u32 start_x_coord; + u32 start_y_coord; + u32 input_tag; + u32 input_tag2; + u32 output_tag; + u32 picture_type; + u32 packet_buffer; + u32 extradata_buffer; + u32 data[]; +}; + +struct hfi_msg_session_fbd_uncompressed_plane1_pkt { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 packet_buffer2; + u32 data[]; +}; + +struct hfi_msg_session_fbd_uncompressed_plane2_pkt { + u32 flags; + u32 alloc_len; + u32 filled_len; + u32 offset; + u32 packet_buffer3; + u32 data[]; +}; + +struct hfi_msg_session_parse_sequence_header_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 num_properties; + u32 data[1]; +}; + +struct hfi_msg_session_property_info_pkt { + struct hfi_session_hdr_pkt shdr; + u32 num_properties; + u32 property; + u8 data[]; +}; + +struct hfi_msg_session_release_resources_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; +}; + +struct hfi_msg_session_release_buffers_done_pkt { + struct hfi_session_hdr_pkt shdr; + u32 error_type; + u32 num_buffers; + u32 buffer_info[1]; +}; + +struct hfi_msg_sys_debug_pkt { + struct hfi_pkt_hdr hdr; + u32 msg_type; + u32 msg_size; + u32 time_stamp_hi; + u32 time_stamp_lo; + u8 msg_data[1]; +}; + +struct hfi_msg_sys_coverage_pkt { + struct hfi_pkt_hdr hdr; + u32 msg_size; + u32 time_stamp_hi; + u32 time_stamp_lo; + u8 msg_data[1]; +}; + +struct venus_core; +struct hfi_pkt_hdr; + +void hfi_process_watchdog_timeout(struct venus_core *core); +u32 hfi_process_msg_packet(struct venus_core *core, struct hfi_pkt_hdr *hdr); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_parser.c b/drivers/media/platform/qcom/venus/hfi_parser.c new file mode 100644 index 000000000..c43839539 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_parser.c @@ -0,0 +1,342 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018 Linaro Ltd. + * + * Author: Stanimir Varbanov + */ +#include +#include + +#include "core.h" +#include "hfi_helper.h" +#include "hfi_parser.h" + +typedef void (*func)(struct hfi_plat_caps *cap, const void *data, + unsigned int size); + +static void init_codecs(struct venus_core *core) +{ + struct hfi_plat_caps *caps = core->caps, *cap; + unsigned long bit; + + if (hweight_long(core->dec_codecs) + hweight_long(core->enc_codecs) > MAX_CODEC_NUM) + return; + + for_each_set_bit(bit, &core->dec_codecs, MAX_CODEC_NUM) { + cap = &caps[core->codecs_count++]; + cap->codec = BIT(bit); + cap->domain = VIDC_SESSION_TYPE_DEC; + cap->valid = false; + } + + for_each_set_bit(bit, &core->enc_codecs, MAX_CODEC_NUM) { + cap = &caps[core->codecs_count++]; + cap->codec = BIT(bit); + cap->domain = VIDC_SESSION_TYPE_ENC; + cap->valid = false; + } +} + +static void for_each_codec(struct hfi_plat_caps *caps, unsigned int caps_num, + u32 codecs, u32 domain, func cb, void *data, + unsigned int size) +{ + struct hfi_plat_caps *cap; + unsigned int i; + + for (i = 0; i < caps_num; i++) { + cap = &caps[i]; + if (cap->valid && cap->domain == domain) + continue; + if (cap->codec & codecs && cap->domain == domain) + cb(cap, data, size); + } +} + +static void +fill_buf_mode(struct hfi_plat_caps *cap, const void *data, unsigned int num) +{ + const u32 *type = data; + + if (*type == HFI_BUFFER_MODE_DYNAMIC) + cap->cap_bufs_mode_dynamic = true; +} + +static void +parse_alloc_mode(struct venus_core *core, u32 codecs, u32 domain, void *data) +{ + struct hfi_buffer_alloc_mode_supported *mode = data; + u32 num_entries = mode->num_entries; + u32 *type; + + if (num_entries > MAX_ALLOC_MODE_ENTRIES) + return; + + type = mode->data; + + while (num_entries--) { + if (mode->buffer_type == HFI_BUFFER_OUTPUT || + mode->buffer_type == HFI_BUFFER_OUTPUT2) + for_each_codec(core->caps, ARRAY_SIZE(core->caps), + codecs, domain, fill_buf_mode, type, 1); + + type++; + } +} + +static void fill_profile_level(struct hfi_plat_caps *cap, const void *data, + unsigned int num) +{ + const struct hfi_profile_level *pl = data; + + if (cap->num_pl + num >= HFI_MAX_PROFILE_COUNT) + return; + + memcpy(&cap->pl[cap->num_pl], pl, num * sizeof(*pl)); + cap->num_pl += num; +} + +static void +parse_profile_level(struct venus_core *core, u32 codecs, u32 domain, void *data) +{ + struct hfi_profile_level_supported *pl = data; + struct hfi_profile_level *proflevel = pl->profile_level; + struct hfi_profile_level pl_arr[HFI_MAX_PROFILE_COUNT] = {}; + + if (pl->profile_count > HFI_MAX_PROFILE_COUNT) + return; + + memcpy(pl_arr, proflevel, pl->profile_count * sizeof(*proflevel)); + + for_each_codec(core->caps, ARRAY_SIZE(core->caps), codecs, domain, + fill_profile_level, pl_arr, pl->profile_count); +} + +static void +fill_caps(struct hfi_plat_caps *cap, const void *data, unsigned int num) +{ + const struct hfi_capability *caps = data; + + if (cap->num_caps + num >= MAX_CAP_ENTRIES) + return; + + memcpy(&cap->caps[cap->num_caps], caps, num * sizeof(*caps)); + cap->num_caps += num; +} + +static void +parse_caps(struct venus_core *core, u32 codecs, u32 domain, void *data) +{ + struct hfi_capabilities *caps = data; + struct hfi_capability *cap = caps->data; + u32 num_caps = caps->num_capabilities; + struct hfi_capability caps_arr[MAX_CAP_ENTRIES] = {}; + + if (num_caps > MAX_CAP_ENTRIES) + return; + + memcpy(caps_arr, cap, num_caps * sizeof(*cap)); + + for_each_codec(core->caps, ARRAY_SIZE(core->caps), codecs, domain, + fill_caps, caps_arr, num_caps); +} + +static void fill_raw_fmts(struct hfi_plat_caps *cap, const void *fmts, + unsigned int num_fmts) +{ + const struct raw_formats *formats = fmts; + + if (cap->num_fmts + num_fmts >= MAX_FMT_ENTRIES) + return; + + memcpy(&cap->fmts[cap->num_fmts], formats, num_fmts * sizeof(*formats)); + cap->num_fmts += num_fmts; +} + +static void +parse_raw_formats(struct venus_core *core, u32 codecs, u32 domain, void *data) +{ + struct hfi_uncompressed_format_supported *fmt = data; + struct hfi_uncompressed_plane_info *pinfo = fmt->plane_info; + struct hfi_uncompressed_plane_constraints *constr; + struct raw_formats rawfmts[MAX_FMT_ENTRIES] = {}; + u32 entries = fmt->format_entries; + unsigned int i = 0; + u32 num_planes; + + while (entries) { + num_planes = pinfo->num_planes; + + rawfmts[i].fmt = pinfo->format; + rawfmts[i].buftype = fmt->buffer_type; + i++; + + if (i >= MAX_FMT_ENTRIES) + return; + + if (pinfo->num_planes > MAX_PLANES) + break; + + pinfo = (void *)pinfo + sizeof(*constr) * num_planes + + 2 * sizeof(u32); + entries--; + } + + for_each_codec(core->caps, ARRAY_SIZE(core->caps), codecs, domain, + fill_raw_fmts, rawfmts, i); +} + +static void parse_codecs(struct venus_core *core, void *data) +{ + struct hfi_codec_supported *codecs = data; + + core->dec_codecs = codecs->dec_codecs; + core->enc_codecs = codecs->enc_codecs; + + if (IS_V1(core)) { + core->dec_codecs &= ~HFI_VIDEO_CODEC_HEVC; + core->dec_codecs &= ~HFI_VIDEO_CODEC_SPARK; + core->enc_codecs &= ~HFI_VIDEO_CODEC_HEVC; + } +} + +static void parse_max_sessions(struct venus_core *core, const void *data) +{ + const struct hfi_max_sessions_supported *sessions = data; + + core->max_sessions_supported = sessions->max_sessions; +} + +static void parse_codecs_mask(u32 *codecs, u32 *domain, void *data) +{ + struct hfi_codec_mask_supported *mask = data; + + *codecs = mask->codecs; + *domain = mask->video_domains; +} + +static void parser_init(struct venus_inst *inst, u32 *codecs, u32 *domain) +{ + if (!inst || !IS_V1(inst->core)) + return; + + *codecs = inst->hfi_codec; + *domain = inst->session_type; +} + +static void parser_fini(struct venus_inst *inst, u32 codecs, u32 domain) +{ + struct hfi_plat_caps *caps, *cap; + unsigned int i; + u32 dom; + + if (!inst || !IS_V1(inst->core)) + return; + + caps = inst->core->caps; + dom = inst->session_type; + + for (i = 0; i < MAX_CODEC_NUM; i++) { + cap = &caps[i]; + if (cap->codec & codecs && cap->domain == dom) + cap->valid = true; + } +} + +static int hfi_platform_parser(struct venus_core *core, struct venus_inst *inst) +{ + const struct hfi_platform *plat; + const struct hfi_plat_caps *caps = NULL; + u32 enc_codecs, dec_codecs, count = 0; + unsigned int entries; + int ret; + + plat = hfi_platform_get(core->res->hfi_version); + if (!plat) + return -EINVAL; + + if (inst) + return 0; + + ret = hfi_platform_get_codecs(core, &enc_codecs, &dec_codecs, &count); + if (ret) + return ret; + + if (plat->capabilities) + caps = plat->capabilities(&entries); + + if (!caps || !entries || !count) + return -EINVAL; + + core->enc_codecs = enc_codecs; + core->dec_codecs = dec_codecs; + core->codecs_count = count; + core->max_sessions_supported = MAX_SESSIONS; + memset(core->caps, 0, sizeof(*caps) * MAX_CODEC_NUM); + memcpy(core->caps, caps, sizeof(*caps) * entries); + + return 0; +} + +u32 hfi_parser(struct venus_core *core, struct venus_inst *inst, void *buf, + u32 size) +{ + unsigned int words_count = size >> 2; + u32 *word = buf, *data, codecs = 0, domain = 0; + int ret; + + ret = hfi_platform_parser(core, inst); + if (!ret) + return HFI_ERR_NONE; + + if (size % 4) + return HFI_ERR_SYS_INSUFFICIENT_RESOURCES; + + parser_init(inst, &codecs, &domain); + + if (core->res->hfi_version > HFI_VERSION_1XX) { + core->codecs_count = 0; + memset(core->caps, 0, sizeof(core->caps)); + } + + while (words_count) { + data = word + 1; + + switch (*word) { + case HFI_PROPERTY_PARAM_CODEC_SUPPORTED: + parse_codecs(core, data); + init_codecs(core); + break; + case HFI_PROPERTY_PARAM_MAX_SESSIONS_SUPPORTED: + parse_max_sessions(core, data); + break; + case HFI_PROPERTY_PARAM_CODEC_MASK_SUPPORTED: + parse_codecs_mask(&codecs, &domain, data); + break; + case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED: + parse_raw_formats(core, codecs, domain, data); + break; + case HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED: + parse_caps(core, codecs, domain, data); + break; + case HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED: + parse_profile_level(core, codecs, domain, data); + break; + case HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE_SUPPORTED: + parse_alloc_mode(core, codecs, domain, data); + break; + default: + break; + } + + word++; + words_count--; + } + + if (!core->max_sessions_supported) + core->max_sessions_supported = MAX_SESSIONS; + + parser_fini(inst, codecs, domain); + + return HFI_ERR_NONE; +} diff --git a/drivers/media/platform/qcom/venus/hfi_parser.h b/drivers/media/platform/qcom/venus/hfi_parser.h new file mode 100644 index 000000000..5751d0140 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_parser.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2018 Linaro Ltd. */ +#ifndef __VENUS_HFI_PARSER_H__ +#define __VENUS_HFI_PARSER_H__ + +#include "core.h" + +u32 hfi_parser(struct venus_core *core, struct venus_inst *inst, + void *buf, u32 size); + +#define WHICH_CAP_MIN 0 +#define WHICH_CAP_MAX 1 +#define WHICH_CAP_STEP 2 + +static inline u32 get_cap(struct venus_inst *inst, u32 type, u32 which) +{ + struct venus_core *core = inst->core; + struct hfi_capability *cap = NULL; + struct hfi_plat_caps *caps; + unsigned int i; + + caps = venus_caps_by_codec(core, inst->hfi_codec, inst->session_type); + if (!caps) + return 0; + + for (i = 0; i < caps->num_caps; i++) { + if (caps->caps[i].capability_type == type) { + cap = &caps->caps[i]; + break; + } + } + + if (!cap) + return 0; + + switch (which) { + case WHICH_CAP_MIN: + return cap->min; + case WHICH_CAP_MAX: + return cap->max; + case WHICH_CAP_STEP: + return cap->step_size; + default: + break; + } + + return 0; +} + +static inline u32 cap_min(struct venus_inst *inst, u32 type) +{ + return get_cap(inst, type, WHICH_CAP_MIN); +} + +static inline u32 cap_max(struct venus_inst *inst, u32 type) +{ + return get_cap(inst, type, WHICH_CAP_MAX); +} + +static inline u32 cap_step(struct venus_inst *inst, u32 type) +{ + return get_cap(inst, type, WHICH_CAP_STEP); +} + +static inline u32 frame_width_min(struct venus_inst *inst) +{ + return cap_min(inst, HFI_CAPABILITY_FRAME_WIDTH); +} + +static inline u32 frame_width_max(struct venus_inst *inst) +{ + return cap_max(inst, HFI_CAPABILITY_FRAME_WIDTH); +} + +static inline u32 frame_width_step(struct venus_inst *inst) +{ + return cap_step(inst, HFI_CAPABILITY_FRAME_WIDTH); +} + +static inline u32 frame_height_min(struct venus_inst *inst) +{ + return cap_min(inst, HFI_CAPABILITY_FRAME_HEIGHT); +} + +static inline u32 frame_height_max(struct venus_inst *inst) +{ + return cap_max(inst, HFI_CAPABILITY_FRAME_HEIGHT); +} + +static inline u32 frame_height_step(struct venus_inst *inst) +{ + return cap_step(inst, HFI_CAPABILITY_FRAME_HEIGHT); +} + +static inline u32 frate_min(struct venus_inst *inst) +{ + return cap_min(inst, HFI_CAPABILITY_FRAMERATE); +} + +static inline u32 frate_max(struct venus_inst *inst) +{ + return cap_max(inst, HFI_CAPABILITY_FRAMERATE); +} + +static inline u32 frate_step(struct venus_inst *inst) +{ + return cap_step(inst, HFI_CAPABILITY_FRAMERATE); +} + +static inline u32 core_num_max(struct venus_inst *inst) +{ + return cap_max(inst, HFI_CAPABILITY_MAX_VIDEOCORES); +} + +static inline u32 mbs_per_frame_max(struct venus_inst *inst) +{ + return cap_max(inst, HFI_CAPABILITY_MBS_PER_FRAME); +} + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_plat_bufs.h b/drivers/media/platform/qcom/venus/hfi_plat_bufs.h new file mode 100644 index 000000000..52a51a3b9 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_plat_bufs.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __HFI_PLATFORM_BUFFERS_H__ +#define __HFI_PLATFORM_BUFFERS_H__ + +#include +#include "hfi_helper.h" + +struct hfi_plat_buffers_params { + u32 width; + u32 height; + u32 codec; + u32 hfi_color_fmt; + enum hfi_version version; + u32 num_vpp_pipes; + union { + struct { + u32 max_mbs_per_frame; + u32 buffer_size_limit; + bool is_secondary_output; + bool is_interlaced; + } dec; + struct { + u32 work_mode; + u32 rc_type; + u32 num_b_frames; + bool is_tenbit; + } enc; + }; +}; + +int hfi_plat_bufreq_v6(struct hfi_plat_buffers_params *params, u32 session_type, + u32 buftype, struct hfi_buffer_requirements *bufreq); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_plat_bufs_v6.c b/drivers/media/platform/qcom/venus/hfi_plat_bufs_v6.c new file mode 100644 index 000000000..ea25c4512 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_plat_bufs_v6.c @@ -0,0 +1,1329 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +#include +#include +#include + +#include "hfi.h" +#include "hfi_plat_bufs.h" +#include "helpers.h" + +#define MIN_INPUT_BUFFERS 4 +#define MIN_ENC_OUTPUT_BUFFERS 4 + +#define NV12_UBWC_Y_TILE_WIDTH 32 +#define NV12_UBWC_Y_TILE_HEIGHT 8 +#define NV12_UBWC_UV_TILE_WIDTH 16 +#define NV12_UBWC_UV_TILE_HEIGHT 8 +#define TP10_UBWC_Y_TILE_WIDTH 48 +#define TP10_UBWC_Y_TILE_HEIGHT 4 +#define METADATA_STRIDE_MULTIPLE 64 +#define METADATA_HEIGHT_MULTIPLE 16 +#define HFI_DMA_ALIGNMENT 256 + +#define MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE 64 +#define MAX_FE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE 64 +#define MAX_FE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE 64 +#define MAX_FE_NBR_DATA_LUMA_LINE_BUFFER_SIZE 640 +#define MAX_FE_NBR_DATA_CB_LINE_BUFFER_SIZE 320 +#define MAX_FE_NBR_DATA_CR_LINE_BUFFER_SIZE 320 + +#define MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE (128 / 8) +#define MAX_SE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE (128 / 8) +#define MAX_SE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE (128 / 8) + +#define MAX_PE_NBR_DATA_LCU64_LINE_BUFFER_SIZE (64 * 2 * 3) +#define MAX_PE_NBR_DATA_LCU32_LINE_BUFFER_SIZE (32 * 2 * 3) +#define MAX_PE_NBR_DATA_LCU16_LINE_BUFFER_SIZE (16 * 2 * 3) + +#define MAX_TILE_COLUMNS 32 /* 8K/256 */ + +#define VPP_CMD_MAX_SIZE BIT(20) +#define NUM_HW_PIC_BUF 32 +#define BIN_BUFFER_THRESHOLD (1280 * 736) +#define H264D_MAX_SLICE 1800 +/* sizeof(h264d_buftab_t) aligned to 256 */ +#define SIZE_H264D_BUFTAB_T 256 +/* sizeof(h264d_hw_pic_t) aligned to 32 */ +#define SIZE_H264D_HW_PIC_T BIT(11) +#define SIZE_H264D_BSE_CMD_PER_BUF (32 * 4) +#define SIZE_H264D_VPP_CMD_PER_BUF 512 + +/* Line Buffer definitions, One for Luma and 1/2 for each Chroma */ +#define SIZE_H264D_LB_FE_TOP_DATA(width, height) \ + (MAX_FE_NBR_DATA_LUMA_LINE_BUFFER_SIZE * ALIGN((width), 16) * 3) + +#define SIZE_H264D_LB_FE_TOP_CTRL(width, height) \ + (MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE * (((width) + 15) >> 4)) + +#define SIZE_H264D_LB_FE_LEFT_CTRL(width, height) \ + (MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE * (((height) + 15) >> 4)) + +#define SIZE_H264D_LB_SE_TOP_CTRL(width, height) \ + (MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE * (((width) + 15) >> 4)) + +#define SIZE_H264D_LB_SE_LEFT_CTRL(width, height) \ + (MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE * (((height) + 15) >> 4)) + +#define SIZE_H264D_LB_PE_TOP_DATA(width, height) \ + (MAX_PE_NBR_DATA_LCU64_LINE_BUFFER_SIZE * (((width) + 15) >> 4)) + +#define SIZE_H264D_LB_VSP_TOP(width, height) (((((width) + 15) >> 4) << 7)) + +#define SIZE_H264D_LB_RECON_DMA_METADATA_WR(width, height) \ + (ALIGN((height), 16) * 32) + +#define SIZE_H264D_QP(width, height) \ + ((((width) + 63) >> 6) * (((height) + 63) >> 6) * 128) + +#define SIZE_HW_PIC(size_per_buf) (NUM_HW_PIC_BUF * (size_per_buf)) + +#define H264_CABAC_HDR_RATIO_HD_TOT 1 +#define H264_CABAC_RES_RATIO_HD_TOT 3 + +/* + * Some content need more bin buffer, but limit buffer + * size for high resolution + */ +#define NUM_SLIST_BUF_H264 (256 + 32) +#define SIZE_SLIST_BUF_H264 512 +#define LCU_MAX_SIZE_PELS 64 +#define LCU_MIN_SIZE_PELS 16 +#define SIZE_SEI_USERDATA 4096 + +#define H265D_MAX_SLICE 600 +#define SIZE_H265D_HW_PIC_T SIZE_H264D_HW_PIC_T +#define SIZE_H265D_BSE_CMD_PER_BUF (16 * sizeof(u32)) +#define SIZE_H265D_VPP_CMD_PER_BUF 256 + +#define SIZE_H265D_LB_FE_TOP_DATA(width, height) \ + (MAX_FE_NBR_DATA_LUMA_LINE_BUFFER_SIZE * (ALIGN(width, 64) + 8) * 2) + +#define SIZE_H265D_LB_FE_TOP_CTRL(width, height) \ + (MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE * \ + (ALIGN(width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS)) + +#define SIZE_H265D_LB_FE_LEFT_CTRL(width, height) \ + (MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE * \ + (ALIGN(height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS)) + +#define SIZE_H265D_LB_SE_TOP_CTRL(width, height) \ + ((LCU_MAX_SIZE_PELS / 8 * (128 / 8)) * (((width) + 15) >> 4)) + +static inline u32 size_h265d_lb_se_left_ctrl(u32 width, u32 height) +{ + u32 x, y, z; + + x = ((height + 16 - 1) / 8) * MAX_SE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE; + y = ((height + 32 - 1) / 8) * MAX_SE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE; + z = ((height + 64 - 1) / 8) * MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE; + + return max3(x, y, z); +} + +#define SIZE_H265D_LB_PE_TOP_DATA(width, height) \ + (MAX_PE_NBR_DATA_LCU64_LINE_BUFFER_SIZE * \ + (ALIGN(width, LCU_MIN_SIZE_PELS) / LCU_MIN_SIZE_PELS)) + +#define SIZE_H265D_LB_VSP_TOP(width, height) ((((width) + 63) >> 6) * 128) + +#define SIZE_H265D_LB_VSP_LEFT(width, height) ((((height) + 63) >> 6) * 128) + +#define SIZE_H265D_LB_RECON_DMA_METADATA_WR(width, height) \ + SIZE_H264D_LB_RECON_DMA_METADATA_WR(width, height) + +#define SIZE_H265D_QP(width, height) SIZE_H264D_QP(width, height) + +#define H265_CABAC_HDR_RATIO_HD_TOT 2 +#define H265_CABAC_RES_RATIO_HD_TOT 2 + +/* + * Some content need more bin buffer, but limit buffer size + * for high resolution + */ +#define SIZE_SLIST_BUF_H265 BIT(10) +#define NUM_SLIST_BUF_H265 (80 + 20) +#define H265_NUM_TILE_COL 32 +#define H265_NUM_TILE_ROW 128 +#define H265_NUM_TILE (H265_NUM_TILE_ROW * H265_NUM_TILE_COL + 1) + +static inline u32 size_vpxd_lb_fe_left_ctrl(u32 width, u32 height) +{ + u32 x, y, z; + + x = ((height + 15) >> 4) * MAX_FE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE; + y = ((height + 31) >> 5) * MAX_FE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE; + z = ((height + 63) >> 6) * MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE; + + return max3(x, y, z); +} + +#define SIZE_VPXD_LB_FE_TOP_CTRL(width, height) \ + (((ALIGN(width, 64) + 8) * 10 * 2)) /* small line */ +#define SIZE_VPXD_LB_SE_TOP_CTRL(width, height) \ + ((((width) + 15) >> 4) * MAX_FE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE) + +static inline u32 size_vpxd_lb_se_left_ctrl(u32 width, u32 height) +{ + u32 x, y, z; + + x = ((height + 15) >> 4) * MAX_SE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE; + y = ((height + 31) >> 5) * MAX_SE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE; + z = ((height + 63) >> 6) * MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE; + + return max3(x, y, z); +} + +#define SIZE_VPXD_LB_RECON_DMA_METADATA_WR(width, height) \ + ALIGN((ALIGN(height, 16) / (4 / 2)) * 64, 32) +#define SIZE_VP8D_LB_FE_TOP_DATA(width, height) \ + ((ALIGN(width, 16) + 8) * 10 * 2) +#define SIZE_VP9D_LB_FE_TOP_DATA(width, height) \ + ((ALIGN(ALIGN(width, 16), 64) + 8) * 10 * 2) +#define SIZE_VP8D_LB_PE_TOP_DATA(width, height) \ + ((ALIGN(width, 16) >> 4) * 64) +#define SIZE_VP9D_LB_PE_TOP_DATA(width, height) \ + ((ALIGN(ALIGN(width, 16), 64) >> 6) * 176) +#define SIZE_VP8D_LB_VSP_TOP(width, height) \ + (((ALIGN(width, 16) >> 4) * 64 / 2) + 256) +#define SIZE_VP9D_LB_VSP_TOP(width, height) \ + (((ALIGN(ALIGN(width, 16), 64) >> 6) * 64 * 8) + 256) + +#define HFI_IRIS2_VP9D_COMV_SIZE \ + ((((8192 + 63) >> 6) * ((4320 + 63) >> 6) * 8 * 8 * 2 * 8)) + +#define VPX_DECODER_FRAME_CONCURENCY_LVL 2 +#define VPX_DECODER_FRAME_BIN_HDR_BUDGET_RATIO_NUM 1 +#define VPX_DECODER_FRAME_BIN_HDR_BUDGET_RATIO_DEN 2 +#define VPX_DECODER_FRAME_BIN_RES_BUDGET_RATIO_NUM 3 +#define VPX_DECODER_FRAME_BIN_RES_BUDGET_RATIO_DEN 2 + +#define VP8_NUM_FRAME_INFO_BUF (5 + 1) +#define VP9_NUM_FRAME_INFO_BUF 32 +#define VP8_NUM_PROBABILITY_TABLE_BUF VP8_NUM_FRAME_INFO_BUF +#define VP9_NUM_PROBABILITY_TABLE_BUF (VP9_NUM_FRAME_INFO_BUF + 4) +#define VP8_PROB_TABLE_SIZE 3840 +#define VP9_PROB_TABLE_SIZE 3840 + +#define VP9_UDC_HEADER_BUF_SIZE (3 * 128) +#define MAX_SUPERFRAME_HEADER_LEN 34 +#define CCE_TILE_OFFSET_SIZE ALIGN(32 * 4 * 4, 32) + +#define QMATRIX_SIZE (sizeof(u32) * 128 + 256) +#define MP2D_QPDUMP_SIZE 115200 +#define HFI_IRIS2_ENC_PERSIST_SIZE 204800 +#define HFI_MAX_COL_FRAME 6 +#define HFI_VENUS_VENC_TRE_WB_BUFF_SIZE (65 << 4) /* in Bytes */ +#define HFI_VENUS_VENC_DB_LINE_BUFF_PER_MB 512 +#define HFI_VENUS_VPPSG_MAX_REGISTERS 2048 +#define HFI_VENUS_WIDTH_ALIGNMENT 128 +#define HFI_VENUS_WIDTH_TEN_BIT_ALIGNMENT 192 +#define HFI_VENUS_HEIGHT_ALIGNMENT 32 + +#define SYSTEM_LAL_TILE10 192 +#define NUM_MBS_720P (((1280 + 15) >> 4) * ((720 + 15) >> 4)) +#define NUM_MBS_4K (((4096 + 15) >> 4) * ((2304 + 15) >> 4)) +#define MB_SIZE_IN_PIXEL (16 * 16) +#define HDR10PLUS_PAYLOAD_SIZE 1024 +#define HDR10_HIST_EXTRADATA_SIZE 4096 + +static u32 size_vpss_lb(u32 width, u32 height, u32 num_vpp_pipes) +{ + u32 vpss_4tap_top_buffer_size, vpss_div2_top_buffer_size; + u32 vpss_4tap_left_buffer_size, vpss_div2_left_buffer_size; + u32 opb_wr_top_line_luma_buf_size, opb_wr_top_line_chroma_buf_size; + u32 opb_lb_wr_llb_y_buffer_size, opb_lb_wr_llb_uv_buffer_size; + u32 macrotiling_size; + u32 size = 0; + + vpss_4tap_top_buffer_size = 0; + vpss_div2_top_buffer_size = 0; + vpss_4tap_left_buffer_size = 0; + vpss_div2_left_buffer_size = 0; + + macrotiling_size = 32; + opb_wr_top_line_luma_buf_size = + ALIGN(width, macrotiling_size) / macrotiling_size * 256; + opb_wr_top_line_luma_buf_size = + ALIGN(opb_wr_top_line_luma_buf_size, HFI_DMA_ALIGNMENT) + + (MAX_TILE_COLUMNS - 1) * 256; + opb_wr_top_line_luma_buf_size = + max(opb_wr_top_line_luma_buf_size, (32 * ALIGN(height, 16))); + opb_wr_top_line_chroma_buf_size = opb_wr_top_line_luma_buf_size; + opb_lb_wr_llb_y_buffer_size = ALIGN((ALIGN(height, 16) / 2) * 64, 32); + opb_lb_wr_llb_uv_buffer_size = opb_lb_wr_llb_y_buffer_size; + size = num_vpp_pipes * + 2 * (vpss_4tap_top_buffer_size + vpss_div2_top_buffer_size) + + 2 * (vpss_4tap_left_buffer_size + vpss_div2_left_buffer_size) + + opb_wr_top_line_luma_buf_size + + opb_wr_top_line_chroma_buf_size + + opb_lb_wr_llb_uv_buffer_size + + opb_lb_wr_llb_y_buffer_size; + + return size; +} + +static u32 size_h264d_hw_bin_buffer(u32 width, u32 height) +{ + u32 size_yuv, size_bin_hdr, size_bin_res; + u32 size = 0; + u32 product; + + product = width * height; + size_yuv = (product <= BIN_BUFFER_THRESHOLD) ? + ((BIN_BUFFER_THRESHOLD * 3) >> 1) : ((product * 3) >> 1); + + size_bin_hdr = size_yuv * H264_CABAC_HDR_RATIO_HD_TOT; + size_bin_res = size_yuv * H264_CABAC_RES_RATIO_HD_TOT; + size_bin_hdr = ALIGN(size_bin_hdr, HFI_DMA_ALIGNMENT); + size_bin_res = ALIGN(size_bin_res, HFI_DMA_ALIGNMENT); + size = size_bin_hdr + size_bin_res; + + return size; +} + +static u32 h264d_scratch_size(u32 width, u32 height, bool is_interlaced) +{ + u32 aligned_width = ALIGN(width, 16); + u32 aligned_height = ALIGN(height, 16); + u32 size = 0; + + if (!is_interlaced) + size = size_h264d_hw_bin_buffer(aligned_width, aligned_height); + + return size; +} + +static u32 size_h265d_hw_bin_buffer(u32 width, u32 height) +{ + u32 size_yuv, size_bin_hdr, size_bin_res; + u32 size = 0; + u32 product; + + product = width * height; + size_yuv = (product <= BIN_BUFFER_THRESHOLD) ? + ((BIN_BUFFER_THRESHOLD * 3) >> 1) : ((product * 3) >> 1); + size_bin_hdr = size_yuv * H265_CABAC_HDR_RATIO_HD_TOT; + size_bin_res = size_yuv * H265_CABAC_RES_RATIO_HD_TOT; + size_bin_hdr = ALIGN(size_bin_hdr, HFI_DMA_ALIGNMENT); + size_bin_res = ALIGN(size_bin_res, HFI_DMA_ALIGNMENT); + size = size_bin_hdr + size_bin_res; + + return size; +} + +static u32 h265d_scratch_size(u32 width, u32 height, bool is_interlaced) +{ + u32 aligned_width = ALIGN(width, 16); + u32 aligned_height = ALIGN(height, 16); + u32 size = 0; + + if (!is_interlaced) + size = size_h265d_hw_bin_buffer(aligned_width, aligned_height); + + return size; +} + +static u32 vpxd_scratch_size(u32 width, u32 height, bool is_interlaced) +{ + u32 aligned_width = ALIGN(width, 16); + u32 aligned_height = ALIGN(height, 16); + u32 size_yuv = aligned_width * aligned_height * 3 / 2; + u32 size = 0; + + if (!is_interlaced) { + u32 binbuffer1_size, binbufer2_size; + + binbuffer1_size = max_t(u32, size_yuv, + ((BIN_BUFFER_THRESHOLD * 3) >> 1)); + binbuffer1_size *= VPX_DECODER_FRAME_CONCURENCY_LVL * + VPX_DECODER_FRAME_BIN_HDR_BUDGET_RATIO_NUM / + VPX_DECODER_FRAME_BIN_HDR_BUDGET_RATIO_DEN; + binbufer2_size = max_t(u32, size_yuv, + ((BIN_BUFFER_THRESHOLD * 3) >> 1)); + binbufer2_size *= VPX_DECODER_FRAME_CONCURENCY_LVL * + VPX_DECODER_FRAME_BIN_RES_BUDGET_RATIO_NUM / + VPX_DECODER_FRAME_BIN_RES_BUDGET_RATIO_DEN; + size = ALIGN(binbuffer1_size + binbufer2_size, + HFI_DMA_ALIGNMENT); + } + + return size; +} + +static u32 mpeg2d_scratch_size(u32 width, u32 height, bool is_interlaced) +{ + return 0; +} + +static u32 calculate_enc_output_frame_size(u32 width, u32 height, u32 rc_type) +{ + u32 aligned_width, aligned_height; + u32 mbs_per_frame; + u32 frame_size; + + /* + * Encoder output size calculation: 32 Align width/height + * For resolution < 720p : YUVsize * 4 + * For resolution > 720p & <= 4K : YUVsize / 2 + * For resolution > 4k : YUVsize / 4 + * Initially frame_size = YUVsize * 2; + */ + aligned_width = ALIGN(width, 32); + aligned_height = ALIGN(height, 32); + mbs_per_frame = (ALIGN(aligned_height, 16) * + ALIGN(aligned_width, 16)) / 256; + frame_size = width * height * 3; + + if (mbs_per_frame < NUM_MBS_720P) + frame_size = frame_size << 1; + else if (mbs_per_frame <= NUM_MBS_4K) + frame_size = frame_size >> 2; + else + frame_size = frame_size >> 3; + + if (rc_type == HFI_RATE_CONTROL_OFF || rc_type == HFI_RATE_CONTROL_CQ) + frame_size = frame_size << 1; + + /* + * In case of opaque color format bitdepth will be known + * with first ETB, buffers allocated already with 8 bit + * won't be sufficient for 10 bit + * calculate size considering 10-bit by default + * For 10-bit cases size = size * 1.25 + */ + frame_size *= 5; + frame_size /= 4; + + return ALIGN(frame_size, SZ_4K); +} + +static u32 calculate_enc_scratch_size(u32 width, u32 height, u32 work_mode, + u32 lcu_size, u32 num_vpp_pipes, + u32 rc_type) +{ + u32 aligned_width, aligned_height, bitstream_size; + u32 total_bitbin_buffers, size_single_pipe, bitbin_size; + u32 sao_bin_buffer_size, padded_bin_size, size; + + aligned_width = ALIGN(width, lcu_size); + aligned_height = ALIGN(height, lcu_size); + bitstream_size = + calculate_enc_output_frame_size(width, height, rc_type); + + bitstream_size = ALIGN(bitstream_size, HFI_DMA_ALIGNMENT); + + if (work_mode == VIDC_WORK_MODE_2) { + total_bitbin_buffers = 3; + bitbin_size = bitstream_size * 17 / 10; + bitbin_size = ALIGN(bitbin_size, HFI_DMA_ALIGNMENT); + } else { + total_bitbin_buffers = 1; + bitstream_size = aligned_width * aligned_height * 3; + bitbin_size = ALIGN(bitstream_size, HFI_DMA_ALIGNMENT); + } + + if (num_vpp_pipes > 2) + size_single_pipe = bitbin_size / 2; + else + size_single_pipe = bitbin_size; + + size_single_pipe = ALIGN(size_single_pipe, HFI_DMA_ALIGNMENT); + sao_bin_buffer_size = + (64 * (((width + 32) * (height + 32)) >> 10)) + 384; + padded_bin_size = ALIGN(size_single_pipe, HFI_DMA_ALIGNMENT); + size_single_pipe = sao_bin_buffer_size + padded_bin_size; + size_single_pipe = ALIGN(size_single_pipe, HFI_DMA_ALIGNMENT); + bitbin_size = size_single_pipe * num_vpp_pipes; + size = ALIGN(bitbin_size, HFI_DMA_ALIGNMENT) * + total_bitbin_buffers + 512; + + return size; +} + +static u32 h264e_scratch_size(u32 width, u32 height, u32 work_mode, + u32 num_vpp_pipes, u32 rc_type) +{ + return calculate_enc_scratch_size(width, height, work_mode, 16, + num_vpp_pipes, rc_type); +} + +static u32 h265e_scratch_size(u32 width, u32 height, u32 work_mode, + u32 num_vpp_pipes, u32 rc_type) +{ + return calculate_enc_scratch_size(width, height, work_mode, 32, + num_vpp_pipes, rc_type); +} + +static u32 vp8e_scratch_size(u32 width, u32 height, u32 work_mode, + u32 num_vpp_pipes, u32 rc_type) +{ + return calculate_enc_scratch_size(width, height, work_mode, 16, + num_vpp_pipes, rc_type); +} + +static u32 hfi_iris2_h264d_comv_size(u32 width, u32 height, + u32 yuv_buf_min_count) +{ + u32 frame_width_in_mbs = ((width + 15) >> 4); + u32 frame_height_in_mbs = ((height + 15) >> 4); + u32 col_mv_aligned_width = (frame_width_in_mbs << 7); + u32 col_zero_aligned_width = (frame_width_in_mbs << 2); + u32 col_zero_size = 0, size_colloc = 0, comv_size = 0; + + col_mv_aligned_width = ALIGN(col_mv_aligned_width, 16); + col_zero_aligned_width = ALIGN(col_zero_aligned_width, 16); + col_zero_size = + col_zero_aligned_width * ((frame_height_in_mbs + 1) >> 1); + col_zero_size = ALIGN(col_zero_size, 64); + col_zero_size <<= 1; + col_zero_size = ALIGN(col_zero_size, 512); + size_colloc = col_mv_aligned_width * ((frame_height_in_mbs + 1) >> 1); + size_colloc = ALIGN(size_colloc, 64); + size_colloc <<= 1; + size_colloc = ALIGN(size_colloc, 512); + size_colloc += (col_zero_size + SIZE_H264D_BUFTAB_T * 2); + comv_size = size_colloc * yuv_buf_min_count; + comv_size += 512; + + return comv_size; +} + +static u32 size_h264d_bse_cmd_buf(u32 height) +{ + u32 aligned_height = ALIGN(height, 32); + + return min_t(u32, (((aligned_height + 15) >> 4) * 3 * 4), + H264D_MAX_SLICE) * SIZE_H264D_BSE_CMD_PER_BUF; +} + +static u32 size_h264d_vpp_cmd_buf(u32 height) +{ + u32 aligned_height = ALIGN(height, 32); + u32 size; + + size = min_t(u32, (((aligned_height + 15) >> 4) * 3 * 4), + H264D_MAX_SLICE) * SIZE_H264D_VPP_CMD_PER_BUF; + if (size > VPP_CMD_MAX_SIZE) + size = VPP_CMD_MAX_SIZE; + + return size; +} + +static u32 hfi_iris2_h264d_non_comv_size(u32 width, u32 height, + u32 num_vpp_pipes) +{ + u32 size_bse, size_vpp, size; + + size_bse = size_h264d_bse_cmd_buf(height); + size_vpp = size_h264d_vpp_cmd_buf(height); + size = + ALIGN(size_bse, HFI_DMA_ALIGNMENT) + + ALIGN(size_vpp, HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_HW_PIC(SIZE_H264D_HW_PIC_T), HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H264D_LB_FE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H264D_LB_FE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H264D_LB_FE_LEFT_CTRL(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(SIZE_H264D_LB_SE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H264D_LB_SE_LEFT_CTRL(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(SIZE_H264D_LB_PE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H264D_LB_VSP_TOP(width, height), HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H264D_LB_RECON_DMA_METADATA_WR(width, height), + HFI_DMA_ALIGNMENT) * 2 + + ALIGN(SIZE_H264D_QP(width, height), HFI_DMA_ALIGNMENT); + + return ALIGN(size, HFI_DMA_ALIGNMENT); +} + +static u32 size_h265d_bse_cmd_buf(u32 width, u32 height) +{ + u32 size; + + size = (ALIGN(width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * + (ALIGN(height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * + NUM_HW_PIC_BUF; + size = min_t(u32, size, H265D_MAX_SLICE + 1); + size = 2 * size * SIZE_H265D_BSE_CMD_PER_BUF; + + return ALIGN(size, HFI_DMA_ALIGNMENT); +} + +static u32 size_h265d_vpp_cmd_buf(u32 width, u32 height) +{ + u32 size; + + size = (ALIGN(width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * + (ALIGN(height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * + NUM_HW_PIC_BUF; + size = min_t(u32, size, H265D_MAX_SLICE + 1); + size = ALIGN(size, 4); + size = 2 * size * SIZE_H265D_VPP_CMD_PER_BUF; + size = ALIGN(size, HFI_DMA_ALIGNMENT); + if (size > VPP_CMD_MAX_SIZE) + size = VPP_CMD_MAX_SIZE; + + return size; +} + +static u32 hfi_iris2_h265d_comv_size(u32 width, u32 height, + u32 yuv_buf_count_min) +{ + u32 size; + + size = ALIGN(((((width + 15) >> 4) * ((height + 15) >> 4)) << 8), 512); + size *= yuv_buf_count_min; + size += 512; + + return size; +} + +static u32 hfi_iris2_h265d_non_comv_size(u32 width, u32 height, + u32 num_vpp_pipes) +{ + u32 size_bse, size_vpp, size; + + size_bse = size_h265d_bse_cmd_buf(width, height); + size_vpp = size_h265d_vpp_cmd_buf(width, height); + size = + ALIGN(size_bse, HFI_DMA_ALIGNMENT) + + ALIGN(size_vpp, HFI_DMA_ALIGNMENT) + + ALIGN(NUM_HW_PIC_BUF * 20 * 22 * 4, HFI_DMA_ALIGNMENT) + + ALIGN(2 * sizeof(u16) * + (ALIGN(width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * + (ALIGN(height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_HW_PIC(SIZE_H265D_HW_PIC_T), HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H265D_LB_FE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H265D_LB_FE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H265D_LB_FE_LEFT_CTRL(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_h265d_lb_se_left_ctrl(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(SIZE_H265D_LB_SE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H265D_LB_PE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H265D_LB_VSP_TOP(width, height), HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_H265D_LB_VSP_LEFT(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(SIZE_H265D_LB_RECON_DMA_METADATA_WR(width, height), + HFI_DMA_ALIGNMENT) + * 4 + + ALIGN(SIZE_H265D_QP(width, height), HFI_DMA_ALIGNMENT); + + return ALIGN(size, HFI_DMA_ALIGNMENT); +} + +static u32 hfi_iris2_vp8d_comv_size(u32 width, u32 height, + u32 yuv_min_buf_count) +{ + return (((width + 15) >> 4) * ((height + 15) >> 4) * 8 * 2); +} + +static u32 h264d_scratch1_size(u32 width, u32 height, u32 min_buf_count, + bool split_mode_enabled, u32 num_vpp_pipes) +{ + u32 co_mv_size, nonco_mv_size, vpss_lb_size = 0; + + co_mv_size = hfi_iris2_h264d_comv_size(width, height, min_buf_count); + nonco_mv_size = hfi_iris2_h264d_non_comv_size(width, height, + num_vpp_pipes); + if (split_mode_enabled) + vpss_lb_size = size_vpss_lb(width, height, num_vpp_pipes); + + return co_mv_size + nonco_mv_size + vpss_lb_size; +} + +static u32 h265d_scratch1_size(u32 width, u32 height, u32 min_buf_count, + bool split_mode_enabled, u32 num_vpp_pipes) +{ + u32 co_mv_size, nonco_mv_size, vpss_lb_size = 0; + + co_mv_size = hfi_iris2_h265d_comv_size(width, height, min_buf_count); + nonco_mv_size = hfi_iris2_h265d_non_comv_size(width, height, + num_vpp_pipes); + if (split_mode_enabled) + vpss_lb_size = size_vpss_lb(width, height, num_vpp_pipes); + + return co_mv_size + nonco_mv_size + vpss_lb_size + + HDR10_HIST_EXTRADATA_SIZE; +} + +static u32 vp8d_scratch1_size(u32 width, u32 height, u32 min_buf_count, + bool split_mode_enabled, u32 num_vpp_pipes) +{ + u32 vpss_lb_size = 0, size; + + size = hfi_iris2_vp8d_comv_size(width, height, 0); + size += ALIGN(size_vpxd_lb_fe_left_ctrl(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_vpxd_lb_se_left_ctrl(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(SIZE_VP8D_LB_VSP_TOP(width, height), HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VPXD_LB_FE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + 2 * ALIGN(SIZE_VPXD_LB_RECON_DMA_METADATA_WR(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VPXD_LB_SE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VP8D_LB_PE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VP8D_LB_FE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT); + if (split_mode_enabled) + vpss_lb_size = size_vpss_lb(width, height, num_vpp_pipes); + + size += vpss_lb_size; + + return size; +} + +static u32 vp9d_scratch1_size(u32 width, u32 height, u32 min_buf_count, + bool split_mode_enabled, u32 num_vpp_pipes) +{ + u32 vpss_lb_size = 0; + u32 size; + + size = + ALIGN(size_vpxd_lb_fe_left_ctrl(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_vpxd_lb_se_left_ctrl(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(SIZE_VP9D_LB_VSP_TOP(width, height), HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VPXD_LB_FE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + 2 * ALIGN(SIZE_VPXD_LB_RECON_DMA_METADATA_WR(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VPXD_LB_SE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VP9D_LB_PE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VP9D_LB_FE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT); + + if (split_mode_enabled) + vpss_lb_size = size_vpss_lb(width, height, num_vpp_pipes); + + size += vpss_lb_size + HDR10_HIST_EXTRADATA_SIZE; + + return size; +} + +static u32 mpeg2d_scratch1_size(u32 width, u32 height, u32 min_buf_count, + bool split_mode_enabled, u32 num_vpp_pipes) +{ + u32 vpss_lb_size = 0; + u32 size; + + size = + ALIGN(size_vpxd_lb_fe_left_ctrl(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_vpxd_lb_se_left_ctrl(width, height), + HFI_DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(SIZE_VP8D_LB_VSP_TOP(width, height), HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VPXD_LB_FE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + 2 * ALIGN(SIZE_VPXD_LB_RECON_DMA_METADATA_WR(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VPXD_LB_SE_TOP_CTRL(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VP8D_LB_PE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT) + + ALIGN(SIZE_VP8D_LB_FE_TOP_DATA(width, height), + HFI_DMA_ALIGNMENT); + + if (split_mode_enabled) + vpss_lb_size = size_vpss_lb(width, height, num_vpp_pipes); + + size += vpss_lb_size; + + return size; +} + +static u32 +calculate_enc_scratch1_size(u32 width, u32 height, u32 lcu_size, u32 num_ref, + bool ten_bit, u32 num_vpp_pipes, bool is_h265) +{ + u32 line_buf_ctrl_size, line_buf_data_size, leftline_buf_ctrl_size; + u32 line_buf_sde_size, sps_pps_slice_hdr, topline_buf_ctrl_size_FE; + u32 leftline_buf_ctrl_size_FE, line_buf_recon_pix_size; + u32 leftline_buf_recon_pix_size, lambda_lut_size, override_buffer_size; + u32 col_mv_buf_size, vpp_reg_buffer_size, ir_buffer_size; + u32 vpss_line_buf, leftline_buf_meta_recony, h265e_colrcbuf_size; + u32 h265e_framerc_bufsize, h265e_lcubitcnt_bufsize; + u32 h265e_lcubitmap_bufsize, se_stats_bufsize; + u32 bse_reg_buffer_size, bse_slice_cmd_buffer_size, slice_info_bufsize; + u32 line_buf_ctrl_size_buffid2, slice_cmd_buffer_size; + u32 width_lcu_num, height_lcu_num, width_coded, height_coded; + u32 frame_num_lcu, linebuf_meta_recon_uv, topline_bufsize_fe_1stg_sao; + u32 size, bit_depth, num_lcu_mb; + u32 vpss_line_buffer_size_1; + + width_lcu_num = (width + lcu_size - 1) / lcu_size; + height_lcu_num = (height + lcu_size - 1) / lcu_size; + frame_num_lcu = width_lcu_num * height_lcu_num; + width_coded = width_lcu_num * lcu_size; + height_coded = height_lcu_num * lcu_size; + num_lcu_mb = (height_coded / lcu_size) * + ((width_coded + lcu_size * 8) / lcu_size); + slice_info_bufsize = 256 + (frame_num_lcu << 4); + slice_info_bufsize = ALIGN(slice_info_bufsize, HFI_DMA_ALIGNMENT); + line_buf_ctrl_size = ALIGN(width_coded, HFI_DMA_ALIGNMENT); + line_buf_ctrl_size_buffid2 = ALIGN(width_coded, HFI_DMA_ALIGNMENT); + + bit_depth = ten_bit ? 10 : 8; + line_buf_data_size = + (((((bit_depth * width_coded + 1024) + + (HFI_DMA_ALIGNMENT - 1)) & (~(HFI_DMA_ALIGNMENT - 1))) * 1) + + (((((bit_depth * width_coded + 1024) >> 1) + + (HFI_DMA_ALIGNMENT - 1)) & (~(HFI_DMA_ALIGNMENT - 1))) * 2)); + + leftline_buf_ctrl_size = is_h265 ? + ((height_coded + 32) / 32 * 4 * 16) : + ((height_coded + 15) / 16 * 5 * 16); + + if (num_vpp_pipes > 1) { + leftline_buf_ctrl_size += 512; + leftline_buf_ctrl_size = + ALIGN(leftline_buf_ctrl_size, 512) * num_vpp_pipes; + } + + leftline_buf_ctrl_size = + ALIGN(leftline_buf_ctrl_size, HFI_DMA_ALIGNMENT); + leftline_buf_recon_pix_size = (((ten_bit + 1) * 2 * + (height_coded) + HFI_DMA_ALIGNMENT) + + (HFI_DMA_ALIGNMENT << (num_vpp_pipes - 1)) - 1) & + (~((HFI_DMA_ALIGNMENT << (num_vpp_pipes - 1)) - 1)) * 1; + + topline_buf_ctrl_size_FE = is_h265 ? (64 * (width_coded >> 5)) : + (HFI_DMA_ALIGNMENT + 16 * (width_coded >> 4)); + topline_buf_ctrl_size_FE = + ALIGN(topline_buf_ctrl_size_FE, HFI_DMA_ALIGNMENT); + leftline_buf_ctrl_size_FE = + (((HFI_DMA_ALIGNMENT + 64 * (height_coded >> 4)) + + (HFI_DMA_ALIGNMENT << (num_vpp_pipes - 1)) - 1) & + (~((HFI_DMA_ALIGNMENT << (num_vpp_pipes - 1)) - 1)) * 1) * + num_vpp_pipes; + leftline_buf_meta_recony = (HFI_DMA_ALIGNMENT + 64 * + ((height_coded) / (8 * (ten_bit ? 4 : 8)))); + leftline_buf_meta_recony = + ALIGN(leftline_buf_meta_recony, HFI_DMA_ALIGNMENT); + leftline_buf_meta_recony = leftline_buf_meta_recony * num_vpp_pipes; + linebuf_meta_recon_uv = (HFI_DMA_ALIGNMENT + 64 * + ((height_coded) / (4 * (ten_bit ? 4 : 8)))); + linebuf_meta_recon_uv = ALIGN(linebuf_meta_recon_uv, HFI_DMA_ALIGNMENT); + linebuf_meta_recon_uv = linebuf_meta_recon_uv * num_vpp_pipes; + line_buf_recon_pix_size = ((ten_bit ? 3 : 2) * width_coded); + line_buf_recon_pix_size = + ALIGN(line_buf_recon_pix_size, HFI_DMA_ALIGNMENT); + slice_cmd_buffer_size = ALIGN(20480, HFI_DMA_ALIGNMENT); + sps_pps_slice_hdr = 2048 + 4096; + col_mv_buf_size = is_h265 ? (16 * ((frame_num_lcu << 2) + 32)) : + (3 * 16 * (width_lcu_num * height_lcu_num + 32)); + col_mv_buf_size = + ALIGN(col_mv_buf_size, HFI_DMA_ALIGNMENT) * (num_ref + 1); + h265e_colrcbuf_size = + (((width_lcu_num + 7) >> 3) * 16 * 2 * height_lcu_num); + if (num_vpp_pipes > 1) + h265e_colrcbuf_size = + ALIGN(h265e_colrcbuf_size, HFI_DMA_ALIGNMENT) * + num_vpp_pipes; + + h265e_colrcbuf_size = ALIGN(h265e_colrcbuf_size, HFI_DMA_ALIGNMENT) * + HFI_MAX_COL_FRAME; + h265e_framerc_bufsize = (is_h265) ? (256 + 16 * + (14 + (((height_coded >> 5) + 7) >> 3))) : + (256 + 16 * (14 + (((height_coded >> 4) + 7) >> 3))); + h265e_framerc_bufsize *= 6; /* multiply by max numtilescol */ + if (num_vpp_pipes > 1) + h265e_framerc_bufsize = + ALIGN(h265e_framerc_bufsize, HFI_DMA_ALIGNMENT) * + num_vpp_pipes; + + h265e_framerc_bufsize = ALIGN(h265e_framerc_bufsize, 512) * + HFI_MAX_COL_FRAME; + h265e_lcubitcnt_bufsize = 256 + 4 * frame_num_lcu; + h265e_lcubitcnt_bufsize = + ALIGN(h265e_lcubitcnt_bufsize, HFI_DMA_ALIGNMENT); + h265e_lcubitmap_bufsize = 256 + (frame_num_lcu >> 3); + h265e_lcubitmap_bufsize = + ALIGN(h265e_lcubitmap_bufsize, HFI_DMA_ALIGNMENT); + line_buf_sde_size = 256 + 16 * (width_coded >> 4); + line_buf_sde_size = ALIGN(line_buf_sde_size, HFI_DMA_ALIGNMENT); + if ((width_coded * height_coded) > (4096 * 2160)) + se_stats_bufsize = 0; + else if ((width_coded * height_coded) > (1920 * 1088)) + se_stats_bufsize = (40 * 4 * frame_num_lcu + 256 + 256); + else + se_stats_bufsize = (1024 * frame_num_lcu + 256 + 256); + + se_stats_bufsize = ALIGN(se_stats_bufsize, HFI_DMA_ALIGNMENT) * 2; + bse_slice_cmd_buffer_size = (((8192 << 2) + 7) & (~7)) * 6; + bse_reg_buffer_size = (((512 << 3) + 7) & (~7)) * 4; + vpp_reg_buffer_size = + (((HFI_VENUS_VPPSG_MAX_REGISTERS << 3) + 31) & (~31)) * 10; + lambda_lut_size = 256 * 11; + override_buffer_size = 16 * ((num_lcu_mb + 7) >> 3); + override_buffer_size = + ALIGN(override_buffer_size, HFI_DMA_ALIGNMENT) * 2; + ir_buffer_size = (((frame_num_lcu << 1) + 7) & (~7)) * 3; + vpss_line_buffer_size_1 = (((8192 >> 2) << 5) * num_vpp_pipes) + 64; + vpss_line_buf = + (((((max(width_coded, height_coded) + 3) >> 2) << 5) + 256) * + 16) + vpss_line_buffer_size_1; + topline_bufsize_fe_1stg_sao = 16 * (width_coded >> 5); + topline_bufsize_fe_1stg_sao = + ALIGN(topline_bufsize_fe_1stg_sao, HFI_DMA_ALIGNMENT); + + size = + line_buf_ctrl_size + line_buf_data_size + + line_buf_ctrl_size_buffid2 + leftline_buf_ctrl_size + + vpss_line_buf + col_mv_buf_size + topline_buf_ctrl_size_FE + + leftline_buf_ctrl_size_FE + line_buf_recon_pix_size + + leftline_buf_recon_pix_size + + leftline_buf_meta_recony + linebuf_meta_recon_uv + + h265e_colrcbuf_size + h265e_framerc_bufsize + + h265e_lcubitcnt_bufsize + h265e_lcubitmap_bufsize + + line_buf_sde_size + + topline_bufsize_fe_1stg_sao + override_buffer_size + + bse_reg_buffer_size + vpp_reg_buffer_size + sps_pps_slice_hdr + + slice_cmd_buffer_size + bse_slice_cmd_buffer_size + + ir_buffer_size + slice_info_bufsize + lambda_lut_size + + se_stats_bufsize + 1024; + + return size; +} + +static u32 h264e_scratch1_size(u32 width, u32 height, u32 num_ref, bool ten_bit, + u32 num_vpp_pipes) +{ + return calculate_enc_scratch1_size(width, height, 16, num_ref, ten_bit, + num_vpp_pipes, false); +} + +static u32 h265e_scratch1_size(u32 width, u32 height, u32 num_ref, bool ten_bit, + u32 num_vpp_pipes) +{ + return calculate_enc_scratch1_size(width, height, 32, num_ref, ten_bit, + num_vpp_pipes, true); +} + +static u32 vp8e_scratch1_size(u32 width, u32 height, u32 num_ref, bool ten_bit, + u32 num_vpp_pipes) +{ + return calculate_enc_scratch1_size(width, height, 16, num_ref, ten_bit, + 1, false); +} + +static u32 ubwc_metadata_plane_stride(u32 width, u32 metadata_stride_multi, + u32 tile_width_pels) +{ + return ALIGN(((width + (tile_width_pels - 1)) / tile_width_pels), + metadata_stride_multi); +} + +static u32 ubwc_metadata_plane_bufheight(u32 height, u32 metadata_height_multi, + u32 tile_height_pels) +{ + return ALIGN(((height + (tile_height_pels - 1)) / tile_height_pels), + metadata_height_multi); +} + +static u32 ubwc_metadata_plane_buffer_size(u32 metadata_stride, + u32 metadata_buf_height) +{ + return ALIGN(metadata_stride * metadata_buf_height, SZ_4K); +} + +static u32 enc_scratch2_size(u32 width, u32 height, u32 num_ref, bool ten_bit) +{ + u32 aligned_width, aligned_height, chroma_height, ref_buf_height; + u32 luma_size, chroma_size; + u32 metadata_stride, meta_buf_height, meta_size_y, meta_size_c; + u32 ref_luma_stride_bytes, ref_chroma_height_bytes; + u32 ref_buf_size, ref_stride; + u32 size; + + if (!ten_bit) { + aligned_height = ALIGN(height, HFI_VENUS_HEIGHT_ALIGNMENT); + chroma_height = height >> 1; + chroma_height = ALIGN(chroma_height, + HFI_VENUS_HEIGHT_ALIGNMENT); + aligned_width = ALIGN(width, HFI_VENUS_WIDTH_ALIGNMENT); + metadata_stride = + ubwc_metadata_plane_stride(width, 64, + NV12_UBWC_Y_TILE_WIDTH); + meta_buf_height = + ubwc_metadata_plane_bufheight(height, 16, + NV12_UBWC_Y_TILE_HEIGHT); + meta_size_y = ubwc_metadata_plane_buffer_size(metadata_stride, + meta_buf_height); + meta_size_c = ubwc_metadata_plane_buffer_size(metadata_stride, + meta_buf_height); + size = (aligned_height + chroma_height) * aligned_width + + meta_size_y + meta_size_c; + size = (size * (num_ref + 3)) + 4096; + } else { + ref_buf_height = (height + (HFI_VENUS_HEIGHT_ALIGNMENT - 1)) + & (~(HFI_VENUS_HEIGHT_ALIGNMENT - 1)); + ref_luma_stride_bytes = + ((width + SYSTEM_LAL_TILE10 - 1) / SYSTEM_LAL_TILE10) * + SYSTEM_LAL_TILE10; + ref_stride = 4 * (ref_luma_stride_bytes / 3); + ref_stride = (ref_stride + (128 - 1)) & (~(128 - 1)); + luma_size = ref_buf_height * ref_stride; + ref_chroma_height_bytes = (((height + 1) >> 1) + + (32 - 1)) & (~(32 - 1)); + chroma_size = ref_stride * ref_chroma_height_bytes; + luma_size = (luma_size + (SZ_4K - 1)) & (~(SZ_4K - 1)); + chroma_size = (chroma_size + (SZ_4K - 1)) & (~(SZ_4K - 1)); + ref_buf_size = luma_size + chroma_size; + metadata_stride = + ubwc_metadata_plane_stride(width, + METADATA_STRIDE_MULTIPLE, + TP10_UBWC_Y_TILE_WIDTH); + meta_buf_height = + ubwc_metadata_plane_bufheight(height, + METADATA_HEIGHT_MULTIPLE, + TP10_UBWC_Y_TILE_HEIGHT); + meta_size_y = ubwc_metadata_plane_buffer_size(metadata_stride, + meta_buf_height); + meta_size_c = ubwc_metadata_plane_buffer_size(metadata_stride, + meta_buf_height); + size = ref_buf_size + meta_size_y + meta_size_c; + size = (size * (num_ref + 3)) + 4096; + } + + return size; +} + +static u32 enc_persist_size(void) +{ + return HFI_IRIS2_ENC_PERSIST_SIZE; +} + +static u32 h264d_persist1_size(void) +{ + return ALIGN((SIZE_SLIST_BUF_H264 * NUM_SLIST_BUF_H264 + + NUM_HW_PIC_BUF * SIZE_SEI_USERDATA), HFI_DMA_ALIGNMENT); +} + +static u32 h265d_persist1_size(void) +{ + return ALIGN((SIZE_SLIST_BUF_H265 * NUM_SLIST_BUF_H265 + H265_NUM_TILE + * sizeof(u32)), HFI_DMA_ALIGNMENT); +} + +static u32 vp8d_persist1_size(void) +{ + return ALIGN(VP8_NUM_PROBABILITY_TABLE_BUF * VP8_PROB_TABLE_SIZE, + HFI_DMA_ALIGNMENT); +} + +static u32 vp9d_persist1_size(void) +{ + return + ALIGN(VP9_NUM_PROBABILITY_TABLE_BUF * VP9_PROB_TABLE_SIZE, + HFI_DMA_ALIGNMENT) + + ALIGN(HFI_IRIS2_VP9D_COMV_SIZE, HFI_DMA_ALIGNMENT) + + ALIGN(MAX_SUPERFRAME_HEADER_LEN, HFI_DMA_ALIGNMENT) + + ALIGN(VP9_UDC_HEADER_BUF_SIZE, HFI_DMA_ALIGNMENT) + + ALIGN(VP9_NUM_FRAME_INFO_BUF * CCE_TILE_OFFSET_SIZE, + HFI_DMA_ALIGNMENT); +} + +static u32 mpeg2d_persist1_size(void) +{ + return QMATRIX_SIZE + MP2D_QPDUMP_SIZE; +} + +struct dec_bufsize_ops { + u32 (*scratch)(u32 width, u32 height, bool is_interlaced); + u32 (*scratch1)(u32 width, u32 height, u32 min_buf_count, + bool split_mode_enabled, u32 num_vpp_pipes); + u32 (*persist1)(void); +}; + +struct enc_bufsize_ops { + u32 (*scratch)(u32 width, u32 height, u32 work_mode, u32 num_vpp_pipes, + u32 rc_type); + u32 (*scratch1)(u32 width, u32 height, u32 num_ref, bool ten_bit, + u32 num_vpp_pipes); + u32 (*scratch2)(u32 width, u32 height, u32 num_ref, bool ten_bit); + u32 (*persist)(void); +}; + +static struct dec_bufsize_ops dec_h264_ops = { + .scratch = h264d_scratch_size, + .scratch1 = h264d_scratch1_size, + .persist1 = h264d_persist1_size, +}; + +static struct dec_bufsize_ops dec_h265_ops = { + .scratch = h265d_scratch_size, + .scratch1 = h265d_scratch1_size, + .persist1 = h265d_persist1_size, +}; + +static struct dec_bufsize_ops dec_vp8_ops = { + .scratch = vpxd_scratch_size, + .scratch1 = vp8d_scratch1_size, + .persist1 = vp8d_persist1_size, +}; + +static struct dec_bufsize_ops dec_vp9_ops = { + .scratch = vpxd_scratch_size, + .scratch1 = vp9d_scratch1_size, + .persist1 = vp9d_persist1_size, +}; + +static struct dec_bufsize_ops dec_mpeg2_ops = { + .scratch = mpeg2d_scratch_size, + .scratch1 = mpeg2d_scratch1_size, + .persist1 = mpeg2d_persist1_size, +}; + +static struct enc_bufsize_ops enc_h264_ops = { + .scratch = h264e_scratch_size, + .scratch1 = h264e_scratch1_size, + .scratch2 = enc_scratch2_size, + .persist = enc_persist_size, +}; + +static struct enc_bufsize_ops enc_h265_ops = { + .scratch = h265e_scratch_size, + .scratch1 = h265e_scratch1_size, + .scratch2 = enc_scratch2_size, + .persist = enc_persist_size, +}; + +static struct enc_bufsize_ops enc_vp8_ops = { + .scratch = vp8e_scratch_size, + .scratch1 = vp8e_scratch1_size, + .scratch2 = enc_scratch2_size, + .persist = enc_persist_size, +}; + +static u32 +calculate_dec_input_frame_size(u32 width, u32 height, u32 codec, + u32 max_mbs_per_frame, u32 buffer_size_limit) +{ + u32 frame_size, num_mbs; + u32 div_factor = 1; + u32 base_res_mbs = NUM_MBS_4K; + + /* + * Decoder input size calculation: + * If clip is 8k buffer size is calculated for 8k : 8k mbs/4 + * For 8k cases we expect width/height to be set always. + * In all other cases size is calculated for 4k: + * 4k mbs for VP8/VP9 and 4k/2 for remaining codecs + */ + num_mbs = (ALIGN(height, 16) * ALIGN(width, 16)) / 256; + if (num_mbs > NUM_MBS_4K) { + div_factor = 4; + base_res_mbs = max_mbs_per_frame; + } else { + base_res_mbs = NUM_MBS_4K; + if (codec == V4L2_PIX_FMT_VP9) + div_factor = 1; + else + div_factor = 2; + } + + frame_size = base_res_mbs * MB_SIZE_IN_PIXEL * 3 / 2 / div_factor; + + /* multiply by 10/8 (1.25) to get size for 10 bit case */ + if (codec == V4L2_PIX_FMT_VP9 || codec == V4L2_PIX_FMT_HEVC) + frame_size = frame_size + (frame_size >> 2); + + if (buffer_size_limit && buffer_size_limit < frame_size) + frame_size = buffer_size_limit; + + return ALIGN(frame_size, SZ_4K); +} + +static int output_buffer_count(u32 session_type, u32 codec) +{ + u32 output_min_count; + + if (session_type == VIDC_SESSION_TYPE_DEC) { + switch (codec) { + case V4L2_PIX_FMT_MPEG2: + case V4L2_PIX_FMT_VP8: + output_min_count = 6; + break; + case V4L2_PIX_FMT_VP9: + output_min_count = 11; + break; + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_HEVC: + default: + output_min_count = 18; + break; + } + } else { + output_min_count = MIN_ENC_OUTPUT_BUFFERS; + } + + return output_min_count; +} + +static int bufreq_dec(struct hfi_plat_buffers_params *params, u32 buftype, + struct hfi_buffer_requirements *bufreq) +{ + enum hfi_version version = params->version; + u32 codec = params->codec; + u32 width = params->width, height = params->height, out_min_count; + struct dec_bufsize_ops *dec_ops; + bool is_secondary_output = params->dec.is_secondary_output; + bool is_interlaced = params->dec.is_interlaced; + u32 max_mbs_per_frame = params->dec.max_mbs_per_frame; + u32 buffer_size_limit = params->dec.buffer_size_limit; + u32 num_vpp_pipes = params->num_vpp_pipes; + + switch (codec) { + case V4L2_PIX_FMT_H264: + dec_ops = &dec_h264_ops; + break; + case V4L2_PIX_FMT_HEVC: + dec_ops = &dec_h265_ops; + break; + case V4L2_PIX_FMT_VP8: + dec_ops = &dec_vp8_ops; + break; + case V4L2_PIX_FMT_VP9: + dec_ops = &dec_vp9_ops; + break; + case V4L2_PIX_FMT_MPEG2: + dec_ops = &dec_mpeg2_ops; + break; + default: + return -EINVAL; + } + + out_min_count = output_buffer_count(VIDC_SESSION_TYPE_DEC, codec); + /* Max of driver and FW count */ + out_min_count = max(out_min_count, bufreq->count_min); + + bufreq->type = buftype; + bufreq->region_size = 0; + bufreq->count_min = 1; + bufreq->count_actual = 1; + bufreq->hold_count = 1; + bufreq->contiguous = 1; + bufreq->alignment = 256; + + if (buftype == HFI_BUFFER_INPUT) { + bufreq->count_min = MIN_INPUT_BUFFERS; + bufreq->size = + calculate_dec_input_frame_size(width, height, codec, + max_mbs_per_frame, + buffer_size_limit); + } else if (buftype == HFI_BUFFER_OUTPUT || + buftype == HFI_BUFFER_OUTPUT2) { + bufreq->count_min = out_min_count; + bufreq->size = + venus_helper_get_framesz_raw(params->hfi_color_fmt, + width, height); + } else if (buftype == HFI_BUFFER_INTERNAL_SCRATCH(version)) { + bufreq->size = dec_ops->scratch(width, height, is_interlaced); + } else if (buftype == HFI_BUFFER_INTERNAL_SCRATCH_1(version)) { + bufreq->size = dec_ops->scratch1(width, height, VB2_MAX_FRAME, + is_secondary_output, + num_vpp_pipes); + } else if (buftype == HFI_BUFFER_INTERNAL_PERSIST_1) { + bufreq->size = dec_ops->persist1(); + } else { + bufreq->size = 0; + } + + return 0; +} + +static int bufreq_enc(struct hfi_plat_buffers_params *params, u32 buftype, + struct hfi_buffer_requirements *bufreq) +{ + enum hfi_version version = params->version; + struct enc_bufsize_ops *enc_ops; + u32 width = params->width; + u32 height = params->height; + bool is_tenbit = params->enc.is_tenbit; + u32 num_bframes = params->enc.num_b_frames; + u32 codec = params->codec; + u32 work_mode = params->enc.work_mode; + u32 rc_type = params->enc.rc_type; + u32 num_vpp_pipes = params->num_vpp_pipes; + u32 num_ref; + + switch (codec) { + case V4L2_PIX_FMT_H264: + enc_ops = &enc_h264_ops; + break; + case V4L2_PIX_FMT_HEVC: + enc_ops = &enc_h265_ops; + break; + case V4L2_PIX_FMT_VP8: + enc_ops = &enc_vp8_ops; + break; + default: + return -EINVAL; + } + + num_ref = num_bframes > 0 ? num_bframes + 1 : 1; + + bufreq->type = buftype; + bufreq->region_size = 0; + bufreq->count_min = 1; + bufreq->count_actual = 1; + bufreq->hold_count = 1; + bufreq->contiguous = 1; + bufreq->alignment = 256; + + if (buftype == HFI_BUFFER_INPUT) { + bufreq->count_min = MIN_INPUT_BUFFERS; + bufreq->size = + venus_helper_get_framesz_raw(params->hfi_color_fmt, + width, height); + } else if (buftype == HFI_BUFFER_OUTPUT || + buftype == HFI_BUFFER_OUTPUT2) { + bufreq->count_min = + output_buffer_count(VIDC_SESSION_TYPE_ENC, codec); + bufreq->size = calculate_enc_output_frame_size(width, height, + rc_type); + } else if (buftype == HFI_BUFFER_INTERNAL_SCRATCH(version)) { + bufreq->size = enc_ops->scratch(width, height, work_mode, + num_vpp_pipes, rc_type); + } else if (buftype == HFI_BUFFER_INTERNAL_SCRATCH_1(version)) { + bufreq->size = enc_ops->scratch1(width, height, num_ref, + is_tenbit, num_vpp_pipes); + } else if (buftype == HFI_BUFFER_INTERNAL_SCRATCH_2(version)) { + bufreq->size = enc_ops->scratch2(width, height, num_ref, + is_tenbit); + } else if (buftype == HFI_BUFFER_INTERNAL_PERSIST) { + bufreq->size = enc_ops->persist(); + } else { + bufreq->size = 0; + } + + return 0; +} + +int hfi_plat_bufreq_v6(struct hfi_plat_buffers_params *params, u32 session_type, + u32 buftype, struct hfi_buffer_requirements *bufreq) +{ + if (session_type == VIDC_SESSION_TYPE_DEC) + return bufreq_dec(params, buftype, bufreq); + else + return bufreq_enc(params, buftype, bufreq); +} diff --git a/drivers/media/platform/qcom/venus/hfi_platform.c b/drivers/media/platform/qcom/venus/hfi_platform.c new file mode 100644 index 000000000..f07f554bc --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_platform.c @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +#include +#include "hfi_platform.h" +#include "core.h" + +const struct hfi_platform *hfi_platform_get(enum hfi_version version) +{ + switch (version) { + case HFI_VERSION_4XX: + return &hfi_plat_v4; + case HFI_VERSION_6XX: + return &hfi_plat_v6; + default: + break; + } + + return NULL; +} + +unsigned long +hfi_platform_get_codec_vpp_freq(enum hfi_version version, u32 codec, u32 session_type) +{ + const struct hfi_platform *plat; + unsigned long freq = 0; + + plat = hfi_platform_get(version); + if (!plat) + return 0; + + if (plat->codec_vpp_freq) + freq = plat->codec_vpp_freq(session_type, codec); + + return freq; +} + +unsigned long +hfi_platform_get_codec_vsp_freq(enum hfi_version version, u32 codec, u32 session_type) +{ + const struct hfi_platform *plat; + unsigned long freq = 0; + + plat = hfi_platform_get(version); + if (!plat) + return 0; + + if (plat->codec_vpp_freq) + freq = plat->codec_vsp_freq(session_type, codec); + + return freq; +} + +unsigned long +hfi_platform_get_codec_lp_freq(enum hfi_version version, u32 codec, u32 session_type) +{ + const struct hfi_platform *plat; + unsigned long freq = 0; + + plat = hfi_platform_get(version); + if (!plat) + return 0; + + if (plat->codec_lp_freq) + freq = plat->codec_lp_freq(session_type, codec); + + return freq; +} + +int +hfi_platform_get_codecs(struct venus_core *core, u32 *enc_codecs, u32 *dec_codecs, u32 *count) +{ + const struct hfi_platform *plat; + + plat = hfi_platform_get(core->res->hfi_version); + if (!plat) + return -EINVAL; + + if (plat->codecs) + plat->codecs(enc_codecs, dec_codecs, count); + + if (of_device_is_compatible(core->dev->of_node, "qcom,sc7280-venus")) { + *enc_codecs &= ~HFI_VIDEO_CODEC_VP8; + *dec_codecs &= ~HFI_VIDEO_CODEC_VP8; + } + + return 0; +} + diff --git a/drivers/media/platform/qcom/venus/hfi_platform.h b/drivers/media/platform/qcom/venus/hfi_platform.h new file mode 100644 index 000000000..ec89a90a8 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_platform.h @@ -0,0 +1,71 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ + +#ifndef __HFI_PLATFORM_H__ +#define __HFI_PLATFORM_H__ + +#include +#include + +#include "hfi.h" +#include "hfi_plat_bufs.h" +#include "hfi_helper.h" + +#define MAX_PLANES 4 +#define MAX_FMT_ENTRIES 32 +#define MAX_CAP_ENTRIES 32 +#define MAX_ALLOC_MODE_ENTRIES 16 +#define MAX_CODEC_NUM 32 +#define MAX_SESSIONS 16 + +struct raw_formats { + u32 buftype; + u32 fmt; +}; + +struct hfi_plat_caps { + u32 codec; + u32 domain; + bool cap_bufs_mode_dynamic; + unsigned int num_caps; + struct hfi_capability caps[MAX_CAP_ENTRIES]; + unsigned int num_pl; + struct hfi_profile_level pl[HFI_MAX_PROFILE_COUNT]; + unsigned int num_fmts; + struct raw_formats fmts[MAX_FMT_ENTRIES]; + bool valid; /* used only for Venus v1xx */ +}; + +struct hfi_platform_codec_freq_data { + u32 pixfmt; + u32 session_type; + unsigned long vpp_freq; + unsigned long vsp_freq; + unsigned long low_power_freq; +}; + +struct hfi_platform { + unsigned long (*codec_vpp_freq)(u32 session_type, u32 codec); + unsigned long (*codec_vsp_freq)(u32 session_type, u32 codec); + unsigned long (*codec_lp_freq)(u32 session_type, u32 codec); + void (*codecs)(u32 *enc_codecs, u32 *dec_codecs, u32 *count); + const struct hfi_plat_caps *(*capabilities)(unsigned int *entries); + int (*bufreq)(struct hfi_plat_buffers_params *params, u32 session_type, + u32 buftype, struct hfi_buffer_requirements *bufreq); +}; + +extern const struct hfi_platform hfi_plat_v4; +extern const struct hfi_platform hfi_plat_v6; + +const struct hfi_platform *hfi_platform_get(enum hfi_version version); +unsigned long hfi_platform_get_codec_vpp_freq(enum hfi_version version, u32 codec, + u32 session_type); +unsigned long hfi_platform_get_codec_vsp_freq(enum hfi_version version, u32 codec, + u32 session_type); +unsigned long hfi_platform_get_codec_lp_freq(enum hfi_version version, u32 codec, + u32 session_type); +int hfi_platform_get_codecs(struct venus_core *core, u32 *enc_codecs, u32 *dec_codecs, + u32 *count); +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_platform_v4.c b/drivers/media/platform/qcom/venus/hfi_platform_v4.c new file mode 100644 index 000000000..e3f0a90a5 --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_platform_v4.c @@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +#include "hfi_platform.h" + +static const struct hfi_plat_caps caps[] = { +{ + .codec = HFI_VIDEO_CODEC_H264, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 96, 4096, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 96, 4096, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 1, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 120000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 4096, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 4096, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 1, 2073600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 480, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 1, 2, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 3, 1}, + .num_caps = 10, + .pl[0] = {HFI_H264_PROFILE_BASELINE, HFI_H264_LEVEL_52}, + .pl[1] = {HFI_H264_PROFILE_MAIN, HFI_H264_LEVEL_52}, + .pl[2] = {HFI_H264_PROFILE_HIGH, HFI_H264_LEVEL_52}, + .pl[3] = {HFI_H264_PROFILE_CONSTRAINED_BASE, HFI_H264_LEVEL_52}, + .pl[4] = {HFI_H264_PROFILE_CONSTRAINED_HIGH, HFI_H264_LEVEL_52}, + .num_pl = 5, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_HEVC, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 96, 4096, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 96, 4096, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 1, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 120000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 4096, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 4096, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 1, 2073600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 480, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 1, 2, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 3, 1}, + .num_caps = 10, + .pl[0] = {HFI_HEVC_PROFILE_MAIN, HFI_HEVC_LEVEL_6 | HFI_HEVC_TIER_HIGH0 << 28}, + .pl[1] = {HFI_HEVC_PROFILE_MAIN10, HFI_HEVC_LEVEL_6 | HFI_HEVC_TIER_HIGH0 << 28}, + .num_pl = 2, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[4] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .fmts[5] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_P010}, + .fmts[6] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .num_fmts = 7, +}, { + .codec = HFI_VIDEO_CODEC_VP8, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 96, 4096, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 96, 4096, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 1, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 120000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 4096, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 4096, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 1, 2073600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 480, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 1, 2, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 3, 1}, + .num_caps = 10, + .pl[0] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_0}, + .pl[1] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_1}, + .pl[2] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_2}, + .pl[3] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_3}, + .num_pl = 4, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_VP9, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 96, 4096, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 96, 4096, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 1, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 120000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 4096, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 4096, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 1, 2073600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 480, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 1, 2, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 3, 1}, + .num_caps = 10, + .pl[0] = {HFI_VP9_PROFILE_P0, 200}, + .pl[1] = {HFI_VP9_PROFILE_P2_10B, 200}, + .num_pl = 2, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[4] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .fmts[5] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_P010}, + .fmts[6] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .num_fmts = 7, +}, { + .codec = HFI_VIDEO_CODEC_MPEG2, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 96, 1920, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 96, 1920, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 1, 8160, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 40000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 4096, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 4096, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 1, 244800, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 30, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 1, 2, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 1, 1}, + .num_caps = 10, + .pl[0] = {HFI_MPEG2_PROFILE_SIMPLE, HFI_MPEG2_LEVEL_H14}, + .pl[1] = {HFI_MPEG2_PROFILE_MAIN, HFI_MPEG2_LEVEL_H14}, + .num_pl = 2, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_H264, + .domain = VIDC_SESSION_TYPE_ENC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 96, 4096, 16}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 96, 4096, 16}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 1, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 120000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 8192, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 8192, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 1, 1036800, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 480, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 1, 3, 1}, + .caps[9] = {HFI_CAPABILITY_PEAKBITRATE, 32000, 160000000, 1}, + .caps[10] = {HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS, 0, 5, 1}, + .caps[11] = {HFI_CAPABILITY_ENC_LTR_COUNT, 0, 4, 1}, + .caps[12] = {HFI_CAPABILITY_LCU_SIZE, 16, 16, 1}, + .caps[13] = {HFI_CAPABILITY_BFRAME, 0, 1, 1}, + .caps[14] = {HFI_CAPABILITY_HIER_P_HYBRID_NUM_ENH_LAYERS, 0, 5, 1}, + .caps[15] = {HFI_CAPABILITY_I_FRAME_QP, 0, 51, 1}, + .caps[16] = {HFI_CAPABILITY_P_FRAME_QP, 0, 51, 1}, + .caps[17] = {HFI_CAPABILITY_B_FRAME_QP, 0, 51, 1}, + .caps[18] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 2, 1}, + .caps[19] = {HFI_CAPABILITY_RATE_CONTROL_MODES, 0x1000001, 0x1000005, 1}, + .caps[20] = {HFI_CAPABILITY_COLOR_SPACE_CONVERSION, 0, 2, 1}, + .num_caps = 21, + .pl[0] = {HFI_H264_PROFILE_BASELINE, HFI_H264_LEVEL_52}, + .pl[1] = {HFI_H264_PROFILE_MAIN, HFI_H264_LEVEL_52}, + .pl[2] = {HFI_H264_PROFILE_HIGH, HFI_H264_LEVEL_52}, + .pl[3] = {HFI_H264_PROFILE_CONSTRAINED_BASE, HFI_H264_LEVEL_52}, + .pl[4] = {HFI_H264_PROFILE_CONSTRAINED_HIGH, HFI_H264_LEVEL_52}, + .num_pl = 5, + .fmts[0] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12}, + .fmts[1] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[3] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_P010}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_HEVC, + .domain = VIDC_SESSION_TYPE_ENC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 96, 4096, 16}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 96, 4096, 16}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 1, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 120000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 8192, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 8192, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 1, 1036800, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 480, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 1, 3, 1}, + .caps[9] = {HFI_CAPABILITY_PEAKBITRATE, 32000, 160000000, 1}, + .caps[10] = {HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS, 0, 5, 1}, + .caps[11] = {HFI_CAPABILITY_ENC_LTR_COUNT, 0, 4, 1}, + .caps[12] = {HFI_CAPABILITY_LCU_SIZE, 32, 32, 1}, + .caps[13] = {HFI_CAPABILITY_BFRAME, 0, 1, 1}, + .caps[14] = {HFI_CAPABILITY_HIER_P_HYBRID_NUM_ENH_LAYERS, 0, 5, 1}, + .caps[15] = {HFI_CAPABILITY_I_FRAME_QP, 0, 63, 1}, + .caps[16] = {HFI_CAPABILITY_P_FRAME_QP, 0, 63, 1}, + .caps[17] = {HFI_CAPABILITY_B_FRAME_QP, 0, 63, 1}, + .caps[18] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 2, 1}, + .caps[19] = {HFI_CAPABILITY_RATE_CONTROL_MODES, 0x1000001, 0x1000005, 1}, + .caps[20] = {HFI_CAPABILITY_COLOR_SPACE_CONVERSION, 0, 2, 1}, + .caps[21] = {HFI_CAPABILITY_ROTATION, 1, 4, 90}, + .caps[22] = {HFI_CAPABILITY_BLUR_WIDTH, 96, 4096, 16}, + .caps[23] = {HFI_CAPABILITY_BLUR_HEIGHT, 96, 4096, 16}, + .num_caps = 24, + .pl[0] = {HFI_HEVC_PROFILE_MAIN, HFI_HEVC_LEVEL_6 | HFI_HEVC_TIER_HIGH0}, + .pl[1] = {HFI_HEVC_PROFILE_MAIN10, HFI_HEVC_LEVEL_6 | HFI_HEVC_TIER_HIGH0}, + .num_pl = 2, + .fmts[0] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12}, + .fmts[1] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[3] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_P010}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_VP8, + .domain = VIDC_SESSION_TYPE_ENC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 96, 4096, 16}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 96, 4096, 16}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 1, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 120000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 8192, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 8192, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 1, 1036800, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 240, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 1, 3, 1}, + .caps[9] = {HFI_CAPABILITY_PEAKBITRATE, 32000, 160000000, 1}, + .caps[10] = {HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS, 0, 3, 1}, + .caps[11] = {HFI_CAPABILITY_ENC_LTR_COUNT, 0, 2, 1}, + .caps[12] = {HFI_CAPABILITY_LCU_SIZE, 16, 16, 1}, + .caps[13] = {HFI_CAPABILITY_BFRAME, 0, 1, 1}, + .caps[14] = {HFI_CAPABILITY_HIER_P_HYBRID_NUM_ENH_LAYERS, 0, 5, 1}, + .caps[15] = {HFI_CAPABILITY_I_FRAME_QP, 0, 127, 1}, + .caps[16] = {HFI_CAPABILITY_P_FRAME_QP, 0, 127, 1}, + .caps[17] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 2, 1}, + .caps[18] = {HFI_CAPABILITY_RATE_CONTROL_MODES, 0x1000001, 0x1000005, 1}, + .caps[19] = {HFI_CAPABILITY_BLUR_WIDTH, 96, 4096, 16}, + .caps[20] = {HFI_CAPABILITY_BLUR_HEIGHT, 96, 4096, 16}, + .caps[21] = {HFI_CAPABILITY_COLOR_SPACE_CONVERSION, 0, 2, 1}, + .caps[22] = {HFI_CAPABILITY_ROTATION, 1, 4, 90}, + .num_caps = 23, + .pl[0] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_0}, + .pl[1] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_1}, + .pl[2] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_2}, + .pl[3] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_3}, + .num_pl = 4, + .fmts[0] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12}, + .fmts[1] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[3] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_P010}, + .num_fmts = 4, +} }; + +static const struct hfi_plat_caps *get_capabilities(unsigned int *entries) +{ + *entries = ARRAY_SIZE(caps); + return caps; +} + +static void get_codecs(u32 *enc_codecs, u32 *dec_codecs, u32 *count) +{ + *enc_codecs = HFI_VIDEO_CODEC_H264 | HFI_VIDEO_CODEC_HEVC | + HFI_VIDEO_CODEC_VP8; + *dec_codecs = HFI_VIDEO_CODEC_H264 | HFI_VIDEO_CODEC_HEVC | + HFI_VIDEO_CODEC_VP8 | HFI_VIDEO_CODEC_VP9 | + HFI_VIDEO_CODEC_MPEG2; + *count = 8; +} + +static const struct hfi_platform_codec_freq_data codec_freq_data[] = { + { V4L2_PIX_FMT_H264, VIDC_SESSION_TYPE_ENC, 675, 10, 320 }, + { V4L2_PIX_FMT_HEVC, VIDC_SESSION_TYPE_ENC, 675, 10, 320 }, + { V4L2_PIX_FMT_VP8, VIDC_SESSION_TYPE_ENC, 675, 10, 320 }, + { V4L2_PIX_FMT_MPEG2, VIDC_SESSION_TYPE_DEC, 200, 10, 200 }, + { V4L2_PIX_FMT_H264, VIDC_SESSION_TYPE_DEC, 200, 10, 200 }, + { V4L2_PIX_FMT_HEVC, VIDC_SESSION_TYPE_DEC, 200, 10, 200 }, + { V4L2_PIX_FMT_VP8, VIDC_SESSION_TYPE_DEC, 200, 10, 200 }, + { V4L2_PIX_FMT_VP9, VIDC_SESSION_TYPE_DEC, 200, 10, 200 }, +}; + +static const struct hfi_platform_codec_freq_data * +get_codec_freq_data(u32 session_type, u32 pixfmt) +{ + const struct hfi_platform_codec_freq_data *data = codec_freq_data; + unsigned int i, data_size = ARRAY_SIZE(codec_freq_data); + const struct hfi_platform_codec_freq_data *found = NULL; + + for (i = 0; i < data_size; i++) { + if (data[i].pixfmt == pixfmt && data[i].session_type == session_type) { + found = &data[i]; + break; + } + } + + return found; +} + +static unsigned long codec_vpp_freq(u32 session_type, u32 codec) +{ + const struct hfi_platform_codec_freq_data *data; + + data = get_codec_freq_data(session_type, codec); + if (data) + return data->vpp_freq; + + return 0; +} + +static unsigned long codec_vsp_freq(u32 session_type, u32 codec) +{ + const struct hfi_platform_codec_freq_data *data; + + data = get_codec_freq_data(session_type, codec); + if (data) + return data->vsp_freq; + + return 0; +} + +static unsigned long codec_lp_freq(u32 session_type, u32 codec) +{ + const struct hfi_platform_codec_freq_data *data; + + data = get_codec_freq_data(session_type, codec); + if (data) + return data->low_power_freq; + + return 0; +} + +const struct hfi_platform hfi_plat_v4 = { + .codec_vpp_freq = codec_vpp_freq, + .codec_vsp_freq = codec_vsp_freq, + .codec_lp_freq = codec_lp_freq, + .codecs = get_codecs, + .capabilities = get_capabilities, +}; diff --git a/drivers/media/platform/qcom/venus/hfi_platform_v6.c b/drivers/media/platform/qcom/venus/hfi_platform_v6.c new file mode 100644 index 000000000..4e8af645f --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_platform_v6.c @@ -0,0 +1,332 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2020, The Linux Foundation. All rights reserved. + */ +#include "hfi_platform.h" + +static const struct hfi_plat_caps caps[] = { +{ + .codec = HFI_VIDEO_CODEC_H264, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 128, 8192, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 128, 8192, 1}, + /* ((5760 * 2880) / 256) */ + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 64, 138240, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 220000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 65536, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 65536, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 64, 7833600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 960, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 0, 1, 1}, + .num_caps = 9, + .pl[0] = {HFI_H264_PROFILE_BASELINE, HFI_H264_LEVEL_52}, + .pl[1] = {HFI_H264_PROFILE_MAIN, HFI_H264_LEVEL_52}, + .pl[2] = {HFI_H264_PROFILE_HIGH, HFI_H264_LEVEL_52}, + .pl[3] = {HFI_H264_PROFILE_CONSTRAINED_BASE, HFI_H264_LEVEL_52}, + .pl[4] = {HFI_H264_PROFILE_CONSTRAINED_HIGH, HFI_H264_LEVEL_52}, + .num_pl = 5, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_HEVC, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 128, 8192, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 128, 8192, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 64, 138240, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 220000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 65536, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 65536, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 64, 7833600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 960, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 0, 1, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 3, 1}, + .num_caps = 10, + .pl[0] = {HFI_HEVC_PROFILE_MAIN, HFI_HEVC_LEVEL_6 | HFI_HEVC_TIER_HIGH0}, + .pl[1] = {HFI_HEVC_PROFILE_MAIN10, HFI_HEVC_LEVEL_6 | HFI_HEVC_TIER_HIGH0}, + .num_pl = 2, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[4] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .fmts[5] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_P010}, + .fmts[6] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .num_fmts = 7, +}, { + .codec = HFI_VIDEO_CODEC_VP8, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 128, 4096, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 128, 4096, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 64, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 100000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 65536, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 65536, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 64, 4423680, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 120, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 0, 1, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 3, 1}, + .num_caps = 10, + .pl[0] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_0}, + .pl[1] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_1}, + .pl[2] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_2}, + .pl[3] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_3}, + .num_pl = 4, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_VP9, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 128, 8192, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 128, 8192, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 64, 138240, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 220000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 65536, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 65536, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 64, 7833600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 960, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 0, 1, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 3, 1}, + .num_caps = 10, + .pl[0] = {HFI_VP9_PROFILE_P0, 200}, + .pl[1] = {HFI_VP9_PROFILE_P2_10B, 200}, + .num_pl = 2, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[4] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .fmts[5] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_P010}, + .fmts[6] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .num_fmts = 7, +}, { + .codec = HFI_VIDEO_CODEC_MPEG2, + .domain = VIDC_SESSION_TYPE_DEC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 128, 1920, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 128, 1920, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 64, 8160, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 40000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 65536, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 65536, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 64, 7833600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 30, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 0, 1, 1}, + .caps[9] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 1, 1}, + .num_caps = 10, + .pl[0] = {HFI_MPEG2_PROFILE_SIMPLE, HFI_MPEG2_LEVEL_H14}, + .pl[1] = {HFI_MPEG2_PROFILE_MAIN, HFI_MPEG2_LEVEL_H14}, + .num_pl = 2, + .fmts[0] = {HFI_BUFFER_OUTPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[1] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV12}, + .fmts[3] = {HFI_BUFFER_OUTPUT2, HFI_COLOR_FORMAT_NV21}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_H264, + .domain = VIDC_SESSION_TYPE_ENC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 128, 8192, 1}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 128, 8192, 1}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 64, 138240, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 220000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 8192, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 8192, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 64, 7833600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 960, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 0, 1, 1}, + .caps[9] = {HFI_CAPABILITY_PEAKBITRATE, 32000, 160000000, 1}, + .caps[10] = {HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS, 0, 6, 1}, + .caps[11] = {HFI_CAPABILITY_ENC_LTR_COUNT, 0, 2, 1}, + .caps[12] = {HFI_CAPABILITY_LCU_SIZE, 16, 16, 1}, + .caps[13] = {HFI_CAPABILITY_BFRAME, 0, 1, 1}, + .caps[14] = {HFI_CAPABILITY_HIER_P_HYBRID_NUM_ENH_LAYERS, 0, 6, 1}, + .caps[15] = {HFI_CAPABILITY_I_FRAME_QP, 0, 51, 1}, + .caps[16] = {HFI_CAPABILITY_P_FRAME_QP, 0, 51, 1}, + .caps[17] = {HFI_CAPABILITY_B_FRAME_QP, 0, 51, 1}, + .caps[18] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 2, 1}, + .caps[19] = {HFI_CAPABILITY_RATE_CONTROL_MODES, 0x1000001, 0x1000005, 1}, + .caps[20] = {HFI_CAPABILITY_COLOR_SPACE_CONVERSION, 0, 2, 1}, + .num_caps = 21, + .pl[0] = {HFI_H264_PROFILE_BASELINE, HFI_H264_LEVEL_52}, + .pl[1] = {HFI_H264_PROFILE_MAIN, HFI_H264_LEVEL_52}, + .pl[2] = {HFI_H264_PROFILE_HIGH, HFI_H264_LEVEL_52}, + .pl[3] = {HFI_H264_PROFILE_CONSTRAINED_BASE, HFI_H264_LEVEL_52}, + .pl[4] = {HFI_H264_PROFILE_CONSTRAINED_HIGH, HFI_H264_LEVEL_52}, + .num_pl = 5, + .fmts[0] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12}, + .fmts[1] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[3] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_P010}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_HEVC, + .domain = VIDC_SESSION_TYPE_ENC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 128, 8192, 16}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 128, 8192, 16}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 64, 138240, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 160000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 8192, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 8192, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 64, 7833600, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 960, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 0, 1, 1}, + .caps[9] = {HFI_CAPABILITY_PEAKBITRATE, 32000, 160000000, 1}, + .caps[10] = {HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS, 0, 5, 1}, + .caps[11] = {HFI_CAPABILITY_ENC_LTR_COUNT, 0, 2, 1}, + .caps[12] = {HFI_CAPABILITY_LCU_SIZE, 32, 32, 1}, + .caps[13] = {HFI_CAPABILITY_BFRAME, 0, 1, 1}, + .caps[14] = {HFI_CAPABILITY_HIER_P_HYBRID_NUM_ENH_LAYERS, 0, 5, 1}, + .caps[15] = {HFI_CAPABILITY_I_FRAME_QP, 0, 51, 1}, + .caps[16] = {HFI_CAPABILITY_P_FRAME_QP, 0, 51, 1}, + .caps[17] = {HFI_CAPABILITY_B_FRAME_QP, 0, 51, 1}, + .caps[18] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 2, 1}, + .caps[19] = {HFI_CAPABILITY_RATE_CONTROL_MODES, 0x1000001, 0x1000005, 1}, + .caps[20] = {HFI_CAPABILITY_COLOR_SPACE_CONVERSION, 0, 2, 1}, + .caps[21] = {HFI_CAPABILITY_ROTATION, 1, 4, 90}, + .caps[22] = {HFI_CAPABILITY_BLUR_WIDTH, 96, 4096, 16}, + .caps[23] = {HFI_CAPABILITY_BLUR_HEIGHT, 96, 4096, 16}, + .num_caps = 24, + .pl[0] = {HFI_HEVC_PROFILE_MAIN, HFI_HEVC_LEVEL_6 | HFI_HEVC_TIER_HIGH0}, + .pl[1] = {HFI_HEVC_PROFILE_MAIN10, HFI_HEVC_LEVEL_6 | HFI_HEVC_TIER_HIGH0}, + .num_pl = 2, + .fmts[0] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12}, + .fmts[1] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[3] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_P010}, + .num_fmts = 4, +}, { + .codec = HFI_VIDEO_CODEC_VP8, + .domain = VIDC_SESSION_TYPE_ENC, + .cap_bufs_mode_dynamic = true, + .caps[0] = {HFI_CAPABILITY_FRAME_WIDTH, 128, 4096, 16}, + .caps[1] = {HFI_CAPABILITY_FRAME_HEIGHT, 128, 4096, 16}, + .caps[2] = {HFI_CAPABILITY_MBS_PER_FRAME, 64, 36864, 1}, + .caps[3] = {HFI_CAPABILITY_BITRATE, 1, 74000000, 1}, + .caps[4] = {HFI_CAPABILITY_SCALE_X, 8192, 65536, 1}, + .caps[5] = {HFI_CAPABILITY_SCALE_Y, 8192, 65536, 1}, + .caps[6] = {HFI_CAPABILITY_MBS_PER_SECOND, 64, 4423680, 1}, + .caps[7] = {HFI_CAPABILITY_FRAMERATE, 1, 120, 1}, + .caps[8] = {HFI_CAPABILITY_MAX_VIDEOCORES, 0, 1, 1}, + .caps[9] = {HFI_CAPABILITY_PEAKBITRATE, 32000, 160000000, 1}, + .caps[10] = {HFI_CAPABILITY_HIER_P_NUM_ENH_LAYERS, 0, 3, 1}, + .caps[11] = {HFI_CAPABILITY_ENC_LTR_COUNT, 0, 2, 1}, + .caps[12] = {HFI_CAPABILITY_LCU_SIZE, 16, 16, 1}, + .caps[13] = {HFI_CAPABILITY_BFRAME, 0, 0, 1}, + .caps[14] = {HFI_CAPABILITY_HIER_P_HYBRID_NUM_ENH_LAYERS, 0, 5, 1}, + .caps[15] = {HFI_CAPABILITY_I_FRAME_QP, 0, 127, 1}, + .caps[16] = {HFI_CAPABILITY_P_FRAME_QP, 0, 127, 1}, + .caps[17] = {HFI_CAPABILITY_MAX_WORKMODES, 1, 2, 1}, + .caps[18] = {HFI_CAPABILITY_RATE_CONTROL_MODES, 0x1000001, 0x1000005, 1}, + .caps[19] = {HFI_CAPABILITY_BLUR_WIDTH, 96, 4096, 16}, + .caps[20] = {HFI_CAPABILITY_BLUR_HEIGHT, 96, 4096, 16}, + .caps[21] = {HFI_CAPABILITY_COLOR_SPACE_CONVERSION, 0, 2, 1}, + .caps[22] = {HFI_CAPABILITY_ROTATION, 1, 4, 90}, + .num_caps = 23, + .pl[0] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_0}, + .pl[1] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_1}, + .pl[2] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_2}, + .pl[3] = {HFI_VPX_PROFILE_MAIN, HFI_VPX_LEVEL_VERSION_3}, + .num_pl = 4, + .fmts[0] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12}, + .fmts[1] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_NV12_UBWC}, + .fmts[2] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_YUV420_TP10_UBWC}, + .fmts[3] = {HFI_BUFFER_INPUT, HFI_COLOR_FORMAT_P010}, + .num_fmts = 4, +} }; + +static const struct hfi_plat_caps *get_capabilities(unsigned int *entries) +{ + *entries = ARRAY_SIZE(caps); + return caps; +} + +static void get_codecs(u32 *enc_codecs, u32 *dec_codecs, u32 *count) +{ + *enc_codecs = HFI_VIDEO_CODEC_H264 | HFI_VIDEO_CODEC_HEVC | + HFI_VIDEO_CODEC_VP8; + *dec_codecs = HFI_VIDEO_CODEC_H264 | HFI_VIDEO_CODEC_HEVC | + HFI_VIDEO_CODEC_VP8 | HFI_VIDEO_CODEC_VP9 | + HFI_VIDEO_CODEC_MPEG2; + *count = 8; +} + +static const struct hfi_platform_codec_freq_data codec_freq_data[] = { + { V4L2_PIX_FMT_H264, VIDC_SESSION_TYPE_ENC, 675, 25, 320 }, + { V4L2_PIX_FMT_HEVC, VIDC_SESSION_TYPE_ENC, 675, 25, 320 }, + { V4L2_PIX_FMT_VP8, VIDC_SESSION_TYPE_ENC, 675, 60, 320 }, + { V4L2_PIX_FMT_MPEG2, VIDC_SESSION_TYPE_DEC, 200, 25, 200 }, + { V4L2_PIX_FMT_H264, VIDC_SESSION_TYPE_DEC, 200, 25, 200 }, + { V4L2_PIX_FMT_HEVC, VIDC_SESSION_TYPE_DEC, 200, 25, 200 }, + { V4L2_PIX_FMT_VP8, VIDC_SESSION_TYPE_DEC, 200, 60, 200 }, + { V4L2_PIX_FMT_VP9, VIDC_SESSION_TYPE_DEC, 200, 60, 200 }, +}; + +static const struct hfi_platform_codec_freq_data * +get_codec_freq_data(u32 session_type, u32 pixfmt) +{ + const struct hfi_platform_codec_freq_data *data = codec_freq_data; + unsigned int i, data_size = ARRAY_SIZE(codec_freq_data); + const struct hfi_platform_codec_freq_data *found = NULL; + + for (i = 0; i < data_size; i++) { + if (data[i].pixfmt == pixfmt && data[i].session_type == session_type) { + found = &data[i]; + break; + } + } + + return found; +} + +static unsigned long codec_vpp_freq(u32 session_type, u32 codec) +{ + const struct hfi_platform_codec_freq_data *data; + + data = get_codec_freq_data(session_type, codec); + if (data) + return data->vpp_freq; + + return 0; +} + +static unsigned long codec_vsp_freq(u32 session_type, u32 codec) +{ + const struct hfi_platform_codec_freq_data *data; + + data = get_codec_freq_data(session_type, codec); + if (data) + return data->vsp_freq; + + return 0; +} + +static unsigned long codec_lp_freq(u32 session_type, u32 codec) +{ + const struct hfi_platform_codec_freq_data *data; + + data = get_codec_freq_data(session_type, codec); + if (data) + return data->low_power_freq; + + return 0; +} + +const struct hfi_platform hfi_plat_v6 = { + .codec_vpp_freq = codec_vpp_freq, + .codec_vsp_freq = codec_vsp_freq, + .codec_lp_freq = codec_lp_freq, + .codecs = get_codecs, + .capabilities = get_capabilities, + .bufreq = hfi_plat_bufreq_v6, +}; diff --git a/drivers/media/platform/qcom/venus/hfi_venus.c b/drivers/media/platform/qcom/venus/hfi_venus.c new file mode 100644 index 000000000..d46938aab --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_venus.c @@ -0,0 +1,1777 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "hfi_cmds.h" +#include "hfi_msgs.h" +#include "hfi_venus.h" +#include "hfi_venus_io.h" +#include "firmware.h" + +#define HFI_MASK_QHDR_TX_TYPE 0xff000000 +#define HFI_MASK_QHDR_RX_TYPE 0x00ff0000 +#define HFI_MASK_QHDR_PRI_TYPE 0x0000ff00 +#define HFI_MASK_QHDR_ID_TYPE 0x000000ff + +#define HFI_HOST_TO_CTRL_CMD_Q 0 +#define HFI_CTRL_TO_HOST_MSG_Q 1 +#define HFI_CTRL_TO_HOST_DBG_Q 2 +#define HFI_MASK_QHDR_STATUS 0x000000ff + +#define IFACEQ_NUM 3 +#define IFACEQ_CMD_IDX 0 +#define IFACEQ_MSG_IDX 1 +#define IFACEQ_DBG_IDX 2 +#define IFACEQ_MAX_BUF_COUNT 50 +#define IFACEQ_MAX_PARALLEL_CLNTS 16 +#define IFACEQ_DFLT_QHDR 0x01010000 + +#define POLL_INTERVAL_US 50 + +#define IFACEQ_MAX_PKT_SIZE 1024 +#define IFACEQ_MED_PKT_SIZE 768 +#define IFACEQ_MIN_PKT_SIZE 8 +#define IFACEQ_VAR_SMALL_PKT_SIZE 100 +#define IFACEQ_VAR_LARGE_PKT_SIZE 512 +#define IFACEQ_VAR_HUGE_PKT_SIZE (1024 * 12) + +struct hfi_queue_table_header { + u32 version; + u32 size; + u32 qhdr0_offset; + u32 qhdr_size; + u32 num_q; + u32 num_active_q; +}; + +struct hfi_queue_header { + u32 status; + u32 start_addr; + u32 type; + u32 q_size; + u32 pkt_size; + u32 pkt_drop_cnt; + u32 rx_wm; + u32 tx_wm; + u32 rx_req; + u32 tx_req; + u32 rx_irq_status; + u32 tx_irq_status; + u32 read_idx; + u32 write_idx; +}; + +#define IFACEQ_TABLE_SIZE \ + (sizeof(struct hfi_queue_table_header) + \ + sizeof(struct hfi_queue_header) * IFACEQ_NUM) + +#define IFACEQ_QUEUE_SIZE (IFACEQ_MAX_PKT_SIZE * \ + IFACEQ_MAX_BUF_COUNT * IFACEQ_MAX_PARALLEL_CLNTS) + +#define IFACEQ_GET_QHDR_START_ADDR(ptr, i) \ + (void *)(((ptr) + sizeof(struct hfi_queue_table_header)) + \ + ((i) * sizeof(struct hfi_queue_header))) + +#define QDSS_SIZE SZ_4K +#define SFR_SIZE SZ_4K +#define QUEUE_SIZE \ + (IFACEQ_TABLE_SIZE + (IFACEQ_QUEUE_SIZE * IFACEQ_NUM)) + +#define ALIGNED_QDSS_SIZE ALIGN(QDSS_SIZE, SZ_4K) +#define ALIGNED_SFR_SIZE ALIGN(SFR_SIZE, SZ_4K) +#define ALIGNED_QUEUE_SIZE ALIGN(QUEUE_SIZE, SZ_4K) +#define SHARED_QSIZE ALIGN(ALIGNED_SFR_SIZE + ALIGNED_QUEUE_SIZE + \ + ALIGNED_QDSS_SIZE, SZ_1M) + +struct mem_desc { + dma_addr_t da; /* device address */ + void *kva; /* kernel virtual address */ + u32 size; + unsigned long attrs; +}; + +struct iface_queue { + struct hfi_queue_header *qhdr; + struct mem_desc qmem; +}; + +enum venus_state { + VENUS_STATE_DEINIT = 1, + VENUS_STATE_INIT, +}; + +struct venus_hfi_device { + struct venus_core *core; + u32 irq_status; + u32 last_packet_type; + bool power_enabled; + bool suspended; + enum venus_state state; + /* serialize read / write to the shared memory */ + struct mutex lock; + struct completion pwr_collapse_prep; + struct completion release_resource; + struct mem_desc ifaceq_table; + struct mem_desc sfr; + struct iface_queue queues[IFACEQ_NUM]; + u8 pkt_buf[IFACEQ_VAR_HUGE_PKT_SIZE]; + u8 dbg_buf[IFACEQ_VAR_HUGE_PKT_SIZE]; +}; + +static bool venus_pkt_debug; +int venus_fw_debug = HFI_DEBUG_MSG_ERROR | HFI_DEBUG_MSG_FATAL; +static bool venus_fw_low_power_mode = true; +static int venus_hw_rsp_timeout = 1000; +static bool venus_fw_coverage; + +static void venus_set_state(struct venus_hfi_device *hdev, + enum venus_state state) +{ + mutex_lock(&hdev->lock); + hdev->state = state; + mutex_unlock(&hdev->lock); +} + +static bool venus_is_valid_state(struct venus_hfi_device *hdev) +{ + return hdev->state != VENUS_STATE_DEINIT; +} + +static void venus_dump_packet(struct venus_hfi_device *hdev, const void *packet) +{ + size_t pkt_size = *(u32 *)packet; + + if (!venus_pkt_debug) + return; + + print_hex_dump(KERN_DEBUG, "", DUMP_PREFIX_OFFSET, 16, 1, packet, + pkt_size, true); +} + +static int venus_write_queue(struct venus_hfi_device *hdev, + struct iface_queue *queue, + void *packet, u32 *rx_req) +{ + struct hfi_queue_header *qhdr; + u32 dwords, new_wr_idx; + u32 empty_space, rd_idx, wr_idx, qsize; + u32 *wr_ptr; + + if (!queue->qmem.kva) + return -EINVAL; + + qhdr = queue->qhdr; + if (!qhdr) + return -EINVAL; + + venus_dump_packet(hdev, packet); + + dwords = (*(u32 *)packet) >> 2; + if (!dwords) + return -EINVAL; + + rd_idx = qhdr->read_idx; + wr_idx = qhdr->write_idx; + qsize = qhdr->q_size; + /* ensure rd/wr indices's are read from memory */ + rmb(); + + if (wr_idx >= rd_idx) + empty_space = qsize - (wr_idx - rd_idx); + else + empty_space = rd_idx - wr_idx; + + if (empty_space <= dwords) { + qhdr->tx_req = 1; + /* ensure tx_req is updated in memory */ + wmb(); + return -ENOSPC; + } + + qhdr->tx_req = 0; + /* ensure tx_req is updated in memory */ + wmb(); + + new_wr_idx = wr_idx + dwords; + wr_ptr = (u32 *)(queue->qmem.kva + (wr_idx << 2)); + + if (wr_ptr < (u32 *)queue->qmem.kva || + wr_ptr > (u32 *)(queue->qmem.kva + queue->qmem.size - sizeof(*wr_ptr))) + return -EINVAL; + + if (new_wr_idx < qsize) { + memcpy(wr_ptr, packet, dwords << 2); + } else { + size_t len; + + new_wr_idx -= qsize; + len = (dwords - new_wr_idx) << 2; + memcpy(wr_ptr, packet, len); + memcpy(queue->qmem.kva, packet + len, new_wr_idx << 2); + } + + /* make sure packet is written before updating the write index */ + wmb(); + + qhdr->write_idx = new_wr_idx; + *rx_req = qhdr->rx_req ? 1 : 0; + + /* make sure write index is updated before an interrupt is raised */ + mb(); + + return 0; +} + +static int venus_read_queue(struct venus_hfi_device *hdev, + struct iface_queue *queue, void *pkt, u32 *tx_req) +{ + struct hfi_queue_header *qhdr; + u32 dwords, new_rd_idx; + u32 rd_idx, wr_idx, type, qsize; + u32 *rd_ptr; + u32 recv_request = 0; + int ret = 0; + + if (!queue->qmem.kva) + return -EINVAL; + + qhdr = queue->qhdr; + if (!qhdr) + return -EINVAL; + + type = qhdr->type; + rd_idx = qhdr->read_idx; + wr_idx = qhdr->write_idx; + qsize = qhdr->q_size; + + /* make sure data is valid before using it */ + rmb(); + + /* + * Do not set receive request for debug queue, if set, Venus generates + * interrupt for debug messages even when there is no response message + * available. In general debug queue will not become full as it is being + * emptied out for every interrupt from Venus. Venus will anyway + * generates interrupt if it is full. + */ + if (type & HFI_CTRL_TO_HOST_MSG_Q) + recv_request = 1; + + if (rd_idx == wr_idx) { + qhdr->rx_req = recv_request; + *tx_req = 0; + /* update rx_req field in memory */ + wmb(); + return -ENODATA; + } + + rd_ptr = (u32 *)(queue->qmem.kva + (rd_idx << 2)); + + if (rd_ptr < (u32 *)queue->qmem.kva || + rd_ptr > (u32 *)(queue->qmem.kva + queue->qmem.size - sizeof(*rd_ptr))) + return -EINVAL; + + dwords = *rd_ptr >> 2; + if (!dwords) + return -EINVAL; + + new_rd_idx = rd_idx + dwords; + if (((dwords << 2) <= IFACEQ_VAR_HUGE_PKT_SIZE) && rd_idx <= qsize) { + if (new_rd_idx < qsize) { + memcpy(pkt, rd_ptr, dwords << 2); + } else { + size_t len; + + new_rd_idx -= qsize; + len = (dwords - new_rd_idx) << 2; + memcpy(pkt, rd_ptr, len); + memcpy(pkt + len, queue->qmem.kva, new_rd_idx << 2); + } + } else { + /* bad packet received, dropping */ + new_rd_idx = qhdr->write_idx; + ret = -EBADMSG; + } + + /* ensure the packet is read before updating read index */ + rmb(); + + qhdr->read_idx = new_rd_idx; + /* ensure updating read index */ + wmb(); + + rd_idx = qhdr->read_idx; + wr_idx = qhdr->write_idx; + /* ensure rd/wr indices are read from memory */ + rmb(); + + if (rd_idx != wr_idx) + qhdr->rx_req = 0; + else + qhdr->rx_req = recv_request; + + *tx_req = qhdr->tx_req ? 1 : 0; + + /* ensure rx_req is stored to memory and tx_req is loaded from memory */ + mb(); + + venus_dump_packet(hdev, pkt); + + return ret; +} + +static int venus_alloc(struct venus_hfi_device *hdev, struct mem_desc *desc, + u32 size) +{ + struct device *dev = hdev->core->dev; + + desc->attrs = DMA_ATTR_WRITE_COMBINE; + desc->size = ALIGN(size, SZ_4K); + + desc->kva = dma_alloc_attrs(dev, desc->size, &desc->da, GFP_KERNEL, + desc->attrs); + if (!desc->kva) + return -ENOMEM; + + return 0; +} + +static void venus_free(struct venus_hfi_device *hdev, struct mem_desc *mem) +{ + struct device *dev = hdev->core->dev; + + dma_free_attrs(dev, mem->size, mem->kva, mem->da, mem->attrs); +} + +static void venus_set_registers(struct venus_hfi_device *hdev) +{ + const struct venus_resources *res = hdev->core->res; + const struct reg_val *tbl = res->reg_tbl; + unsigned int count = res->reg_tbl_size; + unsigned int i; + + for (i = 0; i < count; i++) + writel(tbl[i].value, hdev->core->base + tbl[i].reg); +} + +static void venus_soft_int(struct venus_hfi_device *hdev) +{ + void __iomem *cpu_ic_base = hdev->core->cpu_ic_base; + u32 clear_bit; + + if (IS_V6(hdev->core)) + clear_bit = BIT(CPU_IC_SOFTINT_H2A_SHIFT_V6); + else + clear_bit = BIT(CPU_IC_SOFTINT_H2A_SHIFT); + + writel(clear_bit, cpu_ic_base + CPU_IC_SOFTINT); +} + +static int venus_iface_cmdq_write_nolock(struct venus_hfi_device *hdev, + void *pkt, bool sync) +{ + struct device *dev = hdev->core->dev; + struct hfi_pkt_hdr *cmd_packet; + struct iface_queue *queue; + u32 rx_req; + int ret; + + if (!venus_is_valid_state(hdev)) + return -EINVAL; + + cmd_packet = (struct hfi_pkt_hdr *)pkt; + hdev->last_packet_type = cmd_packet->pkt_type; + + queue = &hdev->queues[IFACEQ_CMD_IDX]; + + ret = venus_write_queue(hdev, queue, pkt, &rx_req); + if (ret) { + dev_err(dev, "write to iface cmd queue failed (%d)\n", ret); + return ret; + } + + if (sync) { + /* + * Inform video hardware to raise interrupt for synchronous + * commands + */ + queue = &hdev->queues[IFACEQ_MSG_IDX]; + queue->qhdr->rx_req = 1; + /* ensure rx_req is updated in memory */ + wmb(); + } + + if (rx_req) + venus_soft_int(hdev); + + return 0; +} + +static int venus_iface_cmdq_write(struct venus_hfi_device *hdev, void *pkt, bool sync) +{ + int ret; + + mutex_lock(&hdev->lock); + ret = venus_iface_cmdq_write_nolock(hdev, pkt, sync); + mutex_unlock(&hdev->lock); + + return ret; +} + +static int venus_hfi_core_set_resource(struct venus_core *core, u32 id, + u32 size, u32 addr, void *cookie) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct hfi_sys_set_resource_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + if (id == VIDC_RESOURCE_NONE) + return 0; + + pkt = (struct hfi_sys_set_resource_pkt *)packet; + + ret = pkt_sys_set_resource(pkt, id, size, addr, cookie); + if (ret) + return ret; + + ret = venus_iface_cmdq_write(hdev, pkt, false); + if (ret) + return ret; + + return 0; +} + +static int venus_boot_core(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + static const unsigned int max_tries = 100; + u32 ctrl_status = 0, mask_val; + unsigned int count = 0; + void __iomem *cpu_cs_base = hdev->core->cpu_cs_base; + void __iomem *wrapper_base = hdev->core->wrapper_base; + int ret = 0; + + if (IS_V6(hdev->core)) { + mask_val = readl(wrapper_base + WRAPPER_INTR_MASK); + mask_val &= ~(WRAPPER_INTR_MASK_A2HWD_BASK_V6 | + WRAPPER_INTR_MASK_A2HCPU_MASK); + } else { + mask_val = WRAPPER_INTR_MASK_A2HVCODEC_MASK; + } + writel(mask_val, wrapper_base + WRAPPER_INTR_MASK); + writel(1, cpu_cs_base + CPU_CS_SCIACMDARG3); + + writel(BIT(VIDC_CTRL_INIT_CTRL_SHIFT), cpu_cs_base + VIDC_CTRL_INIT); + while (!ctrl_status && count < max_tries) { + ctrl_status = readl(cpu_cs_base + CPU_CS_SCIACMDARG0); + if ((ctrl_status & CPU_CS_SCIACMDARG0_ERROR_STATUS_MASK) == 4) { + dev_err(dev, "invalid setting for UC_REGION\n"); + ret = -EINVAL; + break; + } + + usleep_range(500, 1000); + count++; + } + + if (count >= max_tries) + ret = -ETIMEDOUT; + + if (IS_V6(hdev->core)) { + writel(0x1, cpu_cs_base + CPU_CS_H2XSOFTINTEN_V6); + writel(0x0, cpu_cs_base + CPU_CS_X2RPMH_V6); + } + + return ret; +} + +static u32 venus_hwversion(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + void __iomem *wrapper_base = hdev->core->wrapper_base; + u32 ver; + u32 major, minor, step; + + ver = readl(wrapper_base + WRAPPER_HW_VERSION); + major = ver & WRAPPER_HW_VERSION_MAJOR_VERSION_MASK; + major = major >> WRAPPER_HW_VERSION_MAJOR_VERSION_SHIFT; + minor = ver & WRAPPER_HW_VERSION_MINOR_VERSION_MASK; + minor = minor >> WRAPPER_HW_VERSION_MINOR_VERSION_SHIFT; + step = ver & WRAPPER_HW_VERSION_STEP_VERSION_MASK; + + dev_dbg(dev, VDBGL "venus hw version %x.%x.%x\n", major, minor, step); + + return major; +} + +static int venus_run(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + void __iomem *cpu_cs_base = hdev->core->cpu_cs_base; + int ret; + + /* + * Re-program all of the registers that get reset as a result of + * regulator_disable() and _enable() + */ + venus_set_registers(hdev); + + writel(hdev->ifaceq_table.da, cpu_cs_base + UC_REGION_ADDR); + writel(SHARED_QSIZE, cpu_cs_base + UC_REGION_SIZE); + writel(hdev->ifaceq_table.da, cpu_cs_base + CPU_CS_SCIACMDARG2); + writel(0x01, cpu_cs_base + CPU_CS_SCIACMDARG1); + if (hdev->sfr.da) + writel(hdev->sfr.da, cpu_cs_base + SFR_ADDR); + + ret = venus_boot_core(hdev); + if (ret) { + dev_err(dev, "failed to reset venus core\n"); + return ret; + } + + venus_hwversion(hdev); + + return 0; +} + +static int venus_halt_axi(struct venus_hfi_device *hdev) +{ + void __iomem *wrapper_base = hdev->core->wrapper_base; + void __iomem *vbif_base = hdev->core->vbif_base; + void __iomem *cpu_cs_base = hdev->core->cpu_cs_base; + void __iomem *aon_base = hdev->core->aon_base; + struct device *dev = hdev->core->dev; + u32 val; + u32 mask_val; + int ret; + + if (IS_V6(hdev->core)) { + writel(0x3, cpu_cs_base + CPU_CS_X2RPMH_V6); + + if (hdev->core->res->num_vpp_pipes == 1) + goto skip_aon_mvp_noc; + + writel(0x1, aon_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL); + ret = readl_poll_timeout(aon_base + AON_WRAPPER_MVP_NOC_LPI_STATUS, + val, + val & BIT(0), + POLL_INTERVAL_US, + VBIF_AXI_HALT_ACK_TIMEOUT_US); + if (ret) + return -ETIMEDOUT; + +skip_aon_mvp_noc: + mask_val = (BIT(2) | BIT(1) | BIT(0)); + writel(mask_val, wrapper_base + WRAPPER_DEBUG_BRIDGE_LPI_CONTROL_V6); + + writel(0x00, wrapper_base + WRAPPER_DEBUG_BRIDGE_LPI_CONTROL_V6); + ret = readl_poll_timeout(wrapper_base + WRAPPER_DEBUG_BRIDGE_LPI_STATUS_V6, + val, + val == 0, + POLL_INTERVAL_US, + VBIF_AXI_HALT_ACK_TIMEOUT_US); + + if (ret) { + dev_err(dev, "DBLP Release: lpi_status %x\n", val); + return -ETIMEDOUT; + } + return 0; + } + + if (IS_V4(hdev->core)) { + val = readl(wrapper_base + WRAPPER_CPU_AXI_HALT); + val |= WRAPPER_CPU_AXI_HALT_HALT; + writel(val, wrapper_base + WRAPPER_CPU_AXI_HALT); + + ret = readl_poll_timeout(wrapper_base + WRAPPER_CPU_AXI_HALT_STATUS, + val, + val & WRAPPER_CPU_AXI_HALT_STATUS_IDLE, + POLL_INTERVAL_US, + VBIF_AXI_HALT_ACK_TIMEOUT_US); + if (ret) { + dev_err(dev, "AXI bus port halt timeout\n"); + return ret; + } + + return 0; + } + + /* Halt AXI and AXI IMEM VBIF Access */ + val = readl(vbif_base + VBIF_AXI_HALT_CTRL0); + val |= VBIF_AXI_HALT_CTRL0_HALT_REQ; + writel(val, vbif_base + VBIF_AXI_HALT_CTRL0); + + /* Request for AXI bus port halt */ + ret = readl_poll_timeout(vbif_base + VBIF_AXI_HALT_CTRL1, val, + val & VBIF_AXI_HALT_CTRL1_HALT_ACK, + POLL_INTERVAL_US, + VBIF_AXI_HALT_ACK_TIMEOUT_US); + if (ret) { + dev_err(dev, "AXI bus port halt timeout\n"); + return ret; + } + + return 0; +} + +static int venus_power_off(struct venus_hfi_device *hdev) +{ + int ret; + + if (!hdev->power_enabled) + return 0; + + ret = venus_set_hw_state_suspend(hdev->core); + if (ret) + return ret; + + ret = venus_halt_axi(hdev); + if (ret) + return ret; + + hdev->power_enabled = false; + + return 0; +} + +static int venus_power_on(struct venus_hfi_device *hdev) +{ + int ret; + + if (hdev->power_enabled) + return 0; + + ret = venus_set_hw_state_resume(hdev->core); + if (ret) + goto err; + + ret = venus_run(hdev); + if (ret) + goto err_suspend; + + hdev->power_enabled = true; + + return 0; + +err_suspend: + venus_set_hw_state_suspend(hdev->core); +err: + hdev->power_enabled = false; + return ret; +} + +static int venus_iface_msgq_read_nolock(struct venus_hfi_device *hdev, + void *pkt) +{ + struct iface_queue *queue; + u32 tx_req; + int ret; + + if (!venus_is_valid_state(hdev)) + return -EINVAL; + + queue = &hdev->queues[IFACEQ_MSG_IDX]; + + ret = venus_read_queue(hdev, queue, pkt, &tx_req); + if (ret) + return ret; + + if (tx_req) + venus_soft_int(hdev); + + return 0; +} + +static int venus_iface_msgq_read(struct venus_hfi_device *hdev, void *pkt) +{ + int ret; + + mutex_lock(&hdev->lock); + ret = venus_iface_msgq_read_nolock(hdev, pkt); + mutex_unlock(&hdev->lock); + + return ret; +} + +static int venus_iface_dbgq_read_nolock(struct venus_hfi_device *hdev, + void *pkt) +{ + struct iface_queue *queue; + u32 tx_req; + int ret; + + ret = venus_is_valid_state(hdev); + if (!ret) + return -EINVAL; + + queue = &hdev->queues[IFACEQ_DBG_IDX]; + + ret = venus_read_queue(hdev, queue, pkt, &tx_req); + if (ret) + return ret; + + if (tx_req) + venus_soft_int(hdev); + + return 0; +} + +static int venus_iface_dbgq_read(struct venus_hfi_device *hdev, void *pkt) +{ + int ret; + + if (!pkt) + return -EINVAL; + + mutex_lock(&hdev->lock); + ret = venus_iface_dbgq_read_nolock(hdev, pkt); + mutex_unlock(&hdev->lock); + + return ret; +} + +static void venus_set_qhdr_defaults(struct hfi_queue_header *qhdr) +{ + qhdr->status = 1; + qhdr->type = IFACEQ_DFLT_QHDR; + qhdr->q_size = IFACEQ_QUEUE_SIZE / 4; + qhdr->pkt_size = 0; + qhdr->rx_wm = 1; + qhdr->tx_wm = 1; + qhdr->rx_req = 1; + qhdr->tx_req = 0; + qhdr->rx_irq_status = 0; + qhdr->tx_irq_status = 0; + qhdr->read_idx = 0; + qhdr->write_idx = 0; +} + +static void venus_interface_queues_release(struct venus_hfi_device *hdev) +{ + mutex_lock(&hdev->lock); + + venus_free(hdev, &hdev->ifaceq_table); + venus_free(hdev, &hdev->sfr); + + memset(hdev->queues, 0, sizeof(hdev->queues)); + memset(&hdev->ifaceq_table, 0, sizeof(hdev->ifaceq_table)); + memset(&hdev->sfr, 0, sizeof(hdev->sfr)); + + mutex_unlock(&hdev->lock); +} + +static int venus_interface_queues_init(struct venus_hfi_device *hdev) +{ + struct hfi_queue_table_header *tbl_hdr; + struct iface_queue *queue; + struct hfi_sfr *sfr; + struct mem_desc desc = {0}; + unsigned int offset; + unsigned int i; + int ret; + + ret = venus_alloc(hdev, &desc, ALIGNED_QUEUE_SIZE); + if (ret) + return ret; + + hdev->ifaceq_table = desc; + offset = IFACEQ_TABLE_SIZE; + + for (i = 0; i < IFACEQ_NUM; i++) { + queue = &hdev->queues[i]; + queue->qmem.da = desc.da + offset; + queue->qmem.kva = desc.kva + offset; + queue->qmem.size = IFACEQ_QUEUE_SIZE; + offset += queue->qmem.size; + queue->qhdr = + IFACEQ_GET_QHDR_START_ADDR(hdev->ifaceq_table.kva, i); + + venus_set_qhdr_defaults(queue->qhdr); + + queue->qhdr->start_addr = queue->qmem.da; + + if (i == IFACEQ_CMD_IDX) + queue->qhdr->type |= HFI_HOST_TO_CTRL_CMD_Q; + else if (i == IFACEQ_MSG_IDX) + queue->qhdr->type |= HFI_CTRL_TO_HOST_MSG_Q; + else if (i == IFACEQ_DBG_IDX) + queue->qhdr->type |= HFI_CTRL_TO_HOST_DBG_Q; + } + + tbl_hdr = hdev->ifaceq_table.kva; + tbl_hdr->version = 0; + tbl_hdr->size = IFACEQ_TABLE_SIZE; + tbl_hdr->qhdr0_offset = sizeof(struct hfi_queue_table_header); + tbl_hdr->qhdr_size = sizeof(struct hfi_queue_header); + tbl_hdr->num_q = IFACEQ_NUM; + tbl_hdr->num_active_q = IFACEQ_NUM; + + /* + * Set receive request to zero on debug queue as there is no + * need of interrupt from video hardware for debug messages + */ + queue = &hdev->queues[IFACEQ_DBG_IDX]; + queue->qhdr->rx_req = 0; + + ret = venus_alloc(hdev, &desc, ALIGNED_SFR_SIZE); + if (ret) { + hdev->sfr.da = 0; + } else { + hdev->sfr = desc; + sfr = hdev->sfr.kva; + sfr->buf_size = ALIGNED_SFR_SIZE; + } + + /* ensure table and queue header structs are settled in memory */ + wmb(); + + return 0; +} + +static int venus_sys_set_debug(struct venus_hfi_device *hdev, u32 debug) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_debug_config(pkt, HFI_DEBUG_MODE_QUEUE, debug); + + ret = venus_iface_cmdq_write(hdev, pkt, false); + if (ret) + return ret; + + return 0; +} + +static int venus_sys_set_coverage(struct venus_hfi_device *hdev, u32 mode) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_coverage_config(pkt, mode); + + ret = venus_iface_cmdq_write(hdev, pkt, false); + if (ret) + return ret; + + return 0; +} + +static int venus_sys_set_idle_message(struct venus_hfi_device *hdev, + bool enable) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + if (!enable) + return 0; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_idle_indicator(pkt, enable); + + ret = venus_iface_cmdq_write(hdev, pkt, false); + if (ret) + return ret; + + return 0; +} + +static int venus_sys_set_power_control(struct venus_hfi_device *hdev, + bool enable) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_power_control(pkt, enable); + + ret = venus_iface_cmdq_write(hdev, pkt, false); + if (ret) + return ret; + + return 0; +} + +static int venus_sys_set_ubwc_config(struct venus_hfi_device *hdev) +{ + struct hfi_sys_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + const struct venus_resources *res = hdev->core->res; + int ret; + + pkt = (struct hfi_sys_set_property_pkt *)packet; + + pkt_sys_ubwc_config(pkt, res->ubwc_conf); + + ret = venus_iface_cmdq_write(hdev, pkt, false); + if (ret) + return ret; + + return 0; +} + +static int venus_get_queue_size(struct venus_hfi_device *hdev, + unsigned int index) +{ + struct hfi_queue_header *qhdr; + + if (index >= IFACEQ_NUM) + return -EINVAL; + + qhdr = hdev->queues[index].qhdr; + if (!qhdr) + return -EINVAL; + + return abs(qhdr->read_idx - qhdr->write_idx); +} + +static int venus_sys_set_default_properties(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + const struct venus_resources *res = hdev->core->res; + int ret; + + ret = venus_sys_set_debug(hdev, venus_fw_debug); + if (ret) + dev_warn(dev, "setting fw debug msg ON failed (%d)\n", ret); + + /* HFI_PROPERTY_SYS_IDLE_INDICATOR is not supported beyond 8916 (HFI V1) */ + if (IS_V1(hdev->core)) { + ret = venus_sys_set_idle_message(hdev, false); + if (ret) + dev_warn(dev, "setting idle response ON failed (%d)\n", ret); + } + + ret = venus_sys_set_power_control(hdev, venus_fw_low_power_mode); + if (ret) + dev_warn(dev, "setting hw power collapse ON failed (%d)\n", + ret); + + /* For specific venus core, it is mandatory to set the UBWC configuration */ + if (res->ubwc_conf) { + ret = venus_sys_set_ubwc_config(hdev); + if (ret) + dev_warn(dev, "setting ubwc config failed (%d)\n", ret); + } + + return ret; +} + +static int venus_session_cmd(struct venus_inst *inst, u32 pkt_type, bool sync) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_pkt pkt; + + pkt_session_cmd(&pkt, pkt_type, inst); + + return venus_iface_cmdq_write(hdev, &pkt, sync); +} + +static void venus_flush_debug_queue(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + void *packet = hdev->dbg_buf; + + while (!venus_iface_dbgq_read(hdev, packet)) { + struct hfi_msg_sys_coverage_pkt *pkt = packet; + + if (pkt->hdr.pkt_type != HFI_MSG_SYS_COV) { + struct hfi_msg_sys_debug_pkt *pkt = packet; + + dev_dbg(dev, VDBGFW "%s", pkt->msg_data); + } + } +} + +static int venus_prepare_power_collapse(struct venus_hfi_device *hdev, + bool wait) +{ + unsigned long timeout = msecs_to_jiffies(venus_hw_rsp_timeout); + struct hfi_sys_pc_prep_pkt pkt; + int ret; + + init_completion(&hdev->pwr_collapse_prep); + + pkt_sys_pc_prep(&pkt); + + ret = venus_iface_cmdq_write(hdev, &pkt, false); + if (ret) + return ret; + + if (!wait) + return 0; + + ret = wait_for_completion_timeout(&hdev->pwr_collapse_prep, timeout); + if (!ret) { + venus_flush_debug_queue(hdev); + return -ETIMEDOUT; + } + + return 0; +} + +static int venus_are_queues_empty(struct venus_hfi_device *hdev) +{ + int ret1, ret2; + + ret1 = venus_get_queue_size(hdev, IFACEQ_MSG_IDX); + if (ret1 < 0) + return ret1; + + ret2 = venus_get_queue_size(hdev, IFACEQ_CMD_IDX); + if (ret2 < 0) + return ret2; + + if (!ret1 && !ret2) + return 1; + + return 0; +} + +static void venus_sfr_print(struct venus_hfi_device *hdev) +{ + struct device *dev = hdev->core->dev; + struct hfi_sfr *sfr = hdev->sfr.kva; + void *p; + + if (!sfr) + return; + + p = memchr(sfr->data, '\0', sfr->buf_size); + /* + * SFR isn't guaranteed to be NULL terminated since SYS_ERROR indicates + * that Venus is in the process of crashing. + */ + if (!p) + sfr->data[sfr->buf_size - 1] = '\0'; + + dev_err_ratelimited(dev, "SFR message from FW: %s\n", sfr->data); +} + +static void venus_process_msg_sys_error(struct venus_hfi_device *hdev, + void *packet) +{ + struct hfi_msg_event_notify_pkt *event_pkt = packet; + + if (event_pkt->event_id != HFI_EVENT_SYS_ERROR) + return; + + venus_set_state(hdev, VENUS_STATE_DEINIT); + + venus_sfr_print(hdev); +} + +static irqreturn_t venus_isr_thread(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + const struct venus_resources *res; + void *pkt; + u32 msg_ret; + + if (!hdev) + return IRQ_NONE; + + res = hdev->core->res; + pkt = hdev->pkt_buf; + + + while (!venus_iface_msgq_read(hdev, pkt)) { + msg_ret = hfi_process_msg_packet(core, pkt); + switch (msg_ret) { + case HFI_MSG_EVENT_NOTIFY: + venus_process_msg_sys_error(hdev, pkt); + break; + case HFI_MSG_SYS_INIT: + venus_hfi_core_set_resource(core, res->vmem_id, + res->vmem_size, + res->vmem_addr, + hdev); + break; + case HFI_MSG_SYS_RELEASE_RESOURCE: + complete(&hdev->release_resource); + break; + case HFI_MSG_SYS_PC_PREP: + complete(&hdev->pwr_collapse_prep); + break; + default: + break; + } + } + + venus_flush_debug_queue(hdev); + + return IRQ_HANDLED; +} + +static irqreturn_t venus_isr(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + u32 status; + void __iomem *cpu_cs_base; + void __iomem *wrapper_base; + + if (!hdev) + return IRQ_NONE; + + cpu_cs_base = hdev->core->cpu_cs_base; + wrapper_base = hdev->core->wrapper_base; + + status = readl(wrapper_base + WRAPPER_INTR_STATUS); + if (IS_V6(core)) { + if (status & WRAPPER_INTR_STATUS_A2H_MASK || + status & WRAPPER_INTR_STATUS_A2HWD_MASK_V6 || + status & CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK) + hdev->irq_status = status; + } else { + if (status & WRAPPER_INTR_STATUS_A2H_MASK || + status & WRAPPER_INTR_STATUS_A2HWD_MASK || + status & CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK) + hdev->irq_status = status; + } + writel(1, cpu_cs_base + CPU_CS_A2HSOFTINTCLR); + if (!IS_V6(core)) + writel(status, wrapper_base + WRAPPER_INTR_CLEAR); + + return IRQ_WAKE_THREAD; +} + +static int venus_core_init(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct device *dev = core->dev; + struct hfi_sys_get_property_pkt version_pkt; + struct hfi_sys_init_pkt pkt; + int ret; + + pkt_sys_init(&pkt, HFI_VIDEO_ARCH_OX); + + venus_set_state(hdev, VENUS_STATE_INIT); + + ret = venus_iface_cmdq_write(hdev, &pkt, false); + if (ret) + return ret; + + pkt_sys_image_version(&version_pkt); + + ret = venus_iface_cmdq_write(hdev, &version_pkt, false); + if (ret) + dev_warn(dev, "failed to send image version pkt to fw\n"); + + ret = venus_sys_set_default_properties(hdev); + if (ret) + return ret; + + return 0; +} + +static int venus_core_deinit(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + + venus_set_state(hdev, VENUS_STATE_DEINIT); + hdev->suspended = true; + hdev->power_enabled = false; + + return 0; +} + +static int venus_core_ping(struct venus_core *core, u32 cookie) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct hfi_sys_ping_pkt pkt; + + pkt_sys_ping(&pkt, cookie); + + return venus_iface_cmdq_write(hdev, &pkt, false); +} + +static int venus_core_trigger_ssr(struct venus_core *core, u32 trigger_type) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct hfi_sys_test_ssr_pkt pkt; + int ret; + + ret = pkt_sys_ssr_cmd(&pkt, trigger_type); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, &pkt, false); +} + +static int venus_session_init(struct venus_inst *inst, u32 session_type, + u32 codec) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_init_pkt pkt; + int ret; + + ret = venus_sys_set_debug(hdev, venus_fw_debug); + if (ret) + goto err; + + ret = pkt_session_init(&pkt, inst, session_type, codec); + if (ret) + goto err; + + ret = venus_iface_cmdq_write(hdev, &pkt, true); + if (ret) + goto err; + + return 0; + +err: + venus_flush_debug_queue(hdev); + return ret; +} + +static int venus_session_end(struct venus_inst *inst) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct device *dev = hdev->core->dev; + + if (venus_fw_coverage) { + if (venus_sys_set_coverage(hdev, venus_fw_coverage)) + dev_warn(dev, "fw coverage msg ON failed\n"); + } + + return venus_session_cmd(inst, HFI_CMD_SYS_SESSION_END, true); +} + +static int venus_session_abort(struct venus_inst *inst) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + + venus_flush_debug_queue(hdev); + + return venus_session_cmd(inst, HFI_CMD_SYS_SESSION_ABORT, true); +} + +static int venus_session_flush(struct venus_inst *inst, u32 flush_mode) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_flush_pkt pkt; + int ret; + + ret = pkt_session_flush(&pkt, inst, flush_mode); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, &pkt, true); +} + +static int venus_session_start(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_START, true); +} + +static int venus_session_stop(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_STOP, true); +} + +static int venus_session_continue(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_CONTINUE, false); +} + +static int venus_session_etb(struct venus_inst *inst, + struct hfi_frame_data *in_frame) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + u32 session_type = inst->session_type; + int ret; + + if (session_type == VIDC_SESSION_TYPE_DEC) { + struct hfi_session_empty_buffer_compressed_pkt pkt; + + ret = pkt_session_etb_decoder(&pkt, inst, in_frame); + if (ret) + return ret; + + ret = venus_iface_cmdq_write(hdev, &pkt, false); + } else if (session_type == VIDC_SESSION_TYPE_ENC) { + struct hfi_session_empty_buffer_uncompressed_plane0_pkt pkt; + + ret = pkt_session_etb_encoder(&pkt, inst, in_frame); + if (ret) + return ret; + + ret = venus_iface_cmdq_write(hdev, &pkt, false); + } else { + ret = -EINVAL; + } + + return ret; +} + +static int venus_session_ftb(struct venus_inst *inst, + struct hfi_frame_data *out_frame) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_fill_buffer_pkt pkt; + int ret; + + ret = pkt_session_ftb(&pkt, inst, out_frame); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, &pkt, false); +} + +static int venus_session_set_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_set_buffers_pkt *pkt; + u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE]; + int ret; + + if (bd->buffer_type == HFI_BUFFER_INPUT) + return 0; + + pkt = (struct hfi_session_set_buffers_pkt *)packet; + + ret = pkt_session_set_buffers(pkt, inst, bd); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, pkt, false); +} + +static int venus_session_unset_buffers(struct venus_inst *inst, + struct hfi_buffer_desc *bd) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_release_buffer_pkt *pkt; + u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE]; + int ret; + + if (bd->buffer_type == HFI_BUFFER_INPUT) + return 0; + + pkt = (struct hfi_session_release_buffer_pkt *)packet; + + ret = pkt_session_unset_buffers(pkt, inst, bd); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, pkt, true); +} + +static int venus_session_load_res(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_LOAD_RESOURCES, true); +} + +static int venus_session_release_res(struct venus_inst *inst) +{ + return venus_session_cmd(inst, HFI_CMD_SESSION_RELEASE_RESOURCES, true); +} + +static int venus_session_parse_seq_hdr(struct venus_inst *inst, u32 seq_hdr, + u32 seq_hdr_len) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_parse_sequence_header_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_session_parse_sequence_header_pkt *)packet; + + ret = pkt_session_parse_seq_header(pkt, inst, seq_hdr, seq_hdr_len); + if (ret) + return ret; + + ret = venus_iface_cmdq_write(hdev, pkt, false); + if (ret) + return ret; + + return 0; +} + +static int venus_session_get_seq_hdr(struct venus_inst *inst, u32 seq_hdr, + u32 seq_hdr_len) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_get_sequence_header_pkt *pkt; + u8 packet[IFACEQ_VAR_SMALL_PKT_SIZE]; + int ret; + + pkt = (struct hfi_session_get_sequence_header_pkt *)packet; + + ret = pkt_session_get_seq_hdr(pkt, inst, seq_hdr, seq_hdr_len); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, pkt, false); +} + +static int venus_session_set_property(struct venus_inst *inst, u32 ptype, + void *pdata) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_set_property_pkt *pkt; + u8 packet[IFACEQ_VAR_LARGE_PKT_SIZE]; + int ret; + + pkt = (struct hfi_session_set_property_pkt *)packet; + + ret = pkt_session_set_property(pkt, inst, ptype, pdata); + if (ret == -ENOTSUPP) + return 0; + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, pkt, false); +} + +static int venus_session_get_property(struct venus_inst *inst, u32 ptype) +{ + struct venus_hfi_device *hdev = to_hfi_priv(inst->core); + struct hfi_session_get_property_pkt pkt; + int ret; + + ret = pkt_session_get_property(&pkt, inst, ptype); + if (ret) + return ret; + + return venus_iface_cmdq_write(hdev, &pkt, true); +} + +static int venus_resume(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + int ret = 0; + + mutex_lock(&hdev->lock); + + if (!hdev->suspended) + goto unlock; + + ret = venus_power_on(hdev); + +unlock: + if (!ret) + hdev->suspended = false; + + mutex_unlock(&hdev->lock); + + return ret; +} + +static int venus_suspend_1xx(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct device *dev = core->dev; + void __iomem *cpu_cs_base = hdev->core->cpu_cs_base; + u32 ctrl_status; + int ret; + + if (!hdev->power_enabled || hdev->suspended) + return 0; + + mutex_lock(&hdev->lock); + ret = venus_is_valid_state(hdev); + mutex_unlock(&hdev->lock); + + if (!ret) { + dev_err(dev, "bad state, cannot suspend\n"); + return -EINVAL; + } + + ret = venus_prepare_power_collapse(hdev, true); + if (ret) { + dev_err(dev, "prepare for power collapse fail (%d)\n", ret); + return ret; + } + + mutex_lock(&hdev->lock); + + if (hdev->last_packet_type != HFI_CMD_SYS_PC_PREP) { + mutex_unlock(&hdev->lock); + return -EINVAL; + } + + ret = venus_are_queues_empty(hdev); + if (ret < 0 || !ret) { + mutex_unlock(&hdev->lock); + return -EINVAL; + } + + ctrl_status = readl(cpu_cs_base + CPU_CS_SCIACMDARG0); + if (!(ctrl_status & CPU_CS_SCIACMDARG0_PC_READY)) { + mutex_unlock(&hdev->lock); + return -EINVAL; + } + + ret = venus_power_off(hdev); + if (ret) { + mutex_unlock(&hdev->lock); + return ret; + } + + hdev->suspended = true; + + mutex_unlock(&hdev->lock); + + return 0; +} + +static bool venus_cpu_and_video_core_idle(struct venus_hfi_device *hdev) +{ + void __iomem *wrapper_base = hdev->core->wrapper_base; + void __iomem *wrapper_tz_base = hdev->core->wrapper_tz_base; + void __iomem *cpu_cs_base = hdev->core->cpu_cs_base; + u32 ctrl_status, cpu_status; + + if (IS_V6(hdev->core)) + cpu_status = readl(wrapper_tz_base + WRAPPER_TZ_CPU_STATUS_V6); + else + cpu_status = readl(wrapper_base + WRAPPER_CPU_STATUS); + ctrl_status = readl(cpu_cs_base + CPU_CS_SCIACMDARG0); + + if (cpu_status & WRAPPER_CPU_STATUS_WFI && + ctrl_status & CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK) + return true; + + return false; +} + +static bool venus_cpu_idle_and_pc_ready(struct venus_hfi_device *hdev) +{ + void __iomem *wrapper_base = hdev->core->wrapper_base; + void __iomem *wrapper_tz_base = hdev->core->wrapper_tz_base; + void __iomem *cpu_cs_base = hdev->core->cpu_cs_base; + u32 ctrl_status, cpu_status; + + if (IS_V6(hdev->core)) + cpu_status = readl(wrapper_tz_base + WRAPPER_TZ_CPU_STATUS_V6); + else + cpu_status = readl(wrapper_base + WRAPPER_CPU_STATUS); + ctrl_status = readl(cpu_cs_base + CPU_CS_SCIACMDARG0); + + if (cpu_status & WRAPPER_CPU_STATUS_WFI && + ctrl_status & CPU_CS_SCIACMDARG0_PC_READY) + return true; + + return false; +} + +static int venus_suspend_3xx(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct device *dev = core->dev; + void __iomem *cpu_cs_base = hdev->core->cpu_cs_base; + u32 ctrl_status; + bool val; + int ret; + + if (!hdev->power_enabled || hdev->suspended) + return 0; + + mutex_lock(&hdev->lock); + ret = venus_is_valid_state(hdev); + mutex_unlock(&hdev->lock); + + if (!ret) { + dev_err(dev, "bad state, cannot suspend\n"); + return -EINVAL; + } + + ctrl_status = readl(cpu_cs_base + CPU_CS_SCIACMDARG0); + if (ctrl_status & CPU_CS_SCIACMDARG0_PC_READY) + goto power_off; + + /* + * Power collapse sequence for Venus 3xx and 4xx versions: + * 1. Check for ARM9 and video core to be idle by checking WFI bit + * (bit 0) in CPU status register and by checking Idle (bit 30) in + * Control status register for video core. + * 2. Send a command to prepare for power collapse. + * 3. Check for WFI and PC_READY bits. + */ + ret = readx_poll_timeout(venus_cpu_and_video_core_idle, hdev, val, val, + 1500, 100 * 1500); + if (ret) { + dev_err(dev, "wait for cpu and video core idle fail (%d)\n", ret); + return ret; + } + + ret = venus_prepare_power_collapse(hdev, false); + if (ret) { + dev_err(dev, "prepare for power collapse fail (%d)\n", ret); + return ret; + } + + ret = readx_poll_timeout(venus_cpu_idle_and_pc_ready, hdev, val, val, + 1500, 100 * 1500); + if (ret) + return ret; + +power_off: + mutex_lock(&hdev->lock); + + ret = venus_power_off(hdev); + if (ret) { + dev_err(dev, "venus_power_off (%d)\n", ret); + mutex_unlock(&hdev->lock); + return ret; + } + + hdev->suspended = true; + + mutex_unlock(&hdev->lock); + + return 0; +} + +static int venus_suspend(struct venus_core *core) +{ + if (IS_V3(core) || IS_V4(core) || IS_V6(core)) + return venus_suspend_3xx(core); + + return venus_suspend_1xx(core); +} + +static const struct hfi_ops venus_hfi_ops = { + .core_init = venus_core_init, + .core_deinit = venus_core_deinit, + .core_ping = venus_core_ping, + .core_trigger_ssr = venus_core_trigger_ssr, + + .session_init = venus_session_init, + .session_end = venus_session_end, + .session_abort = venus_session_abort, + .session_flush = venus_session_flush, + .session_start = venus_session_start, + .session_stop = venus_session_stop, + .session_continue = venus_session_continue, + .session_etb = venus_session_etb, + .session_ftb = venus_session_ftb, + .session_set_buffers = venus_session_set_buffers, + .session_unset_buffers = venus_session_unset_buffers, + .session_load_res = venus_session_load_res, + .session_release_res = venus_session_release_res, + .session_parse_seq_hdr = venus_session_parse_seq_hdr, + .session_get_seq_hdr = venus_session_get_seq_hdr, + .session_set_property = venus_session_set_property, + .session_get_property = venus_session_get_property, + + .resume = venus_resume, + .suspend = venus_suspend, + + .isr = venus_isr, + .isr_thread = venus_isr_thread, +}; + +void venus_hfi_destroy(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + + core->priv = NULL; + venus_interface_queues_release(hdev); + mutex_destroy(&hdev->lock); + kfree(hdev); + core->ops = NULL; +} + +int venus_hfi_create(struct venus_core *core) +{ + struct venus_hfi_device *hdev; + int ret; + + hdev = kzalloc(sizeof(*hdev), GFP_KERNEL); + if (!hdev) + return -ENOMEM; + + mutex_init(&hdev->lock); + + hdev->core = core; + hdev->suspended = true; + core->priv = hdev; + core->ops = &venus_hfi_ops; + + ret = venus_interface_queues_init(hdev); + if (ret) + goto err_kfree; + + return 0; + +err_kfree: + kfree(hdev); + core->priv = NULL; + core->ops = NULL; + return ret; +} + +void venus_hfi_queues_reinit(struct venus_core *core) +{ + struct venus_hfi_device *hdev = to_hfi_priv(core); + struct hfi_queue_table_header *tbl_hdr; + struct iface_queue *queue; + struct hfi_sfr *sfr; + unsigned int i; + + mutex_lock(&hdev->lock); + + for (i = 0; i < IFACEQ_NUM; i++) { + queue = &hdev->queues[i]; + queue->qhdr = + IFACEQ_GET_QHDR_START_ADDR(hdev->ifaceq_table.kva, i); + + venus_set_qhdr_defaults(queue->qhdr); + + queue->qhdr->start_addr = queue->qmem.da; + + if (i == IFACEQ_CMD_IDX) + queue->qhdr->type |= HFI_HOST_TO_CTRL_CMD_Q; + else if (i == IFACEQ_MSG_IDX) + queue->qhdr->type |= HFI_CTRL_TO_HOST_MSG_Q; + else if (i == IFACEQ_DBG_IDX) + queue->qhdr->type |= HFI_CTRL_TO_HOST_DBG_Q; + } + + tbl_hdr = hdev->ifaceq_table.kva; + tbl_hdr->version = 0; + tbl_hdr->size = IFACEQ_TABLE_SIZE; + tbl_hdr->qhdr0_offset = sizeof(struct hfi_queue_table_header); + tbl_hdr->qhdr_size = sizeof(struct hfi_queue_header); + tbl_hdr->num_q = IFACEQ_NUM; + tbl_hdr->num_active_q = IFACEQ_NUM; + + /* + * Set receive request to zero on debug queue as there is no + * need of interrupt from video hardware for debug messages + */ + queue = &hdev->queues[IFACEQ_DBG_IDX]; + queue->qhdr->rx_req = 0; + + sfr = hdev->sfr.kva; + sfr->buf_size = ALIGNED_SFR_SIZE; + + /* ensure table and queue header structs are settled in memory */ + wmb(); + + mutex_unlock(&hdev->lock); +} diff --git a/drivers/media/platform/qcom/venus/hfi_venus.h b/drivers/media/platform/qcom/venus/hfi_venus.h new file mode 100644 index 000000000..1b656ef2b --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_venus.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_HFI_VENUS_H__ +#define __VENUS_HFI_VENUS_H__ + +struct venus_core; + +void venus_hfi_destroy(struct venus_core *core); +int venus_hfi_create(struct venus_core *core); +void venus_hfi_queues_reinit(struct venus_core *core); + +#endif diff --git a/drivers/media/platform/qcom/venus/hfi_venus_io.h b/drivers/media/platform/qcom/venus/hfi_venus_io.h new file mode 100644 index 000000000..9735a246c --- /dev/null +++ b/drivers/media/platform/qcom/venus/hfi_venus_io.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_HFI_VENUS_IO_H__ +#define __VENUS_HFI_VENUS_IO_H__ + +#define VBIF_BASE 0x80000 + +#define VBIF_AXI_HALT_CTRL0 0x208 +#define VBIF_AXI_HALT_CTRL1 0x20c + +#define VBIF_AXI_HALT_CTRL0_HALT_REQ BIT(0) +#define VBIF_AXI_HALT_CTRL1_HALT_ACK BIT(0) +#define VBIF_AXI_HALT_ACK_TIMEOUT_US 500000 + +#define CPU_BASE 0xc0000 + +#define CPU_CS_BASE (CPU_BASE + 0x12000) +#define CPU_IC_BASE (CPU_BASE + 0x1f000) +#define CPU_BASE_V6 0xa0000 +#define CPU_CS_BASE_V6 CPU_BASE_V6 +#define CPU_IC_BASE_V6 (CPU_BASE_V6 + 0x138) + +#define CPU_CS_A2HSOFTINTCLR 0x1c + +#define VIDC_CTRL_INIT 0x48 +#define VIDC_CTRL_INIT_RESERVED_BITS31_1_MASK 0xfffffffe +#define VIDC_CTRL_INIT_RESERVED_BITS31_1_SHIFT 1 +#define VIDC_CTRL_INIT_CTRL_MASK 0x1 +#define VIDC_CTRL_INIT_CTRL_SHIFT 0 + +/* HFI control status */ +#define CPU_CS_SCIACMDARG0 0x4c +#define CPU_CS_SCIACMDARG0_MASK 0xff +#define CPU_CS_SCIACMDARG0_SHIFT 0x0 +#define CPU_CS_SCIACMDARG0_ERROR_STATUS_MASK 0xfe +#define CPU_CS_SCIACMDARG0_ERROR_STATUS_SHIFT 0x1 +#define CPU_CS_SCIACMDARG0_INIT_STATUS_MASK 0x1 +#define CPU_CS_SCIACMDARG0_INIT_STATUS_SHIFT 0x0 +#define CPU_CS_SCIACMDARG0_PC_READY BIT(8) +#define CPU_CS_SCIACMDARG0_INIT_IDLE_MSG_MASK BIT(30) + +/* HFI queue table info */ +#define CPU_CS_SCIACMDARG1 0x50 + +/* HFI queue table address */ +#define CPU_CS_SCIACMDARG2 0x54 + +/* Venus cpu */ +#define CPU_CS_SCIACMDARG3 0x58 + +#define SFR_ADDR 0x5c +#define MMAP_ADDR 0x60 +#define UC_REGION_ADDR 0x64 +#define UC_REGION_SIZE 0x68 + +#define CPU_CS_H2XSOFTINTEN_V6 0x148 + +#define CPU_CS_X2RPMH_V6 0x168 +#define CPU_CS_X2RPMH_MASK0_BMSK_V6 0x1 +#define CPU_CS_X2RPMH_MASK0_SHFT_V6 0x0 +#define CPU_CS_X2RPMH_MASK1_BMSK_V6 0x2 +#define CPU_CS_X2RPMH_MASK1_SHFT_V6 0x1 +#define CPU_CS_X2RPMH_SWOVERRIDE_BMSK_V6 0x4 +#define CPU_CS_X2RPMH_SWOVERRIDE_SHFT_V6 0x3 + +/* Relative to CPU_IC_BASE */ +#define CPU_IC_SOFTINT 0x18 +#define CPU_IC_SOFTINT_V6 0x150 +#define CPU_IC_SOFTINT_H2A_MASK 0x8000 +#define CPU_IC_SOFTINT_H2A_SHIFT 0xf +#define CPU_IC_SOFTINT_H2A_SHIFT_V6 0x0 + +/* Venus wrapper */ +#define WRAPPER_BASE_V6 0x000b0000 +#define WRAPPER_BASE 0x000e0000 + +#define WRAPPER_HW_VERSION 0x00 +#define WRAPPER_HW_VERSION_MAJOR_VERSION_MASK 0x78000000 +#define WRAPPER_HW_VERSION_MAJOR_VERSION_SHIFT 28 +#define WRAPPER_HW_VERSION_MINOR_VERSION_MASK 0xfff0000 +#define WRAPPER_HW_VERSION_MINOR_VERSION_SHIFT 16 +#define WRAPPER_HW_VERSION_STEP_VERSION_MASK 0xffff + +#define WRAPPER_CLOCK_CONFIG 0x04 + +#define WRAPPER_INTR_STATUS 0x0c +#define WRAPPER_INTR_STATUS_A2HWD_MASK 0x10 +#define WRAPPER_INTR_STATUS_A2HWD_SHIFT 0x4 +#define WRAPPER_INTR_STATUS_A2H_MASK 0x4 +#define WRAPPER_INTR_STATUS_A2H_SHIFT 0x2 + +#define WRAPPER_INTR_MASK 0x10 +#define WRAPPER_INTR_MASK_A2HWD_BASK 0x10 +#define WRAPPER_INTR_MASK_A2HWD_SHIFT 0x4 +#define WRAPPER_INTR_MASK_A2HVCODEC_MASK 0x8 +#define WRAPPER_INTR_MASK_A2HVCODEC_SHIFT 0x3 +#define WRAPPER_INTR_MASK_A2HCPU_MASK 0x4 +#define WRAPPER_INTR_MASK_A2HCPU_SHIFT 0x2 + +#define WRAPPER_INTR_STATUS_A2HWD_MASK_V6 0x8 +#define WRAPPER_INTR_MASK_A2HWD_BASK_V6 0x8 + +#define WRAPPER_INTR_CLEAR 0x14 +#define WRAPPER_INTR_CLEAR_A2HWD_MASK 0x10 +#define WRAPPER_INTR_CLEAR_A2HWD_SHIFT 0x4 +#define WRAPPER_INTR_CLEAR_A2H_MASK 0x4 +#define WRAPPER_INTR_CLEAR_A2H_SHIFT 0x2 + +#define WRAPPER_POWER_STATUS 0x44 +#define WRAPPER_VDEC_VCODEC_POWER_CONTROL 0x48 +#define WRAPPER_VENC_VCODEC_POWER_CONTROL 0x4c +#define WRAPPER_DEBUG_BRIDGE_LPI_CONTROL_V6 0x54 +#define WRAPPER_DEBUG_BRIDGE_LPI_STATUS_V6 0x58 +#define WRAPPER_VDEC_VENC_AHB_BRIDGE_SYNC_RESET 0x64 + +#define WRAPPER_CPU_CLOCK_CONFIG 0x2000 +#define WRAPPER_CPU_AXI_HALT 0x2008 +#define WRAPPER_CPU_AXI_HALT_HALT BIT(16) +#define WRAPPER_CPU_AXI_HALT_STATUS 0x200c +#define WRAPPER_CPU_AXI_HALT_STATUS_IDLE BIT(24) + +#define WRAPPER_CPU_CGC_DIS 0x2010 +#define WRAPPER_CPU_STATUS 0x2014 +#define WRAPPER_CPU_STATUS_WFI BIT(0) +#define WRAPPER_SW_RESET 0x3000 +#define WRAPPER_CPA_START_ADDR 0x1020 +#define WRAPPER_CPA_END_ADDR 0x1024 +#define WRAPPER_FW_START_ADDR 0x1028 +#define WRAPPER_FW_END_ADDR 0x102C +#define WRAPPER_NONPIX_START_ADDR 0x1030 +#define WRAPPER_NONPIX_END_ADDR 0x1034 +#define WRAPPER_A9SS_SW_RESET 0x3000 +#define WRAPPER_A9SS_SW_RESET_BIT BIT(4) + +/* Venus 4xx */ +#define WRAPPER_VCODEC0_MMCC_POWER_STATUS 0x90 +#define WRAPPER_VCODEC0_MMCC_POWER_CONTROL 0x94 + +#define WRAPPER_VCODEC1_MMCC_POWER_STATUS 0x110 +#define WRAPPER_VCODEC1_MMCC_POWER_CONTROL 0x114 + +/* Venus 6xx */ +#define WRAPPER_CORE_POWER_STATUS_V6 0x80 +#define WRAPPER_CORE_POWER_CONTROL_V6 0x84 + +/* Wrapper TZ 6xx */ +#define WRAPPER_TZ_BASE_V6 0x000c0000 +#define WRAPPER_TZ_CPU_STATUS_V6 0x10 +#define WRAPPER_TZ_XTSS_SW_RESET 0x1000 +#define WRAPPER_XTSS_SW_RESET_BIT BIT(0) + +/* Venus AON */ +#define AON_BASE_V6 0x000e0000 +#define AON_WRAPPER_MVP_NOC_LPI_CONTROL 0x00 +#define AON_WRAPPER_MVP_NOC_LPI_STATUS 0x04 + +#endif diff --git a/drivers/media/platform/qcom/venus/pm_helpers.c b/drivers/media/platform/qcom/venus/pm_helpers.c new file mode 100644 index 000000000..48c9084bb --- /dev/null +++ b/drivers/media/platform/qcom/venus/pm_helpers.c @@ -0,0 +1,1207 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2019 Linaro Ltd. + * + * Author: Stanimir Varbanov + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "core.h" +#include "hfi_parser.h" +#include "hfi_venus_io.h" +#include "pm_helpers.h" +#include "hfi_platform.h" + +static bool legacy_binding; + +static int core_clks_get(struct venus_core *core) +{ + const struct venus_resources *res = core->res; + struct device *dev = core->dev; + unsigned int i; + + for (i = 0; i < res->clks_num; i++) { + core->clks[i] = devm_clk_get(dev, res->clks[i]); + if (IS_ERR(core->clks[i])) + return PTR_ERR(core->clks[i]); + } + + return 0; +} + +static int core_clks_enable(struct venus_core *core) +{ + const struct venus_resources *res = core->res; + const struct freq_tbl *freq_tbl = core->res->freq_tbl; + unsigned int freq_tbl_size = core->res->freq_tbl_size; + unsigned long freq; + unsigned int i; + int ret; + + if (!freq_tbl) + return -EINVAL; + + freq = freq_tbl[freq_tbl_size - 1].freq; + + for (i = 0; i < res->clks_num; i++) { + if (IS_V6(core)) { + ret = clk_set_rate(core->clks[i], freq); + if (ret) + goto err; + } + + ret = clk_prepare_enable(core->clks[i]); + if (ret) + goto err; + } + + return 0; +err: + while (i--) + clk_disable_unprepare(core->clks[i]); + + return ret; +} + +static void core_clks_disable(struct venus_core *core) +{ + const struct venus_resources *res = core->res; + unsigned int i = res->clks_num; + + while (i--) + clk_disable_unprepare(core->clks[i]); +} + +static int core_clks_set_rate(struct venus_core *core, unsigned long freq) +{ + int ret; + + ret = dev_pm_opp_set_rate(core->dev, freq); + if (ret) + return ret; + + ret = clk_set_rate(core->vcodec0_clks[0], freq); + if (ret) + return ret; + + ret = clk_set_rate(core->vcodec1_clks[0], freq); + if (ret) + return ret; + + return 0; +} + +static int vcodec_clks_get(struct venus_core *core, struct device *dev, + struct clk **clks, const char * const *id) +{ + const struct venus_resources *res = core->res; + unsigned int i; + + for (i = 0; i < res->vcodec_clks_num; i++) { + if (!id[i]) + continue; + clks[i] = devm_clk_get(dev, id[i]); + if (IS_ERR(clks[i])) + return PTR_ERR(clks[i]); + } + + return 0; +} + +static int vcodec_clks_enable(struct venus_core *core, struct clk **clks) +{ + const struct venus_resources *res = core->res; + unsigned int i; + int ret; + + for (i = 0; i < res->vcodec_clks_num; i++) { + ret = clk_prepare_enable(clks[i]); + if (ret) + goto err; + } + + return 0; +err: + while (i--) + clk_disable_unprepare(clks[i]); + + return ret; +} + +static void vcodec_clks_disable(struct venus_core *core, struct clk **clks) +{ + const struct venus_resources *res = core->res; + unsigned int i = res->vcodec_clks_num; + + while (i--) + clk_disable_unprepare(clks[i]); +} + +static u32 load_per_instance(struct venus_inst *inst) +{ + u32 mbs; + + if (!inst || !(inst->state >= INST_INIT && inst->state < INST_STOP)) + return 0; + + mbs = (ALIGN(inst->width, 16) / 16) * (ALIGN(inst->height, 16) / 16); + + return mbs * inst->fps; +} + +static u32 load_per_type(struct venus_core *core, u32 session_type) +{ + struct venus_inst *inst = NULL; + u32 mbs_per_sec = 0; + + list_for_each_entry(inst, &core->instances, list) { + if (inst->session_type != session_type) + continue; + + mbs_per_sec += load_per_instance(inst); + } + + return mbs_per_sec; +} + +static void mbs_to_bw(struct venus_inst *inst, u32 mbs, u32 *avg, u32 *peak) +{ + const struct venus_resources *res = inst->core->res; + const struct bw_tbl *bw_tbl; + unsigned int num_rows, i; + + *avg = 0; + *peak = 0; + + if (mbs == 0) + return; + + if (inst->session_type == VIDC_SESSION_TYPE_ENC) { + num_rows = res->bw_tbl_enc_size; + bw_tbl = res->bw_tbl_enc; + } else if (inst->session_type == VIDC_SESSION_TYPE_DEC) { + num_rows = res->bw_tbl_dec_size; + bw_tbl = res->bw_tbl_dec; + } else { + return; + } + + if (!bw_tbl || num_rows == 0) + return; + + for (i = 0; i < num_rows; i++) { + if (i != 0 && mbs > bw_tbl[i].mbs_per_sec) + break; + + if (inst->dpb_fmt & HFI_COLOR_FORMAT_10_BIT_BASE) { + *avg = bw_tbl[i].avg_10bit; + *peak = bw_tbl[i].peak_10bit; + } else { + *avg = bw_tbl[i].avg; + *peak = bw_tbl[i].peak; + } + } +} + +static int load_scale_bw(struct venus_core *core) +{ + struct venus_inst *inst = NULL; + u32 mbs_per_sec, avg, peak, total_avg = 0, total_peak = 0; + + list_for_each_entry(inst, &core->instances, list) { + mbs_per_sec = load_per_instance(inst); + mbs_to_bw(inst, mbs_per_sec, &avg, &peak); + total_avg += avg; + total_peak += peak; + } + + /* + * keep minimum bandwidth vote for "video-mem" path, + * so that clks can be disabled during vdec_session_release(). + * Actual bandwidth drop will be done during device supend + * so that device can power down without any warnings. + */ + + if (!total_avg && !total_peak) + total_avg = kbps_to_icc(1000); + + dev_dbg(core->dev, VDBGL "total: avg_bw: %u, peak_bw: %u\n", + total_avg, total_peak); + + return icc_set_bw(core->video_path, total_avg, total_peak); +} + +static int load_scale_v1(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + const struct freq_tbl *table = core->res->freq_tbl; + unsigned int num_rows = core->res->freq_tbl_size; + unsigned long freq = table[0].freq; + struct device *dev = core->dev; + u32 mbs_per_sec; + unsigned int i; + int ret = 0; + + mutex_lock(&core->lock); + mbs_per_sec = load_per_type(core, VIDC_SESSION_TYPE_ENC) + + load_per_type(core, VIDC_SESSION_TYPE_DEC); + + if (mbs_per_sec > core->res->max_load) + dev_warn(dev, "HW is overloaded, needed: %d max: %d\n", + mbs_per_sec, core->res->max_load); + + if (!mbs_per_sec && num_rows > 1) { + freq = table[num_rows - 1].freq; + goto set_freq; + } + + for (i = 0; i < num_rows; i++) { + if (mbs_per_sec > table[i].load) + break; + freq = table[i].freq; + } + +set_freq: + + ret = core_clks_set_rate(core, freq); + if (ret) { + dev_err(dev, "failed to set clock rate %lu (%d)\n", + freq, ret); + goto exit; + } + + ret = load_scale_bw(core); + if (ret) { + dev_err(dev, "failed to set bandwidth (%d)\n", + ret); + goto exit; + } + +exit: + mutex_unlock(&core->lock); + return ret; +} + +static int core_get_v1(struct venus_core *core) +{ + int ret; + + ret = core_clks_get(core); + if (ret) + return ret; + + ret = devm_pm_opp_set_clkname(core->dev, "core"); + if (ret) + return ret; + + return 0; +} + +static void core_put_v1(struct venus_core *core) +{ +} + +static int core_power_v1(struct venus_core *core, int on) +{ + int ret = 0; + + if (on == POWER_ON) + ret = core_clks_enable(core); + else + core_clks_disable(core); + + return ret; +} + +static const struct venus_pm_ops pm_ops_v1 = { + .core_get = core_get_v1, + .core_put = core_put_v1, + .core_power = core_power_v1, + .load_scale = load_scale_v1, +}; + +static void +vcodec_control_v3(struct venus_core *core, u32 session_type, bool enable) +{ + void __iomem *ctrl; + + if (session_type == VIDC_SESSION_TYPE_DEC) + ctrl = core->wrapper_base + WRAPPER_VDEC_VCODEC_POWER_CONTROL; + else + ctrl = core->wrapper_base + WRAPPER_VENC_VCODEC_POWER_CONTROL; + + if (enable) + writel(0, ctrl); + else + writel(1, ctrl); +} + +static int vdec_get_v3(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + + return vcodec_clks_get(core, dev, core->vcodec0_clks, + core->res->vcodec0_clks); +} + +static int vdec_power_v3(struct device *dev, int on) +{ + struct venus_core *core = dev_get_drvdata(dev); + int ret = 0; + + vcodec_control_v3(core, VIDC_SESSION_TYPE_DEC, true); + + if (on == POWER_ON) + ret = vcodec_clks_enable(core, core->vcodec0_clks); + else + vcodec_clks_disable(core, core->vcodec0_clks); + + vcodec_control_v3(core, VIDC_SESSION_TYPE_DEC, false); + + return ret; +} + +static int venc_get_v3(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + + return vcodec_clks_get(core, dev, core->vcodec1_clks, + core->res->vcodec1_clks); +} + +static int venc_power_v3(struct device *dev, int on) +{ + struct venus_core *core = dev_get_drvdata(dev); + int ret = 0; + + vcodec_control_v3(core, VIDC_SESSION_TYPE_ENC, true); + + if (on == POWER_ON) + ret = vcodec_clks_enable(core, core->vcodec1_clks); + else + vcodec_clks_disable(core, core->vcodec1_clks); + + vcodec_control_v3(core, VIDC_SESSION_TYPE_ENC, false); + + return ret; +} + +static const struct venus_pm_ops pm_ops_v3 = { + .core_get = core_get_v1, + .core_put = core_put_v1, + .core_power = core_power_v1, + .vdec_get = vdec_get_v3, + .vdec_power = vdec_power_v3, + .venc_get = venc_get_v3, + .venc_power = venc_power_v3, + .load_scale = load_scale_v1, +}; + +static int vcodec_control_v4(struct venus_core *core, u32 coreid, bool enable) +{ + void __iomem *ctrl, *stat; + u32 val; + int ret; + + if (IS_V6(core)) { + ctrl = core->wrapper_base + WRAPPER_CORE_POWER_CONTROL_V6; + stat = core->wrapper_base + WRAPPER_CORE_POWER_STATUS_V6; + } else if (coreid == VIDC_CORE_ID_1) { + ctrl = core->wrapper_base + WRAPPER_VCODEC0_MMCC_POWER_CONTROL; + stat = core->wrapper_base + WRAPPER_VCODEC0_MMCC_POWER_STATUS; + } else { + ctrl = core->wrapper_base + WRAPPER_VCODEC1_MMCC_POWER_CONTROL; + stat = core->wrapper_base + WRAPPER_VCODEC1_MMCC_POWER_STATUS; + } + + if (enable) { + writel(0, ctrl); + + ret = readl_poll_timeout(stat, val, val & BIT(1), 1, 100); + if (ret) + return ret; + } else { + writel(1, ctrl); + + ret = readl_poll_timeout(stat, val, !(val & BIT(1)), 1, 100); + if (ret) + return ret; + } + + return 0; +} + +static int poweroff_coreid(struct venus_core *core, unsigned int coreid_mask) +{ + int ret; + + if (coreid_mask & VIDC_CORE_ID_1) { + ret = vcodec_control_v4(core, VIDC_CORE_ID_1, true); + if (ret) + return ret; + + vcodec_clks_disable(core, core->vcodec0_clks); + + ret = vcodec_control_v4(core, VIDC_CORE_ID_1, false); + if (ret) + return ret; + + ret = pm_runtime_put_sync(core->pmdomains[1]); + if (ret < 0) + return ret; + } + + if (coreid_mask & VIDC_CORE_ID_2) { + ret = vcodec_control_v4(core, VIDC_CORE_ID_2, true); + if (ret) + return ret; + + vcodec_clks_disable(core, core->vcodec1_clks); + + ret = vcodec_control_v4(core, VIDC_CORE_ID_2, false); + if (ret) + return ret; + + ret = pm_runtime_put_sync(core->pmdomains[2]); + if (ret < 0) + return ret; + } + + return 0; +} + +static int poweron_coreid(struct venus_core *core, unsigned int coreid_mask) +{ + int ret; + + if (coreid_mask & VIDC_CORE_ID_1) { + ret = pm_runtime_get_sync(core->pmdomains[1]); + if (ret < 0) + return ret; + + ret = vcodec_control_v4(core, VIDC_CORE_ID_1, true); + if (ret) + return ret; + + ret = vcodec_clks_enable(core, core->vcodec0_clks); + if (ret) + return ret; + + ret = vcodec_control_v4(core, VIDC_CORE_ID_1, false); + if (ret < 0) + return ret; + } + + if (coreid_mask & VIDC_CORE_ID_2) { + ret = pm_runtime_get_sync(core->pmdomains[2]); + if (ret < 0) + return ret; + + ret = vcodec_control_v4(core, VIDC_CORE_ID_2, true); + if (ret) + return ret; + + ret = vcodec_clks_enable(core, core->vcodec1_clks); + if (ret) + return ret; + + ret = vcodec_control_v4(core, VIDC_CORE_ID_2, false); + if (ret < 0) + return ret; + } + + return 0; +} + +static inline int power_save_mode_enable(struct venus_inst *inst, + bool enable) +{ + struct venc_controls *enc_ctr = &inst->controls.enc; + const u32 ptype = HFI_PROPERTY_CONFIG_VENC_PERF_MODE; + u32 venc_mode; + int ret = 0; + + if (inst->session_type != VIDC_SESSION_TYPE_ENC) + return 0; + + if (enc_ctr->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CQ) + enable = false; + + venc_mode = enable ? HFI_VENC_PERFMODE_POWER_SAVE : + HFI_VENC_PERFMODE_MAX_QUALITY; + + ret = hfi_session_set_property(inst, ptype, &venc_mode); + if (ret) + return ret; + + inst->flags = enable ? inst->flags | VENUS_LOW_POWER : + inst->flags & ~VENUS_LOW_POWER; + + return ret; +} + +static int move_core_to_power_save_mode(struct venus_core *core, + u32 core_id) +{ + struct venus_inst *inst = NULL; + + mutex_lock(&core->lock); + list_for_each_entry(inst, &core->instances, list) { + if (inst->clk_data.core_id == core_id && + inst->session_type == VIDC_SESSION_TYPE_ENC) + power_save_mode_enable(inst, true); + } + mutex_unlock(&core->lock); + return 0; +} + +static void +min_loaded_core(struct venus_inst *inst, u32 *min_coreid, u32 *min_load, bool low_power) +{ + u32 mbs_per_sec, load, core1_load = 0, core2_load = 0; + u32 cores_max = core_num_max(inst); + struct venus_core *core = inst->core; + struct venus_inst *inst_pos; + unsigned long vpp_freq; + u32 coreid; + + mutex_lock(&core->lock); + + list_for_each_entry(inst_pos, &core->instances, list) { + if (inst_pos == inst) + continue; + + if (inst_pos->state != INST_START) + continue; + + if (inst->session_type == VIDC_SESSION_TYPE_DEC) + vpp_freq = inst_pos->clk_data.vpp_freq; + else if (inst->session_type == VIDC_SESSION_TYPE_ENC) + vpp_freq = low_power ? inst_pos->clk_data.low_power_freq : + inst_pos->clk_data.vpp_freq; + else + continue; + + coreid = inst_pos->clk_data.core_id; + + mbs_per_sec = load_per_instance(inst_pos); + load = mbs_per_sec * vpp_freq; + + if ((coreid & VIDC_CORE_ID_3) == VIDC_CORE_ID_3) { + core1_load += load / 2; + core2_load += load / 2; + } else if (coreid & VIDC_CORE_ID_1) { + core1_load += load; + } else if (coreid & VIDC_CORE_ID_2) { + core2_load += load; + } + } + + *min_coreid = core1_load <= core2_load ? + VIDC_CORE_ID_1 : VIDC_CORE_ID_2; + *min_load = min(core1_load, core2_load); + + if (cores_max < VIDC_CORE_ID_2 || core->res->vcodec_num < 2) { + *min_coreid = VIDC_CORE_ID_1; + *min_load = core1_load; + } + + mutex_unlock(&core->lock); +} + +static int decide_core(struct venus_inst *inst) +{ + const u32 ptype = HFI_PROPERTY_CONFIG_VIDEOCORES_USAGE; + struct venus_core *core = inst->core; + u32 min_coreid, min_load, cur_inst_load; + u32 min_lp_coreid, min_lp_load, cur_inst_lp_load; + struct hfi_videocores_usage_type cu; + unsigned long max_freq; + int ret = 0; + + if (legacy_binding) { + if (inst->session_type == VIDC_SESSION_TYPE_DEC) + cu.video_core_enable_mask = VIDC_CORE_ID_1; + else + cu.video_core_enable_mask = VIDC_CORE_ID_2; + + goto done; + } + + if (inst->clk_data.core_id != VIDC_CORE_ID_DEFAULT) + return 0; + + cur_inst_load = load_per_instance(inst); + cur_inst_load *= inst->clk_data.vpp_freq; + /*TODO : divide this inst->load by work_route */ + + cur_inst_lp_load = load_per_instance(inst); + cur_inst_lp_load *= inst->clk_data.low_power_freq; + /*TODO : divide this inst->load by work_route */ + + max_freq = core->res->freq_tbl[0].freq; + + min_loaded_core(inst, &min_coreid, &min_load, false); + min_loaded_core(inst, &min_lp_coreid, &min_lp_load, true); + + if (cur_inst_load + min_load <= max_freq) { + inst->clk_data.core_id = min_coreid; + cu.video_core_enable_mask = min_coreid; + } else if (cur_inst_lp_load + min_load <= max_freq) { + /* Move current instance to LP and return */ + inst->clk_data.core_id = min_coreid; + cu.video_core_enable_mask = min_coreid; + power_save_mode_enable(inst, true); + } else if (cur_inst_lp_load + min_lp_load <= max_freq) { + /* Move all instances to LP mode and return */ + inst->clk_data.core_id = min_lp_coreid; + cu.video_core_enable_mask = min_lp_coreid; + move_core_to_power_save_mode(core, min_lp_coreid); + } else { + dev_warn(core->dev, "HW can't support this load"); + return -EINVAL; + } + +done: + ret = hfi_session_set_property(inst, ptype, &cu); + if (ret) + return ret; + + return ret; +} + +static int acquire_core(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + unsigned int coreid_mask = 0; + + if (inst->core_acquired) + return 0; + + inst->core_acquired = true; + + if (inst->clk_data.core_id & VIDC_CORE_ID_1) { + if (core->core0_usage_count++) + return 0; + + coreid_mask = VIDC_CORE_ID_1; + } + + if (inst->clk_data.core_id & VIDC_CORE_ID_2) { + if (core->core1_usage_count++) + return 0; + + coreid_mask |= VIDC_CORE_ID_2; + } + + return poweron_coreid(core, coreid_mask); +} + +static int release_core(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + unsigned int coreid_mask = 0; + int ret; + + if (!inst->core_acquired) + return 0; + + if (inst->clk_data.core_id & VIDC_CORE_ID_1) { + if (--core->core0_usage_count) + goto done; + + coreid_mask = VIDC_CORE_ID_1; + } + + if (inst->clk_data.core_id & VIDC_CORE_ID_2) { + if (--core->core1_usage_count) + goto done; + + coreid_mask |= VIDC_CORE_ID_2; + } + + ret = poweroff_coreid(core, coreid_mask); + if (ret) + return ret; + +done: + inst->clk_data.core_id = VIDC_CORE_ID_DEFAULT; + inst->core_acquired = false; + return 0; +} + +static int coreid_power_v4(struct venus_inst *inst, int on) +{ + struct venus_core *core = inst->core; + int ret; + + if (legacy_binding) + return 0; + + if (on == POWER_ON) { + ret = decide_core(inst); + if (ret) + return ret; + + mutex_lock(&core->lock); + ret = acquire_core(inst); + mutex_unlock(&core->lock); + } else { + mutex_lock(&core->lock); + ret = release_core(inst); + mutex_unlock(&core->lock); + } + + return ret; +} + +static int vdec_get_v4(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + + if (!legacy_binding) + return 0; + + return vcodec_clks_get(core, dev, core->vcodec0_clks, + core->res->vcodec0_clks); +} + +static void vdec_put_v4(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + unsigned int i; + + if (!legacy_binding) + return; + + for (i = 0; i < core->res->vcodec_clks_num; i++) + core->vcodec0_clks[i] = NULL; +} + +static int vdec_power_v4(struct device *dev, int on) +{ + struct venus_core *core = dev_get_drvdata(dev); + int ret; + + if (!legacy_binding) + return 0; + + ret = vcodec_control_v4(core, VIDC_CORE_ID_1, true); + if (ret) + return ret; + + if (on == POWER_ON) + ret = vcodec_clks_enable(core, core->vcodec0_clks); + else + vcodec_clks_disable(core, core->vcodec0_clks); + + vcodec_control_v4(core, VIDC_CORE_ID_1, false); + + return ret; +} + +static int venc_get_v4(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + + if (!legacy_binding) + return 0; + + return vcodec_clks_get(core, dev, core->vcodec1_clks, + core->res->vcodec1_clks); +} + +static void venc_put_v4(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + unsigned int i; + + if (!legacy_binding) + return; + + for (i = 0; i < core->res->vcodec_clks_num; i++) + core->vcodec1_clks[i] = NULL; +} + +static int venc_power_v4(struct device *dev, int on) +{ + struct venus_core *core = dev_get_drvdata(dev); + int ret; + + if (!legacy_binding) + return 0; + + ret = vcodec_control_v4(core, VIDC_CORE_ID_2, true); + if (ret) + return ret; + + if (on == POWER_ON) + ret = vcodec_clks_enable(core, core->vcodec1_clks); + else + vcodec_clks_disable(core, core->vcodec1_clks); + + vcodec_control_v4(core, VIDC_CORE_ID_2, false); + + return ret; +} + +static int vcodec_domains_get(struct venus_core *core) +{ + int ret; + struct device **opp_virt_dev; + struct device *dev = core->dev; + const struct venus_resources *res = core->res; + struct device *pd; + unsigned int i; + + if (!res->vcodec_pmdomains_num) + goto skip_pmdomains; + + for (i = 0; i < res->vcodec_pmdomains_num; i++) { + pd = dev_pm_domain_attach_by_name(dev, + res->vcodec_pmdomains[i]); + if (IS_ERR_OR_NULL(pd)) + return PTR_ERR(pd) ? : -ENODATA; + core->pmdomains[i] = pd; + } + +skip_pmdomains: + if (!core->res->opp_pmdomain) + return 0; + + /* Attach the power domain for setting performance state */ + ret = devm_pm_opp_attach_genpd(dev, res->opp_pmdomain, &opp_virt_dev); + if (ret) + goto opp_attach_err; + + core->opp_pmdomain = *opp_virt_dev; + core->opp_dl_venus = device_link_add(dev, core->opp_pmdomain, + DL_FLAG_RPM_ACTIVE | + DL_FLAG_PM_RUNTIME | + DL_FLAG_STATELESS); + if (!core->opp_dl_venus) { + ret = -ENODEV; + goto opp_attach_err; + } + + return 0; + +opp_attach_err: + for (i = 0; i < res->vcodec_pmdomains_num; i++) { + if (IS_ERR_OR_NULL(core->pmdomains[i])) + continue; + dev_pm_domain_detach(core->pmdomains[i], true); + } + + return ret; +} + +static void vcodec_domains_put(struct venus_core *core) +{ + const struct venus_resources *res = core->res; + unsigned int i; + + if (!res->vcodec_pmdomains_num) + goto skip_pmdomains; + + for (i = 0; i < res->vcodec_pmdomains_num; i++) { + if (IS_ERR_OR_NULL(core->pmdomains[i])) + continue; + dev_pm_domain_detach(core->pmdomains[i], true); + } + +skip_pmdomains: + if (!core->has_opp_table) + return; + + if (core->opp_dl_venus) + device_link_del(core->opp_dl_venus); +} + +static int core_resets_reset(struct venus_core *core) +{ + const struct venus_resources *res = core->res; + unsigned int i; + int ret; + + if (!res->resets_num) + return 0; + + for (i = 0; i < res->resets_num; i++) { + ret = reset_control_assert(core->resets[i]); + if (ret) + goto err; + + usleep_range(150, 250); + ret = reset_control_deassert(core->resets[i]); + if (ret) + goto err; + } + +err: + return ret; +} + +static int core_resets_get(struct venus_core *core) +{ + struct device *dev = core->dev; + const struct venus_resources *res = core->res; + unsigned int i; + int ret; + + if (!res->resets_num) + return 0; + + for (i = 0; i < res->resets_num; i++) { + core->resets[i] = + devm_reset_control_get_exclusive(dev, res->resets[i]); + if (IS_ERR(core->resets[i])) { + ret = PTR_ERR(core->resets[i]); + return ret; + } + } + + return 0; +} + +static int core_get_v4(struct venus_core *core) +{ + struct device *dev = core->dev; + const struct venus_resources *res = core->res; + int ret; + + ret = core_clks_get(core); + if (ret) + return ret; + + if (!res->vcodec_pmdomains_num) + legacy_binding = true; + + dev_info(dev, "%s legacy binding\n", legacy_binding ? "" : "non"); + + ret = vcodec_clks_get(core, dev, core->vcodec0_clks, res->vcodec0_clks); + if (ret) + return ret; + + ret = vcodec_clks_get(core, dev, core->vcodec1_clks, res->vcodec1_clks); + if (ret) + return ret; + + ret = core_resets_get(core); + if (ret) + return ret; + + if (legacy_binding) + return 0; + + ret = devm_pm_opp_set_clkname(dev, "core"); + if (ret) + return ret; + + ret = vcodec_domains_get(core); + if (ret) + return ret; + + if (core->res->opp_pmdomain) { + ret = devm_pm_opp_of_add_table(dev); + if (!ret) { + core->has_opp_table = true; + } else if (ret != -ENODEV) { + dev_err(dev, "invalid OPP table in device tree\n"); + return ret; + } + } + + return 0; +} + +static void core_put_v4(struct venus_core *core) +{ + if (legacy_binding) + return; + + vcodec_domains_put(core); +} + +static int core_power_v4(struct venus_core *core, int on) +{ + struct device *dev = core->dev; + struct device *pmctrl = core->pmdomains[0]; + int ret = 0; + + if (on == POWER_ON) { + if (pmctrl) { + ret = pm_runtime_resume_and_get(pmctrl); + if (ret < 0) { + return ret; + } + } + + ret = core_resets_reset(core); + if (ret) { + if (pmctrl) + pm_runtime_put_sync(pmctrl); + return ret; + } + + ret = core_clks_enable(core); + if (ret < 0 && pmctrl) + pm_runtime_put_sync(pmctrl); + } else { + /* Drop the performance state vote */ + if (core->opp_pmdomain) + dev_pm_opp_set_rate(dev, 0); + + core_clks_disable(core); + + ret = core_resets_reset(core); + + if (pmctrl) + pm_runtime_put_sync(pmctrl); + } + + return ret; +} + +static unsigned long calculate_inst_freq(struct venus_inst *inst, + unsigned long filled_len) +{ + unsigned long vpp_freq_per_mb = 0, vpp_freq = 0, vsp_freq = 0; + u32 fps = (u32)inst->fps; + u32 mbs_per_sec; + + mbs_per_sec = load_per_instance(inst); + + if (inst->state != INST_START) + return 0; + + if (inst->session_type == VIDC_SESSION_TYPE_ENC) { + vpp_freq_per_mb = inst->flags & VENUS_LOW_POWER ? + inst->clk_data.low_power_freq : + inst->clk_data.vpp_freq; + + vpp_freq = mbs_per_sec * vpp_freq_per_mb; + } else { + vpp_freq = mbs_per_sec * inst->clk_data.vpp_freq; + } + + /* 21 / 20 is overhead factor */ + vpp_freq += vpp_freq / 20; + vsp_freq = mbs_per_sec * inst->clk_data.vsp_freq; + + /* 10 / 7 is overhead factor */ + if (inst->session_type == VIDC_SESSION_TYPE_ENC) + vsp_freq += (inst->controls.enc.bitrate * 10) / 7; + else + vsp_freq += ((fps * filled_len * 8) * 10) / 7; + + return max(vpp_freq, vsp_freq); +} + +static int load_scale_v4(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + const struct freq_tbl *table = core->res->freq_tbl; + unsigned int num_rows = core->res->freq_tbl_size; + struct device *dev = core->dev; + unsigned long freq = 0, freq_core1 = 0, freq_core2 = 0; + unsigned long filled_len = 0; + int i, ret = 0; + + for (i = 0; i < inst->num_input_bufs; i++) + filled_len = max(filled_len, inst->payloads[i]); + + if (inst->session_type == VIDC_SESSION_TYPE_DEC && !filled_len) + return ret; + + freq = calculate_inst_freq(inst, filled_len); + inst->clk_data.freq = freq; + + mutex_lock(&core->lock); + list_for_each_entry(inst, &core->instances, list) { + if (inst->clk_data.core_id == VIDC_CORE_ID_1) { + freq_core1 += inst->clk_data.freq; + } else if (inst->clk_data.core_id == VIDC_CORE_ID_2) { + freq_core2 += inst->clk_data.freq; + } else if (inst->clk_data.core_id == VIDC_CORE_ID_3) { + freq_core1 += inst->clk_data.freq; + freq_core2 += inst->clk_data.freq; + } + } + + freq = max(freq_core1, freq_core2); + + if (freq > table[0].freq) { + dev_dbg(dev, VDBGL "requested clock rate: %lu scaling clock rate : %lu\n", + freq, table[0].freq); + + freq = table[0].freq; + goto set_freq; + } + + for (i = num_rows - 1 ; i >= 0; i--) { + if (freq <= table[i].freq) { + freq = table[i].freq; + break; + } + } + +set_freq: + + ret = core_clks_set_rate(core, freq); + if (ret) { + dev_err(dev, "failed to set clock rate %lu (%d)\n", + freq, ret); + goto exit; + } + + ret = load_scale_bw(core); + if (ret) { + dev_err(dev, "failed to set bandwidth (%d)\n", + ret); + goto exit; + } + +exit: + mutex_unlock(&core->lock); + return ret; +} + +static const struct venus_pm_ops pm_ops_v4 = { + .core_get = core_get_v4, + .core_put = core_put_v4, + .core_power = core_power_v4, + .vdec_get = vdec_get_v4, + .vdec_put = vdec_put_v4, + .vdec_power = vdec_power_v4, + .venc_get = venc_get_v4, + .venc_put = venc_put_v4, + .venc_power = venc_power_v4, + .coreid_power = coreid_power_v4, + .load_scale = load_scale_v4, +}; + +const struct venus_pm_ops *venus_pm_get(enum hfi_version version) +{ + switch (version) { + case HFI_VERSION_1XX: + default: + return &pm_ops_v1; + case HFI_VERSION_3XX: + return &pm_ops_v3; + case HFI_VERSION_4XX: + case HFI_VERSION_6XX: + return &pm_ops_v4; + } + + return NULL; +} diff --git a/drivers/media/platform/qcom/venus/pm_helpers.h b/drivers/media/platform/qcom/venus/pm_helpers.h new file mode 100644 index 000000000..a492c50c5 --- /dev/null +++ b/drivers/media/platform/qcom/venus/pm_helpers.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2019 Linaro Ltd. */ +#ifndef __VENUS_PM_HELPERS_H__ +#define __VENUS_PM_HELPERS_H__ + +struct device; +struct venus_core; + +#define POWER_ON 1 +#define POWER_OFF 0 + +struct venus_pm_ops { + int (*core_get)(struct venus_core *core); + void (*core_put)(struct venus_core *core); + int (*core_power)(struct venus_core *core, int on); + + int (*vdec_get)(struct device *dev); + void (*vdec_put)(struct device *dev); + int (*vdec_power)(struct device *dev, int on); + + int (*venc_get)(struct device *dev); + void (*venc_put)(struct device *dev); + int (*venc_power)(struct device *dev, int on); + + int (*coreid_power)(struct venus_inst *inst, int on); + + int (*load_scale)(struct venus_inst *inst); +}; + +const struct venus_pm_ops *venus_pm_get(enum hfi_version version); + +static inline int venus_pm_load_scale(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + + if (!core->pm_ops || !core->pm_ops->load_scale) + return 0; + + return core->pm_ops->load_scale(inst); +} + +static inline int venus_pm_acquire_core(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + const struct venus_pm_ops *pm_ops = core->pm_ops; + int ret = 0; + + if (pm_ops && pm_ops->coreid_power) + ret = pm_ops->coreid_power(inst, POWER_ON); + + return ret; +} + +static inline int venus_pm_release_core(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + const struct venus_pm_ops *pm_ops = core->pm_ops; + int ret = 0; + + if (pm_ops && pm_ops->coreid_power) + ret = pm_ops->coreid_power(inst, POWER_OFF); + + return ret; +} + +#endif diff --git a/drivers/media/platform/qcom/venus/vdec.c b/drivers/media/platform/qcom/venus/vdec.c new file mode 100644 index 000000000..1a52c2ea2 --- /dev/null +++ b/drivers/media/platform/qcom/venus/vdec.c @@ -0,0 +1,1847 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hfi_venus_io.h" +#include "hfi_parser.h" +#include "core.h" +#include "helpers.h" +#include "vdec.h" +#include "pm_helpers.h" + +/* + * Three resons to keep MPLANE formats (despite that the number of planes + * currently is one): + * - the MPLANE formats allow only one plane to be used + * - the downstream driver use MPLANE formats too + * - future firmware versions could add support for >1 planes + */ +static const struct venus_format vdec_formats[] = { + { + .pixfmt = V4L2_PIX_FMT_NV12, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_QC08C, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_QC10C, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG4, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG2, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H263, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_G, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_VC1_ANNEX_L, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_VP8, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_VP9, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_XVID, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, { + .pixfmt = V4L2_PIX_FMT_HEVC, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, +}; + +static const struct venus_format * +find_format(struct venus_inst *inst, u32 pixfmt, u32 type) +{ + const struct venus_format *fmt = vdec_formats; + unsigned int size = ARRAY_SIZE(vdec_formats); + unsigned int i; + + for (i = 0; i < size; i++) { + if (fmt[i].pixfmt == pixfmt) + break; + } + + if (i == size || fmt[i].type != type) + return NULL; + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + !venus_helper_check_codec(inst, fmt[i].pixfmt)) + return NULL; + + if (V4L2_TYPE_IS_CAPTURE(type) && + !venus_helper_check_format(inst, fmt[i].pixfmt)) + return NULL; + + if (V4L2_TYPE_IS_CAPTURE(type) && fmt[i].pixfmt == V4L2_PIX_FMT_QC10C && + !(inst->bit_depth == VIDC_BITDEPTH_10)) + return NULL; + + return &fmt[i]; +} + +static const struct venus_format * +find_format_by_index(struct venus_inst *inst, unsigned int index, u32 type) +{ + const struct venus_format *fmt = vdec_formats; + unsigned int size = ARRAY_SIZE(vdec_formats); + unsigned int i, k = 0; + + if (index > size) + return NULL; + + for (i = 0; i < size; i++) { + bool valid; + + if (fmt[i].type != type) + continue; + + if (V4L2_TYPE_IS_OUTPUT(type)) { + valid = venus_helper_check_codec(inst, fmt[i].pixfmt); + } else if (V4L2_TYPE_IS_CAPTURE(type)) { + valid = venus_helper_check_format(inst, fmt[i].pixfmt); + + if (fmt[i].pixfmt == V4L2_PIX_FMT_QC10C && + !(inst->bit_depth == VIDC_BITDEPTH_10)) + valid = false; + } + + if (k == index && valid) + break; + if (valid) + k++; + } + + if (i == size) + return NULL; + + return &fmt[i]; +} + +static const struct venus_format * +vdec_try_fmt_common(struct venus_inst *inst, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt; + const struct venus_format *fmt; + u32 szimage; + + memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved)); + memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); + + fmt = find_format(inst, pixmp->pixelformat, f->type); + if (!fmt) { + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + pixmp->pixelformat = V4L2_PIX_FMT_NV12; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + pixmp->pixelformat = V4L2_PIX_FMT_H264; + else + return NULL; + fmt = find_format(inst, pixmp->pixelformat, f->type); + if (!fmt) + return NULL; + } + + pixmp->width = clamp(pixmp->width, frame_width_min(inst), + frame_width_max(inst)); + pixmp->height = clamp(pixmp->height, frame_height_min(inst), + frame_height_max(inst)); + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + pixmp->height = ALIGN(pixmp->height, 32); + + if (pixmp->field == V4L2_FIELD_ANY) + pixmp->field = V4L2_FIELD_NONE; + pixmp->num_planes = fmt->num_planes; + pixmp->flags = 0; + + szimage = venus_helper_get_framesz(pixmp->pixelformat, pixmp->width, + pixmp->height); + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pfmt[0].sizeimage = szimage; + pfmt[0].bytesperline = ALIGN(pixmp->width, 128); + } else { + pfmt[0].sizeimage = clamp_t(u32, pfmt[0].sizeimage, 0, SZ_8M); + pfmt[0].sizeimage = max(pfmt[0].sizeimage, szimage); + pfmt[0].bytesperline = 0; + } + + return fmt; +} + +static int vdec_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + + vdec_try_fmt_common(inst, f); + + return 0; +} + +static int vdec_check_src_change(struct venus_inst *inst) +{ + int ret; + + if (inst->subscriptions & V4L2_EVENT_SOURCE_CHANGE && + inst->codec_state == VENUS_DEC_STATE_INIT && + !inst->reconfig) + return -EINVAL; + + if (inst->subscriptions & V4L2_EVENT_SOURCE_CHANGE) + return 0; + + /* + * The code snippet below is a workaround for backward compatibility + * with applications which doesn't support V4L2 events. It will be + * dropped in future once those applications are fixed. + */ + + if (inst->codec_state != VENUS_DEC_STATE_INIT) + goto done; + + ret = wait_event_timeout(inst->reconf_wait, inst->reconfig, + msecs_to_jiffies(100)); + if (!ret) + return -EINVAL; + + if (!(inst->codec_state == VENUS_DEC_STATE_CAPTURE_SETUP) || + !inst->reconfig) + dev_dbg(inst->core->dev, VDBGH "wrong state\n"); + +done: + return 0; +} + +static int vdec_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt = NULL; + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + int ret; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + fmt = inst->fmt_cap; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + fmt = inst->fmt_out; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = vdec_check_src_change(inst); + if (ret) + return ret; + } + + pixmp->pixelformat = fmt->pixfmt; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixmp->width = inst->width; + pixmp->height = inst->height; + pixmp->colorspace = inst->colorspace; + pixmp->ycbcr_enc = inst->ycbcr_enc; + pixmp->quantization = inst->quantization; + pixmp->xfer_func = inst->xfer_func; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixmp->width = inst->out_width; + pixmp->height = inst->out_height; + } + + vdec_try_fmt_common(inst, f); + + return 0; +} + +static int vdec_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_pix_format_mplane orig_pixmp; + const struct venus_format *fmt; + struct v4l2_format format; + u32 pixfmt_out = 0, pixfmt_cap = 0; + struct vb2_queue *q; + + q = v4l2_m2m_get_vq(inst->m2m_ctx, f->type); + if (!q) + return -EINVAL; + + if (vb2_is_busy(q)) + return -EBUSY; + + orig_pixmp = *pixmp; + + fmt = vdec_try_fmt_common(inst, f); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixfmt_out = pixmp->pixelformat; + pixfmt_cap = inst->fmt_cap->pixfmt; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixfmt_cap = pixmp->pixelformat; + pixfmt_out = inst->fmt_out->pixfmt; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_out; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + vdec_try_fmt_common(inst, &format); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + inst->out_width = format.fmt.pix_mp.width; + inst->out_height = format.fmt.pix_mp.height; + inst->colorspace = pixmp->colorspace; + inst->ycbcr_enc = pixmp->ycbcr_enc; + inst->quantization = pixmp->quantization; + inst->xfer_func = pixmp->xfer_func; + inst->input_buf_size = pixmp->plane_fmt[0].sizeimage; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_cap; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + vdec_try_fmt_common(inst, &format); + + inst->width = format.fmt.pix_mp.width; + inst->height = format.fmt.pix_mp.height; + inst->crop.top = 0; + inst->crop.left = 0; + inst->crop.width = inst->width; + inst->crop.height = inst->height; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->fmt_out = fmt; + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + inst->fmt_cap = fmt; + inst->output2_buf_size = + venus_helper_get_framesz(pixfmt_cap, orig_pixmp.width, orig_pixmp.height); + } + + return 0; +} + +static int +vdec_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct venus_inst *inst = to_inst(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE && + s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + s->r.top = 0; + s->r.left = 0; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + s->r.width = inst->out_width; + s->r.height = inst->out_height; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_PADDED: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + s->r.width = inst->width; + s->r.height = inst->height; + break; + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + s->r = inst->crop; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int +vdec_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + strscpy(cap->driver, "qcom-venus", sizeof(cap->driver)); + strscpy(cap->card, "Qualcomm Venus video decoder", sizeof(cap->card)); + strscpy(cap->bus_info, "platform:qcom-venus", sizeof(cap->bus_info)); + + return 0; +} + +static int vdec_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + memset(f->reserved, 0, sizeof(f->reserved)); + + fmt = find_format_by_index(inst, f->index, f->type); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixfmt; + f->flags = fmt->flags; + + return 0; +} + +static int vdec_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct venus_inst *inst = to_inst(file); + struct v4l2_captureparm *cap = &a->parm.capture; + struct v4l2_fract *timeperframe = &cap->timeperframe; + u64 us_per_frame, fps; + + if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + memset(cap->reserved, 0, sizeof(cap->reserved)); + if (!timeperframe->denominator) + timeperframe->denominator = inst->timeperframe.denominator; + if (!timeperframe->numerator) + timeperframe->numerator = inst->timeperframe.numerator; + cap->readbuffers = 0; + cap->extendedmode = 0; + cap->capability = V4L2_CAP_TIMEPERFRAME; + us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC; + do_div(us_per_frame, timeperframe->denominator); + + if (!us_per_frame) + return -EINVAL; + + fps = (u64)USEC_PER_SEC; + do_div(fps, us_per_frame); + + inst->fps = fps; + inst->timeperframe = *timeperframe; + + return 0; +} + +static int vdec_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + fmt = find_format(inst, fsize->pixel_format, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (!fmt) { + fmt = find_format(inst, fsize->pixel_format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (!fmt) + return -EINVAL; + } + + if (fsize->index) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + fsize->stepwise.min_width = frame_width_min(inst); + fsize->stepwise.max_width = frame_width_max(inst); + fsize->stepwise.step_width = frame_width_step(inst); + fsize->stepwise.min_height = frame_height_min(inst); + fsize->stepwise.max_height = frame_height_max(inst); + fsize->stepwise.step_height = frame_height_step(inst); + + return 0; +} + +static int vdec_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + struct venus_inst *inst = container_of(fh, struct venus_inst, fh); + int ret; + + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + ret = v4l2_src_change_event_subscribe(fh, sub); + if (ret) + return ret; + inst->subscriptions |= V4L2_EVENT_SOURCE_CHANGE; + return 0; + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static int +vdec_decoder_cmd(struct file *file, void *fh, struct v4l2_decoder_cmd *cmd) +{ + struct venus_inst *inst = to_inst(file); + struct vb2_queue *dst_vq; + struct hfi_frame_data fdata = {0}; + int ret; + + ret = v4l2_m2m_ioctl_try_decoder_cmd(file, fh, cmd); + if (ret) + return ret; + + mutex_lock(&inst->lock); + + if (cmd->cmd == V4L2_DEC_CMD_STOP) { + /* + * Implement V4L2_DEC_CMD_STOP by enqueue an empty buffer on + * decoder input to signal EOS. + */ + if (!(inst->streamon_out && inst->streamon_cap)) + goto unlock; + + fdata.buffer_type = HFI_BUFFER_INPUT; + fdata.flags |= HFI_BUFFERFLAG_EOS; + if (IS_V6(inst->core)) + fdata.device_addr = 0; + else + fdata.device_addr = 0xdeadb000; + + ret = hfi_session_process_buf(inst, &fdata); + + if (!ret && inst->codec_state == VENUS_DEC_STATE_DECODING) { + inst->codec_state = VENUS_DEC_STATE_DRAIN; + inst->drain_active = true; + } + } else if (cmd->cmd == V4L2_DEC_CMD_START && + inst->codec_state == VENUS_DEC_STATE_STOPPED) { + dst_vq = v4l2_m2m_get_vq(inst->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + vb2_clear_last_buffer_dequeued(dst_vq); + + inst->codec_state = VENUS_DEC_STATE_DECODING; + } + +unlock: + mutex_unlock(&inst->lock); + return ret; +} + +static const struct v4l2_ioctl_ops vdec_ioctl_ops = { + .vidioc_querycap = vdec_querycap, + .vidioc_enum_fmt_vid_cap = vdec_enum_fmt, + .vidioc_enum_fmt_vid_out = vdec_enum_fmt, + .vidioc_s_fmt_vid_cap_mplane = vdec_s_fmt, + .vidioc_s_fmt_vid_out_mplane = vdec_s_fmt, + .vidioc_g_fmt_vid_cap_mplane = vdec_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vdec_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = vdec_try_fmt, + .vidioc_try_fmt_vid_out_mplane = vdec_try_fmt, + .vidioc_g_selection = vdec_g_selection, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_s_parm = vdec_s_parm, + .vidioc_enum_framesizes = vdec_enum_framesizes, + .vidioc_subscribe_event = vdec_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_try_decoder_cmd = v4l2_m2m_ioctl_try_decoder_cmd, + .vidioc_decoder_cmd = vdec_decoder_cmd, +}; + +static int vdec_pm_get(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev_dec; + int ret; + + mutex_lock(&core->pm_lock); + ret = pm_runtime_resume_and_get(dev); + mutex_unlock(&core->pm_lock); + + return ret; +} + +static int vdec_pm_put(struct venus_inst *inst, bool autosuspend) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev_dec; + int ret; + + mutex_lock(&core->pm_lock); + + if (autosuspend) + ret = pm_runtime_put_autosuspend(dev); + else + ret = pm_runtime_put_sync(dev); + + mutex_unlock(&core->pm_lock); + + return ret < 0 ? ret : 0; +} + +static int vdec_pm_get_put(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev_dec; + int ret = 0; + + mutex_lock(&core->pm_lock); + + if (pm_runtime_suspended(dev)) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto error; + + ret = pm_runtime_put_autosuspend(dev); + } + +error: + mutex_unlock(&core->pm_lock); + + return ret < 0 ? ret : 0; +} + +static void vdec_pm_touch(struct venus_inst *inst) +{ + pm_runtime_mark_last_busy(inst->core->dev_dec); +} + +static int vdec_set_properties(struct venus_inst *inst) +{ + struct vdec_controls *ctr = &inst->controls.dec; + struct hfi_enable en = { .enable = 1 }; + u32 ptype, decode_order, conceal; + int ret; + + if (ctr->post_loop_deb_mode) { + ptype = HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER; + ret = hfi_session_set_property(inst, ptype, &en); + if (ret) + return ret; + } + + if (ctr->display_delay_enable && ctr->display_delay == 0) { + ptype = HFI_PROPERTY_PARAM_VDEC_OUTPUT_ORDER; + decode_order = HFI_OUTPUT_ORDER_DECODE; + ret = hfi_session_set_property(inst, ptype, &decode_order); + if (ret) + return ret; + } + + ptype = HFI_PROPERTY_PARAM_VDEC_CONCEAL_COLOR; + conceal = ctr->conceal_color & 0xffff; + conceal |= ((ctr->conceal_color >> 16) & 0xffff) << 10; + conceal |= ((ctr->conceal_color >> 32) & 0xffff) << 20; + + ret = hfi_session_set_property(inst, ptype, &conceal); + if (ret) + return ret; + + return 0; +} + +static int vdec_set_work_route(struct venus_inst *inst) +{ + u32 ptype = HFI_PROPERTY_PARAM_WORK_ROUTE; + struct hfi_video_work_route wr; + + if (!IS_V6(inst->core)) + return 0; + + wr.video_work_route = inst->core->res->num_vpp_pipes; + + return hfi_session_set_property(inst, ptype, &wr); +} + +#define is_ubwc_fmt(fmt) (!!((fmt) & HFI_COLOR_FORMAT_UBWC_BASE)) + +static int vdec_output_conf(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct hfi_enable en = { .enable = 1 }; + struct hfi_buffer_requirements bufreq; + u32 width = inst->width; + u32 height = inst->height; + u32 out_fmt, out2_fmt; + bool ubwc = false; + u32 ptype; + int ret; + + ret = venus_helper_set_work_mode(inst); + if (ret) + return ret; + + if (core->res->hfi_version == HFI_VERSION_1XX) { + ptype = HFI_PROPERTY_PARAM_VDEC_CONTINUE_DATA_TRANSFER; + ret = hfi_session_set_property(inst, ptype, &en); + if (ret) + return ret; + } + + /* Force searching UBWC formats for bigger then HD resolutions */ + if (width > 1920 && height > ALIGN(1080, 32)) + ubwc = true; + + /* For Venus v4/v6 UBWC format is mandatory */ + if (IS_V4(core) || IS_V6(core)) + ubwc = true; + + ret = venus_helper_get_out_fmts(inst, inst->fmt_cap->pixfmt, &out_fmt, + &out2_fmt, ubwc); + if (ret) + return ret; + + inst->output_buf_size = + venus_helper_get_framesz_raw(out_fmt, width, height); + inst->output2_buf_size = + venus_helper_get_framesz_raw(out2_fmt, width, height); + + if (is_ubwc_fmt(out_fmt)) { + inst->opb_buftype = HFI_BUFFER_OUTPUT2; + inst->opb_fmt = out2_fmt; + inst->dpb_buftype = HFI_BUFFER_OUTPUT; + inst->dpb_fmt = out_fmt; + } else if (is_ubwc_fmt(out2_fmt)) { + inst->opb_buftype = HFI_BUFFER_OUTPUT; + inst->opb_fmt = out_fmt; + inst->dpb_buftype = HFI_BUFFER_OUTPUT2; + inst->dpb_fmt = out2_fmt; + } else { + inst->opb_buftype = HFI_BUFFER_OUTPUT; + inst->opb_fmt = out_fmt; + inst->dpb_buftype = 0; + inst->dpb_fmt = 0; + } + + ret = venus_helper_set_raw_format(inst, inst->opb_fmt, + inst->opb_buftype); + if (ret) + return ret; + + ret = venus_helper_set_format_constraints(inst); + if (ret) + return ret; + + if (inst->dpb_fmt) { + ret = venus_helper_set_multistream(inst, false, true); + if (ret) + return ret; + + ret = venus_helper_set_raw_format(inst, inst->dpb_fmt, + inst->dpb_buftype); + if (ret) + return ret; + + ret = venus_helper_set_output_resolution(inst, width, height, + HFI_BUFFER_OUTPUT2); + if (ret) + return ret; + } + + if (IS_V3(core) || IS_V4(core) || IS_V6(core)) { + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq); + if (ret) + return ret; + + if (bufreq.size > inst->output_buf_size) + return -EINVAL; + + if (inst->dpb_fmt) { + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT2, + &bufreq); + if (ret) + return ret; + + if (bufreq.size > inst->output2_buf_size) + return -EINVAL; + } + + if (inst->output2_buf_size) { + ret = venus_helper_set_bufsize(inst, + inst->output2_buf_size, + HFI_BUFFER_OUTPUT2); + if (ret) + return ret; + } + + if (inst->output_buf_size) { + ret = venus_helper_set_bufsize(inst, + inst->output_buf_size, + HFI_BUFFER_OUTPUT); + if (ret) + return ret; + } + } + + ret = venus_helper_set_dyn_bufmode(inst); + if (ret) + return ret; + + return 0; +} + +static int vdec_session_init(struct venus_inst *inst) +{ + int ret; + + ret = venus_helper_session_init(inst); + if (ret == -EALREADY) + return 0; + else if (ret) + return ret; + + ret = venus_helper_set_input_resolution(inst, frame_width_min(inst), + frame_height_min(inst)); + if (ret) + goto deinit; + + return 0; +deinit: + hfi_session_deinit(inst); + return ret; +} + +static int vdec_num_buffers(struct venus_inst *inst, unsigned int *in_num, + unsigned int *out_num) +{ + enum hfi_version ver = inst->core->res->hfi_version; + struct hfi_buffer_requirements bufreq; + int ret; + + *in_num = *out_num = 0; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq); + if (ret) + return ret; + + *in_num = HFI_BUFREQ_COUNT_MIN(&bufreq, ver); + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq); + if (ret) + return ret; + + *out_num = HFI_BUFREQ_COUNT_MIN(&bufreq, ver); + + return 0; +} + +static int vdec_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + struct venus_core *core = inst->core; + unsigned int in_num, out_num; + int ret = 0; + + if (*num_planes) { + unsigned int output_buf_size = venus_helper_get_opb_size(inst); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + *num_planes != inst->fmt_out->num_planes) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + *num_planes != inst->fmt_cap->num_planes) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + sizes[0] < inst->input_buf_size) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + sizes[0] < output_buf_size) + return -EINVAL; + + return 0; + } + + if (test_bit(0, &core->sys_error)) { + if (inst->nonblock) + return -EAGAIN; + + ret = wait_event_interruptible(core->sys_err_done, + !test_bit(0, &core->sys_error)); + if (ret) + return ret; + } + + ret = vdec_pm_get(inst); + if (ret) + return ret; + + ret = vdec_session_init(inst); + if (ret) + goto put_power; + + ret = vdec_num_buffers(inst, &in_num, &out_num); + if (ret) + goto put_power; + + ret = vdec_pm_put(inst, false); + if (ret) + return ret; + + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + *num_planes = inst->fmt_out->num_planes; + sizes[0] = venus_helper_get_framesz(inst->fmt_out->pixfmt, + inst->out_width, + inst->out_height); + sizes[0] = max(sizes[0], inst->input_buf_size); + inst->input_buf_size = sizes[0]; + *num_buffers = max(*num_buffers, in_num); + inst->num_input_bufs = *num_buffers; + inst->num_output_bufs = out_num; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + *num_planes = inst->fmt_cap->num_planes; + sizes[0] = venus_helper_get_framesz(inst->fmt_cap->pixfmt, + inst->width, + inst->height); + inst->output_buf_size = sizes[0]; + *num_buffers = max(*num_buffers, out_num); + inst->num_output_bufs = *num_buffers; + + mutex_lock(&inst->lock); + if (inst->codec_state == VENUS_DEC_STATE_CAPTURE_SETUP) + inst->codec_state = VENUS_DEC_STATE_STOPPED; + mutex_unlock(&inst->lock); + break; + default: + ret = -EINVAL; + break; + } + + return ret; + +put_power: + vdec_pm_put(inst, false); + return ret; +} + +static int vdec_verify_conf(struct venus_inst *inst) +{ + enum hfi_version ver = inst->core->res->hfi_version; + struct hfi_buffer_requirements bufreq; + int ret; + + if (!inst->num_input_bufs || !inst->num_output_bufs) + return -EINVAL; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq); + if (ret) + return ret; + + if (inst->num_output_bufs < bufreq.count_actual || + inst->num_output_bufs < HFI_BUFREQ_COUNT_MIN(&bufreq, ver)) + return -EINVAL; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq); + if (ret) + return ret; + + if (inst->num_input_bufs < HFI_BUFREQ_COUNT_MIN(&bufreq, ver)) + return -EINVAL; + + return 0; +} + +static int vdec_start_capture(struct venus_inst *inst) +{ + int ret; + + if (!inst->streamon_out) + return 0; + + if (inst->codec_state == VENUS_DEC_STATE_DECODING) { + if (inst->reconfig) + goto reconfigure; + + venus_helper_queue_dpb_bufs(inst); + venus_helper_process_initial_cap_bufs(inst); + inst->streamon_cap = 1; + return 0; + } + + if (inst->codec_state != VENUS_DEC_STATE_STOPPED) + return 0; + +reconfigure: + ret = vdec_output_conf(inst); + if (ret) + return ret; + + ret = venus_helper_set_num_bufs(inst, inst->num_input_bufs, + VB2_MAX_FRAME, VB2_MAX_FRAME); + if (ret) + return ret; + + ret = venus_helper_intbufs_realloc(inst); + if (ret) + goto err; + + venus_pm_load_scale(inst); + + inst->next_buf_last = false; + + ret = venus_helper_alloc_dpb_bufs(inst); + if (ret) + goto err; + + ret = hfi_session_continue(inst); + if (ret) + goto free_dpb_bufs; + + ret = venus_helper_queue_dpb_bufs(inst); + if (ret) + goto free_dpb_bufs; + + ret = venus_helper_process_initial_cap_bufs(inst); + if (ret) + goto free_dpb_bufs; + + inst->codec_state = VENUS_DEC_STATE_DECODING; + + if (inst->drain_active) + inst->codec_state = VENUS_DEC_STATE_DRAIN; + + inst->streamon_cap = 1; + inst->sequence_cap = 0; + inst->reconfig = false; + inst->drain_active = false; + + return 0; + +free_dpb_bufs: + venus_helper_free_dpb_bufs(inst); +err: + return ret; +} + +static int vdec_start_output(struct venus_inst *inst) +{ + int ret; + + if (inst->codec_state == VENUS_DEC_STATE_SEEK) { + ret = venus_helper_process_initial_out_bufs(inst); + if (inst->next_buf_last) + inst->codec_state = VENUS_DEC_STATE_DRC; + else + inst->codec_state = VENUS_DEC_STATE_DECODING; + goto done; + } + + if (inst->codec_state == VENUS_DEC_STATE_INIT || + inst->codec_state == VENUS_DEC_STATE_CAPTURE_SETUP) { + ret = venus_helper_process_initial_out_bufs(inst); + goto done; + } + + if (inst->codec_state != VENUS_DEC_STATE_DEINIT) + return -EINVAL; + + venus_helper_init_instance(inst); + inst->sequence_out = 0; + inst->reconfig = false; + inst->next_buf_last = false; + + ret = vdec_set_properties(inst); + if (ret) + return ret; + + ret = vdec_set_work_route(inst); + if (ret) + return ret; + + ret = vdec_output_conf(inst); + if (ret) + return ret; + + ret = vdec_verify_conf(inst); + if (ret) + return ret; + + ret = venus_helper_set_num_bufs(inst, inst->num_input_bufs, + VB2_MAX_FRAME, VB2_MAX_FRAME); + if (ret) + return ret; + + ret = venus_helper_vb2_start_streaming(inst); + if (ret) + return ret; + + ret = venus_helper_process_initial_out_bufs(inst); + if (ret) + return ret; + + inst->codec_state = VENUS_DEC_STATE_INIT; + +done: + inst->streamon_out = 1; + return ret; +} + +static int vdec_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + int ret; + + mutex_lock(&inst->lock); + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = vdec_start_capture(inst); + } else { + ret = vdec_pm_get(inst); + if (ret) + goto error; + + ret = venus_pm_acquire_core(inst); + if (ret) + goto put_power; + + ret = vdec_pm_put(inst, true); + if (ret) + goto error; + + ret = vdec_start_output(inst); + } + + if (ret) + goto error; + + mutex_unlock(&inst->lock); + return 0; + +put_power: + vdec_pm_put(inst, false); +error: + venus_helper_buffers_done(inst, q->type, VB2_BUF_STATE_QUEUED); + mutex_unlock(&inst->lock); + return ret; +} + +static void vdec_cancel_dst_buffers(struct venus_inst *inst) +{ + struct vb2_v4l2_buffer *buf; + + while ((buf = v4l2_m2m_dst_buf_remove(inst->m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_ERROR); +} + +static int vdec_stop_capture(struct venus_inst *inst) +{ + int ret = 0; + + switch (inst->codec_state) { + case VENUS_DEC_STATE_DECODING: + ret = hfi_session_flush(inst, HFI_FLUSH_ALL, true); + fallthrough; + case VENUS_DEC_STATE_DRAIN: + inst->codec_state = VENUS_DEC_STATE_STOPPED; + inst->drain_active = false; + fallthrough; + case VENUS_DEC_STATE_SEEK: + vdec_cancel_dst_buffers(inst); + break; + case VENUS_DEC_STATE_DRC: + ret = hfi_session_flush(inst, HFI_FLUSH_OUTPUT, true); + inst->codec_state = VENUS_DEC_STATE_CAPTURE_SETUP; + venus_helper_free_dpb_bufs(inst); + break; + default: + break; + } + + return ret; +} + +static int vdec_stop_output(struct venus_inst *inst) +{ + int ret = 0; + + switch (inst->codec_state) { + case VENUS_DEC_STATE_DECODING: + case VENUS_DEC_STATE_DRAIN: + case VENUS_DEC_STATE_STOPPED: + case VENUS_DEC_STATE_DRC: + ret = hfi_session_flush(inst, HFI_FLUSH_ALL, true); + inst->codec_state = VENUS_DEC_STATE_SEEK; + break; + case VENUS_DEC_STATE_INIT: + case VENUS_DEC_STATE_CAPTURE_SETUP: + ret = hfi_session_flush(inst, HFI_FLUSH_INPUT, true); + break; + default: + break; + } + + return ret; +} + +static void vdec_stop_streaming(struct vb2_queue *q) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + int ret = -EINVAL; + + vdec_pm_get_put(inst); + + mutex_lock(&inst->lock); + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + ret = vdec_stop_capture(inst); + else + ret = vdec_stop_output(inst); + + venus_helper_buffers_done(inst, q->type, VB2_BUF_STATE_ERROR); + + inst->session_error = 0; + + if (ret) + goto unlock; + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 0; + else + inst->streamon_cap = 0; + +unlock: + mutex_unlock(&inst->lock); +} + +static void vdec_session_release(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + int ret, abort = 0; + + vdec_pm_get(inst); + + mutex_lock(&inst->lock); + inst->codec_state = VENUS_DEC_STATE_DEINIT; + + ret = hfi_session_stop(inst); + abort = (ret && ret != -EINVAL) ? 1 : 0; + ret = hfi_session_unload_res(inst); + abort = (ret && ret != -EINVAL) ? 1 : 0; + ret = venus_helper_unregister_bufs(inst); + abort = (ret && ret != -EINVAL) ? 1 : 0; + ret = venus_helper_intbufs_free(inst); + abort = (ret && ret != -EINVAL) ? 1 : 0; + ret = hfi_session_deinit(inst); + abort = (ret && ret != -EINVAL) ? 1 : 0; + + if (inst->session_error || test_bit(0, &core->sys_error)) + abort = 1; + + if (abort) + hfi_session_abort(inst); + + venus_helper_free_dpb_bufs(inst); + venus_pm_load_scale(inst); + INIT_LIST_HEAD(&inst->registeredbufs); + mutex_unlock(&inst->lock); + + venus_pm_release_core(inst); + vdec_pm_put(inst, false); +} + +static int vdec_buf_init(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + + inst->buf_count++; + + return venus_helper_vb2_buf_init(vb); +} + +static void vdec_buf_cleanup(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct venus_buffer *buf = to_venus_buffer(vbuf); + + mutex_lock(&inst->lock); + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + if (!list_empty(&inst->registeredbufs)) + list_del_init(&buf->reg_list); + mutex_unlock(&inst->lock); + + inst->buf_count--; + if (!inst->buf_count) + vdec_session_release(inst); +} + +static void vdec_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + static const struct v4l2_event eos = { .type = V4L2_EVENT_EOS }; + + vdec_pm_get_put(inst); + + mutex_lock(&inst->lock); + + if (inst->next_buf_last && V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type) && + inst->codec_state == VENUS_DEC_STATE_DRC) { + vbuf->flags |= V4L2_BUF_FLAG_LAST; + vbuf->sequence = inst->sequence_cap++; + vbuf->field = V4L2_FIELD_NONE; + vb2_set_plane_payload(vb, 0, 0); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); + v4l2_event_queue_fh(&inst->fh, &eos); + inst->next_buf_last = false; + mutex_unlock(&inst->lock); + return; + } + + venus_helper_vb2_buf_queue(vb); + mutex_unlock(&inst->lock); +} + +static const struct vb2_ops vdec_vb2_ops = { + .queue_setup = vdec_queue_setup, + .buf_init = vdec_buf_init, + .buf_cleanup = vdec_buf_cleanup, + .buf_prepare = venus_helper_vb2_buf_prepare, + .start_streaming = vdec_start_streaming, + .stop_streaming = vdec_stop_streaming, + .buf_queue = vdec_vb2_buf_queue, +}; + +static void vdec_buf_done(struct venus_inst *inst, unsigned int buf_type, + u32 tag, u32 bytesused, u32 data_offset, u32 flags, + u32 hfi_flags, u64 timestamp_us) +{ + enum vb2_buffer_state state = VB2_BUF_STATE_DONE; + struct vb2_v4l2_buffer *vbuf; + struct vb2_buffer *vb; + unsigned int type; + + vdec_pm_touch(inst); + + if (buf_type == HFI_BUFFER_INPUT) + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + else + type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + vbuf = venus_helper_find_buf(inst, type, tag); + if (!vbuf) { + venus_helper_change_dpb_owner(inst, vbuf, type, buf_type, tag); + return; + } + + vbuf->flags = flags; + vbuf->field = V4L2_FIELD_NONE; + vb = &vbuf->vb2_buf; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + vb2_set_plane_payload(vb, 0, bytesused); + vb->planes[0].data_offset = data_offset; + vb->timestamp = timestamp_us * NSEC_PER_USEC; + vbuf->sequence = inst->sequence_cap++; + + if (vbuf->flags & V4L2_BUF_FLAG_LAST) { + const struct v4l2_event ev = { .type = V4L2_EVENT_EOS }; + + v4l2_event_queue_fh(&inst->fh, &ev); + + if (inst->codec_state == VENUS_DEC_STATE_DRAIN) { + inst->drain_active = false; + inst->codec_state = VENUS_DEC_STATE_STOPPED; + } + } + + if (!bytesused) + state = VB2_BUF_STATE_ERROR; + } else { + vbuf->sequence = inst->sequence_out++; + } + + venus_helper_get_ts_metadata(inst, timestamp_us, vbuf); + + if (hfi_flags & HFI_BUFFERFLAG_READONLY) + venus_helper_acquire_buf_ref(vbuf); + + if (hfi_flags & HFI_BUFFERFLAG_DATACORRUPT) + state = VB2_BUF_STATE_ERROR; + + if (hfi_flags & HFI_BUFFERFLAG_DROP_FRAME) { + state = VB2_BUF_STATE_ERROR; + vb2_set_plane_payload(vb, 0, 0); + vb->timestamp = 0; + } + + v4l2_m2m_buf_done(vbuf, state); +} + +static void vdec_event_change(struct venus_inst *inst, + struct hfi_event_data *ev_data, bool sufficient) +{ + static const struct v4l2_event ev = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION }; + struct device *dev = inst->core->dev_dec; + struct v4l2_format format = {}; + + mutex_lock(&inst->lock); + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = inst->fmt_cap->pixfmt; + format.fmt.pix_mp.width = ev_data->width; + format.fmt.pix_mp.height = ev_data->height; + + vdec_try_fmt_common(inst, &format); + + inst->width = format.fmt.pix_mp.width; + inst->height = format.fmt.pix_mp.height; + /* + * Some versions of the firmware do not report crop information for + * all codecs. For these cases, set the crop to the coded resolution. + */ + if (ev_data->input_crop.width > 0 && ev_data->input_crop.height > 0) { + inst->crop.left = ev_data->input_crop.left; + inst->crop.top = ev_data->input_crop.top; + inst->crop.width = ev_data->input_crop.width; + inst->crop.height = ev_data->input_crop.height; + } else { + inst->crop.left = 0; + inst->crop.top = 0; + inst->crop.width = ev_data->width; + inst->crop.height = ev_data->height; + } + + inst->fw_min_cnt = ev_data->buf_count; + /* overwriting this to 11 for vp9 due to fw bug */ + if (inst->hfi_codec == HFI_VIDEO_CODEC_VP9) + inst->fw_min_cnt = 11; + + inst->out_width = ev_data->width; + inst->out_height = ev_data->height; + + if (inst->bit_depth != ev_data->bit_depth) + inst->bit_depth = ev_data->bit_depth; + + if (inst->pic_struct != ev_data->pic_struct) + inst->pic_struct = ev_data->pic_struct; + + dev_dbg(dev, VDBGM "event %s sufficient resources (%ux%u)\n", + sufficient ? "" : "not", ev_data->width, ev_data->height); + + switch (inst->codec_state) { + case VENUS_DEC_STATE_INIT: + inst->codec_state = VENUS_DEC_STATE_CAPTURE_SETUP; + break; + case VENUS_DEC_STATE_DECODING: + case VENUS_DEC_STATE_DRAIN: + inst->codec_state = VENUS_DEC_STATE_DRC; + break; + default: + break; + } + + /* + * The assumption is that the firmware have to return the last buffer + * before this event is received in the v4l2 driver. Also the firmware + * itself doesn't mark the last decoder output buffer with HFI EOS flag. + */ + + if (inst->codec_state == VENUS_DEC_STATE_DRC) { + int ret; + + inst->next_buf_last = true; + + ret = hfi_session_flush(inst, HFI_FLUSH_OUTPUT, false); + if (ret) + dev_dbg(dev, VDBGH "flush output error %d\n", ret); + } + + inst->next_buf_last = true; + inst->reconfig = true; + v4l2_event_queue_fh(&inst->fh, &ev); + wake_up(&inst->reconf_wait); + + mutex_unlock(&inst->lock); +} + +static void vdec_event_notify(struct venus_inst *inst, u32 event, + struct hfi_event_data *data) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev_dec; + + vdec_pm_touch(inst); + + switch (event) { + case EVT_SESSION_ERROR: + inst->session_error = true; + venus_helper_vb2_queue_error(inst); + dev_err(dev, "dec: event session error %x\n", inst->error); + break; + case EVT_SYS_EVENT_CHANGE: + switch (data->event_type) { + case HFI_EVENT_DATA_SEQUENCE_CHANGED_SUFFICIENT_BUF_RESOURCES: + vdec_event_change(inst, data, true); + break; + case HFI_EVENT_DATA_SEQUENCE_CHANGED_INSUFFICIENT_BUF_RESOURCES: + vdec_event_change(inst, data, false); + break; + case HFI_EVENT_RELEASE_BUFFER_REFERENCE: + venus_helper_release_buf_ref(inst, data->tag); + break; + default: + break; + } + break; + default: + break; + } +} + +static void vdec_flush_done(struct venus_inst *inst) +{ + dev_dbg(inst->core->dev_dec, VDBGH "flush done\n"); +} + +static const struct hfi_inst_ops vdec_hfi_ops = { + .buf_done = vdec_buf_done, + .event_notify = vdec_event_notify, + .flush_done = vdec_flush_done, +}; + +static void vdec_inst_init(struct venus_inst *inst) +{ + inst->hfi_codec = HFI_VIDEO_CODEC_H264; + inst->fmt_out = &vdec_formats[8]; + inst->fmt_cap = &vdec_formats[0]; + inst->width = frame_width_min(inst); + inst->height = ALIGN(frame_height_min(inst), 32); + inst->crop.left = 0; + inst->crop.top = 0; + inst->crop.width = inst->width; + inst->crop.height = inst->height; + inst->fw_min_cnt = 8; + inst->out_width = frame_width_min(inst); + inst->out_height = frame_height_min(inst); + inst->fps = 30; + inst->timeperframe.numerator = 1; + inst->timeperframe.denominator = 30; + inst->opb_buftype = HFI_BUFFER_OUTPUT; +} + +static void vdec_m2m_device_run(void *priv) +{ +} + +static const struct v4l2_m2m_ops vdec_m2m_ops = { + .device_run = vdec_m2m_device_run, + .job_abort = venus_helper_m2m_job_abort, +}; + +static int m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct venus_inst *inst = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->ops = &vdec_vb2_ops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->drv_priv = inst; + src_vq->buf_struct_size = sizeof(struct venus_buffer); + src_vq->allow_zero_bytesused = 1; + src_vq->min_buffers_needed = 0; + src_vq->dev = inst->core->dev; + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->ops = &vdec_vb2_ops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->drv_priv = inst; + dst_vq->buf_struct_size = sizeof(struct venus_buffer); + dst_vq->allow_zero_bytesused = 1; + dst_vq->min_buffers_needed = 0; + dst_vq->dev = inst->core->dev; + return vb2_queue_init(dst_vq); +} + +static int vdec_open(struct file *file) +{ + struct venus_core *core = video_drvdata(file); + struct venus_inst *inst; + int ret; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + INIT_LIST_HEAD(&inst->dpbbufs); + INIT_LIST_HEAD(&inst->registeredbufs); + INIT_LIST_HEAD(&inst->internalbufs); + INIT_LIST_HEAD(&inst->list); + mutex_init(&inst->lock); + + inst->core = core; + inst->session_type = VIDC_SESSION_TYPE_DEC; + inst->num_output_bufs = 1; + inst->codec_state = VENUS_DEC_STATE_DEINIT; + inst->buf_count = 0; + inst->clk_data.core_id = VIDC_CORE_ID_DEFAULT; + inst->core_acquired = false; + inst->bit_depth = VIDC_BITDEPTH_8; + inst->pic_struct = HFI_INTERLACE_FRAME_PROGRESSIVE; + init_waitqueue_head(&inst->reconf_wait); + inst->nonblock = file->f_flags & O_NONBLOCK; + + venus_helper_init_instance(inst); + + ret = vdec_ctrl_init(inst); + if (ret) + goto err_free; + + ret = hfi_session_create(inst, &vdec_hfi_ops); + if (ret) + goto err_ctrl_deinit; + + vdec_inst_init(inst); + + ida_init(&inst->dpb_ids); + + /* + * create m2m device for every instance, the m2m context scheduling + * is made by firmware side so we do not need to care about. + */ + inst->m2m_dev = v4l2_m2m_init(&vdec_m2m_ops); + if (IS_ERR(inst->m2m_dev)) { + ret = PTR_ERR(inst->m2m_dev); + goto err_session_destroy; + } + + inst->m2m_ctx = v4l2_m2m_ctx_init(inst->m2m_dev, inst, m2m_queue_init); + if (IS_ERR(inst->m2m_ctx)) { + ret = PTR_ERR(inst->m2m_ctx); + goto err_m2m_release; + } + + v4l2_fh_init(&inst->fh, core->vdev_dec); + + inst->fh.ctrl_handler = &inst->ctrl_handler; + v4l2_fh_add(&inst->fh); + inst->fh.m2m_ctx = inst->m2m_ctx; + file->private_data = &inst->fh; + + return 0; + +err_m2m_release: + v4l2_m2m_release(inst->m2m_dev); +err_session_destroy: + hfi_session_destroy(inst); +err_ctrl_deinit: + vdec_ctrl_deinit(inst); +err_free: + kfree(inst); + return ret; +} + +static int vdec_close(struct file *file) +{ + struct venus_inst *inst = to_inst(file); + + vdec_pm_get(inst); + + v4l2_m2m_ctx_release(inst->m2m_ctx); + v4l2_m2m_release(inst->m2m_dev); + vdec_ctrl_deinit(inst); + ida_destroy(&inst->dpb_ids); + hfi_session_destroy(inst); + mutex_destroy(&inst->lock); + v4l2_fh_del(&inst->fh); + v4l2_fh_exit(&inst->fh); + + vdec_pm_put(inst, false); + + kfree(inst); + return 0; +} + +static const struct v4l2_file_operations vdec_fops = { + .owner = THIS_MODULE, + .open = vdec_open, + .release = vdec_close, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int vdec_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct video_device *vdev; + struct venus_core *core; + int ret; + + if (!dev->parent) + return -EPROBE_DEFER; + + core = dev_get_drvdata(dev->parent); + if (!core) + return -EPROBE_DEFER; + + platform_set_drvdata(pdev, core); + + if (core->pm_ops->vdec_get) { + ret = core->pm_ops->vdec_get(dev); + if (ret) + return ret; + } + + vdev = video_device_alloc(); + if (!vdev) + return -ENOMEM; + + strscpy(vdev->name, "qcom-venus-decoder", sizeof(vdev->name)); + vdev->release = video_device_release; + vdev->fops = &vdec_fops; + vdev->ioctl_ops = &vdec_ioctl_ops; + vdev->vfl_dir = VFL_DIR_M2M; + vdev->v4l2_dev = &core->v4l2_dev; + vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) + goto err_vdev_release; + + core->vdev_dec = vdev; + core->dev_dec = dev; + + video_set_drvdata(vdev, core); + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + return 0; + +err_vdev_release: + video_device_release(vdev); + return ret; +} + +static int vdec_remove(struct platform_device *pdev) +{ + struct venus_core *core = dev_get_drvdata(pdev->dev.parent); + + video_unregister_device(core->vdev_dec); + pm_runtime_disable(core->dev_dec); + + if (core->pm_ops->vdec_put) + core->pm_ops->vdec_put(core->dev_dec); + + return 0; +} + +static __maybe_unused int vdec_runtime_suspend(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + const struct venus_pm_ops *pm_ops = core->pm_ops; + int ret = 0; + + if (pm_ops->vdec_power) + ret = pm_ops->vdec_power(dev, POWER_OFF); + + return ret; +} + +static __maybe_unused int vdec_runtime_resume(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + const struct venus_pm_ops *pm_ops = core->pm_ops; + int ret = 0; + + if (pm_ops->vdec_power) + ret = pm_ops->vdec_power(dev, POWER_ON); + + return ret; +} + +static const struct dev_pm_ops vdec_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(vdec_runtime_suspend, vdec_runtime_resume, NULL) +}; + +static const struct of_device_id vdec_dt_match[] = { + { .compatible = "venus-decoder" }, + { } +}; +MODULE_DEVICE_TABLE(of, vdec_dt_match); + +static struct platform_driver qcom_venus_dec_driver = { + .probe = vdec_probe, + .remove = vdec_remove, + .driver = { + .name = "qcom-venus-decoder", + .of_match_table = vdec_dt_match, + .pm = &vdec_pm_ops, + }, +}; +module_platform_driver(qcom_venus_dec_driver); + +MODULE_ALIAS("platform:qcom-venus-decoder"); +MODULE_DESCRIPTION("Qualcomm Venus video decoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/venus/vdec.h b/drivers/media/platform/qcom/venus/vdec.h new file mode 100644 index 000000000..6b262d0bf --- /dev/null +++ b/drivers/media/platform/qcom/venus/vdec.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_VDEC_H__ +#define __VENUS_VDEC_H__ + +struct venus_inst; + +int vdec_ctrl_init(struct venus_inst *inst); +void vdec_ctrl_deinit(struct venus_inst *inst); + +#endif diff --git a/drivers/media/platform/qcom/venus/vdec_ctrls.c b/drivers/media/platform/qcom/venus/vdec_ctrls.c new file mode 100644 index 000000000..fbe12a608 --- /dev/null +++ b/drivers/media/platform/qcom/venus/vdec_ctrls.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include + +#include "core.h" +#include "helpers.h" +#include "vdec.h" + +static int vdec_op_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct venus_inst *inst = ctrl_to_inst(ctrl); + struct vdec_controls *ctr = &inst->controls.dec; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: + ctr->post_loop_deb_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: + case V4L2_CID_MPEG_VIDEO_VP9_PROFILE: + ctr->profile = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + case V4L2_CID_MPEG_VIDEO_VP9_LEVEL: + ctr->level = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY: + ctr->display_delay = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE: + ctr->display_delay_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_DEC_CONCEAL_COLOR: + ctr->conceal_color = *ctrl->p_new.p_s64; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vdec_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct venus_inst *inst = ctrl_to_inst(ctrl); + struct vdec_controls *ctr = &inst->controls.dec; + struct hfi_buffer_requirements bufreq; + enum hfi_version ver = inst->core->res->hfi_version; + u32 profile, level; + int ret; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: + case V4L2_CID_MPEG_VIDEO_VP9_PROFILE: + ret = venus_helper_get_profile_level(inst, &profile, &level); + if (!ret) + ctr->profile = profile; + ctrl->val = ctr->profile; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + case V4L2_CID_MPEG_VIDEO_VP9_LEVEL: + ret = venus_helper_get_profile_level(inst, &profile, &level); + if (!ret) + ctr->level = level; + ctrl->val = ctr->level; + break; + case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: + ctrl->val = ctr->post_loop_deb_mode; + break; + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq); + if (!ret) + ctrl->val = HFI_BUFREQ_COUNT_MIN(&bufreq, ver); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops vdec_ctrl_ops = { + .s_ctrl = vdec_op_s_ctrl, + .g_volatile_ctrl = vdec_op_g_volatile_ctrl, +}; + +int vdec_ctrl_init(struct venus_inst *inst) +{ + struct v4l2_ctrl *ctrl; + int ret; + + ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, 12); + if (ret) + return ret; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY, + ~((1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) | + (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)), + V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5, + 0, V4L2_MPEG_VIDEO_MPEG4_LEVEL_0); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH, + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + 0, V4L2_MPEG_VIDEO_H264_LEVEL_1_0); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VP8_PROFILE, + V4L2_MPEG_VIDEO_VP8_PROFILE_3, + 0, V4L2_MPEG_VIDEO_VP8_PROFILE_0); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VP9_PROFILE, + V4L2_MPEG_VIDEO_VP9_PROFILE_3, + 0, V4L2_MPEG_VIDEO_VP9_PROFILE_0); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VP9_LEVEL, + V4L2_MPEG_VIDEO_VP9_LEVEL_6_2, + 0, V4L2_MPEG_VIDEO_VP9_LEVEL_1_0); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER, 0, 1, 1, 0); + + ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 1); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY, + 0, 16383, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE, + 0, 1, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, + V4L2_CID_MPEG_VIDEO_DEC_CONCEAL_COLOR, 0, + 0xffffffffffffLL, 1, 0x8000800010LL); + + ret = inst->ctrl_handler.error; + if (ret) { + v4l2_ctrl_handler_free(&inst->ctrl_handler); + return ret; + } + + return 0; +} + +void vdec_ctrl_deinit(struct venus_inst *inst) +{ + v4l2_ctrl_handler_free(&inst->ctrl_handler); +} diff --git a/drivers/media/platform/qcom/venus/venc.c b/drivers/media/platform/qcom/venus/venc.c new file mode 100644 index 000000000..cdb12546c --- /dev/null +++ b/drivers/media/platform/qcom/venus/venc.c @@ -0,0 +1,1557 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hfi_venus_io.h" +#include "hfi_parser.h" +#include "core.h" +#include "helpers.h" +#include "venc.h" +#include "pm_helpers.h" + +#define NUM_B_FRAMES_MAX 4 + +/* + * Three resons to keep MPLANE formats (despite that the number of planes + * currently is one): + * - the MPLANE formats allow only one plane to be used + * - the downstream driver use MPLANE formats too + * - future firmware versions could add support for >1 planes + */ +static const struct venus_format venc_formats[] = { + { + .pixfmt = V4L2_PIX_FMT_NV12, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_MPEG4, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_H263, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_H264, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_VP8, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, { + .pixfmt = V4L2_PIX_FMT_HEVC, + .num_planes = 1, + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + }, +}; + +static const struct venus_format * +find_format(struct venus_inst *inst, u32 pixfmt, u32 type) +{ + const struct venus_format *fmt = venc_formats; + unsigned int size = ARRAY_SIZE(venc_formats); + unsigned int i; + + for (i = 0; i < size; i++) { + if (fmt[i].pixfmt == pixfmt) + break; + } + + if (i == size || fmt[i].type != type) + return NULL; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + !venus_helper_check_codec(inst, fmt[i].pixfmt)) + return NULL; + + return &fmt[i]; +} + +static const struct venus_format * +find_format_by_index(struct venus_inst *inst, unsigned int index, u32 type) +{ + const struct venus_format *fmt = venc_formats; + unsigned int size = ARRAY_SIZE(venc_formats); + unsigned int i, k = 0; + + if (index > size) + return NULL; + + for (i = 0; i < size; i++) { + bool valid; + + if (fmt[i].type != type) + continue; + valid = type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + venus_helper_check_codec(inst, fmt[i].pixfmt); + if (k == index && valid) + break; + if (valid) + k++; + } + + if (i == size) + return NULL; + + return &fmt[i]; +} + +static int venc_v4l2_to_hfi(int id, int value) +{ + switch (id) { + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + switch (value) { + case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC: + default: + return HFI_H264_ENTROPY_CAVLC; + case V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC: + return HFI_H264_ENTROPY_CABAC; + } + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: + switch (value) { + case V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED: + default: + return HFI_H264_DB_MODE_ALL_BOUNDARY; + case V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED: + return HFI_H264_DB_MODE_DISABLE; + case V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY: + return HFI_H264_DB_MODE_SKIP_SLICE_BOUNDARY; + } + } + + return 0; +} + +static int +venc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + strscpy(cap->driver, "qcom-venus", sizeof(cap->driver)); + strscpy(cap->card, "Qualcomm Venus video encoder", sizeof(cap->card)); + strscpy(cap->bus_info, "platform:qcom-venus", sizeof(cap->bus_info)); + + return 0; +} + +static int venc_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + fmt = find_format_by_index(inst, f->index, f->type); + + memset(f->reserved, 0, sizeof(f->reserved)); + + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixfmt; + + return 0; +} + +static const struct venus_format * +venc_try_fmt_common(struct venus_inst *inst, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *pfmt = pixmp->plane_fmt; + const struct venus_format *fmt; + u32 sizeimage; + + memset(pfmt[0].reserved, 0, sizeof(pfmt[0].reserved)); + memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); + + fmt = find_format(inst, pixmp->pixelformat, f->type); + if (!fmt) { + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + pixmp->pixelformat = V4L2_PIX_FMT_H264; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + pixmp->pixelformat = V4L2_PIX_FMT_NV12; + else + return NULL; + fmt = find_format(inst, pixmp->pixelformat, f->type); + if (!fmt) + return NULL; + } + + pixmp->width = clamp(pixmp->width, frame_width_min(inst), + frame_width_max(inst)); + pixmp->height = clamp(pixmp->height, frame_height_min(inst), + frame_height_max(inst)); + + pixmp->width = ALIGN(pixmp->width, 128); + pixmp->height = ALIGN(pixmp->height, 32); + + pixmp->width = ALIGN(pixmp->width, 2); + pixmp->height = ALIGN(pixmp->height, 2); + + if (pixmp->field == V4L2_FIELD_ANY) + pixmp->field = V4L2_FIELD_NONE; + pixmp->num_planes = fmt->num_planes; + pixmp->flags = 0; + + sizeimage = venus_helper_get_framesz(pixmp->pixelformat, + pixmp->width, + pixmp->height); + pfmt[0].sizeimage = max(ALIGN(pfmt[0].sizeimage, SZ_4K), sizeimage); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + pfmt[0].bytesperline = ALIGN(pixmp->width, 128); + else + pfmt[0].bytesperline = 0; + + return fmt; +} + +static int venc_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + + venc_try_fmt_common(inst, f); + + return 0; +} + +static int venc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct venus_inst *inst = to_inst(file); + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct v4l2_pix_format_mplane orig_pixmp; + const struct venus_format *fmt; + struct v4l2_format format; + u32 pixfmt_out = 0, pixfmt_cap = 0; + struct vb2_queue *q; + + q = v4l2_m2m_get_vq(inst->m2m_ctx, f->type); + if (!q) + return -EINVAL; + + if (vb2_is_busy(q)) + return -EBUSY; + + orig_pixmp = *pixmp; + + fmt = venc_try_fmt_common(inst, f); + if (!fmt) + return -EINVAL; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixfmt_out = pixmp->pixelformat; + pixfmt_cap = inst->fmt_cap->pixfmt; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixfmt_cap = pixmp->pixelformat; + pixfmt_out = inst->fmt_out->pixfmt; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_out; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + venc_try_fmt_common(inst, &format); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + inst->out_width = format.fmt.pix_mp.width; + inst->out_height = format.fmt.pix_mp.height; + inst->colorspace = pixmp->colorspace; + inst->ycbcr_enc = pixmp->ycbcr_enc; + inst->quantization = pixmp->quantization; + inst->xfer_func = pixmp->xfer_func; + } + + memset(&format, 0, sizeof(format)); + + format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + format.fmt.pix_mp.pixelformat = pixfmt_cap; + format.fmt.pix_mp.width = orig_pixmp.width; + format.fmt.pix_mp.height = orig_pixmp.height; + venc_try_fmt_common(inst, &format); + + inst->width = format.fmt.pix_mp.width; + inst->height = format.fmt.pix_mp.height; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->fmt_out = fmt; + else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + inst->fmt_cap = fmt; + inst->output_buf_size = pixmp->plane_fmt[0].sizeimage; + } + + return 0; +} + +static int venc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + fmt = inst->fmt_cap; + else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + fmt = inst->fmt_out; + else + return -EINVAL; + + pixmp->pixelformat = fmt->pixfmt; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + pixmp->width = inst->width; + pixmp->height = inst->height; + pixmp->colorspace = inst->colorspace; + pixmp->ycbcr_enc = inst->ycbcr_enc; + pixmp->quantization = inst->quantization; + pixmp->xfer_func = inst->xfer_func; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + pixmp->width = inst->out_width; + pixmp->height = inst->out_height; + } + + venc_try_fmt_common(inst, f); + + return 0; +} + +static int +venc_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct venus_inst *inst = to_inst(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r.width = inst->out_width; + s->r.height = inst->out_height; + break; + case V4L2_SEL_TGT_CROP: + s->r.width = inst->width; + s->r.height = inst->height; + break; + default: + return -EINVAL; + } + + s->r.top = 0; + s->r.left = 0; + + return 0; +} + +static int +venc_s_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct venus_inst *inst = to_inst(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + if (s->r.width > inst->out_width || + s->r.height > inst->out_height) + return -EINVAL; + + s->r.width = ALIGN(s->r.width, 2); + s->r.height = ALIGN(s->r.height, 2); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + s->r.top = 0; + s->r.left = 0; + inst->width = s->r.width; + inst->height = s->r.height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int venc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct venus_inst *inst = to_inst(file); + struct v4l2_outputparm *out = &a->parm.output; + struct v4l2_fract *timeperframe = &out->timeperframe; + u64 us_per_frame, fps; + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + memset(out->reserved, 0, sizeof(out->reserved)); + + if (!timeperframe->denominator) + timeperframe->denominator = inst->timeperframe.denominator; + if (!timeperframe->numerator) + timeperframe->numerator = inst->timeperframe.numerator; + + out->capability = V4L2_CAP_TIMEPERFRAME; + + us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC; + do_div(us_per_frame, timeperframe->denominator); + + if (!us_per_frame) + return -EINVAL; + + fps = (u64)USEC_PER_SEC; + do_div(fps, us_per_frame); + + inst->timeperframe = *timeperframe; + inst->fps = fps; + + return 0; +} + +static int venc_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct venus_inst *inst = to_inst(file); + + if (a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + a->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + a->parm.output.capability |= V4L2_CAP_TIMEPERFRAME; + a->parm.output.timeperframe = inst->timeperframe; + + return 0; +} + +static int venc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + fmt = find_format(inst, fsize->pixel_format, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (!fmt) { + fmt = find_format(inst, fsize->pixel_format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (!fmt) + return -EINVAL; + } + + if (fsize->index) + return -EINVAL; + + fsize->stepwise.min_width = frame_width_min(inst); + fsize->stepwise.max_width = frame_width_max(inst); + fsize->stepwise.step_width = frame_width_step(inst); + fsize->stepwise.min_height = frame_height_min(inst); + fsize->stepwise.max_height = frame_height_max(inst); + fsize->stepwise.step_height = frame_height_step(inst); + + return 0; +} + +static int venc_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct venus_inst *inst = to_inst(file); + const struct venus_format *fmt; + unsigned int framerate_factor = 1; + + fival->type = V4L2_FRMIVAL_TYPE_STEPWISE; + + fmt = find_format(inst, fival->pixel_format, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (!fmt) { + fmt = find_format(inst, fival->pixel_format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + if (!fmt) + return -EINVAL; + } + + if (fival->index) + return -EINVAL; + + if (!fival->width || !fival->height) + return -EINVAL; + + if (fival->width > frame_width_max(inst) || + fival->width < frame_width_min(inst) || + fival->height > frame_height_max(inst) || + fival->height < frame_height_min(inst)) + return -EINVAL; + + if (IS_V1(inst->core)) { + /* framerate is reported in 1/65535 fps unit */ + framerate_factor = (1 << 16); + } + + fival->stepwise.min.numerator = 1; + fival->stepwise.min.denominator = frate_max(inst) / framerate_factor; + fival->stepwise.max.numerator = 1; + fival->stepwise.max.denominator = frate_min(inst) / framerate_factor; + fival->stepwise.step.numerator = 1; + fival->stepwise.step.denominator = frate_max(inst) / framerate_factor; + + return 0; +} + +static int venc_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_ioctl_ops venc_ioctl_ops = { + .vidioc_querycap = venc_querycap, + .vidioc_enum_fmt_vid_cap = venc_enum_fmt, + .vidioc_enum_fmt_vid_out = venc_enum_fmt, + .vidioc_s_fmt_vid_cap_mplane = venc_s_fmt, + .vidioc_s_fmt_vid_out_mplane = venc_s_fmt, + .vidioc_g_fmt_vid_cap_mplane = venc_g_fmt, + .vidioc_g_fmt_vid_out_mplane = venc_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = venc_try_fmt, + .vidioc_try_fmt_vid_out_mplane = venc_try_fmt, + .vidioc_g_selection = venc_g_selection, + .vidioc_s_selection = venc_s_selection, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_s_parm = venc_s_parm, + .vidioc_g_parm = venc_g_parm, + .vidioc_enum_framesizes = venc_enum_framesizes, + .vidioc_enum_frameintervals = venc_enum_frameintervals, + .vidioc_subscribe_event = venc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd, +}; + +static int venc_pm_get(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev_enc; + int ret; + + mutex_lock(&core->pm_lock); + ret = pm_runtime_resume_and_get(dev); + mutex_unlock(&core->pm_lock); + + return ret < 0 ? ret : 0; +} + +static int venc_pm_put(struct venus_inst *inst, bool autosuspend) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev_enc; + int ret; + + mutex_lock(&core->pm_lock); + + if (autosuspend) + ret = pm_runtime_put_autosuspend(dev); + else + ret = pm_runtime_put_sync(dev); + + mutex_unlock(&core->pm_lock); + + return ret < 0 ? ret : 0; +} + +static int venc_pm_get_put(struct venus_inst *inst) +{ + struct venus_core *core = inst->core; + struct device *dev = core->dev_enc; + int ret = 0; + + mutex_lock(&core->pm_lock); + + if (pm_runtime_suspended(dev)) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto error; + + ret = pm_runtime_put_autosuspend(dev); + } + +error: + mutex_unlock(&core->pm_lock); + + return ret < 0 ? ret : 0; +} + +static void venc_pm_touch(struct venus_inst *inst) +{ + pm_runtime_mark_last_busy(inst->core->dev_enc); +} + +static int venc_set_properties(struct venus_inst *inst) +{ + struct venc_controls *ctr = &inst->controls.enc; + struct hfi_intra_period intra_period; + struct hfi_framerate frate; + struct hfi_bitrate brate; + struct hfi_idr_period idrp; + struct hfi_quantization quant; + struct hfi_quantization_range quant_range; + struct hfi_enable en; + struct hfi_ltr_mode ltr_mode; + struct hfi_intra_refresh intra_refresh = {}; + u32 ptype, rate_control, bitrate; + u32 profile, level; + int ret; + + ret = venus_helper_set_work_mode(inst); + if (ret) + return ret; + + ptype = HFI_PROPERTY_CONFIG_FRAME_RATE; + frate.buffer_type = HFI_BUFFER_OUTPUT; + frate.framerate = inst->fps * (1 << 16); + + ret = hfi_session_set_property(inst, ptype, &frate); + if (ret) + return ret; + + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264) { + struct hfi_h264_vui_timing_info info; + struct hfi_h264_entropy_control entropy; + struct hfi_h264_db_control deblock; + struct hfi_h264_8x8_transform h264_transform; + + ptype = HFI_PROPERTY_PARAM_VENC_H264_VUI_TIMING_INFO; + info.enable = 1; + info.fixed_framerate = 1; + info.time_scale = NSEC_PER_SEC; + + ret = hfi_session_set_property(inst, ptype, &info); + if (ret) + return ret; + + ptype = HFI_PROPERTY_PARAM_VENC_H264_ENTROPY_CONTROL; + entropy.entropy_mode = venc_v4l2_to_hfi( + V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, + ctr->h264_entropy_mode); + entropy.cabac_model = HFI_H264_CABAC_MODEL_0; + + ret = hfi_session_set_property(inst, ptype, &entropy); + if (ret) + return ret; + + ptype = HFI_PROPERTY_PARAM_VENC_H264_DEBLOCK_CONTROL; + deblock.mode = venc_v4l2_to_hfi( + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, + ctr->h264_loop_filter_mode); + deblock.slice_alpha_offset = ctr->h264_loop_filter_alpha; + deblock.slice_beta_offset = ctr->h264_loop_filter_beta; + + ret = hfi_session_set_property(inst, ptype, &deblock); + if (ret) + return ret; + + ptype = HFI_PROPERTY_PARAM_VENC_H264_TRANSFORM_8X8; + h264_transform.enable_type = 0; + if (ctr->profile.h264 == V4L2_MPEG_VIDEO_H264_PROFILE_HIGH || + ctr->profile.h264 == V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_HIGH) + h264_transform.enable_type = ctr->h264_8x8_transform; + + ret = hfi_session_set_property(inst, ptype, &h264_transform); + if (ret) + return ret; + + } + + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264 || + inst->fmt_cap->pixfmt == V4L2_PIX_FMT_HEVC) { + /* IDR periodicity, n: + * n = 0 - only the first I-frame is IDR frame + * n = 1 - all I-frames will be IDR frames + * n > 1 - every n-th I-frame will be IDR frame + */ + ptype = HFI_PROPERTY_CONFIG_VENC_IDR_PERIOD; + idrp.idr_period = 0; + ret = hfi_session_set_property(inst, ptype, &idrp); + if (ret) + return ret; + } + + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_HEVC && + ctr->profile.hevc == V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10) { + struct hfi_hdr10_pq_sei hdr10; + unsigned int c; + + ptype = HFI_PROPERTY_PARAM_VENC_HDR10_PQ_SEI; + + for (c = 0; c < 3; c++) { + hdr10.mastering.display_primaries_x[c] = + ctr->mastering.display_primaries_x[c]; + hdr10.mastering.display_primaries_y[c] = + ctr->mastering.display_primaries_y[c]; + } + + hdr10.mastering.white_point_x = ctr->mastering.white_point_x; + hdr10.mastering.white_point_y = ctr->mastering.white_point_y; + hdr10.mastering.max_display_mastering_luminance = + ctr->mastering.max_display_mastering_luminance; + hdr10.mastering.min_display_mastering_luminance = + ctr->mastering.min_display_mastering_luminance; + + hdr10.cll.max_content_light = ctr->cll.max_content_light_level; + hdr10.cll.max_pic_average_light = + ctr->cll.max_pic_average_light_level; + + ret = hfi_session_set_property(inst, ptype, &hdr10); + if (ret) + return ret; + } + + if (ctr->num_b_frames) { + u32 max_num_b_frames = NUM_B_FRAMES_MAX; + + ptype = HFI_PROPERTY_PARAM_VENC_MAX_NUM_B_FRAMES; + ret = hfi_session_set_property(inst, ptype, &max_num_b_frames); + if (ret) + return ret; + } + + ptype = HFI_PROPERTY_CONFIG_VENC_INTRA_PERIOD; + intra_period.pframes = ctr->num_p_frames; + intra_period.bframes = ctr->num_b_frames; + + ret = hfi_session_set_property(inst, ptype, &intra_period); + if (ret) + return ret; + + if (!ctr->rc_enable) + rate_control = HFI_RATE_CONTROL_OFF; + else if (ctr->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) + rate_control = ctr->frame_skip_mode ? HFI_RATE_CONTROL_VBR_VFR : + HFI_RATE_CONTROL_VBR_CFR; + else if (ctr->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) + rate_control = ctr->frame_skip_mode ? HFI_RATE_CONTROL_CBR_VFR : + HFI_RATE_CONTROL_CBR_CFR; + else if (ctr->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CQ) + rate_control = HFI_RATE_CONTROL_CQ; + + ptype = HFI_PROPERTY_PARAM_VENC_RATE_CONTROL; + ret = hfi_session_set_property(inst, ptype, &rate_control); + if (ret) + return ret; + + if (rate_control == HFI_RATE_CONTROL_CQ && ctr->const_quality) { + struct hfi_heic_frame_quality quality = {}; + + ptype = HFI_PROPERTY_CONFIG_HEIC_FRAME_QUALITY; + quality.frame_quality = ctr->const_quality; + ret = hfi_session_set_property(inst, ptype, &quality); + if (ret) + return ret; + } + + if (!ctr->bitrate) + bitrate = 64000; + else + bitrate = ctr->bitrate; + + ptype = HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE; + brate.bitrate = bitrate; + brate.layer_id = 0; + + ret = hfi_session_set_property(inst, ptype, &brate); + if (ret) + return ret; + + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264 || + inst->fmt_cap->pixfmt == V4L2_PIX_FMT_HEVC) { + ptype = HFI_PROPERTY_CONFIG_VENC_SYNC_FRAME_SEQUENCE_HEADER; + if (ctr->header_mode == V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE) + en.enable = 0; + else + en.enable = 1; + + ret = hfi_session_set_property(inst, ptype, &en); + if (ret) + return ret; + } + + if (!ctr->bitrate_peak) + bitrate *= 2; + else + bitrate = ctr->bitrate_peak; + + ptype = HFI_PROPERTY_CONFIG_VENC_MAX_BITRATE; + brate.bitrate = bitrate; + brate.layer_id = 0; + + ret = hfi_session_set_property(inst, ptype, &brate); + if (ret) + return ret; + + ptype = HFI_PROPERTY_PARAM_VENC_SESSION_QP; + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_HEVC) { + quant.qp_i = ctr->hevc_i_qp; + quant.qp_p = ctr->hevc_p_qp; + quant.qp_b = ctr->hevc_b_qp; + } else { + quant.qp_i = ctr->h264_i_qp; + quant.qp_p = ctr->h264_p_qp; + quant.qp_b = ctr->h264_b_qp; + } + quant.layer_id = 0; + ret = hfi_session_set_property(inst, ptype, &quant); + if (ret) + return ret; + + ptype = HFI_PROPERTY_PARAM_VENC_SESSION_QP_RANGE; + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_HEVC) { + quant_range.min_qp = ctr->hevc_min_qp; + quant_range.max_qp = ctr->hevc_max_qp; + } else { + quant_range.min_qp = ctr->h264_min_qp; + quant_range.max_qp = ctr->h264_max_qp; + } + quant_range.layer_id = 0; + ret = hfi_session_set_property(inst, ptype, &quant_range); + if (ret) + return ret; + + ptype = HFI_PROPERTY_PARAM_VENC_LTRMODE; + ltr_mode.ltr_count = ctr->ltr_count; + ltr_mode.ltr_mode = HFI_LTR_MODE_MANUAL; + ltr_mode.trust_mode = 1; + ret = hfi_session_set_property(inst, ptype, <r_mode); + if (ret) + return ret; + + switch (inst->hfi_codec) { + case HFI_VIDEO_CODEC_H264: + profile = ctr->profile.h264; + level = ctr->level.h264; + break; + case HFI_VIDEO_CODEC_MPEG4: + profile = ctr->profile.mpeg4; + level = ctr->level.mpeg4; + break; + case HFI_VIDEO_CODEC_VP8: + profile = ctr->profile.vp8; + level = 0; + break; + case HFI_VIDEO_CODEC_VP9: + profile = ctr->profile.vp9; + level = ctr->level.vp9; + break; + case HFI_VIDEO_CODEC_HEVC: + profile = ctr->profile.hevc; + level = ctr->level.hevc; + break; + case HFI_VIDEO_CODEC_MPEG2: + default: + profile = 0; + level = 0; + break; + } + + ret = venus_helper_set_profile_level(inst, profile, level); + if (ret) + return ret; + + if (inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264 || + inst->fmt_cap->pixfmt == V4L2_PIX_FMT_HEVC) { + struct hfi_enable en = {}; + + ptype = HFI_PROPERTY_PARAM_VENC_H264_GENERATE_AUDNAL; + + if (ctr->aud_enable) + en.enable = 1; + + ret = hfi_session_set_property(inst, ptype, &en); + } + + if ((inst->fmt_cap->pixfmt == V4L2_PIX_FMT_H264 || + inst->fmt_cap->pixfmt == V4L2_PIX_FMT_HEVC) && + (rate_control == HFI_RATE_CONTROL_CBR_VFR || + rate_control == HFI_RATE_CONTROL_CBR_CFR)) { + intra_refresh.mode = HFI_INTRA_REFRESH_NONE; + intra_refresh.cir_mbs = 0; + + if (ctr->intra_refresh_period) { + u32 mbs; + + mbs = ALIGN(inst->width, 16) * ALIGN(inst->height, 16); + mbs /= 16 * 16; + if (mbs % ctr->intra_refresh_period) + mbs++; + mbs /= ctr->intra_refresh_period; + + intra_refresh.cir_mbs = mbs; + if (ctr->intra_refresh_type == + V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC) + intra_refresh.mode = HFI_INTRA_REFRESH_CYCLIC; + else + intra_refresh.mode = HFI_INTRA_REFRESH_RANDOM; + } + + ptype = HFI_PROPERTY_PARAM_VENC_INTRA_REFRESH; + + ret = hfi_session_set_property(inst, ptype, &intra_refresh); + if (ret) + return ret; + } + + return 0; +} + +static int venc_init_session(struct venus_inst *inst) +{ + int ret; + + ret = venus_helper_session_init(inst); + if (ret == -EALREADY) + return 0; + else if (ret) + return ret; + + ret = venus_helper_set_stride(inst, inst->out_width, + inst->out_height); + if (ret) + goto deinit; + + ret = venus_helper_set_input_resolution(inst, inst->width, + inst->height); + if (ret) + goto deinit; + + ret = venus_helper_set_output_resolution(inst, inst->width, + inst->height, + HFI_BUFFER_OUTPUT); + if (ret) + goto deinit; + + ret = venus_helper_set_color_format(inst, inst->fmt_out->pixfmt); + if (ret) + goto deinit; + + ret = venc_set_properties(inst); + if (ret) + goto deinit; + + return 0; +deinit: + hfi_session_deinit(inst); + return ret; +} + +static int venc_out_num_buffers(struct venus_inst *inst, unsigned int *num) +{ + struct hfi_buffer_requirements bufreq; + int ret; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq); + if (ret) + return ret; + + *num = bufreq.count_actual; + + return 0; +} + +static int venc_queue_setup(struct vb2_queue *q, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + struct venus_core *core = inst->core; + unsigned int num, min = 4; + int ret; + + if (*num_planes) { + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + *num_planes != inst->fmt_out->num_planes) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + *num_planes != inst->fmt_cap->num_planes) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + sizes[0] < inst->input_buf_size) + return -EINVAL; + + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + sizes[0] < inst->output_buf_size) + return -EINVAL; + + return 0; + } + + if (test_bit(0, &core->sys_error)) { + if (inst->nonblock) + return -EAGAIN; + + ret = wait_event_interruptible(core->sys_err_done, + !test_bit(0, &core->sys_error)); + if (ret) + return ret; + } + + ret = venc_pm_get(inst); + if (ret) + return ret; + + mutex_lock(&inst->lock); + ret = venc_init_session(inst); + mutex_unlock(&inst->lock); + + if (ret) + goto put_power; + + ret = venc_pm_put(inst, false); + if (ret) + return ret; + + switch (q->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + *num_planes = inst->fmt_out->num_planes; + + ret = venc_out_num_buffers(inst, &num); + if (ret) + break; + + num = max(num, min); + *num_buffers = max(*num_buffers, num); + inst->num_input_bufs = *num_buffers; + + sizes[0] = venus_helper_get_framesz(inst->fmt_out->pixfmt, + inst->out_width, + inst->out_height); + inst->input_buf_size = sizes[0]; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + *num_planes = inst->fmt_cap->num_planes; + *num_buffers = max(*num_buffers, min); + inst->num_output_bufs = *num_buffers; + sizes[0] = venus_helper_get_framesz(inst->fmt_cap->pixfmt, + inst->width, + inst->height); + sizes[0] = max(sizes[0], inst->output_buf_size); + inst->output_buf_size = sizes[0]; + break; + default: + ret = -EINVAL; + break; + } + + return ret; +put_power: + venc_pm_put(inst, false); + return ret; +} + +static int venc_buf_init(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + + inst->buf_count++; + + return venus_helper_vb2_buf_init(vb); +} + +static void venc_release_session(struct venus_inst *inst) +{ + int ret; + + venc_pm_get(inst); + + mutex_lock(&inst->lock); + + ret = hfi_session_deinit(inst); + if (ret || inst->session_error) + hfi_session_abort(inst); + + mutex_unlock(&inst->lock); + + venus_pm_load_scale(inst); + INIT_LIST_HEAD(&inst->registeredbufs); + venus_pm_release_core(inst); + + venc_pm_put(inst, false); +} + +static void venc_buf_cleanup(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct venus_buffer *buf = to_venus_buffer(vbuf); + + mutex_lock(&inst->lock); + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + if (!list_empty(&inst->registeredbufs)) + list_del_init(&buf->reg_list); + mutex_unlock(&inst->lock); + + inst->buf_count--; + if (!inst->buf_count) + venc_release_session(inst); +} + +static int venc_verify_conf(struct venus_inst *inst) +{ + enum hfi_version ver = inst->core->res->hfi_version; + struct hfi_buffer_requirements bufreq; + int ret; + + if (!inst->num_input_bufs || !inst->num_output_bufs) + return -EINVAL; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_OUTPUT, &bufreq); + if (ret) + return ret; + + if (inst->num_output_bufs < bufreq.count_actual || + inst->num_output_bufs < HFI_BUFREQ_COUNT_MIN(&bufreq, ver)) + return -EINVAL; + + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq); + if (ret) + return ret; + + if (inst->num_input_bufs < bufreq.count_actual || + inst->num_input_bufs < HFI_BUFREQ_COUNT_MIN(&bufreq, ver)) + return -EINVAL; + + return 0; +} + +static int venc_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct venus_inst *inst = vb2_get_drv_priv(q); + int ret; + + mutex_lock(&inst->lock); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 1; + else + inst->streamon_cap = 1; + + if (!(inst->streamon_out & inst->streamon_cap)) { + mutex_unlock(&inst->lock); + return 0; + } + + venus_helper_init_instance(inst); + + inst->sequence_cap = 0; + inst->sequence_out = 0; + + ret = venc_pm_get(inst); + if (ret) + goto error; + + ret = venus_pm_acquire_core(inst); + if (ret) + goto put_power; + + ret = venc_pm_put(inst, true); + if (ret) + goto error; + + ret = venc_set_properties(inst); + if (ret) + goto error; + + ret = venc_verify_conf(inst); + if (ret) + goto error; + + ret = venus_helper_set_num_bufs(inst, inst->num_input_bufs, + inst->num_output_bufs, 0); + if (ret) + goto error; + + ret = venus_helper_vb2_start_streaming(inst); + if (ret) + goto error; + + mutex_unlock(&inst->lock); + + return 0; + +put_power: + venc_pm_put(inst, false); +error: + venus_helper_buffers_done(inst, q->type, VB2_BUF_STATE_QUEUED); + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + inst->streamon_out = 0; + else + inst->streamon_cap = 0; + mutex_unlock(&inst->lock); + return ret; +} + +static void venc_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct venus_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + + venc_pm_get_put(inst); + + mutex_lock(&inst->lock); + venus_helper_vb2_buf_queue(vb); + mutex_unlock(&inst->lock); +} + +static const struct vb2_ops venc_vb2_ops = { + .queue_setup = venc_queue_setup, + .buf_init = venc_buf_init, + .buf_cleanup = venc_buf_cleanup, + .buf_prepare = venus_helper_vb2_buf_prepare, + .start_streaming = venc_start_streaming, + .stop_streaming = venus_helper_vb2_stop_streaming, + .buf_queue = venc_vb2_buf_queue, +}; + +static void venc_buf_done(struct venus_inst *inst, unsigned int buf_type, + u32 tag, u32 bytesused, u32 data_offset, u32 flags, + u32 hfi_flags, u64 timestamp_us) +{ + struct vb2_v4l2_buffer *vbuf; + struct vb2_buffer *vb; + unsigned int type; + + venc_pm_touch(inst); + + if (buf_type == HFI_BUFFER_INPUT) + type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + else + type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + + vbuf = venus_helper_find_buf(inst, type, tag); + if (!vbuf) + return; + + vbuf->flags = flags; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + vb = &vbuf->vb2_buf; + vb2_set_plane_payload(vb, 0, bytesused + data_offset); + vb->planes[0].data_offset = data_offset; + vb->timestamp = timestamp_us * NSEC_PER_USEC; + vbuf->sequence = inst->sequence_cap++; + } else { + vbuf->sequence = inst->sequence_out++; + } + + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); +} + +static void venc_event_notify(struct venus_inst *inst, u32 event, + struct hfi_event_data *data) +{ + struct device *dev = inst->core->dev_enc; + + venc_pm_touch(inst); + + if (event == EVT_SESSION_ERROR) { + inst->session_error = true; + venus_helper_vb2_queue_error(inst); + dev_err(dev, "enc: event session error %x\n", inst->error); + } +} + +static const struct hfi_inst_ops venc_hfi_ops = { + .buf_done = venc_buf_done, + .event_notify = venc_event_notify, +}; + +static const struct v4l2_m2m_ops venc_m2m_ops = { + .device_run = venus_helper_m2m_device_run, + .job_abort = venus_helper_m2m_job_abort, +}; + +static int m2m_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct venus_inst *inst = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->ops = &venc_vb2_ops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->drv_priv = inst; + src_vq->buf_struct_size = sizeof(struct venus_buffer); + src_vq->allow_zero_bytesused = 1; + src_vq->min_buffers_needed = 1; + src_vq->dev = inst->core->dev; + if (inst->core->res->hfi_version == HFI_VERSION_1XX) + src_vq->bidirectional = 1; + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->ops = &venc_vb2_ops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->drv_priv = inst; + dst_vq->buf_struct_size = sizeof(struct venus_buffer); + dst_vq->allow_zero_bytesused = 1; + dst_vq->min_buffers_needed = 1; + dst_vq->dev = inst->core->dev; + return vb2_queue_init(dst_vq); +} + +static void venc_inst_init(struct venus_inst *inst) +{ + inst->fmt_cap = &venc_formats[3]; + inst->fmt_out = &venc_formats[0]; + inst->width = 1280; + inst->height = ALIGN(720, 32); + inst->out_width = 1280; + inst->out_height = 720; + inst->fps = 15; + inst->timeperframe.numerator = 1; + inst->timeperframe.denominator = 15; + inst->hfi_codec = HFI_VIDEO_CODEC_H264; +} + +static int venc_open(struct file *file) +{ + struct venus_core *core = video_drvdata(file); + struct venus_inst *inst; + int ret; + + inst = kzalloc(sizeof(*inst), GFP_KERNEL); + if (!inst) + return -ENOMEM; + + INIT_LIST_HEAD(&inst->dpbbufs); + INIT_LIST_HEAD(&inst->registeredbufs); + INIT_LIST_HEAD(&inst->internalbufs); + INIT_LIST_HEAD(&inst->list); + mutex_init(&inst->lock); + + inst->core = core; + inst->session_type = VIDC_SESSION_TYPE_ENC; + inst->clk_data.core_id = VIDC_CORE_ID_DEFAULT; + inst->core_acquired = false; + inst->nonblock = file->f_flags & O_NONBLOCK; + + venus_helper_init_instance(inst); + + ret = venc_ctrl_init(inst); + if (ret) + goto err_free; + + ret = hfi_session_create(inst, &venc_hfi_ops); + if (ret) + goto err_ctrl_deinit; + + venc_inst_init(inst); + + /* + * create m2m device for every instance, the m2m context scheduling + * is made by firmware side so we do not need to care about. + */ + inst->m2m_dev = v4l2_m2m_init(&venc_m2m_ops); + if (IS_ERR(inst->m2m_dev)) { + ret = PTR_ERR(inst->m2m_dev); + goto err_session_destroy; + } + + inst->m2m_ctx = v4l2_m2m_ctx_init(inst->m2m_dev, inst, m2m_queue_init); + if (IS_ERR(inst->m2m_ctx)) { + ret = PTR_ERR(inst->m2m_ctx); + goto err_m2m_release; + } + + v4l2_fh_init(&inst->fh, core->vdev_enc); + + inst->fh.ctrl_handler = &inst->ctrl_handler; + v4l2_fh_add(&inst->fh); + inst->fh.m2m_ctx = inst->m2m_ctx; + file->private_data = &inst->fh; + + return 0; + +err_m2m_release: + v4l2_m2m_release(inst->m2m_dev); +err_session_destroy: + hfi_session_destroy(inst); +err_ctrl_deinit: + venc_ctrl_deinit(inst); +err_free: + kfree(inst); + return ret; +} + +static int venc_close(struct file *file) +{ + struct venus_inst *inst = to_inst(file); + + venc_pm_get(inst); + + v4l2_m2m_ctx_release(inst->m2m_ctx); + v4l2_m2m_release(inst->m2m_dev); + venc_ctrl_deinit(inst); + hfi_session_destroy(inst); + mutex_destroy(&inst->lock); + v4l2_fh_del(&inst->fh); + v4l2_fh_exit(&inst->fh); + + venc_pm_put(inst, false); + + kfree(inst); + return 0; +} + +static const struct v4l2_file_operations venc_fops = { + .owner = THIS_MODULE, + .open = venc_open, + .release = venc_close, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int venc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct video_device *vdev; + struct venus_core *core; + int ret; + + if (!dev->parent) + return -EPROBE_DEFER; + + core = dev_get_drvdata(dev->parent); + if (!core) + return -EPROBE_DEFER; + + platform_set_drvdata(pdev, core); + + if (core->pm_ops->venc_get) { + ret = core->pm_ops->venc_get(dev); + if (ret) + return ret; + } + + vdev = video_device_alloc(); + if (!vdev) + return -ENOMEM; + + strscpy(vdev->name, "qcom-venus-encoder", sizeof(vdev->name)); + vdev->release = video_device_release; + vdev->fops = &venc_fops; + vdev->ioctl_ops = &venc_ioctl_ops; + vdev->vfl_dir = VFL_DIR_M2M; + vdev->v4l2_dev = &core->v4l2_dev; + vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) + goto err_vdev_release; + + core->vdev_enc = vdev; + core->dev_enc = dev; + + video_set_drvdata(vdev, core); + pm_runtime_set_autosuspend_delay(dev, 2000); + pm_runtime_use_autosuspend(dev); + pm_runtime_enable(dev); + + return 0; + +err_vdev_release: + video_device_release(vdev); + return ret; +} + +static int venc_remove(struct platform_device *pdev) +{ + struct venus_core *core = dev_get_drvdata(pdev->dev.parent); + + video_unregister_device(core->vdev_enc); + pm_runtime_disable(core->dev_enc); + + if (core->pm_ops->venc_put) + core->pm_ops->venc_put(core->dev_enc); + + return 0; +} + +static __maybe_unused int venc_runtime_suspend(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + const struct venus_pm_ops *pm_ops = core->pm_ops; + int ret = 0; + + if (pm_ops->venc_power) + ret = pm_ops->venc_power(dev, POWER_OFF); + + return ret; +} + +static __maybe_unused int venc_runtime_resume(struct device *dev) +{ + struct venus_core *core = dev_get_drvdata(dev); + const struct venus_pm_ops *pm_ops = core->pm_ops; + int ret = 0; + + if (pm_ops->venc_power) + ret = pm_ops->venc_power(dev, POWER_ON); + + return ret; +} + +static const struct dev_pm_ops venc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(venc_runtime_suspend, venc_runtime_resume, NULL) +}; + +static const struct of_device_id venc_dt_match[] = { + { .compatible = "venus-encoder" }, + { } +}; +MODULE_DEVICE_TABLE(of, venc_dt_match); + +static struct platform_driver qcom_venus_enc_driver = { + .probe = venc_probe, + .remove = venc_remove, + .driver = { + .name = "qcom-venus-encoder", + .of_match_table = venc_dt_match, + .pm = &venc_pm_ops, + }, +}; +module_platform_driver(qcom_venus_enc_driver); + +MODULE_ALIAS("platform:qcom-venus-encoder"); +MODULE_DESCRIPTION("Qualcomm Venus video encoder driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/qcom/venus/venc.h b/drivers/media/platform/qcom/venus/venc.h new file mode 100644 index 000000000..4ea37fdcd --- /dev/null +++ b/drivers/media/platform/qcom/venus/venc.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#ifndef __VENUS_VENC_H__ +#define __VENUS_VENC_H__ + +struct venus_inst; + +int venc_ctrl_init(struct venus_inst *inst); +void venc_ctrl_deinit(struct venus_inst *inst); + +#endif diff --git a/drivers/media/platform/qcom/venus/venc_ctrls.c b/drivers/media/platform/qcom/venus/venc_ctrls.c new file mode 100644 index 000000000..7468e4380 --- /dev/null +++ b/drivers/media/platform/qcom/venus/venc_ctrls.c @@ -0,0 +1,642 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. + * Copyright (C) 2017 Linaro Ltd. + */ +#include +#include + +#include "core.h" +#include "venc.h" +#include "helpers.h" + +#define BITRATE_MIN 32000 +#define BITRATE_MAX 160000000 +#define BITRATE_DEFAULT 1000000 +#define BITRATE_DEFAULT_PEAK (BITRATE_DEFAULT * 2) +#define BITRATE_STEP 100 +#define SLICE_BYTE_SIZE_MAX 1024 +#define SLICE_BYTE_SIZE_MIN 1024 +#define SLICE_MB_SIZE_MAX 300 +#define AT_SLICE_BOUNDARY \ + V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY +#define MAX_LTR_FRAME_COUNT 4 + +static int venc_calc_bpframes(u32 gop_size, u32 conseq_b, u32 *bf, u32 *pf) +{ + u32 half = (gop_size - 1) >> 1; + u32 b, p, ratio; + bool found = false; + + if (!gop_size) + return -EINVAL; + + *bf = *pf = 0; + + if (!conseq_b) { + *pf = gop_size - 1; + return 0; + } + + b = p = half; + + for (; b <= gop_size - 1; b++, p--) { + if (b % p) + continue; + + ratio = b / p; + + if (ratio == conseq_b) { + found = true; + break; + } + + if (ratio > conseq_b) + break; + } + + if (!found) + return -EINVAL; + + if (b + p + 1 != gop_size) + return -EINVAL; + + *bf = b; + *pf = p; + + return 0; +} + +static int venc_op_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct venus_inst *inst = ctrl_to_inst(ctrl); + struct venc_controls *ctr = &inst->controls.enc; + struct hfi_enable en = { .enable = 1 }; + struct hfi_bitrate brate; + struct hfi_ltr_use ltr_use; + struct hfi_ltr_mark ltr_mark; + u32 bframes; + u32 ptype; + int ret; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + ctr->bitrate_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + ctr->bitrate = ctrl->val; + mutex_lock(&inst->lock); + if (inst->streamon_out && inst->streamon_cap) { + ptype = HFI_PROPERTY_CONFIG_VENC_TARGET_BITRATE; + brate.bitrate = ctr->bitrate; + brate.layer_id = 0; + + ret = hfi_session_set_property(inst, ptype, &brate); + if (ret) { + mutex_unlock(&inst->lock); + return ret; + } + } + mutex_unlock(&inst->lock); + break; + case V4L2_CID_MPEG_VIDEO_BITRATE_PEAK: + ctr->bitrate_peak = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + ctr->h264_entropy_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + ctr->profile.mpeg4 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + ctr->profile.h264 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE: + ctr->profile.hevc = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: + ctr->profile.vp8 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + ctr->level.mpeg4 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + ctr->level.h264 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_LEVEL: + ctr->level.hevc = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + ctr->h264_i_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + ctr->h264_p_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + ctr->h264_b_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + ctr->h264_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_MIN_QP: + ctr->h264_i_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_MIN_QP: + ctr->h264_p_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_MIN_QP: + ctr->h264_b_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + ctr->h264_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_MAX_QP: + ctr->h264_i_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_MAX_QP: + ctr->h264_p_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_MAX_QP: + ctr->h264_b_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP: + ctr->hevc_i_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP: + ctr->hevc_p_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP: + ctr->hevc_b_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP: + ctr->hevc_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MIN_QP: + ctr->hevc_i_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_MIN_QP: + ctr->hevc_p_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_MIN_QP: + ctr->hevc_b_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP: + ctr->hevc_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MAX_QP: + ctr->hevc_i_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_MAX_QP: + ctr->hevc_p_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_MAX_QP: + ctr->hevc_b_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + ctr->multi_slice_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + ctr->multi_slice_max_bytes = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + ctr->multi_slice_max_mb = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA: + ctr->h264_loop_filter_alpha = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA: + ctr->h264_loop_filter_beta = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: + ctr->h264_loop_filter_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + ctr->header_mode = ctrl->val; + mutex_lock(&inst->lock); + if (inst->streamon_out && inst->streamon_cap) { + if (ctrl->val == V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE) + en.enable = 0; + else + en.enable = 1; + ptype = HFI_PROPERTY_CONFIG_VENC_SYNC_FRAME_SEQUENCE_HEADER; + ret = hfi_session_set_property(inst, ptype, &en); + if (ret) { + mutex_unlock(&inst->lock); + return ret; + } + } + mutex_unlock(&inst->lock); + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ret = venc_calc_bpframes(ctrl->val, ctr->num_b_frames, &bframes, + &ctr->num_p_frames); + if (ret) + return ret; + + ctr->gop_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: + ctr->h264_i_period = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_MIN_QP: + ctr->vp8_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_MAX_QP: + ctr->vp8_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + ret = venc_calc_bpframes(ctr->gop_size, ctrl->val, &bframes, + &ctr->num_p_frames); + if (ret) + return ret; + + ctr->num_b_frames = bframes; + break; + case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: + mutex_lock(&inst->lock); + if (inst->streamon_out && inst->streamon_cap) { + ptype = HFI_PROPERTY_CONFIG_VENC_REQUEST_SYNC_FRAME; + ret = hfi_session_set_property(inst, ptype, &en); + + if (ret) { + mutex_unlock(&inst->lock); + return ret; + } + } + mutex_unlock(&inst->lock); + break; + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + ctr->rc_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_CONSTANT_QUALITY: + ctr->const_quality = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_FRAME_SKIP_MODE: + ctr->frame_skip_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BASELAYER_PRIORITY_ID: + ctr->base_priority_id = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_AU_DELIMITER: + ctr->aud_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_LTR_COUNT: + ctr->ltr_count = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_FRAME_LTR_INDEX: + mutex_lock(&inst->lock); + if (inst->streamon_out && inst->streamon_cap) { + ptype = HFI_PROPERTY_CONFIG_VENC_MARKLTRFRAME; + ltr_mark.mark_frame = ctrl->val; + ret = hfi_session_set_property(inst, ptype, <r_mark); + if (ret) { + mutex_unlock(&inst->lock); + return ret; + } + } + mutex_unlock(&inst->lock); + break; + case V4L2_CID_MPEG_VIDEO_USE_LTR_FRAMES: + mutex_lock(&inst->lock); + if (inst->streamon_out && inst->streamon_cap) { + ptype = HFI_PROPERTY_CONFIG_VENC_USELTRFRAME; + ltr_use.ref_ltr = ctrl->val; + ltr_use.use_constrnt = true; + ltr_use.frames = 0; + ret = hfi_session_set_property(inst, ptype, <r_use); + if (ret) { + mutex_unlock(&inst->lock); + return ret; + } + } + mutex_unlock(&inst->lock); + break; + case V4L2_CID_COLORIMETRY_HDR10_CLL_INFO: + ctr->cll = *ctrl->p_new.p_hdr10_cll; + break; + case V4L2_CID_COLORIMETRY_HDR10_MASTERING_DISPLAY: + ctr->mastering = *ctrl->p_new.p_hdr10_mastering; + break; + case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE: + ctr->intra_refresh_type = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD: + ctr->intra_refresh_period = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM: + if (ctr->profile.h264 != V4L2_MPEG_VIDEO_H264_PROFILE_HIGH && + ctr->profile.h264 != V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_HIGH) + return -EINVAL; + + /* + * In video firmware, 8x8 transform is supported only for + * high profile(HP) and constrained high profile(CHP). + * If client wants to disable 8x8 transform for HP/CHP, + * it is better to set profile as main profile(MP). + * Because there is no difference between HP and MP + * if we disable 8x8 transform for HP. + */ + + + ctr->h264_8x8_transform = ctrl->val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int venc_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct venus_inst *inst = ctrl_to_inst(ctrl); + struct hfi_buffer_requirements bufreq; + enum hfi_version ver = inst->core->res->hfi_version; + int ret; + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT: + ret = venus_helper_get_bufreq(inst, HFI_BUFFER_INPUT, &bufreq); + if (!ret) + ctrl->val = HFI_BUFREQ_COUNT_MIN(&bufreq, ver); + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops venc_ctrl_ops = { + .s_ctrl = venc_op_s_ctrl, + .g_volatile_ctrl = venc_op_g_volatile_ctrl, +}; + +int venc_ctrl_init(struct venus_inst *inst) +{ + int ret; + struct v4l2_ctrl_hdr10_mastering_display p_hdr10_mastering = { + { 34000, 13250, 7500 }, + { 16000, 34500, 3000 }, 15635, 16450, 10000000, 500, + }; + struct v4l2_ctrl_hdr10_cll_info p_hdr10_cll = { 1000, 400 }; + + ret = v4l2_ctrl_handler_init(&inst->ctrl_handler, 59); + if (ret) + return ret; + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, + ~((1 << V4L2_MPEG_VIDEO_BITRATE_MODE_VBR) | + (1 << V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) | + (1 << V4L2_MPEG_VIDEO_BITRATE_MODE_CQ)), + V4L2_MPEG_VIDEO_BITRATE_MODE_VBR); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, + V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC, + 0, V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_CODING_EFFICIENCY, + ~((1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE) | + (1 << V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE)), + V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + V4L2_MPEG_VIDEO_MPEG4_LEVEL_5, + 0, V4L2_MPEG_VIDEO_MPEG4_LEVEL_0); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_PROFILE, + V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10, + ~((1 << V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE) | + (1 << V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10)), + V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_LEVEL, + V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2, + 0, V4L2_MPEG_VIDEO_HEVC_LEVEL_1); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH, + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_5_1, + 0, V4L2_MPEG_VIDEO_H264_LEVEL_1_0); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, + AT_SLICE_BOUNDARY, + 0, V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEADER_MODE, + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + ~((1 << V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE) | + (1 << V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME)), + V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE, + V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES, + 0, V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VP8_PROFILE, + V4L2_MPEG_VIDEO_VP8_PROFILE_3, + 0, V4L2_MPEG_VIDEO_VP8_PROFILE_0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, 4, 11, 1, 4); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, BITRATE_MIN, BITRATE_MAX, + BITRATE_STEP, BITRATE_DEFAULT); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_PEAK, BITRATE_MIN, BITRATE_MAX, + BITRATE_STEP, BITRATE_DEFAULT_PEAK); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, 1, 51, 1, 26); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, 1, 51, 1, 28); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP, 1, 51, 1, 30); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, 1, 51, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_MIN_QP, 1, 51, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM, 0, 1, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_MIN_QP, 1, 51, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_B_FRAME_MIN_QP, 1, 51, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MAX_QP, 1, 51, 1, 51); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_FRAME_MAX_QP, 1, 51, 1, 51); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_P_FRAME_MAX_QP, 1, 51, 1, 51); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_B_FRAME_MAX_QP, 1, 51, 1, 51); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP, 1, 63, 1, 26); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP, 1, 63, 1, 28); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP, 1, 63, 1, 30); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP, 1, 63, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MIN_QP, 1, 63, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_MIN_QP, 1, 63, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_MIN_QP, 1, 63, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP, 1, 63, 1, 63); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MAX_QP, 1, 63, 1, 63); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_MAX_QP, 1, 63, 1, 63); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_MAX_QP, 1, 63, 1, 63); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES, SLICE_BYTE_SIZE_MIN, + SLICE_BYTE_SIZE_MAX, 1, SLICE_BYTE_SIZE_MIN); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB, 1, + SLICE_MB_SIZE_MAX, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA, -6, 6, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA, -6, 6, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, 0, (1 << 16) - 1, 1, 30); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VPX_MIN_QP, 1, 128, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_VPX_MAX_QP, 1, 128, 1, 128); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_B_FRAMES, 0, 4, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, 0, (1 << 16) - 1, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, 0, 0, 0, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, 0, 1, 1, 1); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_CONSTANT_QUALITY, 0, 100, 1, 0); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FRAME_SKIP_MODE, + V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT, + ~((1 << V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_DISABLED) | + (1 << V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT)), + V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_DISABLED); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BASELAYER_PRIORITY_ID, 0, + 6, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_AU_DELIMITER, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_USE_LTR_FRAMES, 0, + ((1 << MAX_LTR_FRAME_COUNT) - 1), 0, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_LTR_COUNT, 0, + MAX_LTR_FRAME_COUNT, 1, 0); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_FRAME_LTR_INDEX, 0, + (MAX_LTR_FRAME_COUNT - 1), 1, 0); + + v4l2_ctrl_new_std_compound(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_COLORIMETRY_HDR10_CLL_INFO, + v4l2_ctrl_ptr_create(&p_hdr10_cll)); + + v4l2_ctrl_new_std_compound(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_COLORIMETRY_HDR10_MASTERING_DISPLAY, + v4l2_ctrl_ptr_create((void *)&p_hdr10_mastering)); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE, + V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_CYCLIC, + 0, V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD_TYPE_RANDOM); + + v4l2_ctrl_new_std(&inst->ctrl_handler, &venc_ctrl_ops, + V4L2_CID_MPEG_VIDEO_INTRA_REFRESH_PERIOD, 0, + ((4096 * 2304) >> 8), 1, 0); + + ret = inst->ctrl_handler.error; + if (ret) + goto err; + + ret = v4l2_ctrl_handler_setup(&inst->ctrl_handler); + if (ret) + goto err; + + return 0; +err: + v4l2_ctrl_handler_free(&inst->ctrl_handler); + return ret; +} + +void venc_ctrl_deinit(struct venus_inst *inst) +{ + v4l2_ctrl_handler_free(&inst->ctrl_handler); +} diff --git a/drivers/media/platform/renesas/Kconfig b/drivers/media/platform/renesas/Kconfig new file mode 100644 index 000000000..9fd90672e --- /dev/null +++ b/drivers/media/platform/renesas/Kconfig @@ -0,0 +1,121 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Renesas media platform drivers" + +# V4L drivers + +config VIDEO_RENESAS_CEU + tristate "Renesas Capture Engine Unit (CEU) driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_SHMOBILE || ARCH_R7S72100 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + This is a v4l2 driver for the Renesas CEU Interface + +config VIDEO_RCAR_ISP + tristate "R-Car Image Signal Processor (ISP)" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + depends on ARCH_RENESAS || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select RESET_CONTROLLER + select V4L2_FWNODE + help + Support for Renesas R-Car Image Signal Processor (ISP). + Enable this to support the Renesas R-Car Image Signal + Processor (ISP). + + To compile this driver as a module, choose M here: the + module will be called rcar-isp. + +config VIDEO_SH_VOU + tristate "SuperH VOU video output driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && I2C + depends on ARCH_SHMOBILE || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + help + Support for the Video Output Unit (VOU) on SuperH SoCs. + +source "drivers/media/platform/renesas/rcar-vin/Kconfig" + +# Mem2mem drivers + +config VIDEO_RENESAS_FCP + tristate "Renesas Frame Compression Processor" + depends on V4L_MEM2MEM_DRIVERS + depends on ARCH_RENESAS || COMPILE_TEST + depends on OF + help + This is a driver for the Renesas Frame Compression Processor (FCP). + The FCP is a companion module of video processing modules in the + Renesas R-Car Gen3 and RZ/G2 SoCs. It handles memory access for + the codec, VSP and FDP modules. + + To compile this driver as a module, choose M here: the module + will be called rcar-fcp. + +config VIDEO_RENESAS_FDP1 + tristate "Renesas Fine Display Processor" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_RENESAS || COMPILE_TEST + depends on (!ARM64 && !VIDEO_RENESAS_FCP) || VIDEO_RENESAS_FCP + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a V4L2 driver for the Renesas Fine Display Processor + providing colour space conversion, and de-interlacing features. + + To compile this driver as a module, choose M here: the module + will be called rcar_fdp1. + +config VIDEO_RENESAS_JPU + tristate "Renesas JPEG Processing Unit" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_RENESAS || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a V4L2 driver for the Renesas JPEG Processing Unit. + + To compile this driver as a module, choose M here: the module + will be called rcar_jpu. + +config VIDEO_RENESAS_VSP1 + tristate "Renesas VSP1 Video Processing Engine" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_RENESAS || COMPILE_TEST + depends on (!ARM64 && !VIDEO_RENESAS_FCP) || VIDEO_RENESAS_FCP + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC + help + This is a V4L2 driver for the Renesas VSP1 video processing engine. + + To compile this driver as a module, choose M here: the module + will be called vsp1. + +# SDR drivers + +config VIDEO_RCAR_DRIF + tristate "Renesas Digital Radio Interface (DRIF)" + depends on SDR_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_RENESAS || COMPILE_TEST + select VIDEOBUF2_VMALLOC + select V4L2_ASYNC + help + Say Y if you want to enable R-Car Gen3 DRIF support. DRIF is Digital + Radio Interface that interfaces with an RF front end chip. It is a + receiver of digital data which uses DMA to transfer received data to + a configured location for an application to use. + + To compile this driver as a module, choose M here; the module + will be called rcar_drif. diff --git a/drivers/media/platform/renesas/Makefile b/drivers/media/platform/renesas/Makefile new file mode 100644 index 000000000..3ec226ef5 --- /dev/null +++ b/drivers/media/platform/renesas/Makefile @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the Renesas capture/playback device drivers. +# + +obj-y += rcar-vin/ +obj-y += vsp1/ + +obj-$(CONFIG_VIDEO_RCAR_DRIF) += rcar_drif.o +obj-$(CONFIG_VIDEO_RCAR_ISP) += rcar-isp.o +obj-$(CONFIG_VIDEO_RENESAS_CEU) += renesas-ceu.o +obj-$(CONFIG_VIDEO_RENESAS_FCP) += rcar-fcp.o +obj-$(CONFIG_VIDEO_RENESAS_FDP1) += rcar_fdp1.o +obj-$(CONFIG_VIDEO_RENESAS_JPU) += rcar_jpu.o +obj-$(CONFIG_VIDEO_SH_VOU) += sh_vou.o diff --git a/drivers/media/platform/renesas/rcar-fcp.c b/drivers/media/platform/renesas/rcar-fcp.c new file mode 100644 index 000000000..eb59a3ba6 --- /dev/null +++ b/drivers/media/platform/renesas/rcar-fcp.c @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * rcar-fcp.c -- R-Car Frame Compression Processor Driver + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct rcar_fcp_device { + struct list_head list; + struct device *dev; +}; + +static LIST_HEAD(fcp_devices); +static DEFINE_MUTEX(fcp_lock); + +/* ----------------------------------------------------------------------------- + * Public API + */ + +/** + * rcar_fcp_get - Find and acquire a reference to an FCP instance + * @np: Device node of the FCP instance + * + * Search the list of registered FCP instances for the instance corresponding to + * the given device node. + * + * Return a pointer to the FCP instance, or an ERR_PTR if the instance can't be + * found. + */ +struct rcar_fcp_device *rcar_fcp_get(const struct device_node *np) +{ + struct rcar_fcp_device *fcp; + + mutex_lock(&fcp_lock); + + list_for_each_entry(fcp, &fcp_devices, list) { + if (fcp->dev->of_node != np) + continue; + + get_device(fcp->dev); + goto done; + } + + fcp = ERR_PTR(-EPROBE_DEFER); + +done: + mutex_unlock(&fcp_lock); + return fcp; +} +EXPORT_SYMBOL_GPL(rcar_fcp_get); + +/** + * rcar_fcp_put - Release a reference to an FCP instance + * @fcp: The FCP instance + * + * Release the FCP instance acquired by a call to rcar_fcp_get(). + */ +void rcar_fcp_put(struct rcar_fcp_device *fcp) +{ + if (fcp) + put_device(fcp->dev); +} +EXPORT_SYMBOL_GPL(rcar_fcp_put); + +struct device *rcar_fcp_get_device(struct rcar_fcp_device *fcp) +{ + return fcp->dev; +} +EXPORT_SYMBOL_GPL(rcar_fcp_get_device); + +/** + * rcar_fcp_enable - Enable an FCP + * @fcp: The FCP instance + * + * Before any memory access through an FCP is performed by a module, the FCP + * must be enabled by a call to this function. The enable calls are reference + * counted, each successful call must be followed by one rcar_fcp_disable() + * call when no more memory transfer can occur through the FCP. + * + * Return 0 on success or a negative error code if an error occurs. The enable + * reference count isn't increased when this function returns an error. + */ +int rcar_fcp_enable(struct rcar_fcp_device *fcp) +{ + if (!fcp) + return 0; + + return pm_runtime_resume_and_get(fcp->dev); +} +EXPORT_SYMBOL_GPL(rcar_fcp_enable); + +/** + * rcar_fcp_disable - Disable an FCP + * @fcp: The FCP instance + * + * This function is the counterpart of rcar_fcp_enable(). As enable calls are + * reference counted a disable call may not disable the FCP synchronously. + */ +void rcar_fcp_disable(struct rcar_fcp_device *fcp) +{ + if (fcp) + pm_runtime_put(fcp->dev); +} +EXPORT_SYMBOL_GPL(rcar_fcp_disable); + +/* ----------------------------------------------------------------------------- + * Platform Driver + */ + +static int rcar_fcp_probe(struct platform_device *pdev) +{ + struct rcar_fcp_device *fcp; + + fcp = devm_kzalloc(&pdev->dev, sizeof(*fcp), GFP_KERNEL); + if (fcp == NULL) + return -ENOMEM; + + fcp->dev = &pdev->dev; + + dma_set_max_seg_size(fcp->dev, UINT_MAX); + + pm_runtime_enable(&pdev->dev); + + mutex_lock(&fcp_lock); + list_add_tail(&fcp->list, &fcp_devices); + mutex_unlock(&fcp_lock); + + platform_set_drvdata(pdev, fcp); + + return 0; +} + +static int rcar_fcp_remove(struct platform_device *pdev) +{ + struct rcar_fcp_device *fcp = platform_get_drvdata(pdev); + + mutex_lock(&fcp_lock); + list_del(&fcp->list); + mutex_unlock(&fcp_lock); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id rcar_fcp_of_match[] = { + { .compatible = "renesas,fcpf" }, + { .compatible = "renesas,fcpv" }, + { }, +}; +MODULE_DEVICE_TABLE(of, rcar_fcp_of_match); + +static struct platform_driver rcar_fcp_platform_driver = { + .probe = rcar_fcp_probe, + .remove = rcar_fcp_remove, + .driver = { + .name = "rcar-fcp", + .of_match_table = rcar_fcp_of_match, + .suppress_bind_attrs = true, + }, +}; + +module_platform_driver(rcar_fcp_platform_driver); + +MODULE_ALIAS("rcar-fcp"); +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_DESCRIPTION("Renesas FCP Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/rcar-isp.c b/drivers/media/platform/renesas/rcar-isp.c new file mode 100644 index 000000000..10b3474f9 --- /dev/null +++ b/drivers/media/platform/renesas/rcar-isp.c @@ -0,0 +1,535 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 Renesas Electronics Corp. + * + * Driver for Renesas R-Car ISP Channel Selector + * + * The ISP hardware is capable of more than just channel selection, features + * such as demosaicing, white balance control and color space conversion are + * also possible. These more advanced features are not supported by the driver + * due to lack of documentation. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ISPINPUTSEL0_REG 0x0008 +#define ISPINPUTSEL0_SEL_CSI0 BIT(31) + +#define ISPSTART_REG 0x0014 +#define ISPSTART_START 0xffff +#define ISPSTART_STOP 0x0000 + +#define ISPPROCMODE_DT_REG(n) (0x1100 + (0x4 * (n))) +#define ISPPROCMODE_DT_PROC_MODE_VC3(pm) (((pm) & 0x3f) << 24) +#define ISPPROCMODE_DT_PROC_MODE_VC2(pm) (((pm) & 0x3f) << 16) +#define ISPPROCMODE_DT_PROC_MODE_VC1(pm) (((pm) & 0x3f) << 8) +#define ISPPROCMODE_DT_PROC_MODE_VC0(pm) ((pm) & 0x3f) + +#define ISPCS_FILTER_ID_CH_REG(n) (0x3000 + (0x0100 * (n))) + +#define ISPCS_DT_CODE03_CH_REG(n) (0x3008 + (0x100 * (n))) +#define ISPCS_DT_CODE03_EN3 BIT(31) +#define ISPCS_DT_CODE03_DT3(dt) (((dt) & 0x3f) << 24) +#define ISPCS_DT_CODE03_EN2 BIT(23) +#define ISPCS_DT_CODE03_DT2(dt) (((dt) & 0x3f) << 16) +#define ISPCS_DT_CODE03_EN1 BIT(15) +#define ISPCS_DT_CODE03_DT1(dt) (((dt) & 0x3f) << 8) +#define ISPCS_DT_CODE03_EN0 BIT(7) +#define ISPCS_DT_CODE03_DT0(dt) ((dt) & 0x3f) + +struct rcar_isp_format { + u32 code; + unsigned int datatype; + unsigned int procmode; +}; + +static const struct rcar_isp_format rcar_isp_formats[] = { + { + .code = MEDIA_BUS_FMT_RGB888_1X24, + .datatype = MIPI_CSI2_DT_RGB888, + .procmode = 0x15 + }, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .datatype = MIPI_CSI2_DT_RAW10, + .procmode = 0x10, + }, { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .procmode = 0x0c, + }, { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .procmode = 0x0c, + }, { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .procmode = 0x0c, + }, { + .code = MEDIA_BUS_FMT_YUYV10_2X10, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .procmode = 0x0c, + }, +}; + +static const struct rcar_isp_format *risp_code_to_fmt(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcar_isp_formats); i++) { + if (rcar_isp_formats[i].code == code) + return &rcar_isp_formats[i]; + } + + return NULL; +} + +enum rcar_isp_input { + RISP_CSI_INPUT0, + RISP_CSI_INPUT1, +}; + +enum rcar_isp_pads { + RCAR_ISP_SINK, + RCAR_ISP_PORT0, + RCAR_ISP_PORT1, + RCAR_ISP_PORT2, + RCAR_ISP_PORT3, + RCAR_ISP_PORT4, + RCAR_ISP_PORT5, + RCAR_ISP_PORT6, + RCAR_ISP_PORT7, + RCAR_ISP_NUM_PADS, +}; + +struct rcar_isp { + struct device *dev; + void __iomem *base; + struct reset_control *rstc; + + enum rcar_isp_input csi_input; + + struct v4l2_subdev subdev; + struct media_pad pads[RCAR_ISP_NUM_PADS]; + + struct v4l2_async_notifier notifier; + struct v4l2_subdev *remote; + + struct mutex lock; /* Protects mf and stream_count. */ + struct v4l2_mbus_framefmt mf; + int stream_count; +}; + +static inline struct rcar_isp *sd_to_isp(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rcar_isp, subdev); +} + +static inline struct rcar_isp *notifier_to_isp(struct v4l2_async_notifier *n) +{ + return container_of(n, struct rcar_isp, notifier); +} + +static void risp_write(struct rcar_isp *isp, u32 offset, u32 value) +{ + iowrite32(value, isp->base + offset); +} + +static u32 risp_read(struct rcar_isp *isp, u32 offset) +{ + return ioread32(isp->base + offset); +} + +static int risp_power_on(struct rcar_isp *isp) +{ + int ret; + + ret = pm_runtime_resume_and_get(isp->dev); + if (ret < 0) + return ret; + + ret = reset_control_deassert(isp->rstc); + if (ret < 0) { + pm_runtime_put(isp->dev); + return ret; + } + + return 0; +} + +static void risp_power_off(struct rcar_isp *isp) +{ + reset_control_assert(isp->rstc); + pm_runtime_put(isp->dev); +} + +static int risp_start(struct rcar_isp *isp) +{ + const struct rcar_isp_format *format; + unsigned int vc; + u32 sel_csi = 0; + int ret; + + format = risp_code_to_fmt(isp->mf.code); + if (!format) { + dev_err(isp->dev, "Unsupported bus format\n"); + return -EINVAL; + } + + ret = risp_power_on(isp); + if (ret) { + dev_err(isp->dev, "Failed to power on ISP\n"); + return ret; + } + + /* Select CSI-2 input source. */ + if (isp->csi_input == RISP_CSI_INPUT1) + sel_csi = ISPINPUTSEL0_SEL_CSI0; + + risp_write(isp, ISPINPUTSEL0_REG, + risp_read(isp, ISPINPUTSEL0_REG) | sel_csi); + + /* Configure Channel Selector. */ + for (vc = 0; vc < 4; vc++) { + u8 ch = vc + 4; + u8 dt = format->datatype; + + risp_write(isp, ISPCS_FILTER_ID_CH_REG(ch), BIT(vc)); + risp_write(isp, ISPCS_DT_CODE03_CH_REG(ch), + ISPCS_DT_CODE03_EN3 | ISPCS_DT_CODE03_DT3(dt) | + ISPCS_DT_CODE03_EN2 | ISPCS_DT_CODE03_DT2(dt) | + ISPCS_DT_CODE03_EN1 | ISPCS_DT_CODE03_DT1(dt) | + ISPCS_DT_CODE03_EN0 | ISPCS_DT_CODE03_DT0(dt)); + } + + /* Setup processing method. */ + risp_write(isp, ISPPROCMODE_DT_REG(format->datatype), + ISPPROCMODE_DT_PROC_MODE_VC3(format->procmode) | + ISPPROCMODE_DT_PROC_MODE_VC2(format->procmode) | + ISPPROCMODE_DT_PROC_MODE_VC1(format->procmode) | + ISPPROCMODE_DT_PROC_MODE_VC0(format->procmode)); + + /* Start ISP. */ + risp_write(isp, ISPSTART_REG, ISPSTART_START); + + ret = v4l2_subdev_call(isp->remote, video, s_stream, 1); + if (ret) + risp_power_off(isp); + + return ret; +} + +static void risp_stop(struct rcar_isp *isp) +{ + v4l2_subdev_call(isp->remote, video, s_stream, 0); + + /* Stop ISP. */ + risp_write(isp, ISPSTART_REG, ISPSTART_STOP); + + risp_power_off(isp); +} + +static int risp_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rcar_isp *isp = sd_to_isp(sd); + int ret = 0; + + mutex_lock(&isp->lock); + + if (!isp->remote) { + ret = -ENODEV; + goto out; + } + + if (enable && isp->stream_count == 0) { + ret = risp_start(isp); + if (ret) + goto out; + } else if (!enable && isp->stream_count == 1) { + risp_stop(isp); + } + + isp->stream_count += enable ? 1 : -1; +out: + mutex_unlock(&isp->lock); + + return ret; +} + +static const struct v4l2_subdev_video_ops risp_video_ops = { + .s_stream = risp_s_stream, +}; + +static int risp_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct rcar_isp *isp = sd_to_isp(sd); + struct v4l2_mbus_framefmt *framefmt; + + mutex_lock(&isp->lock); + + if (!risp_code_to_fmt(format->format.code)) + format->format.code = rcar_isp_formats[0].code; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + isp->mf = format->format; + } else { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, 0); + *framefmt = format->format; + } + + mutex_unlock(&isp->lock); + + return 0; +} + +static int risp_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct rcar_isp *isp = sd_to_isp(sd); + + mutex_lock(&isp->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + format->format = isp->mf; + else + format->format = *v4l2_subdev_get_try_format(sd, sd_state, 0); + + mutex_unlock(&isp->lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops risp_pad_ops = { + .set_fmt = risp_set_pad_format, + .get_fmt = risp_get_pad_format, + .link_validate = v4l2_subdev_link_validate_default, +}; + +static const struct v4l2_subdev_ops rcar_isp_subdev_ops = { + .video = &risp_video_ops, + .pad = &risp_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * Async handling and registration of subdevices and links + */ + +static int risp_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_isp *isp = notifier_to_isp(notifier); + int pad; + + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) { + dev_err(isp->dev, "Failed to find pad for %s\n", subdev->name); + return pad; + } + + isp->remote = subdev; + + dev_dbg(isp->dev, "Bound %s pad: %d\n", subdev->name, pad); + + return media_create_pad_link(&subdev->entity, pad, + &isp->subdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static void risp_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_isp *isp = notifier_to_isp(notifier); + + isp->remote = NULL; + + dev_dbg(isp->dev, "Unbind %s\n", subdev->name); +} + +static const struct v4l2_async_notifier_operations risp_notify_ops = { + .bound = risp_notify_bound, + .unbind = risp_notify_unbind, +}; + +static int risp_parse_dt(struct rcar_isp *isp) +{ + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode; + struct fwnode_handle *ep; + unsigned int id; + int ret; + + for (id = 0; id < 2; id++) { + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(isp->dev), + 0, id, 0); + if (ep) + break; + } + + if (!ep) { + dev_err(isp->dev, "Not connected to subdevice\n"); + return -EINVAL; + } + + if (id == 1) + isp->csi_input = RISP_CSI_INPUT1; + + fwnode = fwnode_graph_get_remote_endpoint(ep); + fwnode_handle_put(ep); + + dev_dbg(isp->dev, "Found '%pOF'\n", to_of_node(fwnode)); + + v4l2_async_nf_init(&isp->notifier); + isp->notifier.ops = &risp_notify_ops; + + asd = v4l2_async_nf_add_fwnode(&isp->notifier, fwnode, + struct v4l2_async_subdev); + fwnode_handle_put(fwnode); + if (IS_ERR(asd)) + return PTR_ERR(asd); + + ret = v4l2_async_subdev_nf_register(&isp->subdev, &isp->notifier); + if (ret) + v4l2_async_nf_cleanup(&isp->notifier); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver + */ + +static const struct media_entity_operations risp_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int risp_probe_resources(struct rcar_isp *isp, + struct platform_device *pdev) +{ + struct resource *res; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + isp->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(isp->base)) + return PTR_ERR(isp->base); + + isp->rstc = devm_reset_control_get(&pdev->dev, NULL); + + return PTR_ERR_OR_ZERO(isp->rstc); +} + +static const struct of_device_id risp_of_id_table[] = { + { .compatible = "renesas,r8a779a0-isp" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, risp_of_id_table); + +static int risp_probe(struct platform_device *pdev) +{ + struct rcar_isp *isp; + unsigned int i; + int ret; + + isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + isp->dev = &pdev->dev; + + mutex_init(&isp->lock); + + ret = risp_probe_resources(isp, pdev); + if (ret) { + dev_err(isp->dev, "Failed to get resources\n"); + goto error_mutex; + } + + platform_set_drvdata(pdev, isp); + + pm_runtime_enable(&pdev->dev); + + ret = risp_parse_dt(isp); + if (ret) + goto error_pm; + + isp->subdev.owner = THIS_MODULE; + isp->subdev.dev = &pdev->dev; + v4l2_subdev_init(&isp->subdev, &rcar_isp_subdev_ops); + v4l2_set_subdevdata(&isp->subdev, &pdev->dev); + snprintf(isp->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s %s", + KBUILD_MODNAME, dev_name(&pdev->dev)); + isp->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + isp->subdev.entity.function = MEDIA_ENT_F_VID_MUX; + isp->subdev.entity.ops = &risp_entity_ops; + + isp->pads[RCAR_ISP_SINK].flags = MEDIA_PAD_FL_SINK; + for (i = RCAR_ISP_PORT0; i < RCAR_ISP_NUM_PADS; i++) + isp->pads[i].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&isp->subdev.entity, RCAR_ISP_NUM_PADS, + isp->pads); + if (ret) + goto error_notifier; + + ret = v4l2_async_register_subdev(&isp->subdev); + if (ret < 0) + goto error_notifier; + + dev_info(isp->dev, "Using CSI-2 input: %u\n", isp->csi_input); + + return 0; +error_notifier: + v4l2_async_nf_unregister(&isp->notifier); + v4l2_async_nf_cleanup(&isp->notifier); +error_pm: + pm_runtime_disable(&pdev->dev); +error_mutex: + mutex_destroy(&isp->lock); + + return ret; +} + +static int risp_remove(struct platform_device *pdev) +{ + struct rcar_isp *isp = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&isp->notifier); + v4l2_async_nf_cleanup(&isp->notifier); + + v4l2_async_unregister_subdev(&isp->subdev); + + pm_runtime_disable(&pdev->dev); + + mutex_destroy(&isp->lock); + + return 0; +} + +static struct platform_driver rcar_isp_driver = { + .driver = { + .name = "rcar-isp", + .of_match_table = risp_of_id_table, + }, + .probe = risp_probe, + .remove = risp_remove, +}; + +module_platform_driver(rcar_isp_driver); + +MODULE_AUTHOR("Niklas Söderlund "); +MODULE_DESCRIPTION("Renesas R-Car ISP Channel Selector driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/rcar-vin/Kconfig b/drivers/media/platform/renesas/rcar-vin/Kconfig new file mode 100644 index 000000000..de55fe63d --- /dev/null +++ b/drivers/media/platform/renesas/rcar-vin/Kconfig @@ -0,0 +1,32 @@ +# SPDX-License-Identifier: GPL-2.0 +config VIDEO_RCAR_CSI2 + tristate "R-Car MIPI CSI-2 Receiver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + depends on ARCH_RENESAS || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select RESET_CONTROLLER + select V4L2_FWNODE + help + Support for Renesas R-Car MIPI CSI-2 receiver. + Supports R-Car Gen3 and RZ/G2 SoCs. + + To compile this driver as a module, choose M here: the + module will be called rcar-csi2. + +config VIDEO_RCAR_VIN + tristate "R-Car Video Input (VIN) Driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + depends on ARCH_RENESAS || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Support for Renesas R-Car Video Input (VIN) driver. + Supports R-Car Gen{2,3} and RZ/G{1,2} SoCs. + + To compile this driver as a module, choose M here: the + module will be called rcar-vin. diff --git a/drivers/media/platform/renesas/rcar-vin/Makefile b/drivers/media/platform/renesas/rcar-vin/Makefile new file mode 100644 index 000000000..00d809f5d --- /dev/null +++ b/drivers/media/platform/renesas/rcar-vin/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +rcar-vin-objs = rcar-core.o rcar-dma.o rcar-v4l2.o + +obj-$(CONFIG_VIDEO_RCAR_CSI2) += rcar-csi2.o +obj-$(CONFIG_VIDEO_RCAR_VIN) += rcar-vin.o diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c new file mode 100644 index 000000000..2f7daa853 --- /dev/null +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -0,0 +1,1466 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Renesas R-Car VIN + * + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., + * Copyright (C) 2008 Magnus Damm + * + * Based on the soc-camera rcar_vin driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "rcar-vin.h" + +/* + * The companion CSI-2 receiver driver (rcar-csi2) is known + * and we know it has one source pad (pad 0) and four sink + * pads (pad 1-4). So to translate a pad on the remote + * CSI-2 receiver to/from the VIN internal channel number simply + * subtract/add one from the pad/channel number. + */ +#define rvin_group_csi_pad_to_channel(pad) ((pad) - 1) +#define rvin_group_csi_channel_to_pad(channel) ((channel) + 1) + +/* + * Not all VINs are created equal, master VINs control the + * routing for other VIN's. We can figure out which VIN is + * master by looking at a VINs id. + */ +#define rvin_group_id_to_master(vin) ((vin) < 4 ? 0 : 4) + +#define v4l2_dev_to_vin(d) container_of(d, struct rvin_dev, v4l2_dev) + +/* ----------------------------------------------------------------------------- + * Gen3 Group Allocator + */ + +/* FIXME: This should if we find a system that supports more + * than one group for the whole system be replaced with a linked + * list of groups. And eventually all of this should be replaced + * with a global device allocator API. + * + * But for now this works as on all supported systems there will + * be only one group for all instances. + */ + +static DEFINE_MUTEX(rvin_group_lock); +static struct rvin_group *rvin_group_data; + +static void rvin_group_cleanup(struct rvin_group *group) +{ + media_device_cleanup(&group->mdev); + mutex_destroy(&group->lock); +} + +static int rvin_group_init(struct rvin_group *group, struct rvin_dev *vin, + int (*link_setup)(struct rvin_dev *), + const struct media_device_ops *ops) +{ + struct media_device *mdev = &group->mdev; + const struct of_device_id *match; + struct device_node *np; + + mutex_init(&group->lock); + + /* Count number of VINs in the system */ + group->count = 0; + for_each_matching_node(np, vin->dev->driver->of_match_table) + if (of_device_is_available(np)) + group->count++; + + vin_dbg(vin, "found %u enabled VIN's in DT", group->count); + + group->link_setup = link_setup; + + mdev->dev = vin->dev; + mdev->ops = ops; + + match = of_match_node(vin->dev->driver->of_match_table, + vin->dev->of_node); + + strscpy(mdev->driver_name, KBUILD_MODNAME, sizeof(mdev->driver_name)); + strscpy(mdev->model, match->compatible, sizeof(mdev->model)); + + media_device_init(mdev); + + return 0; +} + +static void rvin_group_release(struct kref *kref) +{ + struct rvin_group *group = + container_of(kref, struct rvin_group, refcount); + + mutex_lock(&rvin_group_lock); + + rvin_group_data = NULL; + + rvin_group_cleanup(group); + + kfree(group); + + mutex_unlock(&rvin_group_lock); +} + +static int rvin_group_get(struct rvin_dev *vin, + int (*link_setup)(struct rvin_dev *), + const struct media_device_ops *ops) +{ + struct rvin_group *group; + u32 id; + int ret; + + /* Make sure VIN id is present and sane */ + ret = of_property_read_u32(vin->dev->of_node, "renesas,id", &id); + if (ret) { + vin_err(vin, "%pOF: No renesas,id property found\n", + vin->dev->of_node); + return -EINVAL; + } + + if (id >= RCAR_VIN_NUM) { + vin_err(vin, "%pOF: Invalid renesas,id '%u'\n", + vin->dev->of_node, id); + return -EINVAL; + } + + /* Join or create a VIN group */ + mutex_lock(&rvin_group_lock); + if (rvin_group_data) { + group = rvin_group_data; + kref_get(&group->refcount); + } else { + group = kzalloc(sizeof(*group), GFP_KERNEL); + if (!group) { + ret = -ENOMEM; + goto err_group; + } + + ret = rvin_group_init(group, vin, link_setup, ops); + if (ret) { + kfree(group); + vin_err(vin, "Failed to initialize group\n"); + goto err_group; + } + + kref_init(&group->refcount); + + rvin_group_data = group; + } + mutex_unlock(&rvin_group_lock); + + /* Add VIN to group */ + mutex_lock(&group->lock); + + if (group->vin[id]) { + vin_err(vin, "Duplicate renesas,id property value %u\n", id); + mutex_unlock(&group->lock); + kref_put(&group->refcount, rvin_group_release); + return -EINVAL; + } + + group->vin[id] = vin; + + vin->id = id; + vin->group = group; + vin->v4l2_dev.mdev = &group->mdev; + + mutex_unlock(&group->lock); + + return 0; +err_group: + mutex_unlock(&rvin_group_lock); + return ret; +} + +static void rvin_group_put(struct rvin_dev *vin) +{ + struct rvin_group *group = vin->group; + + mutex_lock(&group->lock); + + vin->group = NULL; + vin->v4l2_dev.mdev = NULL; + + if (WARN_ON(group->vin[vin->id] != vin)) + goto out; + + group->vin[vin->id] = NULL; +out: + mutex_unlock(&group->lock); + + kref_put(&group->refcount, rvin_group_release); +} + +/* group lock should be held when calling this function. */ +static int rvin_group_entity_to_remote_id(struct rvin_group *group, + struct media_entity *entity) +{ + struct v4l2_subdev *sd; + unsigned int i; + + sd = media_entity_to_v4l2_subdev(entity); + + for (i = 0; i < RVIN_REMOTES_MAX; i++) + if (group->remotes[i].subdev == sd) + return i; + + return -ENODEV; +} + +static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + unsigned int i; + int ret; + + ret = media_device_register(&vin->group->mdev); + if (ret) + return ret; + + ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev); + if (ret) { + vin_err(vin, "Failed to register subdev nodes\n"); + return ret; + } + + /* Register all video nodes for the group. */ + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (vin->group->vin[i] && + !video_is_registered(&vin->group->vin[i]->vdev)) { + ret = rvin_v4l2_register(vin->group->vin[i]); + if (ret) + return ret; + } + } + + return vin->group->link_setup(vin); +} + +static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + unsigned int i; + + for (i = 0; i < RCAR_VIN_NUM; i++) + if (vin->group->vin[i]) + rvin_v4l2_unregister(vin->group->vin[i]); + + mutex_lock(&vin->group->lock); + + for (i = 0; i < RVIN_CSI_MAX; i++) { + if (vin->group->remotes[i].asd != asd) + continue; + vin->group->remotes[i].subdev = NULL; + vin_dbg(vin, "Unbind %s from slot %u\n", subdev->name, i); + break; + } + + mutex_unlock(&vin->group->lock); + + media_device_unregister(&vin->group->mdev); +} + +static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + unsigned int i; + + mutex_lock(&vin->group->lock); + + for (i = 0; i < RVIN_CSI_MAX; i++) { + if (vin->group->remotes[i].asd != asd) + continue; + vin->group->remotes[i].subdev = subdev; + vin_dbg(vin, "Bound %s to slot %u\n", subdev->name, i); + break; + } + + mutex_unlock(&vin->group->lock); + + return 0; +} + +static const struct v4l2_async_notifier_operations rvin_group_notify_ops = { + .bound = rvin_group_notify_bound, + .unbind = rvin_group_notify_unbind, + .complete = rvin_group_notify_complete, +}; + +static int rvin_group_parse_of(struct rvin_dev *vin, unsigned int port, + unsigned int id) +{ + struct fwnode_handle *ep, *fwnode; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; + struct v4l2_async_subdev *asd; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(vin->dev), port, id, 0); + if (!ep) + return 0; + + fwnode = fwnode_graph_get_remote_endpoint(ep); + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + fwnode_handle_put(ep); + if (ret) { + vin_err(vin, "Failed to parse %pOF\n", to_of_node(fwnode)); + ret = -EINVAL; + goto out; + } + + asd = v4l2_async_nf_add_fwnode(&vin->group->notifier, fwnode, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out; + } + + vin->group->remotes[vep.base.id].asd = asd; + + vin_dbg(vin, "Add group OF device %pOF to slot %u\n", + to_of_node(fwnode), vep.base.id); +out: + fwnode_handle_put(fwnode); + + return ret; +} + +static void rvin_group_notifier_cleanup(struct rvin_dev *vin) +{ + if (&vin->v4l2_dev == vin->group->notifier.v4l2_dev) { + v4l2_async_nf_unregister(&vin->group->notifier); + v4l2_async_nf_cleanup(&vin->group->notifier); + } +} + +static int rvin_group_notifier_init(struct rvin_dev *vin, unsigned int port, + unsigned int max_id) +{ + unsigned int count = 0, vin_mask = 0; + unsigned int i, id; + int ret; + + mutex_lock(&vin->group->lock); + + /* If not all VIN's are registered don't register the notifier. */ + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (vin->group->vin[i]) { + count++; + vin_mask |= BIT(i); + } + } + + if (vin->group->count != count) { + mutex_unlock(&vin->group->lock); + return 0; + } + + mutex_unlock(&vin->group->lock); + + v4l2_async_nf_init(&vin->group->notifier); + + /* + * Some subdevices may overlap but the parser function can handle it and + * each subdevice will only be registered once with the group notifier. + */ + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (!(vin_mask & BIT(i))) + continue; + + for (id = 0; id < max_id; id++) { + if (vin->group->remotes[id].asd) + continue; + + ret = rvin_group_parse_of(vin->group->vin[i], port, id); + if (ret) + return ret; + } + } + + if (list_empty(&vin->group->notifier.asd_list)) + return 0; + + vin->group->notifier.ops = &rvin_group_notify_ops; + ret = v4l2_async_nf_register(&vin->v4l2_dev, &vin->group->notifier); + if (ret < 0) { + vin_err(vin, "Notifier registration failed\n"); + v4l2_async_nf_cleanup(&vin->group->notifier); + return ret; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +static int rvin_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct rvin_dev *vin = + container_of(ctrl->handler, struct rvin_dev, ctrl_handler); + + switch (ctrl->id) { + case V4L2_CID_ALPHA_COMPONENT: + rvin_set_alpha(vin, ctrl->val); + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops rvin_ctrl_ops = { + .s_ctrl = rvin_s_ctrl, +}; + +static void rvin_free_controls(struct rvin_dev *vin) +{ + v4l2_ctrl_handler_free(&vin->ctrl_handler); + vin->vdev.ctrl_handler = NULL; +} + +static int rvin_create_controls(struct rvin_dev *vin, struct v4l2_subdev *subdev) +{ + int ret; + + ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16); + if (ret < 0) + return ret; + + /* The VIN directly deals with alpha component. */ + v4l2_ctrl_new_std(&vin->ctrl_handler, &rvin_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 255); + + if (vin->ctrl_handler.error) { + ret = vin->ctrl_handler.error; + rvin_free_controls(vin); + return ret; + } + + /* For the non-MC mode add controls from the subdevice. */ + if (subdev) { + ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, + subdev->ctrl_handler, NULL, true); + if (ret < 0) { + rvin_free_controls(vin); + return ret; + } + } + + vin->vdev.ctrl_handler = &vin->ctrl_handler; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Async notifier + */ + +static int rvin_find_pad(struct v4l2_subdev *sd, int direction) +{ + unsigned int pad; + + if (sd->entity.num_pads <= 1) + return 0; + + for (pad = 0; pad < sd->entity.num_pads; pad++) + if (sd->entity.pads[pad].flags & direction) + return pad; + + return -EINVAL; +} + +/* ----------------------------------------------------------------------------- + * Parallel async notifier + */ + +/* The vin lock should be held when calling the subdevice attach and detach */ +static int rvin_parallel_subdevice_attach(struct rvin_dev *vin, + struct v4l2_subdev *subdev) +{ + struct v4l2_subdev_mbus_code_enum code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + /* Find source and sink pad of remote subdevice */ + ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SOURCE); + if (ret < 0) + return ret; + vin->parallel.source_pad = ret; + + ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SINK); + vin->parallel.sink_pad = ret < 0 ? 0 : ret; + + if (vin->info->use_mc) { + vin->parallel.subdev = subdev; + return 0; + } + + /* Find compatible subdevices mbus format */ + vin->mbus_code = 0; + code.index = 0; + code.pad = vin->parallel.source_pad; + while (!vin->mbus_code && + !v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL, &code)) { + code.index++; + switch (code.code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_UYVY10_2X10: + case MEDIA_BUS_FMT_RGB888_1X24: + vin->mbus_code = code.code; + vin_dbg(vin, "Found media bus format for %s: %d\n", + subdev->name, vin->mbus_code); + break; + default: + break; + } + } + + if (!vin->mbus_code) { + vin_err(vin, "Unsupported media bus format for %s\n", + subdev->name); + return -EINVAL; + } + + /* Read tvnorms */ + ret = v4l2_subdev_call(subdev, video, g_tvnorms, &vin->vdev.tvnorms); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + /* Read standard */ + vin->std = V4L2_STD_UNKNOWN; + ret = v4l2_subdev_call(subdev, video, g_std, &vin->std); + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + /* Add the controls */ + ret = rvin_create_controls(vin, subdev); + if (ret < 0) + return ret; + + vin->parallel.subdev = subdev; + + return 0; +} + +static void rvin_parallel_subdevice_detach(struct rvin_dev *vin) +{ + rvin_v4l2_unregister(vin); + vin->parallel.subdev = NULL; + + if (!vin->info->use_mc) + rvin_free_controls(vin); +} + +static int rvin_parallel_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + struct media_entity *source; + struct media_entity *sink; + int ret; + + ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev); + if (ret < 0) { + vin_err(vin, "Failed to register subdev nodes\n"); + return ret; + } + + if (!video_is_registered(&vin->vdev)) { + ret = rvin_v4l2_register(vin); + if (ret < 0) + return ret; + } + + if (!vin->info->use_mc) + return 0; + + /* If we're running with media-controller, link the subdevs. */ + source = &vin->parallel.subdev->entity; + sink = &vin->vdev.entity; + + ret = media_create_pad_link(source, vin->parallel.source_pad, + sink, vin->parallel.sink_pad, 0); + if (ret) + vin_err(vin, "Error adding link from %s to %s: %d\n", + source->name, sink->name, ret); + + return ret; +} + +static void rvin_parallel_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + + vin_dbg(vin, "unbind parallel subdev %s\n", subdev->name); + + mutex_lock(&vin->lock); + rvin_parallel_subdevice_detach(vin); + mutex_unlock(&vin->lock); +} + +static int rvin_parallel_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); + int ret; + + mutex_lock(&vin->lock); + ret = rvin_parallel_subdevice_attach(vin, subdev); + mutex_unlock(&vin->lock); + if (ret) + return ret; + + v4l2_set_subdev_hostdata(subdev, vin); + + vin_dbg(vin, "bound subdev %s source pad: %u sink pad: %u\n", + subdev->name, vin->parallel.source_pad, + vin->parallel.sink_pad); + + return 0; +} + +static const struct v4l2_async_notifier_operations rvin_parallel_notify_ops = { + .bound = rvin_parallel_notify_bound, + .unbind = rvin_parallel_notify_unbind, + .complete = rvin_parallel_notify_complete, +}; + +static int rvin_parallel_parse_of(struct rvin_dev *vin) +{ + struct fwnode_handle *ep, *fwnode; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_UNKNOWN, + }; + struct v4l2_async_subdev *asd; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(vin->dev), 0, 0, 0); + if (!ep) + return 0; + + fwnode = fwnode_graph_get_remote_endpoint(ep); + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + fwnode_handle_put(ep); + if (ret) { + vin_err(vin, "Failed to parse %pOF\n", to_of_node(fwnode)); + ret = -EINVAL; + goto out; + } + + switch (vep.bus_type) { + case V4L2_MBUS_PARALLEL: + case V4L2_MBUS_BT656: + vin_dbg(vin, "Found %s media bus\n", + vep.bus_type == V4L2_MBUS_PARALLEL ? + "PARALLEL" : "BT656"); + vin->parallel.mbus_type = vep.bus_type; + vin->parallel.bus = vep.bus.parallel; + break; + default: + vin_err(vin, "Unknown media bus type\n"); + ret = -EINVAL; + goto out; + } + + asd = v4l2_async_nf_add_fwnode(&vin->notifier, fwnode, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out; + } + + vin->parallel.asd = asd; + + vin_dbg(vin, "Add parallel OF device %pOF\n", to_of_node(fwnode)); +out: + fwnode_handle_put(fwnode); + + return ret; +} + +static void rvin_parallel_cleanup(struct rvin_dev *vin) +{ + v4l2_async_nf_unregister(&vin->notifier); + v4l2_async_nf_cleanup(&vin->notifier); +} + +static int rvin_parallel_init(struct rvin_dev *vin) +{ + int ret; + + v4l2_async_nf_init(&vin->notifier); + + ret = rvin_parallel_parse_of(vin); + if (ret) + return ret; + + if (!vin->parallel.asd) + return -ENODEV; + + vin_dbg(vin, "Found parallel subdevice %pOF\n", + to_of_node(vin->parallel.asd->match.fwnode)); + + vin->notifier.ops = &rvin_parallel_notify_ops; + ret = v4l2_async_nf_register(&vin->v4l2_dev, &vin->notifier); + if (ret < 0) { + vin_err(vin, "Notifier registration failed\n"); + v4l2_async_nf_cleanup(&vin->notifier); + return ret; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * CSI-2 + */ + +/* + * Link setup for the links between a VIN and a CSI-2 receiver is a bit + * complex. The reason for this is that the register controlling routing + * is not present in each VIN instance. There are special VINs which + * control routing for themselves and other VINs. There are not many + * different possible links combinations that can be enabled at the same + * time, therefor all already enabled links which are controlled by a + * master VIN need to be taken into account when making the decision + * if a new link can be enabled or not. + * + * 1. Find out which VIN the link the user tries to enable is connected to. + * 2. Lookup which master VIN controls the links for this VIN. + * 3. Start with a bitmask with all bits set. + * 4. For each previously enabled link from the master VIN bitwise AND its + * route mask (see documentation for mask in struct rvin_group_route) + * with the bitmask. + * 5. Bitwise AND the mask for the link the user tries to enable to the bitmask. + * 6. If the bitmask is not empty at this point the new link can be enabled + * while keeping all previous links enabled. Update the CHSEL value of the + * master VIN and inform the user that the link could be enabled. + * + * Please note that no link can be enabled if any VIN in the group is + * currently open. + */ +static int rvin_csi2_link_notify(struct media_link *link, u32 flags, + unsigned int notification) +{ + struct rvin_group *group = container_of(link->graph_obj.mdev, + struct rvin_group, mdev); + struct media_entity *entity; + struct video_device *vdev; + struct rvin_dev *vin; + unsigned int i; + int csi_id, ret; + + ret = v4l2_pipeline_link_notify(link, flags, notification); + if (ret) + return ret; + + /* Only care about link enablement for VIN nodes. */ + if (!(flags & MEDIA_LNK_FL_ENABLED) || + !is_media_entity_v4l2_video_device(link->sink->entity)) + return 0; + + /* + * Don't allow link changes if any stream in the graph is active as + * modifying the CHSEL register fields can disrupt running streams. + */ + media_device_for_each_entity(entity, &group->mdev) + if (media_entity_is_streaming(entity)) + return -EBUSY; + + /* Find the master VIN that controls the routes. */ + vdev = media_entity_to_video_device(link->sink->entity); + vin = container_of(vdev, struct rvin_dev, vdev); + + mutex_lock(&group->lock); + + csi_id = rvin_group_entity_to_remote_id(group, link->source->entity); + if (csi_id == -ENODEV) { + struct v4l2_subdev *sd; + + /* + * Make sure the source entity subdevice is registered as + * a parallel input of one of the enabled VINs if it is not + * one of the CSI-2 subdevices. + * + * No hardware configuration required for parallel inputs, + * we can return here. + */ + sd = media_entity_to_v4l2_subdev(link->source->entity); + for (i = 0; i < RCAR_VIN_NUM; i++) { + if (group->vin[i] && + group->vin[i]->parallel.subdev == sd) { + group->vin[i]->is_csi = false; + ret = 0; + goto out; + } + } + + vin_err(vin, "Subdevice %s not registered to any VIN\n", + link->source->entity->name); + ret = -ENODEV; + } else { + const struct rvin_group_route *route; + unsigned int chsel = UINT_MAX; + unsigned int master_id; + + master_id = rvin_group_id_to_master(vin->id); + + if (WARN_ON(!group->vin[master_id])) { + ret = -ENODEV; + goto out; + } + + /* Make sure group is connected to same CSI-2 */ + for (i = master_id; i < master_id + 4; i++) { + struct media_pad *csi_pad; + + if (!group->vin[i]) + continue; + + /* Get remote CSI-2, if any. */ + csi_pad = media_pad_remote_pad_first( + &group->vin[i]->vdev.entity.pads[0]); + if (!csi_pad) + continue; + + if (csi_pad->entity != link->source->entity) { + vin_dbg(vin, "Already attached to %s\n", + csi_pad->entity->name); + ret = -EBUSY; + goto out; + } + } + + for (route = vin->info->routes; route->chsel; route++) { + if (route->master == master_id && route->csi == csi_id) { + chsel = route->chsel; + break; + } + } + + if (chsel == UINT_MAX) { + vin_err(vin, "No CHSEL value found\n"); + ret = -EINVAL; + goto out; + } + + ret = rvin_set_channel_routing(group->vin[master_id], chsel); + if (ret) + goto out; + + vin->is_csi = true; + } +out: + mutex_unlock(&group->lock); + + return ret; +} + +static const struct media_device_ops rvin_csi2_media_ops = { + .link_notify = rvin_csi2_link_notify, +}; + +static int rvin_csi2_create_link(struct rvin_group *group, unsigned int id, + const struct rvin_group_route *route) +{ + struct media_entity *source = &group->remotes[route->csi].subdev->entity; + struct media_entity *sink = &group->vin[id]->vdev.entity; + struct media_pad *sink_pad = &sink->pads[0]; + unsigned int channel; + int ret; + + for (channel = 0; channel < 4; channel++) { + unsigned int source_idx = rvin_group_csi_channel_to_pad(channel); + struct media_pad *source_pad = &source->pads[source_idx]; + + /* Skip if link already exists. */ + if (media_entity_find_link(source_pad, sink_pad)) + continue; + + ret = media_create_pad_link(source, source_idx, sink, 0, 0); + if (ret) + return ret; + } + + return 0; +} + +static int rvin_csi2_setup_links(struct rvin_dev *vin) +{ + const struct rvin_group_route *route; + unsigned int id; + int ret = -EINVAL; + + /* Create all media device links between VINs and CSI-2's. */ + mutex_lock(&vin->group->lock); + for (route = vin->info->routes; route->chsel; route++) { + /* Check that VIN' master is part of the group. */ + if (!vin->group->vin[route->master]) + continue; + + /* Check that CSI-2 is part of the group. */ + if (!vin->group->remotes[route->csi].subdev) + continue; + + for (id = route->master; id < route->master + 4; id++) { + /* Check that VIN is part of the group. */ + if (!vin->group->vin[id]) + continue; + + ret = rvin_csi2_create_link(vin->group, id, route); + if (ret) + goto out; + } + } +out: + mutex_unlock(&vin->group->lock); + + return ret; +} + +static void rvin_csi2_cleanup(struct rvin_dev *vin) +{ + rvin_parallel_cleanup(vin); + rvin_group_notifier_cleanup(vin); + rvin_group_put(vin); + rvin_free_controls(vin); +} + +static int rvin_csi2_init(struct rvin_dev *vin) +{ + int ret; + + vin->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad); + if (ret) + return ret; + + ret = rvin_create_controls(vin, NULL); + if (ret < 0) + return ret; + + ret = rvin_group_get(vin, rvin_csi2_setup_links, &rvin_csi2_media_ops); + if (ret) + goto err_controls; + + /* It's OK to not have a parallel subdevice. */ + ret = rvin_parallel_init(vin); + if (ret && ret != -ENODEV) + goto err_group; + + ret = rvin_group_notifier_init(vin, 1, RVIN_CSI_MAX); + if (ret) + goto err_parallel; + + return 0; +err_parallel: + rvin_parallel_cleanup(vin); +err_group: + rvin_group_put(vin); +err_controls: + rvin_free_controls(vin); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * ISP + */ + +static int rvin_isp_setup_links(struct rvin_dev *vin) +{ + unsigned int i; + int ret = -EINVAL; + + /* Create all media device links between VINs and ISP's. */ + mutex_lock(&vin->group->lock); + for (i = 0; i < RCAR_VIN_NUM; i++) { + struct media_pad *source_pad, *sink_pad; + struct media_entity *source, *sink; + unsigned int source_slot = i / 8; + unsigned int source_idx = i % 8 + 1; + + if (!vin->group->vin[i]) + continue; + + /* Check that ISP is part of the group. */ + if (!vin->group->remotes[source_slot].subdev) + continue; + + source = &vin->group->remotes[source_slot].subdev->entity; + source_pad = &source->pads[source_idx]; + + sink = &vin->group->vin[i]->vdev.entity; + sink_pad = &sink->pads[0]; + + /* Skip if link already exists. */ + if (media_entity_find_link(source_pad, sink_pad)) + continue; + + ret = media_create_pad_link(source, source_idx, sink, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + vin_err(vin, "Error adding link from %s to %s\n", + source->name, sink->name); + break; + } + } + mutex_unlock(&vin->group->lock); + + return ret; +} + +static void rvin_isp_cleanup(struct rvin_dev *vin) +{ + rvin_group_notifier_cleanup(vin); + rvin_group_put(vin); + rvin_free_controls(vin); +} + +static int rvin_isp_init(struct rvin_dev *vin) +{ + int ret; + + vin->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad); + if (ret) + return ret; + + ret = rvin_create_controls(vin, NULL); + if (ret < 0) + return ret; + + ret = rvin_group_get(vin, rvin_isp_setup_links, NULL); + if (ret) + goto err_controls; + + ret = rvin_group_notifier_init(vin, 2, RVIN_ISP_MAX); + if (ret) + goto err_group; + + return 0; +err_group: + rvin_group_put(vin); +err_controls: + rvin_free_controls(vin); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Suspend / Resume + */ + +static int __maybe_unused rvin_suspend(struct device *dev) +{ + struct rvin_dev *vin = dev_get_drvdata(dev); + + if (vin->state != RUNNING) + return 0; + + rvin_stop_streaming(vin); + + vin->state = SUSPENDED; + + return 0; +} + +static int __maybe_unused rvin_resume(struct device *dev) +{ + struct rvin_dev *vin = dev_get_drvdata(dev); + + if (vin->state != SUSPENDED) + return 0; + + /* + * Restore group master CHSEL setting. + * + * This needs to be done by every VIN resuming not only the master + * as we don't know if and in which order the master VINs will + * be resumed. + */ + if (vin->info->use_mc) { + unsigned int master_id = rvin_group_id_to_master(vin->id); + struct rvin_dev *master = vin->group->vin[master_id]; + int ret; + + if (WARN_ON(!master)) + return -ENODEV; + + ret = rvin_set_channel_routing(master, master->chsel); + if (ret) + return ret; + } + + return rvin_start_streaming(vin); +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver + */ + +static const struct rvin_info rcar_info_h1 = { + .model = RCAR_H1, + .use_mc = false, + .max_width = 2048, + .max_height = 2048, +}; + +static const struct rvin_info rcar_info_m1 = { + .model = RCAR_M1, + .use_mc = false, + .max_width = 2048, + .max_height = 2048, +}; + +static const struct rvin_info rcar_info_gen2 = { + .model = RCAR_GEN2, + .use_mc = false, + .max_width = 2048, + .max_height = 2048, +}; + +static const struct rvin_group_route rcar_info_r8a774e1_routes[] = { + { .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 }, + { .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a774e1 = { + .model = RCAR_GEN3, + .use_mc = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a774e1_routes, +}; + +static const struct rvin_group_route rcar_info_r8a7795_routes[] = { + { .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 }, + { .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 4, .csi = RVIN_CSI41, .chsel = 0x03 }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a7795 = { + .model = RCAR_GEN3, + .use_mc = true, + .nv12 = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a7795_routes, +}; + +static const struct rvin_group_route rcar_info_r8a7795es1_routes[] = { + { .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 0, .csi = RVIN_CSI21, .chsel = 0x05 }, + { .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 }, + { .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 4, .csi = RVIN_CSI21, .chsel = 0x05 }, + { .master = 4, .csi = RVIN_CSI41, .chsel = 0x03 }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a7795es1 = { + .model = RCAR_GEN3, + .use_mc = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a7795es1_routes, +}; + +static const struct rvin_group_route rcar_info_r8a7796_routes[] = { + { .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 }, + { .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 4, .csi = RVIN_CSI40, .chsel = 0x03 }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a7796 = { + .model = RCAR_GEN3, + .use_mc = true, + .nv12 = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a7796_routes, +}; + +static const struct rvin_group_route rcar_info_r8a77965_routes[] = { + { .master = 0, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 }, + { .master = 4, .csi = RVIN_CSI20, .chsel = 0x04 }, + { .master = 4, .csi = RVIN_CSI40, .chsel = 0x03 }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a77965 = { + .model = RCAR_GEN3, + .use_mc = true, + .nv12 = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a77965_routes, +}; + +static const struct rvin_group_route rcar_info_r8a77970_routes[] = { + { .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a77970 = { + .model = RCAR_GEN3, + .use_mc = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a77970_routes, +}; + +static const struct rvin_group_route rcar_info_r8a77980_routes[] = { + { .master = 0, .csi = RVIN_CSI40, .chsel = 0x03 }, + { .master = 4, .csi = RVIN_CSI41, .chsel = 0x03 }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a77980 = { + .model = RCAR_GEN3, + .use_mc = true, + .nv12 = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a77980_routes, +}; + +static const struct rvin_group_route rcar_info_r8a77990_routes[] = { + { .master = 4, .csi = RVIN_CSI40, .chsel = 0x03 }, + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a77990 = { + .model = RCAR_GEN3, + .use_mc = true, + .nv12 = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a77990_routes, +}; + +static const struct rvin_group_route rcar_info_r8a77995_routes[] = { + { /* Sentinel */ } +}; + +static const struct rvin_info rcar_info_r8a77995 = { + .model = RCAR_GEN3, + .use_mc = true, + .nv12 = true, + .max_width = 4096, + .max_height = 4096, + .routes = rcar_info_r8a77995_routes, +}; + +static const struct rvin_info rcar_info_r8a779a0 = { + .model = RCAR_GEN3, + .use_mc = true, + .use_isp = true, + .nv12 = true, + .max_width = 4096, + .max_height = 4096, +}; + +static const struct of_device_id rvin_of_id_table[] = { + { + .compatible = "renesas,vin-r8a774a1", + .data = &rcar_info_r8a7796, + }, + { + .compatible = "renesas,vin-r8a774b1", + .data = &rcar_info_r8a77965, + }, + { + .compatible = "renesas,vin-r8a774c0", + .data = &rcar_info_r8a77990, + }, + { + .compatible = "renesas,vin-r8a774e1", + .data = &rcar_info_r8a774e1, + }, + { + .compatible = "renesas,vin-r8a7778", + .data = &rcar_info_m1, + }, + { + .compatible = "renesas,vin-r8a7779", + .data = &rcar_info_h1, + }, + { + .compatible = "renesas,rcar-gen2-vin", + .data = &rcar_info_gen2, + }, + { + .compatible = "renesas,vin-r8a7795", + .data = &rcar_info_r8a7795, + }, + { + .compatible = "renesas,vin-r8a7796", + .data = &rcar_info_r8a7796, + }, + { + .compatible = "renesas,vin-r8a77961", + .data = &rcar_info_r8a7796, + }, + { + .compatible = "renesas,vin-r8a77965", + .data = &rcar_info_r8a77965, + }, + { + .compatible = "renesas,vin-r8a77970", + .data = &rcar_info_r8a77970, + }, + { + .compatible = "renesas,vin-r8a77980", + .data = &rcar_info_r8a77980, + }, + { + .compatible = "renesas,vin-r8a77990", + .data = &rcar_info_r8a77990, + }, + { + .compatible = "renesas,vin-r8a77995", + .data = &rcar_info_r8a77995, + }, + { + .compatible = "renesas,vin-r8a779a0", + .data = &rcar_info_r8a779a0, + }, + { /* Sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rvin_of_id_table); + +static const struct soc_device_attribute r8a7795es1[] = { + { + .soc_id = "r8a7795", .revision = "ES1.*", + .data = &rcar_info_r8a7795es1, + }, + { /* Sentinel */ } +}; + +static int rcar_vin_probe(struct platform_device *pdev) +{ + const struct soc_device_attribute *attr; + struct rvin_dev *vin; + int irq, ret; + + vin = devm_kzalloc(&pdev->dev, sizeof(*vin), GFP_KERNEL); + if (!vin) + return -ENOMEM; + + vin->dev = &pdev->dev; + vin->info = of_device_get_match_data(&pdev->dev); + vin->alpha = 0xff; + + /* + * Special care is needed on r8a7795 ES1.x since it + * uses different routing than r8a7795 ES2.0. + */ + attr = soc_device_match(r8a7795es1); + if (attr) + vin->info = attr->data; + + vin->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(vin->base)) + return PTR_ERR(vin->base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = rvin_dma_register(vin, irq); + if (ret) + return ret; + + platform_set_drvdata(pdev, vin); + + if (vin->info->use_isp) + ret = rvin_isp_init(vin); + else if (vin->info->use_mc) + ret = rvin_csi2_init(vin); + else + ret = rvin_parallel_init(vin); + + if (ret) { + rvin_dma_unregister(vin); + return ret; + } + + pm_suspend_ignore_children(&pdev->dev, true); + pm_runtime_enable(&pdev->dev); + + return 0; +} + +static int rcar_vin_remove(struct platform_device *pdev) +{ + struct rvin_dev *vin = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + rvin_v4l2_unregister(vin); + + if (vin->info->use_isp) + rvin_isp_cleanup(vin); + else if (vin->info->use_mc) + rvin_csi2_cleanup(vin); + else + rvin_parallel_cleanup(vin); + + rvin_dma_unregister(vin); + + return 0; +} + +static SIMPLE_DEV_PM_OPS(rvin_pm_ops, rvin_suspend, rvin_resume); + +static struct platform_driver rcar_vin_driver = { + .driver = { + .name = "rcar-vin", + .suppress_bind_attrs = true, + .pm = &rvin_pm_ops, + .of_match_table = rvin_of_id_table, + }, + .probe = rcar_vin_probe, + .remove = rcar_vin_remove, +}; + +module_platform_driver(rcar_vin_driver); + +MODULE_AUTHOR("Niklas Söderlund "); +MODULE_DESCRIPTION("Renesas R-Car VIN camera host driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-csi2.c b/drivers/media/platform/renesas/rcar-vin/rcar-csi2.c new file mode 100644 index 000000000..174aa6176 --- /dev/null +++ b/drivers/media/platform/renesas/rcar-vin/rcar-csi2.c @@ -0,0 +1,1606 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Renesas R-Car MIPI CSI-2 Receiver + * + * Copyright (C) 2018 Renesas Electronics Corp. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +struct rcar_csi2; + +/* Register offsets and bits */ + +/* Control Timing Select */ +#define TREF_REG 0x00 +#define TREF_TREF BIT(0) + +/* Software Reset */ +#define SRST_REG 0x04 +#define SRST_SRST BIT(0) + +/* PHY Operation Control */ +#define PHYCNT_REG 0x08 +#define PHYCNT_SHUTDOWNZ BIT(17) +#define PHYCNT_RSTZ BIT(16) +#define PHYCNT_ENABLECLK BIT(4) +#define PHYCNT_ENABLE_3 BIT(3) +#define PHYCNT_ENABLE_2 BIT(2) +#define PHYCNT_ENABLE_1 BIT(1) +#define PHYCNT_ENABLE_0 BIT(0) + +/* Checksum Control */ +#define CHKSUM_REG 0x0c +#define CHKSUM_ECC_EN BIT(1) +#define CHKSUM_CRC_EN BIT(0) + +/* + * Channel Data Type Select + * VCDT[0-15]: Channel 0 VCDT[16-31]: Channel 1 + * VCDT2[0-15]: Channel 2 VCDT2[16-31]: Channel 3 + */ +#define VCDT_REG 0x10 +#define VCDT2_REG 0x14 +#define VCDT_VCDTN_EN BIT(15) +#define VCDT_SEL_VC(n) (((n) & 0x3) << 8) +#define VCDT_SEL_DTN_ON BIT(6) +#define VCDT_SEL_DT(n) (((n) & 0x3f) << 0) + +/* Frame Data Type Select */ +#define FRDT_REG 0x18 + +/* Field Detection Control */ +#define FLD_REG 0x1c +#define FLD_FLD_NUM(n) (((n) & 0xff) << 16) +#define FLD_DET_SEL(n) (((n) & 0x3) << 4) +#define FLD_FLD_EN4 BIT(3) +#define FLD_FLD_EN3 BIT(2) +#define FLD_FLD_EN2 BIT(1) +#define FLD_FLD_EN BIT(0) + +/* Automatic Standby Control */ +#define ASTBY_REG 0x20 + +/* Long Data Type Setting 0 */ +#define LNGDT0_REG 0x28 + +/* Long Data Type Setting 1 */ +#define LNGDT1_REG 0x2c + +/* Interrupt Enable */ +#define INTEN_REG 0x30 +#define INTEN_INT_AFIFO_OF BIT(27) +#define INTEN_INT_ERRSOTHS BIT(4) +#define INTEN_INT_ERRSOTSYNCHS BIT(3) + +/* Interrupt Source Mask */ +#define INTCLOSE_REG 0x34 + +/* Interrupt Status Monitor */ +#define INTSTATE_REG 0x38 +#define INTSTATE_INT_ULPS_START BIT(7) +#define INTSTATE_INT_ULPS_END BIT(6) + +/* Interrupt Error Status Monitor */ +#define INTERRSTATE_REG 0x3c + +/* Short Packet Data */ +#define SHPDAT_REG 0x40 + +/* Short Packet Count */ +#define SHPCNT_REG 0x44 + +/* LINK Operation Control */ +#define LINKCNT_REG 0x48 +#define LINKCNT_MONITOR_EN BIT(31) +#define LINKCNT_REG_MONI_PACT_EN BIT(25) +#define LINKCNT_ICLK_NONSTOP BIT(24) + +/* Lane Swap */ +#define LSWAP_REG 0x4c +#define LSWAP_L3SEL(n) (((n) & 0x3) << 6) +#define LSWAP_L2SEL(n) (((n) & 0x3) << 4) +#define LSWAP_L1SEL(n) (((n) & 0x3) << 2) +#define LSWAP_L0SEL(n) (((n) & 0x3) << 0) + +/* PHY Test Interface Write Register */ +#define PHTW_REG 0x50 +#define PHTW_DWEN BIT(24) +#define PHTW_TESTDIN_DATA(n) (((n & 0xff)) << 16) +#define PHTW_CWEN BIT(8) +#define PHTW_TESTDIN_CODE(n) ((n & 0xff)) + +#define PHYFRX_REG 0x64 +#define PHYFRX_FORCERX_MODE_3 BIT(3) +#define PHYFRX_FORCERX_MODE_2 BIT(2) +#define PHYFRX_FORCERX_MODE_1 BIT(1) +#define PHYFRX_FORCERX_MODE_0 BIT(0) + +struct phtw_value { + u16 data; + u16 code; +}; + +struct rcsi2_mbps_reg { + u16 mbps; + u16 reg; +}; + +static const struct rcsi2_mbps_reg phtw_mbps_v3u[] = { + { .mbps = 1500, .reg = 0xcc }, + { .mbps = 1550, .reg = 0x1d }, + { .mbps = 1600, .reg = 0x27 }, + { .mbps = 1650, .reg = 0x30 }, + { .mbps = 1700, .reg = 0x39 }, + { .mbps = 1750, .reg = 0x42 }, + { .mbps = 1800, .reg = 0x4b }, + { .mbps = 1850, .reg = 0x55 }, + { .mbps = 1900, .reg = 0x5e }, + { .mbps = 1950, .reg = 0x67 }, + { .mbps = 2000, .reg = 0x71 }, + { .mbps = 2050, .reg = 0x79 }, + { .mbps = 2100, .reg = 0x83 }, + { .mbps = 2150, .reg = 0x8c }, + { .mbps = 2200, .reg = 0x95 }, + { .mbps = 2250, .reg = 0x9e }, + { .mbps = 2300, .reg = 0xa7 }, + { .mbps = 2350, .reg = 0xb0 }, + { .mbps = 2400, .reg = 0xba }, + { .mbps = 2450, .reg = 0xc3 }, + { .mbps = 2500, .reg = 0xcc }, + { /* sentinel */ }, +}; + +static const struct rcsi2_mbps_reg phtw_mbps_h3_v3h_m3n[] = { + { .mbps = 80, .reg = 0x86 }, + { .mbps = 90, .reg = 0x86 }, + { .mbps = 100, .reg = 0x87 }, + { .mbps = 110, .reg = 0x87 }, + { .mbps = 120, .reg = 0x88 }, + { .mbps = 130, .reg = 0x88 }, + { .mbps = 140, .reg = 0x89 }, + { .mbps = 150, .reg = 0x89 }, + { .mbps = 160, .reg = 0x8a }, + { .mbps = 170, .reg = 0x8a }, + { .mbps = 180, .reg = 0x8b }, + { .mbps = 190, .reg = 0x8b }, + { .mbps = 205, .reg = 0x8c }, + { .mbps = 220, .reg = 0x8d }, + { .mbps = 235, .reg = 0x8e }, + { .mbps = 250, .reg = 0x8e }, + { /* sentinel */ }, +}; + +static const struct rcsi2_mbps_reg phtw_mbps_v3m_e3[] = { + { .mbps = 80, .reg = 0x00 }, + { .mbps = 90, .reg = 0x20 }, + { .mbps = 100, .reg = 0x40 }, + { .mbps = 110, .reg = 0x02 }, + { .mbps = 130, .reg = 0x22 }, + { .mbps = 140, .reg = 0x42 }, + { .mbps = 150, .reg = 0x04 }, + { .mbps = 170, .reg = 0x24 }, + { .mbps = 180, .reg = 0x44 }, + { .mbps = 200, .reg = 0x06 }, + { .mbps = 220, .reg = 0x26 }, + { .mbps = 240, .reg = 0x46 }, + { .mbps = 250, .reg = 0x08 }, + { .mbps = 270, .reg = 0x28 }, + { .mbps = 300, .reg = 0x0a }, + { .mbps = 330, .reg = 0x2a }, + { .mbps = 360, .reg = 0x4a }, + { .mbps = 400, .reg = 0x0c }, + { .mbps = 450, .reg = 0x2c }, + { .mbps = 500, .reg = 0x0e }, + { .mbps = 550, .reg = 0x2e }, + { .mbps = 600, .reg = 0x10 }, + { .mbps = 650, .reg = 0x30 }, + { .mbps = 700, .reg = 0x12 }, + { .mbps = 750, .reg = 0x32 }, + { .mbps = 800, .reg = 0x52 }, + { .mbps = 850, .reg = 0x72 }, + { .mbps = 900, .reg = 0x14 }, + { .mbps = 950, .reg = 0x34 }, + { .mbps = 1000, .reg = 0x54 }, + { .mbps = 1050, .reg = 0x74 }, + { .mbps = 1125, .reg = 0x16 }, + { /* sentinel */ }, +}; + +/* PHY Test Interface Clear */ +#define PHTC_REG 0x58 +#define PHTC_TESTCLR BIT(0) + +/* PHY Frequency Control */ +#define PHYPLL_REG 0x68 +#define PHYPLL_HSFREQRANGE(n) ((n) << 16) + +static const struct rcsi2_mbps_reg hsfreqrange_v3u[] = { + { .mbps = 80, .reg = 0x00 }, + { .mbps = 90, .reg = 0x10 }, + { .mbps = 100, .reg = 0x20 }, + { .mbps = 110, .reg = 0x30 }, + { .mbps = 120, .reg = 0x01 }, + { .mbps = 130, .reg = 0x11 }, + { .mbps = 140, .reg = 0x21 }, + { .mbps = 150, .reg = 0x31 }, + { .mbps = 160, .reg = 0x02 }, + { .mbps = 170, .reg = 0x12 }, + { .mbps = 180, .reg = 0x22 }, + { .mbps = 190, .reg = 0x32 }, + { .mbps = 205, .reg = 0x03 }, + { .mbps = 220, .reg = 0x13 }, + { .mbps = 235, .reg = 0x23 }, + { .mbps = 250, .reg = 0x33 }, + { .mbps = 275, .reg = 0x04 }, + { .mbps = 300, .reg = 0x14 }, + { .mbps = 325, .reg = 0x25 }, + { .mbps = 350, .reg = 0x35 }, + { .mbps = 400, .reg = 0x05 }, + { .mbps = 450, .reg = 0x16 }, + { .mbps = 500, .reg = 0x26 }, + { .mbps = 550, .reg = 0x37 }, + { .mbps = 600, .reg = 0x07 }, + { .mbps = 650, .reg = 0x18 }, + { .mbps = 700, .reg = 0x28 }, + { .mbps = 750, .reg = 0x39 }, + { .mbps = 800, .reg = 0x09 }, + { .mbps = 850, .reg = 0x19 }, + { .mbps = 900, .reg = 0x29 }, + { .mbps = 950, .reg = 0x3a }, + { .mbps = 1000, .reg = 0x0a }, + { .mbps = 1050, .reg = 0x1a }, + { .mbps = 1100, .reg = 0x2a }, + { .mbps = 1150, .reg = 0x3b }, + { .mbps = 1200, .reg = 0x0b }, + { .mbps = 1250, .reg = 0x1b }, + { .mbps = 1300, .reg = 0x2b }, + { .mbps = 1350, .reg = 0x3c }, + { .mbps = 1400, .reg = 0x0c }, + { .mbps = 1450, .reg = 0x1c }, + { .mbps = 1500, .reg = 0x2c }, + { .mbps = 1550, .reg = 0x3d }, + { .mbps = 1600, .reg = 0x0d }, + { .mbps = 1650, .reg = 0x1d }, + { .mbps = 1700, .reg = 0x2e }, + { .mbps = 1750, .reg = 0x3e }, + { .mbps = 1800, .reg = 0x0e }, + { .mbps = 1850, .reg = 0x1e }, + { .mbps = 1900, .reg = 0x2f }, + { .mbps = 1950, .reg = 0x3f }, + { .mbps = 2000, .reg = 0x0f }, + { .mbps = 2050, .reg = 0x40 }, + { .mbps = 2100, .reg = 0x41 }, + { .mbps = 2150, .reg = 0x42 }, + { .mbps = 2200, .reg = 0x43 }, + { .mbps = 2300, .reg = 0x45 }, + { .mbps = 2350, .reg = 0x46 }, + { .mbps = 2400, .reg = 0x47 }, + { .mbps = 2450, .reg = 0x48 }, + { .mbps = 2500, .reg = 0x49 }, + { /* sentinel */ }, +}; + +static const struct rcsi2_mbps_reg hsfreqrange_h3_v3h_m3n[] = { + { .mbps = 80, .reg = 0x00 }, + { .mbps = 90, .reg = 0x10 }, + { .mbps = 100, .reg = 0x20 }, + { .mbps = 110, .reg = 0x30 }, + { .mbps = 120, .reg = 0x01 }, + { .mbps = 130, .reg = 0x11 }, + { .mbps = 140, .reg = 0x21 }, + { .mbps = 150, .reg = 0x31 }, + { .mbps = 160, .reg = 0x02 }, + { .mbps = 170, .reg = 0x12 }, + { .mbps = 180, .reg = 0x22 }, + { .mbps = 190, .reg = 0x32 }, + { .mbps = 205, .reg = 0x03 }, + { .mbps = 220, .reg = 0x13 }, + { .mbps = 235, .reg = 0x23 }, + { .mbps = 250, .reg = 0x33 }, + { .mbps = 275, .reg = 0x04 }, + { .mbps = 300, .reg = 0x14 }, + { .mbps = 325, .reg = 0x25 }, + { .mbps = 350, .reg = 0x35 }, + { .mbps = 400, .reg = 0x05 }, + { .mbps = 450, .reg = 0x16 }, + { .mbps = 500, .reg = 0x26 }, + { .mbps = 550, .reg = 0x37 }, + { .mbps = 600, .reg = 0x07 }, + { .mbps = 650, .reg = 0x18 }, + { .mbps = 700, .reg = 0x28 }, + { .mbps = 750, .reg = 0x39 }, + { .mbps = 800, .reg = 0x09 }, + { .mbps = 850, .reg = 0x19 }, + { .mbps = 900, .reg = 0x29 }, + { .mbps = 950, .reg = 0x3a }, + { .mbps = 1000, .reg = 0x0a }, + { .mbps = 1050, .reg = 0x1a }, + { .mbps = 1100, .reg = 0x2a }, + { .mbps = 1150, .reg = 0x3b }, + { .mbps = 1200, .reg = 0x0b }, + { .mbps = 1250, .reg = 0x1b }, + { .mbps = 1300, .reg = 0x2b }, + { .mbps = 1350, .reg = 0x3c }, + { .mbps = 1400, .reg = 0x0c }, + { .mbps = 1450, .reg = 0x1c }, + { .mbps = 1500, .reg = 0x2c }, + { /* sentinel */ }, +}; + +static const struct rcsi2_mbps_reg hsfreqrange_m3w_h3es1[] = { + { .mbps = 80, .reg = 0x00 }, + { .mbps = 90, .reg = 0x10 }, + { .mbps = 100, .reg = 0x20 }, + { .mbps = 110, .reg = 0x30 }, + { .mbps = 120, .reg = 0x01 }, + { .mbps = 130, .reg = 0x11 }, + { .mbps = 140, .reg = 0x21 }, + { .mbps = 150, .reg = 0x31 }, + { .mbps = 160, .reg = 0x02 }, + { .mbps = 170, .reg = 0x12 }, + { .mbps = 180, .reg = 0x22 }, + { .mbps = 190, .reg = 0x32 }, + { .mbps = 205, .reg = 0x03 }, + { .mbps = 220, .reg = 0x13 }, + { .mbps = 235, .reg = 0x23 }, + { .mbps = 250, .reg = 0x33 }, + { .mbps = 275, .reg = 0x04 }, + { .mbps = 300, .reg = 0x14 }, + { .mbps = 325, .reg = 0x05 }, + { .mbps = 350, .reg = 0x15 }, + { .mbps = 400, .reg = 0x25 }, + { .mbps = 450, .reg = 0x06 }, + { .mbps = 500, .reg = 0x16 }, + { .mbps = 550, .reg = 0x07 }, + { .mbps = 600, .reg = 0x17 }, + { .mbps = 650, .reg = 0x08 }, + { .mbps = 700, .reg = 0x18 }, + { .mbps = 750, .reg = 0x09 }, + { .mbps = 800, .reg = 0x19 }, + { .mbps = 850, .reg = 0x29 }, + { .mbps = 900, .reg = 0x39 }, + { .mbps = 950, .reg = 0x0a }, + { .mbps = 1000, .reg = 0x1a }, + { .mbps = 1050, .reg = 0x2a }, + { .mbps = 1100, .reg = 0x3a }, + { .mbps = 1150, .reg = 0x0b }, + { .mbps = 1200, .reg = 0x1b }, + { .mbps = 1250, .reg = 0x2b }, + { .mbps = 1300, .reg = 0x3b }, + { .mbps = 1350, .reg = 0x0c }, + { .mbps = 1400, .reg = 0x1c }, + { .mbps = 1450, .reg = 0x2c }, + { .mbps = 1500, .reg = 0x3c }, + { /* sentinel */ }, +}; + +/* PHY ESC Error Monitor */ +#define PHEERM_REG 0x74 + +/* PHY Clock Lane Monitor */ +#define PHCLM_REG 0x78 +#define PHCLM_STOPSTATECKL BIT(0) + +/* PHY Data Lane Monitor */ +#define PHDLM_REG 0x7c + +/* CSI0CLK Frequency Configuration Preset Register */ +#define CSI0CLKFCPR_REG 0x260 +#define CSI0CLKFREQRANGE(n) ((n & 0x3f) << 16) + +struct rcar_csi2_format { + u32 code; + unsigned int datatype; + unsigned int bpp; +}; + +static const struct rcar_csi2_format rcar_csi2_formats[] = { + { + .code = MEDIA_BUS_FMT_RGB888_1X24, + .datatype = MIPI_CSI2_DT_RGB888, + .bpp = 24, + }, { + .code = MEDIA_BUS_FMT_UYVY8_1X16, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .bpp = 16, + }, { + .code = MEDIA_BUS_FMT_YUYV8_1X16, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .bpp = 16, + }, { + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .bpp = 16, + }, { + .code = MEDIA_BUS_FMT_YUYV10_2X10, + .datatype = MIPI_CSI2_DT_YUV422_8B, + .bpp = 20, + }, { + .code = MEDIA_BUS_FMT_Y10_1X10, + .datatype = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, { + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .datatype = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, { + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .datatype = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .datatype = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, { + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .datatype = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, { + .code = MEDIA_BUS_FMT_Y8_1X8, + .datatype = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, +}; + +static const struct rcar_csi2_format *rcsi2_code_to_fmt(unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rcar_csi2_formats); i++) + if (rcar_csi2_formats[i].code == code) + return &rcar_csi2_formats[i]; + + return NULL; +} + +enum rcar_csi2_pads { + RCAR_CSI2_SINK, + RCAR_CSI2_SOURCE_VC0, + RCAR_CSI2_SOURCE_VC1, + RCAR_CSI2_SOURCE_VC2, + RCAR_CSI2_SOURCE_VC3, + NR_OF_RCAR_CSI2_PAD, +}; + +struct rcar_csi2_info { + int (*init_phtw)(struct rcar_csi2 *priv, unsigned int mbps); + int (*phy_post_init)(struct rcar_csi2 *priv); + const struct rcsi2_mbps_reg *hsfreqrange; + unsigned int csi0clkfreqrange; + unsigned int num_channels; + bool clear_ulps; + bool use_isp; +}; + +struct rcar_csi2 { + struct device *dev; + void __iomem *base; + const struct rcar_csi2_info *info; + struct reset_control *rstc; + + struct v4l2_subdev subdev; + struct media_pad pads[NR_OF_RCAR_CSI2_PAD]; + + struct v4l2_async_notifier notifier; + struct v4l2_subdev *remote; + unsigned int remote_pad; + + int channel_vc[4]; + + struct mutex lock; /* Protects mf and stream_count. */ + struct v4l2_mbus_framefmt mf; + int stream_count; + + unsigned short lanes; + unsigned char lane_swap[4]; +}; + +static inline struct rcar_csi2 *sd_to_csi2(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rcar_csi2, subdev); +} + +static inline struct rcar_csi2 *notifier_to_csi2(struct v4l2_async_notifier *n) +{ + return container_of(n, struct rcar_csi2, notifier); +} + +static u32 rcsi2_read(struct rcar_csi2 *priv, unsigned int reg) +{ + return ioread32(priv->base + reg); +} + +static void rcsi2_write(struct rcar_csi2 *priv, unsigned int reg, u32 data) +{ + iowrite32(data, priv->base + reg); +} + +static void rcsi2_enter_standby(struct rcar_csi2 *priv) +{ + rcsi2_write(priv, PHYCNT_REG, 0); + rcsi2_write(priv, PHTC_REG, PHTC_TESTCLR); + reset_control_assert(priv->rstc); + usleep_range(100, 150); + pm_runtime_put(priv->dev); +} + +static int rcsi2_exit_standby(struct rcar_csi2 *priv) +{ + int ret; + + ret = pm_runtime_resume_and_get(priv->dev); + if (ret < 0) + return ret; + + reset_control_deassert(priv->rstc); + + return 0; +} + +static int rcsi2_wait_phy_start(struct rcar_csi2 *priv, + unsigned int lanes) +{ + unsigned int timeout; + + /* Wait for the clock and data lanes to enter LP-11 state. */ + for (timeout = 0; timeout <= 20; timeout++) { + const u32 lane_mask = (1 << lanes) - 1; + + if ((rcsi2_read(priv, PHCLM_REG) & PHCLM_STOPSTATECKL) && + (rcsi2_read(priv, PHDLM_REG) & lane_mask) == lane_mask) + return 0; + + usleep_range(1000, 2000); + } + + dev_err(priv->dev, "Timeout waiting for LP-11 state\n"); + + return -ETIMEDOUT; +} + +static int rcsi2_set_phypll(struct rcar_csi2 *priv, unsigned int mbps) +{ + const struct rcsi2_mbps_reg *hsfreq; + const struct rcsi2_mbps_reg *hsfreq_prev = NULL; + + if (mbps < priv->info->hsfreqrange->mbps) + dev_warn(priv->dev, "%u Mbps less than min PHY speed %u Mbps", + mbps, priv->info->hsfreqrange->mbps); + + for (hsfreq = priv->info->hsfreqrange; hsfreq->mbps != 0; hsfreq++) { + if (hsfreq->mbps >= mbps) + break; + hsfreq_prev = hsfreq; + } + + if (!hsfreq->mbps) { + dev_err(priv->dev, "Unsupported PHY speed (%u Mbps)", mbps); + return -ERANGE; + } + + if (hsfreq_prev && + ((mbps - hsfreq_prev->mbps) <= (hsfreq->mbps - mbps))) + hsfreq = hsfreq_prev; + + rcsi2_write(priv, PHYPLL_REG, PHYPLL_HSFREQRANGE(hsfreq->reg)); + + return 0; +} + +static int rcsi2_calc_mbps(struct rcar_csi2 *priv, unsigned int bpp, + unsigned int lanes) +{ + struct v4l2_subdev *source; + struct v4l2_ctrl *ctrl; + u64 mbps; + + if (!priv->remote) + return -ENODEV; + + source = priv->remote; + + /* Read the pixel rate control from remote. */ + ctrl = v4l2_ctrl_find(source->ctrl_handler, V4L2_CID_PIXEL_RATE); + if (!ctrl) { + dev_err(priv->dev, "no pixel rate control in subdev %s\n", + source->name); + return -EINVAL; + } + + /* + * Calculate the phypll in mbps. + * link_freq = (pixel_rate * bits_per_sample) / (2 * nr_of_lanes) + * bps = link_freq * 2 + */ + mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * bpp; + do_div(mbps, lanes * 1000000); + + return mbps; +} + +static int rcsi2_get_active_lanes(struct rcar_csi2 *priv, + unsigned int *lanes) +{ + struct v4l2_mbus_config mbus_config = { 0 }; + int ret; + + *lanes = priv->lanes; + + ret = v4l2_subdev_call(priv->remote, pad, get_mbus_config, + priv->remote_pad, &mbus_config); + if (ret == -ENOIOCTLCMD) { + dev_dbg(priv->dev, "No remote mbus configuration available\n"); + return 0; + } + + if (ret) { + dev_err(priv->dev, "Failed to get remote mbus configuration\n"); + return ret; + } + + if (mbus_config.type != V4L2_MBUS_CSI2_DPHY) { + dev_err(priv->dev, "Unsupported media bus type %u\n", + mbus_config.type); + return -EINVAL; + } + + if (mbus_config.bus.mipi_csi2.num_data_lanes > priv->lanes) { + dev_err(priv->dev, + "Unsupported mbus config: too many data lanes %u\n", + mbus_config.bus.mipi_csi2.num_data_lanes); + return -EINVAL; + } + + *lanes = mbus_config.bus.mipi_csi2.num_data_lanes; + + return 0; +} + +static int rcsi2_start_receiver(struct rcar_csi2 *priv) +{ + const struct rcar_csi2_format *format; + u32 phycnt, vcdt = 0, vcdt2 = 0, fld = 0; + unsigned int lanes; + unsigned int i; + int mbps, ret; + + dev_dbg(priv->dev, "Input size (%ux%u%c)\n", + priv->mf.width, priv->mf.height, + priv->mf.field == V4L2_FIELD_NONE ? 'p' : 'i'); + + /* Code is validated in set_fmt. */ + format = rcsi2_code_to_fmt(priv->mf.code); + if (!format) + return -EINVAL; + + /* + * Enable all supported CSI-2 channels with virtual channel and + * data type matching. + * + * NOTE: It's not possible to get individual datatype for each + * source virtual channel. Once this is possible in V4L2 + * it should be used here. + */ + for (i = 0; i < priv->info->num_channels; i++) { + u32 vcdt_part; + + if (priv->channel_vc[i] < 0) + continue; + + vcdt_part = VCDT_SEL_VC(priv->channel_vc[i]) | VCDT_VCDTN_EN | + VCDT_SEL_DTN_ON | VCDT_SEL_DT(format->datatype); + + /* Store in correct reg and offset. */ + if (i < 2) + vcdt |= vcdt_part << ((i % 2) * 16); + else + vcdt2 |= vcdt_part << ((i % 2) * 16); + } + + if (priv->mf.field == V4L2_FIELD_ALTERNATE) { + fld = FLD_DET_SEL(1) | FLD_FLD_EN4 | FLD_FLD_EN3 | FLD_FLD_EN2 + | FLD_FLD_EN; + + if (priv->mf.height == 240) + fld |= FLD_FLD_NUM(0); + else + fld |= FLD_FLD_NUM(1); + } + + /* + * Get the number of active data lanes inspecting the remote mbus + * configuration. + */ + ret = rcsi2_get_active_lanes(priv, &lanes); + if (ret) + return ret; + + phycnt = PHYCNT_ENABLECLK; + phycnt |= (1 << lanes) - 1; + + mbps = rcsi2_calc_mbps(priv, format->bpp, lanes); + if (mbps < 0) + return mbps; + + /* Enable interrupts. */ + rcsi2_write(priv, INTEN_REG, INTEN_INT_AFIFO_OF | INTEN_INT_ERRSOTHS + | INTEN_INT_ERRSOTSYNCHS); + + /* Init */ + rcsi2_write(priv, TREF_REG, TREF_TREF); + rcsi2_write(priv, PHTC_REG, 0); + + /* Configure */ + if (!priv->info->use_isp) { + rcsi2_write(priv, VCDT_REG, vcdt); + if (vcdt2) + rcsi2_write(priv, VCDT2_REG, vcdt2); + } + + /* Lanes are zero indexed. */ + rcsi2_write(priv, LSWAP_REG, + LSWAP_L0SEL(priv->lane_swap[0] - 1) | + LSWAP_L1SEL(priv->lane_swap[1] - 1) | + LSWAP_L2SEL(priv->lane_swap[2] - 1) | + LSWAP_L3SEL(priv->lane_swap[3] - 1)); + + /* Start */ + if (priv->info->init_phtw) { + ret = priv->info->init_phtw(priv, mbps); + if (ret) + return ret; + } + + if (priv->info->hsfreqrange) { + ret = rcsi2_set_phypll(priv, mbps); + if (ret) + return ret; + } + + if (priv->info->csi0clkfreqrange) + rcsi2_write(priv, CSI0CLKFCPR_REG, + CSI0CLKFREQRANGE(priv->info->csi0clkfreqrange)); + + if (priv->info->use_isp) + rcsi2_write(priv, PHYFRX_REG, + PHYFRX_FORCERX_MODE_3 | PHYFRX_FORCERX_MODE_2 | + PHYFRX_FORCERX_MODE_1 | PHYFRX_FORCERX_MODE_0); + + rcsi2_write(priv, PHYCNT_REG, phycnt); + rcsi2_write(priv, LINKCNT_REG, LINKCNT_MONITOR_EN | + LINKCNT_REG_MONI_PACT_EN | LINKCNT_ICLK_NONSTOP); + rcsi2_write(priv, FLD_REG, fld); + rcsi2_write(priv, PHYCNT_REG, phycnt | PHYCNT_SHUTDOWNZ); + rcsi2_write(priv, PHYCNT_REG, phycnt | PHYCNT_SHUTDOWNZ | PHYCNT_RSTZ); + + ret = rcsi2_wait_phy_start(priv, lanes); + if (ret) + return ret; + + if (priv->info->use_isp) + rcsi2_write(priv, PHYFRX_REG, 0); + + /* Run post PHY start initialization, if needed. */ + if (priv->info->phy_post_init) { + ret = priv->info->phy_post_init(priv); + if (ret) + return ret; + } + + /* Clear Ultra Low Power interrupt. */ + if (priv->info->clear_ulps) + rcsi2_write(priv, INTSTATE_REG, + INTSTATE_INT_ULPS_START | + INTSTATE_INT_ULPS_END); + return 0; +} + +static int rcsi2_start(struct rcar_csi2 *priv) +{ + int ret; + + ret = rcsi2_exit_standby(priv); + if (ret < 0) + return ret; + + ret = rcsi2_start_receiver(priv); + if (ret) { + rcsi2_enter_standby(priv); + return ret; + } + + ret = v4l2_subdev_call(priv->remote, video, s_stream, 1); + if (ret) { + rcsi2_enter_standby(priv); + return ret; + } + + return 0; +} + +static void rcsi2_stop(struct rcar_csi2 *priv) +{ + rcsi2_enter_standby(priv); + v4l2_subdev_call(priv->remote, video, s_stream, 0); +} + +static int rcsi2_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + int ret = 0; + + mutex_lock(&priv->lock); + + if (!priv->remote) { + ret = -ENODEV; + goto out; + } + + if (enable && priv->stream_count == 0) { + ret = rcsi2_start(priv); + if (ret) + goto out; + } else if (!enable && priv->stream_count == 1) { + rcsi2_stop(priv); + } + + priv->stream_count += enable ? 1 : -1; +out: + mutex_unlock(&priv->lock); + + return ret; +} + +static int rcsi2_set_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + struct v4l2_mbus_framefmt *framefmt; + + mutex_lock(&priv->lock); + + if (!rcsi2_code_to_fmt(format->format.code)) + format->format.code = rcar_csi2_formats[0].code; + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) { + priv->mf = format->format; + } else { + framefmt = v4l2_subdev_get_try_format(sd, sd_state, 0); + *framefmt = format->format; + } + + mutex_unlock(&priv->lock); + + return 0; +} + +static int rcsi2_get_pad_format(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct rcar_csi2 *priv = sd_to_csi2(sd); + + mutex_lock(&priv->lock); + + if (format->which == V4L2_SUBDEV_FORMAT_ACTIVE) + format->format = priv->mf; + else + format->format = *v4l2_subdev_get_try_format(sd, sd_state, 0); + + mutex_unlock(&priv->lock); + + return 0; +} + +static const struct v4l2_subdev_video_ops rcar_csi2_video_ops = { + .s_stream = rcsi2_s_stream, +}; + +static const struct v4l2_subdev_pad_ops rcar_csi2_pad_ops = { + .set_fmt = rcsi2_set_pad_format, + .get_fmt = rcsi2_get_pad_format, +}; + +static const struct v4l2_subdev_ops rcar_csi2_subdev_ops = { + .video = &rcar_csi2_video_ops, + .pad = &rcar_csi2_pad_ops, +}; + +static irqreturn_t rcsi2_irq(int irq, void *data) +{ + struct rcar_csi2 *priv = data; + u32 status, err_status; + + status = rcsi2_read(priv, INTSTATE_REG); + err_status = rcsi2_read(priv, INTERRSTATE_REG); + + if (!status) + return IRQ_HANDLED; + + rcsi2_write(priv, INTSTATE_REG, status); + + if (!err_status) + return IRQ_HANDLED; + + rcsi2_write(priv, INTERRSTATE_REG, err_status); + + dev_info(priv->dev, "Transfer error, restarting CSI-2 receiver\n"); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t rcsi2_irq_thread(int irq, void *data) +{ + struct rcar_csi2 *priv = data; + + mutex_lock(&priv->lock); + rcsi2_stop(priv); + usleep_range(1000, 2000); + if (rcsi2_start(priv)) + dev_warn(priv->dev, "Failed to restart CSI-2 receiver\n"); + mutex_unlock(&priv->lock); + + return IRQ_HANDLED; +} + +/* ----------------------------------------------------------------------------- + * Async handling and registration of subdevices and links. + */ + +static int rcsi2_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_csi2 *priv = notifier_to_csi2(notifier); + int pad; + + pad = media_entity_get_fwnode_pad(&subdev->entity, asd->match.fwnode, + MEDIA_PAD_FL_SOURCE); + if (pad < 0) { + dev_err(priv->dev, "Failed to find pad for %s\n", subdev->name); + return pad; + } + + priv->remote = subdev; + priv->remote_pad = pad; + + dev_dbg(priv->dev, "Bound %s pad: %d\n", subdev->name, pad); + + return media_create_pad_link(&subdev->entity, pad, + &priv->subdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static void rcsi2_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_csi2 *priv = notifier_to_csi2(notifier); + + priv->remote = NULL; + + dev_dbg(priv->dev, "Unbind %s\n", subdev->name); +} + +static const struct v4l2_async_notifier_operations rcar_csi2_notify_ops = { + .bound = rcsi2_notify_bound, + .unbind = rcsi2_notify_unbind, +}; + +static int rcsi2_parse_v4l2(struct rcar_csi2 *priv, + struct v4l2_fwnode_endpoint *vep) +{ + unsigned int i; + + /* Only port 0 endpoint 0 is valid. */ + if (vep->base.port || vep->base.id) + return -ENOTCONN; + + if (vep->bus_type != V4L2_MBUS_CSI2_DPHY) { + dev_err(priv->dev, "Unsupported bus: %u\n", vep->bus_type); + return -EINVAL; + } + + priv->lanes = vep->bus.mipi_csi2.num_data_lanes; + if (priv->lanes != 1 && priv->lanes != 2 && priv->lanes != 4) { + dev_err(priv->dev, "Unsupported number of data-lanes: %u\n", + priv->lanes); + return -EINVAL; + } + + for (i = 0; i < ARRAY_SIZE(priv->lane_swap); i++) { + priv->lane_swap[i] = i < priv->lanes ? + vep->bus.mipi_csi2.data_lanes[i] : i; + + /* Check for valid lane number. */ + if (priv->lane_swap[i] < 1 || priv->lane_swap[i] > 4) { + dev_err(priv->dev, "data-lanes must be in 1-4 range\n"); + return -EINVAL; + } + } + + return 0; +} + +static int rcsi2_parse_dt(struct rcar_csi2 *priv) +{ + struct v4l2_async_subdev *asd; + struct fwnode_handle *fwnode; + struct fwnode_handle *ep; + struct v4l2_fwnode_endpoint v4l2_ep = { + .bus_type = V4L2_MBUS_CSI2_DPHY + }; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(priv->dev), 0, 0, 0); + if (!ep) { + dev_err(priv->dev, "Not connected to subdevice\n"); + return -EINVAL; + } + + ret = v4l2_fwnode_endpoint_parse(ep, &v4l2_ep); + if (ret) { + dev_err(priv->dev, "Could not parse v4l2 endpoint\n"); + fwnode_handle_put(ep); + return -EINVAL; + } + + ret = rcsi2_parse_v4l2(priv, &v4l2_ep); + if (ret) { + fwnode_handle_put(ep); + return ret; + } + + fwnode = fwnode_graph_get_remote_endpoint(ep); + fwnode_handle_put(ep); + + dev_dbg(priv->dev, "Found '%pOF'\n", to_of_node(fwnode)); + + v4l2_async_nf_init(&priv->notifier); + priv->notifier.ops = &rcar_csi2_notify_ops; + + asd = v4l2_async_nf_add_fwnode(&priv->notifier, fwnode, + struct v4l2_async_subdev); + fwnode_handle_put(fwnode); + if (IS_ERR(asd)) + return PTR_ERR(asd); + + ret = v4l2_async_subdev_nf_register(&priv->subdev, &priv->notifier); + if (ret) + v4l2_async_nf_cleanup(&priv->notifier); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * PHTW initialization sequences. + * + * NOTE: Magic values are from the datasheet and lack documentation. + */ + +static int rcsi2_phtw_write(struct rcar_csi2 *priv, u16 data, u16 code) +{ + unsigned int timeout; + + rcsi2_write(priv, PHTW_REG, + PHTW_DWEN | PHTW_TESTDIN_DATA(data) | + PHTW_CWEN | PHTW_TESTDIN_CODE(code)); + + /* Wait for DWEN and CWEN to be cleared by hardware. */ + for (timeout = 0; timeout <= 20; timeout++) { + if (!(rcsi2_read(priv, PHTW_REG) & (PHTW_DWEN | PHTW_CWEN))) + return 0; + + usleep_range(1000, 2000); + } + + dev_err(priv->dev, "Timeout waiting for PHTW_DWEN and/or PHTW_CWEN\n"); + + return -ETIMEDOUT; +} + +static int rcsi2_phtw_write_array(struct rcar_csi2 *priv, + const struct phtw_value *values) +{ + const struct phtw_value *value; + int ret; + + for (value = values; value->data || value->code; value++) { + ret = rcsi2_phtw_write(priv, value->data, value->code); + if (ret) + return ret; + } + + return 0; +} + +static int rcsi2_phtw_write_mbps(struct rcar_csi2 *priv, unsigned int mbps, + const struct rcsi2_mbps_reg *values, u16 code) +{ + const struct rcsi2_mbps_reg *value; + const struct rcsi2_mbps_reg *prev_value = NULL; + + for (value = values; value->mbps; value++) { + if (value->mbps >= mbps) + break; + prev_value = value; + } + + if (prev_value && + ((mbps - prev_value->mbps) <= (value->mbps - mbps))) + value = prev_value; + + if (!value->mbps) { + dev_err(priv->dev, "Unsupported PHY speed (%u Mbps)", mbps); + return -ERANGE; + } + + return rcsi2_phtw_write(priv, value->reg, code); +} + +static int __rcsi2_init_phtw_h3_v3h_m3n(struct rcar_csi2 *priv, + unsigned int mbps) +{ + static const struct phtw_value step1[] = { + { .data = 0xcc, .code = 0xe2 }, + { .data = 0x01, .code = 0xe3 }, + { .data = 0x11, .code = 0xe4 }, + { .data = 0x01, .code = 0xe5 }, + { .data = 0x10, .code = 0x04 }, + { /* sentinel */ }, + }; + + static const struct phtw_value step2[] = { + { .data = 0x38, .code = 0x08 }, + { .data = 0x01, .code = 0x00 }, + { .data = 0x4b, .code = 0xac }, + { .data = 0x03, .code = 0x00 }, + { .data = 0x80, .code = 0x07 }, + { /* sentinel */ }, + }; + + int ret; + + ret = rcsi2_phtw_write_array(priv, step1); + if (ret) + return ret; + + if (mbps != 0 && mbps <= 250) { + ret = rcsi2_phtw_write(priv, 0x39, 0x05); + if (ret) + return ret; + + ret = rcsi2_phtw_write_mbps(priv, mbps, phtw_mbps_h3_v3h_m3n, + 0xf1); + if (ret) + return ret; + } + + return rcsi2_phtw_write_array(priv, step2); +} + +static int rcsi2_init_phtw_h3_v3h_m3n(struct rcar_csi2 *priv, unsigned int mbps) +{ + return __rcsi2_init_phtw_h3_v3h_m3n(priv, mbps); +} + +static int rcsi2_init_phtw_h3es2(struct rcar_csi2 *priv, unsigned int mbps) +{ + return __rcsi2_init_phtw_h3_v3h_m3n(priv, 0); +} + +static int rcsi2_init_phtw_v3m_e3(struct rcar_csi2 *priv, unsigned int mbps) +{ + return rcsi2_phtw_write_mbps(priv, mbps, phtw_mbps_v3m_e3, 0x44); +} + +static int rcsi2_phy_post_init_v3m_e3(struct rcar_csi2 *priv) +{ + static const struct phtw_value step1[] = { + { .data = 0xee, .code = 0x34 }, + { .data = 0xee, .code = 0x44 }, + { .data = 0xee, .code = 0x54 }, + { .data = 0xee, .code = 0x84 }, + { .data = 0xee, .code = 0x94 }, + { /* sentinel */ }, + }; + + return rcsi2_phtw_write_array(priv, step1); +} + +static int rcsi2_init_phtw_v3u(struct rcar_csi2 *priv, + unsigned int mbps) +{ + /* In case of 1500Mbps or less */ + static const struct phtw_value step1[] = { + { .data = 0xcc, .code = 0xe2 }, + { /* sentinel */ }, + }; + + static const struct phtw_value step2[] = { + { .data = 0x01, .code = 0xe3 }, + { .data = 0x11, .code = 0xe4 }, + { .data = 0x01, .code = 0xe5 }, + { /* sentinel */ }, + }; + + /* In case of 1500Mbps or less */ + static const struct phtw_value step3[] = { + { .data = 0x38, .code = 0x08 }, + { /* sentinel */ }, + }; + + static const struct phtw_value step4[] = { + { .data = 0x01, .code = 0x00 }, + { .data = 0x4b, .code = 0xac }, + { .data = 0x03, .code = 0x00 }, + { .data = 0x80, .code = 0x07 }, + { /* sentinel */ }, + }; + + int ret; + + if (mbps != 0 && mbps <= 1500) + ret = rcsi2_phtw_write_array(priv, step1); + else + ret = rcsi2_phtw_write_mbps(priv, mbps, phtw_mbps_v3u, 0xe2); + if (ret) + return ret; + + ret = rcsi2_phtw_write_array(priv, step2); + if (ret) + return ret; + + if (mbps != 0 && mbps <= 1500) { + ret = rcsi2_phtw_write_array(priv, step3); + if (ret) + return ret; + } + + ret = rcsi2_phtw_write_array(priv, step4); + if (ret) + return ret; + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Platform Device Driver. + */ + +static int rcsi2_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct rcar_csi2 *priv = sd_to_csi2(sd); + struct video_device *vdev; + int channel, vc; + u32 id; + + if (!is_media_entity_v4l2_video_device(remote->entity)) { + dev_err(priv->dev, "Remote is not a video device\n"); + return -EINVAL; + } + + vdev = media_entity_to_video_device(remote->entity); + + if (of_property_read_u32(vdev->dev_parent->of_node, "renesas,id", &id)) { + dev_err(priv->dev, "No renesas,id, can't configure routing\n"); + return -EINVAL; + } + + channel = id % 4; + + if (flags & MEDIA_LNK_FL_ENABLED) { + if (media_pad_remote_pad_first(local)) { + dev_dbg(priv->dev, + "Each VC can only be routed to one output channel\n"); + return -EINVAL; + } + + vc = local->index - 1; + + dev_dbg(priv->dev, "Route VC%d to VIN%u on output channel %d\n", + vc, id, channel); + } else { + vc = -1; + } + + priv->channel_vc[channel] = vc; + + return 0; +} + +static const struct media_entity_operations rcar_csi2_entity_ops = { + .link_setup = rcsi2_link_setup, + .link_validate = v4l2_subdev_link_validate, +}; + +static int rcsi2_probe_resources(struct rcar_csi2 *priv, + struct platform_device *pdev) +{ + int irq, ret; + + priv->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, rcsi2_irq, + rcsi2_irq_thread, IRQF_SHARED, + KBUILD_MODNAME, priv); + if (ret) + return ret; + + priv->rstc = devm_reset_control_get(&pdev->dev, NULL); + + return PTR_ERR_OR_ZERO(priv->rstc); +} + +static const struct rcar_csi2_info rcar_csi2_info_r8a7795 = { + .init_phtw = rcsi2_init_phtw_h3_v3h_m3n, + .hsfreqrange = hsfreqrange_h3_v3h_m3n, + .csi0clkfreqrange = 0x20, + .num_channels = 4, + .clear_ulps = true, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a7795es1 = { + .hsfreqrange = hsfreqrange_m3w_h3es1, + .num_channels = 4, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a7795es2 = { + .init_phtw = rcsi2_init_phtw_h3es2, + .hsfreqrange = hsfreqrange_h3_v3h_m3n, + .csi0clkfreqrange = 0x20, + .num_channels = 4, + .clear_ulps = true, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a7796 = { + .hsfreqrange = hsfreqrange_m3w_h3es1, + .num_channels = 4, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a77961 = { + .hsfreqrange = hsfreqrange_m3w_h3es1, + .num_channels = 4, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a77965 = { + .init_phtw = rcsi2_init_phtw_h3_v3h_m3n, + .hsfreqrange = hsfreqrange_h3_v3h_m3n, + .csi0clkfreqrange = 0x20, + .num_channels = 4, + .clear_ulps = true, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a77970 = { + .init_phtw = rcsi2_init_phtw_v3m_e3, + .phy_post_init = rcsi2_phy_post_init_v3m_e3, + .num_channels = 4, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a77980 = { + .init_phtw = rcsi2_init_phtw_h3_v3h_m3n, + .hsfreqrange = hsfreqrange_h3_v3h_m3n, + .csi0clkfreqrange = 0x20, + .clear_ulps = true, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a77990 = { + .init_phtw = rcsi2_init_phtw_v3m_e3, + .phy_post_init = rcsi2_phy_post_init_v3m_e3, + .num_channels = 2, +}; + +static const struct rcar_csi2_info rcar_csi2_info_r8a779a0 = { + .init_phtw = rcsi2_init_phtw_v3u, + .hsfreqrange = hsfreqrange_v3u, + .csi0clkfreqrange = 0x20, + .clear_ulps = true, + .use_isp = true, +}; + +static const struct of_device_id rcar_csi2_of_table[] = { + { + .compatible = "renesas,r8a774a1-csi2", + .data = &rcar_csi2_info_r8a7796, + }, + { + .compatible = "renesas,r8a774b1-csi2", + .data = &rcar_csi2_info_r8a77965, + }, + { + .compatible = "renesas,r8a774c0-csi2", + .data = &rcar_csi2_info_r8a77990, + }, + { + .compatible = "renesas,r8a774e1-csi2", + .data = &rcar_csi2_info_r8a7795, + }, + { + .compatible = "renesas,r8a7795-csi2", + .data = &rcar_csi2_info_r8a7795, + }, + { + .compatible = "renesas,r8a7796-csi2", + .data = &rcar_csi2_info_r8a7796, + }, + { + .compatible = "renesas,r8a77961-csi2", + .data = &rcar_csi2_info_r8a77961, + }, + { + .compatible = "renesas,r8a77965-csi2", + .data = &rcar_csi2_info_r8a77965, + }, + { + .compatible = "renesas,r8a77970-csi2", + .data = &rcar_csi2_info_r8a77970, + }, + { + .compatible = "renesas,r8a77980-csi2", + .data = &rcar_csi2_info_r8a77980, + }, + { + .compatible = "renesas,r8a77990-csi2", + .data = &rcar_csi2_info_r8a77990, + }, + { + .compatible = "renesas,r8a779a0-csi2", + .data = &rcar_csi2_info_r8a779a0, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, rcar_csi2_of_table); + +static const struct soc_device_attribute r8a7795[] = { + { + .soc_id = "r8a7795", .revision = "ES1.*", + .data = &rcar_csi2_info_r8a7795es1, + }, + { + .soc_id = "r8a7795", .revision = "ES2.*", + .data = &rcar_csi2_info_r8a7795es2, + }, + { /* sentinel */ } +}; + +static int rcsi2_probe(struct platform_device *pdev) +{ + const struct soc_device_attribute *attr; + struct rcar_csi2 *priv; + unsigned int i, num_pads; + int ret; + + priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->info = of_device_get_match_data(&pdev->dev); + + /* + * The different ES versions of r8a7795 (H3) behave differently but + * share the same compatible string. + */ + attr = soc_device_match(r8a7795); + if (attr) + priv->info = attr->data; + + priv->dev = &pdev->dev; + + mutex_init(&priv->lock); + priv->stream_count = 0; + + ret = rcsi2_probe_resources(priv, pdev); + if (ret) { + dev_err(priv->dev, "Failed to get resources\n"); + goto error_mutex; + } + + platform_set_drvdata(pdev, priv); + + ret = rcsi2_parse_dt(priv); + if (ret) + goto error_mutex; + + priv->subdev.owner = THIS_MODULE; + priv->subdev.dev = &pdev->dev; + v4l2_subdev_init(&priv->subdev, &rcar_csi2_subdev_ops); + v4l2_set_subdevdata(&priv->subdev, &pdev->dev); + snprintf(priv->subdev.name, V4L2_SUBDEV_NAME_SIZE, "%s %s", + KBUILD_MODNAME, dev_name(&pdev->dev)); + priv->subdev.flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + priv->subdev.entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + priv->subdev.entity.ops = &rcar_csi2_entity_ops; + + num_pads = priv->info->use_isp ? 2 : NR_OF_RCAR_CSI2_PAD; + + priv->pads[RCAR_CSI2_SINK].flags = MEDIA_PAD_FL_SINK; + for (i = RCAR_CSI2_SOURCE_VC0; i < num_pads; i++) + priv->pads[i].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&priv->subdev.entity, num_pads, + priv->pads); + if (ret) + goto error_async; + + for (i = 0; i < ARRAY_SIZE(priv->channel_vc); i++) + priv->channel_vc[i] = -1; + + pm_runtime_enable(&pdev->dev); + + ret = v4l2_async_register_subdev(&priv->subdev); + if (ret < 0) + goto error_async; + + dev_info(priv->dev, "%d lanes found\n", priv->lanes); + + return 0; + +error_async: + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); +error_mutex: + mutex_destroy(&priv->lock); + + return ret; +} + +static int rcsi2_remove(struct platform_device *pdev) +{ + struct rcar_csi2 *priv = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&priv->notifier); + v4l2_async_nf_cleanup(&priv->notifier); + v4l2_async_unregister_subdev(&priv->subdev); + + pm_runtime_disable(&pdev->dev); + + mutex_destroy(&priv->lock); + + return 0; +} + +static struct platform_driver rcar_csi2_pdrv = { + .remove = rcsi2_remove, + .probe = rcsi2_probe, + .driver = { + .name = "rcar-csi2", + .suppress_bind_attrs = true, + .of_match_table = rcar_csi2_of_table, + }, +}; + +module_platform_driver(rcar_csi2_pdrv); + +MODULE_AUTHOR("Niklas Söderlund "); +MODULE_DESCRIPTION("Renesas R-Car MIPI CSI-2 receiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c new file mode 100644 index 000000000..ef5adffae --- /dev/null +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -0,0 +1,1577 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Renesas R-Car VIN + * + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., + * Copyright (C) 2008 Magnus Damm + * + * Based on the soc-camera rcar_vin driver + */ + +#include +#include +#include + +#include + +#include "rcar-vin.h" + +/* ----------------------------------------------------------------------------- + * HW Functions + */ + +/* Register offsets for R-Car VIN */ +#define VNMC_REG 0x00 /* Video n Main Control Register */ +#define VNMS_REG 0x04 /* Video n Module Status Register */ +#define VNFC_REG 0x08 /* Video n Frame Capture Register */ +#define VNSLPRC_REG 0x0C /* Video n Start Line Pre-Clip Register */ +#define VNELPRC_REG 0x10 /* Video n End Line Pre-Clip Register */ +#define VNSPPRC_REG 0x14 /* Video n Start Pixel Pre-Clip Register */ +#define VNEPPRC_REG 0x18 /* Video n End Pixel Pre-Clip Register */ +#define VNIS_REG 0x2C /* Video n Image Stride Register */ +#define VNMB_REG(m) (0x30 + ((m) << 2)) /* Video n Memory Base m Register */ +#define VNIE_REG 0x40 /* Video n Interrupt Enable Register */ +#define VNINTS_REG 0x44 /* Video n Interrupt Status Register */ +#define VNSI_REG 0x48 /* Video n Scanline Interrupt Register */ +#define VNMTC_REG 0x4C /* Video n Memory Transfer Control Register */ +#define VNDMR_REG 0x58 /* Video n Data Mode Register */ +#define VNDMR2_REG 0x5C /* Video n Data Mode Register 2 */ +#define VNUVAOF_REG 0x60 /* Video n UV Address Offset Register */ + +/* Register offsets specific for Gen2 */ +#define VNSLPOC_REG 0x1C /* Video n Start Line Post-Clip Register */ +#define VNELPOC_REG 0x20 /* Video n End Line Post-Clip Register */ +#define VNSPPOC_REG 0x24 /* Video n Start Pixel Post-Clip Register */ +#define VNEPPOC_REG 0x28 /* Video n End Pixel Post-Clip Register */ +#define VNYS_REG 0x50 /* Video n Y Scale Register */ +#define VNXS_REG 0x54 /* Video n X Scale Register */ +#define VNC1A_REG 0x80 /* Video n Coefficient Set C1A Register */ +#define VNC1B_REG 0x84 /* Video n Coefficient Set C1B Register */ +#define VNC1C_REG 0x88 /* Video n Coefficient Set C1C Register */ +#define VNC2A_REG 0x90 /* Video n Coefficient Set C2A Register */ +#define VNC2B_REG 0x94 /* Video n Coefficient Set C2B Register */ +#define VNC2C_REG 0x98 /* Video n Coefficient Set C2C Register */ +#define VNC3A_REG 0xA0 /* Video n Coefficient Set C3A Register */ +#define VNC3B_REG 0xA4 /* Video n Coefficient Set C3B Register */ +#define VNC3C_REG 0xA8 /* Video n Coefficient Set C3C Register */ +#define VNC4A_REG 0xB0 /* Video n Coefficient Set C4A Register */ +#define VNC4B_REG 0xB4 /* Video n Coefficient Set C4B Register */ +#define VNC4C_REG 0xB8 /* Video n Coefficient Set C4C Register */ +#define VNC5A_REG 0xC0 /* Video n Coefficient Set C5A Register */ +#define VNC5B_REG 0xC4 /* Video n Coefficient Set C5B Register */ +#define VNC5C_REG 0xC8 /* Video n Coefficient Set C5C Register */ +#define VNC6A_REG 0xD0 /* Video n Coefficient Set C6A Register */ +#define VNC6B_REG 0xD4 /* Video n Coefficient Set C6B Register */ +#define VNC6C_REG 0xD8 /* Video n Coefficient Set C6C Register */ +#define VNC7A_REG 0xE0 /* Video n Coefficient Set C7A Register */ +#define VNC7B_REG 0xE4 /* Video n Coefficient Set C7B Register */ +#define VNC7C_REG 0xE8 /* Video n Coefficient Set C7C Register */ +#define VNC8A_REG 0xF0 /* Video n Coefficient Set C8A Register */ +#define VNC8B_REG 0xF4 /* Video n Coefficient Set C8B Register */ +#define VNC8C_REG 0xF8 /* Video n Coefficient Set C8C Register */ + +/* Register offsets specific for Gen3 */ +#define VNCSI_IFMD_REG 0x20 /* Video n CSI2 Interface Mode Register */ + +/* Register bit fields for R-Car VIN */ +/* Video n Main Control Register bits */ +#define VNMC_INF_MASK (7 << 16) +#define VNMC_DPINE (1 << 27) /* Gen3 specific */ +#define VNMC_SCLE (1 << 26) /* Gen3 specific */ +#define VNMC_FOC (1 << 21) +#define VNMC_YCAL (1 << 19) +#define VNMC_INF_YUV8_BT656 (0 << 16) +#define VNMC_INF_YUV8_BT601 (1 << 16) +#define VNMC_INF_YUV10_BT656 (2 << 16) +#define VNMC_INF_YUV10_BT601 (3 << 16) +#define VNMC_INF_RAW8 (4 << 16) +#define VNMC_INF_YUV16 (5 << 16) +#define VNMC_INF_RGB888 (6 << 16) +#define VNMC_INF_RGB666 (7 << 16) +#define VNMC_VUP (1 << 10) +#define VNMC_IM_ODD (0 << 3) +#define VNMC_IM_ODD_EVEN (1 << 3) +#define VNMC_IM_EVEN (2 << 3) +#define VNMC_IM_FULL (3 << 3) +#define VNMC_BPS (1 << 1) +#define VNMC_ME (1 << 0) + +/* Video n Module Status Register bits */ +#define VNMS_FBS_MASK (3 << 3) +#define VNMS_FBS_SHIFT 3 +#define VNMS_FS (1 << 2) +#define VNMS_AV (1 << 1) +#define VNMS_CA (1 << 0) + +/* Video n Frame Capture Register bits */ +#define VNFC_C_FRAME (1 << 1) +#define VNFC_S_FRAME (1 << 0) + +/* Video n Interrupt Enable Register bits */ +#define VNIE_FIE (1 << 4) +#define VNIE_EFE (1 << 1) + +/* Video n Interrupt Status Register bits */ +#define VNINTS_FIS (1 << 4) + +/* Video n Data Mode Register bits */ +#define VNDMR_A8BIT(n) (((n) & 0xff) << 24) +#define VNDMR_A8BIT_MASK (0xff << 24) +#define VNDMR_YMODE_Y8 (1 << 12) +#define VNDMR_EXRGB (1 << 8) +#define VNDMR_BPSM (1 << 4) +#define VNDMR_ABIT (1 << 2) +#define VNDMR_DTMD_YCSEP (1 << 1) +#define VNDMR_DTMD_ARGB (1 << 0) +#define VNDMR_DTMD_YCSEP_420 (3 << 0) + +/* Video n Data Mode Register 2 bits */ +#define VNDMR2_VPS (1 << 30) +#define VNDMR2_HPS (1 << 29) +#define VNDMR2_CES (1 << 28) +#define VNDMR2_YDS (1 << 22) +#define VNDMR2_FTEV (1 << 17) +#define VNDMR2_VLV(n) ((n & 0xf) << 12) + +/* Video n CSI2 Interface Mode Register (Gen3) */ +#define VNCSI_IFMD_DES1 (1 << 26) +#define VNCSI_IFMD_DES0 (1 << 25) +#define VNCSI_IFMD_CSI_CHSEL(n) (((n) & 0xf) << 0) + +struct rvin_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +#define to_buf_list(vb2_buffer) (&container_of(vb2_buffer, \ + struct rvin_buffer, \ + vb)->list) + +static void rvin_write(struct rvin_dev *vin, u32 value, u32 offset) +{ + iowrite32(value, vin->base + offset); +} + +static u32 rvin_read(struct rvin_dev *vin, u32 offset) +{ + return ioread32(vin->base + offset); +} + +/* ----------------------------------------------------------------------------- + * Crop and Scaling Gen2 + */ + +struct vin_coeff { + unsigned short xs_value; + u32 coeff_set[24]; +}; + +static const struct vin_coeff vin_coeff_set[] = { + { 0x0000, { + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000, + 0x00000000, 0x00000000, 0x00000000 }, + }, + { 0x1000, { + 0x000fa400, 0x000fa400, 0x09625902, + 0x000003f8, 0x00000403, 0x3de0d9f0, + 0x001fffed, 0x00000804, 0x3cc1f9c3, + 0x001003de, 0x00000c01, 0x3cb34d7f, + 0x002003d2, 0x00000c00, 0x3d24a92d, + 0x00200bca, 0x00000bff, 0x3df600d2, + 0x002013cc, 0x000007ff, 0x3ed70c7e, + 0x00100fde, 0x00000000, 0x3f87c036 }, + }, + { 0x1200, { + 0x002ffff1, 0x002ffff1, 0x02a0a9c8, + 0x002003e7, 0x001ffffa, 0x000185bc, + 0x002007dc, 0x000003ff, 0x3e52859c, + 0x00200bd4, 0x00000002, 0x3d53996b, + 0x00100fd0, 0x00000403, 0x3d04ad2d, + 0x00000bd5, 0x00000403, 0x3d35ace7, + 0x3ff003e4, 0x00000801, 0x3dc674a1, + 0x3fffe800, 0x00000800, 0x3e76f461 }, + }, + { 0x1400, { + 0x00100be3, 0x00100be3, 0x04d1359a, + 0x00000fdb, 0x002003ed, 0x0211fd93, + 0x00000fd6, 0x002003f4, 0x0002d97b, + 0x000007d6, 0x002ffffb, 0x3e93b956, + 0x3ff003da, 0x001003ff, 0x3db49926, + 0x3fffefe9, 0x00100001, 0x3d655cee, + 0x3fffd400, 0x00000003, 0x3d65f4b6, + 0x000fb421, 0x00000402, 0x3dc6547e }, + }, + { 0x1600, { + 0x00000bdd, 0x00000bdd, 0x06519578, + 0x3ff007da, 0x00000be3, 0x03c24973, + 0x3ff003d9, 0x00000be9, 0x01b30d5f, + 0x3ffff7df, 0x001003f1, 0x0003c542, + 0x000fdfec, 0x001003f7, 0x3ec4711d, + 0x000fc400, 0x002ffffd, 0x3df504f1, + 0x001fa81a, 0x002ffc00, 0x3d957cc2, + 0x002f8c3c, 0x00100000, 0x3db5c891 }, + }, + { 0x1800, { + 0x3ff003dc, 0x3ff003dc, 0x0791e558, + 0x000ff7dd, 0x3ff007de, 0x05328554, + 0x000fe7e3, 0x3ff00be2, 0x03232546, + 0x000fd7ee, 0x000007e9, 0x0143bd30, + 0x001fb800, 0x000007ee, 0x00044511, + 0x002fa015, 0x000007f4, 0x3ef4bcee, + 0x002f8832, 0x001003f9, 0x3e4514c7, + 0x001f7853, 0x001003fd, 0x3de54c9f }, + }, + { 0x1a00, { + 0x000fefe0, 0x000fefe0, 0x08721d3c, + 0x001fdbe7, 0x000ffbde, 0x0652a139, + 0x001fcbf0, 0x000003df, 0x0463292e, + 0x002fb3ff, 0x3ff007e3, 0x0293a91d, + 0x002f9c12, 0x3ff00be7, 0x01241905, + 0x001f8c29, 0x000007ed, 0x3fe470eb, + 0x000f7c46, 0x000007f2, 0x3f04b8ca, + 0x3fef7865, 0x000007f6, 0x3e74e4a8 }, + }, + { 0x1c00, { + 0x001fd3e9, 0x001fd3e9, 0x08f23d26, + 0x002fbff3, 0x001fe3e4, 0x0712ad23, + 0x002fa800, 0x000ff3e0, 0x05631d1b, + 0x001f9810, 0x000ffbe1, 0x03b3890d, + 0x000f8c23, 0x000003e3, 0x0233e8fa, + 0x3fef843b, 0x000003e7, 0x00f430e4, + 0x3fbf8456, 0x3ff00bea, 0x00046cc8, + 0x3f8f8c72, 0x3ff00bef, 0x3f3490ac }, + }, + { 0x1e00, { + 0x001fbbf4, 0x001fbbf4, 0x09425112, + 0x001fa800, 0x002fc7ed, 0x0792b110, + 0x000f980e, 0x001fdbe6, 0x0613110a, + 0x3fff8c20, 0x001fe7e3, 0x04a368fd, + 0x3fcf8c33, 0x000ff7e2, 0x0343b8ed, + 0x3f9f8c4a, 0x000fffe3, 0x0203f8da, + 0x3f5f9c61, 0x000003e6, 0x00e428c5, + 0x3f1fb07b, 0x000003eb, 0x3fe440af }, + }, + { 0x2000, { + 0x000fa400, 0x000fa400, 0x09625902, + 0x3fff980c, 0x001fb7f5, 0x0812b0ff, + 0x3fdf901c, 0x001fc7ed, 0x06b2fcfa, + 0x3faf902d, 0x001fd3e8, 0x055348f1, + 0x3f7f983f, 0x001fe3e5, 0x04038ce3, + 0x3f3fa454, 0x001fefe3, 0x02e3c8d1, + 0x3f0fb86a, 0x001ff7e4, 0x01c3e8c0, + 0x3ecfd880, 0x000fffe6, 0x00c404ac }, + }, + { 0x2200, { + 0x3fdf9c0b, 0x3fdf9c0b, 0x09725cf4, + 0x3fbf9818, 0x3fffa400, 0x0842a8f1, + 0x3f8f9827, 0x000fb3f7, 0x0702f0ec, + 0x3f5fa037, 0x000fc3ef, 0x05d330e4, + 0x3f2fac49, 0x001fcfea, 0x04a364d9, + 0x3effc05c, 0x001fdbe7, 0x038394ca, + 0x3ecfdc6f, 0x001fe7e6, 0x0273b0bb, + 0x3ea00083, 0x001fefe6, 0x0183c0a9 }, + }, + { 0x2400, { + 0x3f9fa014, 0x3f9fa014, 0x098260e6, + 0x3f7f9c23, 0x3fcf9c0a, 0x08629ce5, + 0x3f4fa431, 0x3fefa400, 0x0742d8e1, + 0x3f1fb440, 0x3fffb3f8, 0x062310d9, + 0x3eefc850, 0x000fbbf2, 0x050340d0, + 0x3ecfe062, 0x000fcbec, 0x041364c2, + 0x3ea00073, 0x001fd3ea, 0x03037cb5, + 0x3e902086, 0x001fdfe8, 0x022388a5 }, + }, + { 0x2600, { + 0x3f5fa81e, 0x3f5fa81e, 0x096258da, + 0x3f3fac2b, 0x3f8fa412, 0x088290d8, + 0x3f0fbc38, 0x3fafa408, 0x0772c8d5, + 0x3eefcc47, 0x3fcfa800, 0x0672f4ce, + 0x3ecfe456, 0x3fefaffa, 0x05531cc6, + 0x3eb00066, 0x3fffbbf3, 0x047334bb, + 0x3ea01c77, 0x000fc7ee, 0x039348ae, + 0x3ea04486, 0x000fd3eb, 0x02b350a1 }, + }, + { 0x2800, { + 0x3f2fb426, 0x3f2fb426, 0x094250ce, + 0x3f0fc032, 0x3f4fac1b, 0x086284cd, + 0x3eefd040, 0x3f7fa811, 0x0782acc9, + 0x3ecfe84c, 0x3f9fa807, 0x06a2d8c4, + 0x3eb0005b, 0x3fbfac00, 0x05b2f4bc, + 0x3eb0186a, 0x3fdfb3fa, 0x04c308b4, + 0x3eb04077, 0x3fefbbf4, 0x03f31ca8, + 0x3ec06884, 0x000fbff2, 0x03031c9e }, + }, + { 0x2a00, { + 0x3f0fc42d, 0x3f0fc42d, 0x090240c4, + 0x3eefd439, 0x3f2fb822, 0x08526cc2, + 0x3edfe845, 0x3f4fb018, 0x078294bf, + 0x3ec00051, 0x3f6fac0f, 0x06b2b4bb, + 0x3ec0185f, 0x3f8fac07, 0x05e2ccb4, + 0x3ec0386b, 0x3fafac00, 0x0502e8ac, + 0x3ed05c77, 0x3fcfb3fb, 0x0432f0a3, + 0x3ef08482, 0x3fdfbbf6, 0x0372f898 }, + }, + { 0x2c00, { + 0x3eefdc31, 0x3eefdc31, 0x08e238b8, + 0x3edfec3d, 0x3f0fc828, 0x082258b9, + 0x3ed00049, 0x3f1fc01e, 0x077278b6, + 0x3ed01455, 0x3f3fb815, 0x06c294b2, + 0x3ed03460, 0x3f5fb40d, 0x0602acac, + 0x3ef0506c, 0x3f7fb006, 0x0542c0a4, + 0x3f107476, 0x3f9fb400, 0x0472c89d, + 0x3f309c80, 0x3fbfb7fc, 0x03b2cc94 }, + }, + { 0x2e00, { + 0x3eefec37, 0x3eefec37, 0x088220b0, + 0x3ee00041, 0x3effdc2d, 0x07f244ae, + 0x3ee0144c, 0x3f0fd023, 0x07625cad, + 0x3ef02c57, 0x3f1fc81a, 0x06c274a9, + 0x3f004861, 0x3f3fbc13, 0x060288a6, + 0x3f20686b, 0x3f5fb80c, 0x05529c9e, + 0x3f408c74, 0x3f6fb805, 0x04b2ac96, + 0x3f80ac7e, 0x3f8fb800, 0x0402ac8e }, + }, + { 0x3000, { + 0x3ef0003a, 0x3ef0003a, 0x084210a6, + 0x3ef01045, 0x3effec32, 0x07b228a7, + 0x3f00284e, 0x3f0fdc29, 0x073244a4, + 0x3f104058, 0x3f0fd420, 0x06a258a2, + 0x3f305c62, 0x3f2fc818, 0x0612689d, + 0x3f508069, 0x3f3fc011, 0x05728496, + 0x3f80a072, 0x3f4fc00a, 0x04d28c90, + 0x3fc0c07b, 0x3f6fbc04, 0x04429088 }, + }, + { 0x3200, { + 0x3f00103e, 0x3f00103e, 0x07f1fc9e, + 0x3f102447, 0x3f000035, 0x0782149d, + 0x3f203c4f, 0x3f0ff02c, 0x07122c9c, + 0x3f405458, 0x3f0fe424, 0x06924099, + 0x3f607061, 0x3f1fd41d, 0x06024c97, + 0x3f909068, 0x3f2fcc16, 0x05726490, + 0x3fc0b070, 0x3f3fc80f, 0x04f26c8a, + 0x0000d077, 0x3f4fc409, 0x04627484 }, + }, + { 0x3400, { + 0x3f202040, 0x3f202040, 0x07a1e898, + 0x3f303449, 0x3f100c38, 0x0741fc98, + 0x3f504c50, 0x3f10002f, 0x06e21495, + 0x3f706459, 0x3f1ff028, 0x06722492, + 0x3fa08060, 0x3f1fe421, 0x05f2348f, + 0x3fd09c67, 0x3f1fdc19, 0x05824c89, + 0x0000bc6e, 0x3f2fd014, 0x04f25086, + 0x0040dc74, 0x3f3fcc0d, 0x04825c7f }, + }, + { 0x3600, { + 0x3f403042, 0x3f403042, 0x0761d890, + 0x3f504848, 0x3f301c3b, 0x0701f090, + 0x3f805c50, 0x3f200c33, 0x06a2008f, + 0x3fa07458, 0x3f10002b, 0x06520c8d, + 0x3fd0905e, 0x3f1ff424, 0x05e22089, + 0x0000ac65, 0x3f1fe81d, 0x05823483, + 0x0030cc6a, 0x3f2fdc18, 0x04f23c81, + 0x0080e871, 0x3f2fd412, 0x0482407c }, + }, + { 0x3800, { + 0x3f604043, 0x3f604043, 0x0721c88a, + 0x3f80544a, 0x3f502c3c, 0x06d1d88a, + 0x3fb06851, 0x3f301c35, 0x0681e889, + 0x3fd08456, 0x3f30082f, 0x0611fc88, + 0x00009c5d, 0x3f200027, 0x05d20884, + 0x0030b863, 0x3f2ff421, 0x05621880, + 0x0070d468, 0x3f2fe81b, 0x0502247c, + 0x00c0ec6f, 0x3f2fe015, 0x04a22877 }, + }, + { 0x3a00, { + 0x3f904c44, 0x3f904c44, 0x06e1b884, + 0x3fb0604a, 0x3f70383e, 0x0691c885, + 0x3fe07451, 0x3f502c36, 0x0661d483, + 0x00009055, 0x3f401831, 0x0601ec81, + 0x0030a85b, 0x3f300c2a, 0x05b1f480, + 0x0070c061, 0x3f300024, 0x0562047a, + 0x00b0d867, 0x3f3ff41e, 0x05020c77, + 0x00f0f46b, 0x3f2fec19, 0x04a21474 }, + }, + { 0x3c00, { + 0x3fb05c43, 0x3fb05c43, 0x06c1b07e, + 0x3fe06c4b, 0x3f902c3f, 0x0681c081, + 0x0000844f, 0x3f703838, 0x0631cc7d, + 0x00309855, 0x3f602433, 0x05d1d47e, + 0x0060b459, 0x3f50142e, 0x0581e47b, + 0x00a0c85f, 0x3f400828, 0x0531f078, + 0x00e0e064, 0x3f300021, 0x0501fc73, + 0x00b0fc6a, 0x3f3ff41d, 0x04a20873 }, + }, + { 0x3e00, { + 0x3fe06444, 0x3fe06444, 0x0681a07a, + 0x00007849, 0x3fc0503f, 0x0641b07a, + 0x0020904d, 0x3fa0403a, 0x05f1c07a, + 0x0060a453, 0x3f803034, 0x05c1c878, + 0x0090b858, 0x3f70202f, 0x0571d477, + 0x00d0d05d, 0x3f501829, 0x0531e073, + 0x0110e462, 0x3f500825, 0x04e1e471, + 0x01510065, 0x3f40001f, 0x04a1f06d }, + }, + { 0x4000, { + 0x00007044, 0x00007044, 0x06519476, + 0x00208448, 0x3fe05c3f, 0x0621a476, + 0x0050984d, 0x3fc04c3a, 0x05e1b075, + 0x0080ac52, 0x3fa03c35, 0x05a1b875, + 0x00c0c056, 0x3f803030, 0x0561c473, + 0x0100d45b, 0x3f70202b, 0x0521d46f, + 0x0140e860, 0x3f601427, 0x04d1d46e, + 0x01810064, 0x3f500822, 0x0491dc6b }, + }, + { 0x5000, { + 0x0110a442, 0x0110a442, 0x0551545e, + 0x0140b045, 0x00e0983f, 0x0531585f, + 0x0160c047, 0x00c08c3c, 0x0511645e, + 0x0190cc4a, 0x00908039, 0x04f1685f, + 0x01c0dc4c, 0x00707436, 0x04d1705e, + 0x0200e850, 0x00506833, 0x04b1785b, + 0x0230f453, 0x00305c30, 0x0491805a, + 0x02710056, 0x0010542d, 0x04718059 }, + }, + { 0x6000, { + 0x01c0bc40, 0x01c0bc40, 0x04c13052, + 0x01e0c841, 0x01a0b43d, 0x04c13851, + 0x0210cc44, 0x0180a83c, 0x04a13453, + 0x0230d845, 0x0160a03a, 0x04913c52, + 0x0260e047, 0x01409838, 0x04714052, + 0x0280ec49, 0x01208c37, 0x04514c50, + 0x02b0f44b, 0x01008435, 0x04414c50, + 0x02d1004c, 0x00e07c33, 0x0431544f }, + }, + { 0x7000, { + 0x0230c83e, 0x0230c83e, 0x04711c4c, + 0x0250d03f, 0x0210c43c, 0x0471204b, + 0x0270d840, 0x0200b83c, 0x0451244b, + 0x0290dc42, 0x01e0b43a, 0x0441244c, + 0x02b0e443, 0x01c0b038, 0x0441284b, + 0x02d0ec44, 0x01b0a438, 0x0421304a, + 0x02f0f445, 0x0190a036, 0x04213449, + 0x0310f847, 0x01709c34, 0x04213848 }, + }, + { 0x8000, { + 0x0280d03d, 0x0280d03d, 0x04310c48, + 0x02a0d43e, 0x0270c83c, 0x04311047, + 0x02b0dc3e, 0x0250c83a, 0x04311447, + 0x02d0e040, 0x0240c03a, 0x04211446, + 0x02e0e840, 0x0220bc39, 0x04111847, + 0x0300e842, 0x0210b438, 0x04012445, + 0x0310f043, 0x0200b037, 0x04012045, + 0x0330f444, 0x01e0ac36, 0x03f12445 }, + }, + { 0xefff, { + 0x0340dc3a, 0x0340dc3a, 0x03b0ec40, + 0x0340e03a, 0x0330e039, 0x03c0f03e, + 0x0350e03b, 0x0330dc39, 0x03c0ec3e, + 0x0350e43a, 0x0320dc38, 0x03c0f43e, + 0x0360e43b, 0x0320d839, 0x03b0f03e, + 0x0360e83b, 0x0310d838, 0x03c0fc3b, + 0x0370e83b, 0x0310d439, 0x03a0f83d, + 0x0370e83c, 0x0300d438, 0x03b0fc3c }, + } +}; + +static void rvin_set_coeff(struct rvin_dev *vin, unsigned short xs) +{ + int i; + const struct vin_coeff *p_prev_set = NULL; + const struct vin_coeff *p_set = NULL; + + /* Look for suitable coefficient values */ + for (i = 0; i < ARRAY_SIZE(vin_coeff_set); i++) { + p_prev_set = p_set; + p_set = &vin_coeff_set[i]; + + if (xs < p_set->xs_value) + break; + } + + /* Use previous value if its XS value is closer */ + if (p_prev_set && + xs - p_prev_set->xs_value < p_set->xs_value - xs) + p_set = p_prev_set; + + /* Set coefficient registers */ + rvin_write(vin, p_set->coeff_set[0], VNC1A_REG); + rvin_write(vin, p_set->coeff_set[1], VNC1B_REG); + rvin_write(vin, p_set->coeff_set[2], VNC1C_REG); + + rvin_write(vin, p_set->coeff_set[3], VNC2A_REG); + rvin_write(vin, p_set->coeff_set[4], VNC2B_REG); + rvin_write(vin, p_set->coeff_set[5], VNC2C_REG); + + rvin_write(vin, p_set->coeff_set[6], VNC3A_REG); + rvin_write(vin, p_set->coeff_set[7], VNC3B_REG); + rvin_write(vin, p_set->coeff_set[8], VNC3C_REG); + + rvin_write(vin, p_set->coeff_set[9], VNC4A_REG); + rvin_write(vin, p_set->coeff_set[10], VNC4B_REG); + rvin_write(vin, p_set->coeff_set[11], VNC4C_REG); + + rvin_write(vin, p_set->coeff_set[12], VNC5A_REG); + rvin_write(vin, p_set->coeff_set[13], VNC5B_REG); + rvin_write(vin, p_set->coeff_set[14], VNC5C_REG); + + rvin_write(vin, p_set->coeff_set[15], VNC6A_REG); + rvin_write(vin, p_set->coeff_set[16], VNC6B_REG); + rvin_write(vin, p_set->coeff_set[17], VNC6C_REG); + + rvin_write(vin, p_set->coeff_set[18], VNC7A_REG); + rvin_write(vin, p_set->coeff_set[19], VNC7B_REG); + rvin_write(vin, p_set->coeff_set[20], VNC7C_REG); + + rvin_write(vin, p_set->coeff_set[21], VNC8A_REG); + rvin_write(vin, p_set->coeff_set[22], VNC8B_REG); + rvin_write(vin, p_set->coeff_set[23], VNC8C_REG); +} + +static void rvin_crop_scale_comp_gen2(struct rvin_dev *vin) +{ + unsigned int crop_height; + u32 xs, ys; + + /* Set scaling coefficient */ + crop_height = vin->crop.height; + if (V4L2_FIELD_HAS_BOTH(vin->format.field)) + crop_height *= 2; + + ys = 0; + if (crop_height != vin->compose.height) + ys = (4096 * crop_height) / vin->compose.height; + rvin_write(vin, ys, VNYS_REG); + + xs = 0; + if (vin->crop.width != vin->compose.width) + xs = (4096 * vin->crop.width) / vin->compose.width; + + /* Horizontal upscaling is up to double size */ + if (xs > 0 && xs < 2048) + xs = 2048; + + rvin_write(vin, xs, VNXS_REG); + + /* Horizontal upscaling is done out by scaling down from double size */ + if (xs < 4096) + xs *= 2; + + rvin_set_coeff(vin, xs); + + /* Set Start/End Pixel/Line Post-Clip */ + rvin_write(vin, 0, VNSPPOC_REG); + rvin_write(vin, 0, VNSLPOC_REG); + rvin_write(vin, vin->format.width - 1, VNEPPOC_REG); + + if (V4L2_FIELD_HAS_BOTH(vin->format.field)) + rvin_write(vin, vin->format.height / 2 - 1, VNELPOC_REG); + else + rvin_write(vin, vin->format.height - 1, VNELPOC_REG); + + vin_dbg(vin, + "Pre-Clip: %ux%u@%u:%u YS: %d XS: %d Post-Clip: %ux%u@%u:%u\n", + vin->crop.width, vin->crop.height, vin->crop.left, + vin->crop.top, ys, xs, vin->format.width, vin->format.height, + 0, 0); +} + +void rvin_crop_scale_comp(struct rvin_dev *vin) +{ + const struct rvin_video_format *fmt; + u32 stride; + + /* Set Start/End Pixel/Line Pre-Clip */ + rvin_write(vin, vin->crop.left, VNSPPRC_REG); + rvin_write(vin, vin->crop.left + vin->crop.width - 1, VNEPPRC_REG); + rvin_write(vin, vin->crop.top, VNSLPRC_REG); + rvin_write(vin, vin->crop.top + vin->crop.height - 1, VNELPRC_REG); + + /* TODO: Add support for the UDS scaler. */ + if (vin->info->model != RCAR_GEN3) + rvin_crop_scale_comp_gen2(vin); + + fmt = rvin_format_from_pixel(vin, vin->format.pixelformat); + stride = vin->format.bytesperline / fmt->bpp; + + /* For RAW8 format bpp is 1, but the hardware process RAW8 + * format in 2 pixel unit hence configure VNIS_REG as stride / 2. + */ + switch (vin->format.pixelformat) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + case V4L2_PIX_FMT_GREY: + stride /= 2; + break; + default: + break; + } + + rvin_write(vin, stride, VNIS_REG); +} + +/* ----------------------------------------------------------------------------- + * Hardware setup + */ + +static int rvin_setup(struct rvin_dev *vin) +{ + u32 vnmc, dmr, dmr2, interrupts; + bool progressive = false, output_is_yuv = false, input_is_yuv = false; + + switch (vin->format.field) { + case V4L2_FIELD_TOP: + vnmc = VNMC_IM_ODD; + break; + case V4L2_FIELD_BOTTOM: + vnmc = VNMC_IM_EVEN; + break; + case V4L2_FIELD_INTERLACED: + /* Default to TB */ + vnmc = VNMC_IM_FULL; + /* Use BT if video standard can be read and is 60 Hz format */ + if (!vin->info->use_mc && vin->std & V4L2_STD_525_60) + vnmc = VNMC_IM_FULL | VNMC_FOC; + break; + case V4L2_FIELD_INTERLACED_TB: + vnmc = VNMC_IM_FULL; + break; + case V4L2_FIELD_INTERLACED_BT: + vnmc = VNMC_IM_FULL | VNMC_FOC; + break; + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + case V4L2_FIELD_NONE: + case V4L2_FIELD_ALTERNATE: + vnmc = VNMC_IM_ODD_EVEN; + progressive = true; + break; + default: + vnmc = VNMC_IM_ODD; + break; + } + + /* + * Input interface + */ + switch (vin->mbus_code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + /* BT.601/BT.1358 16bit YCbCr422 */ + vnmc |= VNMC_INF_YUV16; + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_UYVY8_1X16: + vnmc |= VNMC_INF_YUV16 | VNMC_YCAL; + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_UYVY8_2X8: + /* BT.656 8bit YCbCr422 or BT.601 8bit YCbCr422 */ + if (!vin->is_csi && + vin->parallel.mbus_type == V4L2_MBUS_BT656) + vnmc |= VNMC_INF_YUV8_BT656; + else + vnmc |= VNMC_INF_YUV8_BT601; + + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_RGB888_1X24: + vnmc |= VNMC_INF_RGB888; + break; + case MEDIA_BUS_FMT_UYVY10_2X10: + /* BT.656 10bit YCbCr422 or BT.601 10bit YCbCr422 */ + if (!vin->is_csi && + vin->parallel.mbus_type == V4L2_MBUS_BT656) + vnmc |= VNMC_INF_YUV10_BT656; + else + vnmc |= VNMC_INF_YUV10_BT601; + + input_is_yuv = true; + break; + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + case MEDIA_BUS_FMT_Y8_1X8: + vnmc |= VNMC_INF_RAW8; + break; + default: + break; + } + + /* Make sure input interface and input format is valid. */ + if (vin->info->model == RCAR_GEN3) { + switch (vnmc & VNMC_INF_MASK) { + case VNMC_INF_YUV8_BT656: + case VNMC_INF_YUV10_BT656: + case VNMC_INF_YUV16: + case VNMC_INF_RGB666: + if (vin->is_csi) { + vin_err(vin, "Invalid setting in MIPI CSI2\n"); + return -EINVAL; + } + break; + case VNMC_INF_RAW8: + if (!vin->is_csi) { + vin_err(vin, "Invalid setting in Digital Pins\n"); + return -EINVAL; + } + break; + default: + break; + } + } + + /* Enable VSYNC Field Toggle mode after one VSYNC input */ + if (vin->info->model == RCAR_GEN3) + dmr2 = VNDMR2_FTEV; + else + dmr2 = VNDMR2_FTEV | VNDMR2_VLV(1); + + if (!vin->is_csi) { + /* Hsync Signal Polarity Select */ + if (!(vin->parallel.bus.flags & V4L2_MBUS_HSYNC_ACTIVE_LOW)) + dmr2 |= VNDMR2_HPS; + + /* Vsync Signal Polarity Select */ + if (!(vin->parallel.bus.flags & V4L2_MBUS_VSYNC_ACTIVE_LOW)) + dmr2 |= VNDMR2_VPS; + + /* Data Enable Polarity Select */ + if (vin->parallel.bus.flags & V4L2_MBUS_DATA_ENABLE_LOW) + dmr2 |= VNDMR2_CES; + + switch (vin->mbus_code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + if (vin->parallel.bus.bus_width == 8 && + vin->parallel.bus.data_shift == 8) + dmr2 |= VNDMR2_YDS; + break; + default: + break; + } + } + + /* + * Output format + */ + switch (vin->format.pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + rvin_write(vin, + ALIGN(vin->format.bytesperline * vin->format.height, + 0x80), VNUVAOF_REG); + dmr = vin->format.pixelformat == V4L2_PIX_FMT_NV12 ? + VNDMR_DTMD_YCSEP_420 : VNDMR_DTMD_YCSEP; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_YUYV: + dmr = VNDMR_BPSM; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_UYVY: + dmr = 0; + output_is_yuv = true; + break; + case V4L2_PIX_FMT_XRGB555: + dmr = VNDMR_DTMD_ARGB; + break; + case V4L2_PIX_FMT_RGB565: + dmr = 0; + break; + case V4L2_PIX_FMT_XBGR32: + /* Note: not supported on M1 */ + dmr = VNDMR_EXRGB; + break; + case V4L2_PIX_FMT_ARGB555: + dmr = (vin->alpha ? VNDMR_ABIT : 0) | VNDMR_DTMD_ARGB; + break; + case V4L2_PIX_FMT_ABGR32: + dmr = VNDMR_A8BIT(vin->alpha) | VNDMR_EXRGB | VNDMR_DTMD_ARGB; + break; + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + dmr = 0; + break; + case V4L2_PIX_FMT_GREY: + if (input_is_yuv) { + dmr = VNDMR_DTMD_YCSEP | VNDMR_YMODE_Y8; + output_is_yuv = true; + } else { + dmr = 0; + } + break; + default: + vin_err(vin, "Invalid pixelformat (0x%x)\n", + vin->format.pixelformat); + return -EINVAL; + } + + /* Always update on field change */ + vnmc |= VNMC_VUP; + + if (!vin->info->use_isp) { + /* If input and output use the same colorspace, use bypass mode */ + if (input_is_yuv == output_is_yuv) + vnmc |= VNMC_BPS; + + if (vin->info->model == RCAR_GEN3) { + /* Select between CSI-2 and parallel input */ + if (vin->is_csi) + vnmc &= ~VNMC_DPINE; + else + vnmc |= VNMC_DPINE; + } + } + + /* Progressive or interlaced mode */ + interrupts = progressive ? VNIE_FIE : VNIE_EFE; + + /* Ack interrupts */ + rvin_write(vin, interrupts, VNINTS_REG); + /* Enable interrupts */ + rvin_write(vin, interrupts, VNIE_REG); + /* Start capturing */ + rvin_write(vin, dmr, VNDMR_REG); + rvin_write(vin, dmr2, VNDMR2_REG); + + /* Enable module */ + rvin_write(vin, vnmc | VNMC_ME, VNMC_REG); + + return 0; +} + +static void rvin_disable_interrupts(struct rvin_dev *vin) +{ + rvin_write(vin, 0, VNIE_REG); +} + +static u32 rvin_get_interrupt_status(struct rvin_dev *vin) +{ + return rvin_read(vin, VNINTS_REG); +} + +static void rvin_ack_interrupt(struct rvin_dev *vin) +{ + rvin_write(vin, rvin_read(vin, VNINTS_REG), VNINTS_REG); +} + +static bool rvin_capture_active(struct rvin_dev *vin) +{ + return rvin_read(vin, VNMS_REG) & VNMS_CA; +} + +static enum v4l2_field rvin_get_active_field(struct rvin_dev *vin, u32 vnms) +{ + if (vin->format.field == V4L2_FIELD_ALTERNATE) { + /* If FS is set it is an Even field. */ + if (vnms & VNMS_FS) + return V4L2_FIELD_BOTTOM; + return V4L2_FIELD_TOP; + } + + return vin->format.field; +} + +static void rvin_set_slot_addr(struct rvin_dev *vin, int slot, dma_addr_t addr) +{ + const struct rvin_video_format *fmt; + int offsetx, offsety; + dma_addr_t offset; + + fmt = rvin_format_from_pixel(vin, vin->format.pixelformat); + + /* + * There is no HW support for composition do the beast we can + * by modifying the buffer offset + */ + offsetx = vin->compose.left * fmt->bpp; + offsety = vin->compose.top * vin->format.bytesperline; + offset = addr + offsetx + offsety; + + /* + * The address needs to be 128 bytes aligned. Driver should never accept + * settings that do not satisfy this in the first place... + */ + if (WARN_ON((offsetx | offsety | offset) & HW_BUFFER_MASK)) + return; + + rvin_write(vin, offset, VNMB_REG(slot)); +} + +/* + * Moves a buffer from the queue to the HW slot. If no buffer is + * available use the scratch buffer. The scratch buffer is never + * returned to userspace, its only function is to enable the capture + * loop to keep running. + */ +static void rvin_fill_hw_slot(struct rvin_dev *vin, int slot) +{ + struct rvin_buffer *buf; + struct vb2_v4l2_buffer *vbuf; + dma_addr_t phys_addr; + int prev; + + /* A already populated slot shall never be overwritten. */ + if (WARN_ON(vin->buf_hw[slot].buffer)) + return; + + prev = (slot == 0 ? HW_BUFFER_NUM : slot) - 1; + + if (vin->buf_hw[prev].type == HALF_TOP) { + vbuf = vin->buf_hw[prev].buffer; + vin->buf_hw[slot].buffer = vbuf; + vin->buf_hw[slot].type = HALF_BOTTOM; + switch (vin->format.pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + phys_addr = vin->buf_hw[prev].phys + + vin->format.sizeimage / 4; + break; + default: + phys_addr = vin->buf_hw[prev].phys + + vin->format.sizeimage / 2; + break; + } + } else if ((vin->state != STOPPED && vin->state != RUNNING) || + list_empty(&vin->buf_list)) { + vin->buf_hw[slot].buffer = NULL; + vin->buf_hw[slot].type = FULL; + phys_addr = vin->scratch_phys; + } else { + /* Keep track of buffer we give to HW */ + buf = list_entry(vin->buf_list.next, struct rvin_buffer, list); + vbuf = &buf->vb; + list_del_init(to_buf_list(vbuf)); + vin->buf_hw[slot].buffer = vbuf; + + vin->buf_hw[slot].type = + V4L2_FIELD_IS_SEQUENTIAL(vin->format.field) ? + HALF_TOP : FULL; + + /* Setup DMA */ + phys_addr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + } + + vin_dbg(vin, "Filling HW slot: %d type: %d buffer: %p\n", + slot, vin->buf_hw[slot].type, vin->buf_hw[slot].buffer); + + vin->buf_hw[slot].phys = phys_addr; + rvin_set_slot_addr(vin, slot, phys_addr); +} + +static int rvin_capture_start(struct rvin_dev *vin) +{ + int slot, ret; + + for (slot = 0; slot < HW_BUFFER_NUM; slot++) { + vin->buf_hw[slot].buffer = NULL; + vin->buf_hw[slot].type = FULL; + } + + for (slot = 0; slot < HW_BUFFER_NUM; slot++) + rvin_fill_hw_slot(vin, slot); + + rvin_crop_scale_comp(vin); + + ret = rvin_setup(vin); + if (ret) + return ret; + + vin_dbg(vin, "Starting to capture\n"); + + /* Continuous Frame Capture Mode */ + rvin_write(vin, VNFC_C_FRAME, VNFC_REG); + + vin->state = STARTING; + + return 0; +} + +static void rvin_capture_stop(struct rvin_dev *vin) +{ + /* Set continuous & single transfer off */ + rvin_write(vin, 0, VNFC_REG); + + /* Disable module */ + rvin_write(vin, rvin_read(vin, VNMC_REG) & ~VNMC_ME, VNMC_REG); +} + +/* ----------------------------------------------------------------------------- + * DMA Functions + */ + +#define RVIN_TIMEOUT_MS 100 +#define RVIN_RETRIES 10 + +static irqreturn_t rvin_irq(int irq, void *data) +{ + struct rvin_dev *vin = data; + u32 int_status, vnms; + int slot; + unsigned int handled = 0; + unsigned long flags; + + spin_lock_irqsave(&vin->qlock, flags); + + int_status = rvin_get_interrupt_status(vin); + if (!int_status) + goto done; + + rvin_ack_interrupt(vin); + handled = 1; + + /* Nothing to do if nothing was captured. */ + if (!(int_status & VNINTS_FIS)) + goto done; + + /* Nothing to do if capture status is 'STOPPED' */ + if (vin->state == STOPPED) { + vin_dbg(vin, "IRQ while state stopped\n"); + goto done; + } + + /* Prepare for capture and update state */ + vnms = rvin_read(vin, VNMS_REG); + slot = (vnms & VNMS_FBS_MASK) >> VNMS_FBS_SHIFT; + + /* + * To hand buffers back in a known order to userspace start + * to capture first from slot 0. + */ + if (vin->state == STARTING) { + if (slot != 0) { + vin_dbg(vin, "Starting sync slot: %d\n", slot); + goto done; + } + + vin_dbg(vin, "Capture start synced!\n"); + vin->state = RUNNING; + } + + /* Capture frame */ + if (vin->buf_hw[slot].buffer) { + /* + * Nothing to do but refill the hardware slot if + * capture only filled first half of vb2 buffer. + */ + if (vin->buf_hw[slot].type == HALF_TOP) { + vin->buf_hw[slot].buffer = NULL; + rvin_fill_hw_slot(vin, slot); + goto done; + } + + vin->buf_hw[slot].buffer->field = + rvin_get_active_field(vin, vnms); + vin->buf_hw[slot].buffer->sequence = vin->sequence; + vin->buf_hw[slot].buffer->vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&vin->buf_hw[slot].buffer->vb2_buf, + VB2_BUF_STATE_DONE); + vin->buf_hw[slot].buffer = NULL; + } else { + /* Scratch buffer was used, dropping frame. */ + vin_dbg(vin, "Dropping frame %u\n", vin->sequence); + } + + vin->sequence++; + + /* Prepare for next frame */ + rvin_fill_hw_slot(vin, slot); +done: + spin_unlock_irqrestore(&vin->qlock, flags); + + return IRQ_RETVAL(handled); +} + +static void return_unused_buffers(struct rvin_dev *vin, + enum vb2_buffer_state state) +{ + struct rvin_buffer *buf, *node; + unsigned long flags; + + spin_lock_irqsave(&vin->qlock, flags); + + list_for_each_entry_safe(buf, node, &vin->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + + spin_unlock_irqrestore(&vin->qlock, flags); +} + +static int rvin_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) + +{ + struct rvin_dev *vin = vb2_get_drv_priv(vq); + + /* Make sure the image size is large enough. */ + if (*nplanes) + return sizes[0] < vin->format.sizeimage ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = vin->format.sizeimage; + + return 0; +}; + +static int rvin_buffer_prepare(struct vb2_buffer *vb) +{ + struct rvin_dev *vin = vb2_get_drv_priv(vb->vb2_queue); + unsigned long size = vin->format.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + vin_err(vin, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + return 0; +} + +static void rvin_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rvin_dev *vin = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&vin->qlock, flags); + + list_add_tail(to_buf_list(vbuf), &vin->buf_list); + + spin_unlock_irqrestore(&vin->qlock, flags); +} + +static int rvin_mc_validate_format(struct rvin_dev *vin, struct v4l2_subdev *sd, + struct media_pad *pad) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + fmt.pad = pad->index; + if (v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt)) + return -EPIPE; + + switch (fmt.format.code) { + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_UYVY10_2X10: + case MEDIA_BUS_FMT_RGB888_1X24: + break; + case MEDIA_BUS_FMT_SBGGR8_1X8: + if (vin->format.pixelformat != V4L2_PIX_FMT_SBGGR8) + return -EPIPE; + break; + case MEDIA_BUS_FMT_SGBRG8_1X8: + if (vin->format.pixelformat != V4L2_PIX_FMT_SGBRG8) + return -EPIPE; + break; + case MEDIA_BUS_FMT_SGRBG8_1X8: + if (vin->format.pixelformat != V4L2_PIX_FMT_SGRBG8) + return -EPIPE; + break; + case MEDIA_BUS_FMT_SRGGB8_1X8: + if (vin->format.pixelformat != V4L2_PIX_FMT_SRGGB8) + return -EPIPE; + break; + case MEDIA_BUS_FMT_Y8_1X8: + if (vin->format.pixelformat != V4L2_PIX_FMT_GREY) + return -EPIPE; + break; + default: + return -EPIPE; + } + vin->mbus_code = fmt.format.code; + + switch (fmt.format.field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + /* Supported natively */ + break; + case V4L2_FIELD_ALTERNATE: + switch (vin->format.field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_ALTERNATE: + break; + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + /* Use VIN hardware to combine the two fields */ + fmt.format.height *= 2; + break; + default: + return -EPIPE; + } + break; + default: + return -EPIPE; + } + + if (fmt.format.width != vin->format.width || + fmt.format.height != vin->format.height || + fmt.format.code != vin->mbus_code) + return -EPIPE; + + return 0; +} + +static int rvin_set_stream(struct rvin_dev *vin, int on) +{ + struct v4l2_subdev *sd; + struct media_pad *pad; + int ret; + + /* No media controller used, simply pass operation to subdevice. */ + if (!vin->info->use_mc) { + ret = v4l2_subdev_call(vin->parallel.subdev, video, s_stream, + on); + + return ret == -ENOIOCTLCMD ? 0 : ret; + } + + pad = media_pad_remote_pad_first(&vin->pad); + if (!pad) + return -EPIPE; + + sd = media_entity_to_v4l2_subdev(pad->entity); + + if (!on) { + video_device_pipeline_stop(&vin->vdev); + return v4l2_subdev_call(sd, video, s_stream, 0); + } + + ret = rvin_mc_validate_format(vin, sd, pad); + if (ret) + return ret; + + ret = video_device_pipeline_alloc_start(&vin->vdev); + if (ret) + return ret; + + ret = v4l2_subdev_call(sd, video, s_stream, 1); + if (ret == -ENOIOCTLCMD) + ret = 0; + if (ret) + video_device_pipeline_stop(&vin->vdev); + + return ret; +} + +int rvin_start_streaming(struct rvin_dev *vin) +{ + unsigned long flags; + int ret; + + ret = rvin_set_stream(vin, 1); + if (ret) + return ret; + + spin_lock_irqsave(&vin->qlock, flags); + + vin->sequence = 0; + + ret = rvin_capture_start(vin); + if (ret) + rvin_set_stream(vin, 0); + + spin_unlock_irqrestore(&vin->qlock, flags); + + return ret; +} + +static int rvin_start_streaming_vq(struct vb2_queue *vq, unsigned int count) +{ + struct rvin_dev *vin = vb2_get_drv_priv(vq); + int ret = -ENOMEM; + + /* Allocate scratch buffer. */ + vin->scratch = dma_alloc_coherent(vin->dev, vin->format.sizeimage, + &vin->scratch_phys, GFP_KERNEL); + if (!vin->scratch) + goto err_scratch; + + ret = rvin_start_streaming(vin); + if (ret) + goto err_start; + + return 0; +err_start: + dma_free_coherent(vin->dev, vin->format.sizeimage, vin->scratch, + vin->scratch_phys); +err_scratch: + return_unused_buffers(vin, VB2_BUF_STATE_QUEUED); + + return ret; +} + +void rvin_stop_streaming(struct rvin_dev *vin) +{ + unsigned int i, retries; + unsigned long flags; + bool buffersFreed; + + spin_lock_irqsave(&vin->qlock, flags); + + if (vin->state == STOPPED) { + spin_unlock_irqrestore(&vin->qlock, flags); + return; + } + + vin->state = STOPPING; + + /* Wait until only scratch buffer is used, max 3 interrupts. */ + retries = 0; + while (retries++ < RVIN_RETRIES) { + buffersFreed = true; + for (i = 0; i < HW_BUFFER_NUM; i++) + if (vin->buf_hw[i].buffer) + buffersFreed = false; + + if (buffersFreed) + break; + + spin_unlock_irqrestore(&vin->qlock, flags); + msleep(RVIN_TIMEOUT_MS); + spin_lock_irqsave(&vin->qlock, flags); + } + + /* Wait for streaming to stop */ + retries = 0; + while (retries++ < RVIN_RETRIES) { + + rvin_capture_stop(vin); + + /* Check if HW is stopped */ + if (!rvin_capture_active(vin)) { + vin->state = STOPPED; + break; + } + + spin_unlock_irqrestore(&vin->qlock, flags); + msleep(RVIN_TIMEOUT_MS); + spin_lock_irqsave(&vin->qlock, flags); + } + + if (!buffersFreed || vin->state != STOPPED) { + /* + * If this happens something have gone horribly wrong. + * Set state to stopped to prevent the interrupt handler + * to make things worse... + */ + vin_err(vin, "Failed stop HW, something is seriously broken\n"); + vin->state = STOPPED; + } + + spin_unlock_irqrestore(&vin->qlock, flags); + + /* If something went wrong, free buffers with an error. */ + if (!buffersFreed) { + return_unused_buffers(vin, VB2_BUF_STATE_ERROR); + for (i = 0; i < HW_BUFFER_NUM; i++) { + if (vin->buf_hw[i].buffer) + vb2_buffer_done(&vin->buf_hw[i].buffer->vb2_buf, + VB2_BUF_STATE_ERROR); + } + } + + rvin_set_stream(vin, 0); + + /* disable interrupts */ + rvin_disable_interrupts(vin); +} + +static void rvin_stop_streaming_vq(struct vb2_queue *vq) +{ + struct rvin_dev *vin = vb2_get_drv_priv(vq); + + rvin_stop_streaming(vin); + + /* Free scratch buffer. */ + dma_free_coherent(vin->dev, vin->format.sizeimage, vin->scratch, + vin->scratch_phys); + + return_unused_buffers(vin, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops rvin_qops = { + .queue_setup = rvin_queue_setup, + .buf_prepare = rvin_buffer_prepare, + .buf_queue = rvin_buffer_queue, + .start_streaming = rvin_start_streaming_vq, + .stop_streaming = rvin_stop_streaming_vq, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +void rvin_dma_unregister(struct rvin_dev *vin) +{ + mutex_destroy(&vin->lock); + + v4l2_device_unregister(&vin->v4l2_dev); +} + +int rvin_dma_register(struct rvin_dev *vin, int irq) +{ + struct vb2_queue *q = &vin->queue; + int i, ret; + + /* Initialize the top-level structure */ + ret = v4l2_device_register(vin->dev, &vin->v4l2_dev); + if (ret) + return ret; + + mutex_init(&vin->lock); + INIT_LIST_HEAD(&vin->buf_list); + + spin_lock_init(&vin->qlock); + + vin->state = STOPPED; + + for (i = 0; i < HW_BUFFER_NUM; i++) + vin->buf_hw[i].buffer = NULL; + + /* buffer queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + q->lock = &vin->lock; + q->drv_priv = vin; + q->buf_struct_size = sizeof(struct rvin_buffer); + q->ops = &rvin_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 4; + q->dev = vin->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + vin_err(vin, "failed to initialize VB2 queue\n"); + goto error; + } + + /* irq */ + ret = devm_request_irq(vin->dev, irq, rvin_irq, IRQF_SHARED, + KBUILD_MODNAME, vin); + if (ret) { + vin_err(vin, "failed to request irq\n"); + goto error; + } + + return 0; +error: + rvin_dma_unregister(vin); + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Gen3 CHSEL manipulation + */ + +/* + * There is no need to have locking around changing the routing + * as it's only possible to do so when no VIN in the group is + * streaming so nothing can race with the VNMC register. + */ +int rvin_set_channel_routing(struct rvin_dev *vin, u8 chsel) +{ + const struct rvin_group_route *route; + u32 ifmd = 0; + u32 vnmc; + int ret; + + ret = pm_runtime_resume_and_get(vin->dev); + if (ret < 0) + return ret; + + /* Make register writes take effect immediately. */ + vnmc = rvin_read(vin, VNMC_REG); + rvin_write(vin, vnmc & ~VNMC_VUP, VNMC_REG); + + /* + * Set data expansion mode to "pad with 0s" by inspecting the routes + * table to find out which bit fields are available in the IFMD + * register. IFMD_DES1 controls data expansion mode for CSI20/21, + * IFMD_DES0 controls data expansion mode for CSI40/41. + */ + for (route = vin->info->routes; route->chsel; route++) { + if (route->csi == RVIN_CSI20 || route->csi == RVIN_CSI21) + ifmd |= VNCSI_IFMD_DES1; + else + ifmd |= VNCSI_IFMD_DES0; + + if (ifmd == (VNCSI_IFMD_DES0 | VNCSI_IFMD_DES1)) + break; + } + + if (ifmd) { + ifmd |= VNCSI_IFMD_CSI_CHSEL(chsel); + rvin_write(vin, ifmd, VNCSI_IFMD_REG); + } + + vin_dbg(vin, "Set IFMD 0x%x\n", ifmd); + + vin->chsel = chsel; + + /* Restore VNMC. */ + rvin_write(vin, vnmc, VNMC_REG); + + pm_runtime_put(vin->dev); + + return 0; +} + +void rvin_set_alpha(struct rvin_dev *vin, unsigned int alpha) +{ + unsigned long flags; + u32 dmr; + + spin_lock_irqsave(&vin->qlock, flags); + + vin->alpha = alpha; + + if (vin->state == STOPPED) + goto out; + + switch (vin->format.pixelformat) { + case V4L2_PIX_FMT_ARGB555: + dmr = rvin_read(vin, VNDMR_REG) & ~VNDMR_ABIT; + if (vin->alpha) + dmr |= VNDMR_ABIT; + break; + case V4L2_PIX_FMT_ABGR32: + dmr = rvin_read(vin, VNDMR_REG) & ~VNDMR_A8BIT_MASK; + dmr |= VNDMR_A8BIT(vin->alpha); + break; + default: + goto out; + } + + rvin_write(vin, dmr, VNDMR_REG); +out: + spin_unlock_irqrestore(&vin->qlock, flags); +} diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c new file mode 100644 index 000000000..576059f9b --- /dev/null +++ b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c @@ -0,0 +1,1093 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Driver for Renesas R-Car VIN + * + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., + * Copyright (C) 2008 Magnus Damm + * + * Based on the soc-camera rcar_vin driver + */ + +#include + +#include +#include +#include +#include + +#include "rcar-vin.h" + +#define RVIN_DEFAULT_FORMAT V4L2_PIX_FMT_YUYV +#define RVIN_DEFAULT_WIDTH 800 +#define RVIN_DEFAULT_HEIGHT 600 +#define RVIN_DEFAULT_FIELD V4L2_FIELD_NONE +#define RVIN_DEFAULT_COLORSPACE V4L2_COLORSPACE_SRGB + +/* ----------------------------------------------------------------------------- + * Format Conversions + */ + +static const struct rvin_video_format rvin_formats[] = { + { + .fourcc = V4L2_PIX_FMT_NV12, + .bpp = 1, + }, + { + .fourcc = V4L2_PIX_FMT_NV16, + .bpp = 1, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_XRGB555, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_XBGR32, + .bpp = 4, + }, + { + .fourcc = V4L2_PIX_FMT_ARGB555, + .bpp = 2, + }, + { + .fourcc = V4L2_PIX_FMT_ABGR32, + .bpp = 4, + }, + { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .bpp = 1, + }, + { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .bpp = 1, + }, + { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .bpp = 1, + }, + { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .bpp = 1, + }, + { + .fourcc = V4L2_PIX_FMT_GREY, + .bpp = 1, + }, +}; + +const struct rvin_video_format *rvin_format_from_pixel(struct rvin_dev *vin, + u32 pixelformat) +{ + int i; + + switch (pixelformat) { + case V4L2_PIX_FMT_XBGR32: + if (vin->info->model == RCAR_M1) + return NULL; + break; + case V4L2_PIX_FMT_NV12: + /* + * If NV12 is supported it's only supported on channels 0, 1, 4, + * 5, 8, 9, 12 and 13. + */ + if (!vin->info->nv12 || !(BIT(vin->id) & 0x3333)) + return NULL; + break; + default: + break; + } + + for (i = 0; i < ARRAY_SIZE(rvin_formats); i++) + if (rvin_formats[i].fourcc == pixelformat) + return rvin_formats + i; + + return NULL; +} + +static u32 rvin_format_bytesperline(struct rvin_dev *vin, + struct v4l2_pix_format *pix) +{ + const struct rvin_video_format *fmt; + u32 align; + + fmt = rvin_format_from_pixel(vin, pix->pixelformat); + + if (WARN_ON(!fmt)) + return -EINVAL; + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + align = 0x20; + break; + default: + align = 0x10; + break; + } + + if (V4L2_FIELD_IS_SEQUENTIAL(pix->field)) + align = 0x80; + + return ALIGN(pix->width, align) * fmt->bpp; +} + +static u32 rvin_format_sizeimage(struct v4l2_pix_format *pix) +{ + switch (pix->pixelformat) { + case V4L2_PIX_FMT_NV12: + return pix->bytesperline * pix->height * 3 / 2; + case V4L2_PIX_FMT_NV16: + return pix->bytesperline * pix->height * 2; + default: + return pix->bytesperline * pix->height; + } +} + +static void rvin_format_align(struct rvin_dev *vin, struct v4l2_pix_format *pix) +{ + u32 walign; + + if (!rvin_format_from_pixel(vin, pix->pixelformat)) + pix->pixelformat = RVIN_DEFAULT_FORMAT; + + switch (pix->field) { + case V4L2_FIELD_TOP: + case V4L2_FIELD_BOTTOM: + case V4L2_FIELD_NONE: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_ALTERNATE: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + break; + default: + pix->field = RVIN_DEFAULT_FIELD; + break; + } + + /* Hardware limits width alignment based on format. */ + switch (pix->pixelformat) { + /* Multiple of 32 (2^5) for NV12/16. */ + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + walign = 5; + break; + /* Multiple of 2 (2^1) for YUV. */ + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + walign = 1; + break; + /* No multiple for RGB. */ + default: + walign = 0; + break; + } + + /* Limit to VIN capabilities */ + v4l_bound_align_image(&pix->width, 5, vin->info->max_width, walign, + &pix->height, 2, vin->info->max_height, 0, 0); + + pix->bytesperline = rvin_format_bytesperline(vin, pix); + pix->sizeimage = rvin_format_sizeimage(pix); + + vin_dbg(vin, "Format %ux%u bpl: %u size: %u\n", + pix->width, pix->height, pix->bytesperline, pix->sizeimage); +} + +/* ----------------------------------------------------------------------------- + * V4L2 + */ + +static int rvin_reset_format(struct rvin_dev *vin) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = vin->parallel.source_pad, + }; + int ret; + + ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + v4l2_fill_pix_format(&vin->format, &fmt.format); + + vin->src_rect.top = 0; + vin->src_rect.left = 0; + vin->src_rect.width = vin->format.width; + vin->src_rect.height = vin->format.height; + + /* Make use of the hardware interlacer by default. */ + if (vin->format.field == V4L2_FIELD_ALTERNATE) { + vin->format.field = V4L2_FIELD_INTERLACED; + vin->format.height *= 2; + } + + rvin_format_align(vin, &vin->format); + + vin->crop = vin->src_rect; + + vin->compose.top = 0; + vin->compose.left = 0; + vin->compose.width = vin->format.width; + vin->compose.height = vin->format.height; + + return 0; +} + +static int rvin_try_format(struct rvin_dev *vin, u32 which, + struct v4l2_pix_format *pix, + struct v4l2_rect *src_rect) +{ + struct v4l2_subdev *sd = vin_to_source(vin); + struct v4l2_subdev_state *sd_state; + static struct lock_class_key key; + struct v4l2_subdev_format format = { + .which = which, + .pad = vin->parallel.source_pad, + }; + enum v4l2_field field; + u32 width, height; + int ret; + + /* + * FIXME: Drop this call, drivers are not supposed to use + * __v4l2_subdev_state_alloc(). + */ + sd_state = __v4l2_subdev_state_alloc(sd, "rvin:state->lock", &key); + if (IS_ERR(sd_state)) + return PTR_ERR(sd_state); + + if (!rvin_format_from_pixel(vin, pix->pixelformat)) + pix->pixelformat = RVIN_DEFAULT_FORMAT; + + v4l2_fill_mbus_format(&format.format, pix, vin->mbus_code); + + /* Allow the video device to override field and to scale */ + field = pix->field; + width = pix->width; + height = pix->height; + + ret = v4l2_subdev_call(sd, pad, set_fmt, sd_state, &format); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto done; + ret = 0; + + v4l2_fill_pix_format(pix, &format.format); + + if (src_rect) { + src_rect->top = 0; + src_rect->left = 0; + src_rect->width = pix->width; + src_rect->height = pix->height; + } + + if (field != V4L2_FIELD_ANY) + pix->field = field; + + pix->width = width; + pix->height = height; + + rvin_format_align(vin, pix); +done: + __v4l2_subdev_state_free(sd_state); + + return ret; +} + +static int rvin_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "R_Car_VIN", sizeof(cap->card)); + return 0; +} + +static int rvin_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + + return rvin_try_format(vin, V4L2_SUBDEV_FORMAT_TRY, &f->fmt.pix, NULL); +} + +static int rvin_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_rect fmt_rect, src_rect; + int ret; + + if (vb2_is_busy(&vin->queue)) + return -EBUSY; + + ret = rvin_try_format(vin, V4L2_SUBDEV_FORMAT_ACTIVE, &f->fmt.pix, + &src_rect); + if (ret) + return ret; + + vin->format = f->fmt.pix; + + fmt_rect.top = 0; + fmt_rect.left = 0; + fmt_rect.width = vin->format.width; + fmt_rect.height = vin->format.height; + + v4l2_rect_map_inside(&vin->crop, &src_rect); + v4l2_rect_map_inside(&vin->compose, &fmt_rect); + vin->src_rect = src_rect; + + return 0; +} + +static int rvin_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + + f->fmt.pix = vin->format; + + return 0; +} + +static int rvin_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct rvin_dev *vin = video_drvdata(file); + unsigned int i; + int matched; + + /* + * If mbus_code is set only enumerate supported pixel formats for that + * bus code. Converting from YCbCr to RGB and RGB to YCbCr is possible + * with VIN, so all supported YCbCr and RGB media bus codes can produce + * all of the related pixel formats. If mbus_code is not set enumerate + * all possible pixelformats. + * + * TODO: Once raw MEDIA_BUS_FMT_SRGGB12_1X12 format is added to the + * driver this needs to be extended so raw media bus code only result in + * raw pixel format. + */ + switch (f->mbus_code) { + case 0: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_UYVY10_2X10: + case MEDIA_BUS_FMT_RGB888_1X24: + break; + case MEDIA_BUS_FMT_SBGGR8_1X8: + if (f->index) + return -EINVAL; + f->pixelformat = V4L2_PIX_FMT_SBGGR8; + return 0; + case MEDIA_BUS_FMT_SGBRG8_1X8: + if (f->index) + return -EINVAL; + f->pixelformat = V4L2_PIX_FMT_SGBRG8; + return 0; + case MEDIA_BUS_FMT_SGRBG8_1X8: + if (f->index) + return -EINVAL; + f->pixelformat = V4L2_PIX_FMT_SGRBG8; + return 0; + case MEDIA_BUS_FMT_SRGGB8_1X8: + if (f->index) + return -EINVAL; + f->pixelformat = V4L2_PIX_FMT_SRGGB8; + return 0; + default: + return -EINVAL; + } + + matched = -1; + for (i = 0; i < ARRAY_SIZE(rvin_formats); i++) { + if (rvin_format_from_pixel(vin, rvin_formats[i].fourcc)) + matched++; + + if (matched == f->index) { + f->pixelformat = rvin_formats[i].fourcc; + return 0; + } + } + + return -EINVAL; +} + +static int rvin_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct rvin_dev *vin = video_drvdata(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = s->r.top = 0; + s->r.width = vin->src_rect.width; + s->r.height = vin->src_rect.height; + break; + case V4L2_SEL_TGT_CROP: + s->r = vin->crop; + break; + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r.left = s->r.top = 0; + s->r.width = vin->format.width; + s->r.height = vin->format.height; + break; + case V4L2_SEL_TGT_COMPOSE: + s->r = vin->compose; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int rvin_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct rvin_dev *vin = video_drvdata(file); + const struct rvin_video_format *fmt; + struct v4l2_rect r = s->r; + struct v4l2_rect max_rect; + struct v4l2_rect min_rect = { + .width = 6, + .height = 2, + }; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + v4l2_rect_set_min_size(&r, &min_rect); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + /* Can't crop outside of source input */ + max_rect.top = max_rect.left = 0; + max_rect.width = vin->src_rect.width; + max_rect.height = vin->src_rect.height; + v4l2_rect_map_inside(&r, &max_rect); + + v4l_bound_align_image(&r.width, 6, vin->src_rect.width, 0, + &r.height, 2, vin->src_rect.height, 0, 0); + + r.top = clamp_t(s32, r.top, 0, + vin->src_rect.height - r.height); + r.left = clamp_t(s32, r.left, 0, vin->src_rect.width - r.width); + + vin->crop = s->r = r; + + vin_dbg(vin, "Cropped %dx%d@%d:%d of %dx%d\n", + r.width, r.height, r.left, r.top, + vin->src_rect.width, vin->src_rect.height); + break; + case V4L2_SEL_TGT_COMPOSE: + /* Make sure compose rect fits inside output format */ + max_rect.top = max_rect.left = 0; + max_rect.width = vin->format.width; + max_rect.height = vin->format.height; + v4l2_rect_map_inside(&r, &max_rect); + + /* + * Composing is done by adding a offset to the buffer address, + * the HW wants this address to be aligned to HW_BUFFER_MASK. + * Make sure the top and left values meets this requirement. + */ + while ((r.top * vin->format.bytesperline) & HW_BUFFER_MASK) + r.top--; + + fmt = rvin_format_from_pixel(vin, vin->format.pixelformat); + while ((r.left * fmt->bpp) & HW_BUFFER_MASK) + r.left--; + + vin->compose = s->r = r; + + vin_dbg(vin, "Compose %dx%d@%d:%d in %dx%d\n", + r.width, r.height, r.left, r.top, + vin->format.width, vin->format.height); + break; + default: + return -EINVAL; + } + + /* HW supports modifying configuration while running */ + rvin_crop_scale_comp(vin); + + return 0; +} + +static int rvin_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_g_parm_cap(&vin->vdev, sd, parm); +} + +static int rvin_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *parm) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_s_parm_cap(&vin->vdev, sd, parm); +} + +static int rvin_g_pixelaspect(struct file *file, void *priv, + int type, struct v4l2_fract *f) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + return v4l2_subdev_call(sd, video, g_pixelaspect, f); +} + +static int rvin_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int ret; + + if (i->index != 0) + return -EINVAL; + + ret = v4l2_subdev_call(sd, video, g_input_status, &i->status); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + i->type = V4L2_INPUT_TYPE_CAMERA; + + if (v4l2_subdev_has_op(sd, pad, dv_timings_cap)) { + i->capabilities = V4L2_IN_CAP_DV_TIMINGS; + i->std = 0; + } else { + i->capabilities = V4L2_IN_CAP_STD; + i->std = vin->vdev.tvnorms; + } + + strscpy(i->name, "Camera", sizeof(i->name)); + + return 0; +} + +static int rvin_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int rvin_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + return 0; +} + +static int rvin_querystd(struct file *file, void *priv, v4l2_std_id *a) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_subdev_call(sd, video, querystd, a); +} + +static int rvin_s_std(struct file *file, void *priv, v4l2_std_id a) +{ + struct rvin_dev *vin = video_drvdata(file); + int ret; + + ret = v4l2_subdev_call(vin_to_source(vin), video, s_std, a); + if (ret < 0) + return ret; + + vin->std = a; + + /* Changing the standard will change the width/height */ + return rvin_reset_format(vin); +} + +static int rvin_g_std(struct file *file, void *priv, v4l2_std_id *a) +{ + struct rvin_dev *vin = video_drvdata(file); + + if (v4l2_subdev_has_op(vin_to_source(vin), pad, dv_timings_cap)) + return -ENOIOCTLCMD; + + *a = vin->std; + + return 0; +} + +static int rvin_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_event_subscribe(fh, sub, 4, NULL); + } + return v4l2_ctrl_subscribe_event(fh, sub); +} + +static int rvin_enum_dv_timings(struct file *file, void *priv_fh, + struct v4l2_enum_dv_timings *timings) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int ret; + + if (timings->pad) + return -EINVAL; + + timings->pad = vin->parallel.sink_pad; + + ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings); + + timings->pad = 0; + + return ret; +} + +static int rvin_s_dv_timings(struct file *file, void *priv_fh, + struct v4l2_dv_timings *timings) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int ret; + + ret = v4l2_subdev_call(sd, video, s_dv_timings, timings); + if (ret) + return ret; + + /* Changing the timings will change the width/height */ + return rvin_reset_format(vin); +} + +static int rvin_g_dv_timings(struct file *file, void *priv_fh, + struct v4l2_dv_timings *timings) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_subdev_call(sd, video, g_dv_timings, timings); +} + +static int rvin_query_dv_timings(struct file *file, void *priv_fh, + struct v4l2_dv_timings *timings) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + + return v4l2_subdev_call(sd, video, query_dv_timings, timings); +} + +static int rvin_dv_timings_cap(struct file *file, void *priv_fh, + struct v4l2_dv_timings_cap *cap) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int ret; + + if (cap->pad) + return -EINVAL; + + cap->pad = vin->parallel.sink_pad; + + ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap); + + cap->pad = 0; + + return ret; +} + +static int rvin_g_edid(struct file *file, void *fh, struct v4l2_edid *edid) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int ret; + + if (edid->pad) + return -EINVAL; + + edid->pad = vin->parallel.sink_pad; + + ret = v4l2_subdev_call(sd, pad, get_edid, edid); + + edid->pad = 0; + + return ret; +} + +static int rvin_s_edid(struct file *file, void *fh, struct v4l2_edid *edid) +{ + struct rvin_dev *vin = video_drvdata(file); + struct v4l2_subdev *sd = vin_to_source(vin); + int ret; + + if (edid->pad) + return -EINVAL; + + edid->pad = vin->parallel.sink_pad; + + ret = v4l2_subdev_call(sd, pad, set_edid, edid); + + edid->pad = 0; + + return ret; +} + +static const struct v4l2_ioctl_ops rvin_ioctl_ops = { + .vidioc_querycap = rvin_querycap, + .vidioc_try_fmt_vid_cap = rvin_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = rvin_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = rvin_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap, + + .vidioc_g_selection = rvin_g_selection, + .vidioc_s_selection = rvin_s_selection, + + .vidioc_g_parm = rvin_g_parm, + .vidioc_s_parm = rvin_s_parm, + + .vidioc_g_pixelaspect = rvin_g_pixelaspect, + + .vidioc_enum_input = rvin_enum_input, + .vidioc_g_input = rvin_g_input, + .vidioc_s_input = rvin_s_input, + + .vidioc_dv_timings_cap = rvin_dv_timings_cap, + .vidioc_enum_dv_timings = rvin_enum_dv_timings, + .vidioc_g_dv_timings = rvin_g_dv_timings, + .vidioc_s_dv_timings = rvin_s_dv_timings, + .vidioc_query_dv_timings = rvin_query_dv_timings, + + .vidioc_g_edid = rvin_g_edid, + .vidioc_s_edid = rvin_s_edid, + + .vidioc_querystd = rvin_querystd, + .vidioc_g_std = rvin_g_std, + .vidioc_s_std = rvin_s_std, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = rvin_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 Media Controller + */ + +static void rvin_mc_try_format(struct rvin_dev *vin, + struct v4l2_pix_format *pix) +{ + /* + * The V4L2 specification clearly documents the colorspace fields + * as being set by drivers for capture devices. Using the values + * supplied by userspace thus wouldn't comply with the API. Until + * the API is updated force fixed values. + */ + pix->colorspace = RVIN_DEFAULT_COLORSPACE; + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace, + pix->ycbcr_enc); + + rvin_format_align(vin, pix); +} + +static int rvin_mc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + + rvin_mc_try_format(vin, &f->fmt.pix); + + return 0; +} + +static int rvin_mc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rvin_dev *vin = video_drvdata(file); + + if (vb2_is_busy(&vin->queue)) + return -EBUSY; + + rvin_mc_try_format(vin, &f->fmt.pix); + + vin->format = f->fmt.pix; + + vin->crop.top = 0; + vin->crop.left = 0; + vin->crop.width = vin->format.width; + vin->crop.height = vin->format.height; + vin->compose = vin->crop; + + return 0; +} + +static const struct v4l2_ioctl_ops rvin_mc_ioctl_ops = { + .vidioc_querycap = rvin_querycap, + .vidioc_try_fmt_vid_cap = rvin_mc_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = rvin_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = rvin_mc_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = rvin_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* ----------------------------------------------------------------------------- + * File Operations + */ + +static int rvin_power_parallel(struct rvin_dev *vin, bool on) +{ + struct v4l2_subdev *sd = vin_to_source(vin); + int power = on ? 1 : 0; + int ret; + + ret = v4l2_subdev_call(sd, core, s_power, power); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + return ret; + + return 0; +} + +static int rvin_open(struct file *file) +{ + struct rvin_dev *vin = video_drvdata(file); + int ret; + + ret = pm_runtime_resume_and_get(vin->dev); + if (ret < 0) + return ret; + + ret = mutex_lock_interruptible(&vin->lock); + if (ret) + goto err_pm; + + file->private_data = vin; + + ret = v4l2_fh_open(file); + if (ret) + goto err_unlock; + + if (vin->info->use_mc) + ret = v4l2_pipeline_pm_get(&vin->vdev.entity); + else if (v4l2_fh_is_singular_file(file)) + ret = rvin_power_parallel(vin, true); + + if (ret < 0) + goto err_open; + + ret = v4l2_ctrl_handler_setup(&vin->ctrl_handler); + if (ret) + goto err_power; + + mutex_unlock(&vin->lock); + + return 0; +err_power: + if (vin->info->use_mc) + v4l2_pipeline_pm_put(&vin->vdev.entity); + else if (v4l2_fh_is_singular_file(file)) + rvin_power_parallel(vin, false); +err_open: + v4l2_fh_release(file); +err_unlock: + mutex_unlock(&vin->lock); +err_pm: + pm_runtime_put(vin->dev); + + return ret; +} + +static int rvin_release(struct file *file) +{ + struct rvin_dev *vin = video_drvdata(file); + bool fh_singular; + int ret; + + mutex_lock(&vin->lock); + + /* Save the singular status before we call the clean-up helper */ + fh_singular = v4l2_fh_is_singular_file(file); + + /* the release helper will cleanup any on-going streaming */ + ret = _vb2_fop_release(file, NULL); + + if (vin->info->use_mc) { + v4l2_pipeline_pm_put(&vin->vdev.entity); + } else { + if (fh_singular) + rvin_power_parallel(vin, false); + } + + mutex_unlock(&vin->lock); + + pm_runtime_put(vin->dev); + + return ret; +} + +static const struct v4l2_file_operations rvin_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = rvin_open, + .release = rvin_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +void rvin_v4l2_unregister(struct rvin_dev *vin) +{ + if (!video_is_registered(&vin->vdev)) + return; + + v4l2_info(&vin->v4l2_dev, "Removing %s\n", + video_device_node_name(&vin->vdev)); + + /* Checks internally if vdev have been init or not */ + video_unregister_device(&vin->vdev); +} + +static void rvin_notify_video_device(struct rvin_dev *vin, + unsigned int notification, void *arg) +{ + switch (notification) { + case V4L2_DEVICE_NOTIFY_EVENT: + v4l2_event_queue(&vin->vdev, arg); + break; + default: + break; + } +} + +static void rvin_notify(struct v4l2_subdev *sd, + unsigned int notification, void *arg) +{ + struct v4l2_subdev *remote; + struct rvin_group *group; + struct media_pad *pad; + struct rvin_dev *vin = + container_of(sd->v4l2_dev, struct rvin_dev, v4l2_dev); + unsigned int i; + + /* If no media controller, no need to route the event. */ + if (!vin->info->use_mc) { + rvin_notify_video_device(vin, notification, arg); + return; + } + + group = vin->group; + + for (i = 0; i < RCAR_VIN_NUM; i++) { + vin = group->vin[i]; + if (!vin) + continue; + + pad = media_pad_remote_pad_first(&vin->pad); + if (!pad) + continue; + + remote = media_entity_to_v4l2_subdev(pad->entity); + if (remote != sd) + continue; + + rvin_notify_video_device(vin, notification, arg); + } +} + +int rvin_v4l2_register(struct rvin_dev *vin) +{ + struct video_device *vdev = &vin->vdev; + int ret; + + vin->v4l2_dev.notify = rvin_notify; + + /* video node */ + vdev->v4l2_dev = &vin->v4l2_dev; + vdev->queue = &vin->queue; + snprintf(vdev->name, sizeof(vdev->name), "VIN%u output", vin->id); + vdev->release = video_device_release_empty; + vdev->lock = &vin->lock; + vdev->fops = &rvin_fops; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + + /* Set a default format */ + vin->format.pixelformat = RVIN_DEFAULT_FORMAT; + vin->format.width = RVIN_DEFAULT_WIDTH; + vin->format.height = RVIN_DEFAULT_HEIGHT; + vin->format.field = RVIN_DEFAULT_FIELD; + vin->format.colorspace = RVIN_DEFAULT_COLORSPACE; + + if (vin->info->use_mc) { + vdev->device_caps |= V4L2_CAP_IO_MC; + vdev->ioctl_ops = &rvin_mc_ioctl_ops; + } else { + vdev->ioctl_ops = &rvin_ioctl_ops; + rvin_reset_format(vin); + } + + rvin_format_align(vin, &vin->format); + + ret = video_register_device(&vin->vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + vin_err(vin, "Failed to register video device\n"); + return ret; + } + + video_set_drvdata(&vin->vdev, vin); + + v4l2_info(&vin->v4l2_dev, "Device registered as %s\n", + video_device_node_name(&vin->vdev)); + + return ret; +} diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h new file mode 100644 index 000000000..1f94589d9 --- /dev/null +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -0,0 +1,315 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Driver for Renesas R-Car VIN + * + * Copyright (C) 2016 Renesas Electronics Corp. + * Copyright (C) 2011-2013 Renesas Solutions Corp. + * Copyright (C) 2013 Cogent Embedded, Inc., + * Copyright (C) 2008 Magnus Damm + * + * Based on the soc-camera rcar_vin driver + */ + +#ifndef __RCAR_VIN__ +#define __RCAR_VIN__ + +#include + +#include +#include +#include +#include +#include +#include + +/* Number of HW buffers */ +#define HW_BUFFER_NUM 3 + +/* Address alignment mask for HW buffers */ +#define HW_BUFFER_MASK 0x7f + +/* Max number on VIN instances that can be in a system */ +#define RCAR_VIN_NUM 32 + +struct rvin_group; + +enum model_id { + RCAR_H1, + RCAR_M1, + RCAR_GEN2, + RCAR_GEN3, +}; + +enum rvin_csi_id { + RVIN_CSI20, + RVIN_CSI21, + RVIN_CSI40, + RVIN_CSI41, + RVIN_CSI_MAX, +}; + +enum rvin_isp_id { + RVIN_ISP0, + RVIN_ISP1, + RVIN_ISP2, + RVIN_ISP4, + RVIN_ISP_MAX, +}; + +#define RVIN_REMOTES_MAX \ + (((unsigned int)RVIN_CSI_MAX) > ((unsigned int)RVIN_ISP_MAX) ? \ + RVIN_CSI_MAX : RVIN_ISP_MAX) + +/** + * enum rvin_dma_state - DMA states + * @STOPPED: No operation in progress + * @STARTING: Capture starting up + * @RUNNING: Operation in progress have buffers + * @STOPPING: Stopping operation + * @SUSPENDED: Capture is suspended + */ +enum rvin_dma_state { + STOPPED = 0, + STARTING, + RUNNING, + STOPPING, + SUSPENDED, +}; + +/** + * enum rvin_buffer_type + * + * Describes how a buffer is given to the hardware. To be able + * to capture SEQ_TB/BT it's needed to capture to the same vb2 + * buffer twice so the type of buffer needs to be kept. + * + * @FULL: One capture fills the whole vb2 buffer + * @HALF_TOP: One capture fills the top half of the vb2 buffer + * @HALF_BOTTOM: One capture fills the bottom half of the vb2 buffer + */ +enum rvin_buffer_type { + FULL, + HALF_TOP, + HALF_BOTTOM, +}; + +/** + * struct rvin_video_format - Data format stored in memory + * @fourcc: Pixelformat + * @bpp: Bytes per pixel + */ +struct rvin_video_format { + u32 fourcc; + u8 bpp; +}; + +/** + * struct rvin_parallel_entity - Parallel video input endpoint descriptor + * @asd: sub-device descriptor for async framework + * @subdev: subdevice matched using async framework + * @mbus_type: media bus type + * @bus: media bus parallel configuration + * @source_pad: source pad of remote subdevice + * @sink_pad: sink pad of remote subdevice + * + */ +struct rvin_parallel_entity { + struct v4l2_async_subdev *asd; + struct v4l2_subdev *subdev; + + enum v4l2_mbus_type mbus_type; + struct v4l2_mbus_config_parallel bus; + + unsigned int source_pad; + unsigned int sink_pad; +}; + +/** + * struct rvin_group_route - describes a route from a channel of a + * CSI-2 receiver to a VIN + * + * @master: VIN group master ID. + * @csi: CSI-2 receiver ID. + * @chsel: CHSEL register values that connects VIN group to CSI-2. + * + * .. note:: + * Each R-Car CSI-2 receiver has four output channels facing the VIN + * devices, each channel can carry one CSI-2 Virtual Channel (VC). + * There is no correlation between channel number and CSI-2 VC. It's + * up to the CSI-2 receiver driver to configure which VC is output + * on which channel, the VIN devices only care about output channels. + */ +struct rvin_group_route { + unsigned int master; + enum rvin_csi_id csi; + unsigned int chsel; +}; + +/** + * struct rvin_info - Information about the particular VIN implementation + * @model: VIN model + * @use_mc: use media controller instead of controlling subdevice + * @use_isp: the VIN is connected to the ISP and not to the CSI-2 + * @nv12: support outputing NV12 pixel format + * @max_width: max input width the VIN supports + * @max_height: max input height the VIN supports + * @routes: list of possible routes from the CSI-2 recivers to + * all VINs. The list mush be NULL terminated. + */ +struct rvin_info { + enum model_id model; + bool use_mc; + bool use_isp; + bool nv12; + + unsigned int max_width; + unsigned int max_height; + const struct rvin_group_route *routes; +}; + +/** + * struct rvin_dev - Renesas VIN device structure + * @dev: (OF) device + * @base: device I/O register space remapped to virtual memory + * @info: info about VIN instance + * + * @vdev: V4L2 video device associated with VIN + * @v4l2_dev: V4L2 device + * @ctrl_handler: V4L2 control handler + * @notifier: V4L2 asynchronous subdevs notifier + * + * @parallel: parallel input subdevice descriptor + * + * @group: Gen3 CSI group + * @id: Gen3 group id for this VIN + * @pad: media pad for the video device entity + * + * @lock: protects @queue + * @queue: vb2 buffers queue + * @scratch: cpu address for scratch buffer + * @scratch_phys: physical address of the scratch buffer + * + * @qlock: protects @buf_hw, @buf_list, @sequence and @state + * @buf_hw: Keeps track of buffers given to HW slot + * @buf_list: list of queued buffers + * @sequence: V4L2 buffers sequence number + * @state: keeps track of operation state + * + * @is_csi: flag to mark the VIN as using a CSI-2 subdevice + * @chsel: Cached value of the current CSI-2 channel selection + * + * @mbus_code: media bus format code + * @format: active V4L2 pixel format + * + * @crop: active cropping + * @compose: active composing + * @src_rect: active size of the video source + * @std: active video standard of the video source + * + * @alpha: Alpha component to fill in for supported pixel formats + */ +struct rvin_dev { + struct device *dev; + void __iomem *base; + const struct rvin_info *info; + + struct video_device vdev; + struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_async_notifier notifier; + + struct rvin_parallel_entity parallel; + + struct rvin_group *group; + unsigned int id; + struct media_pad pad; + + struct mutex lock; + struct vb2_queue queue; + void *scratch; + dma_addr_t scratch_phys; + + spinlock_t qlock; + struct { + struct vb2_v4l2_buffer *buffer; + enum rvin_buffer_type type; + dma_addr_t phys; + } buf_hw[HW_BUFFER_NUM]; + struct list_head buf_list; + unsigned int sequence; + enum rvin_dma_state state; + + bool is_csi; + unsigned int chsel; + + u32 mbus_code; + struct v4l2_pix_format format; + + struct v4l2_rect crop; + struct v4l2_rect compose; + struct v4l2_rect src_rect; + v4l2_std_id std; + + unsigned int alpha; +}; + +#define vin_to_source(vin) ((vin)->parallel.subdev) + +/* Debug */ +#define vin_dbg(d, fmt, arg...) dev_dbg(d->dev, fmt, ##arg) +#define vin_info(d, fmt, arg...) dev_info(d->dev, fmt, ##arg) +#define vin_warn(d, fmt, arg...) dev_warn(d->dev, fmt, ##arg) +#define vin_err(d, fmt, arg...) dev_err(d->dev, fmt, ##arg) + +/** + * struct rvin_group - VIN CSI2 group information + * @refcount: number of VIN instances using the group + * + * @mdev: media device which represents the group + * + * @lock: protects the count, notifier, vin and csi members + * @count: number of enabled VIN instances found in DT + * @notifier: group notifier for CSI-2 async subdevices + * @vin: VIN instances which are part of the group + * @link_setup: Callback to create all links for the media graph + * @remotes: array of pairs of fwnode and subdev pointers + * to all remote subdevices. + */ +struct rvin_group { + struct kref refcount; + + struct media_device mdev; + + struct mutex lock; + unsigned int count; + struct v4l2_async_notifier notifier; + struct rvin_dev *vin[RCAR_VIN_NUM]; + + int (*link_setup)(struct rvin_dev *vin); + + struct { + struct v4l2_async_subdev *asd; + struct v4l2_subdev *subdev; + } remotes[RVIN_REMOTES_MAX]; +}; + +int rvin_dma_register(struct rvin_dev *vin, int irq); +void rvin_dma_unregister(struct rvin_dev *vin); + +int rvin_v4l2_register(struct rvin_dev *vin); +void rvin_v4l2_unregister(struct rvin_dev *vin); + +const struct rvin_video_format *rvin_format_from_pixel(struct rvin_dev *vin, + u32 pixelformat); + + +/* Cropping, composing and scaling */ +void rvin_crop_scale_comp(struct rvin_dev *vin); + +int rvin_set_channel_routing(struct rvin_dev *vin, u8 chsel); +void rvin_set_alpha(struct rvin_dev *vin, unsigned int alpha); + +int rvin_start_streaming(struct rvin_dev *vin); +void rvin_stop_streaming(struct rvin_dev *vin); + +#endif diff --git a/drivers/media/platform/renesas/rcar_drif.c b/drivers/media/platform/renesas/rcar_drif.c new file mode 100644 index 000000000..3fec41f6e --- /dev/null +++ b/drivers/media/platform/renesas/rcar_drif.c @@ -0,0 +1,1487 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * R-Car Gen3 Digital Radio Interface (DRIF) driver + * + * Copyright (C) 2017 Renesas Electronics Corporation + */ + +/* + * The R-Car DRIF is a receive only MSIOF like controller with an + * external master device driving the SCK. It receives data into a FIFO, + * then this driver uses the SYS-DMAC engine to move the data from + * the device to memory. + * + * Each DRIF channel DRIFx (as per datasheet) contains two internal + * channels DRIFx0 & DRIFx1 within itself with each having its own resources + * like module clk, register set, irq and dma. These internal channels share + * common CLK & SYNC from master. The two data pins D0 & D1 shall be + * considered to represent the two internal channels. This internal split + * is not visible to the master device. + * + * Depending on the master device, a DRIF channel can use + * (1) both internal channels (D0 & D1) to receive data in parallel (or) + * (2) one internal channel (D0 or D1) to receive data + * + * The primary design goal of this controller is to act as a Digital Radio + * Interface that receives digital samples from a tuner device. Hence the + * driver exposes the device as a V4L2 SDR device. In order to qualify as + * a V4L2 SDR device, it should possess a tuner interface as mandated by the + * framework. This driver expects a tuner driver (sub-device) to bind + * asynchronously with this device and the combined drivers shall expose + * a V4L2 compliant SDR device. The DRIF driver is independent of the + * tuner vendor. + * + * The DRIF h/w can support I2S mode and Frame start synchronization pulse mode. + * This driver is tested for I2S mode only because of the availability of + * suitable master devices. Hence, not all configurable options of DRIF h/w + * like lsb/msb first, syncdl, dtdl etc. are exposed via DT and I2S defaults + * are used. These can be exposed later if needed after testing. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* DRIF register offsets */ +#define RCAR_DRIF_SITMDR1 0x00 +#define RCAR_DRIF_SITMDR2 0x04 +#define RCAR_DRIF_SITMDR3 0x08 +#define RCAR_DRIF_SIRMDR1 0x10 +#define RCAR_DRIF_SIRMDR2 0x14 +#define RCAR_DRIF_SIRMDR3 0x18 +#define RCAR_DRIF_SICTR 0x28 +#define RCAR_DRIF_SIFCTR 0x30 +#define RCAR_DRIF_SISTR 0x40 +#define RCAR_DRIF_SIIER 0x44 +#define RCAR_DRIF_SIRFDR 0x60 + +#define RCAR_DRIF_RFOVF BIT(3) /* Receive FIFO overflow */ +#define RCAR_DRIF_RFUDF BIT(4) /* Receive FIFO underflow */ +#define RCAR_DRIF_RFSERR BIT(5) /* Receive frame sync error */ +#define RCAR_DRIF_REOF BIT(7) /* Frame reception end */ +#define RCAR_DRIF_RDREQ BIT(12) /* Receive data xfer req */ +#define RCAR_DRIF_RFFUL BIT(13) /* Receive FIFO full */ + +/* SIRMDR1 */ +#define RCAR_DRIF_SIRMDR1_SYNCMD_FRAME (0 << 28) +#define RCAR_DRIF_SIRMDR1_SYNCMD_LR (3 << 28) + +#define RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH (0 << 25) +#define RCAR_DRIF_SIRMDR1_SYNCAC_POL_LOW (1 << 25) + +#define RCAR_DRIF_SIRMDR1_MSB_FIRST (0 << 24) +#define RCAR_DRIF_SIRMDR1_LSB_FIRST (1 << 24) + +#define RCAR_DRIF_SIRMDR1_DTDL_0 (0 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_1 (1 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_2 (2 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_0PT5 (5 << 20) +#define RCAR_DRIF_SIRMDR1_DTDL_1PT5 (6 << 20) + +#define RCAR_DRIF_SIRMDR1_SYNCDL_0 (0 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_1 (1 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_2 (2 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_3 (3 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_0PT5 (5 << 20) +#define RCAR_DRIF_SIRMDR1_SYNCDL_1PT5 (6 << 20) + +#define RCAR_DRIF_MDR_GRPCNT(n) (((n) - 1) << 30) +#define RCAR_DRIF_MDR_BITLEN(n) (((n) - 1) << 24) +#define RCAR_DRIF_MDR_WDCNT(n) (((n) - 1) << 16) + +/* Hidden Transmit register that controls CLK & SYNC */ +#define RCAR_DRIF_SITMDR1_PCON BIT(30) + +#define RCAR_DRIF_SICTR_RX_RISING_EDGE BIT(26) +#define RCAR_DRIF_SICTR_RX_EN BIT(8) +#define RCAR_DRIF_SICTR_RESET BIT(0) + +/* Constants */ +#define RCAR_DRIF_NUM_HWBUFS 32 +#define RCAR_DRIF_MAX_DEVS 4 +#define RCAR_DRIF_DEFAULT_NUM_HWBUFS 16 +#define RCAR_DRIF_DEFAULT_HWBUF_SIZE (4 * PAGE_SIZE) +#define RCAR_DRIF_MAX_CHANNEL 2 +#define RCAR_SDR_BUFFER_SIZE SZ_64K + +/* Internal buffer status flags */ +#define RCAR_DRIF_BUF_DONE BIT(0) /* DMA completed */ +#define RCAR_DRIF_BUF_OVERFLOW BIT(1) /* Overflow detected */ + +#define to_rcar_drif_buf_pair(sdr, ch_num, idx) \ + (&((sdr)->ch[!(ch_num)]->buf[(idx)])) + +#define for_each_rcar_drif_channel(ch, ch_mask) \ + for_each_set_bit(ch, ch_mask, RCAR_DRIF_MAX_CHANNEL) + +/* Debug */ +#define rdrif_dbg(sdr, fmt, arg...) \ + dev_dbg(sdr->v4l2_dev.dev, fmt, ## arg) + +#define rdrif_err(sdr, fmt, arg...) \ + dev_err(sdr->v4l2_dev.dev, fmt, ## arg) + +/* Stream formats */ +struct rcar_drif_format { + u32 pixelformat; + u32 buffersize; + u32 bitlen; + u32 wdcnt; + u32 num_ch; +}; + +/* Format descriptions for capture */ +static const struct rcar_drif_format formats[] = { + { + .pixelformat = V4L2_SDR_FMT_PCU16BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 16, + .wdcnt = 1, + .num_ch = 2, + }, + { + .pixelformat = V4L2_SDR_FMT_PCU18BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 18, + .wdcnt = 1, + .num_ch = 2, + }, + { + .pixelformat = V4L2_SDR_FMT_PCU20BE, + .buffersize = RCAR_SDR_BUFFER_SIZE, + .bitlen = 20, + .wdcnt = 1, + .num_ch = 2, + }, +}; + +/* Buffer for a received frame from one or both internal channels */ +struct rcar_drif_frame_buf { + /* Common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +/* OF graph endpoint's V4L2 async data */ +struct rcar_drif_graph_ep { + struct v4l2_subdev *subdev; /* Async matched subdev */ +}; + +/* DMA buffer */ +struct rcar_drif_hwbuf { + void *addr; /* CPU-side address */ + unsigned int status; /* Buffer status flags */ +}; + +/* Internal channel */ +struct rcar_drif { + struct rcar_drif_sdr *sdr; /* Group device */ + struct platform_device *pdev; /* Channel's pdev */ + void __iomem *base; /* Base register address */ + resource_size_t start; /* I/O resource offset */ + struct dma_chan *dmach; /* Reserved DMA channel */ + struct clk *clk; /* Module clock */ + struct rcar_drif_hwbuf buf[RCAR_DRIF_NUM_HWBUFS]; /* H/W bufs */ + dma_addr_t dma_handle; /* Handle for all bufs */ + unsigned int num; /* Channel number */ + bool acting_sdr; /* Channel acting as SDR device */ +}; + +/* DRIF V4L2 SDR */ +struct rcar_drif_sdr { + struct device *dev; /* Platform device */ + struct video_device *vdev; /* V4L2 SDR device */ + struct v4l2_device v4l2_dev; /* V4L2 device */ + + /* Videobuf2 queue and queued buffers list */ + struct vb2_queue vb_queue; + struct list_head queued_bufs; + spinlock_t queued_bufs_lock; /* Protects queued_bufs */ + spinlock_t dma_lock; /* To serialize DMA cb of channels */ + + struct mutex v4l2_mutex; /* To serialize ioctls */ + struct mutex vb_queue_mutex; /* To serialize streaming ioctls */ + struct v4l2_ctrl_handler ctrl_hdl; /* SDR control handler */ + struct v4l2_async_notifier notifier; /* For subdev (tuner) */ + struct rcar_drif_graph_ep ep; /* Endpoint V4L2 async data */ + + /* Current V4L2 SDR format ptr */ + const struct rcar_drif_format *fmt; + + /* Device tree SYNC properties */ + u32 mdr1; + + /* Internals */ + struct rcar_drif *ch[RCAR_DRIF_MAX_CHANNEL]; /* DRIFx0,1 */ + unsigned long hw_ch_mask; /* Enabled channels per DT */ + unsigned long cur_ch_mask; /* Used channels for an SDR FMT */ + u32 num_hw_ch; /* Num of DT enabled channels */ + u32 num_cur_ch; /* Num of used channels */ + u32 hwbuf_size; /* Each DMA buffer size */ + u32 produced; /* Buffers produced by sdr dev */ +}; + +/* Register access functions */ +static void rcar_drif_write(struct rcar_drif *ch, u32 offset, u32 data) +{ + writel(data, ch->base + offset); +} + +static u32 rcar_drif_read(struct rcar_drif *ch, u32 offset) +{ + return readl(ch->base + offset); +} + +/* Release DMA channels */ +static void rcar_drif_release_dmachannels(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + if (sdr->ch[i]->dmach) { + dma_release_channel(sdr->ch[i]->dmach); + sdr->ch[i]->dmach = NULL; + } +} + +/* Allocate DMA channels */ +static int rcar_drif_alloc_dmachannels(struct rcar_drif_sdr *sdr) +{ + struct dma_slave_config dma_cfg; + unsigned int i; + int ret; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + ch->dmach = dma_request_chan(&ch->pdev->dev, "rx"); + if (IS_ERR(ch->dmach)) { + ret = PTR_ERR(ch->dmach); + if (ret != -EPROBE_DEFER) + rdrif_err(sdr, + "ch%u: dma channel req failed: %pe\n", + i, ch->dmach); + ch->dmach = NULL; + goto dmach_error; + } + + /* Configure slave */ + memset(&dma_cfg, 0, sizeof(dma_cfg)); + dma_cfg.src_addr = (phys_addr_t)(ch->start + RCAR_DRIF_SIRFDR); + dma_cfg.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + ret = dmaengine_slave_config(ch->dmach, &dma_cfg); + if (ret) { + rdrif_err(sdr, "ch%u: dma slave config failed\n", i); + goto dmach_error; + } + } + return 0; + +dmach_error: + rcar_drif_release_dmachannels(sdr); + return ret; +} + +/* Release queued vb2 buffers */ +static void rcar_drif_release_queued_bufs(struct rcar_drif_sdr *sdr, + enum vb2_buffer_state state) +{ + struct rcar_drif_frame_buf *fbuf, *tmp; + unsigned long flags; + + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + list_for_each_entry_safe(fbuf, tmp, &sdr->queued_bufs, list) { + list_del(&fbuf->list); + vb2_buffer_done(&fbuf->vb.vb2_buf, state); + } + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); +} + +/* Set MDR defaults */ +static inline void rcar_drif_set_mdr1(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + /* Set defaults for enabled internal channels */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + /* Refer MSIOF section in manual for this register setting */ + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SITMDR1, + RCAR_DRIF_SITMDR1_PCON); + + /* Setup MDR1 value */ + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR1, sdr->mdr1); + + rdrif_dbg(sdr, "ch%u: mdr1 = 0x%08x", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR1)); + } +} + +/* Set DRIF receive format */ +static int rcar_drif_set_format(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + rdrif_dbg(sdr, "setfmt: bitlen %u wdcnt %u num_ch %u\n", + sdr->fmt->bitlen, sdr->fmt->wdcnt, sdr->fmt->num_ch); + + /* Sanity check */ + if (sdr->fmt->num_ch > sdr->num_cur_ch) { + rdrif_err(sdr, "fmt num_ch %u cur_ch %u mismatch\n", + sdr->fmt->num_ch, sdr->num_cur_ch); + return -EINVAL; + } + + /* Setup group, bitlen & wdcnt */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + u32 mdr; + + /* Two groups */ + mdr = RCAR_DRIF_MDR_GRPCNT(2) | + RCAR_DRIF_MDR_BITLEN(sdr->fmt->bitlen) | + RCAR_DRIF_MDR_WDCNT(sdr->fmt->wdcnt); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR2, mdr); + + mdr = RCAR_DRIF_MDR_BITLEN(sdr->fmt->bitlen) | + RCAR_DRIF_MDR_WDCNT(sdr->fmt->wdcnt); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SIRMDR3, mdr); + + rdrif_dbg(sdr, "ch%u: new mdr[2,3] = 0x%08x, 0x%08x\n", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR2), + rcar_drif_read(sdr->ch[i], RCAR_DRIF_SIRMDR3)); + } + return 0; +} + +/* Release DMA buffers */ +static void rcar_drif_release_buf(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + /* First entry contains the dma buf ptr */ + if (ch->buf[0].addr) { + dma_free_coherent(&ch->pdev->dev, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + ch->buf[0].addr, ch->dma_handle); + ch->buf[0].addr = NULL; + } + } +} + +/* Request DMA buffers */ +static int rcar_drif_request_buf(struct rcar_drif_sdr *sdr) +{ + int ret = -ENOMEM; + unsigned int i, j; + void *addr; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + struct rcar_drif *ch = sdr->ch[i]; + + /* Allocate DMA buffers */ + addr = dma_alloc_coherent(&ch->pdev->dev, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + &ch->dma_handle, GFP_KERNEL); + if (!addr) { + rdrif_err(sdr, + "ch%u: dma alloc failed. num hwbufs %u size %u\n", + i, RCAR_DRIF_NUM_HWBUFS, sdr->hwbuf_size); + goto error; + } + + /* Split the chunk and populate bufctxt */ + for (j = 0; j < RCAR_DRIF_NUM_HWBUFS; j++) { + ch->buf[j].addr = addr + (j * sdr->hwbuf_size); + ch->buf[j].status = 0; + } + } + return 0; +error: + return ret; +} + +/* Setup vb_queue minimum buffer requirements */ +static int rcar_drif_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + + /* Need at least 16 buffers */ + if (vq->num_buffers + *num_buffers < 16) + *num_buffers = 16 - vq->num_buffers; + + *num_planes = 1; + sizes[0] = PAGE_ALIGN(sdr->fmt->buffersize); + rdrif_dbg(sdr, "num_bufs %d sizes[0] %d\n", *num_buffers, sizes[0]); + + return 0; +} + +/* Enqueue buffer */ +static void rcar_drif_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vb->vb2_queue); + struct rcar_drif_frame_buf *fbuf = + container_of(vbuf, struct rcar_drif_frame_buf, vb); + unsigned long flags; + + rdrif_dbg(sdr, "buf_queue idx %u\n", vb->index); + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + list_add_tail(&fbuf->list, &sdr->queued_bufs); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); +} + +/* Get a frame buf from list */ +static struct rcar_drif_frame_buf * +rcar_drif_get_fbuf(struct rcar_drif_sdr *sdr) +{ + struct rcar_drif_frame_buf *fbuf; + unsigned long flags; + + spin_lock_irqsave(&sdr->queued_bufs_lock, flags); + fbuf = list_first_entry_or_null(&sdr->queued_bufs, struct + rcar_drif_frame_buf, list); + if (!fbuf) { + /* + * App is late in enqueing buffers. Samples lost & there will + * be a gap in sequence number when app recovers + */ + rdrif_dbg(sdr, "\napp late: prod %u\n", sdr->produced); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); + return NULL; + } + list_del(&fbuf->list); + spin_unlock_irqrestore(&sdr->queued_bufs_lock, flags); + + return fbuf; +} + +/* Helpers to set/clear buf pair status */ +static inline bool rcar_drif_bufs_done(struct rcar_drif_hwbuf **buf) +{ + return (buf[0]->status & buf[1]->status & RCAR_DRIF_BUF_DONE); +} + +static inline bool rcar_drif_bufs_overflow(struct rcar_drif_hwbuf **buf) +{ + return ((buf[0]->status | buf[1]->status) & RCAR_DRIF_BUF_OVERFLOW); +} + +static inline void rcar_drif_bufs_clear(struct rcar_drif_hwbuf **buf, + unsigned int bit) +{ + unsigned int i; + + for (i = 0; i < RCAR_DRIF_MAX_CHANNEL; i++) + buf[i]->status &= ~bit; +} + +/* Channel DMA complete */ +static void rcar_drif_channel_complete(struct rcar_drif *ch, u32 idx) +{ + u32 str; + + ch->buf[idx].status |= RCAR_DRIF_BUF_DONE; + + /* Check for DRIF errors */ + str = rcar_drif_read(ch, RCAR_DRIF_SISTR); + if (unlikely(str & RCAR_DRIF_RFOVF)) { + /* Writing the same clears it */ + rcar_drif_write(ch, RCAR_DRIF_SISTR, str); + + /* Overflow: some samples are lost */ + ch->buf[idx].status |= RCAR_DRIF_BUF_OVERFLOW; + } +} + +/* DMA callback for each stage */ +static void rcar_drif_dma_complete(void *dma_async_param) +{ + struct rcar_drif *ch = dma_async_param; + struct rcar_drif_sdr *sdr = ch->sdr; + struct rcar_drif_hwbuf *buf[RCAR_DRIF_MAX_CHANNEL]; + struct rcar_drif_frame_buf *fbuf; + bool overflow = false; + u32 idx, produced; + unsigned int i; + + spin_lock(&sdr->dma_lock); + + /* DMA can be terminated while the callback was waiting on lock */ + if (!vb2_is_streaming(&sdr->vb_queue)) { + spin_unlock(&sdr->dma_lock); + return; + } + + idx = sdr->produced % RCAR_DRIF_NUM_HWBUFS; + rcar_drif_channel_complete(ch, idx); + + if (sdr->num_cur_ch == RCAR_DRIF_MAX_CHANNEL) { + buf[0] = ch->num ? to_rcar_drif_buf_pair(sdr, ch->num, idx) : + &ch->buf[idx]; + buf[1] = ch->num ? &ch->buf[idx] : + to_rcar_drif_buf_pair(sdr, ch->num, idx); + + /* Check if both DMA buffers are done */ + if (!rcar_drif_bufs_done(buf)) { + spin_unlock(&sdr->dma_lock); + return; + } + + /* Clear buf done status */ + rcar_drif_bufs_clear(buf, RCAR_DRIF_BUF_DONE); + + if (rcar_drif_bufs_overflow(buf)) { + overflow = true; + /* Clear the flag in status */ + rcar_drif_bufs_clear(buf, RCAR_DRIF_BUF_OVERFLOW); + } + } else { + buf[0] = &ch->buf[idx]; + if (buf[0]->status & RCAR_DRIF_BUF_OVERFLOW) { + overflow = true; + /* Clear the flag in status */ + buf[0]->status &= ~RCAR_DRIF_BUF_OVERFLOW; + } + } + + /* Buffer produced for consumption */ + produced = sdr->produced++; + spin_unlock(&sdr->dma_lock); + + rdrif_dbg(sdr, "ch%u: prod %u\n", ch->num, produced); + + /* Get fbuf */ + fbuf = rcar_drif_get_fbuf(sdr); + if (!fbuf) + return; + + for (i = 0; i < RCAR_DRIF_MAX_CHANNEL; i++) + memcpy(vb2_plane_vaddr(&fbuf->vb.vb2_buf, 0) + + i * sdr->hwbuf_size, buf[i]->addr, sdr->hwbuf_size); + + fbuf->vb.field = V4L2_FIELD_NONE; + fbuf->vb.sequence = produced; + fbuf->vb.vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&fbuf->vb.vb2_buf, 0, sdr->fmt->buffersize); + + /* Set error state on overflow */ + vb2_buffer_done(&fbuf->vb.vb2_buf, + overflow ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); +} + +static int rcar_drif_qbuf(struct rcar_drif *ch) +{ + struct rcar_drif_sdr *sdr = ch->sdr; + dma_addr_t addr = ch->dma_handle; + struct dma_async_tx_descriptor *rxd; + dma_cookie_t cookie; + int ret = -EIO; + + /* Setup cyclic DMA with given buffers */ + rxd = dmaengine_prep_dma_cyclic(ch->dmach, addr, + sdr->hwbuf_size * RCAR_DRIF_NUM_HWBUFS, + sdr->hwbuf_size, DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT | DMA_CTRL_ACK); + if (!rxd) { + rdrif_err(sdr, "ch%u: prep dma cyclic failed\n", ch->num); + return ret; + } + + /* Submit descriptor */ + rxd->callback = rcar_drif_dma_complete; + rxd->callback_param = ch; + cookie = dmaengine_submit(rxd); + if (dma_submit_error(cookie)) { + rdrif_err(sdr, "ch%u: dma submit failed\n", ch->num); + return ret; + } + + dma_async_issue_pending(ch->dmach); + return 0; +} + +/* Enable reception */ +static int rcar_drif_enable_rx(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + u32 ctr; + int ret = -EINVAL; + + /* + * When both internal channels are enabled, they can be synchronized + * only by the master + */ + + /* Enable receive */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ctr = rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR); + ctr |= (RCAR_DRIF_SICTR_RX_RISING_EDGE | + RCAR_DRIF_SICTR_RX_EN); + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SICTR, ctr); + } + + /* Check receive enabled */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = readl_poll_timeout(sdr->ch[i]->base + RCAR_DRIF_SICTR, + ctr, ctr & RCAR_DRIF_SICTR_RX_EN, 7, 100000); + if (ret) { + rdrif_err(sdr, "ch%u: rx en failed. ctr 0x%08x\n", i, + rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR)); + break; + } + } + return ret; +} + +/* Disable reception */ +static void rcar_drif_disable_rx(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + u32 ctr; + int ret; + + /* Disable receive */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ctr = rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR); + ctr &= ~RCAR_DRIF_SICTR_RX_EN; + rcar_drif_write(sdr->ch[i], RCAR_DRIF_SICTR, ctr); + } + + /* Check receive disabled */ + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = readl_poll_timeout(sdr->ch[i]->base + RCAR_DRIF_SICTR, + ctr, !(ctr & RCAR_DRIF_SICTR_RX_EN), 7, 100000); + if (ret) + dev_warn(&sdr->vdev->dev, + "ch%u: failed to disable rx. ctr 0x%08x\n", + i, rcar_drif_read(sdr->ch[i], RCAR_DRIF_SICTR)); + } +} + +/* Stop channel */ +static void rcar_drif_stop_channel(struct rcar_drif *ch) +{ + /* Disable DMA receive interrupt */ + rcar_drif_write(ch, RCAR_DRIF_SIIER, 0x00000000); + + /* Terminate all DMA transfers */ + dmaengine_terminate_sync(ch->dmach); +} + +/* Stop receive operation */ +static void rcar_drif_stop(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + /* Disable Rx */ + rcar_drif_disable_rx(sdr); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + rcar_drif_stop_channel(sdr->ch[i]); +} + +/* Start channel */ +static int rcar_drif_start_channel(struct rcar_drif *ch) +{ + struct rcar_drif_sdr *sdr = ch->sdr; + u32 ctr, str; + int ret; + + /* Reset receive */ + rcar_drif_write(ch, RCAR_DRIF_SICTR, RCAR_DRIF_SICTR_RESET); + ret = readl_poll_timeout(ch->base + RCAR_DRIF_SICTR, ctr, + !(ctr & RCAR_DRIF_SICTR_RESET), 7, 100000); + if (ret) { + rdrif_err(sdr, "ch%u: failed to reset rx. ctr 0x%08x\n", + ch->num, rcar_drif_read(ch, RCAR_DRIF_SICTR)); + return ret; + } + + /* Queue buffers for DMA */ + ret = rcar_drif_qbuf(ch); + if (ret) + return ret; + + /* Clear status register flags */ + str = RCAR_DRIF_RFFUL | RCAR_DRIF_REOF | RCAR_DRIF_RFSERR | + RCAR_DRIF_RFUDF | RCAR_DRIF_RFOVF; + rcar_drif_write(ch, RCAR_DRIF_SISTR, str); + + /* Enable DMA receive interrupt */ + rcar_drif_write(ch, RCAR_DRIF_SIIER, 0x00009000); + + return ret; +} + +/* Start receive operation */ +static int rcar_drif_start(struct rcar_drif_sdr *sdr) +{ + unsigned long enabled = 0; + unsigned int i; + int ret; + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = rcar_drif_start_channel(sdr->ch[i]); + if (ret) + goto start_error; + enabled |= BIT(i); + } + + ret = rcar_drif_enable_rx(sdr); + if (ret) + goto enable_error; + + sdr->produced = 0; + return ret; + +enable_error: + rcar_drif_disable_rx(sdr); +start_error: + for_each_rcar_drif_channel(i, &enabled) + rcar_drif_stop_channel(sdr->ch[i]); + + return ret; +} + +/* Start streaming */ +static int rcar_drif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + unsigned long enabled = 0; + unsigned int i; + int ret; + + mutex_lock(&sdr->v4l2_mutex); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) { + ret = clk_prepare_enable(sdr->ch[i]->clk); + if (ret) + goto error; + enabled |= BIT(i); + } + + /* Set default MDRx settings */ + rcar_drif_set_mdr1(sdr); + + /* Set new format */ + ret = rcar_drif_set_format(sdr); + if (ret) + goto error; + + if (sdr->num_cur_ch == RCAR_DRIF_MAX_CHANNEL) + sdr->hwbuf_size = sdr->fmt->buffersize / RCAR_DRIF_MAX_CHANNEL; + else + sdr->hwbuf_size = sdr->fmt->buffersize; + + rdrif_dbg(sdr, "num hwbufs %u, hwbuf_size %u\n", + RCAR_DRIF_NUM_HWBUFS, sdr->hwbuf_size); + + /* Alloc DMA channel */ + ret = rcar_drif_alloc_dmachannels(sdr); + if (ret) + goto error; + + /* Request buffers */ + ret = rcar_drif_request_buf(sdr); + if (ret) + goto error; + + /* Start Rx */ + ret = rcar_drif_start(sdr); + if (ret) + goto error; + + mutex_unlock(&sdr->v4l2_mutex); + + return ret; + +error: + rcar_drif_release_queued_bufs(sdr, VB2_BUF_STATE_QUEUED); + rcar_drif_release_buf(sdr); + rcar_drif_release_dmachannels(sdr); + for_each_rcar_drif_channel(i, &enabled) + clk_disable_unprepare(sdr->ch[i]->clk); + + mutex_unlock(&sdr->v4l2_mutex); + + return ret; +} + +/* Stop streaming */ +static void rcar_drif_stop_streaming(struct vb2_queue *vq) +{ + struct rcar_drif_sdr *sdr = vb2_get_drv_priv(vq); + unsigned int i; + + mutex_lock(&sdr->v4l2_mutex); + + /* Stop hardware streaming */ + rcar_drif_stop(sdr); + + /* Return all queued buffers to vb2 */ + rcar_drif_release_queued_bufs(sdr, VB2_BUF_STATE_ERROR); + + /* Release buf */ + rcar_drif_release_buf(sdr); + + /* Release DMA channel resources */ + rcar_drif_release_dmachannels(sdr); + + for_each_rcar_drif_channel(i, &sdr->cur_ch_mask) + clk_disable_unprepare(sdr->ch[i]->clk); + + mutex_unlock(&sdr->v4l2_mutex); +} + +/* Vb2 ops */ +static const struct vb2_ops rcar_drif_vb2_ops = { + .queue_setup = rcar_drif_queue_setup, + .buf_queue = rcar_drif_buf_queue, + .start_streaming = rcar_drif_start_streaming, + .stop_streaming = rcar_drif_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int rcar_drif_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, sdr->vdev->name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + sdr->vdev->name); + + return 0; +} + +static int rcar_drif_set_default_format(struct rcar_drif_sdr *sdr) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + /* Matching fmt based on required channels is set as default */ + if (sdr->num_hw_ch == formats[i].num_ch) { + sdr->fmt = &formats[i]; + sdr->cur_ch_mask = sdr->hw_ch_mask; + sdr->num_cur_ch = sdr->num_hw_ch; + dev_dbg(sdr->dev, "default fmt[%u]: mask %lu num %u\n", + i, sdr->cur_ch_mask, sdr->num_cur_ch); + return 0; + } + } + return -EINVAL; +} + +static int rcar_drif_enum_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(formats)) + return -EINVAL; + + f->pixelformat = formats[f->index].pixelformat; + + return 0; +} + +static int rcar_drif_g_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + f->fmt.sdr.pixelformat = sdr->fmt->pixelformat; + f->fmt.sdr.buffersize = sdr->fmt->buffersize; + + return 0; +} + +static int rcar_drif_s_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + struct vb2_queue *q = &sdr->vb_queue; + unsigned int i; + + if (vb2_is_busy(q)) + return -EBUSY; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) + break; + } + + if (i == ARRAY_SIZE(formats)) + i = 0; /* Set the 1st format as default on no match */ + + sdr->fmt = &formats[i]; + f->fmt.sdr.pixelformat = sdr->fmt->pixelformat; + f->fmt.sdr.buffersize = formats[i].buffersize; + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + + /* + * If a format demands one channel only out of two + * enabled channels, pick the 0th channel. + */ + if (formats[i].num_ch < sdr->num_hw_ch) { + sdr->cur_ch_mask = BIT(0); + sdr->num_cur_ch = formats[i].num_ch; + } else { + sdr->cur_ch_mask = sdr->hw_ch_mask; + sdr->num_cur_ch = sdr->num_hw_ch; + } + + rdrif_dbg(sdr, "cur: idx %u mask %lu num %u\n", + i, sdr->cur_ch_mask, sdr->num_cur_ch); + + return 0; +} + +static int rcar_drif_try_fmt_sdr_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(formats); i++) { + if (formats[i].pixelformat == f->fmt.sdr.pixelformat) { + f->fmt.sdr.buffersize = formats[i].buffersize; + return 0; + } + } + + f->fmt.sdr.pixelformat = formats[0].pixelformat; + f->fmt.sdr.buffersize = formats[0].buffersize; + memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved)); + + return 0; +} + +/* Tuner subdev ioctls */ +static int rcar_drif_enum_freq_bands(struct file *file, void *priv, + struct v4l2_frequency_band *band) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, enum_freq_bands, band); +} + +static int rcar_drif_g_frequency(struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, g_frequency, f); +} + +static int rcar_drif_s_frequency(struct file *file, void *priv, + const struct v4l2_frequency *f) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, s_frequency, f); +} + +static int rcar_drif_g_tuner(struct file *file, void *priv, + struct v4l2_tuner *vt) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, g_tuner, vt); +} + +static int rcar_drif_s_tuner(struct file *file, void *priv, + const struct v4l2_tuner *vt) +{ + struct rcar_drif_sdr *sdr = video_drvdata(file); + + return v4l2_subdev_call(sdr->ep.subdev, tuner, s_tuner, vt); +} + +static const struct v4l2_ioctl_ops rcar_drif_ioctl_ops = { + .vidioc_querycap = rcar_drif_querycap, + + .vidioc_enum_fmt_sdr_cap = rcar_drif_enum_fmt_sdr_cap, + .vidioc_g_fmt_sdr_cap = rcar_drif_g_fmt_sdr_cap, + .vidioc_s_fmt_sdr_cap = rcar_drif_s_fmt_sdr_cap, + .vidioc_try_fmt_sdr_cap = rcar_drif_try_fmt_sdr_cap, + + .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_s_frequency = rcar_drif_s_frequency, + .vidioc_g_frequency = rcar_drif_g_frequency, + .vidioc_s_tuner = rcar_drif_s_tuner, + .vidioc_g_tuner = rcar_drif_g_tuner, + .vidioc_enum_freq_bands = rcar_drif_enum_freq_bands, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +static const struct v4l2_file_operations rcar_drif_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, +}; + +static int rcar_drif_sdr_register(struct rcar_drif_sdr *sdr) +{ + int ret; + + /* Init video_device structure */ + sdr->vdev = video_device_alloc(); + if (!sdr->vdev) + return -ENOMEM; + + snprintf(sdr->vdev->name, sizeof(sdr->vdev->name), "R-Car DRIF"); + sdr->vdev->fops = &rcar_drif_fops; + sdr->vdev->ioctl_ops = &rcar_drif_ioctl_ops; + sdr->vdev->release = video_device_release; + sdr->vdev->lock = &sdr->v4l2_mutex; + sdr->vdev->queue = &sdr->vb_queue; + sdr->vdev->queue->lock = &sdr->vb_queue_mutex; + sdr->vdev->ctrl_handler = &sdr->ctrl_hdl; + sdr->vdev->v4l2_dev = &sdr->v4l2_dev; + sdr->vdev->device_caps = V4L2_CAP_SDR_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_STREAMING | V4L2_CAP_READWRITE; + video_set_drvdata(sdr->vdev, sdr); + + /* Register V4L2 SDR device */ + ret = video_register_device(sdr->vdev, VFL_TYPE_SDR, -1); + if (ret) { + video_device_release(sdr->vdev); + sdr->vdev = NULL; + dev_err(sdr->dev, "failed video_register_device (%d)\n", ret); + } + + return ret; +} + +static void rcar_drif_sdr_unregister(struct rcar_drif_sdr *sdr) +{ + video_unregister_device(sdr->vdev); + sdr->vdev = NULL; +} + +/* Sub-device bound callback */ +static int rcar_drif_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + + v4l2_set_subdev_hostdata(subdev, sdr); + sdr->ep.subdev = subdev; + rdrif_dbg(sdr, "bound asd %s\n", subdev->name); + + return 0; +} + +/* Sub-device unbind callback */ +static void rcar_drif_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + + if (sdr->ep.subdev != subdev) { + rdrif_err(sdr, "subdev %s is not bound\n", subdev->name); + return; + } + + /* Free ctrl handler if initialized */ + v4l2_ctrl_handler_free(&sdr->ctrl_hdl); + sdr->v4l2_dev.ctrl_handler = NULL; + sdr->ep.subdev = NULL; + + rcar_drif_sdr_unregister(sdr); + rdrif_dbg(sdr, "unbind asd %s\n", subdev->name); +} + +/* Sub-device registered notification callback */ +static int rcar_drif_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct rcar_drif_sdr *sdr = + container_of(notifier, struct rcar_drif_sdr, notifier); + int ret; + + /* + * The subdev tested at this point uses 4 controls. Using 10 as a worst + * case scenario hint. When less controls are needed there will be some + * unused memory and when more controls are needed the framework uses + * hash to manage controls within this number. + */ + ret = v4l2_ctrl_handler_init(&sdr->ctrl_hdl, 10); + if (ret) + return -ENOMEM; + + sdr->v4l2_dev.ctrl_handler = &sdr->ctrl_hdl; + ret = v4l2_device_register_subdev_nodes(&sdr->v4l2_dev); + if (ret) { + rdrif_err(sdr, "failed: register subdev nodes ret %d\n", ret); + goto error; + } + + ret = v4l2_ctrl_add_handler(&sdr->ctrl_hdl, + sdr->ep.subdev->ctrl_handler, NULL, true); + if (ret) { + rdrif_err(sdr, "failed: ctrl add hdlr ret %d\n", ret); + goto error; + } + + ret = rcar_drif_sdr_register(sdr); + if (ret) + goto error; + + return ret; + +error: + v4l2_ctrl_handler_free(&sdr->ctrl_hdl); + + return ret; +} + +static const struct v4l2_async_notifier_operations rcar_drif_notify_ops = { + .bound = rcar_drif_notify_bound, + .unbind = rcar_drif_notify_unbind, + .complete = rcar_drif_notify_complete, +}; + +/* Read endpoint properties */ +static void rcar_drif_get_ep_properties(struct rcar_drif_sdr *sdr, + struct fwnode_handle *fwnode) +{ + u32 val; + + /* Set the I2S defaults for SIRMDR1*/ + sdr->mdr1 = RCAR_DRIF_SIRMDR1_SYNCMD_LR | RCAR_DRIF_SIRMDR1_MSB_FIRST | + RCAR_DRIF_SIRMDR1_DTDL_1 | RCAR_DRIF_SIRMDR1_SYNCDL_0; + + /* Parse sync polarity from endpoint */ + if (!fwnode_property_read_u32(fwnode, "sync-active", &val)) + sdr->mdr1 |= val ? RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH : + RCAR_DRIF_SIRMDR1_SYNCAC_POL_LOW; + else + sdr->mdr1 |= RCAR_DRIF_SIRMDR1_SYNCAC_POL_HIGH; /* default */ + + dev_dbg(sdr->dev, "mdr1 0x%08x\n", sdr->mdr1); +} + +/* Parse sub-devs (tuner) to find a matching device */ +static int rcar_drif_parse_subdevs(struct rcar_drif_sdr *sdr) +{ + struct v4l2_async_notifier *notifier = &sdr->notifier; + struct fwnode_handle *fwnode, *ep; + struct v4l2_async_subdev *asd; + + v4l2_async_nf_init(notifier); + + ep = fwnode_graph_get_next_endpoint(of_fwnode_handle(sdr->dev->of_node), + NULL); + if (!ep) + return 0; + + /* Get the endpoint properties */ + rcar_drif_get_ep_properties(sdr, ep); + + fwnode = fwnode_graph_get_remote_port_parent(ep); + fwnode_handle_put(ep); + if (!fwnode) { + dev_warn(sdr->dev, "bad remote port parent\n"); + return -EINVAL; + } + + asd = v4l2_async_nf_add_fwnode(notifier, fwnode, + struct v4l2_async_subdev); + fwnode_handle_put(fwnode); + if (IS_ERR(asd)) + return PTR_ERR(asd); + + return 0; +} + +/* Check if the given device is the primary bond */ +static bool rcar_drif_primary_bond(struct platform_device *pdev) +{ + return of_property_read_bool(pdev->dev.of_node, "renesas,primary-bond"); +} + +/* Check if both devices of the bond are enabled */ +static struct device_node *rcar_drif_bond_enabled(struct platform_device *p) +{ + struct device_node *np; + + np = of_parse_phandle(p->dev.of_node, "renesas,bonding", 0); + if (np && of_device_is_available(np)) + return np; + + return NULL; +} + +/* Check if the bonded device is probed */ +static int rcar_drif_bond_available(struct rcar_drif_sdr *sdr, + struct device_node *np) +{ + struct platform_device *pdev; + struct rcar_drif *ch; + int ret = 0; + + pdev = of_find_device_by_node(np); + if (!pdev) { + dev_err(sdr->dev, "failed to get bonded device from node\n"); + return -ENODEV; + } + + device_lock(&pdev->dev); + ch = platform_get_drvdata(pdev); + if (ch) { + /* Update sdr data in the bonded device */ + ch->sdr = sdr; + + /* Update sdr with bonded device data */ + sdr->ch[ch->num] = ch; + sdr->hw_ch_mask |= BIT(ch->num); + } else { + /* Defer */ + dev_info(sdr->dev, "defer probe\n"); + ret = -EPROBE_DEFER; + } + device_unlock(&pdev->dev); + + put_device(&pdev->dev); + + return ret; +} + +/* V4L2 SDR device probe */ +static int rcar_drif_sdr_probe(struct rcar_drif_sdr *sdr) +{ + int ret; + + /* Validate any supported format for enabled channels */ + ret = rcar_drif_set_default_format(sdr); + if (ret) { + dev_err(sdr->dev, "failed to set default format\n"); + return ret; + } + + /* Set defaults */ + sdr->hwbuf_size = RCAR_DRIF_DEFAULT_HWBUF_SIZE; + + mutex_init(&sdr->v4l2_mutex); + mutex_init(&sdr->vb_queue_mutex); + spin_lock_init(&sdr->queued_bufs_lock); + spin_lock_init(&sdr->dma_lock); + INIT_LIST_HEAD(&sdr->queued_bufs); + + /* Init videobuf2 queue structure */ + sdr->vb_queue.type = V4L2_BUF_TYPE_SDR_CAPTURE; + sdr->vb_queue.io_modes = VB2_READ | VB2_MMAP | VB2_DMABUF; + sdr->vb_queue.drv_priv = sdr; + sdr->vb_queue.buf_struct_size = sizeof(struct rcar_drif_frame_buf); + sdr->vb_queue.ops = &rcar_drif_vb2_ops; + sdr->vb_queue.mem_ops = &vb2_vmalloc_memops; + sdr->vb_queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + /* Init videobuf2 queue */ + ret = vb2_queue_init(&sdr->vb_queue); + if (ret) { + dev_err(sdr->dev, "failed: vb2_queue_init ret %d\n", ret); + return ret; + } + + /* Register the v4l2_device */ + ret = v4l2_device_register(sdr->dev, &sdr->v4l2_dev); + if (ret) { + dev_err(sdr->dev, "failed: v4l2_device_register ret %d\n", ret); + return ret; + } + + /* + * Parse subdevs after v4l2_device_register because if the subdev + * is already probed, bound and complete will be called immediately + */ + ret = rcar_drif_parse_subdevs(sdr); + if (ret) + goto error; + + sdr->notifier.ops = &rcar_drif_notify_ops; + + /* Register notifier */ + ret = v4l2_async_nf_register(&sdr->v4l2_dev, &sdr->notifier); + if (ret < 0) { + dev_err(sdr->dev, "failed: notifier register ret %d\n", ret); + goto cleanup; + } + + return ret; + +cleanup: + v4l2_async_nf_cleanup(&sdr->notifier); +error: + v4l2_device_unregister(&sdr->v4l2_dev); + + return ret; +} + +/* V4L2 SDR device remove */ +static void rcar_drif_sdr_remove(struct rcar_drif_sdr *sdr) +{ + v4l2_async_nf_unregister(&sdr->notifier); + v4l2_async_nf_cleanup(&sdr->notifier); + v4l2_device_unregister(&sdr->v4l2_dev); +} + +/* DRIF channel probe */ +static int rcar_drif_probe(struct platform_device *pdev) +{ + struct rcar_drif_sdr *sdr; + struct device_node *np; + struct rcar_drif *ch; + struct resource *res; + int ret; + + /* Reserve memory for enabled channel */ + ch = devm_kzalloc(&pdev->dev, sizeof(*ch), GFP_KERNEL); + if (!ch) + return -ENOMEM; + + ch->pdev = pdev; + + /* Module clock */ + ch->clk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(ch->clk)) { + ret = PTR_ERR(ch->clk); + dev_err(&pdev->dev, "clk get failed (%d)\n", ret); + return ret; + } + + /* Register map */ + ch->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); + if (IS_ERR(ch->base)) + return PTR_ERR(ch->base); + + ch->start = res->start; + platform_set_drvdata(pdev, ch); + + /* Check if both channels of the bond are enabled */ + np = rcar_drif_bond_enabled(pdev); + if (np) { + /* Check if current channel acting as primary-bond */ + if (!rcar_drif_primary_bond(pdev)) { + ch->num = 1; /* Primary bond is channel 0 always */ + of_node_put(np); + return 0; + } + } + + /* Reserve memory for SDR structure */ + sdr = devm_kzalloc(&pdev->dev, sizeof(*sdr), GFP_KERNEL); + if (!sdr) { + of_node_put(np); + return -ENOMEM; + } + ch->sdr = sdr; + sdr->dev = &pdev->dev; + + /* Establish links between SDR and channel(s) */ + sdr->ch[ch->num] = ch; + sdr->hw_ch_mask = BIT(ch->num); + if (np) { + /* Check if bonded device is ready */ + ret = rcar_drif_bond_available(sdr, np); + of_node_put(np); + if (ret) + return ret; + } + sdr->num_hw_ch = hweight_long(sdr->hw_ch_mask); + + return rcar_drif_sdr_probe(sdr); +} + +/* DRIF channel remove */ +static int rcar_drif_remove(struct platform_device *pdev) +{ + struct rcar_drif *ch = platform_get_drvdata(pdev); + struct rcar_drif_sdr *sdr = ch->sdr; + + /* Channel 0 will be the SDR instance */ + if (ch->num) + return 0; + + /* SDR instance */ + rcar_drif_sdr_remove(sdr); + + return 0; +} + +/* FIXME: Implement suspend/resume support */ +static int __maybe_unused rcar_drif_suspend(struct device *dev) +{ + return 0; +} + +static int __maybe_unused rcar_drif_resume(struct device *dev) +{ + return 0; +} + +static SIMPLE_DEV_PM_OPS(rcar_drif_pm_ops, rcar_drif_suspend, + rcar_drif_resume); + +static const struct of_device_id rcar_drif_of_table[] = { + { .compatible = "renesas,rcar-gen3-drif" }, + { } +}; +MODULE_DEVICE_TABLE(of, rcar_drif_of_table); + +#define RCAR_DRIF_DRV_NAME "rcar_drif" +static struct platform_driver rcar_drif_driver = { + .driver = { + .name = RCAR_DRIF_DRV_NAME, + .of_match_table = rcar_drif_of_table, + .pm = &rcar_drif_pm_ops, + }, + .probe = rcar_drif_probe, + .remove = rcar_drif_remove, +}; + +module_platform_driver(rcar_drif_driver); + +MODULE_DESCRIPTION("Renesas R-Car Gen3 DRIF driver"); +MODULE_ALIAS("platform:" RCAR_DRIF_DRV_NAME); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Ramesh Shanmugasundaram "); diff --git a/drivers/media/platform/renesas/rcar_fdp1.c b/drivers/media/platform/renesas/rcar_fdp1.c new file mode 100644 index 000000000..dea22e357 --- /dev/null +++ b/drivers/media/platform/renesas/rcar_fdp1.c @@ -0,0 +1,2462 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Renesas R-Car Fine Display Processor + * + * Video format converter and frame deinterlacer device. + * + * Author: Kieran Bingham, + * Copyright (c) 2016 Renesas Electronics Corporation. + * + * This code is developed and inspired from the vim2m, rcar_jpu, + * m2m-deinterlace, and vsp1 drivers. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static unsigned int debug; +module_param(debug, uint, 0644); +MODULE_PARM_DESC(debug, "activate debug info"); + +/* Minimum and maximum frame width/height */ +#define FDP1_MIN_W 80U +#define FDP1_MIN_H 80U + +#define FDP1_MAX_W 3840U +#define FDP1_MAX_H 2160U + +#define FDP1_MAX_PLANES 3U +#define FDP1_MAX_STRIDE 8190U + +/* Flags that indicate a format can be used for capture/output */ +#define FDP1_CAPTURE BIT(0) +#define FDP1_OUTPUT BIT(1) + +#define DRIVER_NAME "rcar_fdp1" + +/* Number of Job's to have available on the processing queue */ +#define FDP1_NUMBER_JOBS 8 + +#define dprintk(fdp1, fmt, arg...) \ + v4l2_dbg(1, debug, &fdp1->v4l2_dev, "%s: " fmt, __func__, ## arg) + +/* + * FDP1 registers and bits + */ + +/* FDP1 start register - Imm */ +#define FD1_CTL_CMD 0x0000 +#define FD1_CTL_CMD_STRCMD BIT(0) + +/* Sync generator register - Imm */ +#define FD1_CTL_SGCMD 0x0004 +#define FD1_CTL_SGCMD_SGEN BIT(0) + +/* Register set end register - Imm */ +#define FD1_CTL_REGEND 0x0008 +#define FD1_CTL_REGEND_REGEND BIT(0) + +/* Channel activation register - Vupdt */ +#define FD1_CTL_CHACT 0x000c +#define FD1_CTL_CHACT_SMW BIT(9) +#define FD1_CTL_CHACT_WR BIT(8) +#define FD1_CTL_CHACT_SMR BIT(3) +#define FD1_CTL_CHACT_RD2 BIT(2) +#define FD1_CTL_CHACT_RD1 BIT(1) +#define FD1_CTL_CHACT_RD0 BIT(0) + +/* Operation Mode Register - Vupdt */ +#define FD1_CTL_OPMODE 0x0010 +#define FD1_CTL_OPMODE_PRG BIT(4) +#define FD1_CTL_OPMODE_VIMD_INTERRUPT (0 << 0) +#define FD1_CTL_OPMODE_VIMD_BESTEFFORT (1 << 0) +#define FD1_CTL_OPMODE_VIMD_NOINTERRUPT (2 << 0) + +#define FD1_CTL_VPERIOD 0x0014 +#define FD1_CTL_CLKCTRL 0x0018 +#define FD1_CTL_CLKCTRL_CSTP_N BIT(0) + +/* Software reset register */ +#define FD1_CTL_SRESET 0x001c +#define FD1_CTL_SRESET_SRST BIT(0) + +/* Control status register (V-update-status) */ +#define FD1_CTL_STATUS 0x0024 +#define FD1_CTL_STATUS_VINT_CNT_MASK GENMASK(31, 16) +#define FD1_CTL_STATUS_VINT_CNT_SHIFT 16 +#define FD1_CTL_STATUS_SGREGSET BIT(10) +#define FD1_CTL_STATUS_SGVERR BIT(9) +#define FD1_CTL_STATUS_SGFREND BIT(8) +#define FD1_CTL_STATUS_BSY BIT(0) + +#define FD1_CTL_VCYCLE_STAT 0x0028 + +/* Interrupt enable register */ +#define FD1_CTL_IRQENB 0x0038 +/* Interrupt status register */ +#define FD1_CTL_IRQSTA 0x003c +/* Interrupt control register */ +#define FD1_CTL_IRQFSET 0x0040 + +/* Common IRQ Bit settings */ +#define FD1_CTL_IRQ_VERE BIT(16) +#define FD1_CTL_IRQ_VINTE BIT(4) +#define FD1_CTL_IRQ_FREE BIT(0) +#define FD1_CTL_IRQ_MASK (FD1_CTL_IRQ_VERE | \ + FD1_CTL_IRQ_VINTE | \ + FD1_CTL_IRQ_FREE) + +/* RPF */ +#define FD1_RPF_SIZE 0x0060 +#define FD1_RPF_SIZE_MASK GENMASK(12, 0) +#define FD1_RPF_SIZE_H_SHIFT 16 +#define FD1_RPF_SIZE_V_SHIFT 0 + +#define FD1_RPF_FORMAT 0x0064 +#define FD1_RPF_FORMAT_CIPM BIT(16) +#define FD1_RPF_FORMAT_RSPYCS BIT(13) +#define FD1_RPF_FORMAT_RSPUVS BIT(12) +#define FD1_RPF_FORMAT_CF BIT(8) + +#define FD1_RPF_PSTRIDE 0x0068 +#define FD1_RPF_PSTRIDE_Y_SHIFT 16 +#define FD1_RPF_PSTRIDE_C_SHIFT 0 + +/* RPF0 Source Component Y Address register */ +#define FD1_RPF0_ADDR_Y 0x006c + +/* RPF1 Current Picture Registers */ +#define FD1_RPF1_ADDR_Y 0x0078 +#define FD1_RPF1_ADDR_C0 0x007c +#define FD1_RPF1_ADDR_C1 0x0080 + +/* RPF2 next picture register */ +#define FD1_RPF2_ADDR_Y 0x0084 + +#define FD1_RPF_SMSK_ADDR 0x0090 +#define FD1_RPF_SWAP 0x0094 + +/* WPF */ +#define FD1_WPF_FORMAT 0x00c0 +#define FD1_WPF_FORMAT_PDV_SHIFT 24 +#define FD1_WPF_FORMAT_FCNL BIT(20) +#define FD1_WPF_FORMAT_WSPYCS BIT(15) +#define FD1_WPF_FORMAT_WSPUVS BIT(14) +#define FD1_WPF_FORMAT_WRTM_601_16 (0 << 9) +#define FD1_WPF_FORMAT_WRTM_601_0 (1 << 9) +#define FD1_WPF_FORMAT_WRTM_709_16 (2 << 9) +#define FD1_WPF_FORMAT_CSC BIT(8) + +#define FD1_WPF_RNDCTL 0x00c4 +#define FD1_WPF_RNDCTL_CBRM BIT(28) +#define FD1_WPF_RNDCTL_CLMD_NOCLIP (0 << 12) +#define FD1_WPF_RNDCTL_CLMD_CLIP_16_235 (1 << 12) +#define FD1_WPF_RNDCTL_CLMD_CLIP_1_254 (2 << 12) + +#define FD1_WPF_PSTRIDE 0x00c8 +#define FD1_WPF_PSTRIDE_Y_SHIFT 16 +#define FD1_WPF_PSTRIDE_C_SHIFT 0 + +/* WPF Destination picture */ +#define FD1_WPF_ADDR_Y 0x00cc +#define FD1_WPF_ADDR_C0 0x00d0 +#define FD1_WPF_ADDR_C1 0x00d4 +#define FD1_WPF_SWAP 0x00d8 +#define FD1_WPF_SWAP_OSWAP_SHIFT 0 +#define FD1_WPF_SWAP_SSWAP_SHIFT 4 + +/* WPF/RPF Common */ +#define FD1_RWPF_SWAP_BYTE BIT(0) +#define FD1_RWPF_SWAP_WORD BIT(1) +#define FD1_RWPF_SWAP_LWRD BIT(2) +#define FD1_RWPF_SWAP_LLWD BIT(3) + +/* IPC */ +#define FD1_IPC_MODE 0x0100 +#define FD1_IPC_MODE_DLI BIT(8) +#define FD1_IPC_MODE_DIM_ADAPT2D3D (0 << 0) +#define FD1_IPC_MODE_DIM_FIXED2D (1 << 0) +#define FD1_IPC_MODE_DIM_FIXED3D (2 << 0) +#define FD1_IPC_MODE_DIM_PREVFIELD (3 << 0) +#define FD1_IPC_MODE_DIM_NEXTFIELD (4 << 0) + +#define FD1_IPC_SMSK_THRESH 0x0104 +#define FD1_IPC_SMSK_THRESH_CONST 0x00010002 + +#define FD1_IPC_COMB_DET 0x0108 +#define FD1_IPC_COMB_DET_CONST 0x00200040 + +#define FD1_IPC_MOTDEC 0x010c +#define FD1_IPC_MOTDEC_CONST 0x00008020 + +/* DLI registers */ +#define FD1_IPC_DLI_BLEND 0x0120 +#define FD1_IPC_DLI_BLEND_CONST 0x0080ff02 + +#define FD1_IPC_DLI_HGAIN 0x0124 +#define FD1_IPC_DLI_HGAIN_CONST 0x001000ff + +#define FD1_IPC_DLI_SPRS 0x0128 +#define FD1_IPC_DLI_SPRS_CONST 0x009004ff + +#define FD1_IPC_DLI_ANGLE 0x012c +#define FD1_IPC_DLI_ANGLE_CONST 0x0004080c + +#define FD1_IPC_DLI_ISOPIX0 0x0130 +#define FD1_IPC_DLI_ISOPIX0_CONST 0xff10ff10 + +#define FD1_IPC_DLI_ISOPIX1 0x0134 +#define FD1_IPC_DLI_ISOPIX1_CONST 0x0000ff10 + +/* Sensor registers */ +#define FD1_IPC_SENSOR_TH0 0x0140 +#define FD1_IPC_SENSOR_TH0_CONST 0x20208080 + +#define FD1_IPC_SENSOR_TH1 0x0144 +#define FD1_IPC_SENSOR_TH1_CONST 0 + +#define FD1_IPC_SENSOR_CTL0 0x0170 +#define FD1_IPC_SENSOR_CTL0_CONST 0x00002201 + +#define FD1_IPC_SENSOR_CTL1 0x0174 +#define FD1_IPC_SENSOR_CTL1_CONST 0 + +#define FD1_IPC_SENSOR_CTL2 0x0178 +#define FD1_IPC_SENSOR_CTL2_X_SHIFT 16 +#define FD1_IPC_SENSOR_CTL2_Y_SHIFT 0 + +#define FD1_IPC_SENSOR_CTL3 0x017c +#define FD1_IPC_SENSOR_CTL3_0_SHIFT 16 +#define FD1_IPC_SENSOR_CTL3_1_SHIFT 0 + +/* Line memory pixel number register */ +#define FD1_IPC_LMEM 0x01e0 +#define FD1_IPC_LMEM_LINEAR 1024 +#define FD1_IPC_LMEM_TILE 960 + +/* Internal Data (HW Version) */ +#define FD1_IP_INTDATA 0x0800 +#define FD1_IP_H3_ES1 0x02010101 +#define FD1_IP_M3W 0x02010202 +#define FD1_IP_H3 0x02010203 +#define FD1_IP_M3N 0x02010204 +#define FD1_IP_E3 0x02010205 + +/* LUTs */ +#define FD1_LUT_DIF_ADJ 0x1000 +#define FD1_LUT_SAD_ADJ 0x1400 +#define FD1_LUT_BLD_GAIN 0x1800 +#define FD1_LUT_DIF_GAIN 0x1c00 +#define FD1_LUT_MDET 0x2000 + +/** + * struct fdp1_fmt - The FDP1 internal format data + * @fourcc: the fourcc code, to match the V4L2 API + * @bpp: bits per pixel per plane + * @num_planes: number of planes + * @hsub: horizontal subsampling factor + * @vsub: vertical subsampling factor + * @fmt: 7-bit format code for the fdp1 hardware + * @swap_yc: the Y and C components are swapped (Y comes before C) + * @swap_uv: the U and V components are swapped (V comes before U) + * @swap: swap register control + * @types: types of queue this format is applicable to + */ +struct fdp1_fmt { + u32 fourcc; + u8 bpp[3]; + u8 num_planes; + u8 hsub; + u8 vsub; + u8 fmt; + bool swap_yc; + bool swap_uv; + u8 swap; + u8 types; +}; + +static const struct fdp1_fmt fdp1_formats[] = { + /* RGB formats are only supported by the Write Pixel Formatter */ + + { V4L2_PIX_FMT_RGB332, { 8, 0, 0 }, 1, 1, 1, 0x00, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_XRGB444, { 16, 0, 0 }, 1, 1, 1, 0x01, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_XRGB555, { 16, 0, 0 }, 1, 1, 1, 0x04, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_RGB565, { 16, 0, 0 }, 1, 1, 1, 0x06, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_ABGR32, { 32, 0, 0 }, 1, 1, 1, 0x13, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_XBGR32, { 32, 0, 0 }, 1, 1, 1, 0x13, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_ARGB32, { 32, 0, 0 }, 1, 1, 1, 0x13, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_XRGB32, { 32, 0, 0 }, 1, 1, 1, 0x13, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_RGB24, { 24, 0, 0 }, 1, 1, 1, 0x15, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_BGR24, { 24, 0, 0 }, 1, 1, 1, 0x18, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_ARGB444, { 16, 0, 0 }, 1, 1, 1, 0x19, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD, + FDP1_CAPTURE }, + { V4L2_PIX_FMT_ARGB555, { 16, 0, 0 }, 1, 1, 1, 0x1b, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD, + FDP1_CAPTURE }, + + /* YUV Formats are supported by Read and Write Pixel Formatters */ + + { V4L2_PIX_FMT_NV16M, { 8, 16, 0 }, 2, 2, 1, 0x41, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_NV61M, { 8, 16, 0 }, 2, 2, 1, 0x41, false, true, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_NV12M, { 8, 16, 0 }, 2, 2, 2, 0x42, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_NV21M, { 8, 16, 0 }, 2, 2, 2, 0x42, false, true, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_UYVY, { 16, 0, 0 }, 1, 2, 1, 0x47, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_VYUY, { 16, 0, 0 }, 1, 2, 1, 0x47, false, true, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_YUYV, { 16, 0, 0 }, 1, 2, 1, 0x47, true, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_YVYU, { 16, 0, 0 }, 1, 2, 1, 0x47, true, true, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_YUV444M, { 8, 8, 8 }, 3, 1, 1, 0x4a, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_YVU444M, { 8, 8, 8 }, 3, 1, 1, 0x4a, false, true, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_YUV422M, { 8, 8, 8 }, 3, 2, 1, 0x4b, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_YVU422M, { 8, 8, 8 }, 3, 2, 1, 0x4b, false, true, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_YUV420M, { 8, 8, 8 }, 3, 2, 2, 0x4c, false, false, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, + { V4L2_PIX_FMT_YVU420M, { 8, 8, 8 }, 3, 2, 2, 0x4c, false, true, + FD1_RWPF_SWAP_LLWD | FD1_RWPF_SWAP_LWRD | + FD1_RWPF_SWAP_WORD | FD1_RWPF_SWAP_BYTE, + FDP1_CAPTURE | FDP1_OUTPUT }, +}; + +static int fdp1_fmt_is_rgb(const struct fdp1_fmt *fmt) +{ + return fmt->fmt <= 0x1b; /* Last RGB code */ +} + +/* + * FDP1 Lookup tables range from 0...255 only + * + * Each table must be less than 256 entries, and all tables + * are padded out to 256 entries by duplicating the last value. + */ +static const u8 fdp1_diff_adj[] = { + 0x00, 0x24, 0x43, 0x5e, 0x76, 0x8c, 0x9e, 0xaf, + 0xbd, 0xc9, 0xd4, 0xdd, 0xe4, 0xea, 0xef, 0xf3, + 0xf6, 0xf9, 0xfb, 0xfc, 0xfd, 0xfe, 0xfe, 0xff, +}; + +static const u8 fdp1_sad_adj[] = { + 0x00, 0x24, 0x43, 0x5e, 0x76, 0x8c, 0x9e, 0xaf, + 0xbd, 0xc9, 0xd4, 0xdd, 0xe4, 0xea, 0xef, 0xf3, + 0xf6, 0xf9, 0xfb, 0xfc, 0xfd, 0xfe, 0xfe, 0xff, +}; + +static const u8 fdp1_bld_gain[] = { + 0x80, +}; + +static const u8 fdp1_dif_gain[] = { + 0x80, +}; + +static const u8 fdp1_mdet[] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, + 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, + 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, + 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, + 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, + 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff +}; + +/* Per-queue, driver-specific private data */ +struct fdp1_q_data { + const struct fdp1_fmt *fmt; + struct v4l2_pix_format_mplane format; + + unsigned int vsize; + unsigned int stride_y; + unsigned int stride_c; +}; + +static const struct fdp1_fmt *fdp1_find_format(u32 pixelformat) +{ + const struct fdp1_fmt *fmt; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fdp1_formats); i++) { + fmt = &fdp1_formats[i]; + if (fmt->fourcc == pixelformat) + return fmt; + } + + return NULL; +} + +enum fdp1_deint_mode { + FDP1_PROGRESSIVE = 0, /* Must be zero when !deinterlacing */ + FDP1_ADAPT2D3D, + FDP1_FIXED2D, + FDP1_FIXED3D, + FDP1_PREVFIELD, + FDP1_NEXTFIELD, +}; + +#define FDP1_DEINT_MODE_USES_NEXT(mode) \ + (mode == FDP1_ADAPT2D3D || \ + mode == FDP1_FIXED3D || \ + mode == FDP1_NEXTFIELD) + +#define FDP1_DEINT_MODE_USES_PREV(mode) \ + (mode == FDP1_ADAPT2D3D || \ + mode == FDP1_FIXED3D || \ + mode == FDP1_PREVFIELD) + +/* + * FDP1 operates on potentially 3 fields, which are tracked + * from the VB buffers using this context structure. + * Will always be a field or a full frame, never two fields. + */ +struct fdp1_field_buffer { + struct vb2_v4l2_buffer *vb; + dma_addr_t addrs[3]; + + /* Should be NONE:TOP:BOTTOM only */ + enum v4l2_field field; + + /* Flag to indicate this is the last field in the vb */ + bool last_field; + + /* Buffer queue lists */ + struct list_head list; +}; + +struct fdp1_buffer { + struct v4l2_m2m_buffer m2m_buf; + struct fdp1_field_buffer fields[2]; + unsigned int num_fields; +}; + +static inline struct fdp1_buffer *to_fdp1_buffer(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct fdp1_buffer, m2m_buf.vb); +} + +struct fdp1_job { + struct fdp1_field_buffer *previous; + struct fdp1_field_buffer *active; + struct fdp1_field_buffer *next; + struct fdp1_field_buffer *dst; + + /* A job can only be on one list at a time */ + struct list_head list; +}; + +struct fdp1_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; + + struct mutex dev_mutex; + spinlock_t irqlock; + spinlock_t device_process_lock; + + void __iomem *regs; + unsigned int irq; + struct device *dev; + + /* Job Queues */ + struct fdp1_job jobs[FDP1_NUMBER_JOBS]; + struct list_head free_job_list; + struct list_head queued_job_list; + struct list_head hw_job_list; + + unsigned int clk_rate; + + struct rcar_fcp_device *fcp; + struct v4l2_m2m_dev *m2m_dev; +}; + +struct fdp1_ctx { + struct v4l2_fh fh; + struct fdp1_dev *fdp1; + + struct v4l2_ctrl_handler hdl; + unsigned int sequence; + + /* Processed buffers in this transaction */ + u8 num_processed; + + /* Transaction length (i.e. how many buffers per transaction) */ + u32 translen; + + /* Abort requested by m2m */ + int aborting; + + /* Deinterlace processing mode */ + enum fdp1_deint_mode deint_mode; + + /* + * Adaptive 2D/3D mode uses a shared mask + * This is allocated at streamon, if the ADAPT2D3D mode + * is requested + */ + unsigned int smsk_size; + dma_addr_t smsk_addr[2]; + void *smsk_cpu; + + /* Capture pipeline, can specify an alpha value + * for supported formats. 0-255 only + */ + unsigned char alpha; + + /* Source and destination queue data */ + struct fdp1_q_data out_q; /* HW Source */ + struct fdp1_q_data cap_q; /* HW Destination */ + + /* + * Field Queues + * Interlaced fields are used on 3 occasions, and tracked in this list. + * + * V4L2 Buffers are tracked inside the fdp1_buffer + * and released when the last 'field' completes + */ + struct list_head fields_queue; + unsigned int buffers_queued; + + /* + * For de-interlacing we need to track our previous buffer + * while preparing our job lists. + */ + struct fdp1_field_buffer *previous; +}; + +static inline struct fdp1_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct fdp1_ctx, fh); +} + +static struct fdp1_q_data *get_q_data(struct fdp1_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->out_q; + else + return &ctx->cap_q; +} + +/* + * list_remove_job: Take the first item off the specified job list + * + * Returns: pointer to a job, or NULL if the list is empty. + */ +static struct fdp1_job *list_remove_job(struct fdp1_dev *fdp1, + struct list_head *list) +{ + struct fdp1_job *job; + unsigned long flags; + + spin_lock_irqsave(&fdp1->irqlock, flags); + job = list_first_entry_or_null(list, struct fdp1_job, list); + if (job) + list_del(&job->list); + spin_unlock_irqrestore(&fdp1->irqlock, flags); + + return job; +} + +/* + * list_add_job: Add a job to the specified job list + * + * Returns: void - always succeeds + */ +static void list_add_job(struct fdp1_dev *fdp1, + struct list_head *list, + struct fdp1_job *job) +{ + unsigned long flags; + + spin_lock_irqsave(&fdp1->irqlock, flags); + list_add_tail(&job->list, list); + spin_unlock_irqrestore(&fdp1->irqlock, flags); +} + +static struct fdp1_job *fdp1_job_alloc(struct fdp1_dev *fdp1) +{ + return list_remove_job(fdp1, &fdp1->free_job_list); +} + +static void fdp1_job_free(struct fdp1_dev *fdp1, struct fdp1_job *job) +{ + /* Ensure that all residue from previous jobs is gone */ + memset(job, 0, sizeof(struct fdp1_job)); + + list_add_job(fdp1, &fdp1->free_job_list, job); +} + +static void queue_job(struct fdp1_dev *fdp1, struct fdp1_job *job) +{ + list_add_job(fdp1, &fdp1->queued_job_list, job); +} + +static struct fdp1_job *get_queued_job(struct fdp1_dev *fdp1) +{ + return list_remove_job(fdp1, &fdp1->queued_job_list); +} + +static void queue_hw_job(struct fdp1_dev *fdp1, struct fdp1_job *job) +{ + list_add_job(fdp1, &fdp1->hw_job_list, job); +} + +static struct fdp1_job *get_hw_queued_job(struct fdp1_dev *fdp1) +{ + return list_remove_job(fdp1, &fdp1->hw_job_list); +} + +/* + * Buffer lists handling + */ +static void fdp1_field_complete(struct fdp1_ctx *ctx, + struct fdp1_field_buffer *fbuf) +{ + /* job->previous may be on the first field */ + if (!fbuf) + return; + + if (fbuf->last_field) + v4l2_m2m_buf_done(fbuf->vb, VB2_BUF_STATE_DONE); +} + +static void fdp1_queue_field(struct fdp1_ctx *ctx, + struct fdp1_field_buffer *fbuf) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->fdp1->irqlock, flags); + list_add_tail(&fbuf->list, &ctx->fields_queue); + spin_unlock_irqrestore(&ctx->fdp1->irqlock, flags); + + ctx->buffers_queued++; +} + +static struct fdp1_field_buffer *fdp1_dequeue_field(struct fdp1_ctx *ctx) +{ + struct fdp1_field_buffer *fbuf; + unsigned long flags; + + ctx->buffers_queued--; + + spin_lock_irqsave(&ctx->fdp1->irqlock, flags); + fbuf = list_first_entry_or_null(&ctx->fields_queue, + struct fdp1_field_buffer, list); + if (fbuf) + list_del(&fbuf->list); + spin_unlock_irqrestore(&ctx->fdp1->irqlock, flags); + + return fbuf; +} + +/* + * Return the next field in the queue - or NULL, + * without removing the item from the list + */ +static struct fdp1_field_buffer *fdp1_peek_queued_field(struct fdp1_ctx *ctx) +{ + struct fdp1_field_buffer *fbuf; + unsigned long flags; + + spin_lock_irqsave(&ctx->fdp1->irqlock, flags); + fbuf = list_first_entry_or_null(&ctx->fields_queue, + struct fdp1_field_buffer, list); + spin_unlock_irqrestore(&ctx->fdp1->irqlock, flags); + + return fbuf; +} + +static u32 fdp1_read(struct fdp1_dev *fdp1, unsigned int reg) +{ + u32 value = ioread32(fdp1->regs + reg); + + if (debug >= 2) + dprintk(fdp1, "Read 0x%08x from 0x%04x\n", value, reg); + + return value; +} + +static void fdp1_write(struct fdp1_dev *fdp1, u32 val, unsigned int reg) +{ + if (debug >= 2) + dprintk(fdp1, "Write 0x%08x to 0x%04x\n", val, reg); + + iowrite32(val, fdp1->regs + reg); +} + +/* IPC registers are to be programmed with constant values */ +static void fdp1_set_ipc_dli(struct fdp1_ctx *ctx) +{ + struct fdp1_dev *fdp1 = ctx->fdp1; + + fdp1_write(fdp1, FD1_IPC_SMSK_THRESH_CONST, FD1_IPC_SMSK_THRESH); + fdp1_write(fdp1, FD1_IPC_COMB_DET_CONST, FD1_IPC_COMB_DET); + fdp1_write(fdp1, FD1_IPC_MOTDEC_CONST, FD1_IPC_MOTDEC); + + fdp1_write(fdp1, FD1_IPC_DLI_BLEND_CONST, FD1_IPC_DLI_BLEND); + fdp1_write(fdp1, FD1_IPC_DLI_HGAIN_CONST, FD1_IPC_DLI_HGAIN); + fdp1_write(fdp1, FD1_IPC_DLI_SPRS_CONST, FD1_IPC_DLI_SPRS); + fdp1_write(fdp1, FD1_IPC_DLI_ANGLE_CONST, FD1_IPC_DLI_ANGLE); + fdp1_write(fdp1, FD1_IPC_DLI_ISOPIX0_CONST, FD1_IPC_DLI_ISOPIX0); + fdp1_write(fdp1, FD1_IPC_DLI_ISOPIX1_CONST, FD1_IPC_DLI_ISOPIX1); +} + + +static void fdp1_set_ipc_sensor(struct fdp1_ctx *ctx) +{ + struct fdp1_dev *fdp1 = ctx->fdp1; + struct fdp1_q_data *src_q_data = &ctx->out_q; + unsigned int x0, x1; + unsigned int hsize = src_q_data->format.width; + unsigned int vsize = src_q_data->format.height; + + x0 = hsize / 3; + x1 = 2 * hsize / 3; + + fdp1_write(fdp1, FD1_IPC_SENSOR_TH0_CONST, FD1_IPC_SENSOR_TH0); + fdp1_write(fdp1, FD1_IPC_SENSOR_TH1_CONST, FD1_IPC_SENSOR_TH1); + fdp1_write(fdp1, FD1_IPC_SENSOR_CTL0_CONST, FD1_IPC_SENSOR_CTL0); + fdp1_write(fdp1, FD1_IPC_SENSOR_CTL1_CONST, FD1_IPC_SENSOR_CTL1); + + fdp1_write(fdp1, ((hsize - 1) << FD1_IPC_SENSOR_CTL2_X_SHIFT) | + ((vsize - 1) << FD1_IPC_SENSOR_CTL2_Y_SHIFT), + FD1_IPC_SENSOR_CTL2); + + fdp1_write(fdp1, (x0 << FD1_IPC_SENSOR_CTL3_0_SHIFT) | + (x1 << FD1_IPC_SENSOR_CTL3_1_SHIFT), + FD1_IPC_SENSOR_CTL3); +} + +/* + * fdp1_write_lut: Write a padded LUT to the hw + * + * FDP1 uses constant data for de-interlacing processing, + * with large tables. These hardware tables are all 256 bytes + * long, however they often contain repeated data at the end. + * + * The last byte of the table is written to all remaining entries. + */ +static void fdp1_write_lut(struct fdp1_dev *fdp1, const u8 *lut, + unsigned int len, unsigned int base) +{ + unsigned int i; + u8 pad; + + /* Tables larger than the hw are clipped */ + len = min(len, 256u); + + for (i = 0; i < len; i++) + fdp1_write(fdp1, lut[i], base + (i*4)); + + /* Tables are padded with the last entry */ + pad = lut[i-1]; + + for (; i < 256; i++) + fdp1_write(fdp1, pad, base + (i*4)); +} + +static void fdp1_set_lut(struct fdp1_dev *fdp1) +{ + fdp1_write_lut(fdp1, fdp1_diff_adj, ARRAY_SIZE(fdp1_diff_adj), + FD1_LUT_DIF_ADJ); + fdp1_write_lut(fdp1, fdp1_sad_adj, ARRAY_SIZE(fdp1_sad_adj), + FD1_LUT_SAD_ADJ); + fdp1_write_lut(fdp1, fdp1_bld_gain, ARRAY_SIZE(fdp1_bld_gain), + FD1_LUT_BLD_GAIN); + fdp1_write_lut(fdp1, fdp1_dif_gain, ARRAY_SIZE(fdp1_dif_gain), + FD1_LUT_DIF_GAIN); + fdp1_write_lut(fdp1, fdp1_mdet, ARRAY_SIZE(fdp1_mdet), + FD1_LUT_MDET); +} + +static void fdp1_configure_rpf(struct fdp1_ctx *ctx, + struct fdp1_job *job) +{ + struct fdp1_dev *fdp1 = ctx->fdp1; + u32 picture_size; + u32 pstride; + u32 format; + u32 smsk_addr; + + struct fdp1_q_data *q_data = &ctx->out_q; + + /* Picture size is common to Source and Destination frames */ + picture_size = (q_data->format.width << FD1_RPF_SIZE_H_SHIFT) + | (q_data->vsize << FD1_RPF_SIZE_V_SHIFT); + + /* Strides */ + pstride = q_data->stride_y << FD1_RPF_PSTRIDE_Y_SHIFT; + if (q_data->format.num_planes > 1) + pstride |= q_data->stride_c << FD1_RPF_PSTRIDE_C_SHIFT; + + /* Format control */ + format = q_data->fmt->fmt; + if (q_data->fmt->swap_yc) + format |= FD1_RPF_FORMAT_RSPYCS; + + if (q_data->fmt->swap_uv) + format |= FD1_RPF_FORMAT_RSPUVS; + + if (job->active->field == V4L2_FIELD_BOTTOM) { + format |= FD1_RPF_FORMAT_CF; /* Set for Bottom field */ + smsk_addr = ctx->smsk_addr[0]; + } else { + smsk_addr = ctx->smsk_addr[1]; + } + + /* Deint mode is non-zero when deinterlacing */ + if (ctx->deint_mode) + format |= FD1_RPF_FORMAT_CIPM; + + fdp1_write(fdp1, format, FD1_RPF_FORMAT); + fdp1_write(fdp1, q_data->fmt->swap, FD1_RPF_SWAP); + fdp1_write(fdp1, picture_size, FD1_RPF_SIZE); + fdp1_write(fdp1, pstride, FD1_RPF_PSTRIDE); + fdp1_write(fdp1, smsk_addr, FD1_RPF_SMSK_ADDR); + + /* Previous Field Channel (CH0) */ + if (job->previous) + fdp1_write(fdp1, job->previous->addrs[0], FD1_RPF0_ADDR_Y); + + /* Current Field Channel (CH1) */ + fdp1_write(fdp1, job->active->addrs[0], FD1_RPF1_ADDR_Y); + fdp1_write(fdp1, job->active->addrs[1], FD1_RPF1_ADDR_C0); + fdp1_write(fdp1, job->active->addrs[2], FD1_RPF1_ADDR_C1); + + /* Next Field Channel (CH2) */ + if (job->next) + fdp1_write(fdp1, job->next->addrs[0], FD1_RPF2_ADDR_Y); +} + +static void fdp1_configure_wpf(struct fdp1_ctx *ctx, + struct fdp1_job *job) +{ + struct fdp1_dev *fdp1 = ctx->fdp1; + struct fdp1_q_data *src_q_data = &ctx->out_q; + struct fdp1_q_data *q_data = &ctx->cap_q; + u32 pstride; + u32 format; + u32 swap; + u32 rndctl; + + pstride = q_data->format.plane_fmt[0].bytesperline + << FD1_WPF_PSTRIDE_Y_SHIFT; + + if (q_data->format.num_planes > 1) + pstride |= q_data->format.plane_fmt[1].bytesperline + << FD1_WPF_PSTRIDE_C_SHIFT; + + format = q_data->fmt->fmt; /* Output Format Code */ + + if (q_data->fmt->swap_yc) + format |= FD1_WPF_FORMAT_WSPYCS; + + if (q_data->fmt->swap_uv) + format |= FD1_WPF_FORMAT_WSPUVS; + + if (fdp1_fmt_is_rgb(q_data->fmt)) { + /* Enable Colour Space conversion */ + format |= FD1_WPF_FORMAT_CSC; + + /* Set WRTM */ + if (src_q_data->format.ycbcr_enc == V4L2_YCBCR_ENC_709) + format |= FD1_WPF_FORMAT_WRTM_709_16; + else if (src_q_data->format.quantization == + V4L2_QUANTIZATION_FULL_RANGE) + format |= FD1_WPF_FORMAT_WRTM_601_0; + else + format |= FD1_WPF_FORMAT_WRTM_601_16; + } + + /* Set an alpha value into the Pad Value */ + format |= ctx->alpha << FD1_WPF_FORMAT_PDV_SHIFT; + + /* Determine picture rounding and clipping */ + rndctl = FD1_WPF_RNDCTL_CBRM; /* Rounding Off */ + rndctl |= FD1_WPF_RNDCTL_CLMD_NOCLIP; + + /* WPF Swap needs both ISWAP and OSWAP setting */ + swap = q_data->fmt->swap << FD1_WPF_SWAP_OSWAP_SHIFT; + swap |= src_q_data->fmt->swap << FD1_WPF_SWAP_SSWAP_SHIFT; + + fdp1_write(fdp1, format, FD1_WPF_FORMAT); + fdp1_write(fdp1, rndctl, FD1_WPF_RNDCTL); + fdp1_write(fdp1, swap, FD1_WPF_SWAP); + fdp1_write(fdp1, pstride, FD1_WPF_PSTRIDE); + + fdp1_write(fdp1, job->dst->addrs[0], FD1_WPF_ADDR_Y); + fdp1_write(fdp1, job->dst->addrs[1], FD1_WPF_ADDR_C0); + fdp1_write(fdp1, job->dst->addrs[2], FD1_WPF_ADDR_C1); +} + +static void fdp1_configure_deint_mode(struct fdp1_ctx *ctx, + struct fdp1_job *job) +{ + struct fdp1_dev *fdp1 = ctx->fdp1; + u32 opmode = FD1_CTL_OPMODE_VIMD_NOINTERRUPT; + u32 ipcmode = FD1_IPC_MODE_DLI; /* Always set */ + u32 channels = FD1_CTL_CHACT_WR | FD1_CTL_CHACT_RD1; /* Always on */ + + /* De-interlacing Mode */ + switch (ctx->deint_mode) { + default: + case FDP1_PROGRESSIVE: + dprintk(fdp1, "Progressive Mode\n"); + opmode |= FD1_CTL_OPMODE_PRG; + ipcmode |= FD1_IPC_MODE_DIM_FIXED2D; + break; + case FDP1_ADAPT2D3D: + dprintk(fdp1, "Adapt2D3D Mode\n"); + if (ctx->sequence == 0 || ctx->aborting) + ipcmode |= FD1_IPC_MODE_DIM_FIXED2D; + else + ipcmode |= FD1_IPC_MODE_DIM_ADAPT2D3D; + + if (ctx->sequence > 1) { + channels |= FD1_CTL_CHACT_SMW; + channels |= FD1_CTL_CHACT_RD0 | FD1_CTL_CHACT_RD2; + } + + if (ctx->sequence > 2) + channels |= FD1_CTL_CHACT_SMR; + + break; + case FDP1_FIXED3D: + dprintk(fdp1, "Fixed 3D Mode\n"); + ipcmode |= FD1_IPC_MODE_DIM_FIXED3D; + /* Except for first and last frame, enable all channels */ + if (!(ctx->sequence == 0 || ctx->aborting)) + channels |= FD1_CTL_CHACT_RD0 | FD1_CTL_CHACT_RD2; + break; + case FDP1_FIXED2D: + dprintk(fdp1, "Fixed 2D Mode\n"); + ipcmode |= FD1_IPC_MODE_DIM_FIXED2D; + /* No extra channels enabled */ + break; + case FDP1_PREVFIELD: + dprintk(fdp1, "Previous Field Mode\n"); + ipcmode |= FD1_IPC_MODE_DIM_PREVFIELD; + channels |= FD1_CTL_CHACT_RD0; /* Previous */ + break; + case FDP1_NEXTFIELD: + dprintk(fdp1, "Next Field Mode\n"); + ipcmode |= FD1_IPC_MODE_DIM_NEXTFIELD; + channels |= FD1_CTL_CHACT_RD2; /* Next */ + break; + } + + fdp1_write(fdp1, channels, FD1_CTL_CHACT); + fdp1_write(fdp1, opmode, FD1_CTL_OPMODE); + fdp1_write(fdp1, ipcmode, FD1_IPC_MODE); +} + +/* + * fdp1_device_process() - Run the hardware + * + * Configure and start the hardware to generate a single frame + * of output given our input parameters. + */ +static int fdp1_device_process(struct fdp1_ctx *ctx) + +{ + struct fdp1_dev *fdp1 = ctx->fdp1; + struct fdp1_job *job; + unsigned long flags; + + spin_lock_irqsave(&fdp1->device_process_lock, flags); + + /* Get a job to process */ + job = get_queued_job(fdp1); + if (!job) { + /* + * VINT can call us to see if we can queue another job. + * If we have no work to do, we simply return. + */ + spin_unlock_irqrestore(&fdp1->device_process_lock, flags); + return 0; + } + + /* First Frame only? ... */ + fdp1_write(fdp1, FD1_CTL_CLKCTRL_CSTP_N, FD1_CTL_CLKCTRL); + + /* Set the mode, and configuration */ + fdp1_configure_deint_mode(ctx, job); + + /* DLI Static Configuration */ + fdp1_set_ipc_dli(ctx); + + /* Sensor Configuration */ + fdp1_set_ipc_sensor(ctx); + + /* Setup the source picture */ + fdp1_configure_rpf(ctx, job); + + /* Setup the destination picture */ + fdp1_configure_wpf(ctx, job); + + /* Line Memory Pixel Number Register for linear access */ + fdp1_write(fdp1, FD1_IPC_LMEM_LINEAR, FD1_IPC_LMEM); + + /* Enable Interrupts */ + fdp1_write(fdp1, FD1_CTL_IRQ_MASK, FD1_CTL_IRQENB); + + /* Finally, the Immediate Registers */ + + /* This job is now in the HW queue */ + queue_hw_job(fdp1, job); + + /* Start the command */ + fdp1_write(fdp1, FD1_CTL_CMD_STRCMD, FD1_CTL_CMD); + + /* Registers will update to HW at next VINT */ + fdp1_write(fdp1, FD1_CTL_REGEND_REGEND, FD1_CTL_REGEND); + + /* Enable VINT Generator */ + fdp1_write(fdp1, FD1_CTL_SGCMD_SGEN, FD1_CTL_SGCMD); + + spin_unlock_irqrestore(&fdp1->device_process_lock, flags); + + return 0; +} + +/* + * mem2mem callbacks + */ + +/* + * job_ready() - check whether an instance is ready to be scheduled to run + */ +static int fdp1_m2m_job_ready(void *priv) +{ + struct fdp1_ctx *ctx = priv; + struct fdp1_q_data *src_q_data = &ctx->out_q; + int srcbufs = 1; + int dstbufs = 1; + + dprintk(ctx->fdp1, "+ Src: %d : Dst: %d\n", + v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx), + v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx)); + + /* One output buffer is required for each field */ + if (V4L2_FIELD_HAS_BOTH(src_q_data->format.field)) + dstbufs = 2; + + if (v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) < srcbufs + || v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) < dstbufs) { + dprintk(ctx->fdp1, "Not enough buffers available\n"); + return 0; + } + + return 1; +} + +static void fdp1_m2m_job_abort(void *priv) +{ + struct fdp1_ctx *ctx = priv; + + dprintk(ctx->fdp1, "+\n"); + + /* Will cancel the transaction in the next interrupt handler */ + ctx->aborting = 1; + + /* Immediate abort sequence */ + fdp1_write(ctx->fdp1, 0, FD1_CTL_SGCMD); + fdp1_write(ctx->fdp1, FD1_CTL_SRESET_SRST, FD1_CTL_SRESET); +} + +/* + * fdp1_prepare_job: Prepare and queue a new job for a single action of work + * + * Prepare the next field, (or frame in progressive) and an output + * buffer for the hardware to perform a single operation. + */ +static struct fdp1_job *fdp1_prepare_job(struct fdp1_ctx *ctx) +{ + struct vb2_v4l2_buffer *vbuf; + struct fdp1_buffer *fbuf; + struct fdp1_dev *fdp1 = ctx->fdp1; + struct fdp1_job *job; + unsigned int buffers_required = 1; + + dprintk(fdp1, "+\n"); + + if (FDP1_DEINT_MODE_USES_NEXT(ctx->deint_mode)) + buffers_required = 2; + + if (ctx->buffers_queued < buffers_required) + return NULL; + + job = fdp1_job_alloc(fdp1); + if (!job) { + dprintk(fdp1, "No free jobs currently available\n"); + return NULL; + } + + job->active = fdp1_dequeue_field(ctx); + if (!job->active) { + /* Buffer check should prevent this ever happening */ + dprintk(fdp1, "No input buffers currently available\n"); + + fdp1_job_free(fdp1, job); + return NULL; + } + + dprintk(fdp1, "+ Buffer en-route...\n"); + + /* Source buffers have been prepared on our buffer_queue + * Prepare our Output buffer + */ + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + fbuf = to_fdp1_buffer(vbuf); + job->dst = &fbuf->fields[0]; + + job->active->vb->sequence = ctx->sequence; + job->dst->vb->sequence = ctx->sequence; + ctx->sequence++; + + if (FDP1_DEINT_MODE_USES_PREV(ctx->deint_mode)) { + job->previous = ctx->previous; + + /* Active buffer becomes the next job's previous buffer */ + ctx->previous = job->active; + } + + if (FDP1_DEINT_MODE_USES_NEXT(ctx->deint_mode)) { + /* Must be called after 'active' is dequeued */ + job->next = fdp1_peek_queued_field(ctx); + } + + /* Transfer timestamps and flags from src->dst */ + + job->dst->vb->vb2_buf.timestamp = job->active->vb->vb2_buf.timestamp; + + job->dst->vb->flags = job->active->vb->flags & + V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + + /* Ideally, the frame-end function will just 'check' to see + * if there are more jobs instead + */ + ctx->translen++; + + /* Finally, Put this job on the processing queue */ + queue_job(fdp1, job); + + dprintk(fdp1, "Job Queued translen = %d\n", ctx->translen); + + return job; +} + +/* fdp1_m2m_device_run() - prepares and starts the device for an M2M task + * + * A single input buffer is taken and serialised into our fdp1_buffer + * queue. The queue is then processed to create as many jobs as possible + * from our available input. + */ +static void fdp1_m2m_device_run(void *priv) +{ + struct fdp1_ctx *ctx = priv; + struct fdp1_dev *fdp1 = ctx->fdp1; + struct vb2_v4l2_buffer *src_vb; + struct fdp1_buffer *buf; + unsigned int i; + + dprintk(fdp1, "+\n"); + + ctx->translen = 0; + + /* Get our incoming buffer of either one or two fields, or one frame */ + src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + buf = to_fdp1_buffer(src_vb); + + for (i = 0; i < buf->num_fields; i++) { + struct fdp1_field_buffer *fbuf = &buf->fields[i]; + + fdp1_queue_field(ctx, fbuf); + dprintk(fdp1, "Queued Buffer [%d] last_field:%d\n", + i, fbuf->last_field); + } + + /* Queue as many jobs as our data provides for */ + while (fdp1_prepare_job(ctx)) + ; + + if (ctx->translen == 0) { + dprintk(fdp1, "No jobs were processed. M2M action complete\n"); + v4l2_m2m_job_finish(fdp1->m2m_dev, ctx->fh.m2m_ctx); + return; + } + + /* Kick the job processing action */ + fdp1_device_process(ctx); +} + +/* + * device_frame_end: + * + * Handles the M2M level after a buffer completion event. + */ +static void device_frame_end(struct fdp1_dev *fdp1, + enum vb2_buffer_state state) +{ + struct fdp1_ctx *ctx; + unsigned long flags; + struct fdp1_job *job = get_hw_queued_job(fdp1); + + dprintk(fdp1, "+\n"); + + ctx = v4l2_m2m_get_curr_priv(fdp1->m2m_dev); + + if (ctx == NULL) { + v4l2_err(&fdp1->v4l2_dev, + "Instance released before the end of transaction\n"); + return; + } + + ctx->num_processed++; + + /* + * fdp1_field_complete will call buf_done only when the last vb2_buffer + * reference is complete + */ + if (FDP1_DEINT_MODE_USES_PREV(ctx->deint_mode)) + fdp1_field_complete(ctx, job->previous); + else + fdp1_field_complete(ctx, job->active); + + spin_lock_irqsave(&fdp1->irqlock, flags); + v4l2_m2m_buf_done(job->dst->vb, state); + job->dst = NULL; + spin_unlock_irqrestore(&fdp1->irqlock, flags); + + /* Move this job back to the free job list */ + fdp1_job_free(fdp1, job); + + dprintk(fdp1, "curr_ctx->num_processed %d curr_ctx->translen %d\n", + ctx->num_processed, ctx->translen); + + if (ctx->num_processed == ctx->translen || + ctx->aborting) { + dprintk(ctx->fdp1, "Finishing transaction\n"); + ctx->num_processed = 0; + v4l2_m2m_job_finish(fdp1->m2m_dev, ctx->fh.m2m_ctx); + } else { + /* + * For pipelined performance support, this would + * be called from a VINT handler + */ + fdp1_device_process(ctx); + } +} + +/* + * video ioctls + */ +static int fdp1_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, DRIVER_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", DRIVER_NAME); + return 0; +} + +static int fdp1_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + unsigned int i, num; + + num = 0; + + for (i = 0; i < ARRAY_SIZE(fdp1_formats); ++i) { + if (fdp1_formats[i].types & type) { + if (num == f->index) + break; + ++num; + } + } + + /* Format not found */ + if (i >= ARRAY_SIZE(fdp1_formats)) + return -EINVAL; + + /* Format found */ + f->pixelformat = fdp1_formats[i].fourcc; + + return 0; +} + +static int fdp1_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return fdp1_enum_fmt(f, FDP1_CAPTURE); +} + +static int fdp1_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return fdp1_enum_fmt(f, FDP1_OUTPUT); +} + +static int fdp1_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct fdp1_q_data *q_data; + struct fdp1_ctx *ctx = fh_to_ctx(priv); + + if (!v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type)) + return -EINVAL; + + q_data = get_q_data(ctx, f->type); + f->fmt.pix_mp = q_data->format; + + return 0; +} + +static void fdp1_compute_stride(struct v4l2_pix_format_mplane *pix, + const struct fdp1_fmt *fmt) +{ + unsigned int i; + + /* Compute and clamp the stride and image size. */ + for (i = 0; i < min_t(unsigned int, fmt->num_planes, 2U); ++i) { + unsigned int hsub = i > 0 ? fmt->hsub : 1; + unsigned int vsub = i > 0 ? fmt->vsub : 1; + /* From VSP : TODO: Confirm alignment limits for FDP1 */ + unsigned int align = 128; + unsigned int bpl; + + bpl = clamp_t(unsigned int, pix->plane_fmt[i].bytesperline, + pix->width / hsub * fmt->bpp[i] / 8, + round_down(FDP1_MAX_STRIDE, align)); + + pix->plane_fmt[i].bytesperline = round_up(bpl, align); + pix->plane_fmt[i].sizeimage = pix->plane_fmt[i].bytesperline + * pix->height / vsub; + + } + + if (fmt->num_planes == 3) { + /* The two chroma planes must have the same stride. */ + pix->plane_fmt[2].bytesperline = pix->plane_fmt[1].bytesperline; + pix->plane_fmt[2].sizeimage = pix->plane_fmt[1].sizeimage; + + } +} + +static void fdp1_try_fmt_output(struct fdp1_ctx *ctx, + const struct fdp1_fmt **fmtinfo, + struct v4l2_pix_format_mplane *pix) +{ + const struct fdp1_fmt *fmt; + unsigned int width; + unsigned int height; + + /* Validate the pixel format to ensure the output queue supports it. */ + fmt = fdp1_find_format(pix->pixelformat); + if (!fmt || !(fmt->types & FDP1_OUTPUT)) + fmt = fdp1_find_format(V4L2_PIX_FMT_YUYV); + + if (fmtinfo) + *fmtinfo = fmt; + + pix->pixelformat = fmt->fourcc; + pix->num_planes = fmt->num_planes; + + /* + * Progressive video and all interlaced field orders are acceptable. + * Default to V4L2_FIELD_INTERLACED. + */ + if (pix->field != V4L2_FIELD_NONE && + pix->field != V4L2_FIELD_ALTERNATE && + !V4L2_FIELD_HAS_BOTH(pix->field)) + pix->field = V4L2_FIELD_INTERLACED; + + /* + * The deinterlacer doesn't care about the colorspace, accept all values + * and default to V4L2_COLORSPACE_SMPTE170M. The YUV to RGB conversion + * at the output of the deinterlacer supports a subset of encodings and + * quantization methods and will only be available when the colorspace + * allows it. + */ + if (pix->colorspace == V4L2_COLORSPACE_DEFAULT) + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + + /* + * Align the width and height for YUV 4:2:2 and 4:2:0 formats and clamp + * them to the supported frame size range. The height boundary are + * related to the full frame, divide them by two when the format passes + * fields in separate buffers. + */ + width = round_down(pix->width, fmt->hsub); + pix->width = clamp(width, FDP1_MIN_W, FDP1_MAX_W); + + height = round_down(pix->height, fmt->vsub); + if (pix->field == V4L2_FIELD_ALTERNATE) + pix->height = clamp(height, FDP1_MIN_H / 2, FDP1_MAX_H / 2); + else + pix->height = clamp(height, FDP1_MIN_H, FDP1_MAX_H); + + fdp1_compute_stride(pix, fmt); +} + +static void fdp1_try_fmt_capture(struct fdp1_ctx *ctx, + const struct fdp1_fmt **fmtinfo, + struct v4l2_pix_format_mplane *pix) +{ + struct fdp1_q_data *src_data = &ctx->out_q; + enum v4l2_colorspace colorspace; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + const struct fdp1_fmt *fmt; + bool allow_rgb; + + /* + * Validate the pixel format. We can only accept RGB output formats if + * the input encoding and quantization are compatible with the format + * conversions supported by the hardware. The supported combinations are + * + * V4L2_YCBCR_ENC_601 + V4L2_QUANTIZATION_LIM_RANGE + * V4L2_YCBCR_ENC_601 + V4L2_QUANTIZATION_FULL_RANGE + * V4L2_YCBCR_ENC_709 + V4L2_QUANTIZATION_LIM_RANGE + */ + colorspace = src_data->format.colorspace; + + ycbcr_enc = src_data->format.ycbcr_enc; + if (ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT) + ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(colorspace); + + quantization = src_data->format.quantization; + if (quantization == V4L2_QUANTIZATION_DEFAULT) + quantization = V4L2_MAP_QUANTIZATION_DEFAULT(false, colorspace, + ycbcr_enc); + + allow_rgb = ycbcr_enc == V4L2_YCBCR_ENC_601 || + (ycbcr_enc == V4L2_YCBCR_ENC_709 && + quantization == V4L2_QUANTIZATION_LIM_RANGE); + + fmt = fdp1_find_format(pix->pixelformat); + if (!fmt || (!allow_rgb && fdp1_fmt_is_rgb(fmt))) + fmt = fdp1_find_format(V4L2_PIX_FMT_YUYV); + + if (fmtinfo) + *fmtinfo = fmt; + + pix->pixelformat = fmt->fourcc; + pix->num_planes = fmt->num_planes; + pix->field = V4L2_FIELD_NONE; + + /* + * The colorspace on the capture queue is copied from the output queue + * as the hardware can't change the colorspace. It can convert YCbCr to + * RGB though, in which case the encoding and quantization are set to + * default values as anything else wouldn't make sense. + */ + pix->colorspace = src_data->format.colorspace; + pix->xfer_func = src_data->format.xfer_func; + + if (fdp1_fmt_is_rgb(fmt)) { + pix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pix->quantization = V4L2_QUANTIZATION_DEFAULT; + } else { + pix->ycbcr_enc = src_data->format.ycbcr_enc; + pix->quantization = src_data->format.quantization; + } + + /* + * The frame width is identical to the output queue, and the height is + * either doubled or identical depending on whether the output queue + * field order contains one or two fields per frame. + */ + pix->width = src_data->format.width; + if (src_data->format.field == V4L2_FIELD_ALTERNATE) + pix->height = 2 * src_data->format.height; + else + pix->height = src_data->format.height; + + fdp1_compute_stride(pix, fmt); +} + +static int fdp1_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct fdp1_ctx *ctx = fh_to_ctx(priv); + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + fdp1_try_fmt_output(ctx, NULL, &f->fmt.pix_mp); + else + fdp1_try_fmt_capture(ctx, NULL, &f->fmt.pix_mp); + + dprintk(ctx->fdp1, "Try %s format: %4.4s (0x%08x) %ux%u field %u\n", + V4L2_TYPE_IS_OUTPUT(f->type) ? "output" : "capture", + (char *)&f->fmt.pix_mp.pixelformat, f->fmt.pix_mp.pixelformat, + f->fmt.pix_mp.width, f->fmt.pix_mp.height, f->fmt.pix_mp.field); + + return 0; +} + +static void fdp1_set_format(struct fdp1_ctx *ctx, + struct v4l2_pix_format_mplane *pix, + enum v4l2_buf_type type) +{ + struct fdp1_q_data *q_data = get_q_data(ctx, type); + const struct fdp1_fmt *fmtinfo; + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + fdp1_try_fmt_output(ctx, &fmtinfo, pix); + else + fdp1_try_fmt_capture(ctx, &fmtinfo, pix); + + q_data->fmt = fmtinfo; + q_data->format = *pix; + + q_data->vsize = pix->height; + if (pix->field != V4L2_FIELD_NONE) + q_data->vsize /= 2; + + q_data->stride_y = pix->plane_fmt[0].bytesperline; + q_data->stride_c = pix->plane_fmt[1].bytesperline; + + /* Adjust strides for interleaved buffers */ + if (pix->field == V4L2_FIELD_INTERLACED || + pix->field == V4L2_FIELD_INTERLACED_TB || + pix->field == V4L2_FIELD_INTERLACED_BT) { + q_data->stride_y *= 2; + q_data->stride_c *= 2; + } + + /* Propagate the format from the output node to the capture node. */ + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + struct fdp1_q_data *dst_data = &ctx->cap_q; + + /* + * Copy the format, clear the per-plane bytes per line and image + * size, override the field and double the height if needed. + */ + dst_data->format = q_data->format; + memset(dst_data->format.plane_fmt, 0, + sizeof(dst_data->format.plane_fmt)); + + dst_data->format.field = V4L2_FIELD_NONE; + if (pix->field == V4L2_FIELD_ALTERNATE) + dst_data->format.height *= 2; + + fdp1_try_fmt_capture(ctx, &dst_data->fmt, &dst_data->format); + + dst_data->vsize = dst_data->format.height; + dst_data->stride_y = dst_data->format.plane_fmt[0].bytesperline; + dst_data->stride_c = dst_data->format.plane_fmt[1].bytesperline; + } +} + +static int fdp1_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct fdp1_ctx *ctx = fh_to_ctx(priv); + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + struct vb2_queue *vq = v4l2_m2m_get_vq(m2m_ctx, f->type); + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->fdp1->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + fdp1_set_format(ctx, &f->fmt.pix_mp, f->type); + + dprintk(ctx->fdp1, "Set %s format: %4.4s (0x%08x) %ux%u field %u\n", + V4L2_TYPE_IS_OUTPUT(f->type) ? "output" : "capture", + (char *)&f->fmt.pix_mp.pixelformat, f->fmt.pix_mp.pixelformat, + f->fmt.pix_mp.width, f->fmt.pix_mp.height, f->fmt.pix_mp.field); + + return 0; +} + +static int fdp1_g_ctrl(struct v4l2_ctrl *ctrl) +{ + struct fdp1_ctx *ctx = + container_of(ctrl->handler, struct fdp1_ctx, hdl); + struct fdp1_q_data *src_q_data = &ctx->out_q; + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + if (V4L2_FIELD_HAS_BOTH(src_q_data->format.field)) + ctrl->val = 2; + else + ctrl->val = 1; + return 0; + } + + return 1; +} + +static int fdp1_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct fdp1_ctx *ctx = + container_of(ctrl->handler, struct fdp1_ctx, hdl); + + switch (ctrl->id) { + case V4L2_CID_ALPHA_COMPONENT: + ctx->alpha = ctrl->val; + break; + + case V4L2_CID_DEINTERLACING_MODE: + ctx->deint_mode = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops fdp1_ctrl_ops = { + .s_ctrl = fdp1_s_ctrl, + .g_volatile_ctrl = fdp1_g_ctrl, +}; + +static const char * const fdp1_ctrl_deint_menu[] = { + "Progressive", + "Adaptive 2D/3D", + "Fixed 2D", + "Fixed 3D", + "Previous field", + "Next field", + NULL +}; + +static const struct v4l2_ioctl_ops fdp1_ioctl_ops = { + .vidioc_querycap = fdp1_vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = fdp1_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = fdp1_enum_fmt_vid_out, + .vidioc_g_fmt_vid_cap_mplane = fdp1_g_fmt, + .vidioc_g_fmt_vid_out_mplane = fdp1_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = fdp1_try_fmt, + .vidioc_try_fmt_vid_out_mplane = fdp1_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = fdp1_s_fmt, + .vidioc_s_fmt_vid_out_mplane = fdp1_s_fmt, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * Queue operations + */ + +static int fdp1_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_ctxs[]) +{ + struct fdp1_ctx *ctx = vb2_get_drv_priv(vq); + struct fdp1_q_data *q_data; + unsigned int i; + + q_data = get_q_data(ctx, vq->type); + + if (*nplanes) { + if (*nplanes > FDP1_MAX_PLANES) + return -EINVAL; + + return 0; + } + + *nplanes = q_data->format.num_planes; + + for (i = 0; i < *nplanes; i++) + sizes[i] = q_data->format.plane_fmt[i].sizeimage; + + return 0; +} + +static void fdp1_buf_prepare_field(struct fdp1_q_data *q_data, + struct vb2_v4l2_buffer *vbuf, + unsigned int field_num) +{ + struct fdp1_buffer *buf = to_fdp1_buffer(vbuf); + struct fdp1_field_buffer *fbuf = &buf->fields[field_num]; + unsigned int num_fields; + unsigned int i; + + num_fields = V4L2_FIELD_HAS_BOTH(vbuf->field) ? 2 : 1; + + fbuf->vb = vbuf; + fbuf->last_field = (field_num + 1) == num_fields; + + for (i = 0; i < vbuf->vb2_buf.num_planes; ++i) + fbuf->addrs[i] = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, i); + + switch (vbuf->field) { + case V4L2_FIELD_INTERLACED: + /* + * Interlaced means bottom-top for 60Hz TV standards (NTSC) and + * top-bottom for 50Hz. As TV standards are not applicable to + * the mem-to-mem API, use the height as a heuristic. + */ + fbuf->field = (q_data->format.height < 576) == field_num + ? V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM; + break; + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_SEQ_TB: + fbuf->field = field_num ? V4L2_FIELD_BOTTOM : V4L2_FIELD_TOP; + break; + case V4L2_FIELD_INTERLACED_BT: + case V4L2_FIELD_SEQ_BT: + fbuf->field = field_num ? V4L2_FIELD_TOP : V4L2_FIELD_BOTTOM; + break; + default: + fbuf->field = vbuf->field; + break; + } + + /* Buffer is completed */ + if (!field_num) + return; + + /* Adjust buffer addresses for second field */ + switch (vbuf->field) { + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + for (i = 0; i < vbuf->vb2_buf.num_planes; i++) + fbuf->addrs[i] += + (i == 0 ? q_data->stride_y : q_data->stride_c); + break; + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + for (i = 0; i < vbuf->vb2_buf.num_planes; i++) + fbuf->addrs[i] += q_data->vsize * + (i == 0 ? q_data->stride_y : q_data->stride_c); + break; + } +} + +static int fdp1_buf_prepare(struct vb2_buffer *vb) +{ + struct fdp1_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct fdp1_q_data *q_data = get_q_data(ctx, vb->vb2_queue->type); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct fdp1_buffer *buf = to_fdp1_buffer(vbuf); + unsigned int i; + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + bool field_valid = true; + + /* Validate the buffer field. */ + switch (q_data->format.field) { + case V4L2_FIELD_NONE: + if (vbuf->field != V4L2_FIELD_NONE) + field_valid = false; + break; + + case V4L2_FIELD_ALTERNATE: + if (vbuf->field != V4L2_FIELD_TOP && + vbuf->field != V4L2_FIELD_BOTTOM) + field_valid = false; + break; + + case V4L2_FIELD_INTERLACED: + case V4L2_FIELD_SEQ_TB: + case V4L2_FIELD_SEQ_BT: + case V4L2_FIELD_INTERLACED_TB: + case V4L2_FIELD_INTERLACED_BT: + if (vbuf->field != q_data->format.field) + field_valid = false; + break; + } + + if (!field_valid) { + dprintk(ctx->fdp1, + "buffer field %u invalid for format field %u\n", + vbuf->field, q_data->format.field); + return -EINVAL; + } + } else { + vbuf->field = V4L2_FIELD_NONE; + } + + /* Validate the planes sizes. */ + for (i = 0; i < q_data->format.num_planes; i++) { + unsigned long size = q_data->format.plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + dprintk(ctx->fdp1, + "data will not fit into plane [%u/%u] (%lu < %lu)\n", + i, q_data->format.num_planes, + vb2_plane_size(vb, i), size); + return -EINVAL; + } + + /* We have known size formats all around */ + vb2_set_plane_payload(vb, i, size); + } + + buf->num_fields = V4L2_FIELD_HAS_BOTH(vbuf->field) ? 2 : 1; + for (i = 0; i < buf->num_fields; ++i) + fdp1_buf_prepare_field(q_data, vbuf, i); + + return 0; +} + +static void fdp1_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct fdp1_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int fdp1_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct fdp1_ctx *ctx = vb2_get_drv_priv(q); + struct fdp1_q_data *q_data = get_q_data(ctx, q->type); + + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + /* + * Force our deint_mode when we are progressive, + * ignoring any setting on the device from the user, + * Otherwise, lock in the requested de-interlace mode. + */ + if (q_data->format.field == V4L2_FIELD_NONE) + ctx->deint_mode = FDP1_PROGRESSIVE; + + if (ctx->deint_mode == FDP1_ADAPT2D3D) { + u32 stride; + dma_addr_t smsk_base; + const u32 bpp = 2; /* bytes per pixel */ + + stride = round_up(q_data->format.width, 8); + + ctx->smsk_size = bpp * stride * q_data->vsize; + + ctx->smsk_cpu = dma_alloc_coherent(ctx->fdp1->dev, + ctx->smsk_size, &smsk_base, GFP_KERNEL); + + if (ctx->smsk_cpu == NULL) { + dprintk(ctx->fdp1, "Failed to alloc smsk\n"); + return -ENOMEM; + } + + ctx->smsk_addr[0] = smsk_base; + ctx->smsk_addr[1] = smsk_base + (ctx->smsk_size/2); + } + } + + return 0; +} + +static void fdp1_stop_streaming(struct vb2_queue *q) +{ + struct fdp1_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + unsigned long flags; + + while (1) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (vbuf == NULL) + break; + spin_lock_irqsave(&ctx->fdp1->irqlock, flags); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->fdp1->irqlock, flags); + } + + /* Empty Output queues */ + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + /* Empty our internal queues */ + struct fdp1_field_buffer *fbuf; + + /* Free any queued buffers */ + fbuf = fdp1_dequeue_field(ctx); + while (fbuf != NULL) { + fdp1_field_complete(ctx, fbuf); + fbuf = fdp1_dequeue_field(ctx); + } + + /* Free smsk_data */ + if (ctx->smsk_cpu) { + dma_free_coherent(ctx->fdp1->dev, ctx->smsk_size, + ctx->smsk_cpu, ctx->smsk_addr[0]); + ctx->smsk_addr[0] = ctx->smsk_addr[1] = 0; + ctx->smsk_cpu = NULL; + } + + WARN(!list_empty(&ctx->fields_queue), + "Buffer queue not empty"); + } else { + /* Empty Capture queues (Jobs) */ + struct fdp1_job *job; + + job = get_queued_job(ctx->fdp1); + while (job) { + if (FDP1_DEINT_MODE_USES_PREV(ctx->deint_mode)) + fdp1_field_complete(ctx, job->previous); + else + fdp1_field_complete(ctx, job->active); + + v4l2_m2m_buf_done(job->dst->vb, VB2_BUF_STATE_ERROR); + job->dst = NULL; + + job = get_queued_job(ctx->fdp1); + } + + /* Free any held buffer in the ctx */ + fdp1_field_complete(ctx, ctx->previous); + + WARN(!list_empty(&ctx->fdp1->queued_job_list), + "Queued Job List not empty"); + + WARN(!list_empty(&ctx->fdp1->hw_job_list), + "HW Job list not empty"); + } +} + +static const struct vb2_ops fdp1_qops = { + .queue_setup = fdp1_queue_setup, + .buf_prepare = fdp1_buf_prepare, + .buf_queue = fdp1_buf_queue, + .start_streaming = fdp1_start_streaming, + .stop_streaming = fdp1_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct fdp1_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct fdp1_buffer); + src_vq->ops = &fdp1_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->fdp1->dev_mutex; + src_vq->dev = ctx->fdp1->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct fdp1_buffer); + dst_vq->ops = &fdp1_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->fdp1->dev_mutex; + dst_vq->dev = ctx->fdp1->dev; + + return vb2_queue_init(dst_vq); +} + +/* + * File operations + */ +static int fdp1_open(struct file *file) +{ + struct fdp1_dev *fdp1 = video_drvdata(file); + struct v4l2_pix_format_mplane format; + struct fdp1_ctx *ctx = NULL; + struct v4l2_ctrl *ctrl; + int ret = 0; + + if (mutex_lock_interruptible(&fdp1->dev_mutex)) + return -ERESTARTSYS; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto done; + } + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->fdp1 = fdp1; + + /* Initialise Queues */ + INIT_LIST_HEAD(&ctx->fields_queue); + + ctx->translen = 1; + ctx->sequence = 0; + + /* Initialise controls */ + + v4l2_ctrl_handler_init(&ctx->hdl, 3); + v4l2_ctrl_new_std_menu_items(&ctx->hdl, &fdp1_ctrl_ops, + V4L2_CID_DEINTERLACING_MODE, + FDP1_NEXTFIELD, BIT(0), FDP1_FIXED3D, + fdp1_ctrl_deint_menu); + + ctrl = v4l2_ctrl_new_std(&ctx->hdl, &fdp1_ctrl_ops, + V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 2, 1, 1); + if (ctrl) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE; + + v4l2_ctrl_new_std(&ctx->hdl, &fdp1_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 255); + + if (ctx->hdl.error) { + ret = ctx->hdl.error; + goto error_ctx; + } + + ctx->fh.ctrl_handler = &ctx->hdl; + v4l2_ctrl_handler_setup(&ctx->hdl); + + /* Configure default parameters. */ + memset(&format, 0, sizeof(format)); + fdp1_set_format(ctx, &format, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(fdp1->m2m_dev, ctx, &queue_init); + + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto error_ctx; + } + + /* Perform any power management required */ + ret = pm_runtime_resume_and_get(fdp1->dev); + if (ret < 0) + goto error_pm; + + v4l2_fh_add(&ctx->fh); + + dprintk(fdp1, "Created instance: %p, m2m_ctx: %p\n", + ctx, ctx->fh.m2m_ctx); + + mutex_unlock(&fdp1->dev_mutex); + return 0; + +error_pm: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); +error_ctx: + v4l2_ctrl_handler_free(&ctx->hdl); + kfree(ctx); +done: + mutex_unlock(&fdp1->dev_mutex); + return ret; +} + +static int fdp1_release(struct file *file) +{ + struct fdp1_dev *fdp1 = video_drvdata(file); + struct fdp1_ctx *ctx = fh_to_ctx(file->private_data); + + dprintk(fdp1, "Releasing instance %p\n", ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_ctrl_handler_free(&ctx->hdl); + mutex_lock(&fdp1->dev_mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&fdp1->dev_mutex); + kfree(ctx); + + pm_runtime_put(fdp1->dev); + + return 0; +} + +static const struct v4l2_file_operations fdp1_fops = { + .owner = THIS_MODULE, + .open = fdp1_open, + .release = fdp1_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device fdp1_videodev = { + .name = DRIVER_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &fdp1_fops, + .device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING, + .ioctl_ops = &fdp1_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = fdp1_m2m_device_run, + .job_ready = fdp1_m2m_job_ready, + .job_abort = fdp1_m2m_job_abort, +}; + +static irqreturn_t fdp1_irq_handler(int irq, void *dev_id) +{ + struct fdp1_dev *fdp1 = dev_id; + u32 int_status; + u32 ctl_status; + u32 vint_cnt; + u32 cycles; + + int_status = fdp1_read(fdp1, FD1_CTL_IRQSTA); + cycles = fdp1_read(fdp1, FD1_CTL_VCYCLE_STAT); + ctl_status = fdp1_read(fdp1, FD1_CTL_STATUS); + vint_cnt = (ctl_status & FD1_CTL_STATUS_VINT_CNT_MASK) >> + FD1_CTL_STATUS_VINT_CNT_SHIFT; + + /* Clear interrupts */ + fdp1_write(fdp1, ~(int_status) & FD1_CTL_IRQ_MASK, FD1_CTL_IRQSTA); + + if (debug >= 2) { + dprintk(fdp1, "IRQ: 0x%x %s%s%s\n", int_status, + int_status & FD1_CTL_IRQ_VERE ? "[Error]" : "[!E]", + int_status & FD1_CTL_IRQ_VINTE ? "[VSync]" : "[!V]", + int_status & FD1_CTL_IRQ_FREE ? "[FrameEnd]" : "[!F]"); + + dprintk(fdp1, "CycleStatus = %d (%dms)\n", + cycles, cycles/(fdp1->clk_rate/1000)); + + dprintk(fdp1, + "Control Status = 0x%08x : VINT_CNT = %d %s:%s:%s:%s\n", + ctl_status, vint_cnt, + ctl_status & FD1_CTL_STATUS_SGREGSET ? "RegSet" : "", + ctl_status & FD1_CTL_STATUS_SGVERR ? "Vsync Error" : "", + ctl_status & FD1_CTL_STATUS_SGFREND ? "FrameEnd" : "", + ctl_status & FD1_CTL_STATUS_BSY ? "Busy" : ""); + dprintk(fdp1, "***********************************\n"); + } + + /* Spurious interrupt */ + if (!(FD1_CTL_IRQ_MASK & int_status)) + return IRQ_NONE; + + /* Work completed, release the frame */ + if (FD1_CTL_IRQ_VERE & int_status) + device_frame_end(fdp1, VB2_BUF_STATE_ERROR); + else if (FD1_CTL_IRQ_FREE & int_status) + device_frame_end(fdp1, VB2_BUF_STATE_DONE); + + return IRQ_HANDLED; +} + +static int fdp1_probe(struct platform_device *pdev) +{ + struct fdp1_dev *fdp1; + struct video_device *vfd; + struct device_node *fcp_node; + struct clk *clk; + unsigned int i; + + int ret; + int hw_version; + + fdp1 = devm_kzalloc(&pdev->dev, sizeof(*fdp1), GFP_KERNEL); + if (!fdp1) + return -ENOMEM; + + INIT_LIST_HEAD(&fdp1->free_job_list); + INIT_LIST_HEAD(&fdp1->queued_job_list); + INIT_LIST_HEAD(&fdp1->hw_job_list); + + /* Initialise the jobs on the free list */ + for (i = 0; i < ARRAY_SIZE(fdp1->jobs); i++) + list_add(&fdp1->jobs[i].list, &fdp1->free_job_list); + + mutex_init(&fdp1->dev_mutex); + + spin_lock_init(&fdp1->irqlock); + spin_lock_init(&fdp1->device_process_lock); + fdp1->dev = &pdev->dev; + platform_set_drvdata(pdev, fdp1); + + /* Memory-mapped registers */ + fdp1->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(fdp1->regs)) + return PTR_ERR(fdp1->regs); + + /* Interrupt service routine registration */ + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + fdp1->irq = ret; + + ret = devm_request_irq(&pdev->dev, fdp1->irq, fdp1_irq_handler, 0, + dev_name(&pdev->dev), fdp1); + if (ret) { + dev_err(&pdev->dev, "cannot claim IRQ %d\n", fdp1->irq); + return ret; + } + + /* FCP */ + fcp_node = of_parse_phandle(pdev->dev.of_node, "renesas,fcp", 0); + if (fcp_node) { + fdp1->fcp = rcar_fcp_get(fcp_node); + of_node_put(fcp_node); + if (IS_ERR(fdp1->fcp)) { + dev_dbg(&pdev->dev, "FCP not found (%ld)\n", + PTR_ERR(fdp1->fcp)); + return PTR_ERR(fdp1->fcp); + } + } + + /* Determine our clock rate */ + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto put_dev; + } + + fdp1->clk_rate = clk_get_rate(clk); + clk_put(clk); + + /* V4L2 device registration */ + ret = v4l2_device_register(&pdev->dev, &fdp1->v4l2_dev); + if (ret) { + v4l2_err(&fdp1->v4l2_dev, "Failed to register video device\n"); + goto put_dev; + } + + /* M2M registration */ + fdp1->m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(fdp1->m2m_dev)) { + v4l2_err(&fdp1->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(fdp1->m2m_dev); + goto unreg_dev; + } + + /* Video registration */ + fdp1->vfd = fdp1_videodev; + vfd = &fdp1->vfd; + vfd->lock = &fdp1->dev_mutex; + vfd->v4l2_dev = &fdp1->v4l2_dev; + video_set_drvdata(vfd, fdp1); + strscpy(vfd->name, fdp1_videodev.name, sizeof(vfd->name)); + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&fdp1->v4l2_dev, "Failed to register video device\n"); + goto release_m2m; + } + + v4l2_info(&fdp1->v4l2_dev, "Device registered as /dev/video%d\n", + vfd->num); + + /* Power up the cells to read HW */ + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(fdp1->dev); + if (ret < 0) + goto disable_pm; + + hw_version = fdp1_read(fdp1, FD1_IP_INTDATA); + switch (hw_version) { + case FD1_IP_H3_ES1: + dprintk(fdp1, "FDP1 Version R-Car H3 ES1\n"); + break; + case FD1_IP_M3W: + dprintk(fdp1, "FDP1 Version R-Car M3-W\n"); + break; + case FD1_IP_H3: + dprintk(fdp1, "FDP1 Version R-Car H3\n"); + break; + case FD1_IP_M3N: + dprintk(fdp1, "FDP1 Version R-Car M3-N\n"); + break; + case FD1_IP_E3: + dprintk(fdp1, "FDP1 Version R-Car E3\n"); + break; + default: + dev_err(fdp1->dev, "FDP1 Unidentifiable (0x%08x)\n", + hw_version); + } + + /* Allow the hw to sleep until an open call puts it to use */ + pm_runtime_put(fdp1->dev); + + return 0; + +disable_pm: + pm_runtime_disable(fdp1->dev); + +release_m2m: + v4l2_m2m_release(fdp1->m2m_dev); + +unreg_dev: + v4l2_device_unregister(&fdp1->v4l2_dev); + +put_dev: + rcar_fcp_put(fdp1->fcp); + return ret; +} + +static int fdp1_remove(struct platform_device *pdev) +{ + struct fdp1_dev *fdp1 = platform_get_drvdata(pdev); + + v4l2_m2m_release(fdp1->m2m_dev); + video_unregister_device(&fdp1->vfd); + v4l2_device_unregister(&fdp1->v4l2_dev); + pm_runtime_disable(&pdev->dev); + rcar_fcp_put(fdp1->fcp); + + return 0; +} + +static int __maybe_unused fdp1_pm_runtime_suspend(struct device *dev) +{ + struct fdp1_dev *fdp1 = dev_get_drvdata(dev); + + rcar_fcp_disable(fdp1->fcp); + + return 0; +} + +static int __maybe_unused fdp1_pm_runtime_resume(struct device *dev) +{ + struct fdp1_dev *fdp1 = dev_get_drvdata(dev); + + /* Program in the static LUTs */ + fdp1_set_lut(fdp1); + + return rcar_fcp_enable(fdp1->fcp); +} + +static const struct dev_pm_ops fdp1_pm_ops = { + SET_RUNTIME_PM_OPS(fdp1_pm_runtime_suspend, + fdp1_pm_runtime_resume, + NULL) +}; + +static const struct of_device_id fdp1_dt_ids[] = { + { .compatible = "renesas,fdp1" }, + { }, +}; +MODULE_DEVICE_TABLE(of, fdp1_dt_ids); + +static struct platform_driver fdp1_pdrv = { + .probe = fdp1_probe, + .remove = fdp1_remove, + .driver = { + .name = DRIVER_NAME, + .of_match_table = fdp1_dt_ids, + .pm = &fdp1_pm_ops, + }, +}; + +module_platform_driver(fdp1_pdrv); + +MODULE_DESCRIPTION("Renesas R-Car Fine Display Processor Driver"); +MODULE_AUTHOR("Kieran Bingham "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/media/platform/renesas/rcar_jpu.c b/drivers/media/platform/renesas/rcar_jpu.c new file mode 100644 index 000000000..2f4377cfb --- /dev/null +++ b/drivers/media/platform/renesas/rcar_jpu.c @@ -0,0 +1,1762 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Author: Mikhail Ulyanov + * Copyright (C) 2014-2015 Cogent Embedded, Inc. + * Copyright (C) 2014-2015 Renesas Electronics Corporation + * + * This is based on the drivers/media/platform/samsung/s5p-jpeg driver by + * Andrzej Pietrasiewicz and Jacek Anaszewski. + * Some portions of code inspired by VSP1 driver by Laurent Pinchart. + * + * TODO in order of priority: + * 1) Rotation + * 2) Cropping + * 3) V4L2_CID_JPEG_ACTIVE_MARKER + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define DRV_NAME "rcar_jpu" + +/* + * Align JPEG header end to cache line to make sure we will not have any issues + * with cache; additionally to requirement (33.3.27 R01UH0501EJ0100 Rev.1.00) + */ +#define JPU_JPEG_HDR_SIZE (ALIGN(0x258, L1_CACHE_BYTES)) +#define JPU_JPEG_MAX_BYTES_PER_PIXEL 2 /* 16 bit precision format */ +#define JPU_JPEG_MIN_SIZE 25 /* SOI + SOF + EOI */ +#define JPU_JPEG_QTBL_SIZE 0x40 +#define JPU_JPEG_HDCTBL_SIZE 0x1c +#define JPU_JPEG_HACTBL_SIZE 0xb2 +#define JPU_JPEG_HEIGHT_OFFSET 0x91 +#define JPU_JPEG_WIDTH_OFFSET 0x93 +#define JPU_JPEG_SUBS_OFFSET 0x97 +#define JPU_JPEG_QTBL_LUM_OFFSET 0x07 +#define JPU_JPEG_QTBL_CHR_OFFSET 0x4c +#define JPU_JPEG_HDCTBL_LUM_OFFSET 0xa4 +#define JPU_JPEG_HACTBL_LUM_OFFSET 0xc5 +#define JPU_JPEG_HDCTBL_CHR_OFFSET 0x17c +#define JPU_JPEG_HACTBL_CHR_OFFSET 0x19d +#define JPU_JPEG_PADDING_OFFSET 0x24f +#define JPU_JPEG_LUM 0x00 +#define JPU_JPEG_CHR 0x01 +#define JPU_JPEG_DC 0x00 +#define JPU_JPEG_AC 0x10 + +#define JPU_JPEG_422 0x21 +#define JPU_JPEG_420 0x22 + +#define JPU_JPEG_DEFAULT_422_PIX_FMT V4L2_PIX_FMT_NV16M +#define JPU_JPEG_DEFAULT_420_PIX_FMT V4L2_PIX_FMT_NV12M + +/* JPEG markers */ +#define TEM 0x01 +#define SOF0 0xc0 +#define RST 0xd0 +#define SOI 0xd8 +#define EOI 0xd9 +#define DHP 0xde +#define DHT 0xc4 +#define COM 0xfe +#define DQT 0xdb +#define DRI 0xdd +#define APP0 0xe0 + +#define JPU_RESET_TIMEOUT 100 /* ms */ +#define JPU_JOB_TIMEOUT 300 /* ms */ +#define JPU_MAX_QUALITY 4 +#define JPU_WIDTH_MIN 16 +#define JPU_HEIGHT_MIN 16 +#define JPU_WIDTH_MAX 4096 +#define JPU_HEIGHT_MAX 4096 +#define JPU_MEMALIGN 8 + +/* Flags that indicate a format can be used for capture/output */ +#define JPU_FMT_TYPE_OUTPUT 0 +#define JPU_FMT_TYPE_CAPTURE 1 +#define JPU_ENC_CAPTURE (1 << 0) +#define JPU_ENC_OUTPUT (1 << 1) +#define JPU_DEC_CAPTURE (1 << 2) +#define JPU_DEC_OUTPUT (1 << 3) + +/* + * JPEG registers and bits + */ + +/* JPEG code mode register */ +#define JCMOD 0x00 +#define JCMOD_PCTR (1 << 7) +#define JCMOD_MSKIP_ENABLE (1 << 5) +#define JCMOD_DSP_ENC (0 << 3) +#define JCMOD_DSP_DEC (1 << 3) +#define JCMOD_REDU (7 << 0) +#define JCMOD_REDU_422 (1 << 0) +#define JCMOD_REDU_420 (2 << 0) + +/* JPEG code command register */ +#define JCCMD 0x04 +#define JCCMD_SRST (1 << 12) +#define JCCMD_JEND (1 << 2) +#define JCCMD_JSRT (1 << 0) + +/* JPEG code quantization table number register */ +#define JCQTN 0x0c +#define JCQTN_SHIFT(t) (((t) - 1) << 1) + +/* JPEG code Huffman table number register */ +#define JCHTN 0x10 +#define JCHTN_AC_SHIFT(t) (((t) << 1) - 1) +#define JCHTN_DC_SHIFT(t) (((t) - 1) << 1) + +#define JCVSZU 0x1c /* JPEG code vertical size upper register */ +#define JCVSZD 0x20 /* JPEG code vertical size lower register */ +#define JCHSZU 0x24 /* JPEG code horizontal size upper register */ +#define JCHSZD 0x28 /* JPEG code horizontal size lower register */ +#define JCSZ_MASK 0xff /* JPEG code h/v size register contains only 1 byte*/ + +#define JCDTCU 0x2c /* JPEG code data count upper register */ +#define JCDTCM 0x30 /* JPEG code data count middle register */ +#define JCDTCD 0x34 /* JPEG code data count lower register */ + +/* JPEG interrupt enable register */ +#define JINTE 0x38 +#define JINTE_ERR (7 << 5) /* INT5 + INT6 + INT7 */ +#define JINTE_TRANSF_COMPL (1 << 10) + +/* JPEG interrupt status register */ +#define JINTS 0x3c +#define JINTS_MASK 0x7c68 +#define JINTS_ERR (1 << 5) +#define JINTS_PROCESS_COMPL (1 << 6) +#define JINTS_TRANSF_COMPL (1 << 10) + +#define JCDERR 0x40 /* JPEG code decode error register */ +#define JCDERR_MASK 0xf /* JPEG code decode error register mask*/ + +/* JPEG interface encoding */ +#define JIFECNT 0x70 +#define JIFECNT_INFT_422 0 +#define JIFECNT_INFT_420 1 +#define JIFECNT_SWAP_WB (3 << 4) /* to JPU */ + +#define JIFESYA1 0x74 /* encode source Y address register 1 */ +#define JIFESCA1 0x78 /* encode source C address register 1 */ +#define JIFESYA2 0x7c /* encode source Y address register 2 */ +#define JIFESCA2 0x80 /* encode source C address register 2 */ +#define JIFESMW 0x84 /* encode source memory width register */ +#define JIFESVSZ 0x88 /* encode source vertical size register */ +#define JIFESHSZ 0x8c /* encode source horizontal size register */ +#define JIFEDA1 0x90 /* encode destination address register 1 */ +#define JIFEDA2 0x94 /* encode destination address register 2 */ + +/* JPEG decoding control register */ +#define JIFDCNT 0xa0 +#define JIFDCNT_SWAP_WB (3 << 1) /* from JPU */ + +#define JIFDSA1 0xa4 /* decode source address register 1 */ +#define JIFDDMW 0xb0 /* decode destination memory width register */ +#define JIFDDVSZ 0xb4 /* decode destination vert. size register */ +#define JIFDDHSZ 0xb8 /* decode destination horiz. size register */ +#define JIFDDYA1 0xbc /* decode destination Y address register 1 */ +#define JIFDDCA1 0xc0 /* decode destination C address register 1 */ + +#define JCQTBL(n) (0x10000 + (n) * 0x40) /* quantization tables regs */ +#define JCHTBD(n) (0x10100 + (n) * 0x100) /* Huffman table DC regs */ +#define JCHTBA(n) (0x10120 + (n) * 0x100) /* Huffman table AC regs */ + +/** + * struct jpu - JPEG IP abstraction + * @mutex: the mutex protecting this structure + * @lock: spinlock protecting the device contexts + * @v4l2_dev: v4l2 device for mem2mem mode + * @vfd_encoder: video device node for encoder mem2mem mode + * @vfd_decoder: video device node for decoder mem2mem mode + * @m2m_dev: v4l2 mem2mem device data + * @curr: pointer to current context + * @regs: JPEG IP registers mapping + * @irq: JPEG IP irq + * @clk: JPEG IP clock + * @dev: JPEG IP struct device + * @ref_count: reference counter + */ +struct jpu { + struct mutex mutex; + spinlock_t lock; + struct v4l2_device v4l2_dev; + struct video_device vfd_encoder; + struct video_device vfd_decoder; + struct v4l2_m2m_dev *m2m_dev; + struct jpu_ctx *curr; + + void __iomem *regs; + unsigned int irq; + struct clk *clk; + struct device *dev; + int ref_count; +}; + +/** + * struct jpu_buffer - driver's specific video buffer + * @buf: m2m buffer + * @compr_quality: destination image quality in compression mode + * @subsampling: source image subsampling in decompression mode + */ +struct jpu_buffer { + struct v4l2_m2m_buffer buf; + unsigned short compr_quality; + unsigned char subsampling; +}; + +/** + * struct jpu_fmt - driver's internal format data + * @fourcc: the fourcc code, 0 if not applicable + * @colorspace: the colorspace specifier + * @bpp: number of bits per pixel per plane + * @h_align: horizontal alignment order (align to 2^h_align) + * @v_align: vertical alignment order (align to 2^v_align) + * @subsampling: (horizontal:4 | vertical:4) subsampling factor + * @num_planes: number of planes + * @types: types of queue this format is applicable to + */ +struct jpu_fmt { + u32 fourcc; + u32 colorspace; + u8 bpp[2]; + u8 h_align; + u8 v_align; + u8 subsampling; + u8 num_planes; + u16 types; +}; + +/** + * struct jpu_q_data - parameters of one queue + * @fmtinfo: driver-specific format of this queue + * @format: multiplanar format of this queue + * @sequence: sequence number + */ +struct jpu_q_data { + struct jpu_fmt *fmtinfo; + struct v4l2_pix_format_mplane format; + unsigned int sequence; +}; + +/** + * struct jpu_ctx - the device context data + * @jpu: JPEG IP device for this context + * @encoder: compression (encode) operation or decompression (decode) + * @compr_quality: destination image quality in compression (encode) mode + * @out_q: source (output) queue information + * @cap_q: destination (capture) queue information + * @fh: file handler + * @ctrl_handler: controls handler + */ +struct jpu_ctx { + struct jpu *jpu; + bool encoder; + unsigned short compr_quality; + struct jpu_q_data out_q; + struct jpu_q_data cap_q; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; +}; + + /** + * jpeg_buffer - description of memory containing input JPEG data + * @end: end position in the buffer + * @curr: current position in the buffer + */ +struct jpeg_buffer { + void *end; + void *curr; +}; + +static struct jpu_fmt jpu_formats[] = { + { V4L2_PIX_FMT_JPEG, V4L2_COLORSPACE_JPEG, + {0, 0}, 0, 0, 0, 1, JPU_ENC_CAPTURE | JPU_DEC_OUTPUT }, + { V4L2_PIX_FMT_NV16M, V4L2_COLORSPACE_SRGB, + {8, 8}, 2, 2, JPU_JPEG_422, 2, JPU_ENC_OUTPUT | JPU_DEC_CAPTURE }, + { V4L2_PIX_FMT_NV12M, V4L2_COLORSPACE_SRGB, + {8, 4}, 2, 2, JPU_JPEG_420, 2, JPU_ENC_OUTPUT | JPU_DEC_CAPTURE }, + { V4L2_PIX_FMT_NV16, V4L2_COLORSPACE_SRGB, + {16, 0}, 2, 2, JPU_JPEG_422, 1, JPU_ENC_OUTPUT | JPU_DEC_CAPTURE }, + { V4L2_PIX_FMT_NV12, V4L2_COLORSPACE_SRGB, + {12, 0}, 2, 2, JPU_JPEG_420, 1, JPU_ENC_OUTPUT | JPU_DEC_CAPTURE }, +}; + +static const u8 zigzag[] = { + 0x03, 0x02, 0x0b, 0x13, 0x0a, 0x01, 0x00, 0x09, + 0x12, 0x1b, 0x23, 0x1a, 0x11, 0x08, 0x07, 0x06, + 0x0f, 0x10, 0x19, 0x22, 0x2b, 0x33, 0x2a, 0x21, + 0x18, 0x17, 0x0e, 0x05, 0x04, 0x0d, 0x16, 0x1f, + 0x20, 0x29, 0x32, 0x3b, 0x3a, 0x31, 0x28, 0x27, + 0x1e, 0x15, 0x0e, 0x14, 0x10, 0x26, 0x2f, 0x30, + 0x39, 0x38, 0x37, 0x2e, 0x25, 0x1c, 0x24, 0x2b, + 0x36, 0x3f, 0x3e, 0x35, 0x2c, 0x34, 0x3d, 0x3c +}; + +#define QTBL_SIZE (ALIGN(JPU_JPEG_QTBL_SIZE, \ + sizeof(unsigned int)) / sizeof(unsigned int)) +#define HDCTBL_SIZE (ALIGN(JPU_JPEG_HDCTBL_SIZE, \ + sizeof(unsigned int)) / sizeof(unsigned int)) +#define HACTBL_SIZE (ALIGN(JPU_JPEG_HACTBL_SIZE, \ + sizeof(unsigned int)) / sizeof(unsigned int)) +/* + * Start of image; Quantization tables + * SOF0 (17 bytes payload) is Baseline DCT - Sample precision, height, width, + * Number of image components, (Ci:8 - Hi:4 - Vi:4 - Tq:8) * 3 - Y,Cb,Cr; + * Huffman tables; Padding with 0xff (33.3.27 R01UH0501EJ0100 Rev.1.00) + */ +#define JPU_JPEG_HDR_BLOB { \ + 0xff, SOI, 0xff, DQT, 0x00, JPU_JPEG_QTBL_SIZE + 0x3, JPU_JPEG_LUM, \ + [JPU_JPEG_QTBL_LUM_OFFSET ... \ + JPU_JPEG_QTBL_LUM_OFFSET + JPU_JPEG_QTBL_SIZE - 1] = 0x00, \ + 0xff, DQT, 0x00, JPU_JPEG_QTBL_SIZE + 0x3, JPU_JPEG_CHR, \ + [JPU_JPEG_QTBL_CHR_OFFSET ... JPU_JPEG_QTBL_CHR_OFFSET + \ + JPU_JPEG_QTBL_SIZE - 1] = 0x00, 0xff, SOF0, 0x00, 0x11, 0x08, \ + [JPU_JPEG_HEIGHT_OFFSET ... JPU_JPEG_HEIGHT_OFFSET + 1] = 0x00, \ + [JPU_JPEG_WIDTH_OFFSET ... JPU_JPEG_WIDTH_OFFSET + 1] = 0x00, \ + 0x03, 0x01, [JPU_JPEG_SUBS_OFFSET] = 0x00, JPU_JPEG_LUM, \ + 0x02, 0x11, JPU_JPEG_CHR, 0x03, 0x11, JPU_JPEG_CHR, \ + 0xff, DHT, 0x00, JPU_JPEG_HDCTBL_SIZE + 0x3, JPU_JPEG_LUM|JPU_JPEG_DC, \ + [JPU_JPEG_HDCTBL_LUM_OFFSET ... \ + JPU_JPEG_HDCTBL_LUM_OFFSET + JPU_JPEG_HDCTBL_SIZE - 1] = 0x00, \ + 0xff, DHT, 0x00, JPU_JPEG_HACTBL_SIZE + 0x3, JPU_JPEG_LUM|JPU_JPEG_AC, \ + [JPU_JPEG_HACTBL_LUM_OFFSET ... \ + JPU_JPEG_HACTBL_LUM_OFFSET + JPU_JPEG_HACTBL_SIZE - 1] = 0x00, \ + 0xff, DHT, 0x00, JPU_JPEG_HDCTBL_SIZE + 0x3, JPU_JPEG_CHR|JPU_JPEG_DC, \ + [JPU_JPEG_HDCTBL_CHR_OFFSET ... \ + JPU_JPEG_HDCTBL_CHR_OFFSET + JPU_JPEG_HDCTBL_SIZE - 1] = 0x00, \ + 0xff, DHT, 0x00, JPU_JPEG_HACTBL_SIZE + 0x3, JPU_JPEG_CHR|JPU_JPEG_AC, \ + [JPU_JPEG_HACTBL_CHR_OFFSET ... \ + JPU_JPEG_HACTBL_CHR_OFFSET + JPU_JPEG_HACTBL_SIZE - 1] = 0x00, \ + [JPU_JPEG_PADDING_OFFSET ... JPU_JPEG_HDR_SIZE - 1] = 0xff \ +} + +static unsigned char jpeg_hdrs[JPU_MAX_QUALITY][JPU_JPEG_HDR_SIZE] = { + [0 ... JPU_MAX_QUALITY - 1] = JPU_JPEG_HDR_BLOB +}; + +static const unsigned int qtbl_lum[JPU_MAX_QUALITY][QTBL_SIZE] = { + { + 0x14101927, 0x322e3e44, 0x10121726, 0x26354144, + 0x19171f26, 0x35414444, 0x27262635, 0x41444444, + 0x32263541, 0x44444444, 0x2e354144, 0x44444444, + 0x3e414444, 0x44444444, 0x44444444, 0x44444444 + }, + { + 0x100b0b10, 0x171b1f1e, 0x0b0c0c0f, 0x1417171e, + 0x0b0c0d10, 0x171a232f, 0x100f1017, 0x1a252f40, + 0x1714171a, 0x27334040, 0x1b171a25, 0x33404040, + 0x1f17232f, 0x40404040, 0x1e1e2f40, 0x40404040 + }, + { + 0x0c08080c, 0x11151817, 0x0809090b, 0x0f131217, + 0x08090a0c, 0x13141b24, 0x0c0b0c15, 0x141c2435, + 0x110f1314, 0x1e27333b, 0x1513141c, 0x27333b3b, + 0x18121b24, 0x333b3b3b, 0x17172435, 0x3b3b3b3b + }, + { + 0x08060608, 0x0c0e1011, 0x06060608, 0x0a0d0c0f, + 0x06060708, 0x0d0e1218, 0x0808080e, 0x0d131823, + 0x0c0a0d0d, 0x141a2227, 0x0e0d0e13, 0x1a222727, + 0x100c1318, 0x22272727, 0x110f1823, 0x27272727 + } +}; + +static const unsigned int qtbl_chr[JPU_MAX_QUALITY][QTBL_SIZE] = { + { + 0x15192026, 0x36444444, 0x191c1826, 0x36444444, + 0x2018202b, 0x42444444, 0x26262b35, 0x44444444, + 0x36424444, 0x44444444, 0x44444444, 0x44444444, + 0x44444444, 0x44444444, 0x44444444, 0x44444444 + }, + { + 0x110f1115, 0x141a2630, 0x0f131211, 0x141a232b, + 0x11121416, 0x1a1e2e35, 0x1511161c, 0x1e273540, + 0x14141a1e, 0x27304040, 0x1a1a1e27, 0x303f4040, + 0x26232e35, 0x40404040, 0x302b3540, 0x40404040 + }, + { + 0x0d0b0d10, 0x14141d25, 0x0b0e0e0e, 0x10141a20, + 0x0d0e0f11, 0x14172328, 0x100e1115, 0x171e2832, + 0x14101417, 0x1e25323b, 0x1414171e, 0x25303b3b, + 0x1d1a2328, 0x323b3b3b, 0x25202832, 0x3b3b3b3b + }, + { + 0x0908090b, 0x0e111318, 0x080a090b, 0x0e0d1116, + 0x09090d0e, 0x0d0f171a, 0x0b0b0e0e, 0x0f141a21, + 0x0e0e0d0f, 0x14182127, 0x110d0f14, 0x18202727, + 0x1311171a, 0x21272727, 0x18161a21, 0x27272727 + } +}; + +static const unsigned int hdctbl_lum[HDCTBL_SIZE] = { + 0x00010501, 0x01010101, 0x01000000, 0x00000000, + 0x00010203, 0x04050607, 0x08090a0b +}; + +static const unsigned int hdctbl_chr[HDCTBL_SIZE] = { + 0x00010501, 0x01010101, 0x01000000, 0x00000000, + 0x00010203, 0x04050607, 0x08090a0b +}; + +static const unsigned int hactbl_lum[HACTBL_SIZE] = { + 0x00020103, 0x03020403, 0x05050404, 0x0000017d, 0x01020300, 0x04110512, + 0x21314106, 0x13516107, 0x22711432, 0x8191a108, 0x2342b1c1, 0x1552d1f0, + 0x24336272, 0x82090a16, 0x1718191a, 0x25262728, 0x292a3435, 0x36373839, + 0x3a434445, 0x46474849, 0x4a535455, 0x56575859, 0x5a636465, 0x66676869, + 0x6a737475, 0x76777879, 0x7a838485, 0x86878889, 0x8a929394, 0x95969798, + 0x999aa2a3, 0xa4a5a6a7, 0xa8a9aab2, 0xb3b4b5b6, 0xb7b8b9ba, 0xc2c3c4c5, + 0xc6c7c8c9, 0xcad2d3d4, 0xd5d6d7d8, 0xd9dae1e2, 0xe3e4e5e6, 0xe7e8e9ea, + 0xf1f2f3f4, 0xf5f6f7f8, 0xf9fa0000 +}; + +static const unsigned int hactbl_chr[HACTBL_SIZE] = { + 0x00020103, 0x03020403, 0x05050404, 0x0000017d, 0x01020300, 0x04110512, + 0x21314106, 0x13516107, 0x22711432, 0x8191a108, 0x2342b1c1, 0x1552d1f0, + 0x24336272, 0x82090a16, 0x1718191a, 0x25262728, 0x292a3435, 0x36373839, + 0x3a434445, 0x46474849, 0x4a535455, 0x56575859, 0x5a636465, 0x66676869, + 0x6a737475, 0x76777879, 0x7a838485, 0x86878889, 0x8a929394, 0x95969798, + 0x999aa2a3, 0xa4a5a6a7, 0xa8a9aab2, 0xb3b4b5b6, 0xb7b8b9ba, 0xc2c3c4c5, + 0xc6c7c8c9, 0xcad2d3d4, 0xd5d6d7d8, 0xd9dae1e2, 0xe3e4e5e6, 0xe7e8e9ea, + 0xf1f2f3f4, 0xf5f6f7f8, 0xf9fa0000 +}; + +static const char *error_to_text[16] = { + "Normal", + "SOI not detected", + "SOF1 to SOFF detected", + "Subsampling not detected", + "SOF accuracy error", + "DQT accuracy error", + "Component error 1", + "Component error 2", + "SOF0, DQT, and DHT not detected when SOS detected", + "SOS not detected", + "EOI not detected", + "Restart interval data number error detected", + "Image size error", + "Last MCU data number error", + "Block data number error", + "Unknown" +}; + +static struct jpu_buffer *vb2_to_jpu_buffer(struct vb2_v4l2_buffer *vb) +{ + struct v4l2_m2m_buffer *b = + container_of(vb, struct v4l2_m2m_buffer, vb); + + return container_of(b, struct jpu_buffer, buf); +} + +static u32 jpu_read(struct jpu *jpu, unsigned int reg) +{ + return ioread32(jpu->regs + reg); +} + +static void jpu_write(struct jpu *jpu, u32 val, unsigned int reg) +{ + iowrite32(val, jpu->regs + reg); +} + +static struct jpu_ctx *ctrl_to_ctx(struct v4l2_ctrl *c) +{ + return container_of(c->handler, struct jpu_ctx, ctrl_handler); +} + +static struct jpu_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct jpu_ctx, fh); +} + +static void jpu_set_tbl(struct jpu *jpu, u32 reg, const unsigned int *tbl, + unsigned int len) { + unsigned int i; + + for (i = 0; i < len; i++) + jpu_write(jpu, tbl[i], reg + (i << 2)); +} + +static void jpu_set_qtbl(struct jpu *jpu, unsigned short quality) +{ + jpu_set_tbl(jpu, JCQTBL(0), qtbl_lum[quality], QTBL_SIZE); + jpu_set_tbl(jpu, JCQTBL(1), qtbl_chr[quality], QTBL_SIZE); +} + +static void jpu_set_htbl(struct jpu *jpu) +{ + jpu_set_tbl(jpu, JCHTBD(0), hdctbl_lum, HDCTBL_SIZE); + jpu_set_tbl(jpu, JCHTBA(0), hactbl_lum, HACTBL_SIZE); + jpu_set_tbl(jpu, JCHTBD(1), hdctbl_chr, HDCTBL_SIZE); + jpu_set_tbl(jpu, JCHTBA(1), hactbl_chr, HACTBL_SIZE); +} + +static int jpu_wait_reset(struct jpu *jpu) +{ + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(JPU_RESET_TIMEOUT); + + while (jpu_read(jpu, JCCMD) & JCCMD_SRST) { + if (time_after(jiffies, timeout)) { + dev_err(jpu->dev, "timed out in reset\n"); + return -ETIMEDOUT; + } + schedule(); + } + + return 0; +} + +static int jpu_reset(struct jpu *jpu) +{ + jpu_write(jpu, JCCMD_SRST, JCCMD); + return jpu_wait_reset(jpu); +} + +/* + * ============================================================================ + * video ioctl operations + * ============================================================================ + */ +static void put_qtbl(u8 *p, const u8 *qtbl) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(zigzag); i++) + p[i] = *(qtbl + zigzag[i]); +} + +static void put_htbl(u8 *p, const u8 *htbl, unsigned int len) +{ + unsigned int i, j; + + for (i = 0; i < len; i += 4) + for (j = 0; j < 4 && (i + j) < len; ++j) + p[i + j] = htbl[i + 3 - j]; +} + +static void jpu_generate_hdr(unsigned short quality, unsigned char *p) +{ + put_qtbl(p + JPU_JPEG_QTBL_LUM_OFFSET, (const u8 *)qtbl_lum[quality]); + put_qtbl(p + JPU_JPEG_QTBL_CHR_OFFSET, (const u8 *)qtbl_chr[quality]); + + put_htbl(p + JPU_JPEG_HDCTBL_LUM_OFFSET, (const u8 *)hdctbl_lum, + JPU_JPEG_HDCTBL_SIZE); + put_htbl(p + JPU_JPEG_HACTBL_LUM_OFFSET, (const u8 *)hactbl_lum, + JPU_JPEG_HACTBL_SIZE); + + put_htbl(p + JPU_JPEG_HDCTBL_CHR_OFFSET, (const u8 *)hdctbl_chr, + JPU_JPEG_HDCTBL_SIZE); + put_htbl(p + JPU_JPEG_HACTBL_CHR_OFFSET, (const u8 *)hactbl_chr, + JPU_JPEG_HACTBL_SIZE); +} + +static int get_byte(struct jpeg_buffer *buf) +{ + if (buf->curr >= buf->end) + return -1; + + return *(u8 *)buf->curr++; +} + +static int get_word_be(struct jpeg_buffer *buf, unsigned int *word) +{ + if (buf->end - buf->curr < 2) + return -1; + + *word = get_unaligned_be16(buf->curr); + buf->curr += 2; + + return 0; +} + +static void skip(struct jpeg_buffer *buf, unsigned long len) +{ + buf->curr += min((unsigned long)(buf->end - buf->curr), len); +} + +static u8 jpu_parse_hdr(void *buffer, unsigned long size, unsigned int *width, + unsigned int *height) +{ + struct jpeg_buffer jpeg_buffer; + unsigned int word; + bool soi = false; + + jpeg_buffer.end = buffer + size; + jpeg_buffer.curr = buffer; + + /* + * basic size check and EOI - we don't want to let JPU cross + * buffer bounds in any case. Hope it's stopping by EOI. + */ + if (size < JPU_JPEG_MIN_SIZE || *(u8 *)(buffer + size - 1) != EOI) + return 0; + + for (;;) { + int c; + + /* skip preceding filler bytes */ + do + c = get_byte(&jpeg_buffer); + while (c == 0xff || c == 0); + + if (!soi && c == SOI) { + soi = true; + continue; + } else if (soi != (c != SOI)) + return 0; + + switch (c) { + case SOF0: /* SOF0: baseline JPEG */ + skip(&jpeg_buffer, 3); /* segment length and bpp */ + if (get_word_be(&jpeg_buffer, height) || + get_word_be(&jpeg_buffer, width) || + get_byte(&jpeg_buffer) != 3) /* YCbCr only */ + return 0; + + skip(&jpeg_buffer, 1); + return get_byte(&jpeg_buffer); + case DHT: + case DQT: + case COM: + case DRI: + case APP0 ... APP0 + 0x0f: + if (get_word_be(&jpeg_buffer, &word)) + return 0; + skip(&jpeg_buffer, (long)word - 2); + break; + case 0: + break; + default: + return 0; + } + } + + return 0; +} + +static int jpu_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (ctx->encoder) + strscpy(cap->card, DRV_NAME " encoder", sizeof(cap->card)); + else + strscpy(cap->card, DRV_NAME " decoder", sizeof(cap->card)); + + strscpy(cap->driver, DRV_NAME, sizeof(cap->driver)); + memset(cap->reserved, 0, sizeof(cap->reserved)); + + return 0; +} + +static struct jpu_fmt *jpu_find_format(bool encoder, u32 pixelformat, + unsigned int fmt_type) +{ + unsigned int i, fmt_flag; + + if (encoder) + fmt_flag = fmt_type == JPU_FMT_TYPE_OUTPUT ? JPU_ENC_OUTPUT : + JPU_ENC_CAPTURE; + else + fmt_flag = fmt_type == JPU_FMT_TYPE_OUTPUT ? JPU_DEC_OUTPUT : + JPU_DEC_CAPTURE; + + for (i = 0; i < ARRAY_SIZE(jpu_formats); i++) { + struct jpu_fmt *fmt = &jpu_formats[i]; + + if (fmt->fourcc == pixelformat && fmt->types & fmt_flag) + return fmt; + } + + return NULL; +} + +static int jpu_enum_fmt(struct v4l2_fmtdesc *f, u32 type) +{ + unsigned int i, num = 0; + + for (i = 0; i < ARRAY_SIZE(jpu_formats); ++i) { + if (jpu_formats[i].types & type) { + if (num == f->index) + break; + ++num; + } + } + + if (i >= ARRAY_SIZE(jpu_formats)) + return -EINVAL; + + f->pixelformat = jpu_formats[i].fourcc; + + return 0; +} + +static int jpu_enum_fmt_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + return jpu_enum_fmt(f, ctx->encoder ? JPU_ENC_CAPTURE : + JPU_DEC_CAPTURE); +} + +static int jpu_enum_fmt_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + return jpu_enum_fmt(f, ctx->encoder ? JPU_ENC_OUTPUT : JPU_DEC_OUTPUT); +} + +static struct jpu_q_data *jpu_get_q_data(struct jpu_ctx *ctx, + enum v4l2_buf_type type) +{ + if (V4L2_TYPE_IS_OUTPUT(type)) + return &ctx->out_q; + else + return &ctx->cap_q; +} + +static void jpu_bound_align_image(u32 *w, unsigned int w_min, + unsigned int w_max, unsigned int w_align, + u32 *h, unsigned int h_min, + unsigned int h_max, unsigned int h_align) +{ + unsigned int width, height, w_step, h_step; + + width = *w; + height = *h; + + w_step = 1U << w_align; + h_step = 1U << h_align; + v4l_bound_align_image(w, w_min, w_max, w_align, h, h_min, h_max, + h_align, 3); + + if (*w < width && *w + w_step < w_max) + *w += w_step; + if (*h < height && *h + h_step < h_max) + *h += h_step; +} + +static int __jpu_try_fmt(struct jpu_ctx *ctx, struct jpu_fmt **fmtinfo, + struct v4l2_pix_format_mplane *pix, + enum v4l2_buf_type type) +{ + struct jpu_fmt *fmt; + unsigned int f_type, w, h; + + f_type = V4L2_TYPE_IS_OUTPUT(type) ? JPU_FMT_TYPE_OUTPUT : + JPU_FMT_TYPE_CAPTURE; + + fmt = jpu_find_format(ctx->encoder, pix->pixelformat, f_type); + if (!fmt) { + unsigned int pixelformat; + + dev_dbg(ctx->jpu->dev, "unknown format; set default format\n"); + if (ctx->encoder) + pixelformat = f_type == JPU_FMT_TYPE_OUTPUT ? + V4L2_PIX_FMT_NV16M : V4L2_PIX_FMT_JPEG; + else + pixelformat = f_type == JPU_FMT_TYPE_CAPTURE ? + V4L2_PIX_FMT_NV16M : V4L2_PIX_FMT_JPEG; + fmt = jpu_find_format(ctx->encoder, pixelformat, f_type); + } + + pix->pixelformat = fmt->fourcc; + pix->colorspace = fmt->colorspace; + pix->field = V4L2_FIELD_NONE; + pix->num_planes = fmt->num_planes; + + jpu_bound_align_image(&pix->width, JPU_WIDTH_MIN, JPU_WIDTH_MAX, + fmt->h_align, &pix->height, JPU_HEIGHT_MIN, + JPU_HEIGHT_MAX, fmt->v_align); + + w = pix->width; + h = pix->height; + + if (fmt->fourcc == V4L2_PIX_FMT_JPEG) { + /* ignore userspaces's sizeimage for encoding */ + if (pix->plane_fmt[0].sizeimage <= 0 || ctx->encoder) + pix->plane_fmt[0].sizeimage = JPU_JPEG_HDR_SIZE + + (JPU_JPEG_MAX_BYTES_PER_PIXEL * w * h); + pix->plane_fmt[0].bytesperline = 0; + } else { + unsigned int i, bpl = 0; + + for (i = 0; i < pix->num_planes; ++i) + bpl = max(bpl, pix->plane_fmt[i].bytesperline); + + bpl = clamp_t(unsigned int, bpl, w, JPU_WIDTH_MAX); + bpl = round_up(bpl, JPU_MEMALIGN); + + for (i = 0; i < pix->num_planes; ++i) { + pix->plane_fmt[i].bytesperline = bpl; + pix->plane_fmt[i].sizeimage = bpl * h * fmt->bpp[i] / 8; + } + } + + if (fmtinfo) + *fmtinfo = fmt; + + return 0; +} + +static int jpu_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (!v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type)) + return -EINVAL; + + return __jpu_try_fmt(ctx, NULL, &f->fmt.pix_mp, f->type); +} + +static int jpu_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct jpu_ctx *ctx = fh_to_ctx(priv); + struct v4l2_m2m_ctx *m2m_ctx = ctx->fh.m2m_ctx; + struct jpu_fmt *fmtinfo; + struct jpu_q_data *q_data; + int ret; + + vq = v4l2_m2m_get_vq(m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + if (vb2_is_busy(vq)) { + v4l2_err(&ctx->jpu->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + ret = __jpu_try_fmt(ctx, &fmtinfo, &f->fmt.pix_mp, f->type); + if (ret < 0) + return ret; + + q_data = jpu_get_q_data(ctx, f->type); + + q_data->format = f->fmt.pix_mp; + q_data->fmtinfo = fmtinfo; + + return 0; +} + +static int jpu_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct jpu_q_data *q_data; + struct jpu_ctx *ctx = fh_to_ctx(priv); + + if (!v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type)) + return -EINVAL; + + q_data = jpu_get_q_data(ctx, f->type); + f->fmt.pix_mp = q_data->format; + + return 0; +} + +/* + * V4L2 controls + */ +static int jpu_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct jpu_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; + + spin_lock_irqsave(&ctx->jpu->lock, flags); + if (ctrl->id == V4L2_CID_JPEG_COMPRESSION_QUALITY) + ctx->compr_quality = ctrl->val; + spin_unlock_irqrestore(&ctx->jpu->lock, flags); + + return 0; +} + +static const struct v4l2_ctrl_ops jpu_ctrl_ops = { + .s_ctrl = jpu_s_ctrl, +}; + +static int jpu_streamon(struct file *file, void *priv, enum v4l2_buf_type type) +{ + struct jpu_ctx *ctx = fh_to_ctx(priv); + struct jpu_q_data *src_q_data, *dst_q_data, *orig, adj, *ref; + enum v4l2_buf_type adj_type; + + src_q_data = jpu_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + dst_q_data = jpu_get_q_data(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + if (ctx->encoder) { + adj = *src_q_data; + orig = src_q_data; + ref = dst_q_data; + adj_type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + } else { + adj = *dst_q_data; + orig = dst_q_data; + ref = src_q_data; + adj_type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + } + + adj.format.width = ref->format.width; + adj.format.height = ref->format.height; + + __jpu_try_fmt(ctx, NULL, &adj.format, adj_type); + + if (adj.format.width != orig->format.width || + adj.format.height != orig->format.height) { + dev_err(ctx->jpu->dev, "src and dst formats do not match.\n"); + /* maybe we can return -EPIPE here? */ + return -EINVAL; + } + + return v4l2_m2m_streamon(file, ctx->fh.m2m_ctx, type); +} + +static const struct v4l2_ioctl_ops jpu_ioctl_ops = { + .vidioc_querycap = jpu_querycap, + + .vidioc_enum_fmt_vid_cap = jpu_enum_fmt_cap, + .vidioc_enum_fmt_vid_out = jpu_enum_fmt_out, + .vidioc_g_fmt_vid_cap_mplane = jpu_g_fmt, + .vidioc_g_fmt_vid_out_mplane = jpu_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = jpu_try_fmt, + .vidioc_try_fmt_vid_out_mplane = jpu_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = jpu_s_fmt, + .vidioc_s_fmt_vid_out_mplane = jpu_s_fmt, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = jpu_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe +}; + +static int jpu_controls_create(struct jpu_ctx *ctx) +{ + struct v4l2_ctrl *ctrl; + int ret; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 1); + + ctrl = v4l2_ctrl_new_std(&ctx->ctrl_handler, &jpu_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, + 0, JPU_MAX_QUALITY - 1, 1, 0); + + if (ctx->ctrl_handler.error) { + ret = ctx->ctrl_handler.error; + goto error_free; + } + + if (!ctx->encoder) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY; + + ret = v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + if (ret < 0) + goto error_free; + + return 0; + +error_free: + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return ret; +} + +/* + * ============================================================================ + * Queue operations + * ============================================================================ + */ +static int jpu_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct jpu_ctx *ctx = vb2_get_drv_priv(vq); + struct jpu_q_data *q_data; + unsigned int i; + + q_data = jpu_get_q_data(ctx, vq->type); + + if (*nplanes) { + if (*nplanes != q_data->format.num_planes) + return -EINVAL; + + for (i = 0; i < *nplanes; i++) { + unsigned int q_size = q_data->format.plane_fmt[i].sizeimage; + + if (sizes[i] < q_size) + return -EINVAL; + } + return 0; + } + + *nplanes = q_data->format.num_planes; + + for (i = 0; i < *nplanes; i++) + sizes[i] = q_data->format.plane_fmt[i].sizeimage; + + return 0; +} + +static int jpu_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct jpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct jpu_q_data *q_data; + unsigned int i; + + q_data = jpu_get_q_data(ctx, vb->vb2_queue->type); + + if (V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) { + dev_err(ctx->jpu->dev, "%s field isn't supported\n", + __func__); + return -EINVAL; + } + } + + for (i = 0; i < q_data->format.num_planes; i++) { + unsigned long size = q_data->format.plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + dev_err(ctx->jpu->dev, + "%s: data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, i), size); + return -EINVAL; + } + + /* decoder capture queue */ + if (!ctx->encoder && V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static void jpu_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct jpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + if (!ctx->encoder && V4L2_TYPE_IS_OUTPUT(vb->vb2_queue->type)) { + struct jpu_buffer *jpu_buf = vb2_to_jpu_buffer(vbuf); + struct jpu_q_data *q_data, adjust; + void *buffer = vb2_plane_vaddr(vb, 0); + unsigned long buf_size = vb2_get_plane_payload(vb, 0); + unsigned int width, height; + + u8 subsampling = jpu_parse_hdr(buffer, buf_size, &width, + &height); + + /* check if JPEG data basic parsing was successful */ + if (subsampling != JPU_JPEG_422 && subsampling != JPU_JPEG_420) + goto format_error; + + q_data = &ctx->out_q; + + adjust = *q_data; + adjust.format.width = width; + adjust.format.height = height; + + __jpu_try_fmt(ctx, &adjust.fmtinfo, &adjust.format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + + if (adjust.format.width != q_data->format.width || + adjust.format.height != q_data->format.height) + goto format_error; + + /* + * keep subsampling in buffer to check it + * for compatibility in device_run + */ + jpu_buf->subsampling = subsampling; + } + + if (ctx->fh.m2m_ctx) + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); + + return; + +format_error: + dev_err(ctx->jpu->dev, "incompatible or corrupted JPEG data\n"); + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); +} + +static void jpu_buf_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct jpu_buffer *jpu_buf = vb2_to_jpu_buffer(vbuf); + struct jpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct jpu_q_data *q_data = &ctx->out_q; + enum v4l2_buf_type type = vb->vb2_queue->type; + u8 *buffer; + + if (vb->state == VB2_BUF_STATE_DONE) + vbuf->sequence = jpu_get_q_data(ctx, type)->sequence++; + + if (!ctx->encoder || vb->state != VB2_BUF_STATE_DONE || + V4L2_TYPE_IS_OUTPUT(type)) + return; + + buffer = vb2_plane_vaddr(vb, 0); + + memcpy(buffer, jpeg_hdrs[jpu_buf->compr_quality], JPU_JPEG_HDR_SIZE); + *(__be16 *)(buffer + JPU_JPEG_HEIGHT_OFFSET) = + cpu_to_be16(q_data->format.height); + *(__be16 *)(buffer + JPU_JPEG_WIDTH_OFFSET) = + cpu_to_be16(q_data->format.width); + *(buffer + JPU_JPEG_SUBS_OFFSET) = q_data->fmtinfo->subsampling; +} + +static int jpu_start_streaming(struct vb2_queue *vq, unsigned count) +{ + struct jpu_ctx *ctx = vb2_get_drv_priv(vq); + struct jpu_q_data *q_data = jpu_get_q_data(ctx, vq->type); + + q_data->sequence = 0; + return 0; +} + +static void jpu_stop_streaming(struct vb2_queue *vq) +{ + struct jpu_ctx *ctx = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vb; + unsigned long flags; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (vb == NULL) + return; + spin_lock_irqsave(&ctx->jpu->lock, flags); + v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&ctx->jpu->lock, flags); + } +} + +static const struct vb2_ops jpu_qops = { + .queue_setup = jpu_queue_setup, + .buf_prepare = jpu_buf_prepare, + .buf_queue = jpu_buf_queue, + .buf_finish = jpu_buf_finish, + .start_streaming = jpu_start_streaming, + .stop_streaming = jpu_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int jpu_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct jpu_ctx *ctx = priv; + int ret; + + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct jpu_buffer); + src_vq->ops = &jpu_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->jpu->mutex; + src_vq->dev = ctx->jpu->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct jpu_buffer); + dst_vq->ops = &jpu_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->jpu->mutex; + dst_vq->dev = ctx->jpu->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +/* + * ============================================================================ + * Device file operations + * ============================================================================ + */ +static int jpu_open(struct file *file) +{ + struct jpu *jpu = video_drvdata(file); + struct video_device *vfd = video_devdata(file); + struct jpu_ctx *ctx; + int ret; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + v4l2_fh_init(&ctx->fh, vfd); + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->jpu = jpu; + ctx->encoder = vfd == &jpu->vfd_encoder; + + __jpu_try_fmt(ctx, &ctx->out_q.fmtinfo, &ctx->out_q.format, + V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + __jpu_try_fmt(ctx, &ctx->cap_q.fmtinfo, &ctx->cap_q.format, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(jpu->m2m_dev, ctx, jpu_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto v4l_prepare_rollback; + } + + ret = jpu_controls_create(ctx); + if (ret < 0) + goto v4l_prepare_rollback; + + if (mutex_lock_interruptible(&jpu->mutex)) { + ret = -ERESTARTSYS; + goto v4l_prepare_rollback; + } + + if (jpu->ref_count == 0) { + ret = clk_prepare_enable(jpu->clk); + if (ret < 0) + goto device_prepare_rollback; + /* ...issue software reset */ + ret = jpu_reset(jpu); + if (ret) + goto jpu_reset_rollback; + } + + jpu->ref_count++; + + mutex_unlock(&jpu->mutex); + return 0; + +jpu_reset_rollback: + clk_disable_unprepare(jpu->clk); +device_prepare_rollback: + mutex_unlock(&jpu->mutex); +v4l_prepare_rollback: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + return ret; +} + +static int jpu_release(struct file *file) +{ + struct jpu *jpu = video_drvdata(file); + struct jpu_ctx *ctx = fh_to_ctx(file->private_data); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + + mutex_lock(&jpu->mutex); + if (--jpu->ref_count == 0) + clk_disable_unprepare(jpu->clk); + mutex_unlock(&jpu->mutex); + + return 0; +} + +static const struct v4l2_file_operations jpu_fops = { + .owner = THIS_MODULE, + .open = jpu_open, + .release = jpu_release, + .unlocked_ioctl = video_ioctl2, + .poll = v4l2_m2m_fop_poll, + .mmap = v4l2_m2m_fop_mmap, +}; + +/* + * ============================================================================ + * mem2mem callbacks + * ============================================================================ + */ +static void jpu_cleanup(struct jpu_ctx *ctx, bool reset) +{ + /* remove current buffers and finish job */ + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned long flags; + + spin_lock_irqsave(&ctx->jpu->lock, flags); + + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + + /* ...and give it a chance on next run */ + if (reset) + jpu_write(ctx->jpu, JCCMD_SRST, JCCMD); + + spin_unlock_irqrestore(&ctx->jpu->lock, flags); + + v4l2_m2m_job_finish(ctx->jpu->m2m_dev, ctx->fh.m2m_ctx); +} + +static void jpu_device_run(void *priv) +{ + struct jpu_ctx *ctx = priv; + struct jpu *jpu = ctx->jpu; + struct jpu_buffer *jpu_buf; + struct jpu_q_data *q_data; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned int w, h, bpl; + unsigned char num_planes, subsampling; + unsigned long flags; + + /* ...wait until module reset completes; we have mutex locked here */ + if (jpu_wait_reset(jpu)) { + jpu_cleanup(ctx, true); + return; + } + + spin_lock_irqsave(&ctx->jpu->lock, flags); + + jpu->curr = ctx; + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + if (ctx->encoder) { + jpu_buf = vb2_to_jpu_buffer(dst_buf); + q_data = &ctx->out_q; + } else { + jpu_buf = vb2_to_jpu_buffer(src_buf); + q_data = &ctx->cap_q; + } + + w = q_data->format.width; + h = q_data->format.height; + bpl = q_data->format.plane_fmt[0].bytesperline; + num_planes = q_data->fmtinfo->num_planes; + subsampling = q_data->fmtinfo->subsampling; + + if (ctx->encoder) { + unsigned long src_1_addr, src_2_addr, dst_addr; + unsigned int redu, inft; + + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + src_1_addr = + vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + if (num_planes > 1) + src_2_addr = vb2_dma_contig_plane_dma_addr( + &src_buf->vb2_buf, 1); + else + src_2_addr = src_1_addr + w * h; + + jpu_buf->compr_quality = ctx->compr_quality; + + if (subsampling == JPU_JPEG_420) { + redu = JCMOD_REDU_420; + inft = JIFECNT_INFT_420; + } else { + redu = JCMOD_REDU_422; + inft = JIFECNT_INFT_422; + } + + /* only no marker mode works for encoding */ + jpu_write(jpu, JCMOD_DSP_ENC | JCMOD_PCTR | redu | + JCMOD_MSKIP_ENABLE, JCMOD); + + jpu_write(jpu, JIFECNT_SWAP_WB | inft, JIFECNT); + jpu_write(jpu, JIFDCNT_SWAP_WB, JIFDCNT); + jpu_write(jpu, JINTE_TRANSF_COMPL, JINTE); + + /* Y and C components source addresses */ + jpu_write(jpu, src_1_addr, JIFESYA1); + jpu_write(jpu, src_2_addr, JIFESCA1); + + /* memory width */ + jpu_write(jpu, bpl, JIFESMW); + + jpu_write(jpu, (w >> 8) & JCSZ_MASK, JCHSZU); + jpu_write(jpu, w & JCSZ_MASK, JCHSZD); + + jpu_write(jpu, (h >> 8) & JCSZ_MASK, JCVSZU); + jpu_write(jpu, h & JCSZ_MASK, JCVSZD); + + jpu_write(jpu, w, JIFESHSZ); + jpu_write(jpu, h, JIFESVSZ); + + jpu_write(jpu, dst_addr + JPU_JPEG_HDR_SIZE, JIFEDA1); + + jpu_write(jpu, 0 << JCQTN_SHIFT(1) | 1 << JCQTN_SHIFT(2) | + 1 << JCQTN_SHIFT(3), JCQTN); + + jpu_write(jpu, 0 << JCHTN_AC_SHIFT(1) | 0 << JCHTN_DC_SHIFT(1) | + 1 << JCHTN_AC_SHIFT(2) | 1 << JCHTN_DC_SHIFT(2) | + 1 << JCHTN_AC_SHIFT(3) | 1 << JCHTN_DC_SHIFT(3), + JCHTN); + + jpu_set_qtbl(jpu, ctx->compr_quality); + jpu_set_htbl(jpu); + } else { + unsigned long src_addr, dst_1_addr, dst_2_addr; + + if (jpu_buf->subsampling != subsampling) { + dev_err(ctx->jpu->dev, + "src and dst formats do not match.\n"); + spin_unlock_irqrestore(&ctx->jpu->lock, flags); + jpu_cleanup(ctx, false); + return; + } + + src_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + dst_1_addr = + vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + if (q_data->fmtinfo->num_planes > 1) + dst_2_addr = vb2_dma_contig_plane_dma_addr( + &dst_buf->vb2_buf, 1); + else + dst_2_addr = dst_1_addr + w * h; + + /* ...set up decoder operation */ + jpu_write(jpu, JCMOD_DSP_DEC | JCMOD_PCTR, JCMOD); + jpu_write(jpu, JIFECNT_SWAP_WB, JIFECNT); + jpu_write(jpu, JIFDCNT_SWAP_WB, JIFDCNT); + + /* ...enable interrupts on transfer completion and d-g error */ + jpu_write(jpu, JINTE_TRANSF_COMPL | JINTE_ERR, JINTE); + + /* ...set source/destination addresses of encoded data */ + jpu_write(jpu, src_addr, JIFDSA1); + jpu_write(jpu, dst_1_addr, JIFDDYA1); + jpu_write(jpu, dst_2_addr, JIFDDCA1); + + jpu_write(jpu, bpl, JIFDDMW); + } + + /* ...start encoder/decoder operation */ + jpu_write(jpu, JCCMD_JSRT, JCCMD); + + spin_unlock_irqrestore(&ctx->jpu->lock, flags); +} + +static const struct v4l2_m2m_ops jpu_m2m_ops = { + .device_run = jpu_device_run, +}; + +/* + * ============================================================================ + * IRQ handler + * ============================================================================ + */ +static irqreturn_t jpu_irq_handler(int irq, void *dev_id) +{ + struct jpu *jpu = dev_id; + struct jpu_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned int int_status; + + int_status = jpu_read(jpu, JINTS); + + /* ...spurious interrupt */ + if (!((JINTS_TRANSF_COMPL | JINTS_PROCESS_COMPL | JINTS_ERR) & + int_status)) + return IRQ_NONE; + + /* ...clear interrupts */ + jpu_write(jpu, ~(int_status & JINTS_MASK), JINTS); + if (int_status & (JINTS_ERR | JINTS_PROCESS_COMPL)) + jpu_write(jpu, JCCMD_JEND, JCCMD); + + spin_lock(&jpu->lock); + + if ((int_status & JINTS_PROCESS_COMPL) && + !(int_status & JINTS_TRANSF_COMPL)) + goto handled; + + curr_ctx = v4l2_m2m_get_curr_priv(jpu->m2m_dev); + if (!curr_ctx) { + /* ...instance is not running */ + dev_err(jpu->dev, "no active context for m2m\n"); + goto handled; + } + + src_buf = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + if (int_status & JINTS_TRANSF_COMPL) { + if (curr_ctx->encoder) { + unsigned long payload_size = jpu_read(jpu, JCDTCU) << 16 + | jpu_read(jpu, JCDTCM) << 8 + | jpu_read(jpu, JCDTCD); + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, + payload_size + JPU_JPEG_HDR_SIZE); + } + + dst_buf->field = src_buf->field; + dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp; + if (src_buf->flags & V4L2_BUF_FLAG_TIMECODE) + dst_buf->timecode = src_buf->timecode; + dst_buf->flags = src_buf->flags & + (V4L2_BUF_FLAG_TIMECODE | V4L2_BUF_FLAG_KEYFRAME | + V4L2_BUF_FLAG_PFRAME | V4L2_BUF_FLAG_BFRAME | + V4L2_BUF_FLAG_TSTAMP_SRC_MASK); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + } else if (int_status & JINTS_ERR) { + unsigned char error = jpu_read(jpu, JCDERR) & JCDERR_MASK; + + dev_dbg(jpu->dev, "processing error: %#X: %s\n", error, + error_to_text[error]); + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + } + + jpu->curr = NULL; + + /* ...reset JPU after completion */ + jpu_write(jpu, JCCMD_SRST, JCCMD); + spin_unlock(&jpu->lock); + + v4l2_m2m_job_finish(jpu->m2m_dev, curr_ctx->fh.m2m_ctx); + + return IRQ_HANDLED; + +handled: + spin_unlock(&jpu->lock); + return IRQ_HANDLED; +} + +/* + * ============================================================================ + * Driver basic infrastructure + * ============================================================================ + */ +static const struct of_device_id jpu_dt_ids[] = { + { .compatible = "renesas,jpu-r8a7790" }, /* H2 */ + { .compatible = "renesas,jpu-r8a7791" }, /* M2-W */ + { .compatible = "renesas,jpu-r8a7792" }, /* V2H */ + { .compatible = "renesas,jpu-r8a7793" }, /* M2-N */ + { .compatible = "renesas,rcar-gen2-jpu" }, + { }, +}; +MODULE_DEVICE_TABLE(of, jpu_dt_ids); + +static int jpu_probe(struct platform_device *pdev) +{ + struct jpu *jpu; + int ret; + unsigned int i; + + jpu = devm_kzalloc(&pdev->dev, sizeof(*jpu), GFP_KERNEL); + if (!jpu) + return -ENOMEM; + + mutex_init(&jpu->mutex); + spin_lock_init(&jpu->lock); + jpu->dev = &pdev->dev; + + /* memory-mapped registers */ + jpu->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(jpu->regs)) + return PTR_ERR(jpu->regs); + + /* interrupt service routine registration */ + jpu->irq = ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "cannot find IRQ\n"); + return ret; + } + + ret = devm_request_irq(&pdev->dev, jpu->irq, jpu_irq_handler, 0, + dev_name(&pdev->dev), jpu); + if (ret) { + dev_err(&pdev->dev, "cannot claim IRQ %d\n", jpu->irq); + return ret; + } + + /* clocks */ + jpu->clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(jpu->clk)) { + dev_err(&pdev->dev, "cannot get clock\n"); + return PTR_ERR(jpu->clk); + } + + /* v4l2 device */ + ret = v4l2_device_register(&pdev->dev, &jpu->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register v4l2 device\n"); + return ret; + } + + /* mem2mem device */ + jpu->m2m_dev = v4l2_m2m_init(&jpu_m2m_ops); + if (IS_ERR(jpu->m2m_dev)) { + v4l2_err(&jpu->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(jpu->m2m_dev); + goto device_register_rollback; + } + + /* fill in quantization and Huffman tables for encoder */ + for (i = 0; i < JPU_MAX_QUALITY; i++) + jpu_generate_hdr(i, (unsigned char *)jpeg_hdrs[i]); + + strscpy(jpu->vfd_encoder.name, DRV_NAME, sizeof(jpu->vfd_encoder.name)); + jpu->vfd_encoder.fops = &jpu_fops; + jpu->vfd_encoder.ioctl_ops = &jpu_ioctl_ops; + jpu->vfd_encoder.minor = -1; + jpu->vfd_encoder.release = video_device_release_empty; + jpu->vfd_encoder.lock = &jpu->mutex; + jpu->vfd_encoder.v4l2_dev = &jpu->v4l2_dev; + jpu->vfd_encoder.vfl_dir = VFL_DIR_M2M; + jpu->vfd_encoder.device_caps = V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_M2M_MPLANE; + + ret = video_register_device(&jpu->vfd_encoder, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&jpu->v4l2_dev, "Failed to register video device\n"); + goto m2m_init_rollback; + } + + video_set_drvdata(&jpu->vfd_encoder, jpu); + + strscpy(jpu->vfd_decoder.name, DRV_NAME, sizeof(jpu->vfd_decoder.name)); + jpu->vfd_decoder.fops = &jpu_fops; + jpu->vfd_decoder.ioctl_ops = &jpu_ioctl_ops; + jpu->vfd_decoder.minor = -1; + jpu->vfd_decoder.release = video_device_release_empty; + jpu->vfd_decoder.lock = &jpu->mutex; + jpu->vfd_decoder.v4l2_dev = &jpu->v4l2_dev; + jpu->vfd_decoder.vfl_dir = VFL_DIR_M2M; + jpu->vfd_decoder.device_caps = V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_M2M_MPLANE; + + ret = video_register_device(&jpu->vfd_decoder, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&jpu->v4l2_dev, "Failed to register video device\n"); + goto enc_vdev_register_rollback; + } + + video_set_drvdata(&jpu->vfd_decoder, jpu); + platform_set_drvdata(pdev, jpu); + + v4l2_info(&jpu->v4l2_dev, "encoder device registered as /dev/video%d\n", + jpu->vfd_encoder.num); + v4l2_info(&jpu->v4l2_dev, "decoder device registered as /dev/video%d\n", + jpu->vfd_decoder.num); + + return 0; + +enc_vdev_register_rollback: + video_unregister_device(&jpu->vfd_encoder); + +m2m_init_rollback: + v4l2_m2m_release(jpu->m2m_dev); + +device_register_rollback: + v4l2_device_unregister(&jpu->v4l2_dev); + + return ret; +} + +static int jpu_remove(struct platform_device *pdev) +{ + struct jpu *jpu = platform_get_drvdata(pdev); + + video_unregister_device(&jpu->vfd_decoder); + video_unregister_device(&jpu->vfd_encoder); + v4l2_m2m_release(jpu->m2m_dev); + v4l2_device_unregister(&jpu->v4l2_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int jpu_suspend(struct device *dev) +{ + struct jpu *jpu = dev_get_drvdata(dev); + + if (jpu->ref_count == 0) + return 0; + + clk_disable_unprepare(jpu->clk); + + return 0; +} + +static int jpu_resume(struct device *dev) +{ + struct jpu *jpu = dev_get_drvdata(dev); + + if (jpu->ref_count == 0) + return 0; + + clk_prepare_enable(jpu->clk); + + return 0; +} +#endif + +static const struct dev_pm_ops jpu_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(jpu_suspend, jpu_resume) +}; + +static struct platform_driver jpu_driver = { + .probe = jpu_probe, + .remove = jpu_remove, + .driver = { + .of_match_table = jpu_dt_ids, + .name = DRV_NAME, + .pm = &jpu_pm_ops, + }, +}; + +module_platform_driver(jpu_driver); + +MODULE_ALIAS("platform:" DRV_NAME); +MODULE_AUTHOR("Mikhail Ulianov "); +MODULE_DESCRIPTION("Renesas R-Car JPEG processing unit driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/renesas/renesas-ceu.c b/drivers/media/platform/renesas/renesas-ceu.c new file mode 100644 index 000000000..f70f91b00 --- /dev/null +++ b/drivers/media/platform/renesas/renesas-ceu.c @@ -0,0 +1,1749 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * V4L2 Driver for Renesas Capture Engine Unit (CEU) interface + * Copyright (C) 2017-2018 Jacopo Mondi + * + * Based on soc-camera driver "soc_camera/sh_mobile_ceu_camera.c" + * Copyright (C) 2008 Magnus Damm + * + * Based on V4L2 Driver for PXA camera host - "pxa_camera.c", + * Copyright (C) 2006, Sascha Hauer, Pengutronix + * Copyright (C) 2008, Guennadi Liakhovetski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DRIVER_NAME "renesas-ceu" + +/* CEU registers offsets and masks. */ +#define CEU_CAPSR 0x00 /* Capture start register */ +#define CEU_CAPCR 0x04 /* Capture control register */ +#define CEU_CAMCR 0x08 /* Capture interface control register */ +#define CEU_CAMOR 0x10 /* Capture interface offset register */ +#define CEU_CAPWR 0x14 /* Capture interface width register */ +#define CEU_CAIFR 0x18 /* Capture interface input format register */ +#define CEU_CRCNTR 0x28 /* CEU register control register */ +#define CEU_CRCMPR 0x2c /* CEU register forcible control register */ +#define CEU_CFLCR 0x30 /* Capture filter control register */ +#define CEU_CFSZR 0x34 /* Capture filter size clip register */ +#define CEU_CDWDR 0x38 /* Capture destination width register */ +#define CEU_CDAYR 0x3c /* Capture data address Y register */ +#define CEU_CDACR 0x40 /* Capture data address C register */ +#define CEU_CFWCR 0x5c /* Firewall operation control register */ +#define CEU_CDOCR 0x64 /* Capture data output control register */ +#define CEU_CEIER 0x70 /* Capture event interrupt enable register */ +#define CEU_CETCR 0x74 /* Capture event flag clear register */ +#define CEU_CSTSR 0x7c /* Capture status register */ +#define CEU_CSRTR 0x80 /* Capture software reset register */ + +/* Data synchronous fetch mode. */ +#define CEU_CAMCR_JPEG BIT(4) + +/* Input components ordering: CEU_CAMCR.DTARY field. */ +#define CEU_CAMCR_DTARY_8_UYVY (0x00 << 8) +#define CEU_CAMCR_DTARY_8_VYUY (0x01 << 8) +#define CEU_CAMCR_DTARY_8_YUYV (0x02 << 8) +#define CEU_CAMCR_DTARY_8_YVYU (0x03 << 8) +/* TODO: input components ordering for 16 bits input. */ + +/* Bus transfer MTU. */ +#define CEU_CAPCR_BUS_WIDTH256 (0x3 << 20) + +/* Bus width configuration. */ +#define CEU_CAMCR_DTIF_16BITS BIT(12) + +/* No downsampling to planar YUV420 in image fetch mode. */ +#define CEU_CDOCR_NO_DOWSAMPLE BIT(4) + +/* Swap all input data in 8-bit, 16-bits and 32-bits units (Figure 46.45). */ +#define CEU_CDOCR_SWAP_ENDIANNESS (7) + +/* Capture reset and enable bits. */ +#define CEU_CAPSR_CPKIL BIT(16) +#define CEU_CAPSR_CE BIT(0) + +/* CEU operating flag bit. */ +#define CEU_CAPCR_CTNCP BIT(16) +#define CEU_CSTRST_CPTON BIT(0) + +/* Platform specific IRQ source flags. */ +#define CEU_CETCR_ALL_IRQS_RZ 0x397f313 +#define CEU_CETCR_ALL_IRQS_SH4 0x3d7f313 + +/* Prohibited register access interrupt bit. */ +#define CEU_CETCR_IGRW BIT(4) +/* One-frame capture end interrupt. */ +#define CEU_CEIER_CPE BIT(0) +/* VBP error. */ +#define CEU_CEIER_VBP BIT(20) +#define CEU_CEIER_MASK (CEU_CEIER_CPE | CEU_CEIER_VBP) + +#define CEU_MAX_WIDTH 2560 +#define CEU_MAX_HEIGHT 1920 +#define CEU_MAX_BPL 8188 +#define CEU_W_MAX(w) ((w) < CEU_MAX_WIDTH ? (w) : CEU_MAX_WIDTH) +#define CEU_H_MAX(h) ((h) < CEU_MAX_HEIGHT ? (h) : CEU_MAX_HEIGHT) + +/* + * ceu_bus_fmt - describe a 8-bits yuyv format the sensor can produce + * + * @mbus_code: bus format code + * @fmt_order: CEU_CAMCR.DTARY ordering of input components (Y, Cb, Cr) + * @fmt_order_swap: swapped CEU_CAMCR.DTARY ordering of input components + * (Y, Cr, Cb) + * @swapped: does Cr appear before Cb? + * @bps: number of bits sent over bus for each sample + * @bpp: number of bits per pixels unit + */ +struct ceu_mbus_fmt { + u32 mbus_code; + u32 fmt_order; + u32 fmt_order_swap; + bool swapped; + u8 bps; + u8 bpp; +}; + +/* + * ceu_buffer - Link vb2 buffer to the list of available buffers. + */ +struct ceu_buffer { + struct vb2_v4l2_buffer vb; + struct list_head queue; +}; + +static inline struct ceu_buffer *vb2_to_ceu(struct vb2_v4l2_buffer *vbuf) +{ + return container_of(vbuf, struct ceu_buffer, vb); +} + +/* + * ceu_subdev - Wraps v4l2 sub-device and provides async subdevice. + */ +struct ceu_subdev { + struct v4l2_async_subdev asd; + struct v4l2_subdev *v4l2_sd; + + /* per-subdevice mbus configuration options */ + unsigned int mbus_flags; + struct ceu_mbus_fmt mbus_fmt; +}; + +static struct ceu_subdev *to_ceu_subdev(struct v4l2_async_subdev *asd) +{ + return container_of(asd, struct ceu_subdev, asd); +} + +/* + * ceu_device - CEU device instance + */ +struct ceu_device { + struct device *dev; + struct video_device vdev; + struct v4l2_device v4l2_dev; + + /* subdevices descriptors */ + struct ceu_subdev **subdevs; + /* the subdevice currently in use */ + struct ceu_subdev *sd; + unsigned int sd_index; + unsigned int num_sd; + + /* platform specific mask with all IRQ sources flagged */ + u32 irq_mask; + + /* currently configured field and pixel format */ + enum v4l2_field field; + struct v4l2_pix_format_mplane v4l2_pix; + + /* async subdev notification helpers */ + struct v4l2_async_notifier notifier; + + /* vb2 queue, capture buffer list and active buffer pointer */ + struct vb2_queue vb2_vq; + struct list_head capture; + struct vb2_v4l2_buffer *active; + unsigned int sequence; + + /* mlock - lock access to interface reset and vb2 queue */ + struct mutex mlock; + + /* lock - lock access to capture buffer queue and active buffer */ + spinlock_t lock; + + /* base - CEU memory base address */ + void __iomem *base; +}; + +static inline struct ceu_device *v4l2_to_ceu(struct v4l2_device *v4l2_dev) +{ + return container_of(v4l2_dev, struct ceu_device, v4l2_dev); +} + +/* --- CEU memory output formats --- */ + +/* + * ceu_fmt - describe a memory output format supported by CEU interface. + * + * @fourcc: memory layout fourcc format code + * @bpp: number of bits for each pixel stored in memory + */ +struct ceu_fmt { + u32 fourcc; + u32 bpp; +}; + +/* + * ceu_format_list - List of supported memory output formats + * + * If sensor provides any YUYV bus format, all the following planar memory + * formats are available thanks to CEU re-ordering and sub-sampling + * capabilities. + */ +static const struct ceu_fmt ceu_fmt_list[] = { + { + .fourcc = V4L2_PIX_FMT_NV16, + .bpp = 16, + }, + { + .fourcc = V4L2_PIX_FMT_NV61, + .bpp = 16, + }, + { + .fourcc = V4L2_PIX_FMT_NV12, + .bpp = 12, + }, + { + .fourcc = V4L2_PIX_FMT_NV21, + .bpp = 12, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .bpp = 16, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .bpp = 16, + }, + { + .fourcc = V4L2_PIX_FMT_YVYU, + .bpp = 16, + }, + { + .fourcc = V4L2_PIX_FMT_VYUY, + .bpp = 16, + }, +}; + +static const struct ceu_fmt *get_ceu_fmt_from_fourcc(unsigned int fourcc) +{ + const struct ceu_fmt *fmt = &ceu_fmt_list[0]; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ceu_fmt_list); i++, fmt++) + if (fmt->fourcc == fourcc) + return fmt; + + return NULL; +} + +static bool ceu_fmt_mplane(struct v4l2_pix_format_mplane *pix) +{ + switch (pix->pixelformat) { + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + return false; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + return true; + default: + return false; + } +} + +/* --- CEU HW operations --- */ + +static void ceu_write(struct ceu_device *priv, unsigned int reg_offs, u32 data) +{ + iowrite32(data, priv->base + reg_offs); +} + +static u32 ceu_read(struct ceu_device *priv, unsigned int reg_offs) +{ + return ioread32(priv->base + reg_offs); +} + +/* + * ceu_soft_reset() - Software reset the CEU interface. + * @ceu_device: CEU device. + * + * Returns 0 for success, -EIO for error. + */ +static int ceu_soft_reset(struct ceu_device *ceudev) +{ + unsigned int i; + + ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CPKIL); + + for (i = 0; i < 100; i++) { + if (!(ceu_read(ceudev, CEU_CSTSR) & CEU_CSTRST_CPTON)) + break; + udelay(1); + } + + if (i == 100) { + dev_err(ceudev->dev, "soft reset time out\n"); + return -EIO; + } + + for (i = 0; i < 100; i++) { + if (!(ceu_read(ceudev, CEU_CAPSR) & CEU_CAPSR_CPKIL)) + return 0; + udelay(1); + } + + /* If we get here, CEU has not reset properly. */ + return -EIO; +} + +/* --- CEU Capture Operations --- */ + +/* + * ceu_hw_config() - Configure CEU interface registers. + */ +static int ceu_hw_config(struct ceu_device *ceudev) +{ + u32 camcr, cdocr, cfzsr, cdwdr, capwr; + struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix; + struct ceu_subdev *ceu_sd = ceudev->sd; + struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt; + unsigned int mbus_flags = ceu_sd->mbus_flags; + + /* Start configuring CEU registers */ + ceu_write(ceudev, CEU_CAIFR, 0); + ceu_write(ceudev, CEU_CFWCR, 0); + ceu_write(ceudev, CEU_CRCNTR, 0); + ceu_write(ceudev, CEU_CRCMPR, 0); + + /* Set the frame capture period for both image capture and data sync. */ + capwr = (pix->height << 16) | pix->width * mbus_fmt->bpp / 8; + + /* + * Swap input data endianness by default. + * In data fetch mode bytes are received in chunks of 8 bytes. + * D0, D1, D2, D3, D4, D5, D6, D7 (D0 received first) + * The data is however by default written to memory in reverse order: + * D7, D6, D5, D4, D3, D2, D1, D0 (D7 written to lowest byte) + * + * Use CEU_CDOCR[2:0] to swap data ordering. + */ + cdocr = CEU_CDOCR_SWAP_ENDIANNESS; + + /* + * Configure CAMCR and CDOCR: + * match input components ordering with memory output format and + * handle downsampling to YUV420. + * + * If the memory output planar format is 'swapped' (Cr before Cb) and + * input format is not, use the swapped version of CAMCR.DTARY. + * + * If the memory output planar format is not 'swapped' (Cb before Cr) + * and input format is, use the swapped version of CAMCR.DTARY. + * + * CEU by default downsample to planar YUV420 (CDCOR[4] = 0). + * If output is planar YUV422 set CDOCR[4] = 1 + * + * No downsample for data fetch sync mode. + */ + switch (pix->pixelformat) { + /* Data fetch sync mode */ + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + camcr = CEU_CAMCR_JPEG; + cdocr |= CEU_CDOCR_NO_DOWSAMPLE; + cfzsr = (pix->height << 16) | pix->width; + cdwdr = pix->plane_fmt[0].bytesperline; + break; + + /* Non-swapped planar image capture mode. */ + case V4L2_PIX_FMT_NV16: + cdocr |= CEU_CDOCR_NO_DOWSAMPLE; + fallthrough; + case V4L2_PIX_FMT_NV12: + if (mbus_fmt->swapped) + camcr = mbus_fmt->fmt_order_swap; + else + camcr = mbus_fmt->fmt_order; + + cfzsr = (pix->height << 16) | pix->width; + cdwdr = pix->width; + break; + + /* Swapped planar image capture mode. */ + case V4L2_PIX_FMT_NV61: + cdocr |= CEU_CDOCR_NO_DOWSAMPLE; + fallthrough; + case V4L2_PIX_FMT_NV21: + if (mbus_fmt->swapped) + camcr = mbus_fmt->fmt_order; + else + camcr = mbus_fmt->fmt_order_swap; + + cfzsr = (pix->height << 16) | pix->width; + cdwdr = pix->width; + break; + + default: + return -EINVAL; + } + + camcr |= mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW ? 1 << 1 : 0; + camcr |= mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW ? 1 << 0 : 0; + + /* TODO: handle 16 bit bus width with DTIF bit in CAMCR */ + ceu_write(ceudev, CEU_CAMCR, camcr); + ceu_write(ceudev, CEU_CDOCR, cdocr); + ceu_write(ceudev, CEU_CAPCR, CEU_CAPCR_BUS_WIDTH256); + + /* + * TODO: make CAMOR offsets configurable. + * CAMOR wants to know the number of blanks between a VS/HS signal + * and valid data. This value should actually come from the sensor... + */ + ceu_write(ceudev, CEU_CAMOR, 0); + + /* TODO: 16 bit bus width require re-calculation of cdwdr and cfzsr */ + ceu_write(ceudev, CEU_CAPWR, capwr); + ceu_write(ceudev, CEU_CFSZR, cfzsr); + ceu_write(ceudev, CEU_CDWDR, cdwdr); + + return 0; +} + +/* + * ceu_capture() - Trigger start of a capture sequence. + * + * Program the CEU DMA registers with addresses where to transfer image data. + */ +static int ceu_capture(struct ceu_device *ceudev) +{ + struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix; + dma_addr_t phys_addr_top; + + phys_addr_top = + vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf, 0); + ceu_write(ceudev, CEU_CDAYR, phys_addr_top); + + /* Ignore CbCr plane for non multi-planar image formats. */ + if (ceu_fmt_mplane(pix)) { + phys_addr_top = + vb2_dma_contig_plane_dma_addr(&ceudev->active->vb2_buf, + 1); + ceu_write(ceudev, CEU_CDACR, phys_addr_top); + } + + /* + * Trigger new capture start: once for each frame, as we work in + * one-frame capture mode. + */ + ceu_write(ceudev, CEU_CAPSR, CEU_CAPSR_CE); + + return 0; +} + +static irqreturn_t ceu_irq(int irq, void *data) +{ + struct ceu_device *ceudev = data; + struct vb2_v4l2_buffer *vbuf; + struct ceu_buffer *buf; + u32 status; + + /* Clean interrupt status. */ + status = ceu_read(ceudev, CEU_CETCR); + ceu_write(ceudev, CEU_CETCR, ~ceudev->irq_mask); + + /* Unexpected interrupt. */ + if (!(status & CEU_CEIER_MASK)) + return IRQ_NONE; + + spin_lock(&ceudev->lock); + + /* Stale interrupt from a released buffer, ignore it. */ + vbuf = ceudev->active; + if (!vbuf) { + spin_unlock(&ceudev->lock); + return IRQ_HANDLED; + } + + /* + * When a VBP interrupt occurs, no capture end interrupt will occur + * and the image of that frame is not captured correctly. + */ + if (status & CEU_CEIER_VBP) { + dev_err(ceudev->dev, "VBP interrupt: abort capture\n"); + goto error_irq_out; + } + + /* Prepare to return the 'previous' buffer. */ + vbuf->vb2_buf.timestamp = ktime_get_ns(); + vbuf->sequence = ceudev->sequence++; + vbuf->field = ceudev->field; + + /* Prepare a new 'active' buffer and trigger a new capture. */ + if (!list_empty(&ceudev->capture)) { + buf = list_first_entry(&ceudev->capture, struct ceu_buffer, + queue); + list_del(&buf->queue); + ceudev->active = &buf->vb; + + ceu_capture(ceudev); + } + + /* Return the 'previous' buffer. */ + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); + + spin_unlock(&ceudev->lock); + + return IRQ_HANDLED; + +error_irq_out: + /* Return the 'previous' buffer and all queued ones. */ + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_ERROR); + + list_for_each_entry(buf, &ceudev->capture, queue) + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + + spin_unlock(&ceudev->lock); + + return IRQ_HANDLED; +} + +/* --- CEU Videobuf2 operations --- */ + +static void ceu_update_plane_sizes(struct v4l2_plane_pix_format *plane, + unsigned int bpl, unsigned int szimage) +{ + memset(plane, 0, sizeof(*plane)); + + plane->sizeimage = szimage; + if (plane->bytesperline < bpl || plane->bytesperline > CEU_MAX_BPL) + plane->bytesperline = bpl; +} + +/* + * ceu_calc_plane_sizes() - Fill per-plane 'struct v4l2_plane_pix_format' + * information according to the currently configured + * pixel format. + * @ceu_device: CEU device. + * @ceu_fmt: Active image format. + * @pix: Pixel format information (store line width and image sizes) + */ +static void ceu_calc_plane_sizes(struct ceu_device *ceudev, + const struct ceu_fmt *ceu_fmt, + struct v4l2_pix_format_mplane *pix) +{ + unsigned int bpl, szimage; + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_VYUY: + pix->num_planes = 1; + bpl = pix->width * ceu_fmt->bpp / 8; + szimage = pix->height * bpl; + ceu_update_plane_sizes(&pix->plane_fmt[0], bpl, szimage); + break; + + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + pix->num_planes = 2; + bpl = pix->width; + szimage = pix->height * pix->width; + ceu_update_plane_sizes(&pix->plane_fmt[0], bpl, szimage); + ceu_update_plane_sizes(&pix->plane_fmt[1], bpl, szimage / 2); + break; + + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + default: + pix->num_planes = 2; + bpl = pix->width; + szimage = pix->height * pix->width; + ceu_update_plane_sizes(&pix->plane_fmt[0], bpl, szimage); + ceu_update_plane_sizes(&pix->plane_fmt[1], bpl, szimage); + break; + } +} + +/* + * ceu_vb2_setup() - is called to check whether the driver can accept the + * requested number of buffers and to fill in plane sizes + * for the current frame format, if required. + */ +static int ceu_vb2_setup(struct vb2_queue *vq, unsigned int *count, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct ceu_device *ceudev = vb2_get_drv_priv(vq); + struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix; + unsigned int i; + + /* num_planes is set: just check plane sizes. */ + if (*num_planes) { + for (i = 0; i < pix->num_planes; i++) + if (sizes[i] < pix->plane_fmt[i].sizeimage) + return -EINVAL; + + return 0; + } + + /* num_planes not set: called from REQBUFS, just set plane sizes. */ + *num_planes = pix->num_planes; + for (i = 0; i < pix->num_planes; i++) + sizes[i] = pix->plane_fmt[i].sizeimage; + + return 0; +} + +static void ceu_vb2_queue(struct vb2_buffer *vb) +{ + struct ceu_device *ceudev = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct ceu_buffer *buf = vb2_to_ceu(vbuf); + unsigned long irqflags; + + spin_lock_irqsave(&ceudev->lock, irqflags); + list_add_tail(&buf->queue, &ceudev->capture); + spin_unlock_irqrestore(&ceudev->lock, irqflags); +} + +static int ceu_vb2_prepare(struct vb2_buffer *vb) +{ + struct ceu_device *ceudev = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_pix_format_mplane *pix = &ceudev->v4l2_pix; + unsigned int i; + + for (i = 0; i < pix->num_planes; i++) { + if (vb2_plane_size(vb, i) < pix->plane_fmt[i].sizeimage) { + dev_err(ceudev->dev, + "Plane size too small (%lu < %u)\n", + vb2_plane_size(vb, i), + pix->plane_fmt[i].sizeimage); + return -EINVAL; + } + + vb2_set_plane_payload(vb, i, pix->plane_fmt[i].sizeimage); + } + + return 0; +} + +static int ceu_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct ceu_device *ceudev = vb2_get_drv_priv(vq); + struct v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd; + struct ceu_buffer *buf; + unsigned long irqflags; + int ret; + + /* Program the CEU interface according to the CEU image format. */ + ret = ceu_hw_config(ceudev); + if (ret) + goto error_return_bufs; + + ret = v4l2_subdev_call(v4l2_sd, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) { + dev_dbg(ceudev->dev, + "Subdevice failed to start streaming: %d\n", ret); + goto error_return_bufs; + } + + spin_lock_irqsave(&ceudev->lock, irqflags); + ceudev->sequence = 0; + + /* Grab the first available buffer and trigger the first capture. */ + buf = list_first_entry(&ceudev->capture, struct ceu_buffer, + queue); + if (!buf) { + spin_unlock_irqrestore(&ceudev->lock, irqflags); + dev_dbg(ceudev->dev, + "No buffer available for capture.\n"); + goto error_stop_sensor; + } + + list_del(&buf->queue); + ceudev->active = &buf->vb; + + /* Clean and program interrupts for first capture. */ + ceu_write(ceudev, CEU_CETCR, ~ceudev->irq_mask); + ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK); + + ceu_capture(ceudev); + + spin_unlock_irqrestore(&ceudev->lock, irqflags); + + return 0; + +error_stop_sensor: + v4l2_subdev_call(v4l2_sd, video, s_stream, 0); + +error_return_bufs: + spin_lock_irqsave(&ceudev->lock, irqflags); + list_for_each_entry(buf, &ceudev->capture, queue) + vb2_buffer_done(&ceudev->active->vb2_buf, + VB2_BUF_STATE_QUEUED); + ceudev->active = NULL; + spin_unlock_irqrestore(&ceudev->lock, irqflags); + + return ret; +} + +static void ceu_stop_streaming(struct vb2_queue *vq) +{ + struct ceu_device *ceudev = vb2_get_drv_priv(vq); + struct v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd; + struct ceu_buffer *buf; + unsigned long irqflags; + + /* Clean and disable interrupt sources. */ + ceu_write(ceudev, CEU_CETCR, + ceu_read(ceudev, CEU_CETCR) & ceudev->irq_mask); + ceu_write(ceudev, CEU_CEIER, CEU_CEIER_MASK); + + v4l2_subdev_call(v4l2_sd, video, s_stream, 0); + + spin_lock_irqsave(&ceudev->lock, irqflags); + if (ceudev->active) { + vb2_buffer_done(&ceudev->active->vb2_buf, + VB2_BUF_STATE_ERROR); + ceudev->active = NULL; + } + + /* Release all queued buffers. */ + list_for_each_entry(buf, &ceudev->capture, queue) + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + INIT_LIST_HEAD(&ceudev->capture); + + spin_unlock_irqrestore(&ceudev->lock, irqflags); + + ceu_soft_reset(ceudev); +} + +static const struct vb2_ops ceu_vb2_ops = { + .queue_setup = ceu_vb2_setup, + .buf_queue = ceu_vb2_queue, + .buf_prepare = ceu_vb2_prepare, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = ceu_start_streaming, + .stop_streaming = ceu_stop_streaming, +}; + +/* --- CEU image formats handling --- */ + +/* + * __ceu_try_fmt() - test format on CEU and sensor + * @ceudev: The CEU device. + * @v4l2_fmt: format to test. + * @sd_mbus_code: the media bus code accepted by the subdevice; output param. + * + * Returns 0 for success, < 0 for errors. + */ +static int __ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt, + u32 *sd_mbus_code) +{ + struct ceu_subdev *ceu_sd = ceudev->sd; + struct v4l2_pix_format_mplane *pix = &v4l2_fmt->fmt.pix_mp; + struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd; + struct v4l2_subdev_pad_config pad_cfg; + struct v4l2_subdev_state pad_state = { + .pads = &pad_cfg + }; + const struct ceu_fmt *ceu_fmt; + u32 mbus_code_old; + u32 mbus_code; + int ret; + + /* + * Set format on sensor sub device: bus format used to produce memory + * format is selected depending on YUV component ordering or + * at initialization time. + */ + struct v4l2_subdev_format sd_format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + + mbus_code_old = ceu_sd->mbus_fmt.mbus_code; + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_YUYV: + mbus_code = MEDIA_BUS_FMT_YUYV8_2X8; + break; + case V4L2_PIX_FMT_UYVY: + mbus_code = MEDIA_BUS_FMT_UYVY8_2X8; + break; + case V4L2_PIX_FMT_YVYU: + mbus_code = MEDIA_BUS_FMT_YVYU8_2X8; + break; + case V4L2_PIX_FMT_VYUY: + mbus_code = MEDIA_BUS_FMT_VYUY8_2X8; + break; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + mbus_code = ceu_sd->mbus_fmt.mbus_code; + break; + + default: + pix->pixelformat = V4L2_PIX_FMT_NV16; + mbus_code = ceu_sd->mbus_fmt.mbus_code; + break; + } + + ceu_fmt = get_ceu_fmt_from_fourcc(pix->pixelformat); + + /* CFSZR requires height and width to be 4-pixel aligned. */ + v4l_bound_align_image(&pix->width, 2, CEU_MAX_WIDTH, 4, + &pix->height, 4, CEU_MAX_HEIGHT, 4, 0); + + v4l2_fill_mbus_format_mplane(&sd_format.format, pix); + + /* + * Try with the mbus_code matching YUYV components ordering first, + * if that one fails, fallback to default selected at initialization + * time. + */ + sd_format.format.code = mbus_code; + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, &pad_state, &sd_format); + if (ret) { + if (ret == -EINVAL) { + /* fallback */ + sd_format.format.code = mbus_code_old; + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, + &pad_state, &sd_format); + } + + if (ret) + return ret; + } + + /* Apply size returned by sensor as the CEU can't scale. */ + v4l2_fill_pix_format_mplane(pix, &sd_format.format); + + /* Calculate per-plane sizes based on image format. */ + ceu_calc_plane_sizes(ceudev, ceu_fmt, pix); + + /* Report to caller the configured mbus format. */ + *sd_mbus_code = sd_format.format.code; + + return 0; +} + +/* + * ceu_try_fmt() - Wrapper for __ceu_try_fmt; discard configured mbus_fmt + */ +static int ceu_try_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt) +{ + u32 mbus_code; + + return __ceu_try_fmt(ceudev, v4l2_fmt, &mbus_code); +} + +/* + * ceu_set_fmt() - Apply the supplied format to both sensor and CEU + */ +static int ceu_set_fmt(struct ceu_device *ceudev, struct v4l2_format *v4l2_fmt) +{ + struct ceu_subdev *ceu_sd = ceudev->sd; + struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd; + u32 mbus_code; + int ret; + + /* + * Set format on sensor sub device: bus format used to produce memory + * format is selected at initialization time. + */ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + ret = __ceu_try_fmt(ceudev, v4l2_fmt, &mbus_code); + if (ret) + return ret; + + format.format.code = mbus_code; + v4l2_fill_mbus_format_mplane(&format.format, &v4l2_fmt->fmt.pix_mp); + ret = v4l2_subdev_call(v4l2_sd, pad, set_fmt, NULL, &format); + if (ret) + return ret; + + ceudev->v4l2_pix = v4l2_fmt->fmt.pix_mp; + ceudev->field = V4L2_FIELD_NONE; + + return 0; +} + +/* + * ceu_set_default_fmt() - Apply default NV16 memory output format with VGA + * sizes. + */ +static int ceu_set_default_fmt(struct ceu_device *ceudev) +{ + int ret; + + struct v4l2_format v4l2_fmt = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .fmt.pix_mp = { + .width = VGA_WIDTH, + .height = VGA_HEIGHT, + .field = V4L2_FIELD_NONE, + .pixelformat = V4L2_PIX_FMT_NV16, + .num_planes = 2, + .plane_fmt = { + [0] = { + .sizeimage = VGA_WIDTH * VGA_HEIGHT * 2, + .bytesperline = VGA_WIDTH * 2, + }, + [1] = { + .sizeimage = VGA_WIDTH * VGA_HEIGHT * 2, + .bytesperline = VGA_WIDTH * 2, + }, + }, + }, + }; + + ret = ceu_try_fmt(ceudev, &v4l2_fmt); + if (ret) + return ret; + + ceudev->v4l2_pix = v4l2_fmt.fmt.pix_mp; + ceudev->field = V4L2_FIELD_NONE; + + return 0; +} + +/* + * ceu_init_mbus_fmt() - Query sensor for supported formats and initialize + * CEU media bus format used to produce memory formats. + * + * Find out if sensor can produce a permutation of 8-bits YUYV bus format. + * From a single 8-bits YUYV bus format the CEU can produce several memory + * output formats: + * - NV[12|21|16|61] through image fetch mode; + * - YUYV422 if sensor provides YUYV422 + * + * TODO: Other YUYV422 permutations through data fetch sync mode and DTARY + * TODO: Binary data (eg. JPEG) and raw formats through data fetch sync mode + */ +static int ceu_init_mbus_fmt(struct ceu_device *ceudev) +{ + struct ceu_subdev *ceu_sd = ceudev->sd; + struct ceu_mbus_fmt *mbus_fmt = &ceu_sd->mbus_fmt; + struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd; + bool yuyv_bus_fmt = false; + + struct v4l2_subdev_mbus_code_enum sd_mbus_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .index = 0, + }; + + /* Find out if sensor can produce any permutation of 8-bits YUYV422. */ + while (!yuyv_bus_fmt && + !v4l2_subdev_call(v4l2_sd, pad, enum_mbus_code, + NULL, &sd_mbus_fmt)) { + switch (sd_mbus_fmt.code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + yuyv_bus_fmt = true; + break; + default: + /* + * Only support 8-bits YUYV bus formats at the moment; + * + * TODO: add support for binary formats (data sync + * fetch mode). + */ + break; + } + + sd_mbus_fmt.index++; + } + + if (!yuyv_bus_fmt) + return -ENXIO; + + /* + * Save the first encountered YUYV format as "mbus_fmt" and use it + * to output all planar YUV422 and YUV420 (NV*) formats to memory as + * well as for data synch fetch mode (YUYV - YVYU etc. ). + */ + mbus_fmt->mbus_code = sd_mbus_fmt.code; + mbus_fmt->bps = 8; + + /* Annotate the selected bus format components ordering. */ + switch (sd_mbus_fmt.code) { + case MEDIA_BUS_FMT_YUYV8_2X8: + mbus_fmt->fmt_order = CEU_CAMCR_DTARY_8_YUYV; + mbus_fmt->fmt_order_swap = CEU_CAMCR_DTARY_8_YVYU; + mbus_fmt->swapped = false; + mbus_fmt->bpp = 16; + break; + + case MEDIA_BUS_FMT_YVYU8_2X8: + mbus_fmt->fmt_order = CEU_CAMCR_DTARY_8_YVYU; + mbus_fmt->fmt_order_swap = CEU_CAMCR_DTARY_8_YUYV; + mbus_fmt->swapped = true; + mbus_fmt->bpp = 16; + break; + + case MEDIA_BUS_FMT_UYVY8_2X8: + mbus_fmt->fmt_order = CEU_CAMCR_DTARY_8_UYVY; + mbus_fmt->fmt_order_swap = CEU_CAMCR_DTARY_8_VYUY; + mbus_fmt->swapped = false; + mbus_fmt->bpp = 16; + break; + + case MEDIA_BUS_FMT_VYUY8_2X8: + mbus_fmt->fmt_order = CEU_CAMCR_DTARY_8_VYUY; + mbus_fmt->fmt_order_swap = CEU_CAMCR_DTARY_8_UYVY; + mbus_fmt->swapped = true; + mbus_fmt->bpp = 16; + break; + } + + return 0; +} + +/* --- Runtime PM Handlers --- */ + +/* + * ceu_runtime_resume() - soft-reset the interface and turn sensor power on. + */ +static int __maybe_unused ceu_runtime_resume(struct device *dev) +{ + struct ceu_device *ceudev = dev_get_drvdata(dev); + struct v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd; + + v4l2_subdev_call(v4l2_sd, core, s_power, 1); + + ceu_soft_reset(ceudev); + + return 0; +} + +/* + * ceu_runtime_suspend() - disable capture and interrupts and soft-reset. + * Turn sensor power off. + */ +static int __maybe_unused ceu_runtime_suspend(struct device *dev) +{ + struct ceu_device *ceudev = dev_get_drvdata(dev); + struct v4l2_subdev *v4l2_sd = ceudev->sd->v4l2_sd; + + v4l2_subdev_call(v4l2_sd, core, s_power, 0); + + ceu_write(ceudev, CEU_CEIER, 0); + ceu_soft_reset(ceudev); + + return 0; +} + +/* --- File Operations --- */ + +static int ceu_open(struct file *file) +{ + struct ceu_device *ceudev = video_drvdata(file); + int ret; + + ret = v4l2_fh_open(file); + if (ret) + return ret; + + mutex_lock(&ceudev->mlock); + /* Causes soft-reset and sensor power on on first open */ + ret = pm_runtime_resume_and_get(ceudev->dev); + mutex_unlock(&ceudev->mlock); + + return ret; +} + +static int ceu_release(struct file *file) +{ + struct ceu_device *ceudev = video_drvdata(file); + + vb2_fop_release(file); + + mutex_lock(&ceudev->mlock); + /* Causes soft-reset and sensor power down on last close */ + pm_runtime_put(ceudev->dev); + mutex_unlock(&ceudev->mlock); + + return 0; +} + +static const struct v4l2_file_operations ceu_fops = { + .owner = THIS_MODULE, + .open = ceu_open, + .release = ceu_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +/* --- Video Device IOCTLs --- */ + +static int ceu_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct ceu_device *ceudev = video_drvdata(file); + + strscpy(cap->card, "Renesas CEU", sizeof(cap->card)); + strscpy(cap->driver, DRIVER_NAME, sizeof(cap->driver)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:renesas-ceu-%s", dev_name(ceudev->dev)); + + return 0; +} + +static int ceu_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct ceu_fmt *fmt; + + if (f->index >= ARRAY_SIZE(ceu_fmt_list)) + return -EINVAL; + + fmt = &ceu_fmt_list[f->index]; + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int ceu_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ceu_device *ceudev = video_drvdata(file); + + return ceu_try_fmt(ceudev, f); +} + +static int ceu_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ceu_device *ceudev = video_drvdata(file); + + if (vb2_is_streaming(&ceudev->vb2_vq)) + return -EBUSY; + + return ceu_set_fmt(ceudev, f); +} + +static int ceu_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct ceu_device *ceudev = video_drvdata(file); + + f->fmt.pix_mp = ceudev->v4l2_pix; + + return 0; +} + +static int ceu_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct ceu_device *ceudev = video_drvdata(file); + struct ceu_subdev *ceusd; + + if (inp->index >= ceudev->num_sd) + return -EINVAL; + + ceusd = ceudev->subdevs[inp->index]; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + inp->std = 0; + snprintf(inp->name, sizeof(inp->name), "Camera%u: %s", + inp->index, ceusd->v4l2_sd->name); + + return 0; +} + +static int ceu_g_input(struct file *file, void *priv, unsigned int *i) +{ + struct ceu_device *ceudev = video_drvdata(file); + + *i = ceudev->sd_index; + + return 0; +} + +static int ceu_s_input(struct file *file, void *priv, unsigned int i) +{ + struct ceu_device *ceudev = video_drvdata(file); + struct ceu_subdev *ceu_sd_old; + int ret; + + if (i >= ceudev->num_sd) + return -EINVAL; + + if (vb2_is_streaming(&ceudev->vb2_vq)) + return -EBUSY; + + if (i == ceudev->sd_index) + return 0; + + ceu_sd_old = ceudev->sd; + ceudev->sd = ceudev->subdevs[i]; + + /* + * Make sure we can generate output image formats and apply + * default one. + */ + ret = ceu_init_mbus_fmt(ceudev); + if (ret) { + ceudev->sd = ceu_sd_old; + return -EINVAL; + } + + ret = ceu_set_default_fmt(ceudev); + if (ret) { + ceudev->sd = ceu_sd_old; + return -EINVAL; + } + + /* Now that we're sure we can use the sensor, power off the old one. */ + v4l2_subdev_call(ceu_sd_old->v4l2_sd, core, s_power, 0); + v4l2_subdev_call(ceudev->sd->v4l2_sd, core, s_power, 1); + + ceudev->sd_index = i; + + return 0; +} + +static int ceu_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct ceu_device *ceudev = video_drvdata(file); + + return v4l2_g_parm_cap(video_devdata(file), ceudev->sd->v4l2_sd, a); +} + +static int ceu_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct ceu_device *ceudev = video_drvdata(file); + + return v4l2_s_parm_cap(video_devdata(file), ceudev->sd->v4l2_sd, a); +} + +static int ceu_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct ceu_device *ceudev = video_drvdata(file); + struct ceu_subdev *ceu_sd = ceudev->sd; + const struct ceu_fmt *ceu_fmt; + struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd; + int ret; + + struct v4l2_subdev_frame_size_enum fse = { + .code = ceu_sd->mbus_fmt.mbus_code, + .index = fsize->index, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + /* Just check if user supplied pixel format is supported. */ + ceu_fmt = get_ceu_fmt_from_fourcc(fsize->pixel_format); + if (!ceu_fmt) + return -EINVAL; + + ret = v4l2_subdev_call(v4l2_sd, pad, enum_frame_size, + NULL, &fse); + if (ret) + return ret; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = CEU_W_MAX(fse.max_width); + fsize->discrete.height = CEU_H_MAX(fse.max_height); + + return 0; +} + +static int ceu_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct ceu_device *ceudev = video_drvdata(file); + struct ceu_subdev *ceu_sd = ceudev->sd; + const struct ceu_fmt *ceu_fmt; + struct v4l2_subdev *v4l2_sd = ceu_sd->v4l2_sd; + int ret; + + struct v4l2_subdev_frame_interval_enum fie = { + .code = ceu_sd->mbus_fmt.mbus_code, + .index = fival->index, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + /* Just check if user supplied pixel format is supported. */ + ceu_fmt = get_ceu_fmt_from_fourcc(fival->pixel_format); + if (!ceu_fmt) + return -EINVAL; + + ret = v4l2_subdev_call(v4l2_sd, pad, enum_frame_interval, NULL, + &fie); + if (ret) + return ret; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + + return 0; +} + +static const struct v4l2_ioctl_ops ceu_ioctl_ops = { + .vidioc_querycap = ceu_querycap, + + .vidioc_enum_fmt_vid_cap = ceu_enum_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane = ceu_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = ceu_s_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane = ceu_g_fmt_vid_cap, + + .vidioc_enum_input = ceu_enum_input, + .vidioc_g_input = ceu_g_input, + .vidioc_s_input = ceu_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_g_parm = ceu_g_parm, + .vidioc_s_parm = ceu_s_parm, + .vidioc_enum_framesizes = ceu_enum_framesizes, + .vidioc_enum_frameintervals = ceu_enum_frameintervals, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * ceu_vdev_release() - release CEU video device memory when last reference + * to this driver is closed + */ +static void ceu_vdev_release(struct video_device *vdev) +{ + struct ceu_device *ceudev = video_get_drvdata(vdev); + + kfree(ceudev); +} + +static int ceu_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *v4l2_sd, + struct v4l2_async_subdev *asd) +{ + struct v4l2_device *v4l2_dev = notifier->v4l2_dev; + struct ceu_device *ceudev = v4l2_to_ceu(v4l2_dev); + struct ceu_subdev *ceu_sd = to_ceu_subdev(asd); + + ceu_sd->v4l2_sd = v4l2_sd; + ceudev->num_sd++; + + return 0; +} + +static int ceu_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct v4l2_device *v4l2_dev = notifier->v4l2_dev; + struct ceu_device *ceudev = v4l2_to_ceu(v4l2_dev); + struct video_device *vdev = &ceudev->vdev; + struct vb2_queue *q = &ceudev->vb2_vq; + struct v4l2_subdev *v4l2_sd; + int ret; + + /* Initialize vb2 queue. */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = ceudev; + q->ops = &ceu_vb2_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct ceu_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->lock = &ceudev->mlock; + q->dev = ceudev->v4l2_dev.dev; + + ret = vb2_queue_init(q); + if (ret) + return ret; + + /* + * Make sure at least one sensor is primary and use it to initialize + * ceu formats. + */ + if (!ceudev->sd) { + ceudev->sd = ceudev->subdevs[0]; + ceudev->sd_index = 0; + } + + v4l2_sd = ceudev->sd->v4l2_sd; + + ret = ceu_init_mbus_fmt(ceudev); + if (ret) + return ret; + + ret = ceu_set_default_fmt(ceudev); + if (ret) + return ret; + + /* Register the video device. */ + strscpy(vdev->name, DRIVER_NAME, sizeof(vdev->name)); + vdev->v4l2_dev = v4l2_dev; + vdev->lock = &ceudev->mlock; + vdev->queue = &ceudev->vb2_vq; + vdev->ctrl_handler = v4l2_sd->ctrl_handler; + vdev->fops = &ceu_fops; + vdev->ioctl_ops = &ceu_ioctl_ops; + vdev->release = ceu_vdev_release; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_STREAMING; + video_set_drvdata(vdev, ceudev); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + v4l2_err(vdev->v4l2_dev, + "video_register_device failed: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct v4l2_async_notifier_operations ceu_notify_ops = { + .bound = ceu_notify_bound, + .complete = ceu_notify_complete, +}; + +/* + * ceu_init_async_subdevs() - Initialize CEU subdevices and async_subdevs in + * ceu device. Both DT and platform data parsing use + * this routine. + * + * Returns 0 for success, -ENOMEM for failure. + */ +static int ceu_init_async_subdevs(struct ceu_device *ceudev, unsigned int n_sd) +{ + /* Reserve memory for 'n_sd' ceu_subdev descriptors. */ + ceudev->subdevs = devm_kcalloc(ceudev->dev, n_sd, + sizeof(*ceudev->subdevs), GFP_KERNEL); + if (!ceudev->subdevs) + return -ENOMEM; + + ceudev->sd = NULL; + ceudev->sd_index = 0; + ceudev->num_sd = 0; + + return 0; +} + +/* + * ceu_parse_platform_data() - Initialize async_subdevices using platform + * device provided data. + */ +static int ceu_parse_platform_data(struct ceu_device *ceudev, + const struct ceu_platform_data *pdata) +{ + const struct ceu_async_subdev *async_sd; + struct ceu_subdev *ceu_sd; + unsigned int i; + int ret; + + if (pdata->num_subdevs == 0) + return -ENODEV; + + ret = ceu_init_async_subdevs(ceudev, pdata->num_subdevs); + if (ret) + return ret; + + for (i = 0; i < pdata->num_subdevs; i++) { + + /* Setup the ceu subdevice and the async subdevice. */ + async_sd = &pdata->subdevs[i]; + ceu_sd = v4l2_async_nf_add_i2c(&ceudev->notifier, + async_sd->i2c_adapter_id, + async_sd->i2c_address, + struct ceu_subdev); + if (IS_ERR(ceu_sd)) { + v4l2_async_nf_cleanup(&ceudev->notifier); + return PTR_ERR(ceu_sd); + } + ceu_sd->mbus_flags = async_sd->flags; + ceudev->subdevs[i] = ceu_sd; + } + + return pdata->num_subdevs; +} + +/* + * ceu_parse_dt() - Initialize async_subdevs parsing device tree graph. + */ +static int ceu_parse_dt(struct ceu_device *ceudev) +{ + struct device_node *of = ceudev->dev->of_node; + struct device_node *ep; + struct ceu_subdev *ceu_sd; + unsigned int i; + int num_ep; + int ret; + + num_ep = of_graph_get_endpoint_count(of); + if (!num_ep) + return -ENODEV; + + ret = ceu_init_async_subdevs(ceudev, num_ep); + if (ret) + return ret; + + for (i = 0; i < num_ep; i++) { + struct v4l2_fwnode_endpoint fw_ep = { + .bus_type = V4L2_MBUS_PARALLEL, + .bus = { + .parallel = { + .flags = V4L2_MBUS_HSYNC_ACTIVE_HIGH | + V4L2_MBUS_VSYNC_ACTIVE_HIGH, + .bus_width = 8, + }, + }, + }; + + ep = of_graph_get_endpoint_by_regs(of, 0, i); + if (!ep) { + dev_err(ceudev->dev, + "No subdevice connected on endpoint %u.\n", i); + ret = -ENODEV; + goto error_cleanup; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &fw_ep); + if (ret) { + dev_err(ceudev->dev, + "Unable to parse endpoint #%u: %d.\n", i, ret); + goto error_cleanup; + } + + /* Setup the ceu subdevice and the async subdevice. */ + ceu_sd = v4l2_async_nf_add_fwnode_remote(&ceudev->notifier, + of_fwnode_handle(ep), + struct ceu_subdev); + if (IS_ERR(ceu_sd)) { + ret = PTR_ERR(ceu_sd); + goto error_cleanup; + } + ceu_sd->mbus_flags = fw_ep.bus.parallel.flags; + ceudev->subdevs[i] = ceu_sd; + + of_node_put(ep); + } + + return num_ep; + +error_cleanup: + v4l2_async_nf_cleanup(&ceudev->notifier); + of_node_put(ep); + return ret; +} + +/* + * struct ceu_data - Platform specific CEU data + * @irq_mask: CETCR mask with all interrupt sources enabled. The mask differs + * between SH4 and RZ platforms. + */ +struct ceu_data { + u32 irq_mask; +}; + +static const struct ceu_data ceu_data_sh4 = { + .irq_mask = CEU_CETCR_ALL_IRQS_SH4, +}; + +#if IS_ENABLED(CONFIG_OF) +static const struct ceu_data ceu_data_rz = { + .irq_mask = CEU_CETCR_ALL_IRQS_RZ, +}; + +static const struct of_device_id ceu_of_match[] = { + { .compatible = "renesas,r7s72100-ceu", .data = &ceu_data_rz }, + { .compatible = "renesas,r8a7740-ceu", .data = &ceu_data_rz }, + { } +}; +MODULE_DEVICE_TABLE(of, ceu_of_match); +#endif + +static int ceu_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct ceu_data *ceu_data; + struct ceu_device *ceudev; + unsigned int irq; + int num_subdevs; + int ret; + + ceudev = kzalloc(sizeof(*ceudev), GFP_KERNEL); + if (!ceudev) + return -ENOMEM; + + platform_set_drvdata(pdev, ceudev); + ceudev->dev = dev; + + INIT_LIST_HEAD(&ceudev->capture); + spin_lock_init(&ceudev->lock); + mutex_init(&ceudev->mlock); + + ceudev->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ceudev->base)) { + ret = PTR_ERR(ceudev->base); + goto error_free_ceudev; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto error_free_ceudev; + irq = ret; + + ret = devm_request_irq(dev, irq, ceu_irq, + 0, dev_name(dev), ceudev); + if (ret) { + dev_err(&pdev->dev, "Unable to request CEU interrupt.\n"); + goto error_free_ceudev; + } + + pm_runtime_enable(dev); + + ret = v4l2_device_register(dev, &ceudev->v4l2_dev); + if (ret) + goto error_pm_disable; + + v4l2_async_nf_init(&ceudev->notifier); + + if (IS_ENABLED(CONFIG_OF) && dev->of_node) { + ceu_data = of_device_get_match_data(dev); + num_subdevs = ceu_parse_dt(ceudev); + } else if (dev->platform_data) { + /* Assume SH4 if booting with platform data. */ + ceu_data = &ceu_data_sh4; + num_subdevs = ceu_parse_platform_data(ceudev, + dev->platform_data); + } else { + num_subdevs = -EINVAL; + } + + if (num_subdevs < 0) { + ret = num_subdevs; + goto error_v4l2_unregister; + } + ceudev->irq_mask = ceu_data->irq_mask; + + ceudev->notifier.v4l2_dev = &ceudev->v4l2_dev; + ceudev->notifier.ops = &ceu_notify_ops; + ret = v4l2_async_nf_register(&ceudev->v4l2_dev, &ceudev->notifier); + if (ret) + goto error_cleanup; + + dev_info(dev, "Renesas Capture Engine Unit %s\n", dev_name(dev)); + + return 0; + +error_cleanup: + v4l2_async_nf_cleanup(&ceudev->notifier); +error_v4l2_unregister: + v4l2_device_unregister(&ceudev->v4l2_dev); +error_pm_disable: + pm_runtime_disable(dev); +error_free_ceudev: + kfree(ceudev); + + return ret; +} + +static int ceu_remove(struct platform_device *pdev) +{ + struct ceu_device *ceudev = platform_get_drvdata(pdev); + + pm_runtime_disable(ceudev->dev); + + v4l2_async_nf_unregister(&ceudev->notifier); + + v4l2_async_nf_cleanup(&ceudev->notifier); + + v4l2_device_unregister(&ceudev->v4l2_dev); + + video_unregister_device(&ceudev->vdev); + + return 0; +} + +static const struct dev_pm_ops ceu_pm_ops = { + SET_RUNTIME_PM_OPS(ceu_runtime_suspend, + ceu_runtime_resume, + NULL) +}; + +static struct platform_driver ceu_driver = { + .driver = { + .name = DRIVER_NAME, + .pm = &ceu_pm_ops, + .of_match_table = of_match_ptr(ceu_of_match), + }, + .probe = ceu_probe, + .remove = ceu_remove, +}; + +module_platform_driver(ceu_driver); + +MODULE_DESCRIPTION("Renesas CEU camera driver"); +MODULE_AUTHOR("Jacopo Mondi "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/renesas/sh_vou.c b/drivers/media/platform/renesas/sh_vou.c new file mode 100644 index 000000000..ca4310e26 --- /dev/null +++ b/drivers/media/platform/renesas/sh_vou.c @@ -0,0 +1,1375 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * SuperH Video Output Unit (VOU) driver + * + * Copyright (C) 2010, Guennadi Liakhovetski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/* Mirror addresses are not available for all registers */ +#define VOUER 0 +#define VOUCR 4 +#define VOUSTR 8 +#define VOUVCR 0xc +#define VOUISR 0x10 +#define VOUBCR 0x14 +#define VOUDPR 0x18 +#define VOUDSR 0x1c +#define VOUVPR 0x20 +#define VOUIR 0x24 +#define VOUSRR 0x28 +#define VOUMSR 0x2c +#define VOUHIR 0x30 +#define VOUDFR 0x34 +#define VOUAD1R 0x38 +#define VOUAD2R 0x3c +#define VOUAIR 0x40 +#define VOUSWR 0x44 +#define VOURCR 0x48 +#define VOURPR 0x50 + +enum sh_vou_status { + SH_VOU_IDLE, + SH_VOU_INITIALISING, + SH_VOU_RUNNING, +}; + +#define VOU_MIN_IMAGE_WIDTH 16 +#define VOU_MAX_IMAGE_WIDTH 720 +#define VOU_MIN_IMAGE_HEIGHT 16 + +struct sh_vou_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +static inline struct +sh_vou_buffer *to_sh_vou_buffer(struct vb2_v4l2_buffer *vb2) +{ + return container_of(vb2, struct sh_vou_buffer, vb); +} + +struct sh_vou_device { + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct sh_vou_pdata *pdata; + spinlock_t lock; + void __iomem *base; + /* State information */ + struct v4l2_pix_format pix; + struct v4l2_rect rect; + struct list_head buf_list; + v4l2_std_id std; + int pix_idx; + struct vb2_queue queue; + struct sh_vou_buffer *active; + enum sh_vou_status status; + unsigned sequence; + struct mutex fop_lock; +}; + +/* Register access routines for sides A, B and mirror addresses */ +static void sh_vou_reg_a_write(struct sh_vou_device *vou_dev, unsigned int reg, + u32 value) +{ + __raw_writel(value, vou_dev->base + reg); +} + +static void sh_vou_reg_ab_write(struct sh_vou_device *vou_dev, unsigned int reg, + u32 value) +{ + __raw_writel(value, vou_dev->base + reg); + __raw_writel(value, vou_dev->base + reg + 0x1000); +} + +static void sh_vou_reg_m_write(struct sh_vou_device *vou_dev, unsigned int reg, + u32 value) +{ + __raw_writel(value, vou_dev->base + reg + 0x2000); +} + +static u32 sh_vou_reg_a_read(struct sh_vou_device *vou_dev, unsigned int reg) +{ + return __raw_readl(vou_dev->base + reg); +} + +static void sh_vou_reg_a_set(struct sh_vou_device *vou_dev, unsigned int reg, + u32 value, u32 mask) +{ + u32 old = __raw_readl(vou_dev->base + reg); + + value = (value & mask) | (old & ~mask); + __raw_writel(value, vou_dev->base + reg); +} + +static void sh_vou_reg_b_set(struct sh_vou_device *vou_dev, unsigned int reg, + u32 value, u32 mask) +{ + sh_vou_reg_a_set(vou_dev, reg + 0x1000, value, mask); +} + +static void sh_vou_reg_ab_set(struct sh_vou_device *vou_dev, unsigned int reg, + u32 value, u32 mask) +{ + sh_vou_reg_a_set(vou_dev, reg, value, mask); + sh_vou_reg_b_set(vou_dev, reg, value, mask); +} + +struct sh_vou_fmt { + u32 pfmt; + unsigned char bpp; + unsigned char bpl; + unsigned char rgb; + unsigned char yf; + unsigned char pkf; +}; + +/* Further pixel formats can be added */ +static struct sh_vou_fmt vou_fmt[] = { + { + .pfmt = V4L2_PIX_FMT_NV12, + .bpp = 12, + .bpl = 1, + .yf = 0, + .rgb = 0, + }, + { + .pfmt = V4L2_PIX_FMT_NV16, + .bpp = 16, + .bpl = 1, + .yf = 1, + .rgb = 0, + }, + { + .pfmt = V4L2_PIX_FMT_RGB24, + .bpp = 24, + .bpl = 3, + .pkf = 2, + .rgb = 1, + }, + { + .pfmt = V4L2_PIX_FMT_RGB565, + .bpp = 16, + .bpl = 2, + .pkf = 3, + .rgb = 1, + }, + { + .pfmt = V4L2_PIX_FMT_RGB565X, + .bpp = 16, + .bpl = 2, + .pkf = 3, + .rgb = 1, + }, +}; + +static void sh_vou_schedule_next(struct sh_vou_device *vou_dev, + struct vb2_v4l2_buffer *vbuf) +{ + dma_addr_t addr1, addr2; + + addr1 = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + switch (vou_dev->pix.pixelformat) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + addr2 = addr1 + vou_dev->pix.width * vou_dev->pix.height; + break; + default: + addr2 = 0; + } + + sh_vou_reg_m_write(vou_dev, VOUAD1R, addr1); + sh_vou_reg_m_write(vou_dev, VOUAD2R, addr2); +} + +static void sh_vou_stream_config(struct sh_vou_device *vou_dev) +{ + unsigned int row_coeff; +#ifdef __LITTLE_ENDIAN + u32 dataswap = 7; +#else + u32 dataswap = 0; +#endif + + switch (vou_dev->pix.pixelformat) { + default: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + row_coeff = 1; + break; + case V4L2_PIX_FMT_RGB565: + dataswap ^= 1; + fallthrough; + case V4L2_PIX_FMT_RGB565X: + row_coeff = 2; + break; + case V4L2_PIX_FMT_RGB24: + row_coeff = 3; + break; + } + + sh_vou_reg_a_write(vou_dev, VOUSWR, dataswap); + sh_vou_reg_ab_write(vou_dev, VOUAIR, vou_dev->pix.width * row_coeff); +} + +/* Locking: caller holds fop_lock mutex */ +static int sh_vou_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix = &vou_dev->pix; + int bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; + + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + + if (*nplanes) + return sizes[0] < pix->height * bytes_per_line ? -EINVAL : 0; + *nplanes = 1; + sizes[0] = pix->height * bytes_per_line; + return 0; +} + +static int sh_vou_buf_prepare(struct vb2_buffer *vb) +{ + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue); + struct v4l2_pix_format *pix = &vou_dev->pix; + unsigned bytes_per_line = vou_fmt[vou_dev->pix_idx].bpp * pix->width / 8; + unsigned size = pix->height * bytes_per_line; + + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + + if (vb2_plane_size(vb, 0) < size) { + /* User buffer too small */ + dev_warn(vou_dev->v4l2_dev.dev, "buffer too small (%lu < %u)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + return 0; +} + +/* Locking: caller holds fop_lock mutex and vq->irqlock spinlock */ +static void sh_vou_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vb->vb2_queue); + struct sh_vou_buffer *shbuf = to_sh_vou_buffer(vbuf); + unsigned long flags; + + spin_lock_irqsave(&vou_dev->lock, flags); + list_add_tail(&shbuf->list, &vou_dev->buf_list); + spin_unlock_irqrestore(&vou_dev->lock, flags); +} + +static int sh_vou_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); + struct sh_vou_buffer *buf, *node; + int ret; + + vou_dev->sequence = 0; + ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, + video, s_stream, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) { + list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, + VB2_BUF_STATE_QUEUED); + list_del(&buf->list); + } + vou_dev->active = NULL; + return ret; + } + + buf = list_entry(vou_dev->buf_list.next, struct sh_vou_buffer, list); + + vou_dev->active = buf; + + /* Start from side A: we use mirror addresses, so, set B */ + sh_vou_reg_a_write(vou_dev, VOURPR, 1); + dev_dbg(vou_dev->v4l2_dev.dev, "%s: first buffer status 0x%x\n", + __func__, sh_vou_reg_a_read(vou_dev, VOUSTR)); + sh_vou_schedule_next(vou_dev, &buf->vb); + + buf = list_entry(buf->list.next, struct sh_vou_buffer, list); + + /* Second buffer - initialise register side B */ + sh_vou_reg_a_write(vou_dev, VOURPR, 0); + sh_vou_schedule_next(vou_dev, &buf->vb); + + /* Register side switching with frame VSYNC */ + sh_vou_reg_a_write(vou_dev, VOURCR, 5); + + sh_vou_stream_config(vou_dev); + /* Enable End-of-Frame (VSYNC) interrupts */ + sh_vou_reg_a_write(vou_dev, VOUIR, 0x10004); + + /* Two buffers on the queue - activate the hardware */ + vou_dev->status = SH_VOU_RUNNING; + sh_vou_reg_a_write(vou_dev, VOUER, 0x107); + return 0; +} + +static void sh_vou_stop_streaming(struct vb2_queue *vq) +{ + struct sh_vou_device *vou_dev = vb2_get_drv_priv(vq); + struct sh_vou_buffer *buf, *node; + unsigned long flags; + + v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, + video, s_stream, 0); + /* disable output */ + sh_vou_reg_a_set(vou_dev, VOUER, 0, 1); + /* ...but the current frame will complete */ + sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x30000); + msleep(50); + spin_lock_irqsave(&vou_dev->lock, flags); + list_for_each_entry_safe(buf, node, &vou_dev->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + list_del(&buf->list); + } + vou_dev->active = NULL; + spin_unlock_irqrestore(&vou_dev->lock, flags); +} + +static const struct vb2_ops sh_vou_qops = { + .queue_setup = sh_vou_queue_setup, + .buf_prepare = sh_vou_buf_prepare, + .buf_queue = sh_vou_buf_queue, + .start_streaming = sh_vou_start_streaming, + .stop_streaming = sh_vou_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* Video IOCTLs */ +static int sh_vou_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + + strscpy(cap->card, "SuperH VOU", sizeof(cap->card)); + strscpy(cap->driver, "sh-vou", sizeof(cap->driver)); + strscpy(cap->bus_info, "platform:sh-vou", sizeof(cap->bus_info)); + return 0; +} + +/* Enumerate formats, that the device can accept from the user */ +static int sh_vou_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + + if (fmt->index >= ARRAY_SIZE(vou_fmt)) + return -EINVAL; + + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + + fmt->pixelformat = vou_fmt[fmt->index].pfmt; + + return 0; +} + +static int sh_vou_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + + fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + fmt->fmt.pix = vou_dev->pix; + + return 0; +} + +static const unsigned char vou_scale_h_num[] = {1, 9, 2, 9, 4}; +static const unsigned char vou_scale_h_den[] = {1, 8, 1, 4, 1}; +static const unsigned char vou_scale_h_fld[] = {0, 2, 1, 3}; +static const unsigned char vou_scale_v_num[] = {1, 2, 4}; +static const unsigned char vou_scale_v_den[] = {1, 1, 1}; +static const unsigned char vou_scale_v_fld[] = {0, 1}; + +static void sh_vou_configure_geometry(struct sh_vou_device *vou_dev, + int pix_idx, int w_idx, int h_idx) +{ + struct sh_vou_fmt *fmt = vou_fmt + pix_idx; + unsigned int black_left, black_top, width_max, + frame_in_height, frame_out_height, frame_out_top; + struct v4l2_rect *rect = &vou_dev->rect; + struct v4l2_pix_format *pix = &vou_dev->pix; + u32 vouvcr = 0, dsr_h, dsr_v; + + if (vou_dev->std & V4L2_STD_525_60) { + width_max = 858; + /* height_max = 262; */ + } else { + width_max = 864; + /* height_max = 312; */ + } + + frame_in_height = pix->height / 2; + frame_out_height = rect->height / 2; + frame_out_top = rect->top / 2; + + /* + * Cropping scheme: max useful image is 720x480, and the total video + * area is 858x525 (NTSC) or 864x625 (PAL). AK8813 / 8814 starts + * sampling data beginning with fixed 276th (NTSC) / 288th (PAL) clock, + * of which the first 33 / 25 clocks HSYNC must be held active. This + * has to be configured in CR[HW]. 1 pixel equals 2 clock periods. + * This gives CR[HW] = 16 / 12, VPR[HVP] = 138 / 144, which gives + * exactly 858 - 138 = 864 - 144 = 720! We call the out-of-display area, + * beyond DSR, specified on the left and top by the VPR register "black + * pixels" and out-of-image area (DPR) "background pixels." We fix VPR + * at 138 / 144 : 20, because that's the HSYNC timing, that our first + * client requires, and that's exactly what leaves us 720 pixels for the + * image; we leave VPR[VVP] at default 20 for now, because the client + * doesn't seem to have any special requirements for it. Otherwise we + * could also set it to max - 240 = 22 / 72. Thus VPR depends only on + * the selected standard, and DPR and DSR are selected according to + * cropping. Q: how does the client detect the first valid line? Does + * HSYNC stay inactive during invalid (black) lines? + */ + black_left = width_max - VOU_MAX_IMAGE_WIDTH; + black_top = 20; + + dsr_h = rect->width + rect->left; + dsr_v = frame_out_height + frame_out_top; + + dev_dbg(vou_dev->v4l2_dev.dev, + "image %ux%u, black %u:%u, offset %u:%u, display %ux%u\n", + pix->width, frame_in_height, black_left, black_top, + rect->left, frame_out_top, dsr_h, dsr_v); + + /* VOUISR height - half of a frame height in frame mode */ + sh_vou_reg_ab_write(vou_dev, VOUISR, (pix->width << 16) | frame_in_height); + sh_vou_reg_ab_write(vou_dev, VOUVPR, (black_left << 16) | black_top); + sh_vou_reg_ab_write(vou_dev, VOUDPR, (rect->left << 16) | frame_out_top); + sh_vou_reg_ab_write(vou_dev, VOUDSR, (dsr_h << 16) | dsr_v); + + /* + * if necessary, we could set VOUHIR to + * max(black_left + dsr_h, width_max) here + */ + + if (w_idx) + vouvcr |= (1 << 15) | (vou_scale_h_fld[w_idx - 1] << 4); + if (h_idx) + vouvcr |= (1 << 14) | vou_scale_v_fld[h_idx - 1]; + + dev_dbg(vou_dev->v4l2_dev.dev, "0x%08x: scaling 0x%x\n", + fmt->pfmt, vouvcr); + + /* To produce a colour bar for testing set bit 23 of VOUVCR */ + sh_vou_reg_ab_write(vou_dev, VOUVCR, vouvcr); + sh_vou_reg_ab_write(vou_dev, VOUDFR, + fmt->pkf | (fmt->yf << 8) | (fmt->rgb << 16)); +} + +struct sh_vou_geometry { + struct v4l2_rect output; + unsigned int in_width; + unsigned int in_height; + int scale_idx_h; + int scale_idx_v; +}; + +/* + * Find input geometry, that we can use to produce output, closest to the + * requested rectangle, using VOU scaling + */ +static void vou_adjust_input(struct sh_vou_geometry *geo, v4l2_std_id std) +{ + /* The compiler cannot know, that best and idx will indeed be set */ + unsigned int best_err = UINT_MAX, best = 0, img_height_max; + int i, idx = 0; + + if (std & V4L2_STD_525_60) + img_height_max = 480; + else + img_height_max = 576; + + /* Image width must be a multiple of 4 */ + v4l_bound_align_image(&geo->in_width, + VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2, + &geo->in_height, + VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); + + /* Select scales to come as close as possible to the output image */ + for (i = ARRAY_SIZE(vou_scale_h_num) - 1; i >= 0; i--) { + unsigned int err; + unsigned int found = geo->output.width * vou_scale_h_den[i] / + vou_scale_h_num[i]; + + if (found > VOU_MAX_IMAGE_WIDTH) + /* scales increase */ + break; + + err = abs(found - geo->in_width); + if (err < best_err) { + best_err = err; + idx = i; + best = found; + } + if (!err) + break; + } + + geo->in_width = best; + geo->scale_idx_h = idx; + + best_err = UINT_MAX; + + /* This loop can be replaced with one division */ + for (i = ARRAY_SIZE(vou_scale_v_num) - 1; i >= 0; i--) { + unsigned int err; + unsigned int found = geo->output.height * vou_scale_v_den[i] / + vou_scale_v_num[i]; + + if (found > img_height_max) + /* scales increase */ + break; + + err = abs(found - geo->in_height); + if (err < best_err) { + best_err = err; + idx = i; + best = found; + } + if (!err) + break; + } + + geo->in_height = best; + geo->scale_idx_v = idx; +} + +/* + * Find output geometry, that we can produce, using VOU scaling, closest to + * the requested rectangle + */ +static void vou_adjust_output(struct sh_vou_geometry *geo, v4l2_std_id std) +{ + unsigned int best_err = UINT_MAX, best = geo->in_width, + width_max, height_max, img_height_max; + int i, idx_h = 0, idx_v = 0; + + if (std & V4L2_STD_525_60) { + width_max = 858; + height_max = 262 * 2; + img_height_max = 480; + } else { + width_max = 864; + height_max = 312 * 2; + img_height_max = 576; + } + + /* Select scales to come as close as possible to the output image */ + for (i = 0; i < ARRAY_SIZE(vou_scale_h_num); i++) { + unsigned int err; + unsigned int found = geo->in_width * vou_scale_h_num[i] / + vou_scale_h_den[i]; + + if (found > VOU_MAX_IMAGE_WIDTH) + /* scales increase */ + break; + + err = abs(found - geo->output.width); + if (err < best_err) { + best_err = err; + idx_h = i; + best = found; + } + if (!err) + break; + } + + geo->output.width = best; + geo->scale_idx_h = idx_h; + if (geo->output.left + best > width_max) + geo->output.left = width_max - best; + + pr_debug("%s(): W %u * %u/%u = %u\n", __func__, geo->in_width, + vou_scale_h_num[idx_h], vou_scale_h_den[idx_h], best); + + best_err = UINT_MAX; + + /* This loop can be replaced with one division */ + for (i = 0; i < ARRAY_SIZE(vou_scale_v_num); i++) { + unsigned int err; + unsigned int found = geo->in_height * vou_scale_v_num[i] / + vou_scale_v_den[i]; + + if (found > img_height_max) + /* scales increase */ + break; + + err = abs(found - geo->output.height); + if (err < best_err) { + best_err = err; + idx_v = i; + best = found; + } + if (!err) + break; + } + + geo->output.height = best; + geo->scale_idx_v = idx_v; + if (geo->output.top + best > height_max) + geo->output.top = height_max - best; + + pr_debug("%s(): H %u * %u/%u = %u\n", __func__, geo->in_height, + vou_scale_v_num[idx_v], vou_scale_v_den[idx_v], best); +} + +static int sh_vou_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + struct v4l2_pix_format *pix = &fmt->fmt.pix; + unsigned int img_height_max; + int pix_idx; + + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + + pix->field = V4L2_FIELD_INTERLACED; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + pix->ycbcr_enc = pix->quantization = 0; + + for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++) + if (vou_fmt[pix_idx].pfmt == pix->pixelformat) + break; + + if (pix_idx == ARRAY_SIZE(vou_fmt)) + return -EINVAL; + + if (vou_dev->std & V4L2_STD_525_60) + img_height_max = 480; + else + img_height_max = 576; + + v4l_bound_align_image(&pix->width, + VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 2, + &pix->height, + VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); + pix->bytesperline = pix->width * vou_fmt[pix_idx].bpl; + pix->sizeimage = pix->height * ((pix->width * vou_fmt[pix_idx].bpp) >> 3); + + return 0; +} + +static int sh_vou_set_fmt_vid_out(struct sh_vou_device *vou_dev, + struct v4l2_pix_format *pix) +{ + unsigned int img_height_max; + struct sh_vou_geometry geo; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + /* Revisit: is this the correct code? */ + .format.code = MEDIA_BUS_FMT_YUYV8_2X8, + .format.field = V4L2_FIELD_INTERLACED, + .format.colorspace = V4L2_COLORSPACE_SMPTE170M, + }; + struct v4l2_mbus_framefmt *mbfmt = &format.format; + int pix_idx; + int ret; + + if (vb2_is_busy(&vou_dev->queue)) + return -EBUSY; + + for (pix_idx = 0; pix_idx < ARRAY_SIZE(vou_fmt); pix_idx++) + if (vou_fmt[pix_idx].pfmt == pix->pixelformat) + break; + + geo.in_width = pix->width; + geo.in_height = pix->height; + geo.output = vou_dev->rect; + + vou_adjust_output(&geo, vou_dev->std); + + mbfmt->width = geo.output.width; + mbfmt->height = geo.output.height; + ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, + set_fmt, NULL, &format); + /* Must be implemented, so, don't check for -ENOIOCTLCMD */ + if (ret < 0) + return ret; + + dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u -> %ux%u\n", __func__, + geo.output.width, geo.output.height, mbfmt->width, mbfmt->height); + + if (vou_dev->std & V4L2_STD_525_60) + img_height_max = 480; + else + img_height_max = 576; + + /* Sanity checks */ + if ((unsigned)mbfmt->width > VOU_MAX_IMAGE_WIDTH || + (unsigned)mbfmt->height > img_height_max || + mbfmt->code != MEDIA_BUS_FMT_YUYV8_2X8) + return -EIO; + + if (mbfmt->width != geo.output.width || + mbfmt->height != geo.output.height) { + geo.output.width = mbfmt->width; + geo.output.height = mbfmt->height; + + vou_adjust_input(&geo, vou_dev->std); + } + + /* We tried to preserve output rectangle, but it could have changed */ + vou_dev->rect = geo.output; + pix->width = geo.in_width; + pix->height = geo.in_height; + + dev_dbg(vou_dev->v4l2_dev.dev, "%s(): %ux%u\n", __func__, + pix->width, pix->height); + + vou_dev->pix_idx = pix_idx; + + vou_dev->pix = *pix; + + sh_vou_configure_geometry(vou_dev, pix_idx, + geo.scale_idx_h, geo.scale_idx_v); + + return 0; +} + +static int sh_vou_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + int ret = sh_vou_try_fmt_vid_out(file, priv, fmt); + + if (ret) + return ret; + return sh_vou_set_fmt_vid_out(vou_dev, &fmt->fmt.pix); +} + +static int sh_vou_enum_output(struct file *file, void *fh, + struct v4l2_output *a) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + + if (a->index) + return -EINVAL; + strscpy(a->name, "Video Out", sizeof(a->name)); + a->type = V4L2_OUTPUT_TYPE_ANALOG; + a->std = vou_dev->vdev.tvnorms; + return 0; +} + +static int sh_vou_g_output(struct file *file, void *fh, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int sh_vou_s_output(struct file *file, void *fh, unsigned int i) +{ + return i ? -EINVAL : 0; +} + +static u32 sh_vou_ntsc_mode(enum sh_vou_bus_fmt bus_fmt) +{ + switch (bus_fmt) { + default: + pr_warn("%s(): Invalid bus-format code %d, using default 8-bit\n", + __func__, bus_fmt); + fallthrough; + case SH_VOU_BUS_8BIT: + return 1; + case SH_VOU_BUS_16BIT: + return 0; + case SH_VOU_BUS_BT656: + return 3; + } +} + +static int sh_vou_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + int ret; + + dev_dbg(vou_dev->v4l2_dev.dev, "%s(): 0x%llx\n", __func__, std_id); + + if (std_id == vou_dev->std) + return 0; + + if (vb2_is_busy(&vou_dev->queue)) + return -EBUSY; + + ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, video, + s_std_output, std_id); + /* Shall we continue, if the subdev doesn't support .s_std_output()? */ + if (ret < 0 && ret != -ENOIOCTLCMD) + return ret; + + vou_dev->rect.top = vou_dev->rect.left = 0; + vou_dev->rect.width = VOU_MAX_IMAGE_WIDTH; + if (std_id & V4L2_STD_525_60) { + sh_vou_reg_ab_set(vou_dev, VOUCR, + sh_vou_ntsc_mode(vou_dev->pdata->bus_fmt) << 29, 7 << 29); + vou_dev->rect.height = 480; + } else { + sh_vou_reg_ab_set(vou_dev, VOUCR, 5 << 29, 7 << 29); + vou_dev->rect.height = 576; + } + + vou_dev->pix.width = vou_dev->rect.width; + vou_dev->pix.height = vou_dev->rect.height; + vou_dev->pix.bytesperline = + vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpl; + vou_dev->pix.sizeimage = vou_dev->pix.height * + ((vou_dev->pix.width * vou_fmt[vou_dev->pix_idx].bpp) >> 3); + vou_dev->std = std_id; + sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix); + + return 0; +} + +static int sh_vou_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + + dev_dbg(vou_dev->v4l2_dev.dev, "%s()\n", __func__); + + *std = vou_dev->std; + + return 0; +} + +static int sh_vou_log_status(struct file *file, void *priv) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + + pr_info("VOUER: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUER)); + pr_info("VOUCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUCR)); + pr_info("VOUSTR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSTR)); + pr_info("VOUVCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVCR)); + pr_info("VOUISR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUISR)); + pr_info("VOUBCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUBCR)); + pr_info("VOUDPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDPR)); + pr_info("VOUDSR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDSR)); + pr_info("VOUVPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUVPR)); + pr_info("VOUIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUIR)); + pr_info("VOUSRR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSRR)); + pr_info("VOUMSR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUMSR)); + pr_info("VOUHIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUHIR)); + pr_info("VOUDFR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUDFR)); + pr_info("VOUAD1R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD1R)); + pr_info("VOUAD2R: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAD2R)); + pr_info("VOUAIR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUAIR)); + pr_info("VOUSWR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOUSWR)); + pr_info("VOURCR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURCR)); + pr_info("VOURPR: 0x%08x\n", sh_vou_reg_a_read(vou_dev, VOURPR)); + return 0; +} + +static int sh_vou_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE: + sel->r = vou_dev->rect; + break; + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = VOU_MAX_IMAGE_WIDTH; + if (vou_dev->std & V4L2_STD_525_60) + sel->r.height = 480; + else + sel->r.height = 576; + break; + default: + return -EINVAL; + } + return 0; +} + +/* Assume a dull encoder, do all the work ourselves. */ +static int sh_vou_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct v4l2_rect *rect = &sel->r; + struct sh_vou_device *vou_dev = video_drvdata(file); + struct v4l2_subdev_selection sd_sel = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .target = V4L2_SEL_TGT_COMPOSE, + }; + struct v4l2_pix_format *pix = &vou_dev->pix; + struct sh_vou_geometry geo; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + /* Revisit: is this the correct code? */ + .format.code = MEDIA_BUS_FMT_YUYV8_2X8, + .format.field = V4L2_FIELD_INTERLACED, + .format.colorspace = V4L2_COLORSPACE_SMPTE170M, + }; + unsigned int img_height_max; + int ret; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + sel->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + + if (vb2_is_busy(&vou_dev->queue)) + return -EBUSY; + + if (vou_dev->std & V4L2_STD_525_60) + img_height_max = 480; + else + img_height_max = 576; + + v4l_bound_align_image(&rect->width, + VOU_MIN_IMAGE_WIDTH, VOU_MAX_IMAGE_WIDTH, 1, + &rect->height, + VOU_MIN_IMAGE_HEIGHT, img_height_max, 1, 0); + + if (rect->width + rect->left > VOU_MAX_IMAGE_WIDTH) + rect->left = VOU_MAX_IMAGE_WIDTH - rect->width; + + if (rect->height + rect->top > img_height_max) + rect->top = img_height_max - rect->height; + + geo.output = *rect; + geo.in_width = pix->width; + geo.in_height = pix->height; + + /* Configure the encoder one-to-one, position at 0, ignore errors */ + sd_sel.r.width = geo.output.width; + sd_sel.r.height = geo.output.height; + /* + * We first issue a S_SELECTION, so that the subsequent S_FMT delivers the + * final encoder configuration. + */ + v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, + set_selection, NULL, &sd_sel); + format.format.width = geo.output.width; + format.format.height = geo.output.height; + ret = v4l2_device_call_until_err(&vou_dev->v4l2_dev, 0, pad, + set_fmt, NULL, &format); + /* Must be implemented, so, don't check for -ENOIOCTLCMD */ + if (ret < 0) + return ret; + + /* Sanity checks */ + if ((unsigned)format.format.width > VOU_MAX_IMAGE_WIDTH || + (unsigned)format.format.height > img_height_max || + format.format.code != MEDIA_BUS_FMT_YUYV8_2X8) + return -EIO; + + geo.output.width = format.format.width; + geo.output.height = format.format.height; + + /* + * No down-scaling. According to the API, current call has precedence: + * https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/crop.html#cropping-structures + */ + vou_adjust_input(&geo, vou_dev->std); + + /* We tried to preserve output rectangle, but it could have changed */ + vou_dev->rect = geo.output; + pix->width = geo.in_width; + pix->height = geo.in_height; + + sh_vou_configure_geometry(vou_dev, vou_dev->pix_idx, + geo.scale_idx_h, geo.scale_idx_v); + + return 0; +} + +static irqreturn_t sh_vou_isr(int irq, void *dev_id) +{ + struct sh_vou_device *vou_dev = dev_id; + static unsigned long j; + struct sh_vou_buffer *vb; + static int cnt; + u32 irq_status = sh_vou_reg_a_read(vou_dev, VOUIR), masked; + u32 vou_status = sh_vou_reg_a_read(vou_dev, VOUSTR); + + if (!(irq_status & 0x300)) { + if (printk_timed_ratelimit(&j, 500)) + dev_warn(vou_dev->v4l2_dev.dev, "IRQ status 0x%x!\n", + irq_status); + return IRQ_NONE; + } + + spin_lock(&vou_dev->lock); + if (!vou_dev->active || list_empty(&vou_dev->buf_list)) { + if (printk_timed_ratelimit(&j, 500)) + dev_warn(vou_dev->v4l2_dev.dev, + "IRQ without active buffer: %x!\n", irq_status); + /* Just ack: buf_release will disable further interrupts */ + sh_vou_reg_a_set(vou_dev, VOUIR, 0, 0x300); + spin_unlock(&vou_dev->lock); + return IRQ_HANDLED; + } + + masked = ~(0x300 & irq_status) & irq_status & 0x30304; + dev_dbg(vou_dev->v4l2_dev.dev, + "IRQ status 0x%x -> 0x%x, VOU status 0x%x, cnt %d\n", + irq_status, masked, vou_status, cnt); + + cnt++; + /* side = vou_status & 0x10000; */ + + /* Clear only set interrupts */ + sh_vou_reg_a_write(vou_dev, VOUIR, masked); + + vb = vou_dev->active; + if (list_is_singular(&vb->list)) { + /* Keep cycling while no next buffer is available */ + sh_vou_schedule_next(vou_dev, &vb->vb); + spin_unlock(&vou_dev->lock); + return IRQ_HANDLED; + } + + list_del(&vb->list); + + vb->vb.vb2_buf.timestamp = ktime_get_ns(); + vb->vb.sequence = vou_dev->sequence++; + vb->vb.field = V4L2_FIELD_INTERLACED; + vb2_buffer_done(&vb->vb.vb2_buf, VB2_BUF_STATE_DONE); + + vou_dev->active = list_entry(vou_dev->buf_list.next, + struct sh_vou_buffer, list); + + if (list_is_singular(&vou_dev->buf_list)) { + /* Keep cycling while no next buffer is available */ + sh_vou_schedule_next(vou_dev, &vou_dev->active->vb); + } else { + struct sh_vou_buffer *new = list_entry(vou_dev->active->list.next, + struct sh_vou_buffer, list); + sh_vou_schedule_next(vou_dev, &new->vb); + } + + spin_unlock(&vou_dev->lock); + + return IRQ_HANDLED; +} + +static int sh_vou_hw_init(struct sh_vou_device *vou_dev) +{ + struct sh_vou_pdata *pdata = vou_dev->pdata; + u32 voucr = sh_vou_ntsc_mode(pdata->bus_fmt) << 29; + int i = 100; + + /* Disable all IRQs */ + sh_vou_reg_a_write(vou_dev, VOUIR, 0); + + /* Reset VOU interfaces - registers unaffected */ + sh_vou_reg_a_write(vou_dev, VOUSRR, 0x101); + while (--i && (sh_vou_reg_a_read(vou_dev, VOUSRR) & 0x101)) + udelay(1); + + if (!i) + return -ETIMEDOUT; + + dev_dbg(vou_dev->v4l2_dev.dev, "Reset took %dus\n", 100 - i); + + if (pdata->flags & SH_VOU_PCLK_FALLING) + voucr |= 1 << 28; + if (pdata->flags & SH_VOU_HSYNC_LOW) + voucr |= 1 << 27; + if (pdata->flags & SH_VOU_VSYNC_LOW) + voucr |= 1 << 26; + sh_vou_reg_ab_set(vou_dev, VOUCR, voucr, 0xfc000000); + + /* Manual register side switching at first */ + sh_vou_reg_a_write(vou_dev, VOURCR, 4); + /* Default - fixed HSYNC length, can be made configurable is required */ + sh_vou_reg_ab_write(vou_dev, VOUMSR, 0x800000); + + sh_vou_set_fmt_vid_out(vou_dev, &vou_dev->pix); + + return 0; +} + +/* File operations */ +static int sh_vou_open(struct file *file) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + int err; + + if (mutex_lock_interruptible(&vou_dev->fop_lock)) + return -ERESTARTSYS; + + err = v4l2_fh_open(file); + if (err) + goto done_open; + if (v4l2_fh_is_singular_file(file) && + vou_dev->status == SH_VOU_INITIALISING) { + /* First open */ + err = pm_runtime_resume_and_get(vou_dev->v4l2_dev.dev); + if (err < 0) { + v4l2_fh_release(file); + goto done_open; + } + err = sh_vou_hw_init(vou_dev); + if (err < 0) { + pm_runtime_put(vou_dev->v4l2_dev.dev); + v4l2_fh_release(file); + } else { + vou_dev->status = SH_VOU_IDLE; + } + } +done_open: + mutex_unlock(&vou_dev->fop_lock); + return err; +} + +static int sh_vou_release(struct file *file) +{ + struct sh_vou_device *vou_dev = video_drvdata(file); + bool is_last; + + mutex_lock(&vou_dev->fop_lock); + is_last = v4l2_fh_is_singular_file(file); + _vb2_fop_release(file, NULL); + if (is_last) { + /* Last close */ + vou_dev->status = SH_VOU_INITIALISING; + sh_vou_reg_a_set(vou_dev, VOUER, 0, 0x101); + pm_runtime_put(vou_dev->v4l2_dev.dev); + } + mutex_unlock(&vou_dev->fop_lock); + return 0; +} + +/* sh_vou display ioctl operations */ +static const struct v4l2_ioctl_ops sh_vou_ioctl_ops = { + .vidioc_querycap = sh_vou_querycap, + .vidioc_enum_fmt_vid_out = sh_vou_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = sh_vou_g_fmt_vid_out, + .vidioc_s_fmt_vid_out = sh_vou_s_fmt_vid_out, + .vidioc_try_fmt_vid_out = sh_vou_try_fmt_vid_out, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_g_output = sh_vou_g_output, + .vidioc_s_output = sh_vou_s_output, + .vidioc_enum_output = sh_vou_enum_output, + .vidioc_s_std = sh_vou_s_std, + .vidioc_g_std = sh_vou_g_std, + .vidioc_g_selection = sh_vou_g_selection, + .vidioc_s_selection = sh_vou_s_selection, + .vidioc_log_status = sh_vou_log_status, +}; + +static const struct v4l2_file_operations sh_vou_fops = { + .owner = THIS_MODULE, + .open = sh_vou_open, + .release = sh_vou_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, + .write = vb2_fop_write, +}; + +static const struct video_device sh_vou_video_template = { + .name = "sh_vou", + .fops = &sh_vou_fops, + .ioctl_ops = &sh_vou_ioctl_ops, + .tvnorms = V4L2_STD_525_60, /* PAL only supported in 8-bit non-bt656 mode */ + .vfl_dir = VFL_DIR_TX, + .device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE | + V4L2_CAP_STREAMING, +}; + +static int sh_vou_probe(struct platform_device *pdev) +{ + struct sh_vou_pdata *vou_pdata = pdev->dev.platform_data; + struct v4l2_rect *rect; + struct v4l2_pix_format *pix; + struct i2c_adapter *i2c_adap; + struct video_device *vdev; + struct sh_vou_device *vou_dev; + struct resource *reg_res; + struct v4l2_subdev *subdev; + struct vb2_queue *q; + int irq, ret; + + reg_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq = platform_get_irq(pdev, 0); + + if (!vou_pdata || !reg_res || irq <= 0) { + dev_err(&pdev->dev, "Insufficient VOU platform information.\n"); + return -ENODEV; + } + + vou_dev = devm_kzalloc(&pdev->dev, sizeof(*vou_dev), GFP_KERNEL); + if (!vou_dev) + return -ENOMEM; + + INIT_LIST_HEAD(&vou_dev->buf_list); + spin_lock_init(&vou_dev->lock); + mutex_init(&vou_dev->fop_lock); + vou_dev->pdata = vou_pdata; + vou_dev->status = SH_VOU_INITIALISING; + vou_dev->pix_idx = 1; + + rect = &vou_dev->rect; + pix = &vou_dev->pix; + + /* Fill in defaults */ + vou_dev->std = V4L2_STD_NTSC_M; + rect->left = 0; + rect->top = 0; + rect->width = VOU_MAX_IMAGE_WIDTH; + rect->height = 480; + pix->width = VOU_MAX_IMAGE_WIDTH; + pix->height = 480; + pix->pixelformat = V4L2_PIX_FMT_NV16; + pix->field = V4L2_FIELD_INTERLACED; + pix->bytesperline = VOU_MAX_IMAGE_WIDTH; + pix->sizeimage = VOU_MAX_IMAGE_WIDTH * 2 * 480; + pix->colorspace = V4L2_COLORSPACE_SMPTE170M; + + vou_dev->base = devm_ioremap_resource(&pdev->dev, reg_res); + if (IS_ERR(vou_dev->base)) + return PTR_ERR(vou_dev->base); + + ret = devm_request_irq(&pdev->dev, irq, sh_vou_isr, 0, "vou", vou_dev); + if (ret < 0) + return ret; + + ret = v4l2_device_register(&pdev->dev, &vou_dev->v4l2_dev); + if (ret < 0) { + dev_err(&pdev->dev, "Error registering v4l2 device\n"); + return ret; + } + + vdev = &vou_dev->vdev; + *vdev = sh_vou_video_template; + if (vou_pdata->bus_fmt == SH_VOU_BUS_8BIT) + vdev->tvnorms |= V4L2_STD_PAL; + vdev->v4l2_dev = &vou_dev->v4l2_dev; + vdev->release = video_device_release_empty; + vdev->lock = &vou_dev->fop_lock; + + video_set_drvdata(vdev, vou_dev); + + /* Initialize the vb2 queue */ + q = &vou_dev->queue; + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_WRITE; + q->drv_priv = vou_dev; + q->buf_struct_size = sizeof(struct sh_vou_buffer); + q->ops = &sh_vou_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->lock = &vou_dev->fop_lock; + q->dev = &pdev->dev; + ret = vb2_queue_init(q); + if (ret) + goto ei2cgadap; + + vdev->queue = q; + INIT_LIST_HEAD(&vou_dev->buf_list); + + pm_runtime_enable(&pdev->dev); + pm_runtime_resume(&pdev->dev); + + i2c_adap = i2c_get_adapter(vou_pdata->i2c_adap); + if (!i2c_adap) { + ret = -ENODEV; + goto ei2cgadap; + } + + ret = sh_vou_hw_init(vou_dev); + if (ret < 0) + goto ereset; + + subdev = v4l2_i2c_new_subdev_board(&vou_dev->v4l2_dev, i2c_adap, + vou_pdata->board_info, NULL); + if (!subdev) { + ret = -ENOMEM; + goto ei2cnd; + } + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) + goto evregdev; + + return 0; + +evregdev: +ei2cnd: +ereset: + i2c_put_adapter(i2c_adap); +ei2cgadap: + pm_runtime_disable(&pdev->dev); + v4l2_device_unregister(&vou_dev->v4l2_dev); + return ret; +} + +static int sh_vou_remove(struct platform_device *pdev) +{ + struct v4l2_device *v4l2_dev = platform_get_drvdata(pdev); + struct sh_vou_device *vou_dev = container_of(v4l2_dev, + struct sh_vou_device, v4l2_dev); + struct v4l2_subdev *sd = list_entry(v4l2_dev->subdevs.next, + struct v4l2_subdev, list); + struct i2c_client *client = v4l2_get_subdevdata(sd); + + pm_runtime_disable(&pdev->dev); + video_unregister_device(&vou_dev->vdev); + i2c_put_adapter(client->adapter); + v4l2_device_unregister(&vou_dev->v4l2_dev); + return 0; +} + +static struct platform_driver sh_vou = { + .remove = sh_vou_remove, + .driver = { + .name = "sh-vou", + }, +}; + +module_platform_driver_probe(sh_vou, sh_vou_probe); + +MODULE_DESCRIPTION("SuperH VOU driver"); +MODULE_AUTHOR("Guennadi Liakhovetski "); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1.0"); +MODULE_ALIAS("platform:sh-vou"); diff --git a/drivers/media/platform/renesas/vsp1/Makefile b/drivers/media/platform/renesas/vsp1/Makefile new file mode 100644 index 000000000..4bb4dcbef --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +vsp1-y := vsp1_drv.o vsp1_entity.o vsp1_pipe.o +vsp1-y += vsp1_dl.o vsp1_drm.o vsp1_video.o +vsp1-y += vsp1_rpf.o vsp1_rwpf.o vsp1_wpf.o +vsp1-y += vsp1_clu.o vsp1_hsit.o vsp1_lut.o +vsp1-y += vsp1_brx.o vsp1_sru.o vsp1_uds.o +vsp1-y += vsp1_hgo.o vsp1_hgt.o vsp1_histo.o +vsp1-y += vsp1_lif.o vsp1_uif.o + +obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1.o diff --git a/drivers/media/platform/renesas/vsp1/vsp1.h b/drivers/media/platform/renesas/vsp1/vsp1.h new file mode 100644 index 000000000..2f6f0c6ae --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1.h @@ -0,0 +1,127 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1.h -- R-Car VSP1 Driver + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_H__ +#define __VSP1_H__ + +#include +#include +#include + +#include +#include +#include + +#include "vsp1_regs.h" + +struct clk; +struct device; +struct rcar_fcp_device; +struct reset_control; + +struct vsp1_drm; +struct vsp1_entity; +struct vsp1_platform_data; +struct vsp1_brx; +struct vsp1_clu; +struct vsp1_hgo; +struct vsp1_hgt; +struct vsp1_hsit; +struct vsp1_lif; +struct vsp1_lut; +struct vsp1_rwpf; +struct vsp1_sru; +struct vsp1_uds; +struct vsp1_uif; + +#define VSP1_MAX_LIF 2 +#define VSP1_MAX_RPF 5 +#define VSP1_MAX_UDS 3 +#define VSP1_MAX_UIF 2 +#define VSP1_MAX_WPF 4 + +#define VSP1_HAS_LUT BIT(1) +#define VSP1_HAS_SRU BIT(2) +#define VSP1_HAS_BRU BIT(3) +#define VSP1_HAS_CLU BIT(4) +#define VSP1_HAS_WPF_VFLIP BIT(5) +#define VSP1_HAS_WPF_HFLIP BIT(6) +#define VSP1_HAS_HGO BIT(7) +#define VSP1_HAS_HGT BIT(8) +#define VSP1_HAS_BRS BIT(9) +#define VSP1_HAS_EXT_DL BIT(10) +#define VSP1_HAS_NON_ZERO_LBA BIT(11) + +struct vsp1_device_info { + u32 version; + const char *model; + unsigned int gen; + unsigned int features; + unsigned int lif_count; + unsigned int rpf_count; + unsigned int uds_count; + unsigned int uif_count; + unsigned int wpf_count; + unsigned int num_bru_inputs; + u8 soc; + bool uapi; +}; + +#define vsp1_feature(vsp1, f) ((vsp1)->info->features & (f)) + +struct vsp1_device { + struct device *dev; + const struct vsp1_device_info *info; + u32 version; + + void __iomem *mmio; + struct rcar_fcp_device *fcp; + struct device *bus_master; + struct reset_control *rstc; + + struct vsp1_brx *brs; + struct vsp1_brx *bru; + struct vsp1_clu *clu; + struct vsp1_hgo *hgo; + struct vsp1_hgt *hgt; + struct vsp1_hsit *hsi; + struct vsp1_hsit *hst; + struct vsp1_lif *lif[VSP1_MAX_LIF]; + struct vsp1_lut *lut; + struct vsp1_rwpf *rpf[VSP1_MAX_RPF]; + struct vsp1_sru *sru; + struct vsp1_uds *uds[VSP1_MAX_UDS]; + struct vsp1_uif *uif[VSP1_MAX_UIF]; + struct vsp1_rwpf *wpf[VSP1_MAX_WPF]; + + struct list_head entities; + struct list_head videos; + + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct media_entity_operations media_ops; + + struct vsp1_drm *drm; +}; + +int vsp1_device_get(struct vsp1_device *vsp1); +void vsp1_device_put(struct vsp1_device *vsp1); + +int vsp1_reset_wpf(struct vsp1_device *vsp1, unsigned int index); + +static inline u32 vsp1_read(struct vsp1_device *vsp1, u32 reg) +{ + return ioread32(vsp1->mmio + reg); +} + +static inline void vsp1_write(struct vsp1_device *vsp1, u32 reg, u32 data) +{ + iowrite32(data, vsp1->mmio + reg); +} + +#endif /* __VSP1_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_brx.c b/drivers/media/platform/renesas/vsp1/vsp1_brx.c new file mode 100644 index 000000000..89385b4ca --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_brx.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_brx.c -- R-Car VSP1 Blend ROP Unit (BRU and BRS) + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_brx.h" +#include "vsp1_dl.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define BRX_MIN_SIZE 1U +#define BRX_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_brx_write(struct vsp1_brx *brx, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, brx->base + reg, data); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +static int brx_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_brx *brx = + container_of(ctrl->handler, struct vsp1_brx, ctrls); + + switch (ctrl->id) { + case V4L2_CID_BG_COLOR: + brx->bgcolor = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops brx_ctrl_ops = { + .s_ctrl = brx_s_ctrl, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +/* + * The BRx can't perform format conversion, all sink and source formats must be + * identical. We pick the format on the first sink pad (pad 0) and propagate it + * to all other pads. + */ + +static int brx_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, + }; + + return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, codes, + ARRAY_SIZE(codes)); +} + +static int brx_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->index) + return -EINVAL; + + if (fse->code != MEDIA_BUS_FMT_ARGB8888_1X32 && + fse->code != MEDIA_BUS_FMT_AYUV8_1X32) + return -EINVAL; + + fse->min_width = BRX_MIN_SIZE; + fse->max_width = BRX_MAX_SIZE; + fse->min_height = BRX_MIN_SIZE; + fse->max_height = BRX_MAX_SIZE; + + return 0; +} + +static struct v4l2_rect *brx_get_compose(struct vsp1_brx *brx, + struct v4l2_subdev_state *sd_state, + unsigned int pad) +{ + return v4l2_subdev_get_try_compose(&brx->entity.subdev, sd_state, pad); +} + +static void brx_try_format(struct vsp1_brx *brx, + struct v4l2_subdev_state *sd_state, + unsigned int pad, struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_mbus_framefmt *format; + + switch (pad) { + case BRX_PAD_SINK(0): + /* Default to YUV if the requested format is not supported. */ + if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 && + fmt->code != MEDIA_BUS_FMT_AYUV8_1X32) + fmt->code = MEDIA_BUS_FMT_AYUV8_1X32; + break; + + default: + /* The BRx can't perform format conversion. */ + format = vsp1_entity_get_pad_format(&brx->entity, sd_state, + BRX_PAD_SINK(0)); + fmt->code = format->code; + break; + } + + fmt->width = clamp(fmt->width, BRX_MIN_SIZE, BRX_MAX_SIZE); + fmt->height = clamp(fmt->height, BRX_MIN_SIZE, BRX_MAX_SIZE); + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +static int brx_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_brx *brx = to_brx(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + mutex_lock(&brx->entity.lock); + + config = vsp1_entity_get_pad_config(&brx->entity, sd_state, + fmt->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + brx_try_format(brx, config, fmt->pad, &fmt->format); + + format = vsp1_entity_get_pad_format(&brx->entity, config, fmt->pad); + *format = fmt->format; + + /* Reset the compose rectangle. */ + if (fmt->pad != brx->entity.source_pad) { + struct v4l2_rect *compose; + + compose = brx_get_compose(brx, config, fmt->pad); + compose->left = 0; + compose->top = 0; + compose->width = format->width; + compose->height = format->height; + } + + /* Propagate the format code to all pads. */ + if (fmt->pad == BRX_PAD_SINK(0)) { + unsigned int i; + + for (i = 0; i <= brx->entity.source_pad; ++i) { + format = vsp1_entity_get_pad_format(&brx->entity, + config, i); + format->code = fmt->format.code; + } + } + +done: + mutex_unlock(&brx->entity.lock); + return ret; +} + +static int brx_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_brx *brx = to_brx(subdev); + struct v4l2_subdev_state *config; + + if (sel->pad == brx->entity.source_pad) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = BRX_MAX_SIZE; + sel->r.height = BRX_MAX_SIZE; + return 0; + + case V4L2_SEL_TGT_COMPOSE: + config = vsp1_entity_get_pad_config(&brx->entity, sd_state, + sel->which); + if (!config) + return -EINVAL; + + mutex_lock(&brx->entity.lock); + sel->r = *brx_get_compose(brx, config, sel->pad); + mutex_unlock(&brx->entity.lock); + return 0; + + default: + return -EINVAL; + } +} + +static int brx_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_brx *brx = to_brx(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *compose; + int ret = 0; + + if (sel->pad == brx->entity.source_pad) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + + mutex_lock(&brx->entity.lock); + + config = vsp1_entity_get_pad_config(&brx->entity, sd_state, + sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + /* + * The compose rectangle top left corner must be inside the output + * frame. + */ + format = vsp1_entity_get_pad_format(&brx->entity, config, + brx->entity.source_pad); + sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1); + sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1); + + /* + * Scaling isn't supported, the compose rectangle size must be identical + * to the sink format size. + */ + format = vsp1_entity_get_pad_format(&brx->entity, config, sel->pad); + sel->r.width = format->width; + sel->r.height = format->height; + + compose = brx_get_compose(brx, config, sel->pad); + *compose = sel->r; + +done: + mutex_unlock(&brx->entity.lock); + return ret; +} + +static const struct v4l2_subdev_pad_ops brx_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = brx_enum_mbus_code, + .enum_frame_size = brx_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = brx_set_format, + .get_selection = brx_get_selection, + .set_selection = brx_set_selection, +}; + +static const struct v4l2_subdev_ops brx_ops = { + .pad = &brx_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void brx_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_brx *brx = to_brx(&entity->subdev); + struct v4l2_mbus_framefmt *format; + unsigned int flags; + unsigned int i; + + format = vsp1_entity_get_pad_format(&brx->entity, brx->entity.config, + brx->entity.source_pad); + + /* + * The hardware is extremely flexible but we have no userspace API to + * expose all the parameters, nor is it clear whether we would have use + * cases for all the supported modes. Let's just hardcode the parameters + * to sane default values for now. + */ + + /* + * Disable dithering and enable color data normalization unless the + * format at the pipeline output is premultiplied. + */ + flags = pipe->output ? pipe->output->format.flags : 0; + vsp1_brx_write(brx, dlb, VI6_BRU_INCTRL, + flags & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA ? + 0 : VI6_BRU_INCTRL_NRM); + + /* + * Set the background position to cover the whole output image and + * configure its color. + */ + vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_SIZE, + (format->width << VI6_BRU_VIRRPF_SIZE_HSIZE_SHIFT) | + (format->height << VI6_BRU_VIRRPF_SIZE_VSIZE_SHIFT)); + vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_LOC, 0); + + vsp1_brx_write(brx, dlb, VI6_BRU_VIRRPF_COL, brx->bgcolor | + (0xff << VI6_BRU_VIRRPF_COL_A_SHIFT)); + + /* + * Route BRU input 1 as SRC input to the ROP unit and configure the ROP + * unit with a NOP operation to make BRU input 1 available as the + * Blend/ROP unit B SRC input. Only needed for BRU, the BRS has no ROP + * unit. + */ + if (entity->type == VSP1_ENTITY_BRU) + vsp1_brx_write(brx, dlb, VI6_BRU_ROP, + VI6_BRU_ROP_DSTSEL_BRUIN(1) | + VI6_BRU_ROP_CROP(VI6_ROP_NOP) | + VI6_BRU_ROP_AROP(VI6_ROP_NOP)); + + for (i = 0; i < brx->entity.source_pad; ++i) { + bool premultiplied = false; + u32 ctrl = 0; + + /* + * Configure all Blend/ROP units corresponding to an enabled BRx + * input for alpha blending. Blend/ROP units corresponding to + * disabled BRx inputs are used in ROP NOP mode to ignore the + * SRC input. + */ + if (brx->inputs[i].rpf) { + ctrl |= VI6_BRU_CTRL_RBC; + + premultiplied = brx->inputs[i].rpf->format.flags + & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA; + } else { + ctrl |= VI6_BRU_CTRL_CROP(VI6_ROP_NOP) + | VI6_BRU_CTRL_AROP(VI6_ROP_NOP); + } + + /* + * Select the virtual RPF as the Blend/ROP unit A DST input to + * serve as a background color. + */ + if (i == 0) + ctrl |= VI6_BRU_CTRL_DSTSEL_VRPF; + + /* + * Route inputs 0 to 3 as SRC inputs to Blend/ROP units A to D + * in that order. In the BRU the Blend/ROP unit B SRC is + * hardwired to the ROP unit output, the corresponding register + * bits must be set to 0. The BRS has no ROP unit and doesn't + * need any special processing. + */ + if (!(entity->type == VSP1_ENTITY_BRU && i == 1)) + ctrl |= VI6_BRU_CTRL_SRCSEL_BRUIN(i); + + vsp1_brx_write(brx, dlb, VI6_BRU_CTRL(i), ctrl); + + /* + * Hardcode the blending formula to + * + * DSTc = DSTc * (1 - SRCa) + SRCc * SRCa + * DSTa = DSTa * (1 - SRCa) + SRCa + * + * when the SRC input isn't premultiplied, and to + * + * DSTc = DSTc * (1 - SRCa) + SRCc + * DSTa = DSTa * (1 - SRCa) + SRCa + * + * otherwise. + */ + vsp1_brx_write(brx, dlb, VI6_BRU_BLD(i), + VI6_BRU_BLD_CCMDX_255_SRC_A | + (premultiplied ? VI6_BRU_BLD_CCMDY_COEFY : + VI6_BRU_BLD_CCMDY_SRC_A) | + VI6_BRU_BLD_ACMDX_255_SRC_A | + VI6_BRU_BLD_ACMDY_COEFY | + (0xff << VI6_BRU_BLD_COEFY_SHIFT)); + } +} + +static const struct vsp1_entity_operations brx_entity_ops = { + .configure_stream = brx_configure_stream, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_brx *vsp1_brx_create(struct vsp1_device *vsp1, + enum vsp1_entity_type type) +{ + struct vsp1_brx *brx; + unsigned int num_pads; + const char *name; + int ret; + + brx = devm_kzalloc(vsp1->dev, sizeof(*brx), GFP_KERNEL); + if (brx == NULL) + return ERR_PTR(-ENOMEM); + + brx->base = type == VSP1_ENTITY_BRU ? VI6_BRU_BASE : VI6_BRS_BASE; + brx->entity.ops = &brx_entity_ops; + brx->entity.type = type; + + if (type == VSP1_ENTITY_BRU) { + num_pads = vsp1->info->num_bru_inputs + 1; + name = "bru"; + } else { + num_pads = 3; + name = "brs"; + } + + ret = vsp1_entity_init(vsp1, &brx->entity, name, num_pads, &brx_ops, + MEDIA_ENT_F_PROC_VIDEO_COMPOSER); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&brx->ctrls, 1); + v4l2_ctrl_new_std(&brx->ctrls, &brx_ctrl_ops, V4L2_CID_BG_COLOR, + 0, 0xffffff, 1, 0); + + brx->bgcolor = 0; + + brx->entity.subdev.ctrl_handler = &brx->ctrls; + + if (brx->ctrls.error) { + dev_err(vsp1->dev, "%s: failed to initialize controls\n", name); + ret = brx->ctrls.error; + vsp1_entity_destroy(&brx->entity); + return ERR_PTR(ret); + } + + return brx; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_brx.h b/drivers/media/platform/renesas/vsp1/vsp1_brx.h new file mode 100644 index 000000000..6abbb8c33 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_brx.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_brx.h -- R-Car VSP1 Blend ROP Unit (BRU and BRS) + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_BRX_H__ +#define __VSP1_BRX_H__ + +#include +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; +struct vsp1_rwpf; + +#define BRX_PAD_SINK(n) (n) + +struct vsp1_brx { + struct vsp1_entity entity; + unsigned int base; + + struct v4l2_ctrl_handler ctrls; + + struct { + struct vsp1_rwpf *rpf; + } inputs[VSP1_MAX_RPF]; + + u32 bgcolor; +}; + +static inline struct vsp1_brx *to_brx(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_brx, entity.subdev); +} + +struct vsp1_brx *vsp1_brx_create(struct vsp1_device *vsp1, + enum vsp1_entity_type type); + +#endif /* __VSP1_BRX_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_clu.c b/drivers/media/platform/renesas/vsp1/vsp1_clu.c new file mode 100644 index 000000000..c5217fee2 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_clu.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_clu.c -- R-Car VSP1 Cubic Look-Up Table + * + * Copyright (C) 2015-2016 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_clu.h" +#include "vsp1_dl.h" + +#define CLU_MIN_SIZE 4U +#define CLU_MAX_SIZE 8190U + +#define CLU_SIZE (17 * 17 * 17) + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_clu_write(struct vsp1_clu *clu, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg, data); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +#define V4L2_CID_VSP1_CLU_TABLE (V4L2_CID_USER_BASE | 0x1001) +#define V4L2_CID_VSP1_CLU_MODE (V4L2_CID_USER_BASE | 0x1002) +#define V4L2_CID_VSP1_CLU_MODE_2D 0 +#define V4L2_CID_VSP1_CLU_MODE_3D 1 + +static int clu_set_table(struct vsp1_clu *clu, struct v4l2_ctrl *ctrl) +{ + struct vsp1_dl_body *dlb; + unsigned int i; + + dlb = vsp1_dl_body_get(clu->pool); + if (!dlb) + return -ENOMEM; + + vsp1_dl_body_write(dlb, VI6_CLU_ADDR, 0); + for (i = 0; i < CLU_SIZE; ++i) + vsp1_dl_body_write(dlb, VI6_CLU_DATA, ctrl->p_new.p_u32[i]); + + spin_lock_irq(&clu->lock); + swap(clu->clu, dlb); + spin_unlock_irq(&clu->lock); + + vsp1_dl_body_put(dlb); + return 0; +} + +static int clu_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_clu *clu = + container_of(ctrl->handler, struct vsp1_clu, ctrls); + + switch (ctrl->id) { + case V4L2_CID_VSP1_CLU_TABLE: + clu_set_table(clu, ctrl); + break; + + case V4L2_CID_VSP1_CLU_MODE: + clu->mode = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops clu_ctrl_ops = { + .s_ctrl = clu_s_ctrl, +}; + +static const struct v4l2_ctrl_config clu_table_control = { + .ops = &clu_ctrl_ops, + .id = V4L2_CID_VSP1_CLU_TABLE, + .name = "Look-Up Table", + .type = V4L2_CTRL_TYPE_U32, + .min = 0x00000000, + .max = 0x00ffffff, + .step = 1, + .def = 0, + .dims = { 17, 17, 17 }, +}; + +static const char * const clu_mode_menu[] = { + "2D", + "3D", + NULL, +}; + +static const struct v4l2_ctrl_config clu_mode_control = { + .ops = &clu_ctrl_ops, + .id = V4L2_CID_VSP1_CLU_MODE, + .name = "Mode", + .type = V4L2_CTRL_TYPE_MENU, + .min = 0, + .max = 1, + .def = 1, + .qmenu = clu_mode_menu, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static const unsigned int clu_codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, +}; + +static int clu_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, clu_codes, + ARRAY_SIZE(clu_codes)); +} + +static int clu_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + return vsp1_subdev_enum_frame_size(subdev, sd_state, fse, + CLU_MIN_SIZE, + CLU_MIN_SIZE, CLU_MAX_SIZE, + CLU_MAX_SIZE); +} + +static int clu_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + return vsp1_subdev_set_pad_format(subdev, sd_state, fmt, clu_codes, + ARRAY_SIZE(clu_codes), + CLU_MIN_SIZE, CLU_MIN_SIZE, + CLU_MAX_SIZE, CLU_MAX_SIZE); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static const struct v4l2_subdev_pad_ops clu_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = clu_enum_mbus_code, + .enum_frame_size = clu_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = clu_set_format, +}; + +static const struct v4l2_subdev_ops clu_ops = { + .pad = &clu_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void clu_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_clu *clu = to_clu(&entity->subdev); + struct v4l2_mbus_framefmt *format; + + /* + * The yuv_mode can't be changed during streaming. Cache it internally + * for future runtime configuration calls. + */ + format = vsp1_entity_get_pad_format(&clu->entity, + clu->entity.config, + CLU_PAD_SINK); + clu->yuv_mode = format->code == MEDIA_BUS_FMT_AYUV8_1X32; +} + +static void clu_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_clu *clu = to_clu(&entity->subdev); + struct vsp1_dl_body *clu_dlb; + unsigned long flags; + u32 ctrl = VI6_CLU_CTRL_AAI | VI6_CLU_CTRL_MVS | VI6_CLU_CTRL_EN; + + /* 2D mode can only be used with the YCbCr pixel encoding. */ + if (clu->mode == V4L2_CID_VSP1_CLU_MODE_2D && clu->yuv_mode) + ctrl |= VI6_CLU_CTRL_AX1I_2D | VI6_CLU_CTRL_AX2I_2D + | VI6_CLU_CTRL_OS0_2D | VI6_CLU_CTRL_OS1_2D + | VI6_CLU_CTRL_OS2_2D | VI6_CLU_CTRL_M2D; + + vsp1_clu_write(clu, dlb, VI6_CLU_CTRL, ctrl); + + spin_lock_irqsave(&clu->lock, flags); + clu_dlb = clu->clu; + clu->clu = NULL; + spin_unlock_irqrestore(&clu->lock, flags); + + if (clu_dlb) { + vsp1_dl_list_add_body(dl, clu_dlb); + + /* Release our local reference. */ + vsp1_dl_body_put(clu_dlb); + } +} + +static void clu_destroy(struct vsp1_entity *entity) +{ + struct vsp1_clu *clu = to_clu(&entity->subdev); + + vsp1_dl_body_pool_destroy(clu->pool); +} + +static const struct vsp1_entity_operations clu_entity_ops = { + .configure_stream = clu_configure_stream, + .configure_frame = clu_configure_frame, + .destroy = clu_destroy, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_clu *vsp1_clu_create(struct vsp1_device *vsp1) +{ + struct vsp1_clu *clu; + int ret; + + clu = devm_kzalloc(vsp1->dev, sizeof(*clu), GFP_KERNEL); + if (clu == NULL) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&clu->lock); + + clu->entity.ops = &clu_entity_ops; + clu->entity.type = VSP1_ENTITY_CLU; + + ret = vsp1_entity_init(vsp1, &clu->entity, "clu", 2, &clu_ops, + MEDIA_ENT_F_PROC_VIDEO_LUT); + if (ret < 0) + return ERR_PTR(ret); + + /* + * Pre-allocate a body pool, with 3 bodies allowing a userspace update + * before the hardware has committed a previous set of tables, handling + * both the queued and pending dl entries. One extra entry is added to + * the CLU_SIZE to allow for the VI6_CLU_ADDR header. + */ + clu->pool = vsp1_dl_body_pool_create(clu->entity.vsp1, 3, CLU_SIZE + 1, + 0); + if (!clu->pool) + return ERR_PTR(-ENOMEM); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&clu->ctrls, 2); + v4l2_ctrl_new_custom(&clu->ctrls, &clu_table_control, NULL); + v4l2_ctrl_new_custom(&clu->ctrls, &clu_mode_control, NULL); + + clu->entity.subdev.ctrl_handler = &clu->ctrls; + + if (clu->ctrls.error) { + dev_err(vsp1->dev, "clu: failed to initialize controls\n"); + ret = clu->ctrls.error; + vsp1_entity_destroy(&clu->entity); + return ERR_PTR(ret); + } + + v4l2_ctrl_handler_setup(&clu->ctrls); + + return clu; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_clu.h b/drivers/media/platform/renesas/vsp1/vsp1_clu.h new file mode 100644 index 000000000..cef2f4448 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_clu.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_clu.h -- R-Car VSP1 Cubic Look-Up Table + * + * Copyright (C) 2015 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_CLU_H__ +#define __VSP1_CLU_H__ + +#include + +#include +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; +struct vsp1_dl_body; + +#define CLU_PAD_SINK 0 +#define CLU_PAD_SOURCE 1 + +struct vsp1_clu { + struct vsp1_entity entity; + + struct v4l2_ctrl_handler ctrls; + + bool yuv_mode; + spinlock_t lock; + unsigned int mode; + struct vsp1_dl_body *clu; + struct vsp1_dl_body_pool *pool; +}; + +static inline struct vsp1_clu *to_clu(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_clu, entity.subdev); +} + +struct vsp1_clu *vsp1_clu_create(struct vsp1_device *vsp1); + +#endif /* __VSP1_CLU_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_dl.c b/drivers/media/platform/renesas/vsp1/vsp1_dl.c new file mode 100644 index 000000000..ad3fa1c9c --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_dl.c @@ -0,0 +1,1169 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_dl.c -- R-Car VSP1 Display List + * + * Copyright (C) 2015 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include +#include +#include + +#include "vsp1.h" +#include "vsp1_dl.h" + +#define VSP1_DL_NUM_ENTRIES 256 + +#define VSP1_DLH_INT_ENABLE (1 << 1) +#define VSP1_DLH_AUTO_START (1 << 0) + +#define VSP1_DLH_EXT_PRE_CMD_EXEC (1 << 9) +#define VSP1_DLH_EXT_POST_CMD_EXEC (1 << 8) + +struct vsp1_dl_header_list { + u32 num_bytes; + u32 addr; +} __packed; + +struct vsp1_dl_header { + u32 num_lists; + struct vsp1_dl_header_list lists[8]; + u32 next_header; + u32 flags; +} __packed; + +/** + * struct vsp1_dl_ext_header - Extended display list header + * @padding: padding zero bytes for alignment + * @pre_ext_dl_num_cmd: number of pre-extended command bodies to parse + * @flags: enables or disables execution of the pre and post command + * @pre_ext_dl_plist: start address of pre-extended display list bodies + * @post_ext_dl_num_cmd: number of post-extended command bodies to parse + * @post_ext_dl_plist: start address of post-extended display list bodies + */ +struct vsp1_dl_ext_header { + u32 padding; + + /* + * The datasheet represents flags as stored before pre_ext_dl_num_cmd, + * expecting 32-bit accesses. The flags are appropriate to the whole + * header, not just the pre_ext command, and thus warrant being + * separated out. Due to byte ordering, and representing as 16 bit + * values here, the flags must be positioned after the + * pre_ext_dl_num_cmd. + */ + u16 pre_ext_dl_num_cmd; + u16 flags; + u32 pre_ext_dl_plist; + + u32 post_ext_dl_num_cmd; + u32 post_ext_dl_plist; +} __packed; + +struct vsp1_dl_header_extended { + struct vsp1_dl_header header; + struct vsp1_dl_ext_header ext; +} __packed; + +struct vsp1_dl_entry { + u32 addr; + u32 data; +} __packed; + +/** + * struct vsp1_pre_ext_dl_body - Pre Extended Display List Body + * @opcode: Extended display list command operation code + * @flags: Pre-extended command flags. These are specific to each command + * @address_set: Source address set pointer. Must have 16-byte alignment + * @reserved: Zero bits for alignment. + */ +struct vsp1_pre_ext_dl_body { + u32 opcode; + u32 flags; + u32 address_set; + u32 reserved; +} __packed; + +/** + * struct vsp1_dl_body - Display list body + * @list: entry in the display list list of bodies + * @free: entry in the pool free body list + * @refcnt: reference tracking for the body + * @pool: pool to which this body belongs + * @entries: array of entries + * @dma: DMA address of the entries + * @size: size of the DMA memory in bytes + * @num_entries: number of stored entries + * @max_entries: number of entries available + */ +struct vsp1_dl_body { + struct list_head list; + struct list_head free; + + refcount_t refcnt; + + struct vsp1_dl_body_pool *pool; + + struct vsp1_dl_entry *entries; + dma_addr_t dma; + size_t size; + + unsigned int num_entries; + unsigned int max_entries; +}; + +/** + * struct vsp1_dl_body_pool - display list body pool + * @dma: DMA address of the entries + * @size: size of the full DMA memory pool in bytes + * @mem: CPU memory pointer for the pool + * @bodies: Array of DLB structures for the pool + * @free: List of free DLB entries + * @lock: Protects the free list + * @vsp1: the VSP1 device + */ +struct vsp1_dl_body_pool { + /* DMA allocation */ + dma_addr_t dma; + size_t size; + void *mem; + + /* Body management */ + struct vsp1_dl_body *bodies; + struct list_head free; + spinlock_t lock; + + struct vsp1_device *vsp1; +}; + +/** + * struct vsp1_dl_cmd_pool - Display List commands pool + * @dma: DMA address of the entries + * @size: size of the full DMA memory pool in bytes + * @mem: CPU memory pointer for the pool + * @cmds: Array of command structures for the pool + * @free: Free pool entries + * @lock: Protects the free list + * @vsp1: the VSP1 device + */ +struct vsp1_dl_cmd_pool { + /* DMA allocation */ + dma_addr_t dma; + size_t size; + void *mem; + + struct vsp1_dl_ext_cmd *cmds; + struct list_head free; + + spinlock_t lock; + + struct vsp1_device *vsp1; +}; + +/** + * struct vsp1_dl_list - Display list + * @list: entry in the display list manager lists + * @dlm: the display list manager + * @header: display list header + * @extension: extended display list header. NULL for normal lists + * @dma: DMA address for the header + * @body0: first display list body + * @bodies: list of extra display list bodies + * @pre_cmd: pre command to be issued through extended dl header + * @post_cmd: post command to be issued through extended dl header + * @has_chain: if true, indicates that there's a partition chain + * @chain: entry in the display list partition chain + * @flags: display list flags, a combination of VSP1_DL_FRAME_END_* + */ +struct vsp1_dl_list { + struct list_head list; + struct vsp1_dl_manager *dlm; + + struct vsp1_dl_header *header; + struct vsp1_dl_ext_header *extension; + dma_addr_t dma; + + struct vsp1_dl_body *body0; + struct list_head bodies; + + struct vsp1_dl_ext_cmd *pre_cmd; + struct vsp1_dl_ext_cmd *post_cmd; + + bool has_chain; + struct list_head chain; + + unsigned int flags; +}; + +/** + * struct vsp1_dl_manager - Display List manager + * @index: index of the related WPF + * @singleshot: execute the display list in single-shot mode + * @vsp1: the VSP1 device + * @lock: protects the free, active, queued, and pending lists + * @free: array of all free display lists + * @active: list currently being processed (loaded) by hardware + * @queued: list queued to the hardware (written to the DL registers) + * @pending: list waiting to be queued to the hardware + * @pool: body pool for the display list bodies + * @cmdpool: commands pool for extended display list + */ +struct vsp1_dl_manager { + unsigned int index; + bool singleshot; + struct vsp1_device *vsp1; + + spinlock_t lock; + struct list_head free; + struct vsp1_dl_list *active; + struct vsp1_dl_list *queued; + struct vsp1_dl_list *pending; + + struct vsp1_dl_body_pool *pool; + struct vsp1_dl_cmd_pool *cmdpool; +}; + +/* ----------------------------------------------------------------------------- + * Display List Body Management + */ + +/** + * vsp1_dl_body_pool_create - Create a pool of bodies from a single allocation + * @vsp1: The VSP1 device + * @num_bodies: The number of bodies to allocate + * @num_entries: The maximum number of entries that a body can contain + * @extra_size: Extra allocation provided for the bodies + * + * Allocate a pool of display list bodies each with enough memory to contain the + * requested number of entries plus the @extra_size. + * + * Return a pointer to a pool on success or NULL if memory can't be allocated. + */ +struct vsp1_dl_body_pool * +vsp1_dl_body_pool_create(struct vsp1_device *vsp1, unsigned int num_bodies, + unsigned int num_entries, size_t extra_size) +{ + struct vsp1_dl_body_pool *pool; + size_t dlb_size; + unsigned int i; + + pool = kzalloc(sizeof(*pool), GFP_KERNEL); + if (!pool) + return NULL; + + pool->vsp1 = vsp1; + + /* + * TODO: 'extra_size' is only used by vsp1_dlm_create(), to allocate + * extra memory for the display list header. We need only one header per + * display list, not per display list body, thus this allocation is + * extraneous and should be reworked in the future. + */ + dlb_size = num_entries * sizeof(struct vsp1_dl_entry) + extra_size; + pool->size = dlb_size * num_bodies; + + pool->bodies = kcalloc(num_bodies, sizeof(*pool->bodies), GFP_KERNEL); + if (!pool->bodies) { + kfree(pool); + return NULL; + } + + pool->mem = dma_alloc_wc(vsp1->bus_master, pool->size, &pool->dma, + GFP_KERNEL); + if (!pool->mem) { + kfree(pool->bodies); + kfree(pool); + return NULL; + } + + spin_lock_init(&pool->lock); + INIT_LIST_HEAD(&pool->free); + + for (i = 0; i < num_bodies; ++i) { + struct vsp1_dl_body *dlb = &pool->bodies[i]; + + dlb->pool = pool; + dlb->max_entries = num_entries; + + dlb->dma = pool->dma + i * dlb_size; + dlb->entries = pool->mem + i * dlb_size; + + list_add_tail(&dlb->free, &pool->free); + } + + return pool; +} + +/** + * vsp1_dl_body_pool_destroy - Release a body pool + * @pool: The body pool + * + * Release all components of a pool allocation. + */ +void vsp1_dl_body_pool_destroy(struct vsp1_dl_body_pool *pool) +{ + if (!pool) + return; + + if (pool->mem) + dma_free_wc(pool->vsp1->bus_master, pool->size, pool->mem, + pool->dma); + + kfree(pool->bodies); + kfree(pool); +} + +/** + * vsp1_dl_body_get - Obtain a body from a pool + * @pool: The body pool + * + * Obtain a body from the pool without blocking. + * + * Returns a display list body or NULL if there are none available. + */ +struct vsp1_dl_body *vsp1_dl_body_get(struct vsp1_dl_body_pool *pool) +{ + struct vsp1_dl_body *dlb = NULL; + unsigned long flags; + + spin_lock_irqsave(&pool->lock, flags); + + if (!list_empty(&pool->free)) { + dlb = list_first_entry(&pool->free, struct vsp1_dl_body, free); + list_del(&dlb->free); + refcount_set(&dlb->refcnt, 1); + } + + spin_unlock_irqrestore(&pool->lock, flags); + + return dlb; +} + +/** + * vsp1_dl_body_put - Return a body back to its pool + * @dlb: The display list body + * + * Return a body back to the pool, and reset the num_entries to clear the list. + */ +void vsp1_dl_body_put(struct vsp1_dl_body *dlb) +{ + unsigned long flags; + + if (!dlb) + return; + + if (!refcount_dec_and_test(&dlb->refcnt)) + return; + + dlb->num_entries = 0; + + spin_lock_irqsave(&dlb->pool->lock, flags); + list_add_tail(&dlb->free, &dlb->pool->free); + spin_unlock_irqrestore(&dlb->pool->lock, flags); +} + +/** + * vsp1_dl_body_write - Write a register to a display list body + * @dlb: The body + * @reg: The register address + * @data: The register value + * + * Write the given register and value to the display list body. The maximum + * number of entries that can be written in a body is specified when the body is + * allocated by vsp1_dl_body_alloc(). + */ +void vsp1_dl_body_write(struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + if (WARN_ONCE(dlb->num_entries >= dlb->max_entries, + "DLB size exceeded (max %u)", dlb->max_entries)) + return; + + dlb->entries[dlb->num_entries].addr = reg; + dlb->entries[dlb->num_entries].data = data; + dlb->num_entries++; +} + +/* ----------------------------------------------------------------------------- + * Display List Extended Command Management + */ + +enum vsp1_extcmd_type { + VSP1_EXTCMD_AUTODISP, + VSP1_EXTCMD_AUTOFLD, +}; + +struct vsp1_extended_command_info { + u16 opcode; + size_t body_size; +}; + +static const struct vsp1_extended_command_info vsp1_extended_commands[] = { + [VSP1_EXTCMD_AUTODISP] = { 0x02, 96 }, + [VSP1_EXTCMD_AUTOFLD] = { 0x03, 160 }, +}; + +/** + * vsp1_dl_cmd_pool_create - Create a pool of commands from a single allocation + * @vsp1: The VSP1 device + * @type: The command pool type + * @num_cmds: The number of commands to allocate + * + * Allocate a pool of commands each with enough memory to contain the private + * data of each command. The allocation sizes are dependent upon the command + * type. + * + * Return a pointer to the pool on success or NULL if memory can't be allocated. + */ +static struct vsp1_dl_cmd_pool * +vsp1_dl_cmd_pool_create(struct vsp1_device *vsp1, enum vsp1_extcmd_type type, + unsigned int num_cmds) +{ + struct vsp1_dl_cmd_pool *pool; + unsigned int i; + size_t cmd_size; + + pool = kzalloc(sizeof(*pool), GFP_KERNEL); + if (!pool) + return NULL; + + pool->vsp1 = vsp1; + + spin_lock_init(&pool->lock); + INIT_LIST_HEAD(&pool->free); + + pool->cmds = kcalloc(num_cmds, sizeof(*pool->cmds), GFP_KERNEL); + if (!pool->cmds) { + kfree(pool); + return NULL; + } + + cmd_size = sizeof(struct vsp1_pre_ext_dl_body) + + vsp1_extended_commands[type].body_size; + cmd_size = ALIGN(cmd_size, 16); + + pool->size = cmd_size * num_cmds; + pool->mem = dma_alloc_wc(vsp1->bus_master, pool->size, &pool->dma, + GFP_KERNEL); + if (!pool->mem) { + kfree(pool->cmds); + kfree(pool); + return NULL; + } + + for (i = 0; i < num_cmds; ++i) { + struct vsp1_dl_ext_cmd *cmd = &pool->cmds[i]; + size_t cmd_offset = i * cmd_size; + /* data_offset must be 16 byte aligned for DMA. */ + size_t data_offset = sizeof(struct vsp1_pre_ext_dl_body) + + cmd_offset; + + cmd->pool = pool; + cmd->opcode = vsp1_extended_commands[type].opcode; + + /* + * TODO: Auto-disp can utilise more than one extended body + * command per cmd. + */ + cmd->num_cmds = 1; + cmd->cmds = pool->mem + cmd_offset; + cmd->cmd_dma = pool->dma + cmd_offset; + + cmd->data = pool->mem + data_offset; + cmd->data_dma = pool->dma + data_offset; + + list_add_tail(&cmd->free, &pool->free); + } + + return pool; +} + +static +struct vsp1_dl_ext_cmd *vsp1_dl_ext_cmd_get(struct vsp1_dl_cmd_pool *pool) +{ + struct vsp1_dl_ext_cmd *cmd = NULL; + unsigned long flags; + + spin_lock_irqsave(&pool->lock, flags); + + if (!list_empty(&pool->free)) { + cmd = list_first_entry(&pool->free, struct vsp1_dl_ext_cmd, + free); + list_del(&cmd->free); + } + + spin_unlock_irqrestore(&pool->lock, flags); + + return cmd; +} + +static void vsp1_dl_ext_cmd_put(struct vsp1_dl_ext_cmd *cmd) +{ + unsigned long flags; + + if (!cmd) + return; + + /* Reset flags, these mark data usage. */ + cmd->flags = 0; + + spin_lock_irqsave(&cmd->pool->lock, flags); + list_add_tail(&cmd->free, &cmd->pool->free); + spin_unlock_irqrestore(&cmd->pool->lock, flags); +} + +static void vsp1_dl_ext_cmd_pool_destroy(struct vsp1_dl_cmd_pool *pool) +{ + if (!pool) + return; + + if (pool->mem) + dma_free_wc(pool->vsp1->bus_master, pool->size, pool->mem, + pool->dma); + + kfree(pool->cmds); + kfree(pool); +} + +struct vsp1_dl_ext_cmd *vsp1_dl_get_pre_cmd(struct vsp1_dl_list *dl) +{ + struct vsp1_dl_manager *dlm = dl->dlm; + + if (dl->pre_cmd) + return dl->pre_cmd; + + dl->pre_cmd = vsp1_dl_ext_cmd_get(dlm->cmdpool); + + return dl->pre_cmd; +} + +/* ---------------------------------------------------------------------------- + * Display List Transaction Management + */ + +static struct vsp1_dl_list *vsp1_dl_list_alloc(struct vsp1_dl_manager *dlm) +{ + struct vsp1_dl_list *dl; + size_t header_offset; + + dl = kzalloc(sizeof(*dl), GFP_KERNEL); + if (!dl) + return NULL; + + INIT_LIST_HEAD(&dl->bodies); + dl->dlm = dlm; + + /* Get a default body for our list. */ + dl->body0 = vsp1_dl_body_get(dlm->pool); + if (!dl->body0) { + kfree(dl); + return NULL; + } + + header_offset = dl->body0->max_entries * sizeof(*dl->body0->entries); + + dl->header = ((void *)dl->body0->entries) + header_offset; + dl->dma = dl->body0->dma + header_offset; + + memset(dl->header, 0, sizeof(*dl->header)); + dl->header->lists[0].addr = dl->body0->dma; + + return dl; +} + +static void vsp1_dl_list_bodies_put(struct vsp1_dl_list *dl) +{ + struct vsp1_dl_body *dlb, *tmp; + + list_for_each_entry_safe(dlb, tmp, &dl->bodies, list) { + list_del(&dlb->list); + vsp1_dl_body_put(dlb); + } +} + +static void vsp1_dl_list_free(struct vsp1_dl_list *dl) +{ + vsp1_dl_body_put(dl->body0); + vsp1_dl_list_bodies_put(dl); + + kfree(dl); +} + +/** + * vsp1_dl_list_get - Get a free display list + * @dlm: The display list manager + * + * Get a display list from the pool of free lists and return it. + * + * This function must be called without the display list manager lock held. + */ +struct vsp1_dl_list *vsp1_dl_list_get(struct vsp1_dl_manager *dlm) +{ + struct vsp1_dl_list *dl = NULL; + unsigned long flags; + + spin_lock_irqsave(&dlm->lock, flags); + + if (!list_empty(&dlm->free)) { + dl = list_first_entry(&dlm->free, struct vsp1_dl_list, list); + list_del(&dl->list); + + /* + * The display list chain must be initialised to ensure every + * display list can assert list_empty() if it is not in a chain. + */ + INIT_LIST_HEAD(&dl->chain); + } + + spin_unlock_irqrestore(&dlm->lock, flags); + + return dl; +} + +/* This function must be called with the display list manager lock held.*/ +static void __vsp1_dl_list_put(struct vsp1_dl_list *dl) +{ + struct vsp1_dl_list *dl_next; + + if (!dl) + return; + + /* + * Release any linked display-lists which were chained for a single + * hardware operation. + */ + if (dl->has_chain) { + list_for_each_entry(dl_next, &dl->chain, chain) + __vsp1_dl_list_put(dl_next); + } + + dl->has_chain = false; + + vsp1_dl_list_bodies_put(dl); + + vsp1_dl_ext_cmd_put(dl->pre_cmd); + vsp1_dl_ext_cmd_put(dl->post_cmd); + + dl->pre_cmd = NULL; + dl->post_cmd = NULL; + + /* + * body0 is reused as as an optimisation as presently every display list + * has at least one body, thus we reinitialise the entries list. + */ + dl->body0->num_entries = 0; + + list_add_tail(&dl->list, &dl->dlm->free); +} + +/** + * vsp1_dl_list_put - Release a display list + * @dl: The display list + * + * Release the display list and return it to the pool of free lists. + * + * Passing a NULL pointer to this function is safe, in that case no operation + * will be performed. + */ +void vsp1_dl_list_put(struct vsp1_dl_list *dl) +{ + unsigned long flags; + + if (!dl) + return; + + spin_lock_irqsave(&dl->dlm->lock, flags); + __vsp1_dl_list_put(dl); + spin_unlock_irqrestore(&dl->dlm->lock, flags); +} + +/** + * vsp1_dl_list_get_body0 - Obtain the default body for the display list + * @dl: The display list + * + * Obtain a pointer to the internal display list body allowing this to be passed + * directly to configure operations. + */ +struct vsp1_dl_body *vsp1_dl_list_get_body0(struct vsp1_dl_list *dl) +{ + return dl->body0; +} + +/** + * vsp1_dl_list_add_body - Add a body to the display list + * @dl: The display list + * @dlb: The body + * + * Add a display list body to a display list. Registers contained in bodies are + * processed after registers contained in the main display list, in the order in + * which bodies are added. + * + * Adding a body to a display list passes ownership of the body to the list. The + * caller retains its reference to the body when adding it to the display list, + * but is not allowed to add new entries to the body. + * + * The reference must be explicitly released by a call to vsp1_dl_body_put() + * when the body isn't needed anymore. + */ +int vsp1_dl_list_add_body(struct vsp1_dl_list *dl, struct vsp1_dl_body *dlb) +{ + refcount_inc(&dlb->refcnt); + + list_add_tail(&dlb->list, &dl->bodies); + + return 0; +} + +/** + * vsp1_dl_list_add_chain - Add a display list to a chain + * @head: The head display list + * @dl: The new display list + * + * Add a display list to an existing display list chain. The chained lists + * will be automatically processed by the hardware without intervention from + * the CPU. A display list end interrupt will only complete after the last + * display list in the chain has completed processing. + * + * Adding a display list to a chain passes ownership of the display list to + * the head display list item. The chain is released when the head dl item is + * put back with __vsp1_dl_list_put(). + */ +int vsp1_dl_list_add_chain(struct vsp1_dl_list *head, + struct vsp1_dl_list *dl) +{ + head->has_chain = true; + list_add_tail(&dl->chain, &head->chain); + return 0; +} + +static void vsp1_dl_ext_cmd_fill_header(struct vsp1_dl_ext_cmd *cmd) +{ + cmd->cmds[0].opcode = cmd->opcode; + cmd->cmds[0].flags = cmd->flags; + cmd->cmds[0].address_set = cmd->data_dma; + cmd->cmds[0].reserved = 0; +} + +static void vsp1_dl_list_fill_header(struct vsp1_dl_list *dl, bool is_last) +{ + struct vsp1_dl_manager *dlm = dl->dlm; + struct vsp1_dl_header_list *hdr = dl->header->lists; + struct vsp1_dl_body *dlb; + unsigned int num_lists = 0; + + /* + * Fill the header with the display list bodies addresses and sizes. The + * address of the first body has already been filled when the display + * list was allocated. + */ + + hdr->num_bytes = dl->body0->num_entries + * sizeof(*dl->header->lists); + + list_for_each_entry(dlb, &dl->bodies, list) { + num_lists++; + hdr++; + + hdr->addr = dlb->dma; + hdr->num_bytes = dlb->num_entries + * sizeof(*dl->header->lists); + } + + dl->header->num_lists = num_lists; + dl->header->flags = 0; + + /* + * Enable the interrupt for the end of each frame. In continuous mode + * chained lists are used with one list per frame, so enable the + * interrupt for each list. In singleshot mode chained lists are used + * to partition a single frame, so enable the interrupt for the last + * list only. + */ + if (!dlm->singleshot || is_last) + dl->header->flags |= VSP1_DLH_INT_ENABLE; + + /* + * In continuous mode enable auto-start for all lists, as the VSP must + * loop on the same list until a new one is queued. In singleshot mode + * enable auto-start for all lists but the last to chain processing of + * partitions without software intervention. + */ + if (!dlm->singleshot || !is_last) + dl->header->flags |= VSP1_DLH_AUTO_START; + + if (!is_last) { + /* + * If this is not the last display list in the chain, queue the + * next item for automatic processing by the hardware. + */ + struct vsp1_dl_list *next = list_next_entry(dl, chain); + + dl->header->next_header = next->dma; + } else if (!dlm->singleshot) { + /* + * if the display list manager works in continuous mode, the VSP + * should loop over the display list continuously until + * instructed to do otherwise. + */ + dl->header->next_header = dl->dma; + } + + if (!dl->extension) + return; + + dl->extension->flags = 0; + + if (dl->pre_cmd) { + dl->extension->pre_ext_dl_plist = dl->pre_cmd->cmd_dma; + dl->extension->pre_ext_dl_num_cmd = dl->pre_cmd->num_cmds; + dl->extension->flags |= VSP1_DLH_EXT_PRE_CMD_EXEC; + + vsp1_dl_ext_cmd_fill_header(dl->pre_cmd); + } + + if (dl->post_cmd) { + dl->extension->post_ext_dl_plist = dl->post_cmd->cmd_dma; + dl->extension->post_ext_dl_num_cmd = dl->post_cmd->num_cmds; + dl->extension->flags |= VSP1_DLH_EXT_POST_CMD_EXEC; + + vsp1_dl_ext_cmd_fill_header(dl->post_cmd); + } +} + +static bool vsp1_dl_list_hw_update_pending(struct vsp1_dl_manager *dlm) +{ + struct vsp1_device *vsp1 = dlm->vsp1; + + if (!dlm->queued) + return false; + + /* + * Check whether the VSP1 has taken the update. The hardware indicates + * this by clearing the UPDHDR bit in the CMD register. + */ + return !!(vsp1_read(vsp1, VI6_CMD(dlm->index)) & VI6_CMD_UPDHDR); +} + +static void vsp1_dl_list_hw_enqueue(struct vsp1_dl_list *dl) +{ + struct vsp1_dl_manager *dlm = dl->dlm; + struct vsp1_device *vsp1 = dlm->vsp1; + + /* + * Program the display list header address. If the hardware is idle + * (single-shot mode or first frame in continuous mode) it will then be + * started independently. If the hardware is operating, the + * VI6_DL_HDR_REF_ADDR register will be updated with the display list + * address. + */ + vsp1_write(vsp1, VI6_DL_HDR_ADDR(dlm->index), dl->dma); +} + +static void vsp1_dl_list_commit_continuous(struct vsp1_dl_list *dl) +{ + struct vsp1_dl_manager *dlm = dl->dlm; + + /* + * If a previous display list has been queued to the hardware but not + * processed yet, the VSP can start processing it at any time. In that + * case we can't replace the queued list by the new one, as we could + * race with the hardware. We thus mark the update as pending, it will + * be queued up to the hardware by the frame end interrupt handler. + * + * If a display list is already pending we simply drop it as the new + * display list is assumed to contain a more recent configuration. It is + * an error if the already pending list has the + * VSP1_DL_FRAME_END_INTERNAL flag set, as there is then a process + * waiting for that list to complete. This shouldn't happen as the + * waiting process should perform proper locking, but warn just in + * case. + */ + if (vsp1_dl_list_hw_update_pending(dlm)) { + WARN_ON(dlm->pending && + (dlm->pending->flags & VSP1_DL_FRAME_END_INTERNAL)); + __vsp1_dl_list_put(dlm->pending); + dlm->pending = dl; + return; + } + + /* + * Pass the new display list to the hardware and mark it as queued. It + * will become active when the hardware starts processing it. + */ + vsp1_dl_list_hw_enqueue(dl); + + __vsp1_dl_list_put(dlm->queued); + dlm->queued = dl; +} + +static void vsp1_dl_list_commit_singleshot(struct vsp1_dl_list *dl) +{ + struct vsp1_dl_manager *dlm = dl->dlm; + + /* + * When working in single-shot mode, the caller guarantees that the + * hardware is idle at this point. Just commit the head display list + * to hardware. Chained lists will be started automatically. + */ + vsp1_dl_list_hw_enqueue(dl); + + dlm->active = dl; +} + +void vsp1_dl_list_commit(struct vsp1_dl_list *dl, unsigned int dl_flags) +{ + struct vsp1_dl_manager *dlm = dl->dlm; + struct vsp1_dl_list *dl_next; + unsigned long flags; + + /* Fill the header for the head and chained display lists. */ + vsp1_dl_list_fill_header(dl, list_empty(&dl->chain)); + + list_for_each_entry(dl_next, &dl->chain, chain) { + bool last = list_is_last(&dl_next->chain, &dl->chain); + + vsp1_dl_list_fill_header(dl_next, last); + } + + dl->flags = dl_flags & ~VSP1_DL_FRAME_END_COMPLETED; + + spin_lock_irqsave(&dlm->lock, flags); + + if (dlm->singleshot) + vsp1_dl_list_commit_singleshot(dl); + else + vsp1_dl_list_commit_continuous(dl); + + spin_unlock_irqrestore(&dlm->lock, flags); +} + +/* ----------------------------------------------------------------------------- + * Display List Manager + */ + +/** + * vsp1_dlm_irq_frame_end - Display list handler for the frame end interrupt + * @dlm: the display list manager + * + * Return a set of flags that indicates display list completion status. + * + * The VSP1_DL_FRAME_END_COMPLETED flag indicates that the previous display list + * has completed at frame end. If the flag is not returned display list + * completion has been delayed by one frame because the display list commit + * raced with the frame end interrupt. The function always returns with the flag + * set in single-shot mode as display list processing is then not continuous and + * races never occur. + * + * The following flags are only supported for continuous mode. + * + * The VSP1_DL_FRAME_END_INTERNAL flag indicates that the display list that just + * became active had been queued with the internal notification flag. + * + * The VSP1_DL_FRAME_END_WRITEBACK flag indicates that the previously active + * display list had been queued with the writeback flag. + */ +unsigned int vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm) +{ + struct vsp1_device *vsp1 = dlm->vsp1; + u32 status = vsp1_read(vsp1, VI6_STATUS); + unsigned int flags = 0; + + spin_lock(&dlm->lock); + + /* + * The mem-to-mem pipelines work in single-shot mode. No new display + * list can be queued, we don't have to do anything. + */ + if (dlm->singleshot) { + __vsp1_dl_list_put(dlm->active); + dlm->active = NULL; + flags |= VSP1_DL_FRAME_END_COMPLETED; + goto done; + } + + /* + * If the commit operation raced with the interrupt and occurred after + * the frame end event but before interrupt processing, the hardware + * hasn't taken the update into account yet. We have to skip one frame + * and retry. + */ + if (vsp1_dl_list_hw_update_pending(dlm)) + goto done; + + /* + * Progressive streams report only TOP fields. If we have a BOTTOM + * field, we are interlaced, and expect the frame to complete on the + * next frame end interrupt. + */ + if (status & VI6_STATUS_FLD_STD(dlm->index)) + goto done; + + /* + * If the active display list has the writeback flag set, the frame + * completion marks the end of the writeback capture. Return the + * VSP1_DL_FRAME_END_WRITEBACK flag and reset the display list's + * writeback flag. + */ + if (dlm->active && (dlm->active->flags & VSP1_DL_FRAME_END_WRITEBACK)) { + flags |= VSP1_DL_FRAME_END_WRITEBACK; + dlm->active->flags &= ~VSP1_DL_FRAME_END_WRITEBACK; + } + + /* + * The device starts processing the queued display list right after the + * frame end interrupt. The display list thus becomes active. + */ + if (dlm->queued) { + if (dlm->queued->flags & VSP1_DL_FRAME_END_INTERNAL) + flags |= VSP1_DL_FRAME_END_INTERNAL; + dlm->queued->flags &= ~VSP1_DL_FRAME_END_INTERNAL; + + __vsp1_dl_list_put(dlm->active); + dlm->active = dlm->queued; + dlm->queued = NULL; + flags |= VSP1_DL_FRAME_END_COMPLETED; + } + + /* + * Now that the VSP has started processing the queued display list, we + * can queue the pending display list to the hardware if one has been + * prepared. + */ + if (dlm->pending) { + vsp1_dl_list_hw_enqueue(dlm->pending); + dlm->queued = dlm->pending; + dlm->pending = NULL; + } + +done: + spin_unlock(&dlm->lock); + + return flags; +} + +/* Hardware Setup */ +void vsp1_dlm_setup(struct vsp1_device *vsp1) +{ + unsigned int i; + u32 ctrl = (256 << VI6_DL_CTRL_AR_WAIT_SHIFT) + | VI6_DL_CTRL_DC2 | VI6_DL_CTRL_DC1 | VI6_DL_CTRL_DC0 + | VI6_DL_CTRL_DLE; + u32 ext_dl = (0x02 << VI6_DL_EXT_CTRL_POLINT_SHIFT) + | VI6_DL_EXT_CTRL_DLPRI | VI6_DL_EXT_CTRL_EXT; + + if (vsp1_feature(vsp1, VSP1_HAS_EXT_DL)) { + for (i = 0; i < vsp1->info->wpf_count; ++i) + vsp1_write(vsp1, VI6_DL_EXT_CTRL(i), ext_dl); + } + + vsp1_write(vsp1, VI6_DL_CTRL, ctrl); + vsp1_write(vsp1, VI6_DL_SWAP, VI6_DL_SWAP_LWS); +} + +void vsp1_dlm_reset(struct vsp1_dl_manager *dlm) +{ + unsigned long flags; + + spin_lock_irqsave(&dlm->lock, flags); + + __vsp1_dl_list_put(dlm->active); + __vsp1_dl_list_put(dlm->queued); + __vsp1_dl_list_put(dlm->pending); + + spin_unlock_irqrestore(&dlm->lock, flags); + + dlm->active = NULL; + dlm->queued = NULL; + dlm->pending = NULL; +} + +struct vsp1_dl_body *vsp1_dlm_dl_body_get(struct vsp1_dl_manager *dlm) +{ + return vsp1_dl_body_get(dlm->pool); +} + +struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1, + unsigned int index, + unsigned int prealloc) +{ + struct vsp1_dl_manager *dlm; + size_t header_size; + unsigned int i; + + dlm = devm_kzalloc(vsp1->dev, sizeof(*dlm), GFP_KERNEL); + if (!dlm) + return NULL; + + dlm->index = index; + dlm->singleshot = vsp1->info->uapi; + dlm->vsp1 = vsp1; + + spin_lock_init(&dlm->lock); + INIT_LIST_HEAD(&dlm->free); + + /* + * Initialize the display list body and allocate DMA memory for the body + * and the header. Both are allocated together to avoid memory + * fragmentation, with the header located right after the body in + * memory. An extra body is allocated on top of the prealloc to account + * for the cached body used by the vsp1_pipeline object. + */ + header_size = vsp1_feature(vsp1, VSP1_HAS_EXT_DL) ? + sizeof(struct vsp1_dl_header_extended) : + sizeof(struct vsp1_dl_header); + + header_size = ALIGN(header_size, 8); + + dlm->pool = vsp1_dl_body_pool_create(vsp1, prealloc + 1, + VSP1_DL_NUM_ENTRIES, header_size); + if (!dlm->pool) + return NULL; + + for (i = 0; i < prealloc; ++i) { + struct vsp1_dl_list *dl; + + dl = vsp1_dl_list_alloc(dlm); + if (!dl) { + vsp1_dlm_destroy(dlm); + return NULL; + } + + /* The extended header immediately follows the header. */ + if (vsp1_feature(vsp1, VSP1_HAS_EXT_DL)) + dl->extension = (void *)dl->header + + sizeof(*dl->header); + + list_add_tail(&dl->list, &dlm->free); + } + + if (vsp1_feature(vsp1, VSP1_HAS_EXT_DL)) { + dlm->cmdpool = vsp1_dl_cmd_pool_create(vsp1, + VSP1_EXTCMD_AUTOFLD, prealloc); + if (!dlm->cmdpool) { + vsp1_dlm_destroy(dlm); + return NULL; + } + } + + return dlm; +} + +void vsp1_dlm_destroy(struct vsp1_dl_manager *dlm) +{ + struct vsp1_dl_list *dl, *next; + + if (!dlm) + return; + + list_for_each_entry_safe(dl, next, &dlm->free, list) { + list_del(&dl->list); + vsp1_dl_list_free(dl); + } + + vsp1_dl_body_pool_destroy(dlm->pool); + vsp1_dl_ext_cmd_pool_destroy(dlm->cmdpool); +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_dl.h b/drivers/media/platform/renesas/vsp1/vsp1_dl.h new file mode 100644 index 000000000..bebe16483 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_dl.h @@ -0,0 +1,79 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_dl.h -- R-Car VSP1 Display List + * + * Copyright (C) 2015 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_DL_H__ +#define __VSP1_DL_H__ + +#include + +struct vsp1_device; +struct vsp1_dl_body; +struct vsp1_dl_body_pool; +struct vsp1_dl_list; +struct vsp1_dl_manager; + +/* Keep these flags in sync with VSP1_DU_STATUS_* in include/media/vsp1.h. */ +#define VSP1_DL_FRAME_END_COMPLETED BIT(0) +#define VSP1_DL_FRAME_END_WRITEBACK BIT(1) +#define VSP1_DL_FRAME_END_INTERNAL BIT(2) + +/** + * struct vsp1_dl_ext_cmd - Extended Display command + * @pool: pool to which this command belongs + * @free: entry in the pool of free commands list + * @opcode: command type opcode + * @flags: flags used by the command + * @cmds: array of command bodies for this extended cmd + * @num_cmds: quantity of commands in @cmds array + * @cmd_dma: DMA address of the command body + * @data: memory allocation for command-specific data + * @data_dma: DMA address for command-specific data + */ +struct vsp1_dl_ext_cmd { + struct vsp1_dl_cmd_pool *pool; + struct list_head free; + + u8 opcode; + u32 flags; + + struct vsp1_pre_ext_dl_body *cmds; + unsigned int num_cmds; + dma_addr_t cmd_dma; + + void *data; + dma_addr_t data_dma; +}; + +void vsp1_dlm_setup(struct vsp1_device *vsp1); + +struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1, + unsigned int index, + unsigned int prealloc); +void vsp1_dlm_destroy(struct vsp1_dl_manager *dlm); +void vsp1_dlm_reset(struct vsp1_dl_manager *dlm); +unsigned int vsp1_dlm_irq_frame_end(struct vsp1_dl_manager *dlm); +struct vsp1_dl_body *vsp1_dlm_dl_body_get(struct vsp1_dl_manager *dlm); + +struct vsp1_dl_list *vsp1_dl_list_get(struct vsp1_dl_manager *dlm); +void vsp1_dl_list_put(struct vsp1_dl_list *dl); +struct vsp1_dl_body *vsp1_dl_list_get_body0(struct vsp1_dl_list *dl); +struct vsp1_dl_ext_cmd *vsp1_dl_get_pre_cmd(struct vsp1_dl_list *dl); +void vsp1_dl_list_commit(struct vsp1_dl_list *dl, unsigned int dl_flags); + +struct vsp1_dl_body_pool * +vsp1_dl_body_pool_create(struct vsp1_device *vsp1, unsigned int num_bodies, + unsigned int num_entries, size_t extra_size); +void vsp1_dl_body_pool_destroy(struct vsp1_dl_body_pool *pool); +struct vsp1_dl_body *vsp1_dl_body_get(struct vsp1_dl_body_pool *pool); +void vsp1_dl_body_put(struct vsp1_dl_body *dlb); + +void vsp1_dl_body_write(struct vsp1_dl_body *dlb, u32 reg, u32 data); +int vsp1_dl_list_add_body(struct vsp1_dl_list *dl, struct vsp1_dl_body *dlb); +int vsp1_dl_list_add_chain(struct vsp1_dl_list *head, struct vsp1_dl_list *dl); + +#endif /* __VSP1_DL_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drm.c b/drivers/media/platform/renesas/vsp1/vsp1_drm.c new file mode 100644 index 000000000..7fe375b63 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_drm.c @@ -0,0 +1,996 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_drm.c -- R-Car VSP1 DRM/KMS Interface + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include + +#include +#include +#include + +#include "vsp1.h" +#include "vsp1_brx.h" +#include "vsp1_dl.h" +#include "vsp1_drm.h" +#include "vsp1_lif.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" +#include "vsp1_uif.h" + +#define BRX_NAME(e) (e)->type == VSP1_ENTITY_BRU ? "BRU" : "BRS" + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static void vsp1_du_pipeline_frame_end(struct vsp1_pipeline *pipe, + unsigned int completion) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + + if (drm_pipe->du_complete) { + struct vsp1_entity *uif = drm_pipe->uif; + unsigned int status = completion + & (VSP1_DU_STATUS_COMPLETE | + VSP1_DU_STATUS_WRITEBACK); + u32 crc; + + crc = uif ? vsp1_uif_get_crc(to_uif(&uif->subdev)) : 0; + drm_pipe->du_complete(drm_pipe->du_private, status, crc); + } + + if (completion & VSP1_DL_FRAME_END_INTERNAL) { + drm_pipe->force_brx_release = false; + wake_up(&drm_pipe->wait_queue); + } +} + +/* ----------------------------------------------------------------------------- + * Pipeline Configuration + */ + +/* + * Insert the UIF in the pipeline between the prev and next entities. If no UIF + * is available connect the two entities directly. + */ +static int vsp1_du_insert_uif(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe, + struct vsp1_entity *uif, + struct vsp1_entity *prev, unsigned int prev_pad, + struct vsp1_entity *next, unsigned int next_pad) +{ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + if (!uif) { + /* + * If there's no UIF to be inserted, connect the previous and + * next entities directly. + */ + prev->sink = next; + prev->sink_pad = next_pad; + return 0; + } + + prev->sink = uif; + prev->sink_pad = UIF_PAD_SINK; + + format.pad = prev_pad; + + ret = v4l2_subdev_call(&prev->subdev, pad, get_fmt, NULL, &format); + if (ret < 0) + return ret; + + format.pad = UIF_PAD_SINK; + + ret = v4l2_subdev_call(&uif->subdev, pad, set_fmt, NULL, &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on UIF sink\n", + __func__, format.format.width, format.format.height, + format.format.code); + + /* + * The UIF doesn't mangle the format between its sink and source pads, + * so there is no need to retrieve the format on its source pad. + */ + + uif->sink = next; + uif->sink_pad = next_pad; + + return 0; +} + +/* Setup one RPF and the connected BRx sink pad. */ +static int vsp1_du_pipeline_setup_rpf(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe, + struct vsp1_rwpf *rpf, + struct vsp1_entity *uif, + unsigned int brx_input) +{ + struct v4l2_subdev_selection sel = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const struct v4l2_rect *crop; + int ret; + + /* + * Configure the format on the RPF sink pad and propagate it up to the + * BRx sink pad. + */ + crop = &vsp1->drm->inputs[rpf->entity.index].crop; + + format.pad = RWPF_PAD_SINK; + format.format.width = crop->width + crop->left; + format.format.height = crop->height + crop->top; + format.format.code = rpf->fmtinfo->mbus; + format.format.field = V4L2_FIELD_NONE; + + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, + "%s: set format %ux%u (%x) on RPF%u sink\n", + __func__, format.format.width, format.format.height, + format.format.code, rpf->entity.index); + + sel.pad = RWPF_PAD_SINK; + sel.target = V4L2_SEL_TGT_CROP; + sel.r = *crop; + + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_selection, NULL, + &sel); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, + "%s: set selection (%u,%u)/%ux%u on RPF%u sink\n", + __func__, sel.r.left, sel.r.top, sel.r.width, sel.r.height, + rpf->entity.index); + + /* + * RPF source, hardcode the format to ARGB8888 to turn on format + * conversion if needed. + */ + format.pad = RWPF_PAD_SOURCE; + + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, get_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, + "%s: got format %ux%u (%x) on RPF%u source\n", + __func__, format.format.width, format.format.height, + format.format.code, rpf->entity.index); + + format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; + + ret = v4l2_subdev_call(&rpf->entity.subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + /* Insert and configure the UIF if available. */ + ret = vsp1_du_insert_uif(vsp1, pipe, uif, &rpf->entity, RWPF_PAD_SOURCE, + pipe->brx, brx_input); + if (ret < 0) + return ret; + + /* BRx sink, propagate the format from the RPF source. */ + format.pad = brx_input; + + ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on %s pad %u\n", + __func__, format.format.width, format.format.height, + format.format.code, BRX_NAME(pipe->brx), format.pad); + + sel.pad = brx_input; + sel.target = V4L2_SEL_TGT_COMPOSE; + sel.r = vsp1->drm->inputs[rpf->entity.index].compose; + + ret = v4l2_subdev_call(&pipe->brx->subdev, pad, set_selection, NULL, + &sel); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set selection (%u,%u)/%ux%u on %s pad %u\n", + __func__, sel.r.left, sel.r.top, sel.r.width, sel.r.height, + BRX_NAME(pipe->brx), sel.pad); + + return 0; +} + +/* Setup the BRx source pad. */ +static int vsp1_du_pipeline_setup_inputs(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe); +static void vsp1_du_pipeline_configure(struct vsp1_pipeline *pipe); + +static int vsp1_du_pipeline_setup_brx(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct vsp1_entity *brx; + int ret; + + /* + * Pick a BRx: + * - If we need more than two inputs, use the BRU. + * - Otherwise, if we are not forced to release our BRx, keep it. + * - Else, use any free BRx (randomly starting with the BRU). + */ + if (pipe->num_inputs > 2) + brx = &vsp1->bru->entity; + else if (pipe->brx && !drm_pipe->force_brx_release) + brx = pipe->brx; + else if (vsp1_feature(vsp1, VSP1_HAS_BRU) && !vsp1->bru->entity.pipe) + brx = &vsp1->bru->entity; + else + brx = &vsp1->brs->entity; + + /* Switch BRx if needed. */ + if (brx != pipe->brx) { + struct vsp1_entity *released_brx = NULL; + + /* Release our BRx if we have one. */ + if (pipe->brx) { + dev_dbg(vsp1->dev, "%s: pipe %u: releasing %s\n", + __func__, pipe->lif->index, + BRX_NAME(pipe->brx)); + + /* + * The BRx might be acquired by the other pipeline in + * the next step. We must thus remove it from the list + * of entities for this pipeline. The other pipeline's + * hardware configuration will reconfigure the BRx + * routing. + * + * However, if the other pipeline doesn't acquire our + * BRx, we need to keep it in the list, otherwise the + * hardware configuration step won't disconnect it from + * the pipeline. To solve this, store the released BRx + * pointer to add it back to the list of entities later + * if it isn't acquired by the other pipeline. + */ + released_brx = pipe->brx; + + list_del(&pipe->brx->list_pipe); + pipe->brx->sink = NULL; + pipe->brx->pipe = NULL; + pipe->brx = NULL; + } + + /* + * If the BRx we need is in use, force the owner pipeline to + * switch to the other BRx and wait until the switch completes. + */ + if (brx->pipe) { + struct vsp1_drm_pipeline *owner_pipe; + + dev_dbg(vsp1->dev, "%s: pipe %u: waiting for %s\n", + __func__, pipe->lif->index, BRX_NAME(brx)); + + owner_pipe = to_vsp1_drm_pipeline(brx->pipe); + owner_pipe->force_brx_release = true; + + vsp1_du_pipeline_setup_inputs(vsp1, &owner_pipe->pipe); + vsp1_du_pipeline_configure(&owner_pipe->pipe); + + ret = wait_event_timeout(owner_pipe->wait_queue, + !owner_pipe->force_brx_release, + msecs_to_jiffies(500)); + if (ret == 0) + dev_warn(vsp1->dev, + "DRM pipeline %u reconfiguration timeout\n", + owner_pipe->pipe.lif->index); + } + + /* + * If the BRx we have released previously hasn't been acquired + * by the other pipeline, add it back to the entities list (with + * the pipe pointer NULL) to let vsp1_du_pipeline_configure() + * disconnect it from the hardware pipeline. + */ + if (released_brx && !released_brx->pipe) + list_add_tail(&released_brx->list_pipe, + &pipe->entities); + + /* Add the BRx to the pipeline. */ + dev_dbg(vsp1->dev, "%s: pipe %u: acquired %s\n", + __func__, pipe->lif->index, BRX_NAME(brx)); + + pipe->brx = brx; + pipe->brx->pipe = pipe; + pipe->brx->sink = &pipe->output->entity; + pipe->brx->sink_pad = 0; + + list_add_tail(&pipe->brx->list_pipe, &pipe->entities); + } + + /* + * Configure the format on the BRx source and verify that it matches the + * requested format. We don't set the media bus code as it is configured + * on the BRx sink pad 0 and propagated inside the entity, not on the + * source pad. + */ + format.pad = brx->source_pad; + format.format.width = drm_pipe->width; + format.format.height = drm_pipe->height; + format.format.field = V4L2_FIELD_NONE; + + ret = v4l2_subdev_call(&brx->subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on %s pad %u\n", + __func__, format.format.width, format.format.height, + format.format.code, BRX_NAME(brx), brx->source_pad); + + if (format.format.width != drm_pipe->width || + format.format.height != drm_pipe->height) { + dev_dbg(vsp1->dev, "%s: format mismatch\n", __func__); + return -EPIPE; + } + + return 0; +} + +static unsigned int rpf_zpos(struct vsp1_device *vsp1, struct vsp1_rwpf *rpf) +{ + return vsp1->drm->inputs[rpf->entity.index].zpos; +} + +/* Setup the input side of the pipeline (RPFs and BRx). */ +static int vsp1_du_pipeline_setup_inputs(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + struct vsp1_rwpf *inputs[VSP1_MAX_RPF] = { NULL, }; + struct vsp1_entity *uif; + bool use_uif = false; + struct vsp1_brx *brx; + unsigned int i; + int ret; + + /* Count the number of enabled inputs and sort them by Z-order. */ + pipe->num_inputs = 0; + + for (i = 0; i < vsp1->info->rpf_count; ++i) { + struct vsp1_rwpf *rpf = vsp1->rpf[i]; + unsigned int j; + + if (!pipe->inputs[i]) + continue; + + /* Insert the RPF in the sorted RPFs array. */ + for (j = pipe->num_inputs++; j > 0; --j) { + if (rpf_zpos(vsp1, inputs[j-1]) <= rpf_zpos(vsp1, rpf)) + break; + inputs[j] = inputs[j-1]; + } + + inputs[j] = rpf; + } + + /* + * Setup the BRx. This must be done before setting up the RPF input + * pipelines as the BRx sink compose rectangles depend on the BRx source + * format. + */ + ret = vsp1_du_pipeline_setup_brx(vsp1, pipe); + if (ret < 0) { + dev_err(vsp1->dev, "%s: failed to setup %s source\n", __func__, + BRX_NAME(pipe->brx)); + return ret; + } + + brx = to_brx(&pipe->brx->subdev); + + /* Setup the RPF input pipeline for every enabled input. */ + for (i = 0; i < pipe->brx->source_pad; ++i) { + struct vsp1_rwpf *rpf = inputs[i]; + + if (!rpf) { + brx->inputs[i].rpf = NULL; + continue; + } + + if (!rpf->entity.pipe) { + rpf->entity.pipe = pipe; + list_add_tail(&rpf->entity.list_pipe, &pipe->entities); + } + + brx->inputs[i].rpf = rpf; + rpf->brx_input = i; + rpf->entity.sink = pipe->brx; + rpf->entity.sink_pad = i; + + dev_dbg(vsp1->dev, "%s: connecting RPF.%u to %s:%u\n", + __func__, rpf->entity.index, BRX_NAME(pipe->brx), i); + + uif = drm_pipe->crc.source == VSP1_DU_CRC_PLANE && + drm_pipe->crc.index == i ? drm_pipe->uif : NULL; + if (uif) + use_uif = true; + ret = vsp1_du_pipeline_setup_rpf(vsp1, pipe, rpf, uif, i); + if (ret < 0) { + dev_err(vsp1->dev, + "%s: failed to setup RPF.%u\n", + __func__, rpf->entity.index); + return ret; + } + } + + /* Insert and configure the UIF at the BRx output if available. */ + uif = drm_pipe->crc.source == VSP1_DU_CRC_OUTPUT ? drm_pipe->uif : NULL; + if (uif) + use_uif = true; + ret = vsp1_du_insert_uif(vsp1, pipe, uif, + pipe->brx, pipe->brx->source_pad, + &pipe->output->entity, 0); + if (ret < 0) + dev_err(vsp1->dev, "%s: failed to setup UIF after %s\n", + __func__, BRX_NAME(pipe->brx)); + + /* If the DRM pipe does not have a UIF there is nothing we can update. */ + if (!drm_pipe->uif) + return 0; + + /* + * If the UIF is not in use schedule it for removal by setting its pipe + * pointer to NULL, vsp1_du_pipeline_configure() will remove it from the + * hardware pipeline and from the pipeline's list of entities. Otherwise + * make sure it is present in the pipeline's list of entities if it + * wasn't already. + */ + if (!use_uif) { + drm_pipe->uif->pipe = NULL; + } else if (!drm_pipe->uif->pipe) { + drm_pipe->uif->pipe = pipe; + list_add_tail(&drm_pipe->uif->list_pipe, &pipe->entities); + } + + return 0; +} + +/* Setup the output side of the pipeline (WPF and LIF). */ +static int vsp1_du_pipeline_setup_output(struct vsp1_device *vsp1, + struct vsp1_pipeline *pipe) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + struct v4l2_subdev_format format = { 0, }; + int ret; + + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + format.pad = RWPF_PAD_SINK; + format.format.width = drm_pipe->width; + format.format.height = drm_pipe->height; + format.format.code = MEDIA_BUS_FMT_ARGB8888_1X32; + format.format.field = V4L2_FIELD_NONE; + + ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on WPF%u sink\n", + __func__, format.format.width, format.format.height, + format.format.code, pipe->output->entity.index); + + format.pad = RWPF_PAD_SOURCE; + ret = v4l2_subdev_call(&pipe->output->entity.subdev, pad, get_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: got format %ux%u (%x) on WPF%u source\n", + __func__, format.format.width, format.format.height, + format.format.code, pipe->output->entity.index); + + format.pad = LIF_PAD_SINK; + ret = v4l2_subdev_call(&pipe->lif->subdev, pad, set_fmt, NULL, + &format); + if (ret < 0) + return ret; + + dev_dbg(vsp1->dev, "%s: set format %ux%u (%x) on LIF%u sink\n", + __func__, format.format.width, format.format.height, + format.format.code, pipe->lif->index); + + /* + * Verify that the format at the output of the pipeline matches the + * requested frame size and media bus code. + */ + if (format.format.width != drm_pipe->width || + format.format.height != drm_pipe->height || + format.format.code != MEDIA_BUS_FMT_ARGB8888_1X32) { + dev_dbg(vsp1->dev, "%s: format mismatch on LIF%u\n", __func__, + pipe->lif->index); + return -EPIPE; + } + + return 0; +} + +/* Configure all entities in the pipeline. */ +static void vsp1_du_pipeline_configure(struct vsp1_pipeline *pipe) +{ + struct vsp1_drm_pipeline *drm_pipe = to_vsp1_drm_pipeline(pipe); + struct vsp1_entity *entity; + struct vsp1_entity *next; + struct vsp1_dl_list *dl; + struct vsp1_dl_body *dlb; + unsigned int dl_flags = 0; + + if (drm_pipe->force_brx_release) + dl_flags |= VSP1_DL_FRAME_END_INTERNAL; + if (pipe->output->writeback) + dl_flags |= VSP1_DL_FRAME_END_WRITEBACK; + + dl = vsp1_dl_list_get(pipe->output->dlm); + dlb = vsp1_dl_list_get_body0(dl); + + list_for_each_entry_safe(entity, next, &pipe->entities, list_pipe) { + /* Disconnect unused entities from the pipeline. */ + if (!entity->pipe) { + vsp1_dl_body_write(dlb, entity->route->reg, + VI6_DPR_NODE_UNUSED); + + entity->sink = NULL; + list_del(&entity->list_pipe); + + continue; + } + + vsp1_entity_route_setup(entity, pipe, dlb); + vsp1_entity_configure_stream(entity, pipe, dl, dlb); + vsp1_entity_configure_frame(entity, pipe, dl, dlb); + vsp1_entity_configure_partition(entity, pipe, dl, dlb); + } + + vsp1_dl_list_commit(dl, dl_flags); +} + +static int vsp1_du_pipeline_set_rwpf_format(struct vsp1_device *vsp1, + struct vsp1_rwpf *rwpf, + u32 pixelformat, unsigned int pitch) +{ + const struct vsp1_format_info *fmtinfo; + unsigned int chroma_hsub; + + fmtinfo = vsp1_get_format_info(vsp1, pixelformat); + if (!fmtinfo) { + dev_dbg(vsp1->dev, "Unsupported pixel format %08x\n", + pixelformat); + return -EINVAL; + } + + /* + * Only formats with three planes can affect the chroma planes pitch. + * All formats with two planes have a horizontal subsampling value of 2, + * but combine U and V in a single chroma plane, which thus results in + * the luma plane and chroma plane having the same pitch. + */ + chroma_hsub = (fmtinfo->planes == 3) ? fmtinfo->hsub : 1; + + rwpf->fmtinfo = fmtinfo; + rwpf->format.num_planes = fmtinfo->planes; + rwpf->format.plane_fmt[0].bytesperline = pitch; + rwpf->format.plane_fmt[1].bytesperline = pitch / chroma_hsub; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * DU Driver API + */ + +int vsp1_du_init(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + if (!vsp1) + return -EPROBE_DEFER; + + return 0; +} +EXPORT_SYMBOL_GPL(vsp1_du_init); + +/** + * vsp1_du_setup_lif - Setup the output part of the VSP pipeline + * @dev: the VSP device + * @pipe_index: the DRM pipeline index + * @cfg: the LIF configuration + * + * Configure the output part of VSP DRM pipeline for the given frame @cfg.width + * and @cfg.height. This sets up formats on the BRx source pad, the WPF sink and + * source pads, and the LIF sink pad. + * + * The @pipe_index argument selects which DRM pipeline to setup. The number of + * available pipelines depend on the VSP instance. + * + * As the media bus code on the blend unit source pad is conditioned by the + * configuration of its sink 0 pad, we also set up the formats on all blend unit + * sinks, even if the configuration will be overwritten later by + * vsp1_du_setup_rpf(). This ensures that the blend unit configuration is set to + * a well defined state. + * + * Return 0 on success or a negative error code on failure. + */ +int vsp1_du_setup_lif(struct device *dev, unsigned int pipe_index, + const struct vsp1_du_lif_config *cfg) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + struct vsp1_drm_pipeline *drm_pipe; + struct vsp1_pipeline *pipe; + unsigned long flags; + unsigned int i; + int ret; + + if (pipe_index >= vsp1->info->lif_count) + return -EINVAL; + + drm_pipe = &vsp1->drm->pipe[pipe_index]; + pipe = &drm_pipe->pipe; + + if (!cfg) { + struct vsp1_brx *brx; + + mutex_lock(&vsp1->drm->lock); + + brx = to_brx(&pipe->brx->subdev); + + /* + * NULL configuration means the CRTC is being disabled, stop + * the pipeline and turn the light off. + */ + ret = vsp1_pipeline_stop(pipe); + if (ret == -ETIMEDOUT) + dev_err(vsp1->dev, "DRM pipeline stop timeout\n"); + + for (i = 0; i < ARRAY_SIZE(pipe->inputs); ++i) { + struct vsp1_rwpf *rpf = pipe->inputs[i]; + + if (!rpf) + continue; + + /* + * Remove the RPF from the pipe and the list of BRx + * inputs. + */ + WARN_ON(!rpf->entity.pipe); + rpf->entity.pipe = NULL; + list_del(&rpf->entity.list_pipe); + pipe->inputs[i] = NULL; + + brx->inputs[rpf->brx_input].rpf = NULL; + } + + drm_pipe->du_complete = NULL; + pipe->num_inputs = 0; + + dev_dbg(vsp1->dev, "%s: pipe %u: releasing %s\n", + __func__, pipe->lif->index, + BRX_NAME(pipe->brx)); + + list_del(&pipe->brx->list_pipe); + pipe->brx->pipe = NULL; + pipe->brx = NULL; + + mutex_unlock(&vsp1->drm->lock); + + vsp1_dlm_reset(pipe->output->dlm); + vsp1_device_put(vsp1); + + dev_dbg(vsp1->dev, "%s: pipeline disabled\n", __func__); + + return 0; + } + + drm_pipe->width = cfg->width; + drm_pipe->height = cfg->height; + pipe->interlaced = cfg->interlaced; + + dev_dbg(vsp1->dev, "%s: configuring LIF%u with format %ux%u%s\n", + __func__, pipe_index, cfg->width, cfg->height, + pipe->interlaced ? "i" : ""); + + mutex_lock(&vsp1->drm->lock); + + /* Setup formats through the pipeline. */ + ret = vsp1_du_pipeline_setup_inputs(vsp1, pipe); + if (ret < 0) + goto unlock; + + ret = vsp1_du_pipeline_setup_output(vsp1, pipe); + if (ret < 0) + goto unlock; + + /* Enable the VSP1. */ + ret = vsp1_device_get(vsp1); + if (ret < 0) + goto unlock; + + /* + * Register a callback to allow us to notify the DRM driver of frame + * completion events. + */ + drm_pipe->du_complete = cfg->callback; + drm_pipe->du_private = cfg->callback_data; + + /* Disable the display interrupts. */ + vsp1_write(vsp1, VI6_DISP_IRQ_STA(pipe_index), 0); + vsp1_write(vsp1, VI6_DISP_IRQ_ENB(pipe_index), 0); + + /* Configure all entities in the pipeline. */ + vsp1_du_pipeline_configure(pipe); + +unlock: + mutex_unlock(&vsp1->drm->lock); + + if (ret < 0) + return ret; + + /* Start the pipeline. */ + spin_lock_irqsave(&pipe->irqlock, flags); + vsp1_pipeline_run(pipe); + spin_unlock_irqrestore(&pipe->irqlock, flags); + + dev_dbg(vsp1->dev, "%s: pipeline enabled\n", __func__); + + return 0; +} +EXPORT_SYMBOL_GPL(vsp1_du_setup_lif); + +/** + * vsp1_du_atomic_begin - Prepare for an atomic update + * @dev: the VSP device + * @pipe_index: the DRM pipeline index + */ +void vsp1_du_atomic_begin(struct device *dev, unsigned int pipe_index) +{ +} +EXPORT_SYMBOL_GPL(vsp1_du_atomic_begin); + +/** + * vsp1_du_atomic_update - Setup one RPF input of the VSP pipeline + * @dev: the VSP device + * @pipe_index: the DRM pipeline index + * @rpf_index: index of the RPF to setup (0-based) + * @cfg: the RPF configuration + * + * Configure the VSP to perform image composition through RPF @rpf_index as + * described by the @cfg configuration. The image to compose is referenced by + * @cfg.mem and composed using the @cfg.src crop rectangle and the @cfg.dst + * composition rectangle. The Z-order is configurable with higher @zpos values + * displayed on top. + * + * If the @cfg configuration is NULL, the RPF will be disabled. Calling the + * function on a disabled RPF is allowed. + * + * Image format as stored in memory is expressed as a V4L2 @cfg.pixelformat + * value. The memory pitch is configurable to allow for padding at end of lines, + * or simply for images that extend beyond the crop rectangle boundaries. The + * @cfg.pitch value is expressed in bytes and applies to all planes for + * multiplanar formats. + * + * The source memory buffer is referenced by the DMA address of its planes in + * the @cfg.mem array. Up to two planes are supported. The second plane DMA + * address is ignored for formats using a single plane. + * + * This function isn't reentrant, the caller needs to serialize calls. + * + * Return 0 on success or a negative error code on failure. + */ +int vsp1_du_atomic_update(struct device *dev, unsigned int pipe_index, + unsigned int rpf_index, + const struct vsp1_du_atomic_config *cfg) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + struct vsp1_drm_pipeline *drm_pipe = &vsp1->drm->pipe[pipe_index]; + struct vsp1_rwpf *rpf; + int ret; + + if (rpf_index >= vsp1->info->rpf_count) + return -EINVAL; + + rpf = vsp1->rpf[rpf_index]; + + if (!cfg) { + dev_dbg(vsp1->dev, "%s: RPF%u: disable requested\n", __func__, + rpf_index); + + /* + * Remove the RPF from the pipeline's inputs. Keep it in the + * pipeline's entity list to let vsp1_du_pipeline_configure() + * remove it from the hardware pipeline. + */ + rpf->entity.pipe = NULL; + drm_pipe->pipe.inputs[rpf_index] = NULL; + return 0; + } + + dev_dbg(vsp1->dev, + "%s: RPF%u: (%u,%u)/%ux%u -> (%u,%u)/%ux%u (%08x), pitch %u dma { %pad, %pad, %pad } zpos %u\n", + __func__, rpf_index, + cfg->src.left, cfg->src.top, cfg->src.width, cfg->src.height, + cfg->dst.left, cfg->dst.top, cfg->dst.width, cfg->dst.height, + cfg->pixelformat, cfg->pitch, &cfg->mem[0], &cfg->mem[1], + &cfg->mem[2], cfg->zpos); + + /* + * Store the format, stride, memory buffer address, crop and compose + * rectangles and Z-order position and for the input. + */ + ret = vsp1_du_pipeline_set_rwpf_format(vsp1, rpf, cfg->pixelformat, + cfg->pitch); + if (ret < 0) + return ret; + + rpf->alpha = cfg->alpha; + + rpf->mem.addr[0] = cfg->mem[0]; + rpf->mem.addr[1] = cfg->mem[1]; + rpf->mem.addr[2] = cfg->mem[2]; + + rpf->format.flags = cfg->premult ? V4L2_PIX_FMT_FLAG_PREMUL_ALPHA : 0; + + vsp1->drm->inputs[rpf_index].crop = cfg->src; + vsp1->drm->inputs[rpf_index].compose = cfg->dst; + vsp1->drm->inputs[rpf_index].zpos = cfg->zpos; + + drm_pipe->pipe.inputs[rpf_index] = rpf; + + return 0; +} +EXPORT_SYMBOL_GPL(vsp1_du_atomic_update); + +/** + * vsp1_du_atomic_flush - Commit an atomic update + * @dev: the VSP device + * @pipe_index: the DRM pipeline index + * @cfg: atomic pipe configuration + */ +void vsp1_du_atomic_flush(struct device *dev, unsigned int pipe_index, + const struct vsp1_du_atomic_pipe_config *cfg) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + struct vsp1_drm_pipeline *drm_pipe = &vsp1->drm->pipe[pipe_index]; + struct vsp1_pipeline *pipe = &drm_pipe->pipe; + int ret; + + drm_pipe->crc = cfg->crc; + + mutex_lock(&vsp1->drm->lock); + + if (cfg->writeback.pixelformat) { + const struct vsp1_du_writeback_config *wb_cfg = &cfg->writeback; + + ret = vsp1_du_pipeline_set_rwpf_format(vsp1, pipe->output, + wb_cfg->pixelformat, + wb_cfg->pitch); + if (WARN_ON(ret < 0)) + goto done; + + pipe->output->mem.addr[0] = wb_cfg->mem[0]; + pipe->output->mem.addr[1] = wb_cfg->mem[1]; + pipe->output->mem.addr[2] = wb_cfg->mem[2]; + pipe->output->writeback = true; + } + + vsp1_du_pipeline_setup_inputs(vsp1, pipe); + vsp1_du_pipeline_configure(pipe); + +done: + mutex_unlock(&vsp1->drm->lock); +} +EXPORT_SYMBOL_GPL(vsp1_du_atomic_flush); + +int vsp1_du_map_sg(struct device *dev, struct sg_table *sgt) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + /* + * As all the buffers allocated by the DU driver are coherent, we can + * skip cache sync. This will need to be revisited when support for + * non-coherent buffers will be added to the DU driver. + */ + return dma_map_sgtable(vsp1->bus_master, sgt, DMA_TO_DEVICE, + DMA_ATTR_SKIP_CPU_SYNC); +} +EXPORT_SYMBOL_GPL(vsp1_du_map_sg); + +void vsp1_du_unmap_sg(struct device *dev, struct sg_table *sgt) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + dma_unmap_sgtable(vsp1->bus_master, sgt, DMA_TO_DEVICE, + DMA_ATTR_SKIP_CPU_SYNC); +} +EXPORT_SYMBOL_GPL(vsp1_du_unmap_sg); + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +int vsp1_drm_init(struct vsp1_device *vsp1) +{ + unsigned int i; + + vsp1->drm = devm_kzalloc(vsp1->dev, sizeof(*vsp1->drm), GFP_KERNEL); + if (!vsp1->drm) + return -ENOMEM; + + mutex_init(&vsp1->drm->lock); + + /* Create one DRM pipeline per LIF. */ + for (i = 0; i < vsp1->info->lif_count; ++i) { + struct vsp1_drm_pipeline *drm_pipe = &vsp1->drm->pipe[i]; + struct vsp1_pipeline *pipe = &drm_pipe->pipe; + + init_waitqueue_head(&drm_pipe->wait_queue); + + vsp1_pipeline_init(pipe); + + pipe->frame_end = vsp1_du_pipeline_frame_end; + + /* + * The output side of the DRM pipeline is static, add the + * corresponding entities manually. + */ + pipe->output = vsp1->wpf[i]; + pipe->lif = &vsp1->lif[i]->entity; + + pipe->output->entity.pipe = pipe; + pipe->output->entity.sink = pipe->lif; + pipe->output->entity.sink_pad = 0; + list_add_tail(&pipe->output->entity.list_pipe, &pipe->entities); + + pipe->lif->pipe = pipe; + list_add_tail(&pipe->lif->list_pipe, &pipe->entities); + + /* + * CRC computation is initially disabled, don't add the UIF to + * the pipeline. + */ + if (i < vsp1->info->uif_count) + drm_pipe->uif = &vsp1->uif[i]->entity; + } + + /* Disable all RPFs initially. */ + for (i = 0; i < vsp1->info->rpf_count; ++i) { + struct vsp1_rwpf *input = vsp1->rpf[i]; + + INIT_LIST_HEAD(&input->entity.list_pipe); + } + + return 0; +} + +void vsp1_drm_cleanup(struct vsp1_device *vsp1) +{ + mutex_destroy(&vsp1->drm->lock); +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drm.h b/drivers/media/platform/renesas/vsp1/vsp1_drm.h new file mode 100644 index 000000000..ab8b7e316 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_drm.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_drm.h -- R-Car VSP1 DRM/KMS Interface + * + * Copyright (C) 2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_DRM_H__ +#define __VSP1_DRM_H__ + +#include +#include +#include + +#include + +#include "vsp1_pipe.h" + +/** + * struct vsp1_drm_pipeline - State for the API exposed to the DRM driver + * @pipe: the VSP1 pipeline used for display + * @width: output display width + * @height: output display height + * @force_brx_release: when set, release the BRx during the next reconfiguration + * @wait_queue: wait queue to wait for BRx release completion + * @uif: UIF entity if available for the pipeline + * @crc: CRC computation configuration + * @du_complete: frame completion callback for the DU driver (optional) + * @du_private: data to be passed to the du_complete callback + */ +struct vsp1_drm_pipeline { + struct vsp1_pipeline pipe; + + unsigned int width; + unsigned int height; + + bool force_brx_release; + wait_queue_head_t wait_queue; + + struct vsp1_entity *uif; + struct vsp1_du_crc_config crc; + + /* Frame synchronisation */ + void (*du_complete)(void *data, unsigned int status, u32 crc); + void *du_private; +}; + +/** + * struct vsp1_drm - State for the API exposed to the DRM driver + * @pipe: the VSP1 DRM pipeline used for display + * @lock: protects the BRU and BRS allocation + * @inputs: source crop rectangle, destination compose rectangle and z-order + * position for every input (indexed by RPF index) + */ +struct vsp1_drm { + struct vsp1_drm_pipeline pipe[VSP1_MAX_LIF]; + struct mutex lock; + + struct { + struct v4l2_rect crop; + struct v4l2_rect compose; + unsigned int zpos; + } inputs[VSP1_MAX_RPF]; +}; + +static inline struct vsp1_drm_pipeline * +to_vsp1_drm_pipeline(struct vsp1_pipeline *pipe) +{ + return container_of(pipe, struct vsp1_drm_pipeline, pipe); +} + +int vsp1_drm_init(struct vsp1_device *vsp1); +void vsp1_drm_cleanup(struct vsp1_device *vsp1); + +#endif /* __VSP1_DRM_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drv.c b/drivers/media/platform/renesas/vsp1/vsp1_drv.c new file mode 100644 index 000000000..c260d318d --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_drv.c @@ -0,0 +1,1015 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_drv.c -- R-Car VSP1 Driver + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "vsp1.h" +#include "vsp1_brx.h" +#include "vsp1_clu.h" +#include "vsp1_dl.h" +#include "vsp1_drm.h" +#include "vsp1_hgo.h" +#include "vsp1_hgt.h" +#include "vsp1_hsit.h" +#include "vsp1_lif.h" +#include "vsp1_lut.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" +#include "vsp1_sru.h" +#include "vsp1_uds.h" +#include "vsp1_uif.h" +#include "vsp1_video.h" + +/* ----------------------------------------------------------------------------- + * Interrupt Handling + */ + +static irqreturn_t vsp1_irq_handler(int irq, void *data) +{ + u32 mask = VI6_WPF_IRQ_STA_DFE | VI6_WPF_IRQ_STA_FRE; + struct vsp1_device *vsp1 = data; + irqreturn_t ret = IRQ_NONE; + unsigned int i; + u32 status; + + for (i = 0; i < vsp1->info->wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + + if (wpf == NULL) + continue; + + status = vsp1_read(vsp1, VI6_WPF_IRQ_STA(i)); + vsp1_write(vsp1, VI6_WPF_IRQ_STA(i), ~status & mask); + + if (status & VI6_WPF_IRQ_STA_DFE) { + vsp1_pipeline_frame_end(wpf->entity.pipe); + ret = IRQ_HANDLED; + } + } + + return ret; +} + +/* ----------------------------------------------------------------------------- + * Entities + */ + +/* + * vsp1_create_sink_links - Create links from all sources to the given sink + * + * This function creates media links from all valid sources to the given sink + * pad. Links that would be invalid according to the VSP1 hardware capabilities + * are skipped. Those include all links + * + * - from a UDS to a UDS (UDS entities can't be chained) + * - from an entity to itself (no loops are allowed) + * + * Furthermore, the BRS can't be connected to histogram generators, but no + * special check is currently needed as all VSP instances that include a BRS + * have no histogram generator. + */ +static int vsp1_create_sink_links(struct vsp1_device *vsp1, + struct vsp1_entity *sink) +{ + struct media_entity *entity = &sink->subdev.entity; + struct vsp1_entity *source; + unsigned int pad; + int ret; + + list_for_each_entry(source, &vsp1->entities, list_dev) { + u32 flags; + + if (source->type == sink->type) + continue; + + if (source->type == VSP1_ENTITY_HGO || + source->type == VSP1_ENTITY_HGT || + source->type == VSP1_ENTITY_LIF || + source->type == VSP1_ENTITY_WPF) + continue; + + flags = source->type == VSP1_ENTITY_RPF && + sink->type == VSP1_ENTITY_WPF && + source->index == sink->index + ? MEDIA_LNK_FL_ENABLED : 0; + + for (pad = 0; pad < entity->num_pads; ++pad) { + if (!(entity->pads[pad].flags & MEDIA_PAD_FL_SINK)) + continue; + + ret = media_create_pad_link(&source->subdev.entity, + source->source_pad, + entity, pad, flags); + if (ret < 0) + return ret; + + if (flags & MEDIA_LNK_FL_ENABLED) + source->sink = sink; + } + } + + return 0; +} + +static int vsp1_uapi_create_links(struct vsp1_device *vsp1) +{ + struct vsp1_entity *entity; + unsigned int i; + int ret; + + list_for_each_entry(entity, &vsp1->entities, list_dev) { + if (entity->type == VSP1_ENTITY_LIF || + entity->type == VSP1_ENTITY_RPF) + continue; + + ret = vsp1_create_sink_links(vsp1, entity); + if (ret < 0) + return ret; + } + + if (vsp1->hgo) { + ret = media_create_pad_link(&vsp1->hgo->histo.entity.subdev.entity, + HISTO_PAD_SOURCE, + &vsp1->hgo->histo.video.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) + return ret; + } + + if (vsp1->hgt) { + ret = media_create_pad_link(&vsp1->hgt->histo.entity.subdev.entity, + HISTO_PAD_SOURCE, + &vsp1->hgt->histo.video.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) + return ret; + } + + for (i = 0; i < vsp1->info->lif_count; ++i) { + if (!vsp1->lif[i]) + continue; + + ret = media_create_pad_link(&vsp1->wpf[i]->entity.subdev.entity, + RWPF_PAD_SOURCE, + &vsp1->lif[i]->entity.subdev.entity, + LIF_PAD_SINK, 0); + if (ret < 0) + return ret; + } + + for (i = 0; i < vsp1->info->rpf_count; ++i) { + struct vsp1_rwpf *rpf = vsp1->rpf[i]; + + ret = media_create_pad_link(&rpf->video->video.entity, 0, + &rpf->entity.subdev.entity, + RWPF_PAD_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) + return ret; + } + + for (i = 0; i < vsp1->info->wpf_count; ++i) { + /* + * Connect the video device to the WPF. All connections are + * immutable. + */ + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + + ret = media_create_pad_link(&wpf->entity.subdev.entity, + RWPF_PAD_SOURCE, + &wpf->video->video.entity, 0, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret < 0) + return ret; + } + + return 0; +} + +static void vsp1_destroy_entities(struct vsp1_device *vsp1) +{ + struct vsp1_entity *entity, *_entity; + struct vsp1_video *video, *_video; + + list_for_each_entry_safe(entity, _entity, &vsp1->entities, list_dev) { + list_del(&entity->list_dev); + vsp1_entity_destroy(entity); + } + + list_for_each_entry_safe(video, _video, &vsp1->videos, list) { + list_del(&video->list); + vsp1_video_cleanup(video); + } + + v4l2_device_unregister(&vsp1->v4l2_dev); + if (vsp1->info->uapi) + media_device_unregister(&vsp1->media_dev); + media_device_cleanup(&vsp1->media_dev); + + if (!vsp1->info->uapi) + vsp1_drm_cleanup(vsp1); +} + +static int vsp1_create_entities(struct vsp1_device *vsp1) +{ + struct media_device *mdev = &vsp1->media_dev; + struct v4l2_device *vdev = &vsp1->v4l2_dev; + struct vsp1_entity *entity; + unsigned int i; + int ret; + + mdev->dev = vsp1->dev; + mdev->hw_revision = vsp1->version; + strscpy(mdev->model, vsp1->info->model, sizeof(mdev->model)); + media_device_init(mdev); + + vsp1->media_ops.link_setup = vsp1_entity_link_setup; + /* + * Don't perform link validation when the userspace API is disabled as + * the pipeline is configured internally by the driver in that case, and + * its configuration can thus be trusted. + */ + if (vsp1->info->uapi) + vsp1->media_ops.link_validate = v4l2_subdev_link_validate; + + vdev->mdev = mdev; + ret = v4l2_device_register(vsp1->dev, vdev); + if (ret < 0) { + dev_err(vsp1->dev, "V4L2 device registration failed (%d)\n", + ret); + goto done; + } + + /* Instantiate all the entities. */ + if (vsp1_feature(vsp1, VSP1_HAS_BRS)) { + vsp1->brs = vsp1_brx_create(vsp1, VSP1_ENTITY_BRS); + if (IS_ERR(vsp1->brs)) { + ret = PTR_ERR(vsp1->brs); + goto done; + } + + list_add_tail(&vsp1->brs->entity.list_dev, &vsp1->entities); + } + + if (vsp1_feature(vsp1, VSP1_HAS_BRU)) { + vsp1->bru = vsp1_brx_create(vsp1, VSP1_ENTITY_BRU); + if (IS_ERR(vsp1->bru)) { + ret = PTR_ERR(vsp1->bru); + goto done; + } + + list_add_tail(&vsp1->bru->entity.list_dev, &vsp1->entities); + } + + if (vsp1_feature(vsp1, VSP1_HAS_CLU)) { + vsp1->clu = vsp1_clu_create(vsp1); + if (IS_ERR(vsp1->clu)) { + ret = PTR_ERR(vsp1->clu); + goto done; + } + + list_add_tail(&vsp1->clu->entity.list_dev, &vsp1->entities); + } + + vsp1->hsi = vsp1_hsit_create(vsp1, true); + if (IS_ERR(vsp1->hsi)) { + ret = PTR_ERR(vsp1->hsi); + goto done; + } + + list_add_tail(&vsp1->hsi->entity.list_dev, &vsp1->entities); + + vsp1->hst = vsp1_hsit_create(vsp1, false); + if (IS_ERR(vsp1->hst)) { + ret = PTR_ERR(vsp1->hst); + goto done; + } + + list_add_tail(&vsp1->hst->entity.list_dev, &vsp1->entities); + + if (vsp1_feature(vsp1, VSP1_HAS_HGO) && vsp1->info->uapi) { + vsp1->hgo = vsp1_hgo_create(vsp1); + if (IS_ERR(vsp1->hgo)) { + ret = PTR_ERR(vsp1->hgo); + goto done; + } + + list_add_tail(&vsp1->hgo->histo.entity.list_dev, + &vsp1->entities); + } + + if (vsp1_feature(vsp1, VSP1_HAS_HGT) && vsp1->info->uapi) { + vsp1->hgt = vsp1_hgt_create(vsp1); + if (IS_ERR(vsp1->hgt)) { + ret = PTR_ERR(vsp1->hgt); + goto done; + } + + list_add_tail(&vsp1->hgt->histo.entity.list_dev, + &vsp1->entities); + } + + /* + * The LIFs are only supported when used in conjunction with the DU, in + * which case the userspace API is disabled. If the userspace API is + * enabled skip the LIFs, even when present. + */ + if (!vsp1->info->uapi) { + for (i = 0; i < vsp1->info->lif_count; ++i) { + struct vsp1_lif *lif; + + lif = vsp1_lif_create(vsp1, i); + if (IS_ERR(lif)) { + ret = PTR_ERR(lif); + goto done; + } + + vsp1->lif[i] = lif; + list_add_tail(&lif->entity.list_dev, &vsp1->entities); + } + } + + if (vsp1_feature(vsp1, VSP1_HAS_LUT)) { + vsp1->lut = vsp1_lut_create(vsp1); + if (IS_ERR(vsp1->lut)) { + ret = PTR_ERR(vsp1->lut); + goto done; + } + + list_add_tail(&vsp1->lut->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->info->rpf_count; ++i) { + struct vsp1_rwpf *rpf; + + rpf = vsp1_rpf_create(vsp1, i); + if (IS_ERR(rpf)) { + ret = PTR_ERR(rpf); + goto done; + } + + vsp1->rpf[i] = rpf; + list_add_tail(&rpf->entity.list_dev, &vsp1->entities); + + if (vsp1->info->uapi) { + struct vsp1_video *video = vsp1_video_create(vsp1, rpf); + + if (IS_ERR(video)) { + ret = PTR_ERR(video); + goto done; + } + + list_add_tail(&video->list, &vsp1->videos); + } + } + + if (vsp1_feature(vsp1, VSP1_HAS_SRU)) { + vsp1->sru = vsp1_sru_create(vsp1); + if (IS_ERR(vsp1->sru)) { + ret = PTR_ERR(vsp1->sru); + goto done; + } + + list_add_tail(&vsp1->sru->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->info->uds_count; ++i) { + struct vsp1_uds *uds; + + uds = vsp1_uds_create(vsp1, i); + if (IS_ERR(uds)) { + ret = PTR_ERR(uds); + goto done; + } + + vsp1->uds[i] = uds; + list_add_tail(&uds->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->info->uif_count; ++i) { + struct vsp1_uif *uif; + + uif = vsp1_uif_create(vsp1, i); + if (IS_ERR(uif)) { + ret = PTR_ERR(uif); + goto done; + } + + vsp1->uif[i] = uif; + list_add_tail(&uif->entity.list_dev, &vsp1->entities); + } + + for (i = 0; i < vsp1->info->wpf_count; ++i) { + struct vsp1_rwpf *wpf; + + wpf = vsp1_wpf_create(vsp1, i); + if (IS_ERR(wpf)) { + ret = PTR_ERR(wpf); + goto done; + } + + vsp1->wpf[i] = wpf; + list_add_tail(&wpf->entity.list_dev, &vsp1->entities); + + if (vsp1->info->uapi) { + struct vsp1_video *video = vsp1_video_create(vsp1, wpf); + + if (IS_ERR(video)) { + ret = PTR_ERR(video); + goto done; + } + + list_add_tail(&video->list, &vsp1->videos); + } + } + + /* Register all subdevs. */ + list_for_each_entry(entity, &vsp1->entities, list_dev) { + ret = v4l2_device_register_subdev(&vsp1->v4l2_dev, + &entity->subdev); + if (ret < 0) + goto done; + } + + /* + * Create links and register subdev nodes if the userspace API is + * enabled or initialize the DRM pipeline otherwise. + */ + if (vsp1->info->uapi) { + ret = vsp1_uapi_create_links(vsp1); + if (ret < 0) + goto done; + + ret = v4l2_device_register_subdev_nodes(&vsp1->v4l2_dev); + if (ret < 0) + goto done; + + ret = media_device_register(mdev); + } else { + ret = vsp1_drm_init(vsp1); + } + +done: + if (ret < 0) + vsp1_destroy_entities(vsp1); + + return ret; +} + +int vsp1_reset_wpf(struct vsp1_device *vsp1, unsigned int index) +{ + unsigned int timeout; + u32 status; + + status = vsp1_read(vsp1, VI6_STATUS); + if (!(status & VI6_STATUS_SYS_ACT(index))) + return 0; + + vsp1_write(vsp1, VI6_SRESET, VI6_SRESET_SRTS(index)); + for (timeout = 10; timeout > 0; --timeout) { + status = vsp1_read(vsp1, VI6_STATUS); + if (!(status & VI6_STATUS_SYS_ACT(index))) + break; + + usleep_range(1000, 2000); + } + + if (!timeout) { + dev_err(vsp1->dev, "failed to reset wpf.%u\n", index); + return -ETIMEDOUT; + } + + return 0; +} + +static int vsp1_device_init(struct vsp1_device *vsp1) +{ + unsigned int i; + int ret; + + /* Reset any channel that might be running. */ + for (i = 0; i < vsp1->info->wpf_count; ++i) { + ret = vsp1_reset_wpf(vsp1, i); + if (ret < 0) + return ret; + } + + vsp1_write(vsp1, VI6_CLK_DCSWT, (8 << VI6_CLK_DCSWT_CSTPW_SHIFT) | + (8 << VI6_CLK_DCSWT_CSTRW_SHIFT)); + + for (i = 0; i < vsp1->info->rpf_count; ++i) + vsp1_write(vsp1, VI6_DPR_RPF_ROUTE(i), VI6_DPR_NODE_UNUSED); + + for (i = 0; i < vsp1->info->uds_count; ++i) + vsp1_write(vsp1, VI6_DPR_UDS_ROUTE(i), VI6_DPR_NODE_UNUSED); + + for (i = 0; i < vsp1->info->uif_count; ++i) + vsp1_write(vsp1, VI6_DPR_UIF_ROUTE(i), VI6_DPR_NODE_UNUSED); + + vsp1_write(vsp1, VI6_DPR_SRU_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_LUT_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_CLU_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_HST_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_HSI_ROUTE, VI6_DPR_NODE_UNUSED); + vsp1_write(vsp1, VI6_DPR_BRU_ROUTE, VI6_DPR_NODE_UNUSED); + + if (vsp1_feature(vsp1, VSP1_HAS_BRS)) + vsp1_write(vsp1, VI6_DPR_ILV_BRS_ROUTE, VI6_DPR_NODE_UNUSED); + + vsp1_write(vsp1, VI6_DPR_HGO_SMPPT, (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + vsp1_write(vsp1, VI6_DPR_HGT_SMPPT, (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + + vsp1_dlm_setup(vsp1); + + return 0; +} + +static void vsp1_mask_all_interrupts(struct vsp1_device *vsp1) +{ + unsigned int i; + + for (i = 0; i < vsp1->info->lif_count; ++i) + vsp1_write(vsp1, VI6_DISP_IRQ_ENB(i), 0); + for (i = 0; i < vsp1->info->wpf_count; ++i) + vsp1_write(vsp1, VI6_WPF_IRQ_ENB(i), 0); +} + +/* + * vsp1_device_get - Acquire the VSP1 device + * + * Make sure the device is not suspended and initialize it if needed. + * + * Return 0 on success or a negative error code otherwise. + */ +int vsp1_device_get(struct vsp1_device *vsp1) +{ + return pm_runtime_resume_and_get(vsp1->dev); +} + +/* + * vsp1_device_put - Release the VSP1 device + * + * Decrement the VSP1 reference count and cleanup the device if the last + * reference is released. + */ +void vsp1_device_put(struct vsp1_device *vsp1) +{ + pm_runtime_put_sync(vsp1->dev); +} + +/* ----------------------------------------------------------------------------- + * Power Management + */ + +static int __maybe_unused vsp1_pm_suspend(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + /* + * When used as part of a display pipeline, the VSP is stopped and + * restarted explicitly by the DU. + */ + if (!vsp1->drm) + vsp1_video_suspend(vsp1); + + pm_runtime_force_suspend(vsp1->dev); + + return 0; +} + +static int __maybe_unused vsp1_pm_resume(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + pm_runtime_force_resume(vsp1->dev); + + /* + * When used as part of a display pipeline, the VSP is stopped and + * restarted explicitly by the DU. + */ + if (!vsp1->drm) + vsp1_video_resume(vsp1); + + return 0; +} + +static int __maybe_unused vsp1_pm_runtime_suspend(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + rcar_fcp_disable(vsp1->fcp); + reset_control_assert(vsp1->rstc); + + return 0; +} + +static int __maybe_unused vsp1_pm_runtime_resume(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(vsp1->rstc); + if (ret < 0) + return ret; + + if (vsp1->info) { + /* + * On R-Car Gen2 and RZ/G1, vsp1 register access after deassert + * can cause lock-up. It is a special case and needs some delay + * to avoid this lock-up. + */ + if (vsp1->info->gen == 2) + udelay(1); + + ret = vsp1_device_init(vsp1); + if (ret < 0) + goto done; + } + + ret = rcar_fcp_enable(vsp1->fcp); + +done: + if (ret < 0) + reset_control_assert(vsp1->rstc); + + return ret; +} + +static const struct dev_pm_ops vsp1_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(vsp1_pm_suspend, vsp1_pm_resume) + SET_RUNTIME_PM_OPS(vsp1_pm_runtime_suspend, vsp1_pm_runtime_resume, NULL) +}; + +/* ----------------------------------------------------------------------------- + * Platform Driver + */ + +static const struct vsp1_device_info vsp1_device_infos[] = { + { + .version = VI6_IP_VERSION_MODEL_VSPS_H2, + .model = "VSP1-S", + .gen = 2, + .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_HGO + | VSP1_HAS_HGT | VSP1_HAS_LUT | VSP1_HAS_SRU + | VSP1_HAS_WPF_VFLIP, + .rpf_count = 5, + .uds_count = 3, + .wpf_count = 4, + .num_bru_inputs = 4, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPR_H2, + .model = "VSP1-R", + .gen = 2, + .features = VSP1_HAS_BRU | VSP1_HAS_SRU | VSP1_HAS_WPF_VFLIP, + .rpf_count = 5, + .uds_count = 3, + .wpf_count = 4, + .num_bru_inputs = 4, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPD_GEN2, + .model = "VSP1-D", + .gen = 2, + .features = VSP1_HAS_BRU | VSP1_HAS_HGO | VSP1_HAS_LUT, + .lif_count = 1, + .rpf_count = 4, + .uds_count = 1, + .wpf_count = 1, + .num_bru_inputs = 4, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPS_M2, + .model = "VSP1-S", + .gen = 2, + .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_HGO + | VSP1_HAS_HGT | VSP1_HAS_LUT | VSP1_HAS_SRU + | VSP1_HAS_WPF_VFLIP, + .rpf_count = 5, + .uds_count = 1, + .wpf_count = 4, + .num_bru_inputs = 4, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPS_V2H, + .model = "VSP1V-S", + .gen = 2, + .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_LUT + | VSP1_HAS_SRU | VSP1_HAS_WPF_VFLIP, + .rpf_count = 4, + .uds_count = 1, + .wpf_count = 4, + .num_bru_inputs = 4, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPD_V2H, + .model = "VSP1V-D", + .gen = 2, + .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_LUT, + .lif_count = 1, + .rpf_count = 4, + .uds_count = 1, + .wpf_count = 1, + .num_bru_inputs = 4, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPI_GEN3, + .model = "VSP2-I", + .gen = 3, + .features = VSP1_HAS_CLU | VSP1_HAS_HGO | VSP1_HAS_HGT + | VSP1_HAS_LUT | VSP1_HAS_SRU | VSP1_HAS_WPF_HFLIP + | VSP1_HAS_WPF_VFLIP, + .rpf_count = 1, + .uds_count = 1, + .wpf_count = 1, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPBD_GEN3, + .model = "VSP2-BD", + .gen = 3, + .features = VSP1_HAS_BRU | VSP1_HAS_WPF_VFLIP, + .rpf_count = 5, + .wpf_count = 1, + .num_bru_inputs = 5, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPBC_GEN3, + .model = "VSP2-BC", + .gen = 3, + .features = VSP1_HAS_BRU | VSP1_HAS_CLU | VSP1_HAS_HGO + | VSP1_HAS_LUT | VSP1_HAS_WPF_VFLIP, + .rpf_count = 5, + .wpf_count = 1, + .num_bru_inputs = 5, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPBS_GEN3, + .model = "VSP2-BS", + .gen = 3, + .features = VSP1_HAS_BRS | VSP1_HAS_WPF_VFLIP, + .rpf_count = 2, + .wpf_count = 1, + .uapi = true, + }, { + .version = VI6_IP_VERSION_MODEL_VSPD_GEN3, + .model = "VSP2-D", + .gen = 3, + .features = VSP1_HAS_BRU | VSP1_HAS_WPF_VFLIP | VSP1_HAS_EXT_DL, + .lif_count = 1, + .rpf_count = 5, + .uif_count = 1, + .wpf_count = 2, + .num_bru_inputs = 5, + }, { + .version = VI6_IP_VERSION_MODEL_VSPD_V3, + .model = "VSP2-D", + .soc = VI6_IP_VERSION_SOC_V3H, + .gen = 3, + .features = VSP1_HAS_BRS | VSP1_HAS_BRU, + .lif_count = 1, + .rpf_count = 5, + .uif_count = 1, + .wpf_count = 1, + .num_bru_inputs = 5, + }, { + .version = VI6_IP_VERSION_MODEL_VSPD_V3, + .model = "VSP2-D", + .soc = VI6_IP_VERSION_SOC_V3M, + .gen = 3, + .features = VSP1_HAS_BRS | VSP1_HAS_BRU | VSP1_HAS_NON_ZERO_LBA, + .lif_count = 1, + .rpf_count = 5, + .uif_count = 1, + .wpf_count = 1, + .num_bru_inputs = 5, + }, { + .version = VI6_IP_VERSION_MODEL_VSPDL_GEN3, + .model = "VSP2-DL", + .gen = 3, + .features = VSP1_HAS_BRS | VSP1_HAS_BRU | VSP1_HAS_EXT_DL, + .lif_count = 2, + .rpf_count = 5, + .uif_count = 2, + .wpf_count = 2, + .num_bru_inputs = 5, + }, { + .version = VI6_IP_VERSION_MODEL_VSPD_V3U, + .model = "VSP2-D", + .gen = 3, + .features = VSP1_HAS_BRU | VSP1_HAS_EXT_DL, + .lif_count = 1, + .rpf_count = 5, + .uif_count = 2, + .wpf_count = 1, + .num_bru_inputs = 5, + }, +}; + +static const struct vsp1_device_info rzg2l_vsp2_device_info = { + .version = VI6_IP_VERSION_MODEL_VSPD_RZG2L, + .model = "VSP2-D", + .soc = VI6_IP_VERSION_SOC_RZG2L, + .gen = 3, + .features = VSP1_HAS_BRS | VSP1_HAS_WPF_VFLIP | VSP1_HAS_EXT_DL + | VSP1_HAS_NON_ZERO_LBA, + .lif_count = 1, + .rpf_count = 2, + .wpf_count = 1, +}; + +static const struct vsp1_device_info *vsp1_lookup_info(struct vsp1_device *vsp1) +{ + const struct vsp1_device_info *info; + unsigned int i; + u32 model; + u32 soc; + + /* + * Try the info stored in match data first for devices that don't have + * a version register. + */ + info = of_device_get_match_data(vsp1->dev); + if (info) { + vsp1->version = VI6_IP_VERSION_VSP_SW | info->version | info->soc; + return info; + } + + vsp1->version = vsp1_read(vsp1, VI6_IP_VERSION); + model = vsp1->version & VI6_IP_VERSION_MODEL_MASK; + soc = vsp1->version & VI6_IP_VERSION_SOC_MASK; + + for (i = 0; i < ARRAY_SIZE(vsp1_device_infos); ++i) { + info = &vsp1_device_infos[i]; + + if (model == info->version && (!info->soc || soc == info->soc)) + return info; + } + + dev_err(vsp1->dev, "unsupported IP version 0x%08x\n", vsp1->version); + + return NULL; +} + +static int vsp1_probe(struct platform_device *pdev) +{ + struct vsp1_device *vsp1; + struct device_node *fcp_node; + int ret; + int irq; + + vsp1 = devm_kzalloc(&pdev->dev, sizeof(*vsp1), GFP_KERNEL); + if (vsp1 == NULL) + return -ENOMEM; + + vsp1->dev = &pdev->dev; + INIT_LIST_HEAD(&vsp1->entities); + INIT_LIST_HEAD(&vsp1->videos); + + platform_set_drvdata(pdev, vsp1); + + /* I/O and IRQ resources (clock managed by the clock PM domain). */ + vsp1->mmio = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(vsp1->mmio)) + return PTR_ERR(vsp1->mmio); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + vsp1->rstc = devm_reset_control_get_shared(&pdev->dev, NULL); + if (IS_ERR(vsp1->rstc)) + return dev_err_probe(&pdev->dev, PTR_ERR(vsp1->rstc), + "failed to get reset control\n"); + + /* FCP (optional). */ + fcp_node = of_parse_phandle(pdev->dev.of_node, "renesas,fcp", 0); + if (fcp_node) { + vsp1->fcp = rcar_fcp_get(fcp_node); + of_node_put(fcp_node); + if (IS_ERR(vsp1->fcp)) { + dev_dbg(&pdev->dev, "FCP not found (%ld)\n", + PTR_ERR(vsp1->fcp)); + return PTR_ERR(vsp1->fcp); + } + + /* + * When the FCP is present, it handles all bus master accesses + * for the VSP and must thus be used in place of the VSP device + * to map DMA buffers. + */ + vsp1->bus_master = rcar_fcp_get_device(vsp1->fcp); + } else { + vsp1->bus_master = vsp1->dev; + } + + /* Configure device parameters based on the version register. */ + pm_runtime_enable(&pdev->dev); + + ret = vsp1_device_get(vsp1); + if (ret < 0) + goto done; + + vsp1->info = vsp1_lookup_info(vsp1); + if (!vsp1->info) { + vsp1_device_put(vsp1); + ret = -ENXIO; + goto done; + } + + dev_dbg(&pdev->dev, "IP version 0x%08x\n", vsp1->version); + + /* + * Previous use of the hardware (e.g. by the bootloader) could leave + * some interrupts enabled and pending. + * + * TODO: Investigate if this shouldn't be better handled by using the + * device reset provided by the CPG. + */ + vsp1_mask_all_interrupts(vsp1); + + vsp1_device_put(vsp1); + + ret = devm_request_irq(&pdev->dev, irq, vsp1_irq_handler, + IRQF_SHARED, dev_name(&pdev->dev), vsp1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to request IRQ\n"); + goto done; + } + + /* Instantiate entities. */ + ret = vsp1_create_entities(vsp1); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create entities\n"); + goto done; + } + +done: + if (ret) { + pm_runtime_disable(&pdev->dev); + rcar_fcp_put(vsp1->fcp); + } + + return ret; +} + +static int vsp1_remove(struct platform_device *pdev) +{ + struct vsp1_device *vsp1 = platform_get_drvdata(pdev); + + vsp1_destroy_entities(vsp1); + rcar_fcp_put(vsp1->fcp); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static const struct of_device_id vsp1_of_match[] = { + { .compatible = "renesas,vsp1" }, + { .compatible = "renesas,vsp2" }, + { .compatible = "renesas,r9a07g044-vsp2", .data = &rzg2l_vsp2_device_info }, + { }, +}; +MODULE_DEVICE_TABLE(of, vsp1_of_match); + +static struct platform_driver vsp1_platform_driver = { + .probe = vsp1_probe, + .remove = vsp1_remove, + .driver = { + .name = "vsp1", + .pm = &vsp1_pm_ops, + .of_match_table = vsp1_of_match, + }, +}; + +module_platform_driver(vsp1_platform_driver); + +MODULE_ALIAS("vsp1"); +MODULE_AUTHOR("Laurent Pinchart "); +MODULE_DESCRIPTION("Renesas VSP1 Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/renesas/vsp1/vsp1_entity.c b/drivers/media/platform/renesas/vsp1/vsp1_entity.c new file mode 100644 index 000000000..c31f05a80 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_entity.c @@ -0,0 +1,700 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_entity.c -- R-Car VSP1 Base Entity + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include +#include +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_entity.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" + +void vsp1_entity_route_setup(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb) +{ + struct vsp1_entity *source; + u32 route; + + if (entity->type == VSP1_ENTITY_HGO) { + u32 smppt; + + /* + * The HGO is a special case, its routing is configured on the + * sink pad. + */ + source = entity->sources[0]; + smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT) + | (source->route->output << VI6_DPR_SMPPT_PT_SHIFT); + + vsp1_dl_body_write(dlb, VI6_DPR_HGO_SMPPT, smppt); + return; + } else if (entity->type == VSP1_ENTITY_HGT) { + u32 smppt; + + /* + * The HGT is a special case, its routing is configured on the + * sink pad. + */ + source = entity->sources[0]; + smppt = (pipe->output->entity.index << VI6_DPR_SMPPT_TGW_SHIFT) + | (source->route->output << VI6_DPR_SMPPT_PT_SHIFT); + + vsp1_dl_body_write(dlb, VI6_DPR_HGT_SMPPT, smppt); + return; + } + + source = entity; + if (source->route->reg == 0) + return; + + route = source->sink->route->inputs[source->sink_pad]; + /* + * The ILV and BRS share the same data path route. The extra BRSSEL bit + * selects between the ILV and BRS. + */ + if (source->type == VSP1_ENTITY_BRS) + route |= VI6_DPR_ROUTE_BRSSEL; + vsp1_dl_body_write(dlb, source->route->reg, route); +} + +void vsp1_entity_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + if (entity->ops->configure_stream) + entity->ops->configure_stream(entity, pipe, dl, dlb); +} + +void vsp1_entity_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + if (entity->ops->configure_frame) + entity->ops->configure_frame(entity, pipe, dl, dlb); +} + +void vsp1_entity_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + if (entity->ops->configure_partition) + entity->ops->configure_partition(entity, pipe, dl, dlb); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +/** + * vsp1_entity_get_pad_config - Get the pad configuration for an entity + * @entity: the entity + * @sd_state: the TRY state + * @which: configuration selector (ACTIVE or TRY) + * + * When called with which set to V4L2_SUBDEV_FORMAT_ACTIVE the caller must hold + * the entity lock to access the returned configuration. + * + * Return the pad configuration requested by the which argument. The TRY + * configuration is passed explicitly to the function through the cfg argument + * and simply returned when requested. The ACTIVE configuration comes from the + * entity structure. + */ +struct v4l2_subdev_state * +vsp1_entity_get_pad_config(struct vsp1_entity *entity, + struct v4l2_subdev_state *sd_state, + enum v4l2_subdev_format_whence which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_ACTIVE: + return entity->config; + case V4L2_SUBDEV_FORMAT_TRY: + default: + return sd_state; + } +} + +/** + * vsp1_entity_get_pad_format - Get a pad format from storage for an entity + * @entity: the entity + * @sd_state: the state storage + * @pad: the pad number + * + * Return the format stored in the given configuration for an entity's pad. The + * configuration can be an ACTIVE or TRY configuration. + */ +struct v4l2_mbus_framefmt * +vsp1_entity_get_pad_format(struct vsp1_entity *entity, + struct v4l2_subdev_state *sd_state, + unsigned int pad) +{ + return v4l2_subdev_get_try_format(&entity->subdev, sd_state, pad); +} + +/** + * vsp1_entity_get_pad_selection - Get a pad selection from storage for entity + * @entity: the entity + * @sd_state: the state storage + * @pad: the pad number + * @target: the selection target + * + * Return the selection rectangle stored in the given configuration for an + * entity's pad. The configuration can be an ACTIVE or TRY configuration. The + * selection target can be COMPOSE or CROP. + */ +struct v4l2_rect * +vsp1_entity_get_pad_selection(struct vsp1_entity *entity, + struct v4l2_subdev_state *sd_state, + unsigned int pad, unsigned int target) +{ + switch (target) { + case V4L2_SEL_TGT_COMPOSE: + return v4l2_subdev_get_try_compose(&entity->subdev, sd_state, + pad); + case V4L2_SEL_TGT_CROP: + return v4l2_subdev_get_try_crop(&entity->subdev, sd_state, + pad); + default: + return NULL; + } +} + +/* + * vsp1_entity_init_cfg - Initialize formats on all pads + * @subdev: V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * + * Initialize all pad formats with default values in the given pad config. This + * function can be used as a handler for the subdev pad::init_cfg operation. + */ +int vsp1_entity_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state) +{ + unsigned int pad; + + for (pad = 0; pad < subdev->entity.num_pads - 1; ++pad) { + struct v4l2_subdev_format format = { + .pad = pad, + .which = sd_state ? V4L2_SUBDEV_FORMAT_TRY + : V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + v4l2_subdev_call(subdev, pad, set_fmt, sd_state, &format); + } + + return 0; +} + +/* + * vsp1_subdev_get_pad_format - Subdev pad get_fmt handler + * @subdev: V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: V4L2 subdev format + * + * This function implements the subdev get_fmt pad operation. It can be used as + * a direct drop-in for the operation handler. + */ +int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_entity *entity = to_vsp1_entity(subdev); + struct v4l2_subdev_state *config; + + config = vsp1_entity_get_pad_config(entity, sd_state, fmt->which); + if (!config) + return -EINVAL; + + mutex_lock(&entity->lock); + fmt->format = *vsp1_entity_get_pad_format(entity, config, fmt->pad); + mutex_unlock(&entity->lock); + + return 0; +} + +/* + * vsp1_subdev_enum_mbus_code - Subdev pad enum_mbus_code handler + * @subdev: V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @code: Media bus code enumeration + * @codes: Array of supported media bus codes + * @ncodes: Number of supported media bus codes + * + * This function implements the subdev enum_mbus_code pad operation for entities + * that do not support format conversion. It enumerates the given supported + * media bus codes on the sink pad and reports a source pad format identical to + * the sink pad. + */ +int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code, + const unsigned int *codes, unsigned int ncodes) +{ + struct vsp1_entity *entity = to_vsp1_entity(subdev); + + if (code->pad == 0) { + if (code->index >= ncodes) + return -EINVAL; + + code->code = codes[code->index]; + } else { + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + + /* + * The entity can't perform format conversion, the sink format + * is always identical to the source format. + */ + if (code->index) + return -EINVAL; + + config = vsp1_entity_get_pad_config(entity, sd_state, + code->which); + if (!config) + return -EINVAL; + + mutex_lock(&entity->lock); + format = vsp1_entity_get_pad_format(entity, config, 0); + code->code = format->code; + mutex_unlock(&entity->lock); + } + + return 0; +} + +/* + * vsp1_subdev_enum_frame_size - Subdev pad enum_frame_size handler + * @subdev: V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fse: Frame size enumeration + * @min_width: Minimum image width + * @min_height: Minimum image height + * @max_width: Maximum image width + * @max_height: Maximum image height + * + * This function implements the subdev enum_frame_size pad operation for + * entities that do not support scaling or cropping. It reports the given + * minimum and maximum frame width and height on the sink pad, and a fixed + * source pad size identical to the sink pad. + */ +int vsp1_subdev_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse, + unsigned int min_width, unsigned int min_height, + unsigned int max_width, unsigned int max_height) +{ + struct vsp1_entity *entity = to_vsp1_entity(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + config = vsp1_entity_get_pad_config(entity, sd_state, fse->which); + if (!config) + return -EINVAL; + + format = vsp1_entity_get_pad_format(entity, config, fse->pad); + + mutex_lock(&entity->lock); + + if (fse->index || fse->code != format->code) { + ret = -EINVAL; + goto done; + } + + if (fse->pad == 0) { + fse->min_width = min_width; + fse->max_width = max_width; + fse->min_height = min_height; + fse->max_height = max_height; + } else { + /* + * The size on the source pad are fixed and always identical to + * the size on the sink pad. + */ + fse->min_width = format->width; + fse->max_width = format->width; + fse->min_height = format->height; + fse->max_height = format->height; + } + +done: + mutex_unlock(&entity->lock); + return ret; +} + +/* + * vsp1_subdev_set_pad_format - Subdev pad set_fmt handler + * @subdev: V4L2 subdevice + * @cfg: V4L2 subdev pad configuration + * @fmt: V4L2 subdev format + * @codes: Array of supported media bus codes + * @ncodes: Number of supported media bus codes + * @min_width: Minimum image width + * @min_height: Minimum image height + * @max_width: Maximum image width + * @max_height: Maximum image height + * + * This function implements the subdev set_fmt pad operation for entities that + * do not support scaling or cropping. It defaults to the first supplied media + * bus code if the requested code isn't supported, clamps the size to the + * supplied minimum and maximum, and propagates the sink pad format to the + * source pad. + */ +int vsp1_subdev_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt, + const unsigned int *codes, unsigned int ncodes, + unsigned int min_width, unsigned int min_height, + unsigned int max_width, unsigned int max_height) +{ + struct vsp1_entity *entity = to_vsp1_entity(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *selection; + unsigned int i; + int ret = 0; + + mutex_lock(&entity->lock); + + config = vsp1_entity_get_pad_config(entity, sd_state, fmt->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + format = vsp1_entity_get_pad_format(entity, config, fmt->pad); + + if (fmt->pad == entity->source_pad) { + /* The output format can't be modified. */ + fmt->format = *format; + goto done; + } + + /* + * Default to the first media bus code if the requested format is not + * supported. + */ + for (i = 0; i < ncodes; ++i) { + if (fmt->format.code == codes[i]) + break; + } + + format->code = i < ncodes ? codes[i] : codes[0]; + format->width = clamp_t(unsigned int, fmt->format.width, + min_width, max_width); + format->height = clamp_t(unsigned int, fmt->format.height, + min_height, max_height); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(entity, config, entity->source_pad); + *format = fmt->format; + + /* Reset the crop and compose rectangles. */ + selection = vsp1_entity_get_pad_selection(entity, config, fmt->pad, + V4L2_SEL_TGT_CROP); + selection->left = 0; + selection->top = 0; + selection->width = format->width; + selection->height = format->height; + + selection = vsp1_entity_get_pad_selection(entity, config, fmt->pad, + V4L2_SEL_TGT_COMPOSE); + selection->left = 0; + selection->top = 0; + selection->width = format->width; + selection->height = format->height; + +done: + mutex_unlock(&entity->lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * Media Operations + */ + +static inline struct vsp1_entity * +media_entity_to_vsp1_entity(struct media_entity *entity) +{ + return container_of(entity, struct vsp1_entity, subdev.entity); +} + +static int vsp1_entity_link_setup_source(const struct media_pad *source_pad, + const struct media_pad *sink_pad, + u32 flags) +{ + struct vsp1_entity *source; + + source = media_entity_to_vsp1_entity(source_pad->entity); + + if (!source->route) + return 0; + + if (flags & MEDIA_LNK_FL_ENABLED) { + struct vsp1_entity *sink + = media_entity_to_vsp1_entity(sink_pad->entity); + + /* + * Fan-out is limited to one for the normal data path plus + * optional HGO and HGT. We ignore the HGO and HGT here. + */ + if (sink->type != VSP1_ENTITY_HGO && + sink->type != VSP1_ENTITY_HGT) { + if (source->sink) + return -EBUSY; + source->sink = sink; + source->sink_pad = sink_pad->index; + } + } else { + source->sink = NULL; + source->sink_pad = 0; + } + + return 0; +} + +static int vsp1_entity_link_setup_sink(const struct media_pad *source_pad, + const struct media_pad *sink_pad, + u32 flags) +{ + struct vsp1_entity *sink; + struct vsp1_entity *source; + + sink = media_entity_to_vsp1_entity(sink_pad->entity); + source = media_entity_to_vsp1_entity(source_pad->entity); + + if (flags & MEDIA_LNK_FL_ENABLED) { + /* Fan-in is limited to one. */ + if (sink->sources[sink_pad->index]) + return -EBUSY; + + sink->sources[sink_pad->index] = source; + } else { + sink->sources[sink_pad->index] = NULL; + } + + return 0; +} + +int vsp1_entity_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + if (local->flags & MEDIA_PAD_FL_SOURCE) + return vsp1_entity_link_setup_source(local, remote, flags); + else + return vsp1_entity_link_setup_sink(remote, local, flags); +} + +/** + * vsp1_entity_remote_pad - Find the pad at the remote end of a link + * @pad: Pad at the local end of the link + * + * Search for a remote pad connected to the given pad by iterating over all + * links originating or terminating at that pad until an enabled link is found. + * + * Our link setup implementation guarantees that the output fan-out will not be + * higher than one for the data pipelines, except for the links to the HGO and + * HGT that can be enabled in addition to a regular data link. When traversing + * outgoing links this function ignores HGO and HGT entities and should thus be + * used in place of the generic media_pad_remote_pad_first() function to + * traverse data pipelines. + * + * Return a pointer to the pad at the remote end of the first found enabled + * link, or NULL if no enabled link has been found. + */ +struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad) +{ + struct media_link *link; + + list_for_each_entry(link, &pad->entity->links, list) { + struct vsp1_entity *entity; + + if (!(link->flags & MEDIA_LNK_FL_ENABLED)) + continue; + + /* If we're the sink the source will never be an HGO or HGT. */ + if (link->sink == pad) + return link->source; + + if (link->source != pad) + continue; + + /* If the sink isn't a subdevice it can't be an HGO or HGT. */ + if (!is_media_entity_v4l2_subdev(link->sink->entity)) + return link->sink; + + entity = media_entity_to_vsp1_entity(link->sink->entity); + if (entity->type != VSP1_ENTITY_HGO && + entity->type != VSP1_ENTITY_HGT) + return link->sink; + } + + return NULL; + +} + +/* ----------------------------------------------------------------------------- + * Initialization + */ + +#define VSP1_ENTITY_ROUTE(ent) \ + { VSP1_ENTITY_##ent, 0, VI6_DPR_##ent##_ROUTE, \ + { VI6_DPR_NODE_##ent }, VI6_DPR_NODE_##ent } + +#define VSP1_ENTITY_ROUTE_RPF(idx) \ + { VSP1_ENTITY_RPF, idx, VI6_DPR_RPF_ROUTE(idx), \ + { 0, }, VI6_DPR_NODE_RPF(idx) } + +#define VSP1_ENTITY_ROUTE_UDS(idx) \ + { VSP1_ENTITY_UDS, idx, VI6_DPR_UDS_ROUTE(idx), \ + { VI6_DPR_NODE_UDS(idx) }, VI6_DPR_NODE_UDS(idx) } + +#define VSP1_ENTITY_ROUTE_UIF(idx) \ + { VSP1_ENTITY_UIF, idx, VI6_DPR_UIF_ROUTE(idx), \ + { VI6_DPR_NODE_UIF(idx) }, VI6_DPR_NODE_UIF(idx) } + +#define VSP1_ENTITY_ROUTE_WPF(idx) \ + { VSP1_ENTITY_WPF, idx, 0, \ + { VI6_DPR_NODE_WPF(idx) }, VI6_DPR_NODE_WPF(idx) } + +static const struct vsp1_route vsp1_routes[] = { + { VSP1_ENTITY_BRS, 0, VI6_DPR_ILV_BRS_ROUTE, + { VI6_DPR_NODE_BRS_IN(0), VI6_DPR_NODE_BRS_IN(1) }, 0 }, + { VSP1_ENTITY_BRU, 0, VI6_DPR_BRU_ROUTE, + { VI6_DPR_NODE_BRU_IN(0), VI6_DPR_NODE_BRU_IN(1), + VI6_DPR_NODE_BRU_IN(2), VI6_DPR_NODE_BRU_IN(3), + VI6_DPR_NODE_BRU_IN(4) }, VI6_DPR_NODE_BRU_OUT }, + VSP1_ENTITY_ROUTE(CLU), + { VSP1_ENTITY_HGO, 0, 0, { 0, }, 0 }, + { VSP1_ENTITY_HGT, 0, 0, { 0, }, 0 }, + VSP1_ENTITY_ROUTE(HSI), + VSP1_ENTITY_ROUTE(HST), + { VSP1_ENTITY_LIF, 0, 0, { 0, }, 0 }, + { VSP1_ENTITY_LIF, 1, 0, { 0, }, 0 }, + VSP1_ENTITY_ROUTE(LUT), + VSP1_ENTITY_ROUTE_RPF(0), + VSP1_ENTITY_ROUTE_RPF(1), + VSP1_ENTITY_ROUTE_RPF(2), + VSP1_ENTITY_ROUTE_RPF(3), + VSP1_ENTITY_ROUTE_RPF(4), + VSP1_ENTITY_ROUTE(SRU), + VSP1_ENTITY_ROUTE_UDS(0), + VSP1_ENTITY_ROUTE_UDS(1), + VSP1_ENTITY_ROUTE_UDS(2), + VSP1_ENTITY_ROUTE_UIF(0), /* Named UIF4 in the documentation */ + VSP1_ENTITY_ROUTE_UIF(1), /* Named UIF5 in the documentation */ + VSP1_ENTITY_ROUTE_WPF(0), + VSP1_ENTITY_ROUTE_WPF(1), + VSP1_ENTITY_ROUTE_WPF(2), + VSP1_ENTITY_ROUTE_WPF(3), +}; + +int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, + const char *name, unsigned int num_pads, + const struct v4l2_subdev_ops *ops, u32 function) +{ + static struct lock_class_key key; + struct v4l2_subdev *subdev; + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(vsp1_routes); ++i) { + if (vsp1_routes[i].type == entity->type && + vsp1_routes[i].index == entity->index) { + entity->route = &vsp1_routes[i]; + break; + } + } + + if (i == ARRAY_SIZE(vsp1_routes)) + return -EINVAL; + + mutex_init(&entity->lock); + + entity->vsp1 = vsp1; + entity->source_pad = num_pads - 1; + + /* Allocate and initialize pads. */ + entity->pads = devm_kcalloc(vsp1->dev, + num_pads, sizeof(*entity->pads), + GFP_KERNEL); + if (entity->pads == NULL) + return -ENOMEM; + + for (i = 0; i < num_pads - 1; ++i) + entity->pads[i].flags = MEDIA_PAD_FL_SINK; + + entity->sources = devm_kcalloc(vsp1->dev, max(num_pads - 1, 1U), + sizeof(*entity->sources), GFP_KERNEL); + if (entity->sources == NULL) + return -ENOMEM; + + /* Single-pad entities only have a sink. */ + entity->pads[num_pads - 1].flags = num_pads > 1 ? MEDIA_PAD_FL_SOURCE + : MEDIA_PAD_FL_SINK; + + /* Initialize the media entity. */ + ret = media_entity_pads_init(&entity->subdev.entity, num_pads, + entity->pads); + if (ret < 0) + return ret; + + /* Initialize the V4L2 subdev. */ + subdev = &entity->subdev; + v4l2_subdev_init(subdev, ops); + + subdev->entity.function = function; + subdev->entity.ops = &vsp1->media_ops; + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + + snprintf(subdev->name, sizeof(subdev->name), "%s %s", + dev_name(vsp1->dev), name); + + vsp1_entity_init_cfg(subdev, NULL); + + /* + * Allocate the pad configuration to store formats and selection + * rectangles. + */ + /* + * FIXME: Drop this call, drivers are not supposed to use + * __v4l2_subdev_state_alloc(). + */ + entity->config = __v4l2_subdev_state_alloc(&entity->subdev, + "vsp1:config->lock", &key); + if (IS_ERR(entity->config)) { + media_entity_cleanup(&entity->subdev.entity); + return PTR_ERR(entity->config); + } + + return 0; +} + +void vsp1_entity_destroy(struct vsp1_entity *entity) +{ + if (entity->ops && entity->ops->destroy) + entity->ops->destroy(entity); + if (entity->subdev.ctrl_handler) + v4l2_ctrl_handler_free(entity->subdev.ctrl_handler); + __v4l2_subdev_state_free(entity->config); + media_entity_cleanup(&entity->subdev.entity); +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_entity.h b/drivers/media/platform/renesas/vsp1/vsp1_entity.h new file mode 100644 index 000000000..f22724439 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_entity.h @@ -0,0 +1,194 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_entity.h -- R-Car VSP1 Base Entity + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_ENTITY_H__ +#define __VSP1_ENTITY_H__ + +#include +#include + +#include + +struct vsp1_device; +struct vsp1_dl_body; +struct vsp1_dl_list; +struct vsp1_pipeline; +struct vsp1_partition; +struct vsp1_partition_window; + +enum vsp1_entity_type { + VSP1_ENTITY_BRS, + VSP1_ENTITY_BRU, + VSP1_ENTITY_CLU, + VSP1_ENTITY_HGO, + VSP1_ENTITY_HGT, + VSP1_ENTITY_HSI, + VSP1_ENTITY_HST, + VSP1_ENTITY_LIF, + VSP1_ENTITY_LUT, + VSP1_ENTITY_RPF, + VSP1_ENTITY_SRU, + VSP1_ENTITY_UDS, + VSP1_ENTITY_UIF, + VSP1_ENTITY_WPF, +}; + +#define VSP1_ENTITY_MAX_INPUTS 5 /* For the BRU */ + +/* + * struct vsp1_route - Entity routing configuration + * @type: Entity type this routing entry is associated with + * @index: Entity index this routing entry is associated with + * @reg: Output routing configuration register + * @inputs: Target node value for each input + * @output: Target node value for entity output + * + * Each $vsp1_route entry describes routing configuration for the entity + * specified by the entry's @type and @index. @reg indicates the register that + * holds output routing configuration for the entity, and the @inputs array + * store the target node value for each input of the entity. The @output field + * stores the target node value of the entity output when used as a source for + * histogram generation. + */ +struct vsp1_route { + enum vsp1_entity_type type; + unsigned int index; + unsigned int reg; + unsigned int inputs[VSP1_ENTITY_MAX_INPUTS]; + unsigned int output; +}; + +/** + * struct vsp1_entity_operations - Entity operations + * @destroy: Destroy the entity. + * @configure_stream: Setup the hardware parameters for the stream which do + * not vary between frames (pipeline, formats). Note that + * the vsp1_dl_list argument is only valid for display + * pipeline and will be NULL for mem-to-mem pipelines. + * @configure_frame: Configure the runtime parameters for each frame. + * @configure_partition: Configure partition specific parameters. + * @max_width: Return the max supported width of data that the entity can + * process in a single operation. + * @partition: Process the partition construction based on this entity's + * configuration. + */ +struct vsp1_entity_operations { + void (*destroy)(struct vsp1_entity *); + void (*configure_stream)(struct vsp1_entity *, struct vsp1_pipeline *, + struct vsp1_dl_list *, struct vsp1_dl_body *); + void (*configure_frame)(struct vsp1_entity *, struct vsp1_pipeline *, + struct vsp1_dl_list *, struct vsp1_dl_body *); + void (*configure_partition)(struct vsp1_entity *, + struct vsp1_pipeline *, + struct vsp1_dl_list *, + struct vsp1_dl_body *); + unsigned int (*max_width)(struct vsp1_entity *, struct vsp1_pipeline *); + void (*partition)(struct vsp1_entity *, struct vsp1_pipeline *, + struct vsp1_partition *, unsigned int, + struct vsp1_partition_window *); +}; + +struct vsp1_entity { + struct vsp1_device *vsp1; + + const struct vsp1_entity_operations *ops; + + enum vsp1_entity_type type; + unsigned int index; + const struct vsp1_route *route; + + struct vsp1_pipeline *pipe; + + struct list_head list_dev; + struct list_head list_pipe; + + struct media_pad *pads; + unsigned int source_pad; + + struct vsp1_entity **sources; + struct vsp1_entity *sink; + unsigned int sink_pad; + + struct v4l2_subdev subdev; + struct v4l2_subdev_state *config; + + struct mutex lock; /* Protects the pad config */ +}; + +static inline struct vsp1_entity *to_vsp1_entity(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_entity, subdev); +} + +int vsp1_entity_init(struct vsp1_device *vsp1, struct vsp1_entity *entity, + const char *name, unsigned int num_pads, + const struct v4l2_subdev_ops *ops, u32 function); +void vsp1_entity_destroy(struct vsp1_entity *entity); + +extern const struct v4l2_subdev_internal_ops vsp1_subdev_internal_ops; + +int vsp1_entity_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags); + +struct v4l2_subdev_state * +vsp1_entity_get_pad_config(struct vsp1_entity *entity, + struct v4l2_subdev_state *sd_state, + enum v4l2_subdev_format_whence which); +struct v4l2_mbus_framefmt * +vsp1_entity_get_pad_format(struct vsp1_entity *entity, + struct v4l2_subdev_state *sd_state, + unsigned int pad); +struct v4l2_rect * +vsp1_entity_get_pad_selection(struct vsp1_entity *entity, + struct v4l2_subdev_state *sd_state, + unsigned int pad, unsigned int target); +int vsp1_entity_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state); + +void vsp1_entity_route_setup(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb); + +void vsp1_entity_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb); + +void vsp1_entity_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb); + +void vsp1_entity_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb); + +struct media_pad *vsp1_entity_remote_pad(struct media_pad *pad); + +int vsp1_subdev_get_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt); +int vsp1_subdev_set_pad_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt, + const unsigned int *codes, unsigned int ncodes, + unsigned int min_width, unsigned int min_height, + unsigned int max_width, unsigned int max_height); +int vsp1_subdev_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code, + const unsigned int *codes, unsigned int ncodes); +int vsp1_subdev_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse, + unsigned int min_w, unsigned int min_h, + unsigned int max_w, unsigned int max_h); + +#endif /* __VSP1_ENTITY_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_hgo.c b/drivers/media/platform/renesas/vsp1/vsp1_hgo.c new file mode 100644 index 000000000..bf3f981f9 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_hgo.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_hgo.c -- R-Car VSP1 Histogram Generator 1D + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_hgo.h" + +#define HGO_DATA_SIZE ((2 + 256) * 4) + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_hgo_read(struct vsp1_hgo *hgo, u32 reg) +{ + return vsp1_read(hgo->histo.entity.vsp1, reg); +} + +static inline void vsp1_hgo_write(struct vsp1_hgo *hgo, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg, data); +} + +/* ----------------------------------------------------------------------------- + * Frame End Handler + */ + +void vsp1_hgo_frame_end(struct vsp1_entity *entity) +{ + struct vsp1_hgo *hgo = to_hgo(&entity->subdev); + struct vsp1_histogram_buffer *buf; + unsigned int i; + size_t size; + u32 *data; + + buf = vsp1_histogram_buffer_get(&hgo->histo); + if (!buf) + return; + + data = buf->addr; + + if (hgo->num_bins == 256) { + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM); + + for (i = 0; i < 256; ++i) { + vsp1_write(hgo->histo.entity.vsp1, + VI6_HGO_EXT_HIST_ADDR, i); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_EXT_HIST_DATA); + } + + size = (2 + 256) * sizeof(u32); + } else if (hgo->max_rgb) { + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM); + + for (i = 0; i < 64; ++i) + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_HISTO(i)); + + size = (2 + 64) * sizeof(u32); + } else { + *data++ = vsp1_hgo_read(hgo, VI6_HGO_R_MAXMIN); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_MAXMIN); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_B_MAXMIN); + + *data++ = vsp1_hgo_read(hgo, VI6_HGO_R_SUM); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_G_SUM); + *data++ = vsp1_hgo_read(hgo, VI6_HGO_B_SUM); + + for (i = 0; i < 64; ++i) { + data[i] = vsp1_hgo_read(hgo, VI6_HGO_R_HISTO(i)); + data[i+64] = vsp1_hgo_read(hgo, VI6_HGO_G_HISTO(i)); + data[i+128] = vsp1_hgo_read(hgo, VI6_HGO_B_HISTO(i)); + } + + size = (6 + 64 * 3) * sizeof(u32); + } + + vsp1_histogram_buffer_complete(&hgo->histo, buf, size); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +#define V4L2_CID_VSP1_HGO_MAX_RGB (V4L2_CID_USER_BASE | 0x1001) +#define V4L2_CID_VSP1_HGO_NUM_BINS (V4L2_CID_USER_BASE | 0x1002) + +static const struct v4l2_ctrl_config hgo_max_rgb_control = { + .id = V4L2_CID_VSP1_HGO_MAX_RGB, + .name = "Maximum RGB Mode", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .def = 0, + .step = 1, + .flags = V4L2_CTRL_FLAG_MODIFY_LAYOUT, +}; + +static const s64 hgo_num_bins[] = { + 64, 256, +}; + +static const struct v4l2_ctrl_config hgo_num_bins_control = { + .id = V4L2_CID_VSP1_HGO_NUM_BINS, + .name = "Number of Bins", + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .min = 0, + .max = 1, + .def = 0, + .qmenu_int = hgo_num_bins, + .flags = V4L2_CTRL_FLAG_MODIFY_LAYOUT, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void hgo_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_hgo *hgo = to_hgo(&entity->subdev); + struct v4l2_rect *compose; + struct v4l2_rect *crop; + unsigned int hratio; + unsigned int vratio; + + crop = vsp1_entity_get_pad_selection(entity, entity->config, + HISTO_PAD_SINK, V4L2_SEL_TGT_CROP); + compose = vsp1_entity_get_pad_selection(entity, entity->config, + HISTO_PAD_SINK, + V4L2_SEL_TGT_COMPOSE); + + vsp1_hgo_write(hgo, dlb, VI6_HGO_REGRST, VI6_HGO_REGRST_RCLEA); + + vsp1_hgo_write(hgo, dlb, VI6_HGO_OFFSET, + (crop->left << VI6_HGO_OFFSET_HOFFSET_SHIFT) | + (crop->top << VI6_HGO_OFFSET_VOFFSET_SHIFT)); + vsp1_hgo_write(hgo, dlb, VI6_HGO_SIZE, + (crop->width << VI6_HGO_SIZE_HSIZE_SHIFT) | + (crop->height << VI6_HGO_SIZE_VSIZE_SHIFT)); + + mutex_lock(hgo->ctrls.handler.lock); + hgo->max_rgb = hgo->ctrls.max_rgb->cur.val; + if (hgo->ctrls.num_bins) + hgo->num_bins = hgo_num_bins[hgo->ctrls.num_bins->cur.val]; + mutex_unlock(hgo->ctrls.handler.lock); + + hratio = crop->width * 2 / compose->width / 3; + vratio = crop->height * 2 / compose->height / 3; + vsp1_hgo_write(hgo, dlb, VI6_HGO_MODE, + (hgo->num_bins == 256 ? VI6_HGO_MODE_STEP : 0) | + (hgo->max_rgb ? VI6_HGO_MODE_MAXRGB : 0) | + (hratio << VI6_HGO_MODE_HRATIO_SHIFT) | + (vratio << VI6_HGO_MODE_VRATIO_SHIFT)); +} + +static const struct vsp1_entity_operations hgo_entity_ops = { + .configure_stream = hgo_configure_stream, + .destroy = vsp1_histogram_destroy, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +static const unsigned int hgo_mbus_formats[] = { + MEDIA_BUS_FMT_AYUV8_1X32, + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, +}; + +struct vsp1_hgo *vsp1_hgo_create(struct vsp1_device *vsp1) +{ + struct vsp1_hgo *hgo; + int ret; + + hgo = devm_kzalloc(vsp1->dev, sizeof(*hgo), GFP_KERNEL); + if (hgo == NULL) + return ERR_PTR(-ENOMEM); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&hgo->ctrls.handler, + vsp1->info->gen == 3 ? 2 : 1); + hgo->ctrls.max_rgb = v4l2_ctrl_new_custom(&hgo->ctrls.handler, + &hgo_max_rgb_control, NULL); + if (vsp1->info->gen == 3) + hgo->ctrls.num_bins = + v4l2_ctrl_new_custom(&hgo->ctrls.handler, + &hgo_num_bins_control, NULL); + + hgo->max_rgb = false; + hgo->num_bins = 64; + + hgo->histo.entity.subdev.ctrl_handler = &hgo->ctrls.handler; + + /* Initialize the video device and queue for statistics data. */ + ret = vsp1_histogram_init(vsp1, &hgo->histo, VSP1_ENTITY_HGO, "hgo", + &hgo_entity_ops, hgo_mbus_formats, + ARRAY_SIZE(hgo_mbus_formats), + HGO_DATA_SIZE, V4L2_META_FMT_VSP1_HGO); + if (ret < 0) { + vsp1_entity_destroy(&hgo->histo.entity); + return ERR_PTR(ret); + } + + return hgo; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_hgo.h b/drivers/media/platform/renesas/vsp1/vsp1_hgo.h new file mode 100644 index 000000000..6b0c8580e --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_hgo.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_hgo.h -- R-Car VSP1 Histogram Generator 1D + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_HGO_H__ +#define __VSP1_HGO_H__ + +#include +#include +#include + +#include "vsp1_histo.h" + +struct vsp1_device; + +struct vsp1_hgo { + struct vsp1_histogram histo; + + struct { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *max_rgb; + struct v4l2_ctrl *num_bins; + } ctrls; + + bool max_rgb; + unsigned int num_bins; +}; + +static inline struct vsp1_hgo *to_hgo(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_hgo, histo.entity.subdev); +} + +struct vsp1_hgo *vsp1_hgo_create(struct vsp1_device *vsp1); +void vsp1_hgo_frame_end(struct vsp1_entity *hgo); + +#endif /* __VSP1_HGO_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_hgt.c b/drivers/media/platform/renesas/vsp1/vsp1_hgt.c new file mode 100644 index 000000000..aa1c718e0 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_hgt.c @@ -0,0 +1,215 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_hgt.c -- R-Car VSP1 Histogram Generator 2D + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Niklas Söderlund (niklas.soderlund@ragnatech.se) + */ + +#include +#include + +#include +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_hgt.h" + +#define HGT_DATA_SIZE ((2 + 6 * 32) * 4) + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_hgt_read(struct vsp1_hgt *hgt, u32 reg) +{ + return vsp1_read(hgt->histo.entity.vsp1, reg); +} + +static inline void vsp1_hgt_write(struct vsp1_hgt *hgt, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg, data); +} + +/* ----------------------------------------------------------------------------- + * Frame End Handler + */ + +void vsp1_hgt_frame_end(struct vsp1_entity *entity) +{ + struct vsp1_hgt *hgt = to_hgt(&entity->subdev); + struct vsp1_histogram_buffer *buf; + unsigned int m; + unsigned int n; + u32 *data; + + buf = vsp1_histogram_buffer_get(&hgt->histo); + if (!buf) + return; + + data = buf->addr; + + *data++ = vsp1_hgt_read(hgt, VI6_HGT_MAXMIN); + *data++ = vsp1_hgt_read(hgt, VI6_HGT_SUM); + + for (m = 0; m < 6; ++m) + for (n = 0; n < 32; ++n) + *data++ = vsp1_hgt_read(hgt, VI6_HGT_HISTO(m, n)); + + vsp1_histogram_buffer_complete(&hgt->histo, buf, HGT_DATA_SIZE); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +#define V4L2_CID_VSP1_HGT_HUE_AREAS (V4L2_CID_USER_BASE | 0x1001) + +static int hgt_hue_areas_try_ctrl(struct v4l2_ctrl *ctrl) +{ + const u8 *values = ctrl->p_new.p_u8; + unsigned int i; + + /* + * The hardware has constraints on the hue area boundaries beyond the + * control min, max and step. The values must match one of the following + * expressions. + * + * 0L <= 0U <= 1L <= 1U <= 2L <= 2U <= 3L <= 3U <= 4L <= 4U <= 5L <= 5U + * 0U <= 1L <= 1U <= 2L <= 2U <= 3L <= 3U <= 4L <= 4U <= 5L <= 5U <= 0L + * + * Start by verifying the common part... + */ + for (i = 1; i < (HGT_NUM_HUE_AREAS * 2) - 1; ++i) { + if (values[i] > values[i+1]) + return -EINVAL; + } + + /* ... and handle 0L separately. */ + if (values[0] > values[1] && values[11] > values[0]) + return -EINVAL; + + return 0; +} + +static int hgt_hue_areas_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_hgt *hgt = container_of(ctrl->handler, struct vsp1_hgt, + ctrls); + + memcpy(hgt->hue_areas, ctrl->p_new.p_u8, sizeof(hgt->hue_areas)); + return 0; +} + +static const struct v4l2_ctrl_ops hgt_hue_areas_ctrl_ops = { + .try_ctrl = hgt_hue_areas_try_ctrl, + .s_ctrl = hgt_hue_areas_s_ctrl, +}; + +static const struct v4l2_ctrl_config hgt_hue_areas = { + .ops = &hgt_hue_areas_ctrl_ops, + .id = V4L2_CID_VSP1_HGT_HUE_AREAS, + .name = "Boundary Values for Hue Area", + .type = V4L2_CTRL_TYPE_U8, + .min = 0, + .max = 255, + .def = 0, + .step = 1, + .dims = { 12 }, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void hgt_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_hgt *hgt = to_hgt(&entity->subdev); + struct v4l2_rect *compose; + struct v4l2_rect *crop; + unsigned int hratio; + unsigned int vratio; + u8 lower; + u8 upper; + unsigned int i; + + crop = vsp1_entity_get_pad_selection(entity, entity->config, + HISTO_PAD_SINK, V4L2_SEL_TGT_CROP); + compose = vsp1_entity_get_pad_selection(entity, entity->config, + HISTO_PAD_SINK, + V4L2_SEL_TGT_COMPOSE); + + vsp1_hgt_write(hgt, dlb, VI6_HGT_REGRST, VI6_HGT_REGRST_RCLEA); + + vsp1_hgt_write(hgt, dlb, VI6_HGT_OFFSET, + (crop->left << VI6_HGT_OFFSET_HOFFSET_SHIFT) | + (crop->top << VI6_HGT_OFFSET_VOFFSET_SHIFT)); + vsp1_hgt_write(hgt, dlb, VI6_HGT_SIZE, + (crop->width << VI6_HGT_SIZE_HSIZE_SHIFT) | + (crop->height << VI6_HGT_SIZE_VSIZE_SHIFT)); + + mutex_lock(hgt->ctrls.lock); + for (i = 0; i < HGT_NUM_HUE_AREAS; ++i) { + lower = hgt->hue_areas[i*2 + 0]; + upper = hgt->hue_areas[i*2 + 1]; + vsp1_hgt_write(hgt, dlb, VI6_HGT_HUE_AREA(i), + (lower << VI6_HGT_HUE_AREA_LOWER_SHIFT) | + (upper << VI6_HGT_HUE_AREA_UPPER_SHIFT)); + } + mutex_unlock(hgt->ctrls.lock); + + hratio = crop->width * 2 / compose->width / 3; + vratio = crop->height * 2 / compose->height / 3; + vsp1_hgt_write(hgt, dlb, VI6_HGT_MODE, + (hratio << VI6_HGT_MODE_HRATIO_SHIFT) | + (vratio << VI6_HGT_MODE_VRATIO_SHIFT)); +} + +static const struct vsp1_entity_operations hgt_entity_ops = { + .configure_stream = hgt_configure_stream, + .destroy = vsp1_histogram_destroy, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +static const unsigned int hgt_mbus_formats[] = { + MEDIA_BUS_FMT_AHSV8888_1X32, +}; + +struct vsp1_hgt *vsp1_hgt_create(struct vsp1_device *vsp1) +{ + struct vsp1_hgt *hgt; + int ret; + + hgt = devm_kzalloc(vsp1->dev, sizeof(*hgt), GFP_KERNEL); + if (hgt == NULL) + return ERR_PTR(-ENOMEM); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&hgt->ctrls, 1); + v4l2_ctrl_new_custom(&hgt->ctrls, &hgt_hue_areas, NULL); + + hgt->histo.entity.subdev.ctrl_handler = &hgt->ctrls; + + /* Initialize the video device and queue for statistics data. */ + ret = vsp1_histogram_init(vsp1, &hgt->histo, VSP1_ENTITY_HGT, "hgt", + &hgt_entity_ops, hgt_mbus_formats, + ARRAY_SIZE(hgt_mbus_formats), + HGT_DATA_SIZE, V4L2_META_FMT_VSP1_HGT); + if (ret < 0) { + vsp1_entity_destroy(&hgt->histo.entity); + return ERR_PTR(ret); + } + + v4l2_ctrl_handler_setup(&hgt->ctrls); + + return hgt; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_hgt.h b/drivers/media/platform/renesas/vsp1/vsp1_hgt.h new file mode 100644 index 000000000..38ec237bd --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_hgt.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_hgt.h -- R-Car VSP1 Histogram Generator 2D + * + * Copyright (C) 2016 Renesas Electronics Corporation + * + * Contact: Niklas Söderlund (niklas.soderlund@ragnatech.se) + */ +#ifndef __VSP1_HGT_H__ +#define __VSP1_HGT_H__ + +#include +#include +#include + +#include "vsp1_histo.h" + +struct vsp1_device; + +#define HGT_NUM_HUE_AREAS 6 + +struct vsp1_hgt { + struct vsp1_histogram histo; + + struct v4l2_ctrl_handler ctrls; + + u8 hue_areas[HGT_NUM_HUE_AREAS * 2]; +}; + +static inline struct vsp1_hgt *to_hgt(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_hgt, histo.entity.subdev); +} + +struct vsp1_hgt *vsp1_hgt_create(struct vsp1_device *vsp1); +void vsp1_hgt_frame_end(struct vsp1_entity *hgt); + +#endif /* __VSP1_HGT_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_histo.c b/drivers/media/platform/renesas/vsp1/vsp1_histo.c new file mode 100644 index 000000000..f22449dd6 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_histo.c @@ -0,0 +1,593 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_histo.c -- R-Car VSP1 Histogram API + * + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2016 Laurent Pinchart + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include +#include +#include + +#include "vsp1.h" +#include "vsp1_histo.h" +#include "vsp1_pipe.h" + +#define HISTO_MIN_SIZE 4U +#define HISTO_MAX_SIZE 8192U + +/* ----------------------------------------------------------------------------- + * Buffer Operations + */ + +static inline struct vsp1_histogram_buffer * +to_vsp1_histogram_buffer(struct vb2_v4l2_buffer *vbuf) +{ + return container_of(vbuf, struct vsp1_histogram_buffer, buf); +} + +struct vsp1_histogram_buffer * +vsp1_histogram_buffer_get(struct vsp1_histogram *histo) +{ + struct vsp1_histogram_buffer *buf = NULL; + unsigned long flags; + + spin_lock_irqsave(&histo->irqlock, flags); + + if (list_empty(&histo->irqqueue)) + goto done; + + buf = list_first_entry(&histo->irqqueue, struct vsp1_histogram_buffer, + queue); + list_del(&buf->queue); + histo->readout = true; + +done: + spin_unlock_irqrestore(&histo->irqlock, flags); + return buf; +} + +void vsp1_histogram_buffer_complete(struct vsp1_histogram *histo, + struct vsp1_histogram_buffer *buf, + size_t size) +{ + struct vsp1_pipeline *pipe = histo->entity.pipe; + unsigned long flags; + + /* + * The pipeline pointer is guaranteed to be valid as this function is + * called from the frame completion interrupt handler, which can only + * occur when video streaming is active. + */ + buf->buf.sequence = pipe->sequence; + buf->buf.vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&buf->buf.vb2_buf, 0, size); + vb2_buffer_done(&buf->buf.vb2_buf, VB2_BUF_STATE_DONE); + + spin_lock_irqsave(&histo->irqlock, flags); + histo->readout = false; + wake_up(&histo->wait_queue); + spin_unlock_irqrestore(&histo->irqlock, flags); +} + +/* ----------------------------------------------------------------------------- + * videobuf2 Queue Operations + */ + +static int histo_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct vsp1_histogram *histo = vb2_get_drv_priv(vq); + + if (*nplanes) { + if (*nplanes != 1) + return -EINVAL; + + if (sizes[0] < histo->data_size) + return -EINVAL; + + return 0; + } + + *nplanes = 1; + sizes[0] = histo->data_size; + + return 0; +} + +static int histo_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vsp1_histogram *histo = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_histogram_buffer *buf = to_vsp1_histogram_buffer(vbuf); + + if (vb->num_planes != 1) + return -EINVAL; + + if (vb2_plane_size(vb, 0) < histo->data_size) + return -EINVAL; + + buf->addr = vb2_plane_vaddr(vb, 0); + + return 0; +} + +static void histo_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vsp1_histogram *histo = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_histogram_buffer *buf = to_vsp1_histogram_buffer(vbuf); + unsigned long flags; + + spin_lock_irqsave(&histo->irqlock, flags); + list_add_tail(&buf->queue, &histo->irqqueue); + spin_unlock_irqrestore(&histo->irqlock, flags); +} + +static int histo_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + return 0; +} + +static void histo_stop_streaming(struct vb2_queue *vq) +{ + struct vsp1_histogram *histo = vb2_get_drv_priv(vq); + struct vsp1_histogram_buffer *buffer; + unsigned long flags; + + spin_lock_irqsave(&histo->irqlock, flags); + + /* Remove all buffers from the IRQ queue. */ + list_for_each_entry(buffer, &histo->irqqueue, queue) + vb2_buffer_done(&buffer->buf.vb2_buf, VB2_BUF_STATE_ERROR); + INIT_LIST_HEAD(&histo->irqqueue); + + /* Wait for the buffer being read out (if any) to complete. */ + wait_event_lock_irq(histo->wait_queue, !histo->readout, histo->irqlock); + + spin_unlock_irqrestore(&histo->irqlock, flags); +} + +static const struct vb2_ops histo_video_queue_qops = { + .queue_setup = histo_queue_setup, + .buf_prepare = histo_buffer_prepare, + .buf_queue = histo_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = histo_start_streaming, + .stop_streaming = histo_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static int histo_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + + if (code->pad == HISTO_PAD_SOURCE) { + code->code = MEDIA_BUS_FMT_FIXED; + return 0; + } + + return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, + histo->formats, + histo->num_formats); +} + +static int histo_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + if (fse->pad != HISTO_PAD_SINK) + return -EINVAL; + + return vsp1_subdev_enum_frame_size(subdev, sd_state, fse, + HISTO_MIN_SIZE, + HISTO_MIN_SIZE, HISTO_MAX_SIZE, + HISTO_MAX_SIZE); +} + +static int histo_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + int ret = 0; + + if (sel->pad != HISTO_PAD_SINK) + return -EINVAL; + + mutex_lock(&histo->entity.lock); + + config = vsp1_entity_get_pad_config(&histo->entity, sd_state, + sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + crop = vsp1_entity_get_pad_selection(&histo->entity, config, + HISTO_PAD_SINK, + V4L2_SEL_TGT_CROP); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = crop->width; + sel->r.height = crop->height; + break; + + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + format = vsp1_entity_get_pad_format(&histo->entity, config, + HISTO_PAD_SINK); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = format->width; + sel->r.height = format->height; + break; + + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_CROP: + sel->r = *vsp1_entity_get_pad_selection(&histo->entity, config, + sel->pad, sel->target); + break; + + default: + ret = -EINVAL; + break; + } + +done: + mutex_unlock(&histo->entity.lock); + return ret; +} + +static int histo_set_crop(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *selection; + + /* The crop rectangle must be inside the input frame. */ + format = vsp1_entity_get_pad_format(&histo->entity, sd_state, + HISTO_PAD_SINK); + sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1); + sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1); + sel->r.width = clamp_t(unsigned int, sel->r.width, HISTO_MIN_SIZE, + format->width - sel->r.left); + sel->r.height = clamp_t(unsigned int, sel->r.height, HISTO_MIN_SIZE, + format->height - sel->r.top); + + /* Set the crop rectangle and reset the compose rectangle. */ + selection = vsp1_entity_get_pad_selection(&histo->entity, sd_state, + sel->pad, V4L2_SEL_TGT_CROP); + *selection = sel->r; + + selection = vsp1_entity_get_pad_selection(&histo->entity, sd_state, + sel->pad, + V4L2_SEL_TGT_COMPOSE); + *selection = sel->r; + + return 0; +} + +static int histo_set_compose(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_rect *compose; + struct v4l2_rect *crop; + unsigned int ratio; + + /* + * The compose rectangle is used to configure downscaling, the top left + * corner is fixed to (0,0) and the size to 1/2 or 1/4 of the crop + * rectangle. + */ + sel->r.left = 0; + sel->r.top = 0; + + crop = vsp1_entity_get_pad_selection(&histo->entity, sd_state, + sel->pad, + V4L2_SEL_TGT_CROP); + + /* + * Clamp the width and height to acceptable values first and then + * compute the closest rounded dividing ratio. + * + * Ratio Rounded ratio + * -------------------------- + * [1.0 1.5[ 1 + * [1.5 3.0[ 2 + * [3.0 4.0] 4 + * + * The rounded ratio can be computed using + * + * 1 << (ceil(ratio * 2) / 3) + */ + sel->r.width = clamp(sel->r.width, crop->width / 4, crop->width); + ratio = 1 << (crop->width * 2 / sel->r.width / 3); + sel->r.width = crop->width / ratio; + + + sel->r.height = clamp(sel->r.height, crop->height / 4, crop->height); + ratio = 1 << (crop->height * 2 / sel->r.height / 3); + sel->r.height = crop->height / ratio; + + compose = vsp1_entity_get_pad_selection(&histo->entity, sd_state, + sel->pad, + V4L2_SEL_TGT_COMPOSE); + *compose = sel->r; + + return 0; +} + +static int histo_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + struct v4l2_subdev_state *config; + int ret; + + if (sel->pad != HISTO_PAD_SINK) + return -EINVAL; + + mutex_lock(&histo->entity.lock); + + config = vsp1_entity_get_pad_config(&histo->entity, sd_state, + sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + if (sel->target == V4L2_SEL_TGT_CROP) + ret = histo_set_crop(subdev, config, sel); + else if (sel->target == V4L2_SEL_TGT_COMPOSE) + ret = histo_set_compose(subdev, config, sel); + else + ret = -EINVAL; + +done: + mutex_unlock(&histo->entity.lock); + return ret; +} + +static int histo_get_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + if (fmt->pad == HISTO_PAD_SOURCE) { + fmt->format.code = MEDIA_BUS_FMT_FIXED; + fmt->format.width = 0; + fmt->format.height = 0; + fmt->format.field = V4L2_FIELD_NONE; + fmt->format.colorspace = V4L2_COLORSPACE_RAW; + return 0; + } + + return vsp1_subdev_get_pad_format(subdev, sd_state, fmt); +} + +static int histo_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_histogram *histo = subdev_to_histo(subdev); + + if (fmt->pad != HISTO_PAD_SINK) + return histo_get_format(subdev, sd_state, fmt); + + return vsp1_subdev_set_pad_format(subdev, sd_state, fmt, + histo->formats, histo->num_formats, + HISTO_MIN_SIZE, HISTO_MIN_SIZE, + HISTO_MAX_SIZE, HISTO_MAX_SIZE); +} + +static const struct v4l2_subdev_pad_ops histo_pad_ops = { + .enum_mbus_code = histo_enum_mbus_code, + .enum_frame_size = histo_enum_frame_size, + .get_fmt = histo_get_format, + .set_fmt = histo_set_format, + .get_selection = histo_get_selection, + .set_selection = histo_set_selection, +}; + +static const struct v4l2_subdev_ops histo_ops = { + .pad = &histo_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int histo_v4l2_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev); + + cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING + | V4L2_CAP_VIDEO_CAPTURE_MPLANE + | V4L2_CAP_VIDEO_OUTPUT_MPLANE + | V4L2_CAP_META_CAPTURE; + + strscpy(cap->driver, "vsp1", sizeof(cap->driver)); + strscpy(cap->card, histo->video.name, sizeof(cap->card)); + + return 0; +} + +static int histo_v4l2_enum_format(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev); + + if (f->index > 0 || f->type != histo->queue.type) + return -EINVAL; + + f->pixelformat = histo->meta_format; + + return 0; +} + +static int histo_v4l2_get_format(struct file *file, void *fh, + struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_histogram *histo = vdev_to_histo(vfh->vdev); + struct v4l2_meta_format *meta = &format->fmt.meta; + + if (format->type != histo->queue.type) + return -EINVAL; + + memset(meta, 0, sizeof(*meta)); + + meta->dataformat = histo->meta_format; + meta->buffersize = histo->data_size; + + return 0; +} + +static const struct v4l2_ioctl_ops histo_v4l2_ioctl_ops = { + .vidioc_querycap = histo_v4l2_querycap, + .vidioc_enum_fmt_meta_cap = histo_v4l2_enum_format, + .vidioc_g_fmt_meta_cap = histo_v4l2_get_format, + .vidioc_s_fmt_meta_cap = histo_v4l2_get_format, + .vidioc_try_fmt_meta_cap = histo_v4l2_get_format, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 File Operations + */ + +static const struct v4l2_file_operations histo_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 void vsp1_histogram_cleanup(struct vsp1_histogram *histo) +{ + if (video_is_registered(&histo->video)) + video_unregister_device(&histo->video); + + media_entity_cleanup(&histo->video.entity); +} + +void vsp1_histogram_destroy(struct vsp1_entity *entity) +{ + struct vsp1_histogram *histo = subdev_to_histo(&entity->subdev); + + vsp1_histogram_cleanup(histo); +} + +int vsp1_histogram_init(struct vsp1_device *vsp1, struct vsp1_histogram *histo, + enum vsp1_entity_type type, const char *name, + const struct vsp1_entity_operations *ops, + const unsigned int *formats, unsigned int num_formats, + size_t data_size, u32 meta_format) +{ + int ret; + + histo->formats = formats; + histo->num_formats = num_formats; + histo->data_size = data_size; + histo->meta_format = meta_format; + + histo->pad.flags = MEDIA_PAD_FL_SINK; + histo->video.vfl_dir = VFL_DIR_RX; + + mutex_init(&histo->lock); + spin_lock_init(&histo->irqlock); + INIT_LIST_HEAD(&histo->irqqueue); + init_waitqueue_head(&histo->wait_queue); + + /* Initialize the VSP entity... */ + histo->entity.ops = ops; + histo->entity.type = type; + + ret = vsp1_entity_init(vsp1, &histo->entity, name, 2, &histo_ops, + MEDIA_ENT_F_PROC_VIDEO_STATISTICS); + if (ret < 0) + return ret; + + /* ... and the media entity... */ + ret = media_entity_pads_init(&histo->video.entity, 1, &histo->pad); + if (ret < 0) + return ret; + + /* ... and the video node... */ + histo->video.v4l2_dev = &vsp1->v4l2_dev; + histo->video.fops = &histo_v4l2_fops; + snprintf(histo->video.name, sizeof(histo->video.name), + "%s histo", histo->entity.subdev.name); + histo->video.vfl_type = VFL_TYPE_VIDEO; + histo->video.release = video_device_release_empty; + histo->video.ioctl_ops = &histo_v4l2_ioctl_ops; + histo->video.device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING; + + video_set_drvdata(&histo->video, histo); + + /* ... and the buffers queue... */ + histo->queue.type = V4L2_BUF_TYPE_META_CAPTURE; + histo->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + histo->queue.lock = &histo->lock; + histo->queue.drv_priv = histo; + histo->queue.buf_struct_size = sizeof(struct vsp1_histogram_buffer); + histo->queue.ops = &histo_video_queue_qops; + histo->queue.mem_ops = &vb2_vmalloc_memops; + histo->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + histo->queue.dev = vsp1->dev; + ret = vb2_queue_init(&histo->queue); + if (ret < 0) { + dev_err(vsp1->dev, "failed to initialize vb2 queue\n"); + goto error; + } + + /* ... and register the video device. */ + histo->video.queue = &histo->queue; + ret = video_register_device(&histo->video, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + dev_err(vsp1->dev, "failed to register video device\n"); + goto error; + } + + return 0; + +error: + vsp1_histogram_cleanup(histo); + return ret; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_histo.h b/drivers/media/platform/renesas/vsp1/vsp1_histo.h new file mode 100644 index 000000000..06f029846 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_histo.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_histo.h -- R-Car VSP1 Histogram API + * + * Copyright (C) 2016 Renesas Electronics Corporation + * Copyright (C) 2016 Laurent Pinchart + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_HISTO_H__ +#define __VSP1_HISTO_H__ + +#include +#include +#include + +#include +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define HISTO_PAD_SINK 0 +#define HISTO_PAD_SOURCE 1 + +struct vsp1_histogram_buffer { + struct vb2_v4l2_buffer buf; + struct list_head queue; + void *addr; +}; + +struct vsp1_histogram { + struct vsp1_entity entity; + struct video_device video; + struct media_pad pad; + + const u32 *formats; + unsigned int num_formats; + size_t data_size; + u32 meta_format; + + struct mutex lock; + struct vb2_queue queue; + + spinlock_t irqlock; + struct list_head irqqueue; + + wait_queue_head_t wait_queue; + bool readout; +}; + +static inline struct vsp1_histogram *vdev_to_histo(struct video_device *vdev) +{ + return container_of(vdev, struct vsp1_histogram, video); +} + +static inline struct vsp1_histogram *subdev_to_histo(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_histogram, entity.subdev); +} + +int vsp1_histogram_init(struct vsp1_device *vsp1, struct vsp1_histogram *histo, + enum vsp1_entity_type type, const char *name, + const struct vsp1_entity_operations *ops, + const unsigned int *formats, unsigned int num_formats, + size_t data_size, u32 meta_format); +void vsp1_histogram_destroy(struct vsp1_entity *entity); + +struct vsp1_histogram_buffer * +vsp1_histogram_buffer_get(struct vsp1_histogram *histo); +void vsp1_histogram_buffer_complete(struct vsp1_histogram *histo, + struct vsp1_histogram_buffer *buf, + size_t size); + +#endif /* __VSP1_HISTO_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_hsit.c b/drivers/media/platform/renesas/vsp1/vsp1_hsit.c new file mode 100644 index 000000000..361a87038 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_hsit.c @@ -0,0 +1,178 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_hsit.c -- R-Car VSP1 Hue Saturation value (Inverse) Transform + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_hsit.h" + +#define HSIT_MIN_SIZE 4U +#define HSIT_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_hsit_write(struct vsp1_hsit *hsit, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg, data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static int hsit_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct vsp1_hsit *hsit = to_hsit(subdev); + + if (code->index > 0) + return -EINVAL; + + if ((code->pad == HSIT_PAD_SINK && !hsit->inverse) | + (code->pad == HSIT_PAD_SOURCE && hsit->inverse)) + code->code = MEDIA_BUS_FMT_ARGB8888_1X32; + else + code->code = MEDIA_BUS_FMT_AHSV8888_1X32; + + return 0; +} + +static int hsit_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + return vsp1_subdev_enum_frame_size(subdev, sd_state, fse, + HSIT_MIN_SIZE, + HSIT_MIN_SIZE, HSIT_MAX_SIZE, + HSIT_MAX_SIZE); +} + +static int hsit_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_hsit *hsit = to_hsit(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + mutex_lock(&hsit->entity.lock); + + config = vsp1_entity_get_pad_config(&hsit->entity, sd_state, + fmt->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + format = vsp1_entity_get_pad_format(&hsit->entity, config, fmt->pad); + + if (fmt->pad == HSIT_PAD_SOURCE) { + /* + * The HST and HSI output format code and resolution can't be + * modified. + */ + fmt->format = *format; + goto done; + } + + format->code = hsit->inverse ? MEDIA_BUS_FMT_AHSV8888_1X32 + : MEDIA_BUS_FMT_ARGB8888_1X32; + format->width = clamp_t(unsigned int, fmt->format.width, + HSIT_MIN_SIZE, HSIT_MAX_SIZE); + format->height = clamp_t(unsigned int, fmt->format.height, + HSIT_MIN_SIZE, HSIT_MAX_SIZE); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&hsit->entity, config, + HSIT_PAD_SOURCE); + *format = fmt->format; + format->code = hsit->inverse ? MEDIA_BUS_FMT_ARGB8888_1X32 + : MEDIA_BUS_FMT_AHSV8888_1X32; + +done: + mutex_unlock(&hsit->entity.lock); + return ret; +} + +static const struct v4l2_subdev_pad_ops hsit_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = hsit_enum_mbus_code, + .enum_frame_size = hsit_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = hsit_set_format, +}; + +static const struct v4l2_subdev_ops hsit_ops = { + .pad = &hsit_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void hsit_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_hsit *hsit = to_hsit(&entity->subdev); + + if (hsit->inverse) + vsp1_hsit_write(hsit, dlb, VI6_HSI_CTRL, VI6_HSI_CTRL_EN); + else + vsp1_hsit_write(hsit, dlb, VI6_HST_CTRL, VI6_HST_CTRL_EN); +} + +static const struct vsp1_entity_operations hsit_entity_ops = { + .configure_stream = hsit_configure_stream, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_hsit *vsp1_hsit_create(struct vsp1_device *vsp1, bool inverse) +{ + struct vsp1_hsit *hsit; + int ret; + + hsit = devm_kzalloc(vsp1->dev, sizeof(*hsit), GFP_KERNEL); + if (hsit == NULL) + return ERR_PTR(-ENOMEM); + + hsit->inverse = inverse; + + hsit->entity.ops = &hsit_entity_ops; + + if (inverse) + hsit->entity.type = VSP1_ENTITY_HSI; + else + hsit->entity.type = VSP1_ENTITY_HST; + + ret = vsp1_entity_init(vsp1, &hsit->entity, inverse ? "hsi" : "hst", + 2, &hsit_ops, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_ENC_CONV); + if (ret < 0) + return ERR_PTR(ret); + + return hsit; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_hsit.h b/drivers/media/platform/renesas/vsp1/vsp1_hsit.h new file mode 100644 index 000000000..a658b1aa4 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_hsit.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_hsit.h -- R-Car VSP1 Hue Saturation value (Inverse) Transform + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_HSIT_H__ +#define __VSP1_HSIT_H__ + +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define HSIT_PAD_SINK 0 +#define HSIT_PAD_SOURCE 1 + +struct vsp1_hsit { + struct vsp1_entity entity; + bool inverse; +}; + +static inline struct vsp1_hsit *to_hsit(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_hsit, entity.subdev); +} + +struct vsp1_hsit *vsp1_hsit_create(struct vsp1_device *vsp1, bool inverse); + +#endif /* __VSP1_HSIT_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_lif.c b/drivers/media/platform/renesas/vsp1/vsp1_lif.c new file mode 100644 index 000000000..186a5730e --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_lif.c @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_lif.c -- R-Car VSP1 LCD Controller Interface + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_lif.h" + +#define LIF_MIN_SIZE 2U +#define LIF_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_lif_write(struct vsp1_lif *lif, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg + lif->entity.index * VI6_LIF_OFFSET, + data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static const unsigned int lif_codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, +}; + +static int lif_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, lif_codes, + ARRAY_SIZE(lif_codes)); +} + +static int lif_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + return vsp1_subdev_enum_frame_size(subdev, sd_state, fse, + LIF_MIN_SIZE, + LIF_MIN_SIZE, LIF_MAX_SIZE, + LIF_MAX_SIZE); +} + +static int lif_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + return vsp1_subdev_set_pad_format(subdev, sd_state, fmt, lif_codes, + ARRAY_SIZE(lif_codes), + LIF_MIN_SIZE, LIF_MIN_SIZE, + LIF_MAX_SIZE, LIF_MAX_SIZE); +} + +static const struct v4l2_subdev_pad_ops lif_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = lif_enum_mbus_code, + .enum_frame_size = lif_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = lif_set_format, +}; + +static const struct v4l2_subdev_ops lif_ops = { + .pad = &lif_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void lif_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + const struct v4l2_mbus_framefmt *format; + struct vsp1_lif *lif = to_lif(&entity->subdev); + unsigned int hbth; + unsigned int obth; + unsigned int lbth; + + format = vsp1_entity_get_pad_format(&lif->entity, lif->entity.config, + LIF_PAD_SOURCE); + + switch (entity->vsp1->version & VI6_IP_VERSION_MODEL_MASK) { + case VI6_IP_VERSION_MODEL_VSPD_GEN2: + case VI6_IP_VERSION_MODEL_VSPD_V2H: + hbth = 1536; + obth = min(128U, (format->width + 1) / 2 * format->height - 4); + lbth = 1520; + break; + + case VI6_IP_VERSION_MODEL_VSPDL_GEN3: + case VI6_IP_VERSION_MODEL_VSPD_V3: + case VI6_IP_VERSION_MODEL_VSPD_RZG2L: + hbth = 0; + obth = 1500; + lbth = 0; + break; + + case VI6_IP_VERSION_MODEL_VSPD_GEN3: + default: + hbth = 0; + obth = 3000; + lbth = 0; + break; + } + + vsp1_lif_write(lif, dlb, VI6_LIF_CSBTH, + (hbth << VI6_LIF_CSBTH_HBTH_SHIFT) | + (lbth << VI6_LIF_CSBTH_LBTH_SHIFT)); + + vsp1_lif_write(lif, dlb, VI6_LIF_CTRL, + (obth << VI6_LIF_CTRL_OBTH_SHIFT) | + (format->code == 0 ? VI6_LIF_CTRL_CFMT : 0) | + VI6_LIF_CTRL_REQSEL | VI6_LIF_CTRL_LIF_EN); + + /* + * On R-Car V3M and RZ/G2L the LIF0 buffer attribute register has to be + * set to a non-default value to guarantee proper operation (otherwise + * artifacts may appear on the output). The value required by the + * manual is not explained but is likely a buffer size or threshold. + */ + if (vsp1_feature(entity->vsp1, VSP1_HAS_NON_ZERO_LBA)) + vsp1_lif_write(lif, dlb, VI6_LIF_LBA, + VI6_LIF_LBA_LBA0 | + (1536 << VI6_LIF_LBA_LBA1_SHIFT)); +} + +static const struct vsp1_entity_operations lif_entity_ops = { + .configure_stream = lif_configure_stream, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct vsp1_lif *lif; + int ret; + + lif = devm_kzalloc(vsp1->dev, sizeof(*lif), GFP_KERNEL); + if (lif == NULL) + return ERR_PTR(-ENOMEM); + + lif->entity.ops = &lif_entity_ops; + lif->entity.type = VSP1_ENTITY_LIF; + lif->entity.index = index; + + /* + * The LIF is never exposed to userspace, but media entity registration + * requires a function to be set. Use PROC_VIDEO_PIXEL_FORMATTER just to + * avoid triggering a WARN_ON(), the value won't be seen anywhere. + */ + ret = vsp1_entity_init(vsp1, &lif->entity, "lif", 2, &lif_ops, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER); + if (ret < 0) + return ERR_PTR(ret); + + return lif; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_lif.h b/drivers/media/platform/renesas/vsp1/vsp1_lif.h new file mode 100644 index 000000000..71a4eda9c --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_lif.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_lif.h -- R-Car VSP1 LCD Controller Interface + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_LIF_H__ +#define __VSP1_LIF_H__ + +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define LIF_PAD_SINK 0 +#define LIF_PAD_SOURCE 1 + +struct vsp1_lif { + struct vsp1_entity entity; +}; + +static inline struct vsp1_lif *to_lif(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_lif, entity.subdev); +} + +struct vsp1_lif *vsp1_lif_create(struct vsp1_device *vsp1, unsigned int index); + +#endif /* __VSP1_LIF_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_lut.c b/drivers/media/platform/renesas/vsp1/vsp1_lut.c new file mode 100644 index 000000000..ac6802a32 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_lut.c @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_lut.c -- R-Car VSP1 Look-Up Table + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_lut.h" + +#define LUT_MIN_SIZE 4U +#define LUT_MAX_SIZE 8190U + +#define LUT_SIZE 256 + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_lut_write(struct vsp1_lut *lut, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg, data); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +#define V4L2_CID_VSP1_LUT_TABLE (V4L2_CID_USER_BASE | 0x1001) + +static int lut_set_table(struct vsp1_lut *lut, struct v4l2_ctrl *ctrl) +{ + struct vsp1_dl_body *dlb; + unsigned int i; + + dlb = vsp1_dl_body_get(lut->pool); + if (!dlb) + return -ENOMEM; + + for (i = 0; i < LUT_SIZE; ++i) + vsp1_dl_body_write(dlb, VI6_LUT_TABLE + 4 * i, + ctrl->p_new.p_u32[i]); + + spin_lock_irq(&lut->lock); + swap(lut->lut, dlb); + spin_unlock_irq(&lut->lock); + + vsp1_dl_body_put(dlb); + return 0; +} + +static int lut_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_lut *lut = + container_of(ctrl->handler, struct vsp1_lut, ctrls); + + switch (ctrl->id) { + case V4L2_CID_VSP1_LUT_TABLE: + lut_set_table(lut, ctrl); + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops lut_ctrl_ops = { + .s_ctrl = lut_s_ctrl, +}; + +static const struct v4l2_ctrl_config lut_table_control = { + .ops = &lut_ctrl_ops, + .id = V4L2_CID_VSP1_LUT_TABLE, + .name = "Look-Up Table", + .type = V4L2_CTRL_TYPE_U32, + .min = 0x00000000, + .max = 0x00ffffff, + .step = 1, + .def = 0, + .dims = { LUT_SIZE }, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static const unsigned int lut_codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, +}; + +static int lut_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, lut_codes, + ARRAY_SIZE(lut_codes)); +} + +static int lut_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + return vsp1_subdev_enum_frame_size(subdev, sd_state, fse, + LUT_MIN_SIZE, + LUT_MIN_SIZE, LUT_MAX_SIZE, + LUT_MAX_SIZE); +} + +static int lut_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + return vsp1_subdev_set_pad_format(subdev, sd_state, fmt, lut_codes, + ARRAY_SIZE(lut_codes), + LUT_MIN_SIZE, LUT_MIN_SIZE, + LUT_MAX_SIZE, LUT_MAX_SIZE); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static const struct v4l2_subdev_pad_ops lut_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = lut_enum_mbus_code, + .enum_frame_size = lut_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = lut_set_format, +}; + +static const struct v4l2_subdev_ops lut_ops = { + .pad = &lut_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void lut_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_lut *lut = to_lut(&entity->subdev); + + vsp1_lut_write(lut, dlb, VI6_LUT_CTRL, VI6_LUT_CTRL_EN); +} + +static void lut_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_lut *lut = to_lut(&entity->subdev); + struct vsp1_dl_body *lut_dlb; + unsigned long flags; + + spin_lock_irqsave(&lut->lock, flags); + lut_dlb = lut->lut; + lut->lut = NULL; + spin_unlock_irqrestore(&lut->lock, flags); + + if (lut_dlb) { + vsp1_dl_list_add_body(dl, lut_dlb); + + /* Release our local reference. */ + vsp1_dl_body_put(lut_dlb); + } +} + +static void lut_destroy(struct vsp1_entity *entity) +{ + struct vsp1_lut *lut = to_lut(&entity->subdev); + + vsp1_dl_body_pool_destroy(lut->pool); +} + +static const struct vsp1_entity_operations lut_entity_ops = { + .configure_stream = lut_configure_stream, + .configure_frame = lut_configure_frame, + .destroy = lut_destroy, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_lut *vsp1_lut_create(struct vsp1_device *vsp1) +{ + struct vsp1_lut *lut; + int ret; + + lut = devm_kzalloc(vsp1->dev, sizeof(*lut), GFP_KERNEL); + if (lut == NULL) + return ERR_PTR(-ENOMEM); + + spin_lock_init(&lut->lock); + + lut->entity.ops = &lut_entity_ops; + lut->entity.type = VSP1_ENTITY_LUT; + + ret = vsp1_entity_init(vsp1, &lut->entity, "lut", 2, &lut_ops, + MEDIA_ENT_F_PROC_VIDEO_LUT); + if (ret < 0) + return ERR_PTR(ret); + + /* + * Pre-allocate a body pool, with 3 bodies allowing a userspace update + * before the hardware has committed a previous set of tables, handling + * both the queued and pending dl entries. + */ + lut->pool = vsp1_dl_body_pool_create(vsp1, 3, LUT_SIZE, 0); + if (!lut->pool) + return ERR_PTR(-ENOMEM); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&lut->ctrls, 1); + v4l2_ctrl_new_custom(&lut->ctrls, &lut_table_control, NULL); + + lut->entity.subdev.ctrl_handler = &lut->ctrls; + + if (lut->ctrls.error) { + dev_err(vsp1->dev, "lut: failed to initialize controls\n"); + ret = lut->ctrls.error; + vsp1_entity_destroy(&lut->entity); + return ERR_PTR(ret); + } + + v4l2_ctrl_handler_setup(&lut->ctrls); + + return lut; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_lut.h b/drivers/media/platform/renesas/vsp1/vsp1_lut.h new file mode 100644 index 000000000..8cb0df1b7 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_lut.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_lut.h -- R-Car VSP1 Look-Up Table + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_LUT_H__ +#define __VSP1_LUT_H__ + +#include + +#include +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define LUT_PAD_SINK 0 +#define LUT_PAD_SOURCE 1 + +struct vsp1_lut { + struct vsp1_entity entity; + + struct v4l2_ctrl_handler ctrls; + + spinlock_t lock; + struct vsp1_dl_body *lut; + struct vsp1_dl_body_pool *pool; +}; + +static inline struct vsp1_lut *to_lut(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_lut, entity.subdev); +} + +struct vsp1_lut *vsp1_lut_create(struct vsp1_device *vsp1); + +#endif /* __VSP1_LUT_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_pipe.c b/drivers/media/platform/renesas/vsp1/vsp1_pipe.c new file mode 100644 index 000000000..f72ac01c2 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_pipe.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_pipe.c -- R-Car VSP1 Pipeline + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include + +#include +#include + +#include "vsp1.h" +#include "vsp1_brx.h" +#include "vsp1_dl.h" +#include "vsp1_entity.h" +#include "vsp1_hgo.h" +#include "vsp1_hgt.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" +#include "vsp1_uds.h" + +/* ----------------------------------------------------------------------------- + * Helper Functions + */ + +static const struct vsp1_format_info vsp1_video_formats[] = { + { V4L2_PIX_FMT_RGB332, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_332, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 8, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_ARGB444, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_XRGB444, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_XRGB_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_RGBA444, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGBA_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_RGBX444, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGBX_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_ABGR444, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ABGR_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_XBGR444, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ABGR_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_BGRA444, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_BGRA_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_BGRX444, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_BGRA_4444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_ARGB555, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_XRGB555, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_XRGB_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_RGBA555, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGBA_5551, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_RGBX555, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGBX_5551, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_ABGR555, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ABGR_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_XBGR555, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ABGR_1555, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_BGRA555, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_BGRA_5551, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_BGRX555, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_BGRA_5551, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_RGB565, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_565, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS, + 1, { 16, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_BGR24, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_BGR_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 24, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_RGB24, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGB_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 24, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_ABGR32, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS, + 1, { 32, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_XBGR32, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS, + 1, { 32, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_BGRA32, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGBA_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS, + 1, { 32, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_BGRX32, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGBA_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS, + 1, { 32, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_RGBA32, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGBA_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 32, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_RGBX32, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_RGBA_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 32, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_ARGB32, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 32, 0, 0 }, false, false, 1, 1, true }, + { V4L2_PIX_FMT_XRGB32, MEDIA_BUS_FMT_ARGB8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 32, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_HSV24, MEDIA_BUS_FMT_AHSV8888_1X32, + VI6_FMT_RGB_888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 24, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_HSV32, MEDIA_BUS_FMT_AHSV8888_1X32, + VI6_FMT_ARGB_8888, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 32, 0, 0 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_UYVY, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, false, false, 2, 1, false }, + { V4L2_PIX_FMT_VYUY, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, false, true, 2, 1, false }, + { V4L2_PIX_FMT_YUYV, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, true, false, 2, 1, false }, + { V4L2_PIX_FMT_YVYU, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_YUYV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 1, { 16, 0, 0 }, true, true, 2, 1, false }, + { V4L2_PIX_FMT_NV12M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, false, 2, 2, false }, + { V4L2_PIX_FMT_NV21M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, true, 2, 2, false }, + { V4L2_PIX_FMT_NV16M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, false, 2, 1, false }, + { V4L2_PIX_FMT_NV61M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_UV_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 2, { 8, 16, 0 }, false, true, 2, 1, false }, + { V4L2_PIX_FMT_YUV420M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_U_V_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 3, { 8, 8, 8 }, false, false, 2, 2, false }, + { V4L2_PIX_FMT_YVU420M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_U_V_420, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 3, { 8, 8, 8 }, false, true, 2, 2, false }, + { V4L2_PIX_FMT_YUV422M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_U_V_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 3, { 8, 8, 8 }, false, false, 2, 1, false }, + { V4L2_PIX_FMT_YVU422M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_U_V_422, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 3, { 8, 8, 8 }, false, true, 2, 1, false }, + { V4L2_PIX_FMT_YUV444M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_U_V_444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 3, { 8, 8, 8 }, false, false, 1, 1, false }, + { V4L2_PIX_FMT_YVU444M, MEDIA_BUS_FMT_AYUV8_1X32, + VI6_FMT_Y_U_V_444, VI6_RPF_DSWAP_P_LLS | VI6_RPF_DSWAP_P_LWS | + VI6_RPF_DSWAP_P_WDS | VI6_RPF_DSWAP_P_BTS, + 3, { 8, 8, 8 }, false, true, 1, 1, false }, +}; + +/** + * vsp1_get_format_info - Retrieve format information for a 4CC + * @vsp1: the VSP1 device + * @fourcc: the format 4CC + * + * Return a pointer to the format information structure corresponding to the + * given V4L2 format 4CC, or NULL if no corresponding format can be found. + */ +const struct vsp1_format_info *vsp1_get_format_info(struct vsp1_device *vsp1, + u32 fourcc) +{ + unsigned int i; + + /* Special case, the VYUY and HSV formats are supported on Gen2 only. */ + if (vsp1->info->gen != 2) { + switch (fourcc) { + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_HSV24: + case V4L2_PIX_FMT_HSV32: + return NULL; + } + } + + for (i = 0; i < ARRAY_SIZE(vsp1_video_formats); ++i) { + const struct vsp1_format_info *info = &vsp1_video_formats[i]; + + if (info->fourcc == fourcc) + return info; + } + + return NULL; +} + +/* ----------------------------------------------------------------------------- + * Pipeline Management + */ + +void vsp1_pipeline_reset(struct vsp1_pipeline *pipe) +{ + struct vsp1_entity *entity; + unsigned int i; + + if (pipe->brx) { + struct vsp1_brx *brx = to_brx(&pipe->brx->subdev); + + for (i = 0; i < ARRAY_SIZE(brx->inputs); ++i) + brx->inputs[i].rpf = NULL; + } + + for (i = 0; i < ARRAY_SIZE(pipe->inputs); ++i) + pipe->inputs[i] = NULL; + + pipe->output = NULL; + + list_for_each_entry(entity, &pipe->entities, list_pipe) + entity->pipe = NULL; + + INIT_LIST_HEAD(&pipe->entities); + pipe->state = VSP1_PIPELINE_STOPPED; + pipe->buffers_ready = 0; + pipe->num_inputs = 0; + pipe->brx = NULL; + pipe->hgo = NULL; + pipe->hgt = NULL; + pipe->lif = NULL; + pipe->uds = NULL; +} + +void vsp1_pipeline_init(struct vsp1_pipeline *pipe) +{ + mutex_init(&pipe->lock); + spin_lock_init(&pipe->irqlock); + init_waitqueue_head(&pipe->wq); + kref_init(&pipe->kref); + + INIT_LIST_HEAD(&pipe->entities); + pipe->state = VSP1_PIPELINE_STOPPED; +} + +/* Must be called with the pipe irqlock held. */ +void vsp1_pipeline_run(struct vsp1_pipeline *pipe) +{ + struct vsp1_device *vsp1 = pipe->output->entity.vsp1; + + if (pipe->state == VSP1_PIPELINE_STOPPED) { + vsp1_write(vsp1, VI6_CMD(pipe->output->entity.index), + VI6_CMD_STRCMD); + pipe->state = VSP1_PIPELINE_RUNNING; + } + + pipe->buffers_ready = 0; +} + +bool vsp1_pipeline_stopped(struct vsp1_pipeline *pipe) +{ + unsigned long flags; + bool stopped; + + spin_lock_irqsave(&pipe->irqlock, flags); + stopped = pipe->state == VSP1_PIPELINE_STOPPED; + spin_unlock_irqrestore(&pipe->irqlock, flags); + + return stopped; +} + +int vsp1_pipeline_stop(struct vsp1_pipeline *pipe) +{ + struct vsp1_device *vsp1 = pipe->output->entity.vsp1; + struct vsp1_entity *entity; + unsigned long flags; + int ret; + + if (pipe->lif) { + /* + * When using display lists in continuous frame mode the only + * way to stop the pipeline is to reset the hardware. + */ + ret = vsp1_reset_wpf(vsp1, pipe->output->entity.index); + if (ret == 0) { + spin_lock_irqsave(&pipe->irqlock, flags); + pipe->state = VSP1_PIPELINE_STOPPED; + spin_unlock_irqrestore(&pipe->irqlock, flags); + } + } else { + /* Otherwise just request a stop and wait. */ + spin_lock_irqsave(&pipe->irqlock, flags); + if (pipe->state == VSP1_PIPELINE_RUNNING) + pipe->state = VSP1_PIPELINE_STOPPING; + spin_unlock_irqrestore(&pipe->irqlock, flags); + + ret = wait_event_timeout(pipe->wq, vsp1_pipeline_stopped(pipe), + msecs_to_jiffies(500)); + ret = ret == 0 ? -ETIMEDOUT : 0; + } + + list_for_each_entry(entity, &pipe->entities, list_pipe) { + if (entity->route && entity->route->reg) + vsp1_write(vsp1, entity->route->reg, + VI6_DPR_NODE_UNUSED); + } + + if (pipe->hgo) + vsp1_write(vsp1, VI6_DPR_HGO_SMPPT, + (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + + if (pipe->hgt) + vsp1_write(vsp1, VI6_DPR_HGT_SMPPT, + (7 << VI6_DPR_SMPPT_TGW_SHIFT) | + (VI6_DPR_NODE_UNUSED << VI6_DPR_SMPPT_PT_SHIFT)); + + v4l2_subdev_call(&pipe->output->entity.subdev, video, s_stream, 0); + + return ret; +} + +bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe) +{ + unsigned int mask; + + mask = ((1 << pipe->num_inputs) - 1) << 1; + if (!pipe->lif) + mask |= 1 << 0; + + return pipe->buffers_ready == mask; +} + +void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe) +{ + unsigned int flags; + + if (pipe == NULL) + return; + + /* + * If the DL commit raced with the frame end interrupt, the commit ends + * up being postponed by one frame. The returned flags tell whether the + * active frame was finished or postponed. + */ + flags = vsp1_dlm_irq_frame_end(pipe->output->dlm); + + if (pipe->hgo) + vsp1_hgo_frame_end(pipe->hgo); + + if (pipe->hgt) + vsp1_hgt_frame_end(pipe->hgt); + + /* + * Regardless of frame completion we still need to notify the pipe + * frame_end to account for vblank events. + */ + if (pipe->frame_end) + pipe->frame_end(pipe, flags); + + pipe->sequence++; +} + +/* + * Propagate the alpha value through the pipeline. + * + * As the UDS has restricted scaling capabilities when the alpha component needs + * to be scaled, we disable alpha scaling when the UDS input has a fixed alpha + * value. The UDS then outputs a fixed alpha value which needs to be programmed + * from the input RPF alpha. + */ +void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb, unsigned int alpha) +{ + if (!pipe->uds) + return; + + /* + * The BRU and BRS background color has a fixed alpha value set to 255, + * the output alpha value is thus always equal to 255. + */ + if (pipe->uds_input->type == VSP1_ENTITY_BRU || + pipe->uds_input->type == VSP1_ENTITY_BRS) + alpha = 255; + + vsp1_uds_set_alpha(pipe->uds, dlb, alpha); +} + +/* + * Propagate the partition calculations through the pipeline + * + * Work backwards through the pipe, allowing each entity to update the partition + * parameters based on its configuration, and the entity connected to its + * source. Each entity must produce the partition required for the previous + * entity in the pipeline. + */ +void vsp1_pipeline_propagate_partition(struct vsp1_pipeline *pipe, + struct vsp1_partition *partition, + unsigned int index, + struct vsp1_partition_window *window) +{ + struct vsp1_entity *entity; + + list_for_each_entry_reverse(entity, &pipe->entities, list_pipe) { + if (entity->ops->partition) + entity->ops->partition(entity, pipe, partition, index, + window); + } +} + diff --git a/drivers/media/platform/renesas/vsp1/vsp1_pipe.h b/drivers/media/platform/renesas/vsp1/vsp1_pipe.h new file mode 100644 index 000000000..ae646c9ef --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_pipe.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_pipe.h -- R-Car VSP1 Pipeline + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_PIPE_H__ +#define __VSP1_PIPE_H__ + +#include +#include +#include +#include + +#include + +struct vsp1_dl_list; +struct vsp1_rwpf; + +/* + * struct vsp1_format_info - VSP1 video format description + * @fourcc: V4L2 pixel format FCC identifier + * @mbus: media bus format code + * @hwfmt: VSP1 hardware format + * @swap: swap register control + * @planes: number of planes + * @bpp: bits per pixel + * @swap_yc: the Y and C components are swapped (Y comes before C) + * @swap_uv: the U and V components are swapped (V comes before U) + * @hsub: horizontal subsampling factor + * @vsub: vertical subsampling factor + * @alpha: has an alpha channel + */ +struct vsp1_format_info { + u32 fourcc; + unsigned int mbus; + unsigned int hwfmt; + unsigned int swap; + unsigned int planes; + unsigned int bpp[3]; + bool swap_yc; + bool swap_uv; + unsigned int hsub; + unsigned int vsub; + bool alpha; +}; + +enum vsp1_pipeline_state { + VSP1_PIPELINE_STOPPED, + VSP1_PIPELINE_RUNNING, + VSP1_PIPELINE_STOPPING, +}; + +/* + * struct vsp1_partition_window - Partition window coordinates + * @left: horizontal coordinate of the partition start in pixels relative to the + * left edge of the image + * @width: partition width in pixels + */ +struct vsp1_partition_window { + unsigned int left; + unsigned int width; +}; + +/* + * struct vsp1_partition - A description of a slice for the partition algorithm + * @rpf: The RPF partition window configuration + * @uds_sink: The UDS input partition window configuration + * @uds_source: The UDS output partition window configuration + * @sru: The SRU partition window configuration + * @wpf: The WPF partition window configuration + */ +struct vsp1_partition { + struct vsp1_partition_window rpf; + struct vsp1_partition_window uds_sink; + struct vsp1_partition_window uds_source; + struct vsp1_partition_window sru; + struct vsp1_partition_window wpf; +}; + +/* + * struct vsp1_pipeline - A VSP1 hardware pipeline + * @pipe: the media pipeline + * @irqlock: protects the pipeline state + * @state: current state + * @wq: wait queue to wait for state change completion + * @frame_end: frame end interrupt handler + * @lock: protects the pipeline use count and stream count + * @kref: pipeline reference count + * @stream_count: number of streaming video nodes + * @buffers_ready: bitmask of RPFs and WPFs with at least one buffer available + * @sequence: frame sequence number + * @num_inputs: number of RPFs + * @inputs: array of RPFs in the pipeline (indexed by RPF index) + * @output: WPF at the output of the pipeline + * @brx: BRx entity, if present + * @hgo: HGO entity, if present + * @hgt: HGT entity, if present + * @lif: LIF entity, if present + * @uds: UDS entity, if present + * @uds_input: entity at the input of the UDS, if the UDS is present + * @entities: list of entities in the pipeline + * @stream_config: cached stream configuration for video pipelines + * @configured: when false the @stream_config shall be written to the hardware + * @interlaced: True when the pipeline is configured in interlaced mode + * @partitions: The number of partitions used to process one frame + * @partition: The current partition for configuration to process + * @part_table: The pre-calculated partitions used by the pipeline + */ +struct vsp1_pipeline { + struct media_pipeline pipe; + + spinlock_t irqlock; + enum vsp1_pipeline_state state; + wait_queue_head_t wq; + + void (*frame_end)(struct vsp1_pipeline *pipe, unsigned int completion); + + struct mutex lock; + struct kref kref; + unsigned int stream_count; + unsigned int buffers_ready; + unsigned int sequence; + + unsigned int num_inputs; + struct vsp1_rwpf *inputs[VSP1_MAX_RPF]; + struct vsp1_rwpf *output; + struct vsp1_entity *brx; + struct vsp1_entity *hgo; + struct vsp1_entity *hgt; + struct vsp1_entity *lif; + struct vsp1_entity *uds; + struct vsp1_entity *uds_input; + + /* + * The order of this list must be identical to the order of the entities + * in the pipeline, as it is assumed by the partition algorithm that we + * can walk this list in sequence. + */ + struct list_head entities; + + struct vsp1_dl_body *stream_config; + bool configured; + bool interlaced; + + unsigned int partitions; + struct vsp1_partition *partition; + struct vsp1_partition *part_table; +}; + +void vsp1_pipeline_reset(struct vsp1_pipeline *pipe); +void vsp1_pipeline_init(struct vsp1_pipeline *pipe); + +void vsp1_pipeline_run(struct vsp1_pipeline *pipe); +bool vsp1_pipeline_stopped(struct vsp1_pipeline *pipe); +int vsp1_pipeline_stop(struct vsp1_pipeline *pipe); +bool vsp1_pipeline_ready(struct vsp1_pipeline *pipe); + +void vsp1_pipeline_frame_end(struct vsp1_pipeline *pipe); + +void vsp1_pipeline_propagate_alpha(struct vsp1_pipeline *pipe, + struct vsp1_dl_body *dlb, + unsigned int alpha); + +void vsp1_pipeline_propagate_partition(struct vsp1_pipeline *pipe, + struct vsp1_partition *partition, + unsigned int index, + struct vsp1_partition_window *window); + +const struct vsp1_format_info *vsp1_get_format_info(struct vsp1_device *vsp1, + u32 fourcc); + +#endif /* __VSP1_PIPE_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_regs.h b/drivers/media/platform/renesas/vsp1/vsp1_regs.h new file mode 100644 index 000000000..8928f4c6b --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_regs.h @@ -0,0 +1,860 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_regs.h -- R-Car VSP1 Registers Definitions + * + * Copyright (C) 2013 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#ifndef __VSP1_REGS_H__ +#define __VSP1_REGS_H__ + +/* ----------------------------------------------------------------------------- + * General Control Registers + */ + +#define VI6_CMD(n) (0x0000 + (n) * 4) +#define VI6_CMD_UPDHDR BIT(4) +#define VI6_CMD_STRCMD BIT(0) + +#define VI6_CLK_DCSWT 0x0018 +#define VI6_CLK_DCSWT_CSTPW_MASK (0xff << 8) +#define VI6_CLK_DCSWT_CSTPW_SHIFT 8 +#define VI6_CLK_DCSWT_CSTRW_MASK (0xff << 0) +#define VI6_CLK_DCSWT_CSTRW_SHIFT 0 + +#define VI6_SRESET 0x0028 +#define VI6_SRESET_SRTS(n) BIT(n) + +#define VI6_STATUS 0x0038 +#define VI6_STATUS_FLD_STD(n) BIT((n) + 28) +#define VI6_STATUS_SYS_ACT(n) BIT((n) + 8) + +#define VI6_WPF_IRQ_ENB(n) (0x0048 + (n) * 12) +#define VI6_WPF_IRQ_ENB_DFEE BIT(1) +#define VI6_WPF_IRQ_ENB_FREE BIT(0) + +#define VI6_WPF_IRQ_STA(n) (0x004c + (n) * 12) +#define VI6_WPF_IRQ_STA_DFE BIT(1) +#define VI6_WPF_IRQ_STA_FRE BIT(0) + +#define VI6_DISP_IRQ_ENB(n) (0x0078 + (n) * 60) +#define VI6_DISP_IRQ_ENB_DSTE BIT(8) +#define VI6_DISP_IRQ_ENB_MAEE BIT(5) +#define VI6_DISP_IRQ_ENB_LNEE(n) BIT(n) + +#define VI6_DISP_IRQ_STA(n) (0x007c + (n) * 60) +#define VI6_DISP_IRQ_STA_DST BIT(8) +#define VI6_DISP_IRQ_STA_MAE BIT(5) +#define VI6_DISP_IRQ_STA_LNE(n) BIT(n) + +#define VI6_WPF_LINE_COUNT(n) (0x0084 + (n) * 4) +#define VI6_WPF_LINE_COUNT_MASK (0x1fffff << 0) + +/* ----------------------------------------------------------------------------- + * Display List Control Registers + */ + +#define VI6_DL_CTRL 0x0100 +#define VI6_DL_CTRL_AR_WAIT_MASK (0xffff << 16) +#define VI6_DL_CTRL_AR_WAIT_SHIFT 16 +#define VI6_DL_CTRL_DC2 BIT(12) +#define VI6_DL_CTRL_DC1 BIT(8) +#define VI6_DL_CTRL_DC0 BIT(4) +#define VI6_DL_CTRL_CFM0 BIT(2) +#define VI6_DL_CTRL_NH0 BIT(1) +#define VI6_DL_CTRL_DLE BIT(0) + +#define VI6_DL_HDR_ADDR(n) (0x0104 + (n) * 4) + +#define VI6_DL_SWAP 0x0114 +#define VI6_DL_SWAP_LWS BIT(2) +#define VI6_DL_SWAP_WDS BIT(1) +#define VI6_DL_SWAP_BTS BIT(0) + +#define VI6_DL_EXT_CTRL(n) (0x011c + (n) * 36) +#define VI6_DL_EXT_CTRL_NWE BIT(16) +#define VI6_DL_EXT_CTRL_POLINT_MASK (0x3f << 8) +#define VI6_DL_EXT_CTRL_POLINT_SHIFT 8 +#define VI6_DL_EXT_CTRL_DLPRI BIT(5) +#define VI6_DL_EXT_CTRL_EXPRI BIT(4) +#define VI6_DL_EXT_CTRL_EXT BIT(0) + +#define VI6_DL_EXT_AUTOFLD_INT BIT(0) + +#define VI6_DL_BODY_SIZE 0x0120 +#define VI6_DL_BODY_SIZE_UPD BIT(24) +#define VI6_DL_BODY_SIZE_BS_MASK (0x1ffff << 0) +#define VI6_DL_BODY_SIZE_BS_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * RPF Control Registers + */ + +#define VI6_RPF_OFFSET 0x100 + +#define VI6_RPF_SRC_BSIZE 0x0300 +#define VI6_RPF_SRC_BSIZE_BHSIZE_MASK (0x1fff << 16) +#define VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT 16 +#define VI6_RPF_SRC_BSIZE_BVSIZE_MASK (0x1fff << 0) +#define VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT 0 + +#define VI6_RPF_SRC_ESIZE 0x0304 +#define VI6_RPF_SRC_ESIZE_EHSIZE_MASK (0x1fff << 16) +#define VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT 16 +#define VI6_RPF_SRC_ESIZE_EVSIZE_MASK (0x1fff << 0) +#define VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT 0 + +#define VI6_RPF_INFMT 0x0308 +#define VI6_RPF_INFMT_VIR BIT(28) +#define VI6_RPF_INFMT_CIPM BIT(16) +#define VI6_RPF_INFMT_SPYCS BIT(15) +#define VI6_RPF_INFMT_SPUVS BIT(14) +#define VI6_RPF_INFMT_CEXT_ZERO (0 << 12) +#define VI6_RPF_INFMT_CEXT_EXT (1 << 12) +#define VI6_RPF_INFMT_CEXT_ONE (2 << 12) +#define VI6_RPF_INFMT_CEXT_MASK (3 << 12) +#define VI6_RPF_INFMT_RDTM_BT601 (0 << 9) +#define VI6_RPF_INFMT_RDTM_BT601_EXT (1 << 9) +#define VI6_RPF_INFMT_RDTM_BT709 (2 << 9) +#define VI6_RPF_INFMT_RDTM_BT709_EXT (3 << 9) +#define VI6_RPF_INFMT_RDTM_MASK (7 << 9) +#define VI6_RPF_INFMT_CSC BIT(8) +#define VI6_RPF_INFMT_RDFMT_MASK (0x7f << 0) +#define VI6_RPF_INFMT_RDFMT_SHIFT 0 + +#define VI6_RPF_DSWAP 0x030c +#define VI6_RPF_DSWAP_A_LLS BIT(11) +#define VI6_RPF_DSWAP_A_LWS BIT(10) +#define VI6_RPF_DSWAP_A_WDS BIT(9) +#define VI6_RPF_DSWAP_A_BTS BIT(8) +#define VI6_RPF_DSWAP_P_LLS BIT(3) +#define VI6_RPF_DSWAP_P_LWS BIT(2) +#define VI6_RPF_DSWAP_P_WDS BIT(1) +#define VI6_RPF_DSWAP_P_BTS BIT(0) + +#define VI6_RPF_LOC 0x0310 +#define VI6_RPF_LOC_HCOORD_MASK (0x1fff << 16) +#define VI6_RPF_LOC_HCOORD_SHIFT 16 +#define VI6_RPF_LOC_VCOORD_MASK (0x1fff << 0) +#define VI6_RPF_LOC_VCOORD_SHIFT 0 + +#define VI6_RPF_ALPH_SEL 0x0314 +#define VI6_RPF_ALPH_SEL_ASEL_PACKED (0 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_8B_PLANE (1 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_SELECT (2 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_1B_PLANE (3 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_FIXED (4 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_MASK (7 << 28) +#define VI6_RPF_ALPH_SEL_ASEL_SHIFT 28 +#define VI6_RPF_ALPH_SEL_IROP_MASK (0xf << 24) +#define VI6_RPF_ALPH_SEL_IROP_SHIFT 24 +#define VI6_RPF_ALPH_SEL_BSEL BIT(23) +#define VI6_RPF_ALPH_SEL_AEXT_ZERO (0 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_EXT (1 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_ONE (2 << 18) +#define VI6_RPF_ALPH_SEL_AEXT_MASK (3 << 18) +#define VI6_RPF_ALPH_SEL_ALPHA1_MASK (0xff << 8) +#define VI6_RPF_ALPH_SEL_ALPHA1_SHIFT 8 +#define VI6_RPF_ALPH_SEL_ALPHA0_MASK (0xff << 0) +#define VI6_RPF_ALPH_SEL_ALPHA0_SHIFT 0 + +#define VI6_RPF_VRTCOL_SET 0x0318 +#define VI6_RPF_VRTCOL_SET_LAYA_MASK (0xff << 24) +#define VI6_RPF_VRTCOL_SET_LAYA_SHIFT 24 +#define VI6_RPF_VRTCOL_SET_LAYR_MASK (0xff << 16) +#define VI6_RPF_VRTCOL_SET_LAYR_SHIFT 16 +#define VI6_RPF_VRTCOL_SET_LAYG_MASK (0xff << 8) +#define VI6_RPF_VRTCOL_SET_LAYG_SHIFT 8 +#define VI6_RPF_VRTCOL_SET_LAYB_MASK (0xff << 0) +#define VI6_RPF_VRTCOL_SET_LAYB_SHIFT 0 + +#define VI6_RPF_MSK_CTRL 0x031c +#define VI6_RPF_MSK_CTRL_MSK_EN BIT(24) +#define VI6_RPF_MSK_CTRL_MGR_MASK (0xff << 16) +#define VI6_RPF_MSK_CTRL_MGR_SHIFT 16 +#define VI6_RPF_MSK_CTRL_MGG_MASK (0xff << 8) +#define VI6_RPF_MSK_CTRL_MGG_SHIFT 8 +#define VI6_RPF_MSK_CTRL_MGB_MASK (0xff << 0) +#define VI6_RPF_MSK_CTRL_MGB_SHIFT 0 + +#define VI6_RPF_MSK_SET0 0x0320 +#define VI6_RPF_MSK_SET1 0x0324 +#define VI6_RPF_MSK_SET_MSA_MASK (0xff << 24) +#define VI6_RPF_MSK_SET_MSA_SHIFT 24 +#define VI6_RPF_MSK_SET_MSR_MASK (0xff << 16) +#define VI6_RPF_MSK_SET_MSR_SHIFT 16 +#define VI6_RPF_MSK_SET_MSG_MASK (0xff << 8) +#define VI6_RPF_MSK_SET_MSG_SHIFT 8 +#define VI6_RPF_MSK_SET_MSB_MASK (0xff << 0) +#define VI6_RPF_MSK_SET_MSB_SHIFT 0 + +#define VI6_RPF_CKEY_CTRL 0x0328 +#define VI6_RPF_CKEY_CTRL_CV BIT(4) +#define VI6_RPF_CKEY_CTRL_SAPE1 BIT(1) +#define VI6_RPF_CKEY_CTRL_SAPE0 BIT(0) + +#define VI6_RPF_CKEY_SET0 0x032c +#define VI6_RPF_CKEY_SET1 0x0330 +#define VI6_RPF_CKEY_SET_AP_MASK (0xff << 24) +#define VI6_RPF_CKEY_SET_AP_SHIFT 24 +#define VI6_RPF_CKEY_SET_R_MASK (0xff << 16) +#define VI6_RPF_CKEY_SET_R_SHIFT 16 +#define VI6_RPF_CKEY_SET_GY_MASK (0xff << 8) +#define VI6_RPF_CKEY_SET_GY_SHIFT 8 +#define VI6_RPF_CKEY_SET_B_MASK (0xff << 0) +#define VI6_RPF_CKEY_SET_B_SHIFT 0 + +#define VI6_RPF_SRCM_PSTRIDE 0x0334 +#define VI6_RPF_SRCM_PSTRIDE_Y_SHIFT 16 +#define VI6_RPF_SRCM_PSTRIDE_C_SHIFT 0 + +#define VI6_RPF_SRCM_ASTRIDE 0x0338 +#define VI6_RPF_SRCM_PSTRIDE_A_SHIFT 0 + +#define VI6_RPF_SRCM_ADDR_Y 0x033c +#define VI6_RPF_SRCM_ADDR_C0 0x0340 +#define VI6_RPF_SRCM_ADDR_C1 0x0344 +#define VI6_RPF_SRCM_ADDR_AI 0x0348 + +#define VI6_RPF_MULT_ALPHA 0x036c +#define VI6_RPF_MULT_ALPHA_A_MMD_NONE (0 << 12) +#define VI6_RPF_MULT_ALPHA_A_MMD_RATIO (1 << 12) +#define VI6_RPF_MULT_ALPHA_P_MMD_NONE (0 << 8) +#define VI6_RPF_MULT_ALPHA_P_MMD_RATIO (1 << 8) +#define VI6_RPF_MULT_ALPHA_P_MMD_IMAGE (2 << 8) +#define VI6_RPF_MULT_ALPHA_P_MMD_BOTH (3 << 8) +#define VI6_RPF_MULT_ALPHA_RATIO_MASK (0xff << 0) +#define VI6_RPF_MULT_ALPHA_RATIO_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * WPF Control Registers + */ + +#define VI6_WPF_OFFSET 0x100 + +#define VI6_WPF_SRCRPF 0x1000 +#define VI6_WPF_SRCRPF_VIRACT_DIS (0 << 28) +#define VI6_WPF_SRCRPF_VIRACT_SUB (1 << 28) +#define VI6_WPF_SRCRPF_VIRACT_MST (2 << 28) +#define VI6_WPF_SRCRPF_VIRACT_MASK (3 << 28) +#define VI6_WPF_SRCRPF_VIRACT2_DIS (0 << 24) +#define VI6_WPF_SRCRPF_VIRACT2_SUB (1 << 24) +#define VI6_WPF_SRCRPF_VIRACT2_MST (2 << 24) +#define VI6_WPF_SRCRPF_VIRACT2_MASK (3 << 24) +#define VI6_WPF_SRCRPF_RPF_ACT_DIS(n) (0 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_SUB(n) (1 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_MST(n) (2 << ((n) * 2)) +#define VI6_WPF_SRCRPF_RPF_ACT_MASK(n) (3 << ((n) * 2)) + +#define VI6_WPF_HSZCLIP 0x1004 +#define VI6_WPF_VSZCLIP 0x1008 +#define VI6_WPF_SZCLIP_EN BIT(28) +#define VI6_WPF_SZCLIP_OFST_MASK (0xff << 16) +#define VI6_WPF_SZCLIP_OFST_SHIFT 16 +#define VI6_WPF_SZCLIP_SIZE_MASK (0xfff << 0) +#define VI6_WPF_SZCLIP_SIZE_SHIFT 0 + +#define VI6_WPF_OUTFMT 0x100c +#define VI6_WPF_OUTFMT_PDV_MASK (0xff << 24) +#define VI6_WPF_OUTFMT_PDV_SHIFT 24 +#define VI6_WPF_OUTFMT_PXA BIT(23) +#define VI6_WPF_OUTFMT_ROT BIT(18) +#define VI6_WPF_OUTFMT_HFLP BIT(17) +#define VI6_WPF_OUTFMT_FLP BIT(16) +#define VI6_WPF_OUTFMT_SPYCS BIT(15) +#define VI6_WPF_OUTFMT_SPUVS BIT(14) +#define VI6_WPF_OUTFMT_DITH_DIS (0 << 12) +#define VI6_WPF_OUTFMT_DITH_EN (3 << 12) +#define VI6_WPF_OUTFMT_DITH_MASK (3 << 12) +#define VI6_WPF_OUTFMT_WRTM_BT601 (0 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT601_EXT (1 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT709 (2 << 9) +#define VI6_WPF_OUTFMT_WRTM_BT709_EXT (3 << 9) +#define VI6_WPF_OUTFMT_WRTM_MASK (7 << 9) +#define VI6_WPF_OUTFMT_CSC BIT(8) +#define VI6_WPF_OUTFMT_WRFMT_MASK (0x7f << 0) +#define VI6_WPF_OUTFMT_WRFMT_SHIFT 0 + +#define VI6_WPF_DSWAP 0x1010 +#define VI6_WPF_DSWAP_P_LLS BIT(3) +#define VI6_WPF_DSWAP_P_LWS BIT(2) +#define VI6_WPF_DSWAP_P_WDS BIT(1) +#define VI6_WPF_DSWAP_P_BTS BIT(0) + +#define VI6_WPF_RNDCTRL 0x1014 +#define VI6_WPF_RNDCTRL_CBRM BIT(28) +#define VI6_WPF_RNDCTRL_ABRM_TRUNC (0 << 24) +#define VI6_WPF_RNDCTRL_ABRM_ROUND (1 << 24) +#define VI6_WPF_RNDCTRL_ABRM_THRESH (2 << 24) +#define VI6_WPF_RNDCTRL_ABRM_MASK (3 << 24) +#define VI6_WPF_RNDCTRL_ATHRESH_MASK (0xff << 16) +#define VI6_WPF_RNDCTRL_ATHRESH_SHIFT 16 +#define VI6_WPF_RNDCTRL_CLMD_FULL (0 << 12) +#define VI6_WPF_RNDCTRL_CLMD_CLIP (1 << 12) +#define VI6_WPF_RNDCTRL_CLMD_EXT (2 << 12) +#define VI6_WPF_RNDCTRL_CLMD_MASK (3 << 12) + +#define VI6_WPF_ROT_CTRL 0x1018 +#define VI6_WPF_ROT_CTRL_LN16 BIT(17) +#define VI6_WPF_ROT_CTRL_LMEM_WD_MASK (0x1fff << 0) +#define VI6_WPF_ROT_CTRL_LMEM_WD_SHIFT 0 + +#define VI6_WPF_DSTM_STRIDE_Y 0x101c +#define VI6_WPF_DSTM_STRIDE_C 0x1020 +#define VI6_WPF_DSTM_ADDR_Y 0x1024 +#define VI6_WPF_DSTM_ADDR_C0 0x1028 +#define VI6_WPF_DSTM_ADDR_C1 0x102c + +#define VI6_WPF_WRBCK_CTRL(n) (0x1034 + (n) * 0x100) +#define VI6_WPF_WRBCK_CTRL_WBMD BIT(0) + +/* ----------------------------------------------------------------------------- + * UIF Control Registers + */ + +#define VI6_UIF_OFFSET 0x100 + +#define VI6_UIF_DISCOM_DOCMCR 0x1c00 +#define VI6_UIF_DISCOM_DOCMCR_CMPRU BIT(16) +#define VI6_UIF_DISCOM_DOCMCR_CMPR BIT(0) + +#define VI6_UIF_DISCOM_DOCMSTR 0x1c04 +#define VI6_UIF_DISCOM_DOCMSTR_CMPPRE BIT(1) +#define VI6_UIF_DISCOM_DOCMSTR_CMPST BIT(0) + +#define VI6_UIF_DISCOM_DOCMCLSTR 0x1c08 +#define VI6_UIF_DISCOM_DOCMCLSTR_CMPCLPRE BIT(1) +#define VI6_UIF_DISCOM_DOCMCLSTR_CMPCLST BIT(0) + +#define VI6_UIF_DISCOM_DOCMIENR 0x1c0c +#define VI6_UIF_DISCOM_DOCMIENR_CMPPREIEN BIT(1) +#define VI6_UIF_DISCOM_DOCMIENR_CMPIEN BIT(0) + +#define VI6_UIF_DISCOM_DOCMMDR 0x1c10 +#define VI6_UIF_DISCOM_DOCMMDR_INTHRH(n) ((n) << 16) + +#define VI6_UIF_DISCOM_DOCMPMR 0x1c14 +#define VI6_UIF_DISCOM_DOCMPMR_CMPDFF(n) ((n) << 17) +#define VI6_UIF_DISCOM_DOCMPMR_CMPDFA(n) ((n) << 8) +#define VI6_UIF_DISCOM_DOCMPMR_CMPDAUF BIT(7) +#define VI6_UIF_DISCOM_DOCMPMR_SEL(n) ((n) << 0) + +#define VI6_UIF_DISCOM_DOCMECRCR 0x1c18 +#define VI6_UIF_DISCOM_DOCMCCRCR 0x1c1c +#define VI6_UIF_DISCOM_DOCMSPXR 0x1c20 +#define VI6_UIF_DISCOM_DOCMSPYR 0x1c24 +#define VI6_UIF_DISCOM_DOCMSZXR 0x1c28 +#define VI6_UIF_DISCOM_DOCMSZYR 0x1c2c + +/* ----------------------------------------------------------------------------- + * DPR Control Registers + */ + +#define VI6_DPR_RPF_ROUTE(n) (0x2000 + (n) * 4) + +#define VI6_DPR_WPF_FPORCH(n) (0x2014 + (n) * 4) +#define VI6_DPR_WPF_FPORCH_FP_WPFN (5 << 8) + +#define VI6_DPR_SRU_ROUTE 0x2024 +#define VI6_DPR_UDS_ROUTE(n) (0x2028 + (n) * 4) +#define VI6_DPR_LUT_ROUTE 0x203c +#define VI6_DPR_CLU_ROUTE 0x2040 +#define VI6_DPR_HST_ROUTE 0x2044 +#define VI6_DPR_HSI_ROUTE 0x2048 +#define VI6_DPR_BRU_ROUTE 0x204c +#define VI6_DPR_ILV_BRS_ROUTE 0x2050 +#define VI6_DPR_ROUTE_BRSSEL BIT(28) +#define VI6_DPR_ROUTE_FXA_MASK (0xff << 16) +#define VI6_DPR_ROUTE_FXA_SHIFT 16 +#define VI6_DPR_ROUTE_FP_MASK (0x3f << 8) +#define VI6_DPR_ROUTE_FP_SHIFT 8 +#define VI6_DPR_ROUTE_RT_MASK (0x3f << 0) +#define VI6_DPR_ROUTE_RT_SHIFT 0 + +#define VI6_DPR_HGO_SMPPT 0x2054 +#define VI6_DPR_HGT_SMPPT 0x2058 +#define VI6_DPR_SMPPT_TGW_MASK (7 << 8) +#define VI6_DPR_SMPPT_TGW_SHIFT 8 +#define VI6_DPR_SMPPT_PT_MASK (0x3f << 0) +#define VI6_DPR_SMPPT_PT_SHIFT 0 + +#define VI6_DPR_UIF_ROUTE(n) (0x2074 + (n) * 4) + +#define VI6_DPR_NODE_RPF(n) (n) +#define VI6_DPR_NODE_UIF(n) (12 + (n)) +#define VI6_DPR_NODE_SRU 16 +#define VI6_DPR_NODE_UDS(n) (17 + (n)) +#define VI6_DPR_NODE_LUT 22 +#define VI6_DPR_NODE_BRU_IN(n) (((n) <= 3) ? 23 + (n) : 49) +#define VI6_DPR_NODE_BRU_OUT 27 +#define VI6_DPR_NODE_CLU 29 +#define VI6_DPR_NODE_HST 30 +#define VI6_DPR_NODE_HSI 31 +#define VI6_DPR_NODE_BRS_IN(n) (38 + (n)) +#define VI6_DPR_NODE_LIF 55 /* Gen2 only */ +#define VI6_DPR_NODE_WPF(n) (56 + (n)) +#define VI6_DPR_NODE_UNUSED 63 + +/* ----------------------------------------------------------------------------- + * SRU Control Registers + */ + +#define VI6_SRU_CTRL0 0x2200 +#define VI6_SRU_CTRL0_PARAM0_MASK (0x1ff << 16) +#define VI6_SRU_CTRL0_PARAM0_SHIFT 16 +#define VI6_SRU_CTRL0_PARAM1_MASK (0x1f << 8) +#define VI6_SRU_CTRL0_PARAM1_SHIFT 8 +#define VI6_SRU_CTRL0_MODE_UPSCALE (4 << 4) +#define VI6_SRU_CTRL0_PARAM2 BIT(3) +#define VI6_SRU_CTRL0_PARAM3 BIT(2) +#define VI6_SRU_CTRL0_PARAM4 BIT(1) +#define VI6_SRU_CTRL0_EN BIT(0) + +#define VI6_SRU_CTRL1 0x2204 +#define VI6_SRU_CTRL1_PARAM5 0x7ff + +#define VI6_SRU_CTRL2 0x2208 +#define VI6_SRU_CTRL2_PARAM6_SHIFT 16 +#define VI6_SRU_CTRL2_PARAM7_SHIFT 8 +#define VI6_SRU_CTRL2_PARAM8_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * UDS Control Registers + */ + +#define VI6_UDS_OFFSET 0x100 + +#define VI6_UDS_CTRL 0x2300 +#define VI6_UDS_CTRL_AMD BIT(30) +#define VI6_UDS_CTRL_FMD BIT(29) +#define VI6_UDS_CTRL_BLADV BIT(28) +#define VI6_UDS_CTRL_AON BIT(25) +#define VI6_UDS_CTRL_ATHON BIT(24) +#define VI6_UDS_CTRL_BC BIT(20) +#define VI6_UDS_CTRL_NE_A BIT(19) +#define VI6_UDS_CTRL_NE_RCR BIT(18) +#define VI6_UDS_CTRL_NE_GY BIT(17) +#define VI6_UDS_CTRL_NE_BCB BIT(16) +#define VI6_UDS_CTRL_AMDSLH BIT(2) +#define VI6_UDS_CTRL_TDIPC BIT(1) + +#define VI6_UDS_SCALE 0x2304 +#define VI6_UDS_SCALE_HMANT_MASK (0xf << 28) +#define VI6_UDS_SCALE_HMANT_SHIFT 28 +#define VI6_UDS_SCALE_HFRAC_MASK (0xfff << 16) +#define VI6_UDS_SCALE_HFRAC_SHIFT 16 +#define VI6_UDS_SCALE_VMANT_MASK (0xf << 12) +#define VI6_UDS_SCALE_VMANT_SHIFT 12 +#define VI6_UDS_SCALE_VFRAC_MASK (0xfff << 0) +#define VI6_UDS_SCALE_VFRAC_SHIFT 0 + +#define VI6_UDS_ALPTH 0x2308 +#define VI6_UDS_ALPTH_TH1_MASK (0xff << 8) +#define VI6_UDS_ALPTH_TH1_SHIFT 8 +#define VI6_UDS_ALPTH_TH0_MASK (0xff << 0) +#define VI6_UDS_ALPTH_TH0_SHIFT 0 + +#define VI6_UDS_ALPVAL 0x230c +#define VI6_UDS_ALPVAL_VAL2_MASK (0xff << 16) +#define VI6_UDS_ALPVAL_VAL2_SHIFT 16 +#define VI6_UDS_ALPVAL_VAL1_MASK (0xff << 8) +#define VI6_UDS_ALPVAL_VAL1_SHIFT 8 +#define VI6_UDS_ALPVAL_VAL0_MASK (0xff << 0) +#define VI6_UDS_ALPVAL_VAL0_SHIFT 0 + +#define VI6_UDS_PASS_BWIDTH 0x2310 +#define VI6_UDS_PASS_BWIDTH_H_MASK (0x7f << 16) +#define VI6_UDS_PASS_BWIDTH_H_SHIFT 16 +#define VI6_UDS_PASS_BWIDTH_V_MASK (0x7f << 0) +#define VI6_UDS_PASS_BWIDTH_V_SHIFT 0 + +#define VI6_UDS_HPHASE 0x2314 +#define VI6_UDS_HPHASE_HSTP_MASK (0xfff << 16) +#define VI6_UDS_HPHASE_HSTP_SHIFT 16 +#define VI6_UDS_HPHASE_HEDP_MASK (0xfff << 0) +#define VI6_UDS_HPHASE_HEDP_SHIFT 0 + +#define VI6_UDS_IPC 0x2318 +#define VI6_UDS_IPC_FIELD BIT(27) +#define VI6_UDS_IPC_VEDP_MASK (0xfff << 0) +#define VI6_UDS_IPC_VEDP_SHIFT 0 + +#define VI6_UDS_HSZCLIP 0x231c +#define VI6_UDS_HSZCLIP_HCEN BIT(28) +#define VI6_UDS_HSZCLIP_HCL_OFST_MASK (0xff << 16) +#define VI6_UDS_HSZCLIP_HCL_OFST_SHIFT 16 +#define VI6_UDS_HSZCLIP_HCL_SIZE_MASK (0x1fff << 0) +#define VI6_UDS_HSZCLIP_HCL_SIZE_SHIFT 0 + +#define VI6_UDS_CLIP_SIZE 0x2324 +#define VI6_UDS_CLIP_SIZE_HSIZE_MASK (0x1fff << 16) +#define VI6_UDS_CLIP_SIZE_HSIZE_SHIFT 16 +#define VI6_UDS_CLIP_SIZE_VSIZE_MASK (0x1fff << 0) +#define VI6_UDS_CLIP_SIZE_VSIZE_SHIFT 0 + +#define VI6_UDS_FILL_COLOR 0x2328 +#define VI6_UDS_FILL_COLOR_RFILC_MASK (0xff << 16) +#define VI6_UDS_FILL_COLOR_RFILC_SHIFT 16 +#define VI6_UDS_FILL_COLOR_GFILC_MASK (0xff << 8) +#define VI6_UDS_FILL_COLOR_GFILC_SHIFT 8 +#define VI6_UDS_FILL_COLOR_BFILC_MASK (0xff << 0) +#define VI6_UDS_FILL_COLOR_BFILC_SHIFT 0 + +/* ----------------------------------------------------------------------------- + * LUT Control Registers + */ + +#define VI6_LUT_CTRL 0x2800 +#define VI6_LUT_CTRL_EN BIT(0) + +/* ----------------------------------------------------------------------------- + * CLU Control Registers + */ + +#define VI6_CLU_CTRL 0x2900 +#define VI6_CLU_CTRL_AAI BIT(28) +#define VI6_CLU_CTRL_MVS BIT(24) +#define VI6_CLU_CTRL_AX1I_2D (3 << 14) +#define VI6_CLU_CTRL_AX2I_2D (1 << 12) +#define VI6_CLU_CTRL_OS0_2D (3 << 8) +#define VI6_CLU_CTRL_OS1_2D (1 << 6) +#define VI6_CLU_CTRL_OS2_2D (3 << 4) +#define VI6_CLU_CTRL_M2D BIT(1) +#define VI6_CLU_CTRL_EN BIT(0) + +/* ----------------------------------------------------------------------------- + * HST Control Registers + */ + +#define VI6_HST_CTRL 0x2a00 +#define VI6_HST_CTRL_EN BIT(0) + +/* ----------------------------------------------------------------------------- + * HSI Control Registers + */ + +#define VI6_HSI_CTRL 0x2b00 +#define VI6_HSI_CTRL_EN BIT(0) + +/* ----------------------------------------------------------------------------- + * BRS and BRU Control Registers + */ + +#define VI6_ROP_NOP 0 +#define VI6_ROP_AND 1 +#define VI6_ROP_AND_REV 2 +#define VI6_ROP_COPY 3 +#define VI6_ROP_AND_INV 4 +#define VI6_ROP_CLEAR 5 +#define VI6_ROP_XOR 6 +#define VI6_ROP_OR 7 +#define VI6_ROP_NOR 8 +#define VI6_ROP_EQUIV 9 +#define VI6_ROP_INVERT 10 +#define VI6_ROP_OR_REV 11 +#define VI6_ROP_COPY_INV 12 +#define VI6_ROP_OR_INV 13 +#define VI6_ROP_NAND 14 +#define VI6_ROP_SET 15 + +#define VI6_BRU_BASE 0x2c00 +#define VI6_BRS_BASE 0x3900 + +#define VI6_BRU_INCTRL 0x0000 +#define VI6_BRU_INCTRL_NRM BIT(28) +#define VI6_BRU_INCTRL_DnON (1 << (16 + (n))) +#define VI6_BRU_INCTRL_DITHn_OFF (0 << ((n) * 4)) +#define VI6_BRU_INCTRL_DITHn_18BPP (1 << ((n) * 4)) +#define VI6_BRU_INCTRL_DITHn_16BPP (2 << ((n) * 4)) +#define VI6_BRU_INCTRL_DITHn_15BPP (3 << ((n) * 4)) +#define VI6_BRU_INCTRL_DITHn_12BPP (4 << ((n) * 4)) +#define VI6_BRU_INCTRL_DITHn_8BPP (5 << ((n) * 4)) +#define VI6_BRU_INCTRL_DITHn_MASK (7 << ((n) * 4)) +#define VI6_BRU_INCTRL_DITHn_SHIFT ((n) * 4) + +#define VI6_BRU_VIRRPF_SIZE 0x0004 +#define VI6_BRU_VIRRPF_SIZE_HSIZE_MASK (0x1fff << 16) +#define VI6_BRU_VIRRPF_SIZE_HSIZE_SHIFT 16 +#define VI6_BRU_VIRRPF_SIZE_VSIZE_MASK (0x1fff << 0) +#define VI6_BRU_VIRRPF_SIZE_VSIZE_SHIFT 0 + +#define VI6_BRU_VIRRPF_LOC 0x0008 +#define VI6_BRU_VIRRPF_LOC_HCOORD_MASK (0x1fff << 16) +#define VI6_BRU_VIRRPF_LOC_HCOORD_SHIFT 16 +#define VI6_BRU_VIRRPF_LOC_VCOORD_MASK (0x1fff << 0) +#define VI6_BRU_VIRRPF_LOC_VCOORD_SHIFT 0 + +#define VI6_BRU_VIRRPF_COL 0x000c +#define VI6_BRU_VIRRPF_COL_A_MASK (0xff << 24) +#define VI6_BRU_VIRRPF_COL_A_SHIFT 24 +#define VI6_BRU_VIRRPF_COL_RCR_MASK (0xff << 16) +#define VI6_BRU_VIRRPF_COL_RCR_SHIFT 16 +#define VI6_BRU_VIRRPF_COL_GY_MASK (0xff << 8) +#define VI6_BRU_VIRRPF_COL_GY_SHIFT 8 +#define VI6_BRU_VIRRPF_COL_BCB_MASK (0xff << 0) +#define VI6_BRU_VIRRPF_COL_BCB_SHIFT 0 + +#define VI6_BRU_CTRL(n) (0x0010 + (n) * 8 + ((n) <= 3 ? 0 : 4)) +#define VI6_BRU_CTRL_RBC BIT(31) +#define VI6_BRU_CTRL_DSTSEL_BRUIN(n) (((n) <= 3 ? (n) : (n)+1) << 20) +#define VI6_BRU_CTRL_DSTSEL_VRPF (4 << 20) +#define VI6_BRU_CTRL_DSTSEL_MASK (7 << 20) +#define VI6_BRU_CTRL_SRCSEL_BRUIN(n) (((n) <= 3 ? (n) : (n)+1) << 16) +#define VI6_BRU_CTRL_SRCSEL_VRPF (4 << 16) +#define VI6_BRU_CTRL_SRCSEL_MASK (7 << 16) +#define VI6_BRU_CTRL_CROP(rop) ((rop) << 4) +#define VI6_BRU_CTRL_CROP_MASK (0xf << 4) +#define VI6_BRU_CTRL_AROP(rop) ((rop) << 0) +#define VI6_BRU_CTRL_AROP_MASK (0xf << 0) + +#define VI6_BRU_BLD(n) (0x0014 + (n) * 8 + ((n) <= 3 ? 0 : 4)) +#define VI6_BRU_BLD_CBES BIT(31) +#define VI6_BRU_BLD_CCMDX_DST_A (0 << 28) +#define VI6_BRU_BLD_CCMDX_255_DST_A (1 << 28) +#define VI6_BRU_BLD_CCMDX_SRC_A (2 << 28) +#define VI6_BRU_BLD_CCMDX_255_SRC_A (3 << 28) +#define VI6_BRU_BLD_CCMDX_COEFX (4 << 28) +#define VI6_BRU_BLD_CCMDX_MASK (7 << 28) +#define VI6_BRU_BLD_CCMDY_DST_A (0 << 24) +#define VI6_BRU_BLD_CCMDY_255_DST_A (1 << 24) +#define VI6_BRU_BLD_CCMDY_SRC_A (2 << 24) +#define VI6_BRU_BLD_CCMDY_255_SRC_A (3 << 24) +#define VI6_BRU_BLD_CCMDY_COEFY (4 << 24) +#define VI6_BRU_BLD_CCMDY_MASK (7 << 24) +#define VI6_BRU_BLD_CCMDY_SHIFT 24 +#define VI6_BRU_BLD_ABES BIT(23) +#define VI6_BRU_BLD_ACMDX_DST_A (0 << 20) +#define VI6_BRU_BLD_ACMDX_255_DST_A (1 << 20) +#define VI6_BRU_BLD_ACMDX_SRC_A (2 << 20) +#define VI6_BRU_BLD_ACMDX_255_SRC_A (3 << 20) +#define VI6_BRU_BLD_ACMDX_COEFX (4 << 20) +#define VI6_BRU_BLD_ACMDX_MASK (7 << 20) +#define VI6_BRU_BLD_ACMDY_DST_A (0 << 16) +#define VI6_BRU_BLD_ACMDY_255_DST_A (1 << 16) +#define VI6_BRU_BLD_ACMDY_SRC_A (2 << 16) +#define VI6_BRU_BLD_ACMDY_255_SRC_A (3 << 16) +#define VI6_BRU_BLD_ACMDY_COEFY (4 << 16) +#define VI6_BRU_BLD_ACMDY_MASK (7 << 16) +#define VI6_BRU_BLD_COEFX_MASK (0xff << 8) +#define VI6_BRU_BLD_COEFX_SHIFT 8 +#define VI6_BRU_BLD_COEFY_MASK (0xff << 0) +#define VI6_BRU_BLD_COEFY_SHIFT 0 + +#define VI6_BRU_ROP 0x0030 /* Only available on BRU */ +#define VI6_BRU_ROP_DSTSEL_BRUIN(n) (((n) <= 3 ? (n) : (n)+1) << 20) +#define VI6_BRU_ROP_DSTSEL_VRPF (4 << 20) +#define VI6_BRU_ROP_DSTSEL_MASK (7 << 20) +#define VI6_BRU_ROP_CROP(rop) ((rop) << 4) +#define VI6_BRU_ROP_CROP_MASK (0xf << 4) +#define VI6_BRU_ROP_AROP(rop) ((rop) << 0) +#define VI6_BRU_ROP_AROP_MASK (0xf << 0) + +/* ----------------------------------------------------------------------------- + * HGO Control Registers + */ + +#define VI6_HGO_OFFSET 0x3000 +#define VI6_HGO_OFFSET_HOFFSET_SHIFT 16 +#define VI6_HGO_OFFSET_VOFFSET_SHIFT 0 +#define VI6_HGO_SIZE 0x3004 +#define VI6_HGO_SIZE_HSIZE_SHIFT 16 +#define VI6_HGO_SIZE_VSIZE_SHIFT 0 +#define VI6_HGO_MODE 0x3008 +#define VI6_HGO_MODE_STEP BIT(10) +#define VI6_HGO_MODE_MAXRGB BIT(7) +#define VI6_HGO_MODE_OFSB_R BIT(6) +#define VI6_HGO_MODE_OFSB_G BIT(5) +#define VI6_HGO_MODE_OFSB_B BIT(4) +#define VI6_HGO_MODE_HRATIO_SHIFT 2 +#define VI6_HGO_MODE_VRATIO_SHIFT 0 +#define VI6_HGO_LB_TH 0x300c +#define VI6_HGO_LBn_H(n) (0x3010 + (n) * 8) +#define VI6_HGO_LBn_V(n) (0x3014 + (n) * 8) +#define VI6_HGO_R_HISTO(n) (0x3030 + (n) * 4) +#define VI6_HGO_R_MAXMIN 0x3130 +#define VI6_HGO_R_SUM 0x3134 +#define VI6_HGO_R_LB_DET 0x3138 +#define VI6_HGO_G_HISTO(n) (0x3140 + (n) * 4) +#define VI6_HGO_G_MAXMIN 0x3240 +#define VI6_HGO_G_SUM 0x3244 +#define VI6_HGO_G_LB_DET 0x3248 +#define VI6_HGO_B_HISTO(n) (0x3250 + (n) * 4) +#define VI6_HGO_B_MAXMIN 0x3350 +#define VI6_HGO_B_SUM 0x3354 +#define VI6_HGO_B_LB_DET 0x3358 +#define VI6_HGO_EXT_HIST_ADDR 0x335c +#define VI6_HGO_EXT_HIST_DATA 0x3360 +#define VI6_HGO_REGRST 0x33fc +#define VI6_HGO_REGRST_RCLEA BIT(0) + +/* ----------------------------------------------------------------------------- + * HGT Control Registers + */ + +#define VI6_HGT_OFFSET 0x3400 +#define VI6_HGT_OFFSET_HOFFSET_SHIFT 16 +#define VI6_HGT_OFFSET_VOFFSET_SHIFT 0 +#define VI6_HGT_SIZE 0x3404 +#define VI6_HGT_SIZE_HSIZE_SHIFT 16 +#define VI6_HGT_SIZE_VSIZE_SHIFT 0 +#define VI6_HGT_MODE 0x3408 +#define VI6_HGT_MODE_HRATIO_SHIFT 2 +#define VI6_HGT_MODE_VRATIO_SHIFT 0 +#define VI6_HGT_HUE_AREA(n) (0x340c + (n) * 4) +#define VI6_HGT_HUE_AREA_LOWER_SHIFT 16 +#define VI6_HGT_HUE_AREA_UPPER_SHIFT 0 +#define VI6_HGT_LB_TH 0x3424 +#define VI6_HGT_LBn_H(n) (0x3428 + (n) * 8) +#define VI6_HGT_LBn_V(n) (0x342c + (n) * 8) +#define VI6_HGT_HISTO(m, n) (0x3450 + (m) * 128 + (n) * 4) +#define VI6_HGT_MAXMIN 0x3750 +#define VI6_HGT_SUM 0x3754 +#define VI6_HGT_LB_DET 0x3758 +#define VI6_HGT_REGRST 0x37fc +#define VI6_HGT_REGRST_RCLEA BIT(0) + +/* ----------------------------------------------------------------------------- + * LIF Control Registers + */ + +#define VI6_LIF_OFFSET (-0x100) + +#define VI6_LIF_CTRL 0x3b00 +#define VI6_LIF_CTRL_OBTH_MASK (0x7ff << 16) +#define VI6_LIF_CTRL_OBTH_SHIFT 16 +#define VI6_LIF_CTRL_CFMT BIT(4) +#define VI6_LIF_CTRL_REQSEL BIT(1) +#define VI6_LIF_CTRL_LIF_EN BIT(0) + +#define VI6_LIF_CSBTH 0x3b04 +#define VI6_LIF_CSBTH_HBTH_MASK (0x7ff << 16) +#define VI6_LIF_CSBTH_HBTH_SHIFT 16 +#define VI6_LIF_CSBTH_LBTH_MASK (0x7ff << 0) +#define VI6_LIF_CSBTH_LBTH_SHIFT 0 + +#define VI6_LIF_LBA 0x3b0c +#define VI6_LIF_LBA_LBA0 BIT(31) +#define VI6_LIF_LBA_LBA1_MASK (0xfff << 16) +#define VI6_LIF_LBA_LBA1_SHIFT 16 + +/* ----------------------------------------------------------------------------- + * Security Control Registers + */ + +#define VI6_SECURITY_CTRL0 0x3d00 +#define VI6_SECURITY_CTRL1 0x3d04 + +/* ----------------------------------------------------------------------------- + * IP Version Registers + */ + +#define VI6_IP_VERSION 0x3f00 +#define VI6_IP_VERSION_MASK (0xffff << 0) +#define VI6_IP_VERSION_MODEL_MASK (0xff << 8) +#define VI6_IP_VERSION_MODEL_VSPS_H2 (0x09 << 8) +#define VI6_IP_VERSION_MODEL_VSPR_H2 (0x0a << 8) +#define VI6_IP_VERSION_MODEL_VSPD_GEN2 (0x0b << 8) +#define VI6_IP_VERSION_MODEL_VSPS_M2 (0x0c << 8) +#define VI6_IP_VERSION_MODEL_VSPS_V2H (0x12 << 8) +#define VI6_IP_VERSION_MODEL_VSPD_V2H (0x13 << 8) +#define VI6_IP_VERSION_MODEL_VSPI_GEN3 (0x14 << 8) +#define VI6_IP_VERSION_MODEL_VSPBD_GEN3 (0x15 << 8) +#define VI6_IP_VERSION_MODEL_VSPBC_GEN3 (0x16 << 8) +#define VI6_IP_VERSION_MODEL_VSPD_GEN3 (0x17 << 8) +#define VI6_IP_VERSION_MODEL_VSPD_V3 (0x18 << 8) +#define VI6_IP_VERSION_MODEL_VSPDL_GEN3 (0x19 << 8) +#define VI6_IP_VERSION_MODEL_VSPBS_GEN3 (0x1a << 8) +#define VI6_IP_VERSION_MODEL_VSPD_V3U (0x1c << 8) +/* RZ/G2L SoCs have no version register, So use 0x80 as the model version */ +#define VI6_IP_VERSION_MODEL_VSPD_RZG2L (0x80 << 8) + +#define VI6_IP_VERSION_SOC_MASK (0xff << 0) +#define VI6_IP_VERSION_SOC_H2 (0x01 << 0) +#define VI6_IP_VERSION_SOC_V2H (0x01 << 0) +#define VI6_IP_VERSION_SOC_V3M (0x01 << 0) +#define VI6_IP_VERSION_SOC_M2 (0x02 << 0) +#define VI6_IP_VERSION_SOC_M3W (0x02 << 0) +#define VI6_IP_VERSION_SOC_V3H (0x02 << 0) +#define VI6_IP_VERSION_SOC_H3 (0x03 << 0) +#define VI6_IP_VERSION_SOC_D3 (0x04 << 0) +#define VI6_IP_VERSION_SOC_M3N (0x04 << 0) +#define VI6_IP_VERSION_SOC_E3 (0x04 << 0) +#define VI6_IP_VERSION_SOC_V3U (0x05 << 0) +/* RZ/G2L SoCs have no version register, So use 0x80 for SoC Identification */ +#define VI6_IP_VERSION_SOC_RZG2L (0x80 << 0) + +#define VI6_IP_VERSION_VSP_SW (0xfffe << 16) /* SW VSP version */ + +/* ----------------------------------------------------------------------------- + * RPF CLUT Registers + */ + +#define VI6_CLUT_TABLE 0x4000 + +/* ----------------------------------------------------------------------------- + * 1D LUT Registers + */ + +#define VI6_LUT_TABLE 0x7000 + +/* ----------------------------------------------------------------------------- + * 3D LUT Registers + */ + +#define VI6_CLU_ADDR 0x7400 +#define VI6_CLU_DATA 0x7404 + +/* ----------------------------------------------------------------------------- + * Formats + */ + +#define VI6_FMT_RGB_332 0x00 +#define VI6_FMT_XRGB_4444 0x01 +#define VI6_FMT_RGBX_4444 0x02 +#define VI6_FMT_XRGB_1555 0x04 +#define VI6_FMT_RGBX_5551 0x05 +#define VI6_FMT_RGB_565 0x06 +#define VI6_FMT_AXRGB_86666 0x07 +#define VI6_FMT_RGBXA_66668 0x08 +#define VI6_FMT_XRGBA_66668 0x09 +#define VI6_FMT_ARGBX_86666 0x0a +#define VI6_FMT_AXRXGXB_8262626 0x0b +#define VI6_FMT_XRXGXBA_2626268 0x0c +#define VI6_FMT_ARXGXBX_8626262 0x0d +#define VI6_FMT_RXGXBXA_6262628 0x0e +#define VI6_FMT_XRGB_6666 0x0f +#define VI6_FMT_RGBX_6666 0x10 +#define VI6_FMT_XRXGXB_262626 0x11 +#define VI6_FMT_RXGXBX_626262 0x12 +#define VI6_FMT_ARGB_8888 0x13 +#define VI6_FMT_RGBA_8888 0x14 +#define VI6_FMT_RGB_888 0x15 +#define VI6_FMT_XRGXGB_763763 0x16 +#define VI6_FMT_XXRGB_86666 0x17 +#define VI6_FMT_BGR_888 0x18 +#define VI6_FMT_ARGB_4444 0x19 +#define VI6_FMT_RGBA_4444 0x1a +#define VI6_FMT_ARGB_1555 0x1b +#define VI6_FMT_RGBA_5551 0x1c +#define VI6_FMT_ABGR_4444 0x1d +#define VI6_FMT_BGRA_4444 0x1e +#define VI6_FMT_ABGR_1555 0x1f +#define VI6_FMT_BGRA_5551 0x20 +#define VI6_FMT_XBXGXR_262626 0x21 +#define VI6_FMT_ABGR_8888 0x22 +#define VI6_FMT_XXRGB_88565 0x23 + +#define VI6_FMT_Y_UV_444 0x40 +#define VI6_FMT_Y_UV_422 0x41 +#define VI6_FMT_Y_UV_420 0x42 +#define VI6_FMT_YUV_444 0x46 +#define VI6_FMT_YUYV_422 0x47 +#define VI6_FMT_YYUV_422 0x48 +#define VI6_FMT_YUV_420 0x49 +#define VI6_FMT_Y_U_V_444 0x4a +#define VI6_FMT_Y_U_V_422 0x4b +#define VI6_FMT_Y_U_V_420 0x4c + +#endif /* __VSP1_REGS_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_rpf.c b/drivers/media/platform/renesas/vsp1/vsp1_rpf.c new file mode 100644 index 000000000..75083cb23 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_rpf.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_rpf.c -- R-Car VSP1 Read Pixel Formatter + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include + +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define RPF_MAX_WIDTH 8190 +#define RPF_MAX_HEIGHT 8190 + +/* Pre extended display list command data structure. */ +struct vsp1_extcmd_auto_fld_body { + u32 top_y0; + u32 bottom_y0; + u32 top_c0; + u32 bottom_c0; + u32 top_c1; + u32 bottom_c1; + u32 reserved0; + u32 reserved1; +} __packed; + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_rpf_write(struct vsp1_rwpf *rpf, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg + rpf->entity.index * VI6_RPF_OFFSET, + data); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static const struct v4l2_subdev_ops rpf_ops = { + .pad = &vsp1_rwpf_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void rpf_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev); + const struct vsp1_format_info *fmtinfo = rpf->fmtinfo; + const struct v4l2_pix_format_mplane *format = &rpf->format; + const struct v4l2_mbus_framefmt *source_format; + const struct v4l2_mbus_framefmt *sink_format; + unsigned int left = 0; + unsigned int top = 0; + u32 pstride; + u32 infmt; + + /* Stride */ + pstride = format->plane_fmt[0].bytesperline + << VI6_RPF_SRCM_PSTRIDE_Y_SHIFT; + if (format->num_planes > 1) + pstride |= format->plane_fmt[1].bytesperline + << VI6_RPF_SRCM_PSTRIDE_C_SHIFT; + + /* + * pstride has both STRIDE_Y and STRIDE_C, but multiplying the whole + * of pstride by 2 is conveniently OK here as we are multiplying both + * values. + */ + if (pipe->interlaced) + pstride *= 2; + + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_PSTRIDE, pstride); + + /* Format */ + sink_format = vsp1_entity_get_pad_format(&rpf->entity, + rpf->entity.config, + RWPF_PAD_SINK); + source_format = vsp1_entity_get_pad_format(&rpf->entity, + rpf->entity.config, + RWPF_PAD_SOURCE); + + infmt = VI6_RPF_INFMT_CIPM + | (fmtinfo->hwfmt << VI6_RPF_INFMT_RDFMT_SHIFT); + + if (fmtinfo->swap_yc) + infmt |= VI6_RPF_INFMT_SPYCS; + if (fmtinfo->swap_uv) + infmt |= VI6_RPF_INFMT_SPUVS; + + if (sink_format->code != source_format->code) + infmt |= VI6_RPF_INFMT_CSC; + + vsp1_rpf_write(rpf, dlb, VI6_RPF_INFMT, infmt); + vsp1_rpf_write(rpf, dlb, VI6_RPF_DSWAP, fmtinfo->swap); + + /* Output location. */ + if (pipe->brx) { + const struct v4l2_rect *compose; + + compose = vsp1_entity_get_pad_selection(pipe->brx, + pipe->brx->config, + rpf->brx_input, + V4L2_SEL_TGT_COMPOSE); + left = compose->left; + top = compose->top; + } + + if (pipe->interlaced) + top /= 2; + + vsp1_rpf_write(rpf, dlb, VI6_RPF_LOC, + (left << VI6_RPF_LOC_HCOORD_SHIFT) | + (top << VI6_RPF_LOC_VCOORD_SHIFT)); + + /* + * On Gen2 use the alpha channel (extended to 8 bits) when available or + * a fixed alpha value set through the V4L2_CID_ALPHA_COMPONENT control + * otherwise. + * + * The Gen3 RPF has extended alpha capability and can both multiply the + * alpha channel by a fixed global alpha value, and multiply the pixel + * components to convert the input to premultiplied alpha. + * + * As alpha premultiplication is available in the BRx for both Gen2 and + * Gen3 we handle it there and use the Gen3 alpha multiplier for global + * alpha multiplication only. This however prevents conversion to + * premultiplied alpha if no BRx is present in the pipeline. If that use + * case turns out to be useful we will revisit the implementation (for + * Gen3 only). + * + * We enable alpha multiplication on Gen3 using the fixed alpha value + * set through the V4L2_CID_ALPHA_COMPONENT control when the input + * contains an alpha channel. On Gen2 the global alpha is ignored in + * that case. + * + * In all cases, disable color keying. + */ + vsp1_rpf_write(rpf, dlb, VI6_RPF_ALPH_SEL, VI6_RPF_ALPH_SEL_AEXT_EXT | + (fmtinfo->alpha ? VI6_RPF_ALPH_SEL_ASEL_PACKED + : VI6_RPF_ALPH_SEL_ASEL_FIXED)); + + if (entity->vsp1->info->gen == 3) { + u32 mult; + + if (fmtinfo->alpha) { + /* + * When the input contains an alpha channel enable the + * alpha multiplier. If the input is premultiplied we + * need to multiply both the alpha channel and the pixel + * components by the global alpha value to keep them + * premultiplied. Otherwise multiply the alpha channel + * only. + */ + bool premultiplied = format->flags + & V4L2_PIX_FMT_FLAG_PREMUL_ALPHA; + + mult = VI6_RPF_MULT_ALPHA_A_MMD_RATIO + | (premultiplied ? + VI6_RPF_MULT_ALPHA_P_MMD_RATIO : + VI6_RPF_MULT_ALPHA_P_MMD_NONE); + } else { + /* + * When the input doesn't contain an alpha channel the + * global alpha value is applied in the unpacking unit, + * the alpha multiplier isn't needed and must be + * disabled. + */ + mult = VI6_RPF_MULT_ALPHA_A_MMD_NONE + | VI6_RPF_MULT_ALPHA_P_MMD_NONE; + } + + rpf->mult_alpha = mult; + } + + vsp1_rpf_write(rpf, dlb, VI6_RPF_MSK_CTRL, 0); + vsp1_rpf_write(rpf, dlb, VI6_RPF_CKEY_CTRL, 0); + +} + +static void vsp1_rpf_configure_autofld(struct vsp1_rwpf *rpf, + struct vsp1_dl_list *dl) +{ + const struct v4l2_pix_format_mplane *format = &rpf->format; + struct vsp1_dl_ext_cmd *cmd; + struct vsp1_extcmd_auto_fld_body *auto_fld; + u32 offset_y, offset_c; + + cmd = vsp1_dl_get_pre_cmd(dl); + if (WARN_ONCE(!cmd, "Failed to obtain an autofld cmd")) + return; + + /* Re-index our auto_fld to match the current RPF. */ + auto_fld = cmd->data; + auto_fld = &auto_fld[rpf->entity.index]; + + auto_fld->top_y0 = rpf->mem.addr[0]; + auto_fld->top_c0 = rpf->mem.addr[1]; + auto_fld->top_c1 = rpf->mem.addr[2]; + + offset_y = format->plane_fmt[0].bytesperline; + offset_c = format->plane_fmt[1].bytesperline; + + auto_fld->bottom_y0 = rpf->mem.addr[0] + offset_y; + auto_fld->bottom_c0 = rpf->mem.addr[1] + offset_c; + auto_fld->bottom_c1 = rpf->mem.addr[2] + offset_c; + + cmd->flags |= VI6_DL_EXT_AUTOFLD_INT | BIT(16 + rpf->entity.index); +} + +static void rpf_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev); + + vsp1_rpf_write(rpf, dlb, VI6_RPF_VRTCOL_SET, + rpf->alpha << VI6_RPF_VRTCOL_SET_LAYA_SHIFT); + vsp1_rpf_write(rpf, dlb, VI6_RPF_MULT_ALPHA, rpf->mult_alpha | + (rpf->alpha << VI6_RPF_MULT_ALPHA_RATIO_SHIFT)); + + vsp1_pipeline_propagate_alpha(pipe, dlb, rpf->alpha); +} + +static void rpf_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_rwpf *rpf = to_rwpf(&entity->subdev); + struct vsp1_rwpf_memory mem = rpf->mem; + struct vsp1_device *vsp1 = rpf->entity.vsp1; + const struct vsp1_format_info *fmtinfo = rpf->fmtinfo; + const struct v4l2_pix_format_mplane *format = &rpf->format; + struct v4l2_rect crop; + + /* + * Source size and crop offsets. + * + * The crop offsets correspond to the location of the crop + * rectangle top left corner in the plane buffer. Only two + * offsets are needed, as planes 2 and 3 always have identical + * strides. + */ + crop = *vsp1_rwpf_get_crop(rpf, rpf->entity.config); + + /* + * Partition Algorithm Control + * + * The partition algorithm can split this frame into multiple + * slices. We must scale our partition window based on the pipe + * configuration to match the destination partition window. + * To achieve this, we adjust our crop to provide a 'sub-crop' + * matching the expected partition window. Only 'left' and + * 'width' need to be adjusted. + */ + if (pipe->partitions > 1) { + crop.width = pipe->partition->rpf.width; + crop.left += pipe->partition->rpf.left; + } + + if (pipe->interlaced) { + crop.height = round_down(crop.height / 2, fmtinfo->vsub); + crop.top = round_down(crop.top / 2, fmtinfo->vsub); + } + + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRC_BSIZE, + (crop.width << VI6_RPF_SRC_BSIZE_BHSIZE_SHIFT) | + (crop.height << VI6_RPF_SRC_BSIZE_BVSIZE_SHIFT)); + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRC_ESIZE, + (crop.width << VI6_RPF_SRC_ESIZE_EHSIZE_SHIFT) | + (crop.height << VI6_RPF_SRC_ESIZE_EVSIZE_SHIFT)); + + mem.addr[0] += crop.top * format->plane_fmt[0].bytesperline + + crop.left * fmtinfo->bpp[0] / 8; + + if (format->num_planes > 1) { + unsigned int bpl = format->plane_fmt[1].bytesperline; + unsigned int offset; + + offset = crop.top / fmtinfo->vsub * bpl + + crop.left / fmtinfo->hsub * fmtinfo->bpp[1] / 8; + mem.addr[1] += offset; + mem.addr[2] += offset; + } + + /* + * On Gen3 hardware the SPUVS bit has no effect on 3-planar + * formats. Swap the U and V planes manually in that case. + */ + if (vsp1->info->gen == 3 && format->num_planes == 3 && + fmtinfo->swap_uv) + swap(mem.addr[1], mem.addr[2]); + + /* + * Interlaced pipelines will use the extended pre-cmd to process + * SRCM_ADDR_{Y,C0,C1}. + */ + if (pipe->interlaced) { + vsp1_rpf_configure_autofld(rpf, dl); + } else { + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_Y, mem.addr[0]); + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_C0, mem.addr[1]); + vsp1_rpf_write(rpf, dlb, VI6_RPF_SRCM_ADDR_C1, mem.addr[2]); + } +} + +static void rpf_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_partition *partition, + unsigned int partition_idx, + struct vsp1_partition_window *window) +{ + partition->rpf = *window; +} + +static const struct vsp1_entity_operations rpf_entity_ops = { + .configure_stream = rpf_configure_stream, + .configure_frame = rpf_configure_frame, + .configure_partition = rpf_configure_partition, + .partition = rpf_partition, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_rwpf *vsp1_rpf_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct vsp1_rwpf *rpf; + char name[6]; + int ret; + + rpf = devm_kzalloc(vsp1->dev, sizeof(*rpf), GFP_KERNEL); + if (rpf == NULL) + return ERR_PTR(-ENOMEM); + + rpf->max_width = RPF_MAX_WIDTH; + rpf->max_height = RPF_MAX_HEIGHT; + + rpf->entity.ops = &rpf_entity_ops; + rpf->entity.type = VSP1_ENTITY_RPF; + rpf->entity.index = index; + + sprintf(name, "rpf.%u", index); + ret = vsp1_entity_init(vsp1, &rpf->entity, name, 2, &rpf_ops, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the control handler. */ + ret = vsp1_rwpf_init_ctrls(rpf, 0); + if (ret < 0) { + dev_err(vsp1->dev, "rpf%u: failed to initialize controls\n", + index); + goto error; + } + + v4l2_ctrl_handler_setup(&rpf->ctrls); + + return rpf; + +error: + vsp1_entity_destroy(&rpf->entity); + return ERR_PTR(ret); +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c b/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c new file mode 100644 index 000000000..22a82d218 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_rwpf.c @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_rwpf.c -- R-Car VSP1 Read and Write Pixel Formatters + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include + +#include "vsp1.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define RWPF_MIN_WIDTH 1 +#define RWPF_MIN_HEIGHT 1 + +struct v4l2_rect *vsp1_rwpf_get_crop(struct vsp1_rwpf *rwpf, + struct v4l2_subdev_state *sd_state) +{ + return v4l2_subdev_get_try_crop(&rwpf->entity.subdev, sd_state, + RWPF_PAD_SINK); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int vsp1_rwpf_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, + }; + + if (code->index >= ARRAY_SIZE(codes)) + return -EINVAL; + + code->code = codes[code->index]; + + return 0; +} + +static int vsp1_rwpf_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + + return vsp1_subdev_enum_frame_size(subdev, sd_state, fse, + RWPF_MIN_WIDTH, + RWPF_MIN_HEIGHT, rwpf->max_width, + rwpf->max_height); +} + +static int vsp1_rwpf_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + mutex_lock(&rwpf->entity.lock); + + config = vsp1_entity_get_pad_config(&rwpf->entity, sd_state, + fmt->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + /* Default to YUV if the requested format is not supported. */ + if (fmt->format.code != MEDIA_BUS_FMT_ARGB8888_1X32 && + fmt->format.code != MEDIA_BUS_FMT_AHSV8888_1X32 && + fmt->format.code != MEDIA_BUS_FMT_AYUV8_1X32) + fmt->format.code = MEDIA_BUS_FMT_AYUV8_1X32; + + format = vsp1_entity_get_pad_format(&rwpf->entity, config, fmt->pad); + + if (fmt->pad == RWPF_PAD_SOURCE) { + /* + * The RWPF performs format conversion but can't scale, only the + * format code can be changed on the source pad. + */ + format->code = fmt->format.code; + fmt->format = *format; + goto done; + } + + format->code = fmt->format.code; + format->width = clamp_t(unsigned int, fmt->format.width, + RWPF_MIN_WIDTH, rwpf->max_width); + format->height = clamp_t(unsigned int, fmt->format.height, + RWPF_MIN_HEIGHT, rwpf->max_height); + format->field = V4L2_FIELD_NONE; + format->colorspace = V4L2_COLORSPACE_SRGB; + + fmt->format = *format; + + if (rwpf->entity.type == VSP1_ENTITY_RPF) { + struct v4l2_rect *crop; + + /* Update the sink crop rectangle. */ + crop = vsp1_rwpf_get_crop(rwpf, config); + crop->left = 0; + crop->top = 0; + crop->width = fmt->format.width; + crop->height = fmt->format.height; + } + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&rwpf->entity, config, + RWPF_PAD_SOURCE); + *format = fmt->format; + + if (rwpf->flip.rotate) { + format->width = fmt->format.height; + format->height = fmt->format.width; + } + +done: + mutex_unlock(&rwpf->entity.lock); + return ret; +} + +static int vsp1_rwpf_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + /* + * Cropping is only supported on the RPF and is implemented on the sink + * pad. + */ + if (rwpf->entity.type == VSP1_ENTITY_WPF || sel->pad != RWPF_PAD_SINK) + return -EINVAL; + + mutex_lock(&rwpf->entity.lock); + + config = vsp1_entity_get_pad_config(&rwpf->entity, sd_state, + sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + sel->r = *vsp1_rwpf_get_crop(rwpf, config); + break; + + case V4L2_SEL_TGT_CROP_BOUNDS: + format = vsp1_entity_get_pad_format(&rwpf->entity, config, + RWPF_PAD_SINK); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = format->width; + sel->r.height = format->height; + break; + + default: + ret = -EINVAL; + break; + } + +done: + mutex_unlock(&rwpf->entity.lock); + return ret; +} + +static int vsp1_rwpf_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_rwpf *rwpf = to_rwpf(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *crop; + int ret = 0; + + /* + * Cropping is only supported on the RPF and is implemented on the sink + * pad. + */ + if (rwpf->entity.type == VSP1_ENTITY_WPF || sel->pad != RWPF_PAD_SINK) + return -EINVAL; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + mutex_lock(&rwpf->entity.lock); + + config = vsp1_entity_get_pad_config(&rwpf->entity, sd_state, + sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + /* Make sure the crop rectangle is entirely contained in the image. */ + format = vsp1_entity_get_pad_format(&rwpf->entity, config, + RWPF_PAD_SINK); + + /* + * Restrict the crop rectangle coordinates to multiples of 2 to avoid + * shifting the color plane. + */ + if (format->code == MEDIA_BUS_FMT_AYUV8_1X32) { + sel->r.left = ALIGN(sel->r.left, 2); + sel->r.top = ALIGN(sel->r.top, 2); + sel->r.width = round_down(sel->r.width, 2); + sel->r.height = round_down(sel->r.height, 2); + } + + sel->r.left = min_t(unsigned int, sel->r.left, format->width - 2); + sel->r.top = min_t(unsigned int, sel->r.top, format->height - 2); + sel->r.width = min_t(unsigned int, sel->r.width, + format->width - sel->r.left); + sel->r.height = min_t(unsigned int, sel->r.height, + format->height - sel->r.top); + + crop = vsp1_rwpf_get_crop(rwpf, config); + *crop = sel->r; + + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&rwpf->entity, config, + RWPF_PAD_SOURCE); + format->width = crop->width; + format->height = crop->height; + +done: + mutex_unlock(&rwpf->entity.lock); + return ret; +} + +const struct v4l2_subdev_pad_ops vsp1_rwpf_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = vsp1_rwpf_enum_mbus_code, + .enum_frame_size = vsp1_rwpf_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = vsp1_rwpf_set_format, + .get_selection = vsp1_rwpf_get_selection, + .set_selection = vsp1_rwpf_set_selection, +}; + +/* ----------------------------------------------------------------------------- + * Controls + */ + +static int vsp1_rwpf_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_rwpf *rwpf = + container_of(ctrl->handler, struct vsp1_rwpf, ctrls); + + switch (ctrl->id) { + case V4L2_CID_ALPHA_COMPONENT: + rwpf->alpha = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops vsp1_rwpf_ctrl_ops = { + .s_ctrl = vsp1_rwpf_s_ctrl, +}; + +int vsp1_rwpf_init_ctrls(struct vsp1_rwpf *rwpf, unsigned int ncontrols) +{ + v4l2_ctrl_handler_init(&rwpf->ctrls, ncontrols + 1); + v4l2_ctrl_new_std(&rwpf->ctrls, &vsp1_rwpf_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 255); + + rwpf->entity.subdev.ctrl_handler = &rwpf->ctrls; + + return rwpf->ctrls.error; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_rwpf.h b/drivers/media/platform/renesas/vsp1/vsp1_rwpf.h new file mode 100644 index 000000000..eac5c04c2 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_rwpf.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_rwpf.h -- R-Car VSP1 Read and Write Pixel Formatters + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_RWPF_H__ +#define __VSP1_RWPF_H__ + +#include + +#include +#include +#include + +#include "vsp1.h" +#include "vsp1_entity.h" + +#define RWPF_PAD_SINK 0 +#define RWPF_PAD_SOURCE 1 + +struct v4l2_ctrl; +struct vsp1_dl_manager; +struct vsp1_rwpf; +struct vsp1_video; + +struct vsp1_rwpf_memory { + dma_addr_t addr[3]; +}; + +struct vsp1_rwpf { + struct vsp1_entity entity; + struct v4l2_ctrl_handler ctrls; + + struct vsp1_video *video; + + unsigned int max_width; + unsigned int max_height; + + struct v4l2_pix_format_mplane format; + const struct vsp1_format_info *fmtinfo; + unsigned int brx_input; + + unsigned int alpha; + + u32 mult_alpha; + u32 outfmt; + + struct { + spinlock_t lock; + struct { + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *rotate; + } ctrls; + unsigned int pending; + unsigned int active; + bool rotate; + } flip; + + struct vsp1_rwpf_memory mem; + bool writeback; + + struct vsp1_dl_manager *dlm; +}; + +static inline struct vsp1_rwpf *to_rwpf(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_rwpf, entity.subdev); +} + +static inline struct vsp1_rwpf *entity_to_rwpf(struct vsp1_entity *entity) +{ + return container_of(entity, struct vsp1_rwpf, entity); +} + +struct vsp1_rwpf *vsp1_rpf_create(struct vsp1_device *vsp1, unsigned int index); +struct vsp1_rwpf *vsp1_wpf_create(struct vsp1_device *vsp1, unsigned int index); + +int vsp1_rwpf_init_ctrls(struct vsp1_rwpf *rwpf, unsigned int ncontrols); + +extern const struct v4l2_subdev_pad_ops vsp1_rwpf_pad_ops; + +struct v4l2_rect *vsp1_rwpf_get_crop(struct vsp1_rwpf *rwpf, + struct v4l2_subdev_state *sd_state); + +#endif /* __VSP1_RWPF_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_sru.c b/drivers/media/platform/renesas/vsp1/vsp1_sru.c new file mode 100644 index 000000000..b614a2aea --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_sru.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_sru.c -- R-Car VSP1 Super Resolution Unit + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_pipe.h" +#include "vsp1_sru.h" + +#define SRU_MIN_SIZE 4U +#define SRU_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_sru_write(struct vsp1_sru *sru, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg, data); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +#define V4L2_CID_VSP1_SRU_INTENSITY (V4L2_CID_USER_BASE | 0x1001) + +struct vsp1_sru_param { + u32 ctrl0; + u32 ctrl2; +}; + +#define VI6_SRU_CTRL0_PARAMS(p0, p1) \ + (((p0) << VI6_SRU_CTRL0_PARAM0_SHIFT) | \ + ((p1) << VI6_SRU_CTRL0_PARAM1_SHIFT)) + +#define VI6_SRU_CTRL2_PARAMS(p6, p7, p8) \ + (((p6) << VI6_SRU_CTRL2_PARAM6_SHIFT) | \ + ((p7) << VI6_SRU_CTRL2_PARAM7_SHIFT) | \ + ((p8) << VI6_SRU_CTRL2_PARAM8_SHIFT)) + +static const struct vsp1_sru_param vsp1_sru_params[] = { + { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(256, 4) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(24, 40, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(256, 4) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(8, 16, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(384, 5) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(36, 60, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(384, 5) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(12, 27, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(511, 6) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(48, 80, 255), + }, { + .ctrl0 = VI6_SRU_CTRL0_PARAMS(511, 6) | VI6_SRU_CTRL0_EN, + .ctrl2 = VI6_SRU_CTRL2_PARAMS(16, 36, 255), + }, +}; + +static int sru_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_sru *sru = + container_of(ctrl->handler, struct vsp1_sru, ctrls); + + switch (ctrl->id) { + case V4L2_CID_VSP1_SRU_INTENSITY: + sru->intensity = ctrl->val; + break; + } + + return 0; +} + +static const struct v4l2_ctrl_ops sru_ctrl_ops = { + .s_ctrl = sru_s_ctrl, +}; + +static const struct v4l2_ctrl_config sru_intensity_control = { + .ops = &sru_ctrl_ops, + .id = V4L2_CID_VSP1_SRU_INTENSITY, + .name = "Intensity", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 1, + .max = 6, + .def = 1, + .step = 1, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static int sru_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, + }; + + return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, codes, + ARRAY_SIZE(codes)); +} + +static int sru_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vsp1_sru *sru = to_sru(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + config = vsp1_entity_get_pad_config(&sru->entity, sd_state, + fse->which); + if (!config) + return -EINVAL; + + format = vsp1_entity_get_pad_format(&sru->entity, config, SRU_PAD_SINK); + + mutex_lock(&sru->entity.lock); + + if (fse->index || fse->code != format->code) { + ret = -EINVAL; + goto done; + } + + if (fse->pad == SRU_PAD_SINK) { + fse->min_width = SRU_MIN_SIZE; + fse->max_width = SRU_MAX_SIZE; + fse->min_height = SRU_MIN_SIZE; + fse->max_height = SRU_MAX_SIZE; + } else { + fse->min_width = format->width; + fse->min_height = format->height; + if (format->width <= SRU_MAX_SIZE / 2 && + format->height <= SRU_MAX_SIZE / 2) { + fse->max_width = format->width * 2; + fse->max_height = format->height * 2; + } else { + fse->max_width = format->width; + fse->max_height = format->height; + } + } + +done: + mutex_unlock(&sru->entity.lock); + return ret; +} + +static void sru_try_format(struct vsp1_sru *sru, + struct v4l2_subdev_state *sd_state, + unsigned int pad, struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_mbus_framefmt *format; + unsigned int input_area; + unsigned int output_area; + + switch (pad) { + case SRU_PAD_SINK: + /* Default to YUV if the requested format is not supported. */ + if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 && + fmt->code != MEDIA_BUS_FMT_AYUV8_1X32) + fmt->code = MEDIA_BUS_FMT_AYUV8_1X32; + + fmt->width = clamp(fmt->width, SRU_MIN_SIZE, SRU_MAX_SIZE); + fmt->height = clamp(fmt->height, SRU_MIN_SIZE, SRU_MAX_SIZE); + break; + + case SRU_PAD_SOURCE: + /* The SRU can't perform format conversion. */ + format = vsp1_entity_get_pad_format(&sru->entity, sd_state, + SRU_PAD_SINK); + fmt->code = format->code; + + /* + * We can upscale by 2 in both direction, but not independently. + * Compare the input and output rectangles areas (avoiding + * integer overflows on the output): if the requested output + * area is larger than 1.5^2 the input area upscale by two, + * otherwise don't scale. + */ + input_area = format->width * format->height; + output_area = min(fmt->width, SRU_MAX_SIZE) + * min(fmt->height, SRU_MAX_SIZE); + + if (fmt->width <= SRU_MAX_SIZE / 2 && + fmt->height <= SRU_MAX_SIZE / 2 && + output_area > input_area * 9 / 4) { + fmt->width = format->width * 2; + fmt->height = format->height * 2; + } else { + fmt->width = format->width; + fmt->height = format->height; + } + break; + } + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +static int sru_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_sru *sru = to_sru(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + mutex_lock(&sru->entity.lock); + + config = vsp1_entity_get_pad_config(&sru->entity, sd_state, + fmt->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + sru_try_format(sru, config, fmt->pad, &fmt->format); + + format = vsp1_entity_get_pad_format(&sru->entity, config, fmt->pad); + *format = fmt->format; + + if (fmt->pad == SRU_PAD_SINK) { + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&sru->entity, config, + SRU_PAD_SOURCE); + *format = fmt->format; + + sru_try_format(sru, config, SRU_PAD_SOURCE, format); + } + +done: + mutex_unlock(&sru->entity.lock); + return ret; +} + +static const struct v4l2_subdev_pad_ops sru_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = sru_enum_mbus_code, + .enum_frame_size = sru_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = sru_set_format, +}; + +static const struct v4l2_subdev_ops sru_ops = { + .pad = &sru_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void sru_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + const struct vsp1_sru_param *param; + struct vsp1_sru *sru = to_sru(&entity->subdev); + struct v4l2_mbus_framefmt *input; + struct v4l2_mbus_framefmt *output; + u32 ctrl0; + + input = vsp1_entity_get_pad_format(&sru->entity, sru->entity.config, + SRU_PAD_SINK); + output = vsp1_entity_get_pad_format(&sru->entity, sru->entity.config, + SRU_PAD_SOURCE); + + if (input->code == MEDIA_BUS_FMT_ARGB8888_1X32) + ctrl0 = VI6_SRU_CTRL0_PARAM2 | VI6_SRU_CTRL0_PARAM3 + | VI6_SRU_CTRL0_PARAM4; + else + ctrl0 = VI6_SRU_CTRL0_PARAM3; + + if (input->width != output->width) + ctrl0 |= VI6_SRU_CTRL0_MODE_UPSCALE; + + param = &vsp1_sru_params[sru->intensity - 1]; + + ctrl0 |= param->ctrl0; + + vsp1_sru_write(sru, dlb, VI6_SRU_CTRL0, ctrl0); + vsp1_sru_write(sru, dlb, VI6_SRU_CTRL1, VI6_SRU_CTRL1_PARAM5); + vsp1_sru_write(sru, dlb, VI6_SRU_CTRL2, param->ctrl2); +} + +static unsigned int sru_max_width(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe) +{ + struct vsp1_sru *sru = to_sru(&entity->subdev); + struct v4l2_mbus_framefmt *input; + struct v4l2_mbus_framefmt *output; + + input = vsp1_entity_get_pad_format(&sru->entity, sru->entity.config, + SRU_PAD_SINK); + output = vsp1_entity_get_pad_format(&sru->entity, sru->entity.config, + SRU_PAD_SOURCE); + + /* + * The maximum input width of the SRU is 288 input pixels, but 32 + * pixels are reserved to support overlapping partition windows when + * scaling. + */ + if (input->width != output->width) + return 512; + else + return 256; +} + +static void sru_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_partition *partition, + unsigned int partition_idx, + struct vsp1_partition_window *window) +{ + struct vsp1_sru *sru = to_sru(&entity->subdev); + struct v4l2_mbus_framefmt *input; + struct v4l2_mbus_framefmt *output; + + input = vsp1_entity_get_pad_format(&sru->entity, sru->entity.config, + SRU_PAD_SINK); + output = vsp1_entity_get_pad_format(&sru->entity, sru->entity.config, + SRU_PAD_SOURCE); + + /* Adapt if SRUx2 is enabled. */ + if (input->width != output->width) { + window->width /= 2; + window->left /= 2; + } + + partition->sru = *window; +} + +static const struct vsp1_entity_operations sru_entity_ops = { + .configure_stream = sru_configure_stream, + .max_width = sru_max_width, + .partition = sru_partition, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_sru *vsp1_sru_create(struct vsp1_device *vsp1) +{ + struct vsp1_sru *sru; + int ret; + + sru = devm_kzalloc(vsp1->dev, sizeof(*sru), GFP_KERNEL); + if (sru == NULL) + return ERR_PTR(-ENOMEM); + + sru->entity.ops = &sru_entity_ops; + sru->entity.type = VSP1_ENTITY_SRU; + + ret = vsp1_entity_init(vsp1, &sru->entity, "sru", 2, &sru_ops, + MEDIA_ENT_F_PROC_VIDEO_SCALER); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the control handler. */ + v4l2_ctrl_handler_init(&sru->ctrls, 1); + v4l2_ctrl_new_custom(&sru->ctrls, &sru_intensity_control, NULL); + + sru->intensity = 1; + + sru->entity.subdev.ctrl_handler = &sru->ctrls; + + if (sru->ctrls.error) { + dev_err(vsp1->dev, "sru: failed to initialize controls\n"); + ret = sru->ctrls.error; + vsp1_entity_destroy(&sru->entity); + return ERR_PTR(ret); + } + + return sru; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_sru.h b/drivers/media/platform/renesas/vsp1/vsp1_sru.h new file mode 100644 index 000000000..ddb00eadd --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_sru.h @@ -0,0 +1,38 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_sru.h -- R-Car VSP1 Super Resolution Unit + * + * Copyright (C) 2013 Renesas Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_SRU_H__ +#define __VSP1_SRU_H__ + +#include +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define SRU_PAD_SINK 0 +#define SRU_PAD_SOURCE 1 + +struct vsp1_sru { + struct vsp1_entity entity; + + struct v4l2_ctrl_handler ctrls; + + unsigned int intensity; +}; + +static inline struct vsp1_sru *to_sru(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_sru, entity.subdev); +} + +struct vsp1_sru *vsp1_sru_create(struct vsp1_device *vsp1); + +#endif /* __VSP1_SRU_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_uds.c b/drivers/media/platform/renesas/vsp1/vsp1_uds.c new file mode 100644 index 000000000..1c290cda0 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_uds.c @@ -0,0 +1,429 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_uds.c -- R-Car VSP1 Up and Down Scaler + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include + +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_pipe.h" +#include "vsp1_uds.h" + +#define UDS_MIN_SIZE 4U +#define UDS_MAX_SIZE 8190U + +#define UDS_MIN_FACTOR 0x0100 +#define UDS_MAX_FACTOR 0xffff + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_uds_write(struct vsp1_uds *uds, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg + uds->entity.index * VI6_UDS_OFFSET, data); +} + +/* ----------------------------------------------------------------------------- + * Scaling Computation + */ + +void vsp1_uds_set_alpha(struct vsp1_entity *entity, struct vsp1_dl_body *dlb, + unsigned int alpha) +{ + struct vsp1_uds *uds = to_uds(&entity->subdev); + + vsp1_uds_write(uds, dlb, VI6_UDS_ALPVAL, + alpha << VI6_UDS_ALPVAL_VAL0_SHIFT); +} + +/* + * uds_output_size - Return the output size for an input size and scaling ratio + * @input: input size in pixels + * @ratio: scaling ratio in U4.12 fixed-point format + */ +static unsigned int uds_output_size(unsigned int input, unsigned int ratio) +{ + if (ratio > 4096) { + /* Down-scaling */ + unsigned int mp; + + mp = ratio / 4096; + mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4); + + return (input - 1) / mp * mp * 4096 / ratio + 1; + } else { + /* Up-scaling */ + return (input - 1) * 4096 / ratio + 1; + } +} + +/* + * uds_output_limits - Return the min and max output sizes for an input size + * @input: input size in pixels + * @minimum: minimum output size (returned) + * @maximum: maximum output size (returned) + */ +static void uds_output_limits(unsigned int input, + unsigned int *minimum, unsigned int *maximum) +{ + *minimum = max(uds_output_size(input, UDS_MAX_FACTOR), UDS_MIN_SIZE); + *maximum = min(uds_output_size(input, UDS_MIN_FACTOR), UDS_MAX_SIZE); +} + +/* + * uds_passband_width - Return the passband filter width for a scaling ratio + * @ratio: scaling ratio in U4.12 fixed-point format + */ +static unsigned int uds_passband_width(unsigned int ratio) +{ + if (ratio >= 4096) { + /* Down-scaling */ + unsigned int mp; + + mp = ratio / 4096; + mp = mp < 4 ? 1 : (mp < 8 ? 2 : 4); + + return 64 * 4096 * mp / ratio; + } else { + /* Up-scaling */ + return 64; + } +} + +static unsigned int uds_compute_ratio(unsigned int input, unsigned int output) +{ + /* TODO: This is an approximation that will need to be refined. */ + return (input - 1) * 4096 / (output - 1); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static int uds_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + static const unsigned int codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, + }; + + return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, codes, + ARRAY_SIZE(codes)); +} + +static int uds_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct vsp1_uds *uds = to_uds(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + config = vsp1_entity_get_pad_config(&uds->entity, sd_state, + fse->which); + if (!config) + return -EINVAL; + + format = vsp1_entity_get_pad_format(&uds->entity, config, + UDS_PAD_SINK); + + mutex_lock(&uds->entity.lock); + + if (fse->index || fse->code != format->code) { + ret = -EINVAL; + goto done; + } + + if (fse->pad == UDS_PAD_SINK) { + fse->min_width = UDS_MIN_SIZE; + fse->max_width = UDS_MAX_SIZE; + fse->min_height = UDS_MIN_SIZE; + fse->max_height = UDS_MAX_SIZE; + } else { + uds_output_limits(format->width, &fse->min_width, + &fse->max_width); + uds_output_limits(format->height, &fse->min_height, + &fse->max_height); + } + +done: + mutex_unlock(&uds->entity.lock); + return ret; +} + +static void uds_try_format(struct vsp1_uds *uds, + struct v4l2_subdev_state *sd_state, + unsigned int pad, struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_mbus_framefmt *format; + unsigned int minimum; + unsigned int maximum; + + switch (pad) { + case UDS_PAD_SINK: + /* Default to YUV if the requested format is not supported. */ + if (fmt->code != MEDIA_BUS_FMT_ARGB8888_1X32 && + fmt->code != MEDIA_BUS_FMT_AYUV8_1X32) + fmt->code = MEDIA_BUS_FMT_AYUV8_1X32; + + fmt->width = clamp(fmt->width, UDS_MIN_SIZE, UDS_MAX_SIZE); + fmt->height = clamp(fmt->height, UDS_MIN_SIZE, UDS_MAX_SIZE); + break; + + case UDS_PAD_SOURCE: + /* The UDS scales but can't perform format conversion. */ + format = vsp1_entity_get_pad_format(&uds->entity, sd_state, + UDS_PAD_SINK); + fmt->code = format->code; + + uds_output_limits(format->width, &minimum, &maximum); + fmt->width = clamp(fmt->width, minimum, maximum); + uds_output_limits(format->height, &minimum, &maximum); + fmt->height = clamp(fmt->height, minimum, maximum); + break; + } + + fmt->field = V4L2_FIELD_NONE; + fmt->colorspace = V4L2_COLORSPACE_SRGB; +} + +static int uds_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct vsp1_uds *uds = to_uds(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + mutex_lock(&uds->entity.lock); + + config = vsp1_entity_get_pad_config(&uds->entity, sd_state, + fmt->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + uds_try_format(uds, config, fmt->pad, &fmt->format); + + format = vsp1_entity_get_pad_format(&uds->entity, config, fmt->pad); + *format = fmt->format; + + if (fmt->pad == UDS_PAD_SINK) { + /* Propagate the format to the source pad. */ + format = vsp1_entity_get_pad_format(&uds->entity, config, + UDS_PAD_SOURCE); + *format = fmt->format; + + uds_try_format(uds, config, UDS_PAD_SOURCE, format); + } + +done: + mutex_unlock(&uds->entity.lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static const struct v4l2_subdev_pad_ops uds_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = uds_enum_mbus_code, + .enum_frame_size = uds_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = uds_set_format, +}; + +static const struct v4l2_subdev_ops uds_ops = { + .pad = &uds_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void uds_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_uds *uds = to_uds(&entity->subdev); + const struct v4l2_mbus_framefmt *output; + const struct v4l2_mbus_framefmt *input; + unsigned int hscale; + unsigned int vscale; + bool multitap; + + input = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, + UDS_PAD_SINK); + output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, + UDS_PAD_SOURCE); + + hscale = uds_compute_ratio(input->width, output->width); + vscale = uds_compute_ratio(input->height, output->height); + + dev_dbg(uds->entity.vsp1->dev, "hscale %u vscale %u\n", hscale, vscale); + + /* + * Multi-tap scaling can't be enabled along with alpha scaling when + * scaling down with a factor lower than or equal to 1/2 in either + * direction. + */ + if (uds->scale_alpha && (hscale >= 8192 || vscale >= 8192)) + multitap = false; + else + multitap = true; + + vsp1_uds_write(uds, dlb, VI6_UDS_CTRL, + (uds->scale_alpha ? VI6_UDS_CTRL_AON : 0) | + (multitap ? VI6_UDS_CTRL_BC : 0)); + + vsp1_uds_write(uds, dlb, VI6_UDS_PASS_BWIDTH, + (uds_passband_width(hscale) + << VI6_UDS_PASS_BWIDTH_H_SHIFT) | + (uds_passband_width(vscale) + << VI6_UDS_PASS_BWIDTH_V_SHIFT)); + + /* Set the scaling ratios. */ + vsp1_uds_write(uds, dlb, VI6_UDS_SCALE, + (hscale << VI6_UDS_SCALE_HFRAC_SHIFT) | + (vscale << VI6_UDS_SCALE_VFRAC_SHIFT)); +} + +static void uds_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_uds *uds = to_uds(&entity->subdev); + struct vsp1_partition *partition = pipe->partition; + const struct v4l2_mbus_framefmt *output; + + output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, + UDS_PAD_SOURCE); + + /* Input size clipping. */ + vsp1_uds_write(uds, dlb, VI6_UDS_HSZCLIP, VI6_UDS_HSZCLIP_HCEN | + (0 << VI6_UDS_HSZCLIP_HCL_OFST_SHIFT) | + (partition->uds_sink.width + << VI6_UDS_HSZCLIP_HCL_SIZE_SHIFT)); + + /* Output size clipping. */ + vsp1_uds_write(uds, dlb, VI6_UDS_CLIP_SIZE, + (partition->uds_source.width + << VI6_UDS_CLIP_SIZE_HSIZE_SHIFT) | + (output->height + << VI6_UDS_CLIP_SIZE_VSIZE_SHIFT)); +} + +static unsigned int uds_max_width(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe) +{ + struct vsp1_uds *uds = to_uds(&entity->subdev); + const struct v4l2_mbus_framefmt *output; + const struct v4l2_mbus_framefmt *input; + unsigned int hscale; + + input = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, + UDS_PAD_SINK); + output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, + UDS_PAD_SOURCE); + hscale = output->width / input->width; + + /* + * The maximum width of the UDS is 304 pixels. These are input pixels + * in the event of up-scaling, and output pixels in the event of + * downscaling. + * + * To support overlapping partition windows we clamp at units of 256 and + * the remaining pixels are reserved. + */ + if (hscale <= 2) + return 256; + else if (hscale <= 4) + return 512; + else if (hscale <= 8) + return 1024; + else + return 2048; +} + +/* ----------------------------------------------------------------------------- + * Partition Algorithm Support + */ + +static void uds_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_partition *partition, + unsigned int partition_idx, + struct vsp1_partition_window *window) +{ + struct vsp1_uds *uds = to_uds(&entity->subdev); + const struct v4l2_mbus_framefmt *output; + const struct v4l2_mbus_framefmt *input; + + /* Initialise the partition state. */ + partition->uds_sink = *window; + partition->uds_source = *window; + + input = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, + UDS_PAD_SINK); + output = vsp1_entity_get_pad_format(&uds->entity, uds->entity.config, + UDS_PAD_SOURCE); + + partition->uds_sink.width = window->width * input->width + / output->width; + partition->uds_sink.left = window->left * input->width + / output->width; + + *window = partition->uds_sink; +} + +static const struct vsp1_entity_operations uds_entity_ops = { + .configure_stream = uds_configure_stream, + .configure_partition = uds_configure_partition, + .max_width = uds_max_width, + .partition = uds_partition, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct vsp1_uds *uds; + char name[6]; + int ret; + + uds = devm_kzalloc(vsp1->dev, sizeof(*uds), GFP_KERNEL); + if (uds == NULL) + return ERR_PTR(-ENOMEM); + + uds->entity.ops = &uds_entity_ops; + uds->entity.type = VSP1_ENTITY_UDS; + uds->entity.index = index; + + sprintf(name, "uds.%u", index); + ret = vsp1_entity_init(vsp1, &uds->entity, name, 2, &uds_ops, + MEDIA_ENT_F_PROC_VIDEO_SCALER); + if (ret < 0) + return ERR_PTR(ret); + + return uds; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_uds.h b/drivers/media/platform/renesas/vsp1/vsp1_uds.h new file mode 100644 index 000000000..c34f95a66 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_uds.h @@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_uds.h -- R-Car VSP1 Up and Down Scaler + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_UDS_H__ +#define __VSP1_UDS_H__ + +#include +#include + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define UDS_PAD_SINK 0 +#define UDS_PAD_SOURCE 1 + +struct vsp1_uds { + struct vsp1_entity entity; + bool scale_alpha; +}; + +static inline struct vsp1_uds *to_uds(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_uds, entity.subdev); +} + +struct vsp1_uds *vsp1_uds_create(struct vsp1_device *vsp1, unsigned int index); + +void vsp1_uds_set_alpha(struct vsp1_entity *uds, struct vsp1_dl_body *dlb, + unsigned int alpha); + +#endif /* __VSP1_UDS_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_uif.c b/drivers/media/platform/renesas/vsp1/vsp1_uif.c new file mode 100644 index 000000000..83d7f17df --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_uif.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_uif.c -- R-Car VSP1 User Logic Interface + * + * Copyright (C) 2017-2018 Laurent Pinchart + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include + +#include +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_entity.h" +#include "vsp1_uif.h" + +#define UIF_MIN_SIZE 4U +#define UIF_MAX_SIZE 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline u32 vsp1_uif_read(struct vsp1_uif *uif, u32 reg) +{ + return vsp1_read(uif->entity.vsp1, + uif->entity.index * VI6_UIF_OFFSET + reg); +} + +static inline void vsp1_uif_write(struct vsp1_uif *uif, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg + uif->entity.index * VI6_UIF_OFFSET, data); +} + +u32 vsp1_uif_get_crc(struct vsp1_uif *uif) +{ + return vsp1_uif_read(uif, VI6_UIF_DISCOM_DOCMCCRCR); +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Pad Operations + */ + +static const unsigned int uif_codes[] = { + MEDIA_BUS_FMT_ARGB8888_1X32, + MEDIA_BUS_FMT_AHSV8888_1X32, + MEDIA_BUS_FMT_AYUV8_1X32, +}; + +static int uif_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + return vsp1_subdev_enum_mbus_code(subdev, sd_state, code, uif_codes, + ARRAY_SIZE(uif_codes)); +} + +static int uif_enum_frame_size(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + return vsp1_subdev_enum_frame_size(subdev, sd_state, fse, + UIF_MIN_SIZE, + UIF_MIN_SIZE, UIF_MAX_SIZE, + UIF_MAX_SIZE); +} + +static int uif_set_format(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + return vsp1_subdev_set_pad_format(subdev, sd_state, fmt, uif_codes, + ARRAY_SIZE(uif_codes), + UIF_MIN_SIZE, UIF_MIN_SIZE, + UIF_MAX_SIZE, UIF_MAX_SIZE); +} + +static int uif_get_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_uif *uif = to_uif(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + int ret = 0; + + if (sel->pad != UIF_PAD_SINK) + return -EINVAL; + + mutex_lock(&uif->entity.lock); + + config = vsp1_entity_get_pad_config(&uif->entity, sd_state, + sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + format = vsp1_entity_get_pad_format(&uif->entity, config, + UIF_PAD_SINK); + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = format->width; + sel->r.height = format->height; + break; + + case V4L2_SEL_TGT_CROP: + sel->r = *vsp1_entity_get_pad_selection(&uif->entity, config, + sel->pad, sel->target); + break; + + default: + ret = -EINVAL; + break; + } + +done: + mutex_unlock(&uif->entity.lock); + return ret; +} + +static int uif_set_selection(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct vsp1_uif *uif = to_uif(subdev); + struct v4l2_subdev_state *config; + struct v4l2_mbus_framefmt *format; + struct v4l2_rect *selection; + int ret = 0; + + if (sel->pad != UIF_PAD_SINK || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + mutex_lock(&uif->entity.lock); + + config = vsp1_entity_get_pad_config(&uif->entity, sd_state, + sel->which); + if (!config) { + ret = -EINVAL; + goto done; + } + + /* The crop rectangle must be inside the input frame. */ + format = vsp1_entity_get_pad_format(&uif->entity, config, UIF_PAD_SINK); + + sel->r.left = clamp_t(unsigned int, sel->r.left, 0, format->width - 1); + sel->r.top = clamp_t(unsigned int, sel->r.top, 0, format->height - 1); + sel->r.width = clamp_t(unsigned int, sel->r.width, UIF_MIN_SIZE, + format->width - sel->r.left); + sel->r.height = clamp_t(unsigned int, sel->r.height, UIF_MIN_SIZE, + format->height - sel->r.top); + + /* Store the crop rectangle. */ + selection = vsp1_entity_get_pad_selection(&uif->entity, config, + sel->pad, V4L2_SEL_TGT_CROP); + *selection = sel->r; + +done: + mutex_unlock(&uif->entity.lock); + return ret; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static const struct v4l2_subdev_pad_ops uif_pad_ops = { + .init_cfg = vsp1_entity_init_cfg, + .enum_mbus_code = uif_enum_mbus_code, + .enum_frame_size = uif_enum_frame_size, + .get_fmt = vsp1_subdev_get_pad_format, + .set_fmt = uif_set_format, + .get_selection = uif_get_selection, + .set_selection = uif_set_selection, +}; + +static const struct v4l2_subdev_ops uif_ops = { + .pad = &uif_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void uif_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_uif *uif = to_uif(&entity->subdev); + const struct v4l2_rect *crop; + unsigned int left; + unsigned int width; + + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMPMR, + VI6_UIF_DISCOM_DOCMPMR_SEL(9)); + + crop = vsp1_entity_get_pad_selection(entity, entity->config, + UIF_PAD_SINK, V4L2_SEL_TGT_CROP); + + left = crop->left; + width = crop->width; + + /* On M3-W the horizontal coordinates are twice the register value. */ + if (uif->m3w_quirk) { + left /= 2; + width /= 2; + } + + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSPXR, left); + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSPYR, crop->top); + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSZXR, width); + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMSZYR, crop->height); + + vsp1_uif_write(uif, dlb, VI6_UIF_DISCOM_DOCMCR, + VI6_UIF_DISCOM_DOCMCR_CMPR); +} + +static const struct vsp1_entity_operations uif_entity_ops = { + .configure_stream = uif_configure_stream, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +static const struct soc_device_attribute vsp1_r8a7796[] = { + { .soc_id = "r8a7796" }, + { /* sentinel */ } +}; + +struct vsp1_uif *vsp1_uif_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct vsp1_uif *uif; + char name[6]; + int ret; + + uif = devm_kzalloc(vsp1->dev, sizeof(*uif), GFP_KERNEL); + if (!uif) + return ERR_PTR(-ENOMEM); + + if (soc_device_match(vsp1_r8a7796)) + uif->m3w_quirk = true; + + uif->entity.ops = &uif_entity_ops; + uif->entity.type = VSP1_ENTITY_UIF; + uif->entity.index = index; + + /* The datasheet names the two UIF instances UIF4 and UIF5. */ + sprintf(name, "uif.%u", index + 4); + ret = vsp1_entity_init(vsp1, &uif->entity, name, 2, &uif_ops, + MEDIA_ENT_F_PROC_VIDEO_STATISTICS); + if (ret < 0) + return ERR_PTR(ret); + + return uif; +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_uif.h b/drivers/media/platform/renesas/vsp1/vsp1_uif.h new file mode 100644 index 000000000..c71ab5f6a --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_uif.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_uif.h -- R-Car VSP1 User Logic Interface + * + * Copyright (C) 2017-2018 Laurent Pinchart + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_UIF_H__ +#define __VSP1_UIF_H__ + +#include "vsp1_entity.h" + +struct vsp1_device; + +#define UIF_PAD_SINK 0 +#define UIF_PAD_SOURCE 1 + +struct vsp1_uif { + struct vsp1_entity entity; + bool m3w_quirk; +}; + +static inline struct vsp1_uif *to_uif(struct v4l2_subdev *subdev) +{ + return container_of(subdev, struct vsp1_uif, entity.subdev); +} + +struct vsp1_uif *vsp1_uif_create(struct vsp1_device *vsp1, unsigned int index); +u32 vsp1_uif_get_crc(struct vsp1_uif *uif); + +#endif /* __VSP1_UIF_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_video.c b/drivers/media/platform/renesas/vsp1/vsp1_video.c new file mode 100644 index 000000000..9d24647c8 --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_video.c @@ -0,0 +1,1326 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_video.c -- R-Car VSP1 Video Node + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "vsp1.h" +#include "vsp1_brx.h" +#include "vsp1_dl.h" +#include "vsp1_entity.h" +#include "vsp1_hgo.h" +#include "vsp1_hgt.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" +#include "vsp1_uds.h" +#include "vsp1_video.h" + +#define VSP1_VIDEO_DEF_FORMAT V4L2_PIX_FMT_YUYV +#define VSP1_VIDEO_DEF_WIDTH 1024 +#define VSP1_VIDEO_DEF_HEIGHT 768 + +#define VSP1_VIDEO_MAX_WIDTH 8190U +#define VSP1_VIDEO_MAX_HEIGHT 8190U + +/* ----------------------------------------------------------------------------- + * Helper functions + */ + +static struct v4l2_subdev * +vsp1_video_remote_subdev(struct media_pad *local, u32 *pad) +{ + struct media_pad *remote; + + remote = media_pad_remote_pad_first(local); + if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) + return NULL; + + if (pad) + *pad = remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +static int vsp1_video_verify_format(struct vsp1_video *video) +{ + struct v4l2_subdev_format fmt; + struct v4l2_subdev *subdev; + int ret; + + subdev = vsp1_video_remote_subdev(&video->pad, &fmt.pad); + if (subdev == NULL) + return -EINVAL; + + fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(subdev, pad, get_fmt, NULL, &fmt); + if (ret < 0) + return ret == -ENOIOCTLCMD ? -EINVAL : ret; + + if (video->rwpf->fmtinfo->mbus != fmt.format.code || + video->rwpf->format.height != fmt.format.height || + video->rwpf->format.width != fmt.format.width) + return -EINVAL; + + return 0; +} + +static int __vsp1_video_try_format(struct vsp1_video *video, + struct v4l2_pix_format_mplane *pix, + const struct vsp1_format_info **fmtinfo) +{ + static const u32 xrgb_formats[][2] = { + { V4L2_PIX_FMT_RGB444, V4L2_PIX_FMT_XRGB444 }, + { V4L2_PIX_FMT_RGB555, V4L2_PIX_FMT_XRGB555 }, + { V4L2_PIX_FMT_BGR32, V4L2_PIX_FMT_XBGR32 }, + { V4L2_PIX_FMT_RGB32, V4L2_PIX_FMT_XRGB32 }, + }; + + const struct vsp1_format_info *info; + unsigned int width = pix->width; + unsigned int height = pix->height; + unsigned int i; + + /* + * Backward compatibility: replace deprecated RGB formats by their XRGB + * equivalent. This selects the format older userspace applications want + * while still exposing the new format. + */ + for (i = 0; i < ARRAY_SIZE(xrgb_formats); ++i) { + if (xrgb_formats[i][0] == pix->pixelformat) { + pix->pixelformat = xrgb_formats[i][1]; + break; + } + } + + /* + * Retrieve format information and select the default format if the + * requested format isn't supported. + */ + info = vsp1_get_format_info(video->vsp1, pix->pixelformat); + if (info == NULL) + info = vsp1_get_format_info(video->vsp1, VSP1_VIDEO_DEF_FORMAT); + + pix->pixelformat = info->fourcc; + pix->colorspace = V4L2_COLORSPACE_SRGB; + pix->field = V4L2_FIELD_NONE; + + if (info->fourcc == V4L2_PIX_FMT_HSV24 || + info->fourcc == V4L2_PIX_FMT_HSV32) + pix->hsv_enc = V4L2_HSV_ENC_256; + + memset(pix->reserved, 0, sizeof(pix->reserved)); + + /* Align the width and height for YUV 4:2:2 and 4:2:0 formats. */ + width = round_down(width, info->hsub); + height = round_down(height, info->vsub); + + /* Clamp the width and height. */ + pix->width = clamp(width, info->hsub, VSP1_VIDEO_MAX_WIDTH); + pix->height = clamp(height, info->vsub, VSP1_VIDEO_MAX_HEIGHT); + + /* + * Compute and clamp the stride and image size. While not documented in + * the datasheet, strides not aligned to a multiple of 128 bytes result + * in image corruption. + */ + for (i = 0; i < min(info->planes, 2U); ++i) { + unsigned int hsub = i > 0 ? info->hsub : 1; + unsigned int vsub = i > 0 ? info->vsub : 1; + unsigned int align = 128; + unsigned int bpl; + + bpl = clamp_t(unsigned int, pix->plane_fmt[i].bytesperline, + pix->width / hsub * info->bpp[i] / 8, + round_down(65535U, align)); + + pix->plane_fmt[i].bytesperline = round_up(bpl, align); + pix->plane_fmt[i].sizeimage = pix->plane_fmt[i].bytesperline + * pix->height / vsub; + } + + if (info->planes == 3) { + /* The second and third planes must have the same stride. */ + pix->plane_fmt[2].bytesperline = pix->plane_fmt[1].bytesperline; + pix->plane_fmt[2].sizeimage = pix->plane_fmt[1].sizeimage; + } + + pix->num_planes = info->planes; + + if (fmtinfo) + *fmtinfo = info; + + return 0; +} + +/* ----------------------------------------------------------------------------- + * VSP1 Partition Algorithm support + */ + +/** + * vsp1_video_calculate_partition - Calculate the active partition output window + * + * @pipe: the pipeline + * @partition: partition that will hold the calculated values + * @div_size: pre-determined maximum partition division size + * @index: partition index + */ +static void vsp1_video_calculate_partition(struct vsp1_pipeline *pipe, + struct vsp1_partition *partition, + unsigned int div_size, + unsigned int index) +{ + const struct v4l2_mbus_framefmt *format; + struct vsp1_partition_window window; + unsigned int modulus; + + /* + * Partitions are computed on the size before rotation, use the format + * at the WPF sink. + */ + format = vsp1_entity_get_pad_format(&pipe->output->entity, + pipe->output->entity.config, + RWPF_PAD_SINK); + + /* A single partition simply processes the output size in full. */ + if (pipe->partitions <= 1) { + window.left = 0; + window.width = format->width; + + vsp1_pipeline_propagate_partition(pipe, partition, index, + &window); + return; + } + + /* Initialise the partition with sane starting conditions. */ + window.left = index * div_size; + window.width = div_size; + + modulus = format->width % div_size; + + /* + * We need to prevent the last partition from being smaller than the + * *minimum* width of the hardware capabilities. + * + * If the modulus is less than half of the partition size, + * the penultimate partition is reduced to half, which is added + * to the final partition: |1234|1234|1234|12|341| + * to prevent this: |1234|1234|1234|1234|1|. + */ + if (modulus) { + /* + * pipe->partitions is 1 based, whilst index is a 0 based index. + * Normalise this locally. + */ + unsigned int partitions = pipe->partitions - 1; + + if (modulus < div_size / 2) { + if (index == partitions - 1) { + /* Halve the penultimate partition. */ + window.width = div_size / 2; + } else if (index == partitions) { + /* Increase the final partition. */ + window.width = (div_size / 2) + modulus; + window.left -= div_size / 2; + } + } else if (index == partitions) { + window.width = modulus; + } + } + + vsp1_pipeline_propagate_partition(pipe, partition, index, &window); +} + +static int vsp1_video_pipeline_setup_partitions(struct vsp1_pipeline *pipe) +{ + struct vsp1_device *vsp1 = pipe->output->entity.vsp1; + const struct v4l2_mbus_framefmt *format; + struct vsp1_entity *entity; + unsigned int div_size; + unsigned int i; + + /* + * Partitions are computed on the size before rotation, use the format + * at the WPF sink. + */ + format = vsp1_entity_get_pad_format(&pipe->output->entity, + pipe->output->entity.config, + RWPF_PAD_SINK); + div_size = format->width; + + /* + * Only Gen3 hardware requires image partitioning, Gen2 will operate + * with a single partition that covers the whole output. + */ + if (vsp1->info->gen == 3) { + list_for_each_entry(entity, &pipe->entities, list_pipe) { + unsigned int entity_max; + + if (!entity->ops->max_width) + continue; + + entity_max = entity->ops->max_width(entity, pipe); + if (entity_max) + div_size = min(div_size, entity_max); + } + } + + pipe->partitions = DIV_ROUND_UP(format->width, div_size); + pipe->part_table = kcalloc(pipe->partitions, sizeof(*pipe->part_table), + GFP_KERNEL); + if (!pipe->part_table) + return -ENOMEM; + + for (i = 0; i < pipe->partitions; ++i) + vsp1_video_calculate_partition(pipe, &pipe->part_table[i], + div_size, i); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Pipeline Management + */ + +/* + * vsp1_video_complete_buffer - Complete the current buffer + * @video: the video node + * + * This function completes the current buffer by filling its sequence number, + * time stamp and payload size, and hands it back to the vb2 core. + * + * Return the next queued buffer or NULL if the queue is empty. + */ +static struct vsp1_vb2_buffer * +vsp1_video_complete_buffer(struct vsp1_video *video) +{ + struct vsp1_pipeline *pipe = video->rwpf->entity.pipe; + struct vsp1_vb2_buffer *next = NULL; + struct vsp1_vb2_buffer *done; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&video->irqlock, flags); + + if (list_empty(&video->irqqueue)) { + spin_unlock_irqrestore(&video->irqlock, flags); + return NULL; + } + + done = list_first_entry(&video->irqqueue, + struct vsp1_vb2_buffer, queue); + + list_del(&done->queue); + + if (!list_empty(&video->irqqueue)) + next = list_first_entry(&video->irqqueue, + struct vsp1_vb2_buffer, queue); + + spin_unlock_irqrestore(&video->irqlock, flags); + + done->buf.sequence = pipe->sequence; + done->buf.vb2_buf.timestamp = ktime_get_ns(); + for (i = 0; i < done->buf.vb2_buf.num_planes; ++i) + vb2_set_plane_payload(&done->buf.vb2_buf, i, + vb2_plane_size(&done->buf.vb2_buf, i)); + vb2_buffer_done(&done->buf.vb2_buf, VB2_BUF_STATE_DONE); + + return next; +} + +static void vsp1_video_frame_end(struct vsp1_pipeline *pipe, + struct vsp1_rwpf *rwpf) +{ + struct vsp1_video *video = rwpf->video; + struct vsp1_vb2_buffer *buf; + + buf = vsp1_video_complete_buffer(video); + if (buf == NULL) + return; + + video->rwpf->mem = buf->mem; + pipe->buffers_ready |= 1 << video->pipe_index; +} + +static void vsp1_video_pipeline_run_partition(struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + unsigned int partition) +{ + struct vsp1_dl_body *dlb = vsp1_dl_list_get_body0(dl); + struct vsp1_entity *entity; + + pipe->partition = &pipe->part_table[partition]; + + list_for_each_entry(entity, &pipe->entities, list_pipe) + vsp1_entity_configure_partition(entity, pipe, dl, dlb); +} + +static void vsp1_video_pipeline_run(struct vsp1_pipeline *pipe) +{ + struct vsp1_device *vsp1 = pipe->output->entity.vsp1; + struct vsp1_entity *entity; + struct vsp1_dl_body *dlb; + struct vsp1_dl_list *dl; + unsigned int partition; + + dl = vsp1_dl_list_get(pipe->output->dlm); + + /* + * If the VSP hardware isn't configured yet (which occurs either when + * processing the first frame or after a system suspend/resume), add the + * cached stream configuration to the display list to perform a full + * initialisation. + */ + if (!pipe->configured) + vsp1_dl_list_add_body(dl, pipe->stream_config); + + dlb = vsp1_dl_list_get_body0(dl); + + list_for_each_entry(entity, &pipe->entities, list_pipe) + vsp1_entity_configure_frame(entity, pipe, dl, dlb); + + /* Run the first partition. */ + vsp1_video_pipeline_run_partition(pipe, dl, 0); + + /* Process consecutive partitions as necessary. */ + for (partition = 1; partition < pipe->partitions; ++partition) { + struct vsp1_dl_list *dl_next; + + dl_next = vsp1_dl_list_get(pipe->output->dlm); + + /* + * An incomplete chain will still function, but output only + * the partitions that had a dl available. The frame end + * interrupt will be marked on the last dl in the chain. + */ + if (!dl_next) { + dev_err(vsp1->dev, "Failed to obtain a dl list. Frame will be incomplete\n"); + break; + } + + vsp1_video_pipeline_run_partition(pipe, dl_next, partition); + vsp1_dl_list_add_chain(dl, dl_next); + } + + /* Complete, and commit the head display list. */ + vsp1_dl_list_commit(dl, 0); + pipe->configured = true; + + vsp1_pipeline_run(pipe); +} + +static void vsp1_video_pipeline_frame_end(struct vsp1_pipeline *pipe, + unsigned int completion) +{ + struct vsp1_device *vsp1 = pipe->output->entity.vsp1; + enum vsp1_pipeline_state state; + unsigned long flags; + unsigned int i; + + /* M2M Pipelines should never call here with an incomplete frame. */ + WARN_ON_ONCE(!(completion & VSP1_DL_FRAME_END_COMPLETED)); + + spin_lock_irqsave(&pipe->irqlock, flags); + + /* Complete buffers on all video nodes. */ + for (i = 0; i < vsp1->info->rpf_count; ++i) { + if (!pipe->inputs[i]) + continue; + + vsp1_video_frame_end(pipe, pipe->inputs[i]); + } + + vsp1_video_frame_end(pipe, pipe->output); + + state = pipe->state; + pipe->state = VSP1_PIPELINE_STOPPED; + + /* + * If a stop has been requested, mark the pipeline as stopped and + * return. Otherwise restart the pipeline if ready. + */ + if (state == VSP1_PIPELINE_STOPPING) + wake_up(&pipe->wq); + else if (vsp1_pipeline_ready(pipe)) + vsp1_video_pipeline_run(pipe); + + spin_unlock_irqrestore(&pipe->irqlock, flags); +} + +static int vsp1_video_pipeline_build_branch(struct vsp1_pipeline *pipe, + struct vsp1_rwpf *input, + struct vsp1_rwpf *output) +{ + struct media_entity_enum ent_enum; + struct vsp1_entity *entity; + struct media_pad *pad; + struct vsp1_brx *brx = NULL; + int ret; + + ret = media_entity_enum_init(&ent_enum, &input->entity.vsp1->media_dev); + if (ret < 0) + return ret; + + /* + * The main data path doesn't include the HGO or HGT, use + * vsp1_entity_remote_pad() to traverse the graph. + */ + + pad = vsp1_entity_remote_pad(&input->entity.pads[RWPF_PAD_SOURCE]); + + while (1) { + if (pad == NULL) { + ret = -EPIPE; + goto out; + } + + /* We've reached a video node, that shouldn't have happened. */ + if (!is_media_entity_v4l2_subdev(pad->entity)) { + ret = -EPIPE; + goto out; + } + + entity = to_vsp1_entity( + media_entity_to_v4l2_subdev(pad->entity)); + + /* + * A BRU or BRS is present in the pipeline, store its input pad + * number in the input RPF for use when configuring the RPF. + */ + if (entity->type == VSP1_ENTITY_BRU || + entity->type == VSP1_ENTITY_BRS) { + /* BRU and BRS can't be chained. */ + if (brx) { + ret = -EPIPE; + goto out; + } + + brx = to_brx(&entity->subdev); + brx->inputs[pad->index].rpf = input; + input->brx_input = pad->index; + } + + /* We've reached the WPF, we're done. */ + if (entity->type == VSP1_ENTITY_WPF) + break; + + /* Ensure the branch has no loop. */ + if (media_entity_enum_test_and_set(&ent_enum, + &entity->subdev.entity)) { + ret = -EPIPE; + goto out; + } + + /* UDS can't be chained. */ + if (entity->type == VSP1_ENTITY_UDS) { + if (pipe->uds) { + ret = -EPIPE; + goto out; + } + + pipe->uds = entity; + pipe->uds_input = brx ? &brx->entity : &input->entity; + } + + /* Follow the source link, ignoring any HGO or HGT. */ + pad = &entity->pads[entity->source_pad]; + pad = vsp1_entity_remote_pad(pad); + } + + /* The last entity must be the output WPF. */ + if (entity != &output->entity) + ret = -EPIPE; + +out: + media_entity_enum_cleanup(&ent_enum); + + return ret; +} + +static int vsp1_video_pipeline_build(struct vsp1_pipeline *pipe, + struct vsp1_video *video) +{ + struct media_graph graph; + struct media_entity *entity = &video->video.entity; + struct media_device *mdev = entity->graph_obj.mdev; + unsigned int i; + int ret; + + /* Walk the graph to locate the entities and video nodes. */ + ret = media_graph_walk_init(&graph, mdev); + if (ret) + return ret; + + media_graph_walk_start(&graph, entity); + + while ((entity = media_graph_walk_next(&graph))) { + struct v4l2_subdev *subdev; + struct vsp1_rwpf *rwpf; + struct vsp1_entity *e; + + if (!is_media_entity_v4l2_subdev(entity)) + continue; + + subdev = media_entity_to_v4l2_subdev(entity); + e = to_vsp1_entity(subdev); + list_add_tail(&e->list_pipe, &pipe->entities); + e->pipe = pipe; + + switch (e->type) { + case VSP1_ENTITY_RPF: + rwpf = to_rwpf(subdev); + pipe->inputs[rwpf->entity.index] = rwpf; + rwpf->video->pipe_index = ++pipe->num_inputs; + break; + + case VSP1_ENTITY_WPF: + rwpf = to_rwpf(subdev); + pipe->output = rwpf; + rwpf->video->pipe_index = 0; + break; + + case VSP1_ENTITY_LIF: + pipe->lif = e; + break; + + case VSP1_ENTITY_BRU: + case VSP1_ENTITY_BRS: + pipe->brx = e; + break; + + case VSP1_ENTITY_HGO: + pipe->hgo = e; + break; + + case VSP1_ENTITY_HGT: + pipe->hgt = e; + break; + + default: + break; + } + } + + media_graph_walk_cleanup(&graph); + + /* We need one output and at least one input. */ + if (pipe->num_inputs == 0 || !pipe->output) + return -EPIPE; + + /* + * Follow links downstream for each input and make sure the graph + * contains no loop and that all branches end at the output WPF. + */ + for (i = 0; i < video->vsp1->info->rpf_count; ++i) { + if (!pipe->inputs[i]) + continue; + + ret = vsp1_video_pipeline_build_branch(pipe, pipe->inputs[i], + pipe->output); + if (ret < 0) + return ret; + } + + return 0; +} + +static int vsp1_video_pipeline_init(struct vsp1_pipeline *pipe, + struct vsp1_video *video) +{ + vsp1_pipeline_init(pipe); + + pipe->frame_end = vsp1_video_pipeline_frame_end; + + return vsp1_video_pipeline_build(pipe, video); +} + +static struct vsp1_pipeline *vsp1_video_pipeline_get(struct vsp1_video *video) +{ + struct vsp1_pipeline *pipe; + int ret; + + /* + * Get a pipeline object for the video node. If a pipeline has already + * been allocated just increment its reference count and return it. + * Otherwise allocate a new pipeline and initialize it, it will be freed + * when the last reference is released. + */ + if (!video->rwpf->entity.pipe) { + pipe = kzalloc(sizeof(*pipe), GFP_KERNEL); + if (!pipe) + return ERR_PTR(-ENOMEM); + + ret = vsp1_video_pipeline_init(pipe, video); + if (ret < 0) { + vsp1_pipeline_reset(pipe); + kfree(pipe); + return ERR_PTR(ret); + } + } else { + pipe = video->rwpf->entity.pipe; + kref_get(&pipe->kref); + } + + return pipe; +} + +static void vsp1_video_pipeline_release(struct kref *kref) +{ + struct vsp1_pipeline *pipe = container_of(kref, typeof(*pipe), kref); + + vsp1_pipeline_reset(pipe); + kfree(pipe); +} + +static void vsp1_video_pipeline_put(struct vsp1_pipeline *pipe) +{ + struct media_device *mdev = &pipe->output->entity.vsp1->media_dev; + + mutex_lock(&mdev->graph_mutex); + kref_put(&pipe->kref, vsp1_video_pipeline_release); + mutex_unlock(&mdev->graph_mutex); +} + +/* ----------------------------------------------------------------------------- + * videobuf2 Queue Operations + */ + +static int +vsp1_video_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + const struct v4l2_pix_format_mplane *format = &video->rwpf->format; + unsigned int i; + + if (*nplanes) { + if (*nplanes != format->num_planes) + return -EINVAL; + + for (i = 0; i < *nplanes; i++) + if (sizes[i] < format->plane_fmt[i].sizeimage) + return -EINVAL; + return 0; + } + + *nplanes = format->num_planes; + + for (i = 0; i < format->num_planes; ++i) + sizes[i] = format->plane_fmt[i].sizeimage; + + return 0; +} + +static int vsp1_video_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vsp1_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_vb2_buffer *buf = to_vsp1_vb2_buffer(vbuf); + const struct v4l2_pix_format_mplane *format = &video->rwpf->format; + unsigned int i; + + if (vb->num_planes < format->num_planes) + return -EINVAL; + + for (i = 0; i < vb->num_planes; ++i) { + buf->mem.addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + + if (vb2_plane_size(vb, i) < format->plane_fmt[i].sizeimage) + return -EINVAL; + } + + for ( ; i < 3; ++i) + buf->mem.addr[i] = 0; + + return 0; +} + +static void vsp1_video_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vsp1_video *video = vb2_get_drv_priv(vb->vb2_queue); + struct vsp1_pipeline *pipe = video->rwpf->entity.pipe; + struct vsp1_vb2_buffer *buf = to_vsp1_vb2_buffer(vbuf); + unsigned long flags; + bool empty; + + spin_lock_irqsave(&video->irqlock, flags); + empty = list_empty(&video->irqqueue); + list_add_tail(&buf->queue, &video->irqqueue); + spin_unlock_irqrestore(&video->irqlock, flags); + + if (!empty) + return; + + spin_lock_irqsave(&pipe->irqlock, flags); + + video->rwpf->mem = buf->mem; + pipe->buffers_ready |= 1 << video->pipe_index; + + if (vb2_is_streaming(&video->queue) && + vsp1_pipeline_ready(pipe)) + vsp1_video_pipeline_run(pipe); + + spin_unlock_irqrestore(&pipe->irqlock, flags); +} + +static int vsp1_video_setup_pipeline(struct vsp1_pipeline *pipe) +{ + struct vsp1_entity *entity; + int ret; + + /* Determine this pipelines sizes for image partitioning support. */ + ret = vsp1_video_pipeline_setup_partitions(pipe); + if (ret < 0) + return ret; + + if (pipe->uds) { + struct vsp1_uds *uds = to_uds(&pipe->uds->subdev); + + /* + * If a BRU or BRS is present in the pipeline before the UDS, + * the alpha component doesn't need to be scaled as the BRU and + * BRS output alpha value is fixed to 255. Otherwise we need to + * scale the alpha component only when available at the input + * RPF. + */ + if (pipe->uds_input->type == VSP1_ENTITY_BRU || + pipe->uds_input->type == VSP1_ENTITY_BRS) { + uds->scale_alpha = false; + } else { + struct vsp1_rwpf *rpf = + to_rwpf(&pipe->uds_input->subdev); + + uds->scale_alpha = rpf->fmtinfo->alpha; + } + } + + /* + * Compute and cache the stream configuration into a body. The cached + * body will be added to the display list by vsp1_video_pipeline_run() + * whenever the pipeline needs to be fully reconfigured. + */ + pipe->stream_config = vsp1_dlm_dl_body_get(pipe->output->dlm); + if (!pipe->stream_config) + return -ENOMEM; + + list_for_each_entry(entity, &pipe->entities, list_pipe) { + vsp1_entity_route_setup(entity, pipe, pipe->stream_config); + vsp1_entity_configure_stream(entity, pipe, NULL, + pipe->stream_config); + } + + return 0; +} + +static void vsp1_video_release_buffers(struct vsp1_video *video) +{ + struct vsp1_vb2_buffer *buffer; + unsigned long flags; + + /* Remove all buffers from the IRQ queue. */ + spin_lock_irqsave(&video->irqlock, flags); + list_for_each_entry(buffer, &video->irqqueue, queue) + vb2_buffer_done(&buffer->buf.vb2_buf, VB2_BUF_STATE_ERROR); + INIT_LIST_HEAD(&video->irqqueue); + spin_unlock_irqrestore(&video->irqlock, flags); +} + +static void vsp1_video_cleanup_pipeline(struct vsp1_pipeline *pipe) +{ + lockdep_assert_held(&pipe->lock); + + /* Release any cached configuration from our output video. */ + vsp1_dl_body_put(pipe->stream_config); + pipe->stream_config = NULL; + pipe->configured = false; + + /* Release our partition table allocation. */ + kfree(pipe->part_table); + pipe->part_table = NULL; +} + +static int vsp1_video_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + struct vsp1_pipeline *pipe = video->rwpf->entity.pipe; + bool start_pipeline = false; + unsigned long flags; + int ret; + + mutex_lock(&pipe->lock); + if (pipe->stream_count == pipe->num_inputs) { + ret = vsp1_video_setup_pipeline(pipe); + if (ret < 0) { + vsp1_video_release_buffers(video); + vsp1_video_cleanup_pipeline(pipe); + mutex_unlock(&pipe->lock); + return ret; + } + + start_pipeline = true; + } + + pipe->stream_count++; + mutex_unlock(&pipe->lock); + + /* + * vsp1_pipeline_ready() is not sufficient to establish that all streams + * are prepared and the pipeline is configured, as multiple streams + * can race through streamon with buffers already queued; Therefore we + * don't even attempt to start the pipeline until the last stream has + * called through here. + */ + if (!start_pipeline) + return 0; + + spin_lock_irqsave(&pipe->irqlock, flags); + if (vsp1_pipeline_ready(pipe)) + vsp1_video_pipeline_run(pipe); + spin_unlock_irqrestore(&pipe->irqlock, flags); + + return 0; +} + +static void vsp1_video_stop_streaming(struct vb2_queue *vq) +{ + struct vsp1_video *video = vb2_get_drv_priv(vq); + struct vsp1_pipeline *pipe = video->rwpf->entity.pipe; + unsigned long flags; + int ret; + + /* + * Clear the buffers ready flag to make sure the device won't be started + * by a QBUF on the video node on the other side of the pipeline. + */ + spin_lock_irqsave(&video->irqlock, flags); + pipe->buffers_ready &= ~(1 << video->pipe_index); + spin_unlock_irqrestore(&video->irqlock, flags); + + mutex_lock(&pipe->lock); + if (--pipe->stream_count == pipe->num_inputs) { + /* Stop the pipeline. */ + ret = vsp1_pipeline_stop(pipe); + if (ret == -ETIMEDOUT) + dev_err(video->vsp1->dev, "pipeline stop timeout\n"); + + vsp1_video_cleanup_pipeline(pipe); + } + mutex_unlock(&pipe->lock); + + video_device_pipeline_stop(&video->video); + vsp1_video_release_buffers(video); + vsp1_video_pipeline_put(pipe); +} + +static const struct vb2_ops vsp1_video_queue_qops = { + .queue_setup = vsp1_video_queue_setup, + .buf_prepare = vsp1_video_buffer_prepare, + .buf_queue = vsp1_video_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = vsp1_video_start_streaming, + .stop_streaming = vsp1_video_stop_streaming, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 ioctls + */ + +static int +vsp1_video_querycap(struct file *file, void *fh, struct v4l2_capability *cap) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING + | V4L2_CAP_VIDEO_CAPTURE_MPLANE + | V4L2_CAP_VIDEO_OUTPUT_MPLANE; + + + strscpy(cap->driver, "vsp1", sizeof(cap->driver)); + strscpy(cap->card, video->video.name, sizeof(cap->card)); + + return 0; +} + +static int +vsp1_video_get_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + if (format->type != video->queue.type) + return -EINVAL; + + mutex_lock(&video->lock); + format->fmt.pix_mp = video->rwpf->format; + mutex_unlock(&video->lock); + + return 0; +} + +static int +vsp1_video_try_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + + if (format->type != video->queue.type) + return -EINVAL; + + return __vsp1_video_try_format(video, &format->fmt.pix_mp, NULL); +} + +static int +vsp1_video_set_format(struct file *file, void *fh, struct v4l2_format *format) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + const struct vsp1_format_info *info; + int ret; + + if (format->type != video->queue.type) + return -EINVAL; + + ret = __vsp1_video_try_format(video, &format->fmt.pix_mp, &info); + if (ret < 0) + return ret; + + mutex_lock(&video->lock); + + if (vb2_is_busy(&video->queue)) { + ret = -EBUSY; + goto done; + } + + video->rwpf->format = format->fmt.pix_mp; + video->rwpf->fmtinfo = info; + +done: + mutex_unlock(&video->lock); + return ret; +} + +static int +vsp1_video_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct v4l2_fh *vfh = file->private_data; + struct vsp1_video *video = to_vsp1_video(vfh->vdev); + struct media_device *mdev = &video->vsp1->media_dev; + struct vsp1_pipeline *pipe; + int ret; + + if (vb2_queue_is_busy(&video->queue, file)) + return -EBUSY; + + /* + * Get a pipeline for the video node and start streaming on it. No link + * touching an entity in the pipeline can be activated or deactivated + * once streaming is started. + */ + mutex_lock(&mdev->graph_mutex); + + pipe = vsp1_video_pipeline_get(video); + if (IS_ERR(pipe)) { + mutex_unlock(&mdev->graph_mutex); + return PTR_ERR(pipe); + } + + ret = __video_device_pipeline_start(&video->video, &pipe->pipe); + if (ret < 0) { + mutex_unlock(&mdev->graph_mutex); + goto err_pipe; + } + + mutex_unlock(&mdev->graph_mutex); + + /* + * Verify that the configured format matches the output of the connected + * subdev. + */ + ret = vsp1_video_verify_format(video); + if (ret < 0) + goto err_stop; + + /* Start the queue. */ + ret = vb2_streamon(&video->queue, type); + if (ret < 0) + goto err_stop; + + return 0; + +err_stop: + video_device_pipeline_stop(&video->video); +err_pipe: + vsp1_video_pipeline_put(pipe); + return ret; +} + +static const struct v4l2_ioctl_ops vsp1_video_ioctl_ops = { + .vidioc_querycap = vsp1_video_querycap, + .vidioc_g_fmt_vid_cap_mplane = vsp1_video_get_format, + .vidioc_s_fmt_vid_cap_mplane = vsp1_video_set_format, + .vidioc_try_fmt_vid_cap_mplane = vsp1_video_try_format, + .vidioc_g_fmt_vid_out_mplane = vsp1_video_get_format, + .vidioc_s_fmt_vid_out_mplane = vsp1_video_set_format, + .vidioc_try_fmt_vid_out_mplane = vsp1_video_try_format, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vsp1_video_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * V4L2 File Operations + */ + +static int vsp1_video_open(struct file *file) +{ + struct vsp1_video *video = video_drvdata(file); + struct v4l2_fh *vfh; + int ret = 0; + + vfh = kzalloc(sizeof(*vfh), GFP_KERNEL); + if (vfh == NULL) + return -ENOMEM; + + v4l2_fh_init(vfh, &video->video); + v4l2_fh_add(vfh); + + file->private_data = vfh; + + ret = vsp1_device_get(video->vsp1); + if (ret < 0) { + v4l2_fh_del(vfh); + v4l2_fh_exit(vfh); + kfree(vfh); + } + + return ret; +} + +static int vsp1_video_release(struct file *file) +{ + struct vsp1_video *video = video_drvdata(file); + + vb2_fop_release(file); + + vsp1_device_put(video->vsp1); + + return 0; +} + +static const struct v4l2_file_operations vsp1_video_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = vsp1_video_open, + .release = vsp1_video_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +/* ----------------------------------------------------------------------------- + * Suspend and Resume + */ + +void vsp1_video_suspend(struct vsp1_device *vsp1) +{ + unsigned long flags; + unsigned int i; + int ret; + + /* + * To avoid increasing the system suspend time needlessly, loop over the + * pipelines twice, first to set them all to the stopping state, and + * then to wait for the stop to complete. + */ + for (i = 0; i < vsp1->info->wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = wpf->entity.pipe; + if (pipe == NULL) + continue; + + spin_lock_irqsave(&pipe->irqlock, flags); + if (pipe->state == VSP1_PIPELINE_RUNNING) + pipe->state = VSP1_PIPELINE_STOPPING; + spin_unlock_irqrestore(&pipe->irqlock, flags); + } + + for (i = 0; i < vsp1->info->wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = wpf->entity.pipe; + if (pipe == NULL) + continue; + + ret = wait_event_timeout(pipe->wq, vsp1_pipeline_stopped(pipe), + msecs_to_jiffies(500)); + if (ret == 0) + dev_warn(vsp1->dev, "pipeline %u stop timeout\n", + wpf->entity.index); + } +} + +void vsp1_video_resume(struct vsp1_device *vsp1) +{ + unsigned long flags; + unsigned int i; + + /* Resume all running pipelines. */ + for (i = 0; i < vsp1->info->wpf_count; ++i) { + struct vsp1_rwpf *wpf = vsp1->wpf[i]; + struct vsp1_pipeline *pipe; + + if (wpf == NULL) + continue; + + pipe = wpf->entity.pipe; + if (pipe == NULL) + continue; + + /* + * The hardware may have been reset during a suspend and will + * need a full reconfiguration. + */ + pipe->configured = false; + + spin_lock_irqsave(&pipe->irqlock, flags); + if (vsp1_pipeline_ready(pipe)) + vsp1_video_pipeline_run(pipe); + spin_unlock_irqrestore(&pipe->irqlock, flags); + } +} + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_video *vsp1_video_create(struct vsp1_device *vsp1, + struct vsp1_rwpf *rwpf) +{ + struct vsp1_video *video; + const char *direction; + int ret; + + video = devm_kzalloc(vsp1->dev, sizeof(*video), GFP_KERNEL); + if (!video) + return ERR_PTR(-ENOMEM); + + rwpf->video = video; + + video->vsp1 = vsp1; + video->rwpf = rwpf; + + if (rwpf->entity.type == VSP1_ENTITY_RPF) { + direction = "input"; + video->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + video->pad.flags = MEDIA_PAD_FL_SOURCE; + video->video.vfl_dir = VFL_DIR_TX; + video->video.device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE | + V4L2_CAP_STREAMING; + } else { + direction = "output"; + video->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + video->pad.flags = MEDIA_PAD_FL_SINK; + video->video.vfl_dir = VFL_DIR_RX; + video->video.device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_STREAMING; + } + + mutex_init(&video->lock); + spin_lock_init(&video->irqlock); + INIT_LIST_HEAD(&video->irqqueue); + + /* Initialize the media entity... */ + ret = media_entity_pads_init(&video->video.entity, 1, &video->pad); + if (ret < 0) + return ERR_PTR(ret); + + /* ... and the format ... */ + rwpf->format.pixelformat = VSP1_VIDEO_DEF_FORMAT; + rwpf->format.width = VSP1_VIDEO_DEF_WIDTH; + rwpf->format.height = VSP1_VIDEO_DEF_HEIGHT; + __vsp1_video_try_format(video, &rwpf->format, &rwpf->fmtinfo); + + /* ... and the video node... */ + video->video.v4l2_dev = &video->vsp1->v4l2_dev; + video->video.fops = &vsp1_video_fops; + snprintf(video->video.name, sizeof(video->video.name), "%s %s", + rwpf->entity.subdev.name, direction); + video->video.vfl_type = VFL_TYPE_VIDEO; + video->video.release = video_device_release_empty; + video->video.ioctl_ops = &vsp1_video_ioctl_ops; + + video_set_drvdata(&video->video, video); + + video->queue.type = video->type; + video->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + video->queue.lock = &video->lock; + video->queue.drv_priv = video; + video->queue.buf_struct_size = sizeof(struct vsp1_vb2_buffer); + video->queue.ops = &vsp1_video_queue_qops; + video->queue.mem_ops = &vb2_dma_contig_memops; + video->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + video->queue.dev = video->vsp1->bus_master; + ret = vb2_queue_init(&video->queue); + if (ret < 0) { + dev_err(video->vsp1->dev, "failed to initialize vb2 queue\n"); + goto error; + } + + /* ... and register the video device. */ + video->video.queue = &video->queue; + ret = video_register_device(&video->video, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + dev_err(video->vsp1->dev, "failed to register video device\n"); + goto error; + } + + return video; + +error: + vsp1_video_cleanup(video); + return ERR_PTR(ret); +} + +void vsp1_video_cleanup(struct vsp1_video *video) +{ + if (video_is_registered(&video->video)) + video_unregister_device(&video->video); + + media_entity_cleanup(&video->video.entity); +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_video.h b/drivers/media/platform/renesas/vsp1/vsp1_video.h new file mode 100644 index 000000000..f3cf5e2fd --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_video.h @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_video.h -- R-Car VSP1 Video Node + * + * Copyright (C) 2013-2015 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ +#ifndef __VSP1_VIDEO_H__ +#define __VSP1_VIDEO_H__ + +#include +#include + +#include + +#include "vsp1_rwpf.h" + +struct vsp1_vb2_buffer { + struct vb2_v4l2_buffer buf; + struct list_head queue; + struct vsp1_rwpf_memory mem; +}; + +static inline struct vsp1_vb2_buffer * +to_vsp1_vb2_buffer(struct vb2_v4l2_buffer *vbuf) +{ + return container_of(vbuf, struct vsp1_vb2_buffer, buf); +} + +struct vsp1_video { + struct list_head list; + struct vsp1_device *vsp1; + struct vsp1_rwpf *rwpf; + + struct video_device video; + enum v4l2_buf_type type; + struct media_pad pad; + + struct mutex lock; + + unsigned int pipe_index; + + struct vb2_queue queue; + spinlock_t irqlock; + struct list_head irqqueue; +}; + +static inline struct vsp1_video *to_vsp1_video(struct video_device *vdev) +{ + return container_of(vdev, struct vsp1_video, video); +} + +void vsp1_video_suspend(struct vsp1_device *vsp1); +void vsp1_video_resume(struct vsp1_device *vsp1); + +struct vsp1_video *vsp1_video_create(struct vsp1_device *vsp1, + struct vsp1_rwpf *rwpf); +void vsp1_video_cleanup(struct vsp1_video *video); + +#endif /* __VSP1_VIDEO_H__ */ diff --git a/drivers/media/platform/renesas/vsp1/vsp1_wpf.c b/drivers/media/platform/renesas/vsp1/vsp1_wpf.c new file mode 100644 index 000000000..94e91d7bb --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_wpf.c @@ -0,0 +1,613 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_wpf.c -- R-Car VSP1 Write Pixel Formatter + * + * Copyright (C) 2013-2014 Renesas Electronics Corporation + * + * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) + */ + +#include + +#include + +#include "vsp1.h" +#include "vsp1_dl.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" +#include "vsp1_video.h" + +#define WPF_GEN2_MAX_WIDTH 2048U +#define WPF_GEN2_MAX_HEIGHT 2048U +#define WPF_GEN3_MAX_WIDTH 8190U +#define WPF_GEN3_MAX_HEIGHT 8190U + +/* ----------------------------------------------------------------------------- + * Device Access + */ + +static inline void vsp1_wpf_write(struct vsp1_rwpf *wpf, + struct vsp1_dl_body *dlb, u32 reg, u32 data) +{ + vsp1_dl_body_write(dlb, reg + wpf->entity.index * VI6_WPF_OFFSET, data); +} + +/* ----------------------------------------------------------------------------- + * Controls + */ + +enum wpf_flip_ctrl { + WPF_CTRL_VFLIP = 0, + WPF_CTRL_HFLIP = 1, +}; + +static int vsp1_wpf_set_rotation(struct vsp1_rwpf *wpf, unsigned int rotation) +{ + struct vsp1_video *video = wpf->video; + struct v4l2_mbus_framefmt *sink_format; + struct v4l2_mbus_framefmt *source_format; + bool rotate; + int ret = 0; + + /* + * Only consider the 0°/180° from/to 90°/270° modifications, the rest + * is taken care of by the flipping configuration. + */ + rotate = rotation == 90 || rotation == 270; + if (rotate == wpf->flip.rotate) + return 0; + + /* Changing rotation isn't allowed when buffers are allocated. */ + mutex_lock(&video->lock); + + if (vb2_is_busy(&video->queue)) { + ret = -EBUSY; + goto done; + } + + sink_format = vsp1_entity_get_pad_format(&wpf->entity, + wpf->entity.config, + RWPF_PAD_SINK); + source_format = vsp1_entity_get_pad_format(&wpf->entity, + wpf->entity.config, + RWPF_PAD_SOURCE); + + mutex_lock(&wpf->entity.lock); + + if (rotate) { + source_format->width = sink_format->height; + source_format->height = sink_format->width; + } else { + source_format->width = sink_format->width; + source_format->height = sink_format->height; + } + + wpf->flip.rotate = rotate; + + mutex_unlock(&wpf->entity.lock); + +done: + mutex_unlock(&video->lock); + return ret; +} + +static int vsp1_wpf_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct vsp1_rwpf *wpf = + container_of(ctrl->handler, struct vsp1_rwpf, ctrls); + unsigned int rotation; + u32 flip = 0; + int ret; + + /* Update the rotation. */ + rotation = wpf->flip.ctrls.rotate ? wpf->flip.ctrls.rotate->val : 0; + ret = vsp1_wpf_set_rotation(wpf, rotation); + if (ret < 0) + return ret; + + /* + * Compute the flip value resulting from all three controls, with + * rotation by 180° flipping the image in both directions. Store the + * result in the pending flip field for the next frame that will be + * processed. + */ + if (wpf->flip.ctrls.vflip->val) + flip |= BIT(WPF_CTRL_VFLIP); + + if (wpf->flip.ctrls.hflip && wpf->flip.ctrls.hflip->val) + flip |= BIT(WPF_CTRL_HFLIP); + + if (rotation == 180 || rotation == 270) + flip ^= BIT(WPF_CTRL_VFLIP) | BIT(WPF_CTRL_HFLIP); + + spin_lock_irq(&wpf->flip.lock); + wpf->flip.pending = flip; + spin_unlock_irq(&wpf->flip.lock); + + return 0; +} + +static const struct v4l2_ctrl_ops vsp1_wpf_ctrl_ops = { + .s_ctrl = vsp1_wpf_s_ctrl, +}; + +static int wpf_init_controls(struct vsp1_rwpf *wpf) +{ + struct vsp1_device *vsp1 = wpf->entity.vsp1; + unsigned int num_flip_ctrls; + + spin_lock_init(&wpf->flip.lock); + + if (wpf->entity.index != 0) { + /* Only WPF0 supports flipping. */ + num_flip_ctrls = 0; + } else if (vsp1_feature(vsp1, VSP1_HAS_WPF_HFLIP)) { + /* + * When horizontal flip is supported the WPF implements three + * controls (horizontal flip, vertical flip and rotation). + */ + num_flip_ctrls = 3; + } else if (vsp1_feature(vsp1, VSP1_HAS_WPF_VFLIP)) { + /* + * When only vertical flip is supported the WPF implements a + * single control (vertical flip). + */ + num_flip_ctrls = 1; + } else { + /* Otherwise flipping is not supported. */ + num_flip_ctrls = 0; + } + + vsp1_rwpf_init_ctrls(wpf, num_flip_ctrls); + + if (num_flip_ctrls >= 1) { + wpf->flip.ctrls.vflip = + v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + } + + if (num_flip_ctrls == 3) { + wpf->flip.ctrls.hflip = + v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + wpf->flip.ctrls.rotate = + v4l2_ctrl_new_std(&wpf->ctrls, &vsp1_wpf_ctrl_ops, + V4L2_CID_ROTATE, 0, 270, 90, 0); + v4l2_ctrl_cluster(3, &wpf->flip.ctrls.vflip); + } + + if (wpf->ctrls.error) { + dev_err(vsp1->dev, "wpf%u: failed to initialize controls\n", + wpf->entity.index); + return wpf->ctrls.error; + } + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Core Operations + */ + +static int wpf_s_stream(struct v4l2_subdev *subdev, int enable) +{ + struct vsp1_rwpf *wpf = to_rwpf(subdev); + struct vsp1_device *vsp1 = wpf->entity.vsp1; + + if (enable) + return 0; + + /* + * Write to registers directly when stopping the stream as there will be + * no pipeline run to apply the display list. + */ + vsp1_write(vsp1, VI6_WPF_IRQ_ENB(wpf->entity.index), 0); + vsp1_write(vsp1, wpf->entity.index * VI6_WPF_OFFSET + + VI6_WPF_SRCRPF, 0); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * V4L2 Subdevice Operations + */ + +static const struct v4l2_subdev_video_ops wpf_video_ops = { + .s_stream = wpf_s_stream, +}; + +static const struct v4l2_subdev_ops wpf_ops = { + .video = &wpf_video_ops, + .pad = &vsp1_rwpf_pad_ops, +}; + +/* ----------------------------------------------------------------------------- + * VSP1 Entity Operations + */ + +static void vsp1_wpf_destroy(struct vsp1_entity *entity) +{ + struct vsp1_rwpf *wpf = entity_to_rwpf(entity); + + vsp1_dlm_destroy(wpf->dlm); +} + +static int wpf_configure_writeback_chain(struct vsp1_rwpf *wpf, + struct vsp1_dl_list *dl) +{ + unsigned int index = wpf->entity.index; + struct vsp1_dl_list *dl_next; + struct vsp1_dl_body *dlb; + + dl_next = vsp1_dl_list_get(wpf->dlm); + if (!dl_next) { + dev_err(wpf->entity.vsp1->dev, + "Failed to obtain a dl list, disabling writeback\n"); + return -ENOMEM; + } + + dlb = vsp1_dl_list_get_body0(dl_next); + vsp1_dl_body_write(dlb, VI6_WPF_WRBCK_CTRL(index), 0); + vsp1_dl_list_add_chain(dl, dl_next); + + return 0; +} + +static void wpf_configure_stream(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev); + struct vsp1_device *vsp1 = wpf->entity.vsp1; + const struct v4l2_mbus_framefmt *source_format; + const struct v4l2_mbus_framefmt *sink_format; + unsigned int index = wpf->entity.index; + unsigned int i; + u32 outfmt = 0; + u32 srcrpf = 0; + int ret; + + sink_format = vsp1_entity_get_pad_format(&wpf->entity, + wpf->entity.config, + RWPF_PAD_SINK); + source_format = vsp1_entity_get_pad_format(&wpf->entity, + wpf->entity.config, + RWPF_PAD_SOURCE); + + /* Format */ + if (!pipe->lif || wpf->writeback) { + const struct v4l2_pix_format_mplane *format = &wpf->format; + const struct vsp1_format_info *fmtinfo = wpf->fmtinfo; + + outfmt = fmtinfo->hwfmt << VI6_WPF_OUTFMT_WRFMT_SHIFT; + + if (wpf->flip.rotate) + outfmt |= VI6_WPF_OUTFMT_ROT; + + if (fmtinfo->alpha) + outfmt |= VI6_WPF_OUTFMT_PXA; + if (fmtinfo->swap_yc) + outfmt |= VI6_WPF_OUTFMT_SPYCS; + if (fmtinfo->swap_uv) + outfmt |= VI6_WPF_OUTFMT_SPUVS; + + /* Destination stride and byte swapping. */ + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_STRIDE_Y, + format->plane_fmt[0].bytesperline); + if (format->num_planes > 1) + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_STRIDE_C, + format->plane_fmt[1].bytesperline); + + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSWAP, fmtinfo->swap); + + if (vsp1_feature(vsp1, VSP1_HAS_WPF_HFLIP) && index == 0) + vsp1_wpf_write(wpf, dlb, VI6_WPF_ROT_CTRL, + VI6_WPF_ROT_CTRL_LN16 | + (256 << VI6_WPF_ROT_CTRL_LMEM_WD_SHIFT)); + } + + if (sink_format->code != source_format->code) + outfmt |= VI6_WPF_OUTFMT_CSC; + + wpf->outfmt = outfmt; + + vsp1_dl_body_write(dlb, VI6_DPR_WPF_FPORCH(index), + VI6_DPR_WPF_FPORCH_FP_WPFN); + + /* + * Sources. If the pipeline has a single input and BRx is not used, + * configure it as the master layer. Otherwise configure all + * inputs as sub-layers and select the virtual RPF as the master + * layer. + */ + for (i = 0; i < vsp1->info->rpf_count; ++i) { + struct vsp1_rwpf *input = pipe->inputs[i]; + + if (!input) + continue; + + srcrpf |= (!pipe->brx && pipe->num_inputs == 1) + ? VI6_WPF_SRCRPF_RPF_ACT_MST(input->entity.index) + : VI6_WPF_SRCRPF_RPF_ACT_SUB(input->entity.index); + } + + if (pipe->brx) + srcrpf |= pipe->brx->type == VSP1_ENTITY_BRU + ? VI6_WPF_SRCRPF_VIRACT_MST + : VI6_WPF_SRCRPF_VIRACT2_MST; + + vsp1_wpf_write(wpf, dlb, VI6_WPF_SRCRPF, srcrpf); + + /* Enable interrupts. */ + vsp1_dl_body_write(dlb, VI6_WPF_IRQ_STA(index), 0); + vsp1_dl_body_write(dlb, VI6_WPF_IRQ_ENB(index), + VI6_WPF_IRQ_ENB_DFEE); + + /* + * Configure writeback for display pipelines (the wpf writeback flag is + * never set for memory-to-memory pipelines). Start by adding a chained + * display list to disable writeback after a single frame, and process + * to enable writeback. If the display list allocation fails don't + * enable writeback as we wouldn't be able to safely disable it, + * resulting in possible memory corruption. + */ + if (wpf->writeback) { + ret = wpf_configure_writeback_chain(wpf, dl); + if (ret < 0) + wpf->writeback = false; + } + + vsp1_dl_body_write(dlb, VI6_WPF_WRBCK_CTRL(index), + wpf->writeback ? VI6_WPF_WRBCK_CTRL_WBMD : 0); +} + +static void wpf_configure_frame(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + const unsigned int mask = BIT(WPF_CTRL_VFLIP) + | BIT(WPF_CTRL_HFLIP); + struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev); + unsigned long flags; + u32 outfmt; + + spin_lock_irqsave(&wpf->flip.lock, flags); + wpf->flip.active = (wpf->flip.active & ~mask) + | (wpf->flip.pending & mask); + spin_unlock_irqrestore(&wpf->flip.lock, flags); + + outfmt = (wpf->alpha << VI6_WPF_OUTFMT_PDV_SHIFT) | wpf->outfmt; + + if (wpf->flip.active & BIT(WPF_CTRL_VFLIP)) + outfmt |= VI6_WPF_OUTFMT_FLP; + if (wpf->flip.active & BIT(WPF_CTRL_HFLIP)) + outfmt |= VI6_WPF_OUTFMT_HFLP; + + vsp1_wpf_write(wpf, dlb, VI6_WPF_OUTFMT, outfmt); +} + +static void wpf_configure_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev); + struct vsp1_device *vsp1 = wpf->entity.vsp1; + struct vsp1_rwpf_memory mem = wpf->mem; + const struct v4l2_mbus_framefmt *sink_format; + const struct v4l2_pix_format_mplane *format = &wpf->format; + const struct vsp1_format_info *fmtinfo = wpf->fmtinfo; + unsigned int width; + unsigned int height; + unsigned int left; + unsigned int offset; + unsigned int flip; + unsigned int i; + + sink_format = vsp1_entity_get_pad_format(&wpf->entity, + wpf->entity.config, + RWPF_PAD_SINK); + width = sink_format->width; + height = sink_format->height; + left = 0; + + /* + * Cropping. The partition algorithm can split the image into + * multiple slices. + */ + if (pipe->partitions > 1) { + width = pipe->partition->wpf.width; + left = pipe->partition->wpf.left; + } + + vsp1_wpf_write(wpf, dlb, VI6_WPF_HSZCLIP, VI6_WPF_SZCLIP_EN | + (0 << VI6_WPF_SZCLIP_OFST_SHIFT) | + (width << VI6_WPF_SZCLIP_SIZE_SHIFT)); + vsp1_wpf_write(wpf, dlb, VI6_WPF_VSZCLIP, VI6_WPF_SZCLIP_EN | + (0 << VI6_WPF_SZCLIP_OFST_SHIFT) | + (height << VI6_WPF_SZCLIP_SIZE_SHIFT)); + + /* + * For display pipelines without writeback enabled there's no memory + * address to configure, return now. + */ + if (pipe->lif && !wpf->writeback) + return; + + /* + * Update the memory offsets based on flipping configuration. + * The destination addresses point to the locations where the + * VSP starts writing to memory, which can be any corner of the + * image depending on the combination of flipping and rotation. + */ + + /* + * First take the partition left coordinate into account. + * Compute the offset to order the partitions correctly on the + * output based on whether flipping is enabled. Consider + * horizontal flipping when rotation is disabled but vertical + * flipping when rotation is enabled, as rotating the image + * switches the horizontal and vertical directions. The offset + * is applied horizontally or vertically accordingly. + */ + flip = wpf->flip.active; + + if (flip & BIT(WPF_CTRL_HFLIP) && !wpf->flip.rotate) + offset = format->width - left - width; + else if (flip & BIT(WPF_CTRL_VFLIP) && wpf->flip.rotate) + offset = format->height - left - width; + else + offset = left; + + for (i = 0; i < format->num_planes; ++i) { + unsigned int hsub = i > 0 ? fmtinfo->hsub : 1; + unsigned int vsub = i > 0 ? fmtinfo->vsub : 1; + + if (wpf->flip.rotate) + mem.addr[i] += offset / vsub + * format->plane_fmt[i].bytesperline; + else + mem.addr[i] += offset / hsub + * fmtinfo->bpp[i] / 8; + } + + if (flip & BIT(WPF_CTRL_VFLIP)) { + /* + * When rotating the output (after rotation) image + * height is equal to the partition width (before + * rotation). Otherwise it is equal to the output + * image height. + */ + if (wpf->flip.rotate) + height = width; + else + height = format->height; + + mem.addr[0] += (height - 1) + * format->plane_fmt[0].bytesperline; + + if (format->num_planes > 1) { + offset = (height / fmtinfo->vsub - 1) + * format->plane_fmt[1].bytesperline; + mem.addr[1] += offset; + mem.addr[2] += offset; + } + } + + if (wpf->flip.rotate && !(flip & BIT(WPF_CTRL_HFLIP))) { + unsigned int hoffset = max(0, (int)format->width - 16); + + /* + * Compute the output coordinate. The partition + * horizontal (left) offset becomes a vertical offset. + */ + for (i = 0; i < format->num_planes; ++i) { + unsigned int hsub = i > 0 ? fmtinfo->hsub : 1; + + mem.addr[i] += hoffset / hsub + * fmtinfo->bpp[i] / 8; + } + } + + /* + * On Gen3 hardware the SPUVS bit has no effect on 3-planar + * formats. Swap the U and V planes manually in that case. + */ + if (vsp1->info->gen == 3 && format->num_planes == 3 && + fmtinfo->swap_uv) + swap(mem.addr[1], mem.addr[2]); + + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_Y, mem.addr[0]); + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_C0, mem.addr[1]); + vsp1_wpf_write(wpf, dlb, VI6_WPF_DSTM_ADDR_C1, mem.addr[2]); + + /* + * Writeback operates in single-shot mode and lasts for a single frame, + * reset the writeback flag to false for the next frame. + */ + wpf->writeback = false; +} + +static unsigned int wpf_max_width(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe) +{ + struct vsp1_rwpf *wpf = to_rwpf(&entity->subdev); + + return wpf->flip.rotate ? 256 : wpf->max_width; +} + +static void wpf_partition(struct vsp1_entity *entity, + struct vsp1_pipeline *pipe, + struct vsp1_partition *partition, + unsigned int partition_idx, + struct vsp1_partition_window *window) +{ + partition->wpf = *window; +} + +static const struct vsp1_entity_operations wpf_entity_ops = { + .destroy = vsp1_wpf_destroy, + .configure_stream = wpf_configure_stream, + .configure_frame = wpf_configure_frame, + .configure_partition = wpf_configure_partition, + .max_width = wpf_max_width, + .partition = wpf_partition, +}; + +/* ----------------------------------------------------------------------------- + * Initialization and Cleanup + */ + +struct vsp1_rwpf *vsp1_wpf_create(struct vsp1_device *vsp1, unsigned int index) +{ + struct vsp1_rwpf *wpf; + char name[6]; + int ret; + + wpf = devm_kzalloc(vsp1->dev, sizeof(*wpf), GFP_KERNEL); + if (wpf == NULL) + return ERR_PTR(-ENOMEM); + + if (vsp1->info->gen == 2) { + wpf->max_width = WPF_GEN2_MAX_WIDTH; + wpf->max_height = WPF_GEN2_MAX_HEIGHT; + } else { + wpf->max_width = WPF_GEN3_MAX_WIDTH; + wpf->max_height = WPF_GEN3_MAX_HEIGHT; + } + + wpf->entity.ops = &wpf_entity_ops; + wpf->entity.type = VSP1_ENTITY_WPF; + wpf->entity.index = index; + + sprintf(name, "wpf.%u", index); + ret = vsp1_entity_init(vsp1, &wpf->entity, name, 2, &wpf_ops, + MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER); + if (ret < 0) + return ERR_PTR(ret); + + /* Initialize the display list manager. */ + wpf->dlm = vsp1_dlm_create(vsp1, index, 64); + if (!wpf->dlm) { + ret = -ENOMEM; + goto error; + } + + /* Initialize the control handler. */ + ret = wpf_init_controls(wpf); + if (ret < 0) { + dev_err(vsp1->dev, "wpf%u: failed to initialize controls\n", + index); + goto error; + } + + v4l2_ctrl_handler_setup(&wpf->ctrls); + + return wpf; + +error: + vsp1_entity_destroy(&wpf->entity); + return ERR_PTR(ret); +} diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig new file mode 100644 index 000000000..b41d3960c --- /dev/null +++ b/drivers/media/platform/rockchip/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Rockchip media platform drivers" + +source "drivers/media/platform/rockchip/rga/Kconfig" +source "drivers/media/platform/rockchip/rkisp1/Kconfig" diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile new file mode 100644 index 000000000..4f782b876 --- /dev/null +++ b/drivers/media/platform/rockchip/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += rga/ +obj-y += rkisp1/ diff --git a/drivers/media/platform/rockchip/rga/Kconfig b/drivers/media/platform/rockchip/rga/Kconfig new file mode 100644 index 000000000..727a0f6ea --- /dev/null +++ b/drivers/media/platform/rockchip/rga/Kconfig @@ -0,0 +1,14 @@ +config VIDEO_ROCKCHIP_RGA + tristate "Rockchip Raster 2d Graphic Acceleration Unit" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_ROCKCHIP || COMPILE_TEST + select VIDEOBUF2_DMA_SG + select V4L2_MEM2MEM_DEV + help + This is a v4l2 driver for Rockchip SOC RGA 2d graphics accelerator. + Rockchip RGA is a separate 2D raster graphic acceleration unit. + It accelerates 2D graphics operations, such as point/line drawing, + image scaling, rotation, BitBLT, alpha blending and image blur/sharpness. + + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/rockchip/rga/Makefile b/drivers/media/platform/rockchip/rga/Makefile new file mode 100644 index 000000000..1bbecdc3d --- /dev/null +++ b/drivers/media/platform/rockchip/rga/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +rockchip-rga-objs := rga.o rga-hw.o rga-buf.o + +obj-$(CONFIG_VIDEO_ROCKCHIP_RGA) += rockchip-rga.o diff --git a/drivers/media/platform/rockchip/rga/rga-buf.c b/drivers/media/platform/rockchip/rga/rga-buf.c new file mode 100644 index 000000000..81508ed5a --- /dev/null +++ b/drivers/media/platform/rockchip/rga/rga-buf.c @@ -0,0 +1,148 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017 Fuzhou Rockchip Electronics Co.Ltd + * Author: Jacob Chen + */ + +#include + +#include +#include +#include +#include +#include + +#include "rga-hw.h" +#include "rga.h" + +static int +rga_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct rga_ctx *ctx = vb2_get_drv_priv(vq); + struct rga_frame *f = rga_get_frame(ctx, vq->type); + + if (IS_ERR(f)) + return PTR_ERR(f); + + if (*nplanes) + return sizes[0] < f->size ? -EINVAL : 0; + + sizes[0] = f->size; + *nplanes = 1; + + return 0; +} + +static int rga_buf_prepare(struct vb2_buffer *vb) +{ + struct rga_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct rga_frame *f = rga_get_frame(ctx, vb->vb2_queue->type); + + if (IS_ERR(f)) + return PTR_ERR(f); + + vb2_set_plane_payload(vb, 0, f->size); + + return 0; +} + +static void rga_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rga_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void rga_buf_return_buffers(struct vb2_queue *q, + enum vb2_buffer_state state) +{ + struct rga_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) + break; + v4l2_m2m_buf_done(vbuf, state); + } +} + +static int rga_buf_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct rga_ctx *ctx = vb2_get_drv_priv(q); + struct rockchip_rga *rga = ctx->rga; + int ret; + + ret = pm_runtime_resume_and_get(rga->dev); + if (ret < 0) { + rga_buf_return_buffers(q, VB2_BUF_STATE_QUEUED); + return ret; + } + + return 0; +} + +static void rga_buf_stop_streaming(struct vb2_queue *q) +{ + struct rga_ctx *ctx = vb2_get_drv_priv(q); + struct rockchip_rga *rga = ctx->rga; + + rga_buf_return_buffers(q, VB2_BUF_STATE_ERROR); + pm_runtime_put(rga->dev); +} + +const struct vb2_ops rga_qops = { + .queue_setup = rga_queue_setup, + .buf_prepare = rga_buf_prepare, + .buf_queue = rga_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = rga_buf_start_streaming, + .stop_streaming = rga_buf_stop_streaming, +}; + +/* RGA MMU is a 1-Level MMU, so it can't be used through the IOMMU API. + * We use it more like a scatter-gather list. + */ +void rga_buf_map(struct vb2_buffer *vb) +{ + struct rga_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct rockchip_rga *rga = ctx->rga; + struct sg_table *sgt; + struct scatterlist *sgl; + unsigned int *pages; + unsigned int address, len, i, p; + unsigned int mapped_size = 0; + + if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + pages = rga->src_mmu_pages; + else + pages = rga->dst_mmu_pages; + + /* Create local MMU table for RGA */ + sgt = vb2_plane_cookie(vb, 0); + + for_each_sg(sgt->sgl, sgl, sgt->nents, i) { + len = sg_dma_len(sgl) >> PAGE_SHIFT; + address = sg_phys(sgl); + + for (p = 0; p < len; p++) { + dma_addr_t phys = address + + ((dma_addr_t)p << PAGE_SHIFT); + + pages[mapped_size + p] = phys; + } + + mapped_size += len; + } + + /* sync local MMU table for RGA */ + dma_sync_single_for_device(rga->dev, virt_to_phys(pages), + 8 * PAGE_SIZE, DMA_BIDIRECTIONAL); +} diff --git a/drivers/media/platform/rockchip/rga/rga-hw.c b/drivers/media/platform/rockchip/rga/rga-hw.c new file mode 100644 index 000000000..aaa96f256 --- /dev/null +++ b/drivers/media/platform/rockchip/rga/rga-hw.c @@ -0,0 +1,416 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author: Jacob Chen + */ + +#include + +#include "rga-hw.h" +#include "rga.h" + +enum e_rga_start_pos { + LT = 0, + LB = 1, + RT = 2, + RB = 3, +}; + +struct rga_addr_offset { + unsigned int y_off; + unsigned int u_off; + unsigned int v_off; +}; + +struct rga_corners_addr_offset { + struct rga_addr_offset left_top; + struct rga_addr_offset right_top; + struct rga_addr_offset left_bottom; + struct rga_addr_offset right_bottom; +}; + +static unsigned int rga_get_scaling(unsigned int src, unsigned int dst) +{ + /* + * The rga hw scaling factor is a normalized inverse of the + * scaling factor. + * For example: When source width is 100 and destination width is 200 + * (scaling of 2x), then the hw factor is NC * 100 / 200. + * The normalization factor (NC) is 2^16 = 0x10000. + */ + + return (src > dst) ? ((dst << 16) / src) : ((src << 16) / dst); +} + +static struct rga_corners_addr_offset +rga_get_addr_offset(struct rga_frame *frm, unsigned int x, unsigned int y, + unsigned int w, unsigned int h) +{ + struct rga_corners_addr_offset offsets; + struct rga_addr_offset *lt, *lb, *rt, *rb; + unsigned int x_div = 0, + y_div = 0, uv_stride = 0, pixel_width = 0, uv_factor = 0; + + lt = &offsets.left_top; + lb = &offsets.left_bottom; + rt = &offsets.right_top; + rb = &offsets.right_bottom; + + x_div = frm->fmt->x_div; + y_div = frm->fmt->y_div; + uv_factor = frm->fmt->uv_factor; + uv_stride = frm->stride / x_div; + pixel_width = frm->stride / frm->width; + + lt->y_off = y * frm->stride + x * pixel_width; + lt->u_off = + frm->width * frm->height + (y / y_div) * uv_stride + x / x_div; + lt->v_off = lt->u_off + frm->width * frm->height / uv_factor; + + lb->y_off = lt->y_off + (h - 1) * frm->stride; + lb->u_off = lt->u_off + (h / y_div - 1) * uv_stride; + lb->v_off = lt->v_off + (h / y_div - 1) * uv_stride; + + rt->y_off = lt->y_off + (w - 1) * pixel_width; + rt->u_off = lt->u_off + w / x_div - 1; + rt->v_off = lt->v_off + w / x_div - 1; + + rb->y_off = lb->y_off + (w - 1) * pixel_width; + rb->u_off = lb->u_off + w / x_div - 1; + rb->v_off = lb->v_off + w / x_div - 1; + + return offsets; +} + +static struct rga_addr_offset *rga_lookup_draw_pos(struct + rga_corners_addr_offset + * offsets, u32 rotate_mode, + u32 mirr_mode) +{ + static enum e_rga_start_pos rot_mir_point_matrix[4][4] = { + { + LT, RT, LB, RB, + }, + { + RT, LT, RB, LB, + }, + { + RB, LB, RT, LT, + }, + { + LB, RB, LT, RT, + }, + }; + + if (!offsets) + return NULL; + + switch (rot_mir_point_matrix[rotate_mode][mirr_mode]) { + case LT: + return &offsets->left_top; + case LB: + return &offsets->left_bottom; + case RT: + return &offsets->right_top; + case RB: + return &offsets->right_bottom; + } + + return NULL; +} + +static void rga_cmd_set_src_addr(struct rga_ctx *ctx, void *mmu_pages) +{ + struct rockchip_rga *rga = ctx->rga; + u32 *dest = rga->cmdbuf_virt; + unsigned int reg; + + reg = RGA_MMU_SRC_BASE - RGA_MODE_BASE_REG; + dest[reg >> 2] = virt_to_phys(mmu_pages) >> 4; + + reg = RGA_MMU_CTRL1 - RGA_MODE_BASE_REG; + dest[reg >> 2] |= 0x7; +} + +static void rga_cmd_set_src1_addr(struct rga_ctx *ctx, void *mmu_pages) +{ + struct rockchip_rga *rga = ctx->rga; + u32 *dest = rga->cmdbuf_virt; + unsigned int reg; + + reg = RGA_MMU_SRC1_BASE - RGA_MODE_BASE_REG; + dest[reg >> 2] = virt_to_phys(mmu_pages) >> 4; + + reg = RGA_MMU_CTRL1 - RGA_MODE_BASE_REG; + dest[reg >> 2] |= 0x7 << 4; +} + +static void rga_cmd_set_dst_addr(struct rga_ctx *ctx, void *mmu_pages) +{ + struct rockchip_rga *rga = ctx->rga; + u32 *dest = rga->cmdbuf_virt; + unsigned int reg; + + reg = RGA_MMU_DST_BASE - RGA_MODE_BASE_REG; + dest[reg >> 2] = virt_to_phys(mmu_pages) >> 4; + + reg = RGA_MMU_CTRL1 - RGA_MODE_BASE_REG; + dest[reg >> 2] |= 0x7 << 8; +} + +static void rga_cmd_set_trans_info(struct rga_ctx *ctx) +{ + struct rockchip_rga *rga = ctx->rga; + u32 *dest = rga->cmdbuf_virt; + unsigned int scale_dst_w, scale_dst_h; + unsigned int src_h, src_w, src_x, src_y, dst_h, dst_w, dst_x, dst_y; + union rga_src_info src_info; + union rga_dst_info dst_info; + union rga_src_x_factor x_factor; + union rga_src_y_factor y_factor; + union rga_src_vir_info src_vir_info; + union rga_src_act_info src_act_info; + union rga_dst_vir_info dst_vir_info; + union rga_dst_act_info dst_act_info; + + struct rga_addr_offset *dst_offset; + struct rga_corners_addr_offset offsets; + struct rga_corners_addr_offset src_offsets; + + src_h = ctx->in.crop.height; + src_w = ctx->in.crop.width; + src_x = ctx->in.crop.left; + src_y = ctx->in.crop.top; + dst_h = ctx->out.crop.height; + dst_w = ctx->out.crop.width; + dst_x = ctx->out.crop.left; + dst_y = ctx->out.crop.top; + + src_info.val = dest[(RGA_SRC_INFO - RGA_MODE_BASE_REG) >> 2]; + dst_info.val = dest[(RGA_DST_INFO - RGA_MODE_BASE_REG) >> 2]; + x_factor.val = dest[(RGA_SRC_X_FACTOR - RGA_MODE_BASE_REG) >> 2]; + y_factor.val = dest[(RGA_SRC_Y_FACTOR - RGA_MODE_BASE_REG) >> 2]; + src_vir_info.val = dest[(RGA_SRC_VIR_INFO - RGA_MODE_BASE_REG) >> 2]; + src_act_info.val = dest[(RGA_SRC_ACT_INFO - RGA_MODE_BASE_REG) >> 2]; + dst_vir_info.val = dest[(RGA_DST_VIR_INFO - RGA_MODE_BASE_REG) >> 2]; + dst_act_info.val = dest[(RGA_DST_ACT_INFO - RGA_MODE_BASE_REG) >> 2]; + + src_info.data.format = ctx->in.fmt->hw_format; + src_info.data.swap = ctx->in.fmt->color_swap; + dst_info.data.format = ctx->out.fmt->hw_format; + dst_info.data.swap = ctx->out.fmt->color_swap; + + /* + * CSC mode must only be set when the colorspace families differ between + * input and output. It must remain unset (zeroed) if both are the same. + */ + + if (RGA_COLOR_FMT_IS_YUV(ctx->in.fmt->hw_format) && + RGA_COLOR_FMT_IS_RGB(ctx->out.fmt->hw_format)) { + switch (ctx->in.colorspace) { + case V4L2_COLORSPACE_REC709: + src_info.data.csc_mode = RGA_SRC_CSC_MODE_BT709_R0; + break; + default: + src_info.data.csc_mode = RGA_SRC_CSC_MODE_BT601_R0; + break; + } + } + + if (RGA_COLOR_FMT_IS_RGB(ctx->in.fmt->hw_format) && + RGA_COLOR_FMT_IS_YUV(ctx->out.fmt->hw_format)) { + switch (ctx->out.colorspace) { + case V4L2_COLORSPACE_REC709: + dst_info.data.csc_mode = RGA_SRC_CSC_MODE_BT709_R0; + break; + default: + dst_info.data.csc_mode = RGA_DST_CSC_MODE_BT601_R0; + break; + } + } + + if (ctx->vflip) + src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_X; + + if (ctx->hflip) + src_info.data.mir_mode |= RGA_SRC_MIRR_MODE_Y; + + switch (ctx->rotate) { + case 90: + src_info.data.rot_mode = RGA_SRC_ROT_MODE_90_DEGREE; + break; + case 180: + src_info.data.rot_mode = RGA_SRC_ROT_MODE_180_DEGREE; + break; + case 270: + src_info.data.rot_mode = RGA_SRC_ROT_MODE_270_DEGREE; + break; + default: + src_info.data.rot_mode = RGA_SRC_ROT_MODE_0_DEGREE; + break; + } + + /* + * Calculate the up/down scaling mode/factor. + * + * RGA used to scale the picture first, and then rotate second, + * so we need to swap the w/h when rotate degree is 90/270. + */ + if (src_info.data.rot_mode == RGA_SRC_ROT_MODE_90_DEGREE || + src_info.data.rot_mode == RGA_SRC_ROT_MODE_270_DEGREE) { + if (rga->version.major == 0 || rga->version.minor == 0) { + if (dst_w == src_h) + src_h -= 8; + if (abs(src_w - dst_h) < 16) + src_w -= 16; + } + + scale_dst_h = dst_w; + scale_dst_w = dst_h; + } else { + scale_dst_w = dst_w; + scale_dst_h = dst_h; + } + + if (src_w == scale_dst_w) { + src_info.data.hscl_mode = RGA_SRC_HSCL_MODE_NO; + x_factor.val = 0; + } else if (src_w > scale_dst_w) { + src_info.data.hscl_mode = RGA_SRC_HSCL_MODE_DOWN; + x_factor.data.down_scale_factor = + rga_get_scaling(src_w, scale_dst_w) + 1; + } else { + src_info.data.hscl_mode = RGA_SRC_HSCL_MODE_UP; + x_factor.data.up_scale_factor = + rga_get_scaling(src_w - 1, scale_dst_w - 1); + } + + if (src_h == scale_dst_h) { + src_info.data.vscl_mode = RGA_SRC_VSCL_MODE_NO; + y_factor.val = 0; + } else if (src_h > scale_dst_h) { + src_info.data.vscl_mode = RGA_SRC_VSCL_MODE_DOWN; + y_factor.data.down_scale_factor = + rga_get_scaling(src_h, scale_dst_h) + 1; + } else { + src_info.data.vscl_mode = RGA_SRC_VSCL_MODE_UP; + y_factor.data.up_scale_factor = + rga_get_scaling(src_h - 1, scale_dst_h - 1); + } + + /* + * Calculate the framebuffer virtual strides and active size, + * note that the step of vir_stride / vir_width is 4 byte words + */ + src_vir_info.data.vir_stride = ctx->in.stride >> 2; + src_vir_info.data.vir_width = ctx->in.stride >> 2; + + src_act_info.data.act_height = src_h - 1; + src_act_info.data.act_width = src_w - 1; + + dst_vir_info.data.vir_stride = ctx->out.stride >> 2; + dst_act_info.data.act_height = dst_h - 1; + dst_act_info.data.act_width = dst_w - 1; + + /* + * Calculate the source framebuffer base address with offset pixel. + */ + src_offsets = rga_get_addr_offset(&ctx->in, src_x, src_y, + src_w, src_h); + + /* + * Configure the dest framebuffer base address with pixel offset. + */ + offsets = rga_get_addr_offset(&ctx->out, dst_x, dst_y, dst_w, dst_h); + dst_offset = rga_lookup_draw_pos(&offsets, src_info.data.rot_mode, + src_info.data.mir_mode); + + dest[(RGA_SRC_Y_RGB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] = + src_offsets.left_top.y_off; + dest[(RGA_SRC_CB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] = + src_offsets.left_top.u_off; + dest[(RGA_SRC_CR_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] = + src_offsets.left_top.v_off; + + dest[(RGA_SRC_X_FACTOR - RGA_MODE_BASE_REG) >> 2] = x_factor.val; + dest[(RGA_SRC_Y_FACTOR - RGA_MODE_BASE_REG) >> 2] = y_factor.val; + dest[(RGA_SRC_VIR_INFO - RGA_MODE_BASE_REG) >> 2] = src_vir_info.val; + dest[(RGA_SRC_ACT_INFO - RGA_MODE_BASE_REG) >> 2] = src_act_info.val; + + dest[(RGA_SRC_INFO - RGA_MODE_BASE_REG) >> 2] = src_info.val; + + dest[(RGA_DST_Y_RGB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] = + dst_offset->y_off; + dest[(RGA_DST_CB_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] = + dst_offset->u_off; + dest[(RGA_DST_CR_BASE_ADDR - RGA_MODE_BASE_REG) >> 2] = + dst_offset->v_off; + + dest[(RGA_DST_VIR_INFO - RGA_MODE_BASE_REG) >> 2] = dst_vir_info.val; + dest[(RGA_DST_ACT_INFO - RGA_MODE_BASE_REG) >> 2] = dst_act_info.val; + + dest[(RGA_DST_INFO - RGA_MODE_BASE_REG) >> 2] = dst_info.val; +} + +static void rga_cmd_set_mode(struct rga_ctx *ctx) +{ + struct rockchip_rga *rga = ctx->rga; + u32 *dest = rga->cmdbuf_virt; + union rga_mode_ctrl mode; + union rga_alpha_ctrl0 alpha_ctrl0; + union rga_alpha_ctrl1 alpha_ctrl1; + + mode.val = 0; + alpha_ctrl0.val = 0; + alpha_ctrl1.val = 0; + + mode.data.gradient_sat = 1; + mode.data.render = RGA_MODE_RENDER_BITBLT; + mode.data.bitblt = RGA_MODE_BITBLT_MODE_SRC_TO_DST; + + /* disable alpha blending */ + dest[(RGA_ALPHA_CTRL0 - RGA_MODE_BASE_REG) >> 2] = alpha_ctrl0.val; + dest[(RGA_ALPHA_CTRL1 - RGA_MODE_BASE_REG) >> 2] = alpha_ctrl1.val; + + dest[(RGA_MODE_CTRL - RGA_MODE_BASE_REG) >> 2] = mode.val; +} + +static void rga_cmd_set(struct rga_ctx *ctx) +{ + struct rockchip_rga *rga = ctx->rga; + + memset(rga->cmdbuf_virt, 0, RGA_CMDBUF_SIZE * 4); + + rga_cmd_set_src_addr(ctx, rga->src_mmu_pages); + /* + * Due to hardware bug, + * src1 mmu also should be configured when using alpha blending. + */ + rga_cmd_set_src1_addr(ctx, rga->dst_mmu_pages); + + rga_cmd_set_dst_addr(ctx, rga->dst_mmu_pages); + rga_cmd_set_mode(ctx); + + rga_cmd_set_trans_info(ctx); + + rga_write(rga, RGA_CMD_BASE, rga->cmdbuf_phy); + + /* sync CMD buf for RGA */ + dma_sync_single_for_device(rga->dev, rga->cmdbuf_phy, + PAGE_SIZE, DMA_BIDIRECTIONAL); +} + +void rga_hw_start(struct rockchip_rga *rga) +{ + struct rga_ctx *ctx = rga->curr; + + rga_cmd_set(ctx); + + rga_write(rga, RGA_SYS_CTRL, 0x00); + + rga_write(rga, RGA_SYS_CTRL, 0x22); + + rga_write(rga, RGA_INT, 0x600); + + rga_write(rga, RGA_CMD_CTRL, 0x1); +} diff --git a/drivers/media/platform/rockchip/rga/rga-hw.h b/drivers/media/platform/rockchip/rga/rga-hw.h new file mode 100644 index 000000000..e8917e563 --- /dev/null +++ b/drivers/media/platform/rockchip/rga/rga-hw.h @@ -0,0 +1,434 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author: Jacob Chen + */ +#ifndef __RGA_HW_H__ +#define __RGA_HW_H__ + +#define RGA_CMDBUF_SIZE 0x20 + +/* Hardware limits */ +#define MAX_WIDTH 8192 +#define MAX_HEIGHT 8192 + +#define MIN_WIDTH 34 +#define MIN_HEIGHT 34 + +#define DEFAULT_WIDTH 100 +#define DEFAULT_HEIGHT 100 + +#define RGA_TIMEOUT 500 + +/* Registers address */ +#define RGA_SYS_CTRL 0x0000 +#define RGA_CMD_CTRL 0x0004 +#define RGA_CMD_BASE 0x0008 +#define RGA_INT 0x0010 +#define RGA_MMU_CTRL0 0x0014 +#define RGA_VERSION_INFO 0x0028 + +#define RGA_MODE_BASE_REG 0x0100 +#define RGA_MODE_MAX_REG 0x017C + +#define RGA_MODE_CTRL 0x0100 +#define RGA_SRC_INFO 0x0104 +#define RGA_SRC_Y_RGB_BASE_ADDR 0x0108 +#define RGA_SRC_CB_BASE_ADDR 0x010c +#define RGA_SRC_CR_BASE_ADDR 0x0110 +#define RGA_SRC1_RGB_BASE_ADDR 0x0114 +#define RGA_SRC_VIR_INFO 0x0118 +#define RGA_SRC_ACT_INFO 0x011c +#define RGA_SRC_X_FACTOR 0x0120 +#define RGA_SRC_Y_FACTOR 0x0124 +#define RGA_SRC_BG_COLOR 0x0128 +#define RGA_SRC_FG_COLOR 0x012c +#define RGA_SRC_TR_COLOR0 0x0130 +#define RGA_SRC_TR_COLOR1 0x0134 + +#define RGA_DST_INFO 0x0138 +#define RGA_DST_Y_RGB_BASE_ADDR 0x013c +#define RGA_DST_CB_BASE_ADDR 0x0140 +#define RGA_DST_CR_BASE_ADDR 0x0144 +#define RGA_DST_VIR_INFO 0x0148 +#define RGA_DST_ACT_INFO 0x014c + +#define RGA_ALPHA_CTRL0 0x0150 +#define RGA_ALPHA_CTRL1 0x0154 +#define RGA_FADING_CTRL 0x0158 +#define RGA_PAT_CON 0x015c +#define RGA_ROP_CON0 0x0160 +#define RGA_ROP_CON1 0x0164 +#define RGA_MASK_BASE 0x0168 + +#define RGA_MMU_CTRL1 0x016C +#define RGA_MMU_SRC_BASE 0x0170 +#define RGA_MMU_SRC1_BASE 0x0174 +#define RGA_MMU_DST_BASE 0x0178 + +/* Registers value */ +#define RGA_MODE_RENDER_BITBLT 0 +#define RGA_MODE_RENDER_COLOR_PALETTE 1 +#define RGA_MODE_RENDER_RECTANGLE_FILL 2 +#define RGA_MODE_RENDER_UPDATE_PALETTE_LUT_RAM 3 + +#define RGA_MODE_BITBLT_MODE_SRC_TO_DST 0 +#define RGA_MODE_BITBLT_MODE_SRC_SRC1_TO_DST 1 + +#define RGA_MODE_CF_ROP4_SOLID 0 +#define RGA_MODE_CF_ROP4_PATTERN 1 + +#define RGA_COLOR_FMT_ABGR8888 0 +#define RGA_COLOR_FMT_XBGR8888 1 +#define RGA_COLOR_FMT_RGB888 2 +#define RGA_COLOR_FMT_BGR565 4 +#define RGA_COLOR_FMT_ABGR1555 5 +#define RGA_COLOR_FMT_ABGR4444 6 +#define RGA_COLOR_FMT_YUV422SP 8 +#define RGA_COLOR_FMT_YUV422P 9 +#define RGA_COLOR_FMT_YUV420SP 10 +#define RGA_COLOR_FMT_YUV420P 11 +/* SRC_COLOR Palette */ +#define RGA_COLOR_FMT_CP_1BPP 12 +#define RGA_COLOR_FMT_CP_2BPP 13 +#define RGA_COLOR_FMT_CP_4BPP 14 +#define RGA_COLOR_FMT_CP_8BPP 15 +#define RGA_COLOR_FMT_MASK 15 + +#define RGA_COLOR_FMT_IS_YUV(fmt) \ + (((fmt) >= RGA_COLOR_FMT_YUV422SP) && ((fmt) < RGA_COLOR_FMT_CP_1BPP)) +#define RGA_COLOR_FMT_IS_RGB(fmt) \ + ((fmt) < RGA_COLOR_FMT_YUV422SP) + +#define RGA_COLOR_NONE_SWAP 0 +#define RGA_COLOR_RB_SWAP 1 +#define RGA_COLOR_ALPHA_SWAP 2 +#define RGA_COLOR_UV_SWAP 4 + +#define RGA_SRC_CSC_MODE_BYPASS 0 +#define RGA_SRC_CSC_MODE_BT601_R0 1 +#define RGA_SRC_CSC_MODE_BT601_R1 2 +#define RGA_SRC_CSC_MODE_BT709_R0 3 +#define RGA_SRC_CSC_MODE_BT709_R1 4 + +#define RGA_SRC_ROT_MODE_0_DEGREE 0 +#define RGA_SRC_ROT_MODE_90_DEGREE 1 +#define RGA_SRC_ROT_MODE_180_DEGREE 2 +#define RGA_SRC_ROT_MODE_270_DEGREE 3 + +#define RGA_SRC_MIRR_MODE_NO 0 +#define RGA_SRC_MIRR_MODE_X 1 +#define RGA_SRC_MIRR_MODE_Y 2 +#define RGA_SRC_MIRR_MODE_X_Y 3 + +#define RGA_SRC_HSCL_MODE_NO 0 +#define RGA_SRC_HSCL_MODE_DOWN 1 +#define RGA_SRC_HSCL_MODE_UP 2 + +#define RGA_SRC_VSCL_MODE_NO 0 +#define RGA_SRC_VSCL_MODE_DOWN 1 +#define RGA_SRC_VSCL_MODE_UP 2 + +#define RGA_SRC_TRANS_ENABLE_R 1 +#define RGA_SRC_TRANS_ENABLE_G 2 +#define RGA_SRC_TRANS_ENABLE_B 4 +#define RGA_SRC_TRANS_ENABLE_A 8 + +#define RGA_SRC_BIC_COE_SELEC_CATROM 0 +#define RGA_SRC_BIC_COE_SELEC_MITCHELL 1 +#define RGA_SRC_BIC_COE_SELEC_HERMITE 2 +#define RGA_SRC_BIC_COE_SELEC_BSPLINE 3 + +#define RGA_DST_DITHER_MODE_888_TO_666 0 +#define RGA_DST_DITHER_MODE_888_TO_565 1 +#define RGA_DST_DITHER_MODE_888_TO_555 2 +#define RGA_DST_DITHER_MODE_888_TO_444 3 + +#define RGA_DST_CSC_MODE_BYPASS 0 +#define RGA_DST_CSC_MODE_BT601_R0 1 +#define RGA_DST_CSC_MODE_BT601_R1 2 +#define RGA_DST_CSC_MODE_BT709_R0 3 + +#define RGA_ALPHA_ROP_MODE_2 0 +#define RGA_ALPHA_ROP_MODE_3 1 +#define RGA_ALPHA_ROP_MODE_4 2 + +#define RGA_ALPHA_SELECT_ALPHA 0 +#define RGA_ALPHA_SELECT_ROP 1 + +#define RGA_ALPHA_MASK_BIG_ENDIAN 0 +#define RGA_ALPHA_MASK_LITTLE_ENDIAN 1 + +#define RGA_ALPHA_NORMAL 0 +#define RGA_ALPHA_REVERSE 1 + +#define RGA_ALPHA_BLEND_GLOBAL 0 +#define RGA_ALPHA_BLEND_NORMAL 1 +#define RGA_ALPHA_BLEND_MULTIPLY 2 + +#define RGA_ALPHA_CAL_CUT 0 +#define RGA_ALPHA_CAL_NORMAL 1 + +#define RGA_ALPHA_FACTOR_ZERO 0 +#define RGA_ALPHA_FACTOR_ONE 1 +#define RGA_ALPHA_FACTOR_OTHER 2 +#define RGA_ALPHA_FACTOR_OTHER_REVERSE 3 +#define RGA_ALPHA_FACTOR_SELF 4 + +#define RGA_ALPHA_COLOR_NORMAL 0 +#define RGA_ALPHA_COLOR_MULTIPLY_CAL 1 + +/* Registers union */ +union rga_mode_ctrl { + unsigned int val; + struct { + /* [0:2] */ + unsigned int render:3; + /* [3:6] */ + unsigned int bitblt:1; + unsigned int cf_rop4_pat:1; + unsigned int alpha_zero_key:1; + unsigned int gradient_sat:1; + /* [7:31] */ + unsigned int reserved:25; + } data; +}; + +union rga_src_info { + unsigned int val; + struct { + /* [0:3] */ + unsigned int format:4; + /* [4:7] */ + unsigned int swap:3; + unsigned int cp_endian:1; + /* [8:17] */ + unsigned int csc_mode:2; + unsigned int rot_mode:2; + unsigned int mir_mode:2; + unsigned int hscl_mode:2; + unsigned int vscl_mode:2; + /* [18:22] */ + unsigned int trans_mode:1; + unsigned int trans_enable:4; + /* [23:25] */ + unsigned int dither_up_en:1; + unsigned int bic_coe_sel:2; + /* [26:31] */ + unsigned int reserved:6; + } data; +}; + +union rga_src_vir_info { + unsigned int val; + struct { + /* [0:15] */ + unsigned int vir_width:15; + unsigned int reserved:1; + /* [16:25] */ + unsigned int vir_stride:10; + /* [26:31] */ + unsigned int reserved1:6; + } data; +}; + +union rga_src_act_info { + unsigned int val; + struct { + /* [0:15] */ + unsigned int act_width:13; + unsigned int reserved:3; + /* [16:31] */ + unsigned int act_height:13; + unsigned int reserved1:3; + } data; +}; + +union rga_src_x_factor { + unsigned int val; + struct { + /* [0:15] */ + unsigned int down_scale_factor:16; + /* [16:31] */ + unsigned int up_scale_factor:16; + } data; +}; + +union rga_src_y_factor { + unsigned int val; + struct { + /* [0:15] */ + unsigned int down_scale_factor:16; + /* [16:31] */ + unsigned int up_scale_factor:16; + } data; +}; + +/* Alpha / Red / Green / Blue */ +union rga_src_cp_gr_color { + unsigned int val; + struct { + /* [0:15] */ + unsigned int gradient_x:16; + /* [16:31] */ + unsigned int gradient_y:16; + } data; +}; + +union rga_src_transparency_color0 { + unsigned int val; + struct { + /* [0:7] */ + unsigned int trans_rmin:8; + /* [8:15] */ + unsigned int trans_gmin:8; + /* [16:23] */ + unsigned int trans_bmin:8; + /* [24:31] */ + unsigned int trans_amin:8; + } data; +}; + +union rga_src_transparency_color1 { + unsigned int val; + struct { + /* [0:7] */ + unsigned int trans_rmax:8; + /* [8:15] */ + unsigned int trans_gmax:8; + /* [16:23] */ + unsigned int trans_bmax:8; + /* [24:31] */ + unsigned int trans_amax:8; + } data; +}; + +union rga_dst_info { + unsigned int val; + struct { + /* [0:3] */ + unsigned int format:4; + /* [4:6] */ + unsigned int swap:3; + /* [7:9] */ + unsigned int src1_format:3; + /* [10:11] */ + unsigned int src1_swap:2; + /* [12:15] */ + unsigned int dither_up_en:1; + unsigned int dither_down_en:1; + unsigned int dither_down_mode:2; + /* [16:18] */ + unsigned int csc_mode:2; + unsigned int csc_clip:1; + /* [19:31] */ + unsigned int reserved:13; + } data; +}; + +union rga_dst_vir_info { + unsigned int val; + struct { + /* [0:15] */ + unsigned int vir_stride:15; + unsigned int reserved:1; + /* [16:31] */ + unsigned int src1_vir_stride:15; + unsigned int reserved1:1; + } data; +}; + +union rga_dst_act_info { + unsigned int val; + struct { + /* [0:15] */ + unsigned int act_width:12; + unsigned int reserved:4; + /* [16:31] */ + unsigned int act_height:12; + unsigned int reserved1:4; + } data; +}; + +union rga_alpha_ctrl0 { + unsigned int val; + struct { + /* [0:3] */ + unsigned int rop_en:1; + unsigned int rop_select:1; + unsigned int rop_mode:2; + /* [4:11] */ + unsigned int src_fading_val:8; + /* [12:20] */ + unsigned int dst_fading_val:8; + unsigned int mask_endian:1; + /* [21:31] */ + unsigned int reserved:11; + } data; +}; + +union rga_alpha_ctrl1 { + unsigned int val; + struct { + /* [0:1] */ + unsigned int dst_color_m0:1; + unsigned int src_color_m0:1; + /* [2:7] */ + unsigned int dst_factor_m0:3; + unsigned int src_factor_m0:3; + /* [8:9] */ + unsigned int dst_alpha_cal_m0:1; + unsigned int src_alpha_cal_m0:1; + /* [10:13] */ + unsigned int dst_blend_m0:2; + unsigned int src_blend_m0:2; + /* [14:15] */ + unsigned int dst_alpha_m0:1; + unsigned int src_alpha_m0:1; + /* [16:21] */ + unsigned int dst_factor_m1:3; + unsigned int src_factor_m1:3; + /* [22:23] */ + unsigned int dst_alpha_cal_m1:1; + unsigned int src_alpha_cal_m1:1; + /* [24:27] */ + unsigned int dst_blend_m1:2; + unsigned int src_blend_m1:2; + /* [28:29] */ + unsigned int dst_alpha_m1:1; + unsigned int src_alpha_m1:1; + /* [30:31] */ + unsigned int reserved:2; + } data; +}; + +union rga_fading_ctrl { + unsigned int val; + struct { + /* [0:7] */ + unsigned int fading_offset_r:8; + /* [8:15] */ + unsigned int fading_offset_g:8; + /* [16:23] */ + unsigned int fading_offset_b:8; + /* [24:31] */ + unsigned int fading_en:1; + unsigned int reserved:7; + } data; +}; + +union rga_pat_con { + unsigned int val; + struct { + /* [0:7] */ + unsigned int width:8; + /* [8:15] */ + unsigned int height:8; + /* [16:23] */ + unsigned int offset_x:8; + /* [24:31] */ + unsigned int offset_y:8; + } data; +}; + +#endif diff --git a/drivers/media/platform/rockchip/rga/rga.c b/drivers/media/platform/rockchip/rga/rga.c new file mode 100644 index 000000000..61b25fcf8 --- /dev/null +++ b/drivers/media/platform/rockchip/rga/rga.c @@ -0,0 +1,1001 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author: Jacob Chen + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "rga-hw.h" +#include "rga.h" + +static int debug; +module_param(debug, int, 0644); + +static void device_run(void *prv) +{ + struct rga_ctx *ctx = prv; + struct rockchip_rga *rga = ctx->rga; + struct vb2_v4l2_buffer *src, *dst; + unsigned long flags; + + spin_lock_irqsave(&rga->ctrl_lock, flags); + + rga->curr = ctx; + + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + rga_buf_map(&src->vb2_buf); + rga_buf_map(&dst->vb2_buf); + + rga_hw_start(rga); + + spin_unlock_irqrestore(&rga->ctrl_lock, flags); +} + +static irqreturn_t rga_isr(int irq, void *prv) +{ + struct rockchip_rga *rga = prv; + int intr; + + intr = rga_read(rga, RGA_INT) & 0xf; + + rga_mod(rga, RGA_INT, intr << 4, 0xf << 4); + + if (intr & 0x04) { + struct vb2_v4l2_buffer *src, *dst; + struct rga_ctx *ctx = rga->curr; + + WARN_ON(!ctx); + + rga->curr = NULL; + + src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + WARN_ON(!src); + WARN_ON(!dst); + + dst->timecode = src->timecode; + dst->vb2_buf.timestamp = src->vb2_buf.timestamp; + dst->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst->flags |= src->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + + v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE); + v4l2_m2m_job_finish(rga->m2m_dev, ctx->fh.m2m_ctx); + } + + return IRQ_HANDLED; +} + +static const struct v4l2_m2m_ops rga_m2m_ops = { + .device_run = device_run, +}; + +static int +queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) +{ + struct rga_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->ops = &rga_qops; + src_vq->mem_ops = &vb2_dma_sg_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->rga->mutex; + src_vq->dev = ctx->rga->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->ops = &rga_qops; + dst_vq->mem_ops = &vb2_dma_sg_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->rga->mutex; + dst_vq->dev = ctx->rga->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +static int rga_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct rga_ctx *ctx = container_of(ctrl->handler, struct rga_ctx, + ctrl_handler); + unsigned long flags; + + spin_lock_irqsave(&ctx->rga->ctrl_lock, flags); + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->hflip = ctrl->val; + break; + case V4L2_CID_VFLIP: + ctx->vflip = ctrl->val; + break; + case V4L2_CID_ROTATE: + ctx->rotate = ctrl->val; + break; + case V4L2_CID_BG_COLOR: + ctx->fill_color = ctrl->val; + break; + } + spin_unlock_irqrestore(&ctx->rga->ctrl_lock, flags); + return 0; +} + +static const struct v4l2_ctrl_ops rga_ctrl_ops = { + .s_ctrl = rga_s_ctrl, +}; + +static int rga_setup_ctrls(struct rga_ctx *ctx) +{ + struct rockchip_rga *rga = ctx->rga; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 4); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops, + V4L2_CID_ROTATE, 0, 270, 90, 0); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rga_ctrl_ops, + V4L2_CID_BG_COLOR, 0, 0xffffffff, 1, 0); + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + + v4l2_err(&rga->v4l2_dev, "%s failed\n", __func__); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; + } + + return 0; +} + +static struct rga_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_ARGB32, + .color_swap = RGA_COLOR_RB_SWAP, + .hw_format = RGA_COLOR_FMT_ABGR8888, + .depth = 32, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_XRGB32, + .color_swap = RGA_COLOR_RB_SWAP, + .hw_format = RGA_COLOR_FMT_XBGR8888, + .depth = 32, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_ABGR32, + .color_swap = RGA_COLOR_ALPHA_SWAP, + .hw_format = RGA_COLOR_FMT_ABGR8888, + .depth = 32, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_XBGR32, + .color_swap = RGA_COLOR_ALPHA_SWAP, + .hw_format = RGA_COLOR_FMT_XBGR8888, + .depth = 32, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_RGB24, + .color_swap = RGA_COLOR_NONE_SWAP, + .hw_format = RGA_COLOR_FMT_RGB888, + .depth = 24, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_BGR24, + .color_swap = RGA_COLOR_RB_SWAP, + .hw_format = RGA_COLOR_FMT_RGB888, + .depth = 24, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_ARGB444, + .color_swap = RGA_COLOR_RB_SWAP, + .hw_format = RGA_COLOR_FMT_ABGR4444, + .depth = 16, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_ARGB555, + .color_swap = RGA_COLOR_RB_SWAP, + .hw_format = RGA_COLOR_FMT_ABGR1555, + .depth = 16, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .color_swap = RGA_COLOR_RB_SWAP, + .hw_format = RGA_COLOR_FMT_BGR565, + .depth = 16, + .uv_factor = 1, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_NV21, + .color_swap = RGA_COLOR_UV_SWAP, + .hw_format = RGA_COLOR_FMT_YUV420SP, + .depth = 12, + .uv_factor = 4, + .y_div = 2, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_NV61, + .color_swap = RGA_COLOR_UV_SWAP, + .hw_format = RGA_COLOR_FMT_YUV422SP, + .depth = 16, + .uv_factor = 2, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_NV12, + .color_swap = RGA_COLOR_NONE_SWAP, + .hw_format = RGA_COLOR_FMT_YUV420SP, + .depth = 12, + .uv_factor = 4, + .y_div = 2, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_NV16, + .color_swap = RGA_COLOR_NONE_SWAP, + .hw_format = RGA_COLOR_FMT_YUV422SP, + .depth = 16, + .uv_factor = 2, + .y_div = 1, + .x_div = 1, + }, + { + .fourcc = V4L2_PIX_FMT_YUV420, + .color_swap = RGA_COLOR_NONE_SWAP, + .hw_format = RGA_COLOR_FMT_YUV420P, + .depth = 12, + .uv_factor = 4, + .y_div = 2, + .x_div = 2, + }, + { + .fourcc = V4L2_PIX_FMT_YUV422P, + .color_swap = RGA_COLOR_NONE_SWAP, + .hw_format = RGA_COLOR_FMT_YUV422P, + .depth = 16, + .uv_factor = 2, + .y_div = 1, + .x_div = 2, + }, + { + .fourcc = V4L2_PIX_FMT_YVU420, + .color_swap = RGA_COLOR_UV_SWAP, + .hw_format = RGA_COLOR_FMT_YUV420P, + .depth = 12, + .uv_factor = 4, + .y_div = 2, + .x_div = 2, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static struct rga_fmt *rga_fmt_find(struct v4l2_format *f) +{ + unsigned int i; + + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].fourcc == f->fmt.pix.pixelformat) + return &formats[i]; + } + return NULL; +} + +static struct rga_frame def_frame = { + .width = DEFAULT_WIDTH, + .height = DEFAULT_HEIGHT, + .colorspace = V4L2_COLORSPACE_DEFAULT, + .crop.left = 0, + .crop.top = 0, + .crop.width = DEFAULT_WIDTH, + .crop.height = DEFAULT_HEIGHT, + .fmt = &formats[0], +}; + +struct rga_frame *rga_get_frame(struct rga_ctx *ctx, enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &ctx->in; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &ctx->out; + default: + return ERR_PTR(-EINVAL); + } +} + +static int rga_open(struct file *file) +{ + struct rockchip_rga *rga = video_drvdata(file); + struct rga_ctx *ctx = NULL; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + ctx->rga = rga; + /* Set default formats */ + ctx->in = def_frame; + ctx->out = def_frame; + + if (mutex_lock_interruptible(&rga->mutex)) { + kfree(ctx); + return -ERESTARTSYS; + } + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(rga->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + mutex_unlock(&rga->mutex); + kfree(ctx); + return ret; + } + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + rga_setup_ctrls(ctx); + + /* Write the default values to the ctx struct */ + v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + mutex_unlock(&rga->mutex); + + return 0; +} + +static int rga_release(struct file *file) +{ + struct rga_ctx *ctx = + container_of(file->private_data, struct rga_ctx, fh); + struct rockchip_rga *rga = ctx->rga; + + mutex_lock(&rga->mutex); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + + mutex_unlock(&rga->mutex); + + return 0; +} + +static const struct v4l2_file_operations rga_fops = { + .owner = THIS_MODULE, + .open = rga_open, + .release = rga_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int +vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *cap) +{ + strscpy(cap->driver, RGA_NAME, sizeof(cap->driver)); + strscpy(cap->card, "rockchip-rga", sizeof(cap->card)); + strscpy(cap->bus_info, "platform:rga", sizeof(cap->bus_info)); + + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *prv, struct v4l2_fmtdesc *f) +{ + struct rga_fmt *fmt; + + if (f->index >= NUM_FORMATS) + return -EINVAL; + + fmt = &formats[f->index]; + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int vidioc_g_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct rga_ctx *ctx = prv; + struct vb2_queue *vq; + struct rga_frame *frm; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + frm = rga_get_frame(ctx, f->type); + if (IS_ERR(frm)) + return PTR_ERR(frm); + + f->fmt.pix.width = frm->width; + f->fmt.pix.height = frm->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = frm->fmt->fourcc; + f->fmt.pix.bytesperline = frm->stride; + f->fmt.pix.sizeimage = frm->size; + f->fmt.pix.colorspace = frm->colorspace; + + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct rga_fmt *fmt; + + fmt = rga_fmt_find(f); + if (!fmt) { + fmt = &formats[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + } + + f->fmt.pix.field = V4L2_FIELD_NONE; + + if (f->fmt.pix.width > MAX_WIDTH) + f->fmt.pix.width = MAX_WIDTH; + if (f->fmt.pix.height > MAX_HEIGHT) + f->fmt.pix.height = MAX_HEIGHT; + + if (f->fmt.pix.width < MIN_WIDTH) + f->fmt.pix.width = MIN_WIDTH; + if (f->fmt.pix.height < MIN_HEIGHT) + f->fmt.pix.height = MIN_HEIGHT; + + if (fmt->hw_format >= RGA_COLOR_FMT_YUV422SP) + f->fmt.pix.bytesperline = f->fmt.pix.width; + else + f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + + f->fmt.pix.sizeimage = + f->fmt.pix.height * (f->fmt.pix.width * fmt->depth) >> 3; + + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct rga_ctx *ctx = prv; + struct rockchip_rga *rga = ctx->rga; + struct vb2_queue *vq; + struct rga_frame *frm; + struct rga_fmt *fmt; + int ret = 0; + + /* Adjust all values accordingly to the hardware capabilities + * and chosen format. + */ + ret = vidioc_try_fmt(file, prv, f); + if (ret) + return ret; + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) { + v4l2_err(&rga->v4l2_dev, "queue (%d) bust\n", f->type); + return -EBUSY; + } + frm = rga_get_frame(ctx, f->type); + if (IS_ERR(frm)) + return PTR_ERR(frm); + fmt = rga_fmt_find(f); + if (!fmt) + return -EINVAL; + frm->width = f->fmt.pix.width; + frm->height = f->fmt.pix.height; + frm->size = f->fmt.pix.sizeimage; + frm->fmt = fmt; + frm->stride = f->fmt.pix.bytesperline; + frm->colorspace = f->fmt.pix.colorspace; + + /* Reset crop settings */ + frm->crop.left = 0; + frm->crop.top = 0; + frm->crop.width = frm->width; + frm->crop.height = frm->height; + + return 0; +} + +static int vidioc_g_selection(struct file *file, void *prv, + struct v4l2_selection *s) +{ + struct rga_ctx *ctx = prv; + struct rga_frame *f; + bool use_frame = false; + + f = rga_get_frame(ctx, s->type); + if (IS_ERR(f)) + return PTR_ERR(f); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + break; + case V4L2_SEL_TGT_COMPOSE: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + use_frame = true; + break; + case V4L2_SEL_TGT_CROP: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + use_frame = true; + break; + default: + return -EINVAL; + } + + if (use_frame) { + s->r = f->crop; + } else { + s->r.left = 0; + s->r.top = 0; + s->r.width = f->width; + s->r.height = f->height; + } + + return 0; +} + +static int vidioc_s_selection(struct file *file, void *prv, + struct v4l2_selection *s) +{ + struct rga_ctx *ctx = prv; + struct rockchip_rga *rga = ctx->rga; + struct rga_frame *f; + int ret = 0; + + f = rga_get_frame(ctx, s->type); + if (IS_ERR(f)) + return PTR_ERR(f); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + /* + * COMPOSE target is only valid for capture buffer type, return + * error for output buffer type + */ + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + break; + case V4L2_SEL_TGT_CROP: + /* + * CROP target is only valid for output buffer type, return + * error for capture buffer type + */ + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + break; + /* + * bound and default crop/compose targets are invalid targets to + * try/set + */ + default: + return -EINVAL; + } + + if (s->r.top < 0 || s->r.left < 0) { + v4l2_dbg(debug, 1, &rga->v4l2_dev, + "doesn't support negative values for top & left.\n"); + return -EINVAL; + } + + if (s->r.left + s->r.width > f->width || + s->r.top + s->r.height > f->height || + s->r.width < MIN_WIDTH || s->r.height < MIN_HEIGHT) { + v4l2_dbg(debug, 1, &rga->v4l2_dev, "unsupported crop value.\n"); + return -EINVAL; + } + + f->crop = s->r; + + return ret; +} + +static const struct v4l2_ioctl_ops rga_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt, + .vidioc_g_fmt_vid_out = vidioc_g_fmt, + .vidioc_try_fmt_vid_out = vidioc_try_fmt, + .vidioc_s_fmt_vid_out = vidioc_s_fmt, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_g_selection = vidioc_g_selection, + .vidioc_s_selection = vidioc_s_selection, +}; + +static const struct video_device rga_videodev = { + .name = "rockchip-rga", + .fops = &rga_fops, + .ioctl_ops = &rga_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, +}; + +static int rga_enable_clocks(struct rockchip_rga *rga) +{ + int ret; + + ret = clk_prepare_enable(rga->sclk); + if (ret) { + dev_err(rga->dev, "Cannot enable rga sclk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(rga->aclk); + if (ret) { + dev_err(rga->dev, "Cannot enable rga aclk: %d\n", ret); + goto err_disable_sclk; + } + + ret = clk_prepare_enable(rga->hclk); + if (ret) { + dev_err(rga->dev, "Cannot enable rga hclk: %d\n", ret); + goto err_disable_aclk; + } + + return 0; + +err_disable_sclk: + clk_disable_unprepare(rga->sclk); +err_disable_aclk: + clk_disable_unprepare(rga->aclk); + + return ret; +} + +static void rga_disable_clocks(struct rockchip_rga *rga) +{ + clk_disable_unprepare(rga->sclk); + clk_disable_unprepare(rga->hclk); + clk_disable_unprepare(rga->aclk); +} + +static int rga_parse_dt(struct rockchip_rga *rga) +{ + struct reset_control *core_rst, *axi_rst, *ahb_rst; + + core_rst = devm_reset_control_get(rga->dev, "core"); + if (IS_ERR(core_rst)) { + dev_err(rga->dev, "failed to get core reset controller\n"); + return PTR_ERR(core_rst); + } + + axi_rst = devm_reset_control_get(rga->dev, "axi"); + if (IS_ERR(axi_rst)) { + dev_err(rga->dev, "failed to get axi reset controller\n"); + return PTR_ERR(axi_rst); + } + + ahb_rst = devm_reset_control_get(rga->dev, "ahb"); + if (IS_ERR(ahb_rst)) { + dev_err(rga->dev, "failed to get ahb reset controller\n"); + return PTR_ERR(ahb_rst); + } + + reset_control_assert(core_rst); + udelay(1); + reset_control_deassert(core_rst); + + reset_control_assert(axi_rst); + udelay(1); + reset_control_deassert(axi_rst); + + reset_control_assert(ahb_rst); + udelay(1); + reset_control_deassert(ahb_rst); + + rga->sclk = devm_clk_get(rga->dev, "sclk"); + if (IS_ERR(rga->sclk)) { + dev_err(rga->dev, "failed to get sclk clock\n"); + return PTR_ERR(rga->sclk); + } + + rga->aclk = devm_clk_get(rga->dev, "aclk"); + if (IS_ERR(rga->aclk)) { + dev_err(rga->dev, "failed to get aclk clock\n"); + return PTR_ERR(rga->aclk); + } + + rga->hclk = devm_clk_get(rga->dev, "hclk"); + if (IS_ERR(rga->hclk)) { + dev_err(rga->dev, "failed to get hclk clock\n"); + return PTR_ERR(rga->hclk); + } + + return 0; +} + +static int rga_probe(struct platform_device *pdev) +{ + struct rockchip_rga *rga; + struct video_device *vfd; + int ret = 0; + int irq; + + if (!pdev->dev.of_node) + return -ENODEV; + + rga = devm_kzalloc(&pdev->dev, sizeof(*rga), GFP_KERNEL); + if (!rga) + return -ENOMEM; + + rga->dev = &pdev->dev; + spin_lock_init(&rga->ctrl_lock); + mutex_init(&rga->mutex); + + ret = rga_parse_dt(rga); + if (ret) + return dev_err_probe(&pdev->dev, ret, "Unable to parse OF data\n"); + + pm_runtime_enable(rga->dev); + + rga->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rga->regs)) { + ret = PTR_ERR(rga->regs); + goto err_put_clk; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_put_clk; + } + + ret = devm_request_irq(rga->dev, irq, rga_isr, 0, + dev_name(rga->dev), rga); + if (ret < 0) { + dev_err(rga->dev, "failed to request irq\n"); + goto err_put_clk; + } + + ret = v4l2_device_register(&pdev->dev, &rga->v4l2_dev); + if (ret) + goto err_put_clk; + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&rga->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_v4l2_dev; + } + *vfd = rga_videodev; + vfd->lock = &rga->mutex; + vfd->v4l2_dev = &rga->v4l2_dev; + + video_set_drvdata(vfd, rga); + rga->vfd = vfd; + + platform_set_drvdata(pdev, rga); + rga->m2m_dev = v4l2_m2m_init(&rga_m2m_ops); + if (IS_ERR(rga->m2m_dev)) { + v4l2_err(&rga->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(rga->m2m_dev); + goto rel_vdev; + } + + ret = pm_runtime_resume_and_get(rga->dev); + if (ret < 0) + goto rel_m2m; + + rga->version.major = (rga_read(rga, RGA_VERSION_INFO) >> 24) & 0xFF; + rga->version.minor = (rga_read(rga, RGA_VERSION_INFO) >> 20) & 0x0F; + + v4l2_info(&rga->v4l2_dev, "HW Version: 0x%02x.%02x\n", + rga->version.major, rga->version.minor); + + pm_runtime_put(rga->dev); + + /* Create CMD buffer */ + rga->cmdbuf_virt = dma_alloc_attrs(rga->dev, RGA_CMDBUF_SIZE, + &rga->cmdbuf_phy, GFP_KERNEL, + DMA_ATTR_WRITE_COMBINE); + if (!rga->cmdbuf_virt) { + ret = -ENOMEM; + goto rel_m2m; + } + + rga->src_mmu_pages = + (unsigned int *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 3); + if (!rga->src_mmu_pages) { + ret = -ENOMEM; + goto free_dma; + } + rga->dst_mmu_pages = + (unsigned int *)__get_free_pages(GFP_KERNEL | __GFP_ZERO, 3); + if (!rga->dst_mmu_pages) { + ret = -ENOMEM; + goto free_src_pages; + } + + def_frame.stride = (def_frame.width * def_frame.fmt->depth) >> 3; + def_frame.size = def_frame.stride * def_frame.height; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&rga->v4l2_dev, "Failed to register video device\n"); + goto free_dst_pages; + } + + v4l2_info(&rga->v4l2_dev, "Registered %s as /dev/%s\n", + vfd->name, video_device_node_name(vfd)); + + return 0; + +free_dst_pages: + free_pages((unsigned long)rga->dst_mmu_pages, 3); +free_src_pages: + free_pages((unsigned long)rga->src_mmu_pages, 3); +free_dma: + dma_free_attrs(rga->dev, RGA_CMDBUF_SIZE, rga->cmdbuf_virt, + rga->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE); +rel_m2m: + v4l2_m2m_release(rga->m2m_dev); +rel_vdev: + video_device_release(vfd); +unreg_v4l2_dev: + v4l2_device_unregister(&rga->v4l2_dev); +err_put_clk: + pm_runtime_disable(rga->dev); + + return ret; +} + +static int rga_remove(struct platform_device *pdev) +{ + struct rockchip_rga *rga = platform_get_drvdata(pdev); + + dma_free_attrs(rga->dev, RGA_CMDBUF_SIZE, rga->cmdbuf_virt, + rga->cmdbuf_phy, DMA_ATTR_WRITE_COMBINE); + + free_pages((unsigned long)rga->src_mmu_pages, 3); + free_pages((unsigned long)rga->dst_mmu_pages, 3); + + v4l2_info(&rga->v4l2_dev, "Removing\n"); + + v4l2_m2m_release(rga->m2m_dev); + video_unregister_device(rga->vfd); + v4l2_device_unregister(&rga->v4l2_dev); + + pm_runtime_disable(rga->dev); + + return 0; +} + +static int __maybe_unused rga_runtime_suspend(struct device *dev) +{ + struct rockchip_rga *rga = dev_get_drvdata(dev); + + rga_disable_clocks(rga); + + return 0; +} + +static int __maybe_unused rga_runtime_resume(struct device *dev) +{ + struct rockchip_rga *rga = dev_get_drvdata(dev); + + return rga_enable_clocks(rga); +} + +static const struct dev_pm_ops rga_pm = { + SET_RUNTIME_PM_OPS(rga_runtime_suspend, + rga_runtime_resume, NULL) +}; + +static const struct of_device_id rockchip_rga_match[] = { + { + .compatible = "rockchip,rk3288-rga", + }, + { + .compatible = "rockchip,rk3399-rga", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, rockchip_rga_match); + +static struct platform_driver rga_pdrv = { + .probe = rga_probe, + .remove = rga_remove, + .driver = { + .name = RGA_NAME, + .pm = &rga_pm, + .of_match_table = rockchip_rga_match, + }, +}; + +module_platform_driver(rga_pdrv); + +MODULE_AUTHOR("Jacob Chen "); +MODULE_DESCRIPTION("Rockchip Raster 2d Graphic Acceleration Unit"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/rockchip/rga/rga.h b/drivers/media/platform/rockchip/rga/rga.h new file mode 100644 index 000000000..5fa9d2f36 --- /dev/null +++ b/drivers/media/platform/rockchip/rga/rga.h @@ -0,0 +1,115 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author: Jacob Chen + */ +#ifndef __RGA_H__ +#define __RGA_H__ + +#include +#include +#include +#include + +#define RGA_NAME "rockchip-rga" + +struct rga_fmt { + u32 fourcc; + int depth; + u8 uv_factor; + u8 y_div; + u8 x_div; + u8 color_swap; + u8 hw_format; +}; + +struct rga_frame { + /* Original dimensions */ + u32 width; + u32 height; + u32 colorspace; + + /* Crop */ + struct v4l2_rect crop; + + /* Image format */ + struct rga_fmt *fmt; + + /* Variables that can calculated once and reused */ + u32 stride; + u32 size; +}; + +struct rockchip_rga_version { + u32 major; + u32 minor; +}; + +struct rga_ctx { + struct v4l2_fh fh; + struct rockchip_rga *rga; + struct rga_frame in; + struct rga_frame out; + struct v4l2_ctrl_handler ctrl_handler; + + /* Control values */ + u32 op; + u32 hflip; + u32 vflip; + u32 rotate; + u32 fill_color; +}; + +struct rockchip_rga { + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct video_device *vfd; + + struct device *dev; + struct regmap *grf; + void __iomem *regs; + struct clk *sclk; + struct clk *aclk; + struct clk *hclk; + struct rockchip_rga_version version; + + /* vfd lock */ + struct mutex mutex; + /* ctrl parm lock */ + spinlock_t ctrl_lock; + + struct rga_ctx *curr; + dma_addr_t cmdbuf_phy; + void *cmdbuf_virt; + unsigned int *src_mmu_pages; + unsigned int *dst_mmu_pages; +}; + +struct rga_frame *rga_get_frame(struct rga_ctx *ctx, enum v4l2_buf_type type); + +/* RGA Buffers Manage */ +extern const struct vb2_ops rga_qops; +void rga_buf_map(struct vb2_buffer *vb); + +/* RGA Hardware */ +static inline void rga_write(struct rockchip_rga *rga, u32 reg, u32 value) +{ + writel(value, rga->regs + reg); +}; + +static inline u32 rga_read(struct rockchip_rga *rga, u32 reg) +{ + return readl(rga->regs + reg); +}; + +static inline void rga_mod(struct rockchip_rga *rga, u32 reg, u32 val, u32 mask) +{ + u32 temp = rga_read(rga, reg) & ~(mask); + + temp |= val & mask; + rga_write(rga, reg, temp); +}; + +void rga_hw_start(struct rockchip_rga *rga); + +#endif diff --git a/drivers/media/platform/rockchip/rkisp1/Kconfig b/drivers/media/platform/rockchip/rkisp1/Kconfig new file mode 100644 index 000000000..731c9acbf --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/Kconfig @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_ROCKCHIP_ISP1 + tristate "Rockchip Image Signal Processing v1 Unit driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + depends on ARCH_ROCKCHIP || ARCH_MXC || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC + select V4L2_FWNODE + select GENERIC_PHY_MIPI_DPHY + default n + help + Enable this to support the Image Signal Processing (ISP) module + present in RK3399 SoCs. + + To compile this driver as a module, choose M here: the module + will be called rockchip-isp1. diff --git a/drivers/media/platform/rockchip/rkisp1/Makefile b/drivers/media/platform/rockchip/rkisp1/Makefile new file mode 100644 index 000000000..b3844c4f7 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +rockchip-isp1-y := rkisp1-capture.o \ + rkisp1-common.o \ + rkisp1-csi.o \ + rkisp1-dev.o \ + rkisp1-isp.o \ + rkisp1-resizer.o \ + rkisp1-stats.o \ + rkisp1-params.o + +rockchip-isp1-$(CONFIG_DEBUG_FS) += rkisp1-debug.o + +obj-$(CONFIG_VIDEO_ROCKCHIP_ISP1) += rockchip-isp1.o diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c new file mode 100644 index 000000000..d4540684e --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-capture.c @@ -0,0 +1,1455 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - V4l capture device + * + * Copyright (C) 2019 Collabora, Ltd. + * + * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd. + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rkisp1-common.h" + +/* + * NOTE: There are two capture video devices in rkisp1, selfpath and mainpath. + * + * differences between selfpath and mainpath + * available mp sink input: isp + * available sp sink input : isp, dma(TODO) + * available mp sink pad fmts: yuv422, raw + * available sp sink pad fmts: yuv422, yuv420...... + * available mp source fmts: yuv, raw, jpeg(TODO) + * available sp source fmts: yuv, rgb + */ + +#define RKISP1_SP_DEV_NAME RKISP1_DRIVER_NAME "_selfpath" +#define RKISP1_MP_DEV_NAME RKISP1_DRIVER_NAME "_mainpath" + +#define RKISP1_MIN_BUFFERS_NEEDED 3 + +enum rkisp1_plane { + RKISP1_PLANE_Y = 0, + RKISP1_PLANE_CB = 1, + RKISP1_PLANE_CR = 2 +}; + +/* + * @fourcc: pixel format + * @fmt_type: helper filed for pixel format + * @uv_swap: if cb cr swapped, for yuv + * @write_format: defines how YCbCr self picture data is written to memory + * @output_format: defines sp output format + * @mbus: the mbus code on the src resizer pad that matches the pixel format + */ +struct rkisp1_capture_fmt_cfg { + u32 fourcc; + u8 uv_swap; + u32 write_format; + u32 output_format; + u32 mbus; +}; + +struct rkisp1_capture_ops { + void (*config)(struct rkisp1_capture *cap); + void (*stop)(struct rkisp1_capture *cap); + void (*enable)(struct rkisp1_capture *cap); + void (*disable)(struct rkisp1_capture *cap); + void (*set_data_path)(struct rkisp1_capture *cap); + bool (*is_stopped)(struct rkisp1_capture *cap); +}; + +struct rkisp1_capture_config { + const struct rkisp1_capture_fmt_cfg *fmts; + int fmt_size; + struct { + u32 y_size_init; + u32 cb_size_init; + u32 cr_size_init; + u32 y_base_ad_init; + u32 cb_base_ad_init; + u32 cr_base_ad_init; + u32 y_offs_cnt_init; + u32 cb_offs_cnt_init; + u32 cr_offs_cnt_init; + } mi; +}; + +/* + * The supported pixel formats for mainpath. NOTE, pixel formats with identical 'mbus' + * are grouped together. This is assumed and used by the function rkisp1_cap_enum_mbus_codes + */ +static const struct rkisp1_capture_fmt_cfg rkisp1_mp_fmts[] = { + /* yuv422 */ + { + .fourcc = V4L2_PIX_FMT_YUYV, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUVINT, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_SPLA, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_SPLA, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_YVU422M, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, + /* yuv400 */ + { + .fourcc = V4L2_PIX_FMT_GREY, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, + /* yuv420 */ + { + .fourcc = V4L2_PIX_FMT_NV21, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_SPLA, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_SPLA, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_NV21M, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_SPLA, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_NV12M, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_SPLA, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_YVU420, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, + /* raw */ + { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_SRGGB8_1X8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_SGRBG8_1X8, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_SGBRG8_1X8, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .write_format = RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8, + .mbus = MEDIA_BUS_FMT_SBGGR8_1X8, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .write_format = RKISP1_MI_CTRL_MP_WRITE_RAW12, + .mbus = MEDIA_BUS_FMT_SRGGB10_1X10, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .write_format = RKISP1_MI_CTRL_MP_WRITE_RAW12, + .mbus = MEDIA_BUS_FMT_SGRBG10_1X10, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .write_format = RKISP1_MI_CTRL_MP_WRITE_RAW12, + .mbus = MEDIA_BUS_FMT_SGBRG10_1X10, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .write_format = RKISP1_MI_CTRL_MP_WRITE_RAW12, + .mbus = MEDIA_BUS_FMT_SBGGR10_1X10, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .write_format = RKISP1_MI_CTRL_MP_WRITE_RAW12, + .mbus = MEDIA_BUS_FMT_SRGGB12_1X12, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .write_format = RKISP1_MI_CTRL_MP_WRITE_RAW12, + .mbus = MEDIA_BUS_FMT_SGRBG12_1X12, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .write_format = RKISP1_MI_CTRL_MP_WRITE_RAW12, + .mbus = MEDIA_BUS_FMT_SGBRG12_1X12, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .write_format = RKISP1_MI_CTRL_MP_WRITE_RAW12, + .mbus = MEDIA_BUS_FMT_SBGGR12_1X12, + }, +}; + +/* + * The supported pixel formats for selfpath. NOTE, pixel formats with identical 'mbus' + * are grouped together. This is assumed and used by the function rkisp1_cap_enum_mbus_codes + */ +static const struct rkisp1_capture_fmt_cfg rkisp1_sp_fmts[] = { + /* yuv422 */ + { + .fourcc = V4L2_PIX_FMT_YUYV, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_SP_WRITE_INT, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV422, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_SP_WRITE_PLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV422, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_SP_WRITE_SPLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV422, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_SP_WRITE_SPLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV422, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_YVU422M, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_SP_WRITE_PLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV422, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, + /* yuv400 */ + { + .fourcc = V4L2_PIX_FMT_GREY, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_SP_WRITE_PLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV422, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, + /* rgb */ + { + .fourcc = V4L2_PIX_FMT_XBGR32, + .write_format = RKISP1_MI_CTRL_SP_WRITE_PLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_RGB888, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .write_format = RKISP1_MI_CTRL_SP_WRITE_PLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_RGB565, + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + }, + /* yuv420 */ + { + .fourcc = V4L2_PIX_FMT_NV21, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_SP_WRITE_SPLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV420, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_SP_WRITE_SPLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV420, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_NV21M, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_SP_WRITE_SPLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV420, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_NV12M, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_SP_WRITE_SPLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV420, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + .uv_swap = 0, + .write_format = RKISP1_MI_CTRL_SP_WRITE_PLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV420, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, { + .fourcc = V4L2_PIX_FMT_YVU420, + .uv_swap = 1, + .write_format = RKISP1_MI_CTRL_SP_WRITE_PLA, + .output_format = RKISP1_MI_CTRL_SP_OUTPUT_YUV420, + .mbus = MEDIA_BUS_FMT_YUYV8_1_5X8, + }, +}; + +static const struct rkisp1_capture_config rkisp1_capture_config_mp = { + .fmts = rkisp1_mp_fmts, + .fmt_size = ARRAY_SIZE(rkisp1_mp_fmts), + .mi = { + .y_size_init = RKISP1_CIF_MI_MP_Y_SIZE_INIT, + .cb_size_init = RKISP1_CIF_MI_MP_CB_SIZE_INIT, + .cr_size_init = RKISP1_CIF_MI_MP_CR_SIZE_INIT, + .y_base_ad_init = RKISP1_CIF_MI_MP_Y_BASE_AD_INIT, + .cb_base_ad_init = RKISP1_CIF_MI_MP_CB_BASE_AD_INIT, + .cr_base_ad_init = RKISP1_CIF_MI_MP_CR_BASE_AD_INIT, + .y_offs_cnt_init = RKISP1_CIF_MI_MP_Y_OFFS_CNT_INIT, + .cb_offs_cnt_init = RKISP1_CIF_MI_MP_CB_OFFS_CNT_INIT, + .cr_offs_cnt_init = RKISP1_CIF_MI_MP_CR_OFFS_CNT_INIT, + }, +}; + +static const struct rkisp1_capture_config rkisp1_capture_config_sp = { + .fmts = rkisp1_sp_fmts, + .fmt_size = ARRAY_SIZE(rkisp1_sp_fmts), + .mi = { + .y_size_init = RKISP1_CIF_MI_SP_Y_SIZE_INIT, + .cb_size_init = RKISP1_CIF_MI_SP_CB_SIZE_INIT, + .cr_size_init = RKISP1_CIF_MI_SP_CR_SIZE_INIT, + .y_base_ad_init = RKISP1_CIF_MI_SP_Y_BASE_AD_INIT, + .cb_base_ad_init = RKISP1_CIF_MI_SP_CB_BASE_AD_INIT, + .cr_base_ad_init = RKISP1_CIF_MI_SP_CR_BASE_AD_INIT, + .y_offs_cnt_init = RKISP1_CIF_MI_SP_Y_OFFS_CNT_INIT, + .cb_offs_cnt_init = RKISP1_CIF_MI_SP_CB_OFFS_CNT_INIT, + .cr_offs_cnt_init = RKISP1_CIF_MI_SP_CR_OFFS_CNT_INIT, + }, +}; + +static inline struct rkisp1_vdev_node * +rkisp1_vdev_to_node(struct video_device *vdev) +{ + return container_of(vdev, struct rkisp1_vdev_node, vdev); +} + +int rkisp1_cap_enum_mbus_codes(struct rkisp1_capture *cap, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct rkisp1_capture_fmt_cfg *fmts = cap->config->fmts; + /* + * initialize curr_mbus to non existing mbus code 0 to ensure it is + * different from fmts[0].mbus + */ + u32 curr_mbus = 0; + int i, n = 0; + + for (i = 0; i < cap->config->fmt_size; i++) { + if (fmts[i].mbus == curr_mbus) + continue; + + curr_mbus = fmts[i].mbus; + if (n++ == code->index) { + code->code = curr_mbus; + return 0; + } + } + return -EINVAL; +} + +/* ---------------------------------------------------------------------------- + * Stream operations for self-picture path (sp) and main-picture path (mp) + */ + +static void rkisp1_mi_config_ctrl(struct rkisp1_capture *cap) +{ + u32 mi_ctrl = rkisp1_read(cap->rkisp1, RKISP1_CIF_MI_CTRL); + + mi_ctrl &= ~GENMASK(17, 16); + mi_ctrl |= RKISP1_CIF_MI_CTRL_BURST_LEN_LUM_64; + + mi_ctrl &= ~GENMASK(19, 18); + mi_ctrl |= RKISP1_CIF_MI_CTRL_BURST_LEN_CHROM_64; + + mi_ctrl |= RKISP1_CIF_MI_CTRL_INIT_BASE_EN | + RKISP1_CIF_MI_CTRL_INIT_OFFSET_EN; + + rkisp1_write(cap->rkisp1, RKISP1_CIF_MI_CTRL, mi_ctrl); +} + +static u32 rkisp1_pixfmt_comp_size(const struct v4l2_pix_format_mplane *pixm, + unsigned int component) +{ + /* + * If packed format, then plane_fmt[0].sizeimage is the sum of all + * components, so we need to calculate just the size of Y component. + * See rkisp1_fill_pixfmt(). + */ + if (!component && pixm->num_planes == 1) + return pixm->plane_fmt[0].bytesperline * pixm->height; + return pixm->plane_fmt[component].sizeimage; +} + +static void rkisp1_irq_frame_end_enable(struct rkisp1_capture *cap) +{ + u32 mi_imsc = rkisp1_read(cap->rkisp1, RKISP1_CIF_MI_IMSC); + + mi_imsc |= RKISP1_CIF_MI_FRAME(cap); + rkisp1_write(cap->rkisp1, RKISP1_CIF_MI_IMSC, mi_imsc); +} + +static void rkisp1_mp_config(struct rkisp1_capture *cap) +{ + const struct v4l2_pix_format_mplane *pixm = &cap->pix.fmt; + struct rkisp1_device *rkisp1 = cap->rkisp1; + u32 reg; + + rkisp1_write(rkisp1, cap->config->mi.y_size_init, + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_Y)); + rkisp1_write(rkisp1, cap->config->mi.cb_size_init, + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_CB)); + rkisp1_write(rkisp1, cap->config->mi.cr_size_init, + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_CR)); + + rkisp1_irq_frame_end_enable(cap); + + /* set uv swapping for semiplanar formats */ + if (cap->pix.info->comp_planes == 2) { + reg = rkisp1_read(rkisp1, RKISP1_CIF_MI_XTD_FORMAT_CTRL); + if (cap->pix.cfg->uv_swap) + reg |= RKISP1_CIF_MI_XTD_FMT_CTRL_MP_CB_CR_SWAP; + else + reg &= ~RKISP1_CIF_MI_XTD_FMT_CTRL_MP_CB_CR_SWAP; + rkisp1_write(rkisp1, RKISP1_CIF_MI_XTD_FORMAT_CTRL, reg); + } + + rkisp1_mi_config_ctrl(cap); + + reg = rkisp1_read(rkisp1, RKISP1_CIF_MI_CTRL); + reg &= ~RKISP1_MI_CTRL_MP_FMT_MASK; + reg |= cap->pix.cfg->write_format; + rkisp1_write(rkisp1, RKISP1_CIF_MI_CTRL, reg); + + reg = rkisp1_read(rkisp1, RKISP1_CIF_MI_CTRL); + reg |= RKISP1_CIF_MI_MP_AUTOUPDATE_ENABLE; + rkisp1_write(rkisp1, RKISP1_CIF_MI_CTRL, reg); +} + +static void rkisp1_sp_config(struct rkisp1_capture *cap) +{ + const struct v4l2_pix_format_mplane *pixm = &cap->pix.fmt; + struct rkisp1_device *rkisp1 = cap->rkisp1; + u32 mi_ctrl, reg; + + rkisp1_write(rkisp1, cap->config->mi.y_size_init, + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_Y)); + rkisp1_write(rkisp1, cap->config->mi.cb_size_init, + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_CB)); + rkisp1_write(rkisp1, cap->config->mi.cr_size_init, + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_CR)); + + rkisp1_write(rkisp1, RKISP1_CIF_MI_SP_Y_PIC_WIDTH, pixm->width); + rkisp1_write(rkisp1, RKISP1_CIF_MI_SP_Y_PIC_HEIGHT, pixm->height); + rkisp1_write(rkisp1, RKISP1_CIF_MI_SP_Y_LLENGTH, cap->sp_y_stride); + + rkisp1_irq_frame_end_enable(cap); + + /* set uv swapping for semiplanar formats */ + if (cap->pix.info->comp_planes == 2) { + reg = rkisp1_read(rkisp1, RKISP1_CIF_MI_XTD_FORMAT_CTRL); + if (cap->pix.cfg->uv_swap) + reg |= RKISP1_CIF_MI_XTD_FMT_CTRL_SP_CB_CR_SWAP; + else + reg &= ~RKISP1_CIF_MI_XTD_FMT_CTRL_SP_CB_CR_SWAP; + rkisp1_write(rkisp1, RKISP1_CIF_MI_XTD_FORMAT_CTRL, reg); + } + + rkisp1_mi_config_ctrl(cap); + + mi_ctrl = rkisp1_read(rkisp1, RKISP1_CIF_MI_CTRL); + mi_ctrl &= ~RKISP1_MI_CTRL_SP_FMT_MASK; + mi_ctrl |= cap->pix.cfg->write_format | + RKISP1_MI_CTRL_SP_INPUT_YUV422 | + cap->pix.cfg->output_format | + RKISP1_CIF_MI_SP_AUTOUPDATE_ENABLE; + rkisp1_write(rkisp1, RKISP1_CIF_MI_CTRL, mi_ctrl); +} + +static void rkisp1_mp_disable(struct rkisp1_capture *cap) +{ + u32 mi_ctrl = rkisp1_read(cap->rkisp1, RKISP1_CIF_MI_CTRL); + + mi_ctrl &= ~(RKISP1_CIF_MI_CTRL_MP_ENABLE | + RKISP1_CIF_MI_CTRL_RAW_ENABLE); + rkisp1_write(cap->rkisp1, RKISP1_CIF_MI_CTRL, mi_ctrl); +} + +static void rkisp1_sp_disable(struct rkisp1_capture *cap) +{ + u32 mi_ctrl = rkisp1_read(cap->rkisp1, RKISP1_CIF_MI_CTRL); + + mi_ctrl &= ~RKISP1_CIF_MI_CTRL_SP_ENABLE; + rkisp1_write(cap->rkisp1, RKISP1_CIF_MI_CTRL, mi_ctrl); +} + +static void rkisp1_mp_enable(struct rkisp1_capture *cap) +{ + u32 mi_ctrl; + + rkisp1_mp_disable(cap); + + mi_ctrl = rkisp1_read(cap->rkisp1, RKISP1_CIF_MI_CTRL); + if (v4l2_is_format_bayer(cap->pix.info)) + mi_ctrl |= RKISP1_CIF_MI_CTRL_RAW_ENABLE; + /* YUV */ + else + mi_ctrl |= RKISP1_CIF_MI_CTRL_MP_ENABLE; + + rkisp1_write(cap->rkisp1, RKISP1_CIF_MI_CTRL, mi_ctrl); +} + +static void rkisp1_sp_enable(struct rkisp1_capture *cap) +{ + u32 mi_ctrl = rkisp1_read(cap->rkisp1, RKISP1_CIF_MI_CTRL); + + mi_ctrl |= RKISP1_CIF_MI_CTRL_SP_ENABLE; + rkisp1_write(cap->rkisp1, RKISP1_CIF_MI_CTRL, mi_ctrl); +} + +static void rkisp1_mp_sp_stop(struct rkisp1_capture *cap) +{ + if (!cap->is_streaming) + return; + rkisp1_write(cap->rkisp1, RKISP1_CIF_MI_ICR, RKISP1_CIF_MI_FRAME(cap)); + cap->ops->disable(cap); +} + +static bool rkisp1_mp_is_stopped(struct rkisp1_capture *cap) +{ + u32 en = RKISP1_CIF_MI_CTRL_SHD_MP_IN_ENABLED | + RKISP1_CIF_MI_CTRL_SHD_RAW_OUT_ENABLED; + + return !(rkisp1_read(cap->rkisp1, RKISP1_CIF_MI_CTRL_SHD) & en); +} + +static bool rkisp1_sp_is_stopped(struct rkisp1_capture *cap) +{ + return !(rkisp1_read(cap->rkisp1, RKISP1_CIF_MI_CTRL_SHD) & + RKISP1_CIF_MI_CTRL_SHD_SP_IN_ENABLED); +} + +static void rkisp1_mp_set_data_path(struct rkisp1_capture *cap) +{ + u32 dpcl = rkisp1_read(cap->rkisp1, RKISP1_CIF_VI_DPCL); + + dpcl = dpcl | RKISP1_CIF_VI_DPCL_CHAN_MODE_MP | + RKISP1_CIF_VI_DPCL_MP_MUX_MRSZ_MI; + rkisp1_write(cap->rkisp1, RKISP1_CIF_VI_DPCL, dpcl); +} + +static void rkisp1_sp_set_data_path(struct rkisp1_capture *cap) +{ + u32 dpcl = rkisp1_read(cap->rkisp1, RKISP1_CIF_VI_DPCL); + + dpcl |= RKISP1_CIF_VI_DPCL_CHAN_MODE_SP; + rkisp1_write(cap->rkisp1, RKISP1_CIF_VI_DPCL, dpcl); +} + +static const struct rkisp1_capture_ops rkisp1_capture_ops_mp = { + .config = rkisp1_mp_config, + .enable = rkisp1_mp_enable, + .disable = rkisp1_mp_disable, + .stop = rkisp1_mp_sp_stop, + .set_data_path = rkisp1_mp_set_data_path, + .is_stopped = rkisp1_mp_is_stopped, +}; + +static const struct rkisp1_capture_ops rkisp1_capture_ops_sp = { + .config = rkisp1_sp_config, + .enable = rkisp1_sp_enable, + .disable = rkisp1_sp_disable, + .stop = rkisp1_mp_sp_stop, + .set_data_path = rkisp1_sp_set_data_path, + .is_stopped = rkisp1_sp_is_stopped, +}; + +/* ---------------------------------------------------------------------------- + * Frame buffer operations + */ + +static int rkisp1_dummy_buf_create(struct rkisp1_capture *cap) +{ + const struct v4l2_pix_format_mplane *pixm = &cap->pix.fmt; + struct rkisp1_dummy_buffer *dummy_buf = &cap->buf.dummy; + + dummy_buf->size = max3(rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_Y), + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_CB), + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_CR)); + + /* The driver never access vaddr, no mapping is required */ + dummy_buf->vaddr = dma_alloc_attrs(cap->rkisp1->dev, + dummy_buf->size, + &dummy_buf->dma_addr, + GFP_KERNEL, + DMA_ATTR_NO_KERNEL_MAPPING); + if (!dummy_buf->vaddr) + return -ENOMEM; + + return 0; +} + +static void rkisp1_dummy_buf_destroy(struct rkisp1_capture *cap) +{ + dma_free_attrs(cap->rkisp1->dev, + cap->buf.dummy.size, cap->buf.dummy.vaddr, + cap->buf.dummy.dma_addr, DMA_ATTR_NO_KERNEL_MAPPING); +} + +static void rkisp1_set_next_buf(struct rkisp1_capture *cap) +{ + cap->buf.curr = cap->buf.next; + cap->buf.next = NULL; + + if (!list_empty(&cap->buf.queue)) { + u32 *buff_addr; + + cap->buf.next = list_first_entry(&cap->buf.queue, struct rkisp1_buffer, queue); + list_del(&cap->buf.next->queue); + + buff_addr = cap->buf.next->buff_addr; + + rkisp1_write(cap->rkisp1, cap->config->mi.y_base_ad_init, + buff_addr[RKISP1_PLANE_Y]); + /* + * In order to support grey format we capture + * YUV422 planar format from the camera and + * set the U and V planes to the dummy buffer + */ + if (cap->pix.cfg->fourcc == V4L2_PIX_FMT_GREY) { + rkisp1_write(cap->rkisp1, + cap->config->mi.cb_base_ad_init, + cap->buf.dummy.dma_addr); + rkisp1_write(cap->rkisp1, + cap->config->mi.cr_base_ad_init, + cap->buf.dummy.dma_addr); + } else { + rkisp1_write(cap->rkisp1, + cap->config->mi.cb_base_ad_init, + buff_addr[RKISP1_PLANE_CB]); + rkisp1_write(cap->rkisp1, + cap->config->mi.cr_base_ad_init, + buff_addr[RKISP1_PLANE_CR]); + } + } else { + /* + * Use the dummy space allocated by dma_alloc_coherent to + * throw data if there is no available buffer. + */ + rkisp1_write(cap->rkisp1, cap->config->mi.y_base_ad_init, + cap->buf.dummy.dma_addr); + rkisp1_write(cap->rkisp1, cap->config->mi.cb_base_ad_init, + cap->buf.dummy.dma_addr); + rkisp1_write(cap->rkisp1, cap->config->mi.cr_base_ad_init, + cap->buf.dummy.dma_addr); + } + + /* Set plane offsets */ + rkisp1_write(cap->rkisp1, cap->config->mi.y_offs_cnt_init, 0); + rkisp1_write(cap->rkisp1, cap->config->mi.cb_offs_cnt_init, 0); + rkisp1_write(cap->rkisp1, cap->config->mi.cr_offs_cnt_init, 0); +} + +/* + * This function is called when a frame end comes. The next frame + * is processing and we should set up buffer for next-next frame, + * otherwise it will overflow. + */ +static void rkisp1_handle_buffer(struct rkisp1_capture *cap) +{ + struct rkisp1_isp *isp = &cap->rkisp1->isp; + struct rkisp1_buffer *curr_buf; + + spin_lock(&cap->buf.lock); + curr_buf = cap->buf.curr; + + if (curr_buf) { + curr_buf->vb.sequence = isp->frame_sequence; + curr_buf->vb.vb2_buf.timestamp = ktime_get_boottime_ns(); + curr_buf->vb.field = V4L2_FIELD_NONE; + vb2_buffer_done(&curr_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } else { + cap->rkisp1->debug.frame_drop[cap->id]++; + } + + rkisp1_set_next_buf(cap); + spin_unlock(&cap->buf.lock); +} + +irqreturn_t rkisp1_capture_isr(int irq, void *ctx) +{ + struct device *dev = ctx; + struct rkisp1_device *rkisp1 = dev_get_drvdata(dev); + unsigned int i; + u32 status; + + status = rkisp1_read(rkisp1, RKISP1_CIF_MI_MIS); + if (!status) + return IRQ_NONE; + + rkisp1_write(rkisp1, RKISP1_CIF_MI_ICR, status); + + for (i = 0; i < ARRAY_SIZE(rkisp1->capture_devs); ++i) { + struct rkisp1_capture *cap = &rkisp1->capture_devs[i]; + + if (!(status & RKISP1_CIF_MI_FRAME(cap))) + continue; + if (!cap->is_stopping) { + rkisp1_handle_buffer(cap); + continue; + } + /* + * Make sure stream is actually stopped, whose state + * can be read from the shadow register, before + * wake_up() thread which would immediately free all + * frame buffers. stop() takes effect at the next + * frame end that sync the configurations to shadow + * regs. + */ + if (!cap->ops->is_stopped(cap)) { + cap->ops->stop(cap); + continue; + } + cap->is_stopping = false; + cap->is_streaming = false; + wake_up(&cap->done); + } + + return IRQ_HANDLED; +} + +/* ---------------------------------------------------------------------------- + * Vb2 operations + */ + +static int rkisp1_vb2_queue_setup(struct vb2_queue *queue, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct rkisp1_capture *cap = queue->drv_priv; + const struct v4l2_pix_format_mplane *pixm = &cap->pix.fmt; + unsigned int i; + + if (*num_planes) { + if (*num_planes != pixm->num_planes) + return -EINVAL; + + for (i = 0; i < pixm->num_planes; i++) + if (sizes[i] < pixm->plane_fmt[i].sizeimage) + return -EINVAL; + } else { + *num_planes = pixm->num_planes; + for (i = 0; i < pixm->num_planes; i++) + sizes[i] = pixm->plane_fmt[i].sizeimage; + } + + return 0; +} + +static int rkisp1_vb2_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rkisp1_buffer *ispbuf = + container_of(vbuf, struct rkisp1_buffer, vb); + struct rkisp1_capture *cap = vb->vb2_queue->drv_priv; + const struct v4l2_pix_format_mplane *pixm = &cap->pix.fmt; + unsigned int i; + + memset(ispbuf->buff_addr, 0, sizeof(ispbuf->buff_addr)); + for (i = 0; i < pixm->num_planes; i++) + ispbuf->buff_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + + /* Convert to non-MPLANE */ + if (pixm->num_planes == 1) { + ispbuf->buff_addr[RKISP1_PLANE_CB] = + ispbuf->buff_addr[RKISP1_PLANE_Y] + + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_Y); + ispbuf->buff_addr[RKISP1_PLANE_CR] = + ispbuf->buff_addr[RKISP1_PLANE_CB] + + rkisp1_pixfmt_comp_size(pixm, RKISP1_PLANE_CB); + } + + /* + * uv swap can be supported for planar formats by switching + * the address of cb and cr + */ + if (cap->pix.info->comp_planes == 3 && cap->pix.cfg->uv_swap) + swap(ispbuf->buff_addr[RKISP1_PLANE_CR], + ispbuf->buff_addr[RKISP1_PLANE_CB]); + return 0; +} + +static void rkisp1_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rkisp1_buffer *ispbuf = + container_of(vbuf, struct rkisp1_buffer, vb); + struct rkisp1_capture *cap = vb->vb2_queue->drv_priv; + + spin_lock_irq(&cap->buf.lock); + list_add_tail(&ispbuf->queue, &cap->buf.queue); + spin_unlock_irq(&cap->buf.lock); +} + +static int rkisp1_vb2_buf_prepare(struct vb2_buffer *vb) +{ + struct rkisp1_capture *cap = vb->vb2_queue->drv_priv; + unsigned int i; + + for (i = 0; i < cap->pix.fmt.num_planes; i++) { + unsigned long size = cap->pix.fmt.plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + dev_err(cap->rkisp1->dev, + "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static void rkisp1_return_all_buffers(struct rkisp1_capture *cap, + enum vb2_buffer_state state) +{ + struct rkisp1_buffer *buf; + + spin_lock_irq(&cap->buf.lock); + if (cap->buf.curr) { + vb2_buffer_done(&cap->buf.curr->vb.vb2_buf, state); + cap->buf.curr = NULL; + } + if (cap->buf.next) { + vb2_buffer_done(&cap->buf.next->vb.vb2_buf, state); + cap->buf.next = NULL; + } + while (!list_empty(&cap->buf.queue)) { + buf = list_first_entry(&cap->buf.queue, + struct rkisp1_buffer, queue); + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + spin_unlock_irq(&cap->buf.lock); +} + +/* + * Most registers inside the rockchip ISP1 have shadow register since + * they must not be changed while processing a frame. + * Usually, each sub-module updates its shadow register after + * processing the last pixel of a frame. + */ +static void rkisp1_cap_stream_enable(struct rkisp1_capture *cap) +{ + struct rkisp1_device *rkisp1 = cap->rkisp1; + struct rkisp1_capture *other = &rkisp1->capture_devs[cap->id ^ 1]; + + cap->ops->set_data_path(cap); + cap->ops->config(cap); + + /* Setup a buffer for the next frame */ + spin_lock_irq(&cap->buf.lock); + rkisp1_set_next_buf(cap); + cap->ops->enable(cap); + /* It's safe to configure ACTIVE and SHADOW registers for the + * first stream. While when the second is starting, do NOT + * force update because it also updates the first one. + * + * The latter case would drop one more buffer(that is 2) since + * there's no buffer in a shadow register when the second FE received. + * This's also required because the second FE maybe corrupt + * especially when run at 120fps. + */ + if (!other->is_streaming) { + /* force cfg update */ + rkisp1_write(rkisp1, RKISP1_CIF_MI_INIT, + RKISP1_CIF_MI_INIT_SOFT_UPD); + rkisp1_set_next_buf(cap); + } + spin_unlock_irq(&cap->buf.lock); + cap->is_streaming = true; +} + +static void rkisp1_cap_stream_disable(struct rkisp1_capture *cap) +{ + int ret; + + /* Stream should stop in interrupt. If it doesn't, stop it by force. */ + cap->is_stopping = true; + ret = wait_event_timeout(cap->done, + !cap->is_streaming, + msecs_to_jiffies(1000)); + if (!ret) { + cap->rkisp1->debug.stop_timeout[cap->id]++; + cap->ops->stop(cap); + cap->is_stopping = false; + cap->is_streaming = false; + } +} + +/* + * rkisp1_pipeline_stream_disable - disable nodes in the pipeline + * + * Call s_stream(false) in the reverse order from + * rkisp1_pipeline_stream_enable() and disable the DMA engine. + * Should be called before video_device_pipeline_stop() + */ +static void rkisp1_pipeline_stream_disable(struct rkisp1_capture *cap) + __must_hold(&cap->rkisp1->stream_lock) +{ + struct rkisp1_device *rkisp1 = cap->rkisp1; + + rkisp1_cap_stream_disable(cap); + + /* + * If the other capture is streaming, isp and sensor nodes shouldn't + * be disabled, skip them. + */ + if (rkisp1->pipe.start_count < 2) + v4l2_subdev_call(&rkisp1->isp.sd, video, s_stream, false); + + v4l2_subdev_call(&rkisp1->resizer_devs[cap->id].sd, video, s_stream, + false); +} + +/* + * rkisp1_pipeline_stream_enable - enable nodes in the pipeline + * + * Enable the DMA Engine and call s_stream(true) through the pipeline. + * Should be called after video_device_pipeline_start() + */ +static int rkisp1_pipeline_stream_enable(struct rkisp1_capture *cap) + __must_hold(&cap->rkisp1->stream_lock) +{ + struct rkisp1_device *rkisp1 = cap->rkisp1; + int ret; + + rkisp1_cap_stream_enable(cap); + + ret = v4l2_subdev_call(&rkisp1->resizer_devs[cap->id].sd, video, + s_stream, true); + if (ret) + goto err_disable_cap; + + /* + * If the other capture is streaming, isp and sensor nodes are already + * enabled, skip them. + */ + if (rkisp1->pipe.start_count > 1) + return 0; + + ret = v4l2_subdev_call(&rkisp1->isp.sd, video, s_stream, true); + if (ret) + goto err_disable_rsz; + + return 0; + +err_disable_rsz: + v4l2_subdev_call(&rkisp1->resizer_devs[cap->id].sd, video, s_stream, + false); +err_disable_cap: + rkisp1_cap_stream_disable(cap); + + return ret; +} + +static void rkisp1_vb2_stop_streaming(struct vb2_queue *queue) +{ + struct rkisp1_capture *cap = queue->drv_priv; + struct rkisp1_vdev_node *node = &cap->vnode; + struct rkisp1_device *rkisp1 = cap->rkisp1; + int ret; + + mutex_lock(&cap->rkisp1->stream_lock); + + rkisp1_pipeline_stream_disable(cap); + + rkisp1_return_all_buffers(cap, VB2_BUF_STATE_ERROR); + + v4l2_pipeline_pm_put(&node->vdev.entity); + ret = pm_runtime_put(rkisp1->dev); + if (ret < 0) + dev_err(rkisp1->dev, "power down failed error:%d\n", ret); + + rkisp1_dummy_buf_destroy(cap); + + video_device_pipeline_stop(&node->vdev); + + mutex_unlock(&cap->rkisp1->stream_lock); +} + +static int +rkisp1_vb2_start_streaming(struct vb2_queue *queue, unsigned int count) +{ + struct rkisp1_capture *cap = queue->drv_priv; + struct media_entity *entity = &cap->vnode.vdev.entity; + int ret; + + mutex_lock(&cap->rkisp1->stream_lock); + + ret = video_device_pipeline_start(&cap->vnode.vdev, &cap->rkisp1->pipe); + if (ret) { + dev_err(cap->rkisp1->dev, "start pipeline failed %d\n", ret); + goto err_ret_buffers; + } + + ret = rkisp1_dummy_buf_create(cap); + if (ret) + goto err_pipeline_stop; + + ret = pm_runtime_resume_and_get(cap->rkisp1->dev); + if (ret < 0) { + dev_err(cap->rkisp1->dev, "power up failed %d\n", ret); + goto err_destroy_dummy; + } + ret = v4l2_pipeline_pm_get(entity); + if (ret) { + dev_err(cap->rkisp1->dev, "open cif pipeline failed %d\n", ret); + goto err_pipe_pm_put; + } + + ret = rkisp1_pipeline_stream_enable(cap); + if (ret) + goto err_v4l2_pm_put; + + mutex_unlock(&cap->rkisp1->stream_lock); + + return 0; + +err_v4l2_pm_put: + v4l2_pipeline_pm_put(entity); +err_pipe_pm_put: + pm_runtime_put(cap->rkisp1->dev); +err_destroy_dummy: + rkisp1_dummy_buf_destroy(cap); +err_pipeline_stop: + video_device_pipeline_stop(&cap->vnode.vdev); +err_ret_buffers: + rkisp1_return_all_buffers(cap, VB2_BUF_STATE_QUEUED); + mutex_unlock(&cap->rkisp1->stream_lock); + + return ret; +} + +static const struct vb2_ops rkisp1_vb2_ops = { + .queue_setup = rkisp1_vb2_queue_setup, + .buf_init = rkisp1_vb2_buf_init, + .buf_queue = rkisp1_vb2_buf_queue, + .buf_prepare = rkisp1_vb2_buf_prepare, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = rkisp1_vb2_stop_streaming, + .start_streaming = rkisp1_vb2_start_streaming, +}; + +/* ---------------------------------------------------------------------------- + * IOCTLs operations + */ + +static const struct v4l2_format_info * +rkisp1_fill_pixfmt(struct v4l2_pix_format_mplane *pixm, + enum rkisp1_stream_id id) +{ + struct v4l2_plane_pix_format *plane_y = &pixm->plane_fmt[0]; + const struct v4l2_format_info *info; + unsigned int i; + u32 stride; + + memset(pixm->plane_fmt, 0, sizeof(pixm->plane_fmt)); + info = v4l2_format_info(pixm->pixelformat); + pixm->num_planes = info->mem_planes; + stride = info->bpp[0] * pixm->width; + /* Self path supports custom stride but Main path doesn't */ + if (id == RKISP1_MAINPATH || plane_y->bytesperline < stride) + plane_y->bytesperline = stride; + plane_y->sizeimage = plane_y->bytesperline * pixm->height; + + /* normalize stride to pixels per line */ + stride = DIV_ROUND_UP(plane_y->bytesperline, info->bpp[0]); + + for (i = 1; i < info->comp_planes; i++) { + struct v4l2_plane_pix_format *plane = &pixm->plane_fmt[i]; + + /* bytesperline for other components derive from Y component */ + plane->bytesperline = DIV_ROUND_UP(stride, info->hdiv) * + info->bpp[i]; + plane->sizeimage = plane->bytesperline * + DIV_ROUND_UP(pixm->height, info->vdiv); + } + + /* + * If pixfmt is packed, then plane_fmt[0] should contain the total size + * considering all components. plane_fmt[i] for i > 0 should be ignored + * by userspace as mem_planes == 1, but we are keeping information there + * for convenience. + */ + if (info->mem_planes == 1) + for (i = 1; i < info->comp_planes; i++) + plane_y->sizeimage += pixm->plane_fmt[i].sizeimage; + + return info; +} + +static const struct rkisp1_capture_fmt_cfg * +rkisp1_find_fmt_cfg(const struct rkisp1_capture *cap, const u32 pixelfmt) +{ + unsigned int i; + + for (i = 0; i < cap->config->fmt_size; i++) { + if (cap->config->fmts[i].fourcc == pixelfmt) + return &cap->config->fmts[i]; + } + return NULL; +} + +static void rkisp1_try_fmt(const struct rkisp1_capture *cap, + struct v4l2_pix_format_mplane *pixm, + const struct rkisp1_capture_fmt_cfg **fmt_cfg, + const struct v4l2_format_info **fmt_info) +{ + const struct rkisp1_capture_config *config = cap->config; + const struct rkisp1_capture_fmt_cfg *fmt; + const struct v4l2_format_info *info; + const unsigned int max_widths[] = { RKISP1_RSZ_MP_SRC_MAX_WIDTH, + RKISP1_RSZ_SP_SRC_MAX_WIDTH }; + const unsigned int max_heights[] = { RKISP1_RSZ_MP_SRC_MAX_HEIGHT, + RKISP1_RSZ_SP_SRC_MAX_HEIGHT}; + + fmt = rkisp1_find_fmt_cfg(cap, pixm->pixelformat); + if (!fmt) { + fmt = config->fmts; + pixm->pixelformat = fmt->fourcc; + } + + pixm->width = clamp_t(u32, pixm->width, + RKISP1_RSZ_SRC_MIN_WIDTH, max_widths[cap->id]); + pixm->height = clamp_t(u32, pixm->height, + RKISP1_RSZ_SRC_MIN_HEIGHT, max_heights[cap->id]); + + pixm->field = V4L2_FIELD_NONE; + pixm->colorspace = V4L2_COLORSPACE_DEFAULT; + pixm->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pixm->quantization = V4L2_QUANTIZATION_DEFAULT; + + info = rkisp1_fill_pixfmt(pixm, cap->id); + + if (fmt_cfg) + *fmt_cfg = fmt; + if (fmt_info) + *fmt_info = info; +} + +static void rkisp1_set_fmt(struct rkisp1_capture *cap, + struct v4l2_pix_format_mplane *pixm) +{ + rkisp1_try_fmt(cap, pixm, &cap->pix.cfg, &cap->pix.info); + cap->pix.fmt = *pixm; + + /* SP supports custom stride in number of pixels of the Y plane */ + if (cap->id == RKISP1_SELFPATH) + cap->sp_y_stride = pixm->plane_fmt[0].bytesperline / + cap->pix.info->bpp[0]; +} + +static int rkisp1_try_fmt_vid_cap_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct rkisp1_capture *cap = video_drvdata(file); + + rkisp1_try_fmt(cap, &f->fmt.pix_mp, NULL, NULL); + + return 0; +} + +static int rkisp1_enum_fmt_vid_cap_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct rkisp1_capture *cap = video_drvdata(file); + const struct rkisp1_capture_fmt_cfg *fmt = NULL; + unsigned int i, n = 0; + + if (!f->mbus_code) { + if (f->index >= cap->config->fmt_size) + return -EINVAL; + + fmt = &cap->config->fmts[f->index]; + f->pixelformat = fmt->fourcc; + return 0; + } + + for (i = 0; i < cap->config->fmt_size; i++) { + if (cap->config->fmts[i].mbus != f->mbus_code) + continue; + + if (n++ == f->index) { + f->pixelformat = cap->config->fmts[i].fourcc; + return 0; + } + } + return -EINVAL; +} + +static int rkisp1_s_fmt_vid_cap_mplane(struct file *file, + void *priv, struct v4l2_format *f) +{ + struct rkisp1_capture *cap = video_drvdata(file); + struct rkisp1_vdev_node *node = + rkisp1_vdev_to_node(&cap->vnode.vdev); + + if (vb2_is_busy(&node->buf_queue)) + return -EBUSY; + + rkisp1_set_fmt(cap, &f->fmt.pix_mp); + + return 0; +} + +static int rkisp1_g_fmt_vid_cap_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct rkisp1_capture *cap = video_drvdata(file); + + f->fmt.pix_mp = cap->pix.fmt; + + return 0; +} + +static int +rkisp1_querycap(struct file *file, void *priv, struct v4l2_capability *cap) +{ + strscpy(cap->driver, RKISP1_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, RKISP1_DRIVER_NAME, sizeof(cap->card)); + strscpy(cap->bus_info, RKISP1_BUS_INFO, sizeof(cap->bus_info)); + + return 0; +} + +static const struct v4l2_ioctl_ops rkisp1_v4l2_ioctl_ops = { + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_try_fmt_vid_cap_mplane = rkisp1_try_fmt_vid_cap_mplane, + .vidioc_s_fmt_vid_cap_mplane = rkisp1_s_fmt_vid_cap_mplane, + .vidioc_g_fmt_vid_cap_mplane = rkisp1_g_fmt_vid_cap_mplane, + .vidioc_enum_fmt_vid_cap = rkisp1_enum_fmt_vid_cap_mplane, + .vidioc_querycap = rkisp1_querycap, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int rkisp1_capture_link_validate(struct media_link *link) +{ + struct video_device *vdev = + media_entity_to_video_device(link->sink->entity); + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(link->source->entity); + struct rkisp1_capture *cap = video_get_drvdata(vdev); + const struct rkisp1_capture_fmt_cfg *fmt = + rkisp1_find_fmt_cfg(cap, cap->pix.fmt.pixelformat); + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = link->source->index, + }; + int ret; + + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sd_fmt); + if (ret) + return ret; + + if (sd_fmt.format.height != cap->pix.fmt.height || + sd_fmt.format.width != cap->pix.fmt.width || + sd_fmt.format.code != fmt->mbus) { + dev_dbg(cap->rkisp1->dev, + "link '%s':%u -> '%s':%u not valid: 0x%04x/%ux%u != 0x%04x/%ux%u\n", + link->source->entity->name, link->source->index, + link->sink->entity->name, link->sink->index, + sd_fmt.format.code, sd_fmt.format.width, + sd_fmt.format.height, fmt->mbus, cap->pix.fmt.width, + cap->pix.fmt.height); + return -EPIPE; + } + + return 0; +} + +/* ---------------------------------------------------------------------------- + * core functions + */ + +static const struct media_entity_operations rkisp1_media_ops = { + .link_validate = rkisp1_capture_link_validate, +}; + +static const struct v4l2_file_operations rkisp1_fops = { + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +static void rkisp1_unregister_capture(struct rkisp1_capture *cap) +{ + if (!video_is_registered(&cap->vnode.vdev)) + return; + + media_entity_cleanup(&cap->vnode.vdev.entity); + vb2_video_unregister_device(&cap->vnode.vdev); + mutex_destroy(&cap->vnode.vlock); +} + +void rkisp1_capture_devs_unregister(struct rkisp1_device *rkisp1) +{ + struct rkisp1_capture *mp = &rkisp1->capture_devs[RKISP1_MAINPATH]; + struct rkisp1_capture *sp = &rkisp1->capture_devs[RKISP1_SELFPATH]; + + rkisp1_unregister_capture(mp); + rkisp1_unregister_capture(sp); +} + +static int rkisp1_register_capture(struct rkisp1_capture *cap) +{ + const char * const dev_names[] = {RKISP1_MP_DEV_NAME, + RKISP1_SP_DEV_NAME}; + struct v4l2_device *v4l2_dev = &cap->rkisp1->v4l2_dev; + struct video_device *vdev = &cap->vnode.vdev; + struct rkisp1_vdev_node *node; + struct vb2_queue *q; + int ret; + + strscpy(vdev->name, dev_names[cap->id], sizeof(vdev->name)); + node = rkisp1_vdev_to_node(vdev); + mutex_init(&node->vlock); + + vdev->ioctl_ops = &rkisp1_v4l2_ioctl_ops; + vdev->release = video_device_release_empty; + vdev->fops = &rkisp1_fops; + vdev->minor = -1; + vdev->v4l2_dev = v4l2_dev; + vdev->lock = &node->vlock; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | + V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; + vdev->entity.ops = &rkisp1_media_ops; + video_set_drvdata(vdev, cap); + vdev->vfl_dir = VFL_DIR_RX; + node->pad.flags = MEDIA_PAD_FL_SINK; + + q = &node->buf_queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = cap; + q->ops = &rkisp1_vb2_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct rkisp1_buffer); + q->min_buffers_needed = RKISP1_MIN_BUFFERS_NEEDED; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &node->vlock; + q->dev = cap->rkisp1->dev; + ret = vb2_queue_init(q); + if (ret) { + dev_err(cap->rkisp1->dev, + "vb2 queue init failed (err=%d)\n", ret); + goto error; + } + + vdev->queue = q; + + ret = media_entity_pads_init(&vdev->entity, 1, &node->pad); + if (ret) + goto error; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(cap->rkisp1->dev, + "failed to register %s, ret=%d\n", vdev->name, ret); + goto error; + } + + v4l2_info(v4l2_dev, "registered %s as /dev/video%d\n", vdev->name, + vdev->num); + + return 0; + +error: + media_entity_cleanup(&vdev->entity); + mutex_destroy(&node->vlock); + return ret; +} + +static void +rkisp1_capture_init(struct rkisp1_device *rkisp1, enum rkisp1_stream_id id) +{ + struct rkisp1_capture *cap = &rkisp1->capture_devs[id]; + struct v4l2_pix_format_mplane pixm; + + memset(cap, 0, sizeof(*cap)); + cap->id = id; + cap->rkisp1 = rkisp1; + + INIT_LIST_HEAD(&cap->buf.queue); + init_waitqueue_head(&cap->done); + spin_lock_init(&cap->buf.lock); + if (cap->id == RKISP1_SELFPATH) { + cap->ops = &rkisp1_capture_ops_sp; + cap->config = &rkisp1_capture_config_sp; + } else { + cap->ops = &rkisp1_capture_ops_mp; + cap->config = &rkisp1_capture_config_mp; + } + + cap->is_streaming = false; + + memset(&pixm, 0, sizeof(pixm)); + pixm.pixelformat = V4L2_PIX_FMT_YUYV; + pixm.width = RKISP1_DEFAULT_WIDTH; + pixm.height = RKISP1_DEFAULT_HEIGHT; + rkisp1_set_fmt(cap, &pixm); +} + +int rkisp1_capture_devs_register(struct rkisp1_device *rkisp1) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(rkisp1->capture_devs); i++) { + struct rkisp1_capture *cap = &rkisp1->capture_devs[i]; + + rkisp1_capture_init(rkisp1, i); + + ret = rkisp1_register_capture(cap); + if (ret) { + rkisp1_capture_devs_unregister(rkisp1); + return ret; + } + } + + return 0; + +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-common.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-common.c new file mode 100644 index 000000000..f956b90a4 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-common.c @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - Common definitions + * + * Copyright (C) 2019 Collabora, Ltd. + */ + +#include +#include + +#include "rkisp1-common.h" + +static const struct rkisp1_mbus_info rkisp1_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .pixel_enc = V4L2_PIXEL_ENC_YUV, + .direction = RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW10, + .bayer_pat = RKISP1_RAW_RGGB, + .bus_width = 10, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW10, + .bayer_pat = RKISP1_RAW_BGGR, + .bus_width = 10, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW10, + .bayer_pat = RKISP1_RAW_GBRG, + .bus_width = 10, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW10, + .bayer_pat = RKISP1_RAW_GRBG, + .bus_width = 10, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW12, + .bayer_pat = RKISP1_RAW_RGGB, + .bus_width = 12, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW12, + .bayer_pat = RKISP1_RAW_BGGR, + .bus_width = 12, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW12, + .bayer_pat = RKISP1_RAW_GBRG, + .bus_width = 12, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW12, + .bayer_pat = RKISP1_RAW_GRBG, + .bus_width = 12, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW8, + .bayer_pat = RKISP1_RAW_RGGB, + .bus_width = 8, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW8, + .bayer_pat = RKISP1_RAW_BGGR, + .bus_width = 8, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW8, + .bayer_pat = RKISP1_RAW_GBRG, + .bus_width = 8, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .pixel_enc = V4L2_PIXEL_ENC_BAYER, + .mipi_dt = MIPI_CSI2_DT_RAW8, + .bayer_pat = RKISP1_RAW_GRBG, + .bus_width = 8, + .direction = RKISP1_ISP_SD_SINK | RKISP1_ISP_SD_SRC, + }, { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .pixel_enc = V4L2_PIXEL_ENC_YUV, + .mipi_dt = MIPI_CSI2_DT_YUV422_8B, + .yuv_seq = RKISP1_CIF_ISP_ACQ_PROP_YCBYCR, + .bus_width = 16, + .direction = RKISP1_ISP_SD_SINK, + }, { + .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, + .pixel_enc = V4L2_PIXEL_ENC_YUV, + .mipi_dt = MIPI_CSI2_DT_YUV422_8B, + .yuv_seq = RKISP1_CIF_ISP_ACQ_PROP_YCRYCB, + .bus_width = 16, + .direction = RKISP1_ISP_SD_SINK, + }, { + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .pixel_enc = V4L2_PIXEL_ENC_YUV, + .mipi_dt = MIPI_CSI2_DT_YUV422_8B, + .yuv_seq = RKISP1_CIF_ISP_ACQ_PROP_CBYCRY, + .bus_width = 16, + .direction = RKISP1_ISP_SD_SINK, + }, { + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + .pixel_enc = V4L2_PIXEL_ENC_YUV, + .mipi_dt = MIPI_CSI2_DT_YUV422_8B, + .yuv_seq = RKISP1_CIF_ISP_ACQ_PROP_CRYCBY, + .bus_width = 16, + .direction = RKISP1_ISP_SD_SINK, + }, +}; + +const struct rkisp1_mbus_info *rkisp1_mbus_info_get_by_index(unsigned int index) +{ + if (index >= ARRAY_SIZE(rkisp1_formats)) + return NULL; + + return &rkisp1_formats[index]; +} + +const struct rkisp1_mbus_info *rkisp1_mbus_info_get_by_code(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rkisp1_formats); i++) { + const struct rkisp1_mbus_info *fmt = &rkisp1_formats[i]; + + if (fmt->mbus_code == mbus_code) + return fmt; + } + + return NULL; +} + +static const struct v4l2_rect rkisp1_sd_min_crop = { + .width = RKISP1_ISP_MIN_WIDTH, + .height = RKISP1_ISP_MIN_HEIGHT, + .top = 0, + .left = 0, +}; + +void rkisp1_sd_adjust_crop_rect(struct v4l2_rect *crop, + const struct v4l2_rect *bounds) +{ + v4l2_rect_set_min_size(crop, &rkisp1_sd_min_crop); + v4l2_rect_map_inside(crop, bounds); +} + +void rkisp1_sd_adjust_crop(struct v4l2_rect *crop, + const struct v4l2_mbus_framefmt *bounds) +{ + struct v4l2_rect crop_bounds = { + .left = 0, + .top = 0, + .width = bounds->width, + .height = bounds->height, + }; + + rkisp1_sd_adjust_crop_rect(crop, &crop_bounds); +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h b/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h new file mode 100644 index 000000000..a1293c45a --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h @@ -0,0 +1,630 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Rockchip ISP1 Driver - Common definitions + * + * Copyright (C) 2019 Collabora, Ltd. + * + * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd. + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#ifndef _RKISP1_COMMON_H +#define _RKISP1_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rkisp1-regs.h" + +struct dentry; + +/* + * flags on the 'direction' field in struct rkisp1_mbus_info' that indicate + * on which pad the media bus format is supported + */ +#define RKISP1_ISP_SD_SRC BIT(0) +#define RKISP1_ISP_SD_SINK BIT(1) + +/* min and max values for the widths and heights of the entities */ +#define RKISP1_ISP_MAX_WIDTH 4032 +#define RKISP1_ISP_MAX_HEIGHT 3024 +#define RKISP1_ISP_MIN_WIDTH 32 +#define RKISP1_ISP_MIN_HEIGHT 32 + +#define RKISP1_RSZ_MP_SRC_MAX_WIDTH 4416 +#define RKISP1_RSZ_MP_SRC_MAX_HEIGHT 3312 +#define RKISP1_RSZ_SP_SRC_MAX_WIDTH 1920 +#define RKISP1_RSZ_SP_SRC_MAX_HEIGHT 1920 +#define RKISP1_RSZ_SRC_MIN_WIDTH 32 +#define RKISP1_RSZ_SRC_MIN_HEIGHT 16 + +/* the default width and height of all the entities */ +#define RKISP1_DEFAULT_WIDTH 800 +#define RKISP1_DEFAULT_HEIGHT 600 + +#define RKISP1_DRIVER_NAME "rkisp1" +#define RKISP1_BUS_INFO "platform:" RKISP1_DRIVER_NAME + +/* maximum number of clocks */ +#define RKISP1_MAX_BUS_CLK 8 + +/* a bitmask of the ready stats */ +#define RKISP1_STATS_MEAS_MASK (RKISP1_CIF_ISP_AWB_DONE | \ + RKISP1_CIF_ISP_AFM_FIN | \ + RKISP1_CIF_ISP_EXP_END | \ + RKISP1_CIF_ISP_HIST_MEASURE_RDY) + +/* enum for the resizer pads */ +enum rkisp1_rsz_pad { + RKISP1_RSZ_PAD_SINK, + RKISP1_RSZ_PAD_SRC, + RKISP1_RSZ_PAD_MAX +}; + +/* enum for the csi receiver pads */ +enum rkisp1_csi_pad { + RKISP1_CSI_PAD_SINK, + RKISP1_CSI_PAD_SRC, + RKISP1_CSI_PAD_NUM +}; + +/* enum for the capture id */ +enum rkisp1_stream_id { + RKISP1_MAINPATH, + RKISP1_SELFPATH, +}; + +/* bayer patterns */ +enum rkisp1_fmt_raw_pat_type { + RKISP1_RAW_RGGB = 0, + RKISP1_RAW_GRBG, + RKISP1_RAW_GBRG, + RKISP1_RAW_BGGR, +}; + +/* enum for the isp pads */ +enum rkisp1_isp_pad { + RKISP1_ISP_PAD_SINK_VIDEO, + RKISP1_ISP_PAD_SINK_PARAMS, + RKISP1_ISP_PAD_SOURCE_VIDEO, + RKISP1_ISP_PAD_SOURCE_STATS, + RKISP1_ISP_PAD_MAX +}; + +/* + * enum rkisp1_feature - ISP features + * + * @RKISP1_FEATURE_MIPI_CSI2: The ISP has an internal MIPI CSI-2 receiver + * + * The ISP features are stored in a bitmask in &rkisp1_info.features and allow + * the driver to implement support for features present in some ISP versions + * only. + */ +enum rkisp1_feature { + RKISP1_FEATURE_MIPI_CSI2 = BIT(0), +}; + +/* + * struct rkisp1_info - Model-specific ISP Information + * + * @clks: array of ISP clock names + * @clk_size: number of entries in the @clks array + * @isrs: array of ISP interrupt descriptors + * @isr_size: number of entries in the @isrs array + * @isp_ver: ISP version + * @features: bitmask of rkisp1_feature features implemented by the ISP + * + * This structure contains information about the ISP specific to a particular + * ISP model, version, or integration in a particular SoC. + */ +struct rkisp1_info { + const char * const *clks; + unsigned int clk_size; + const struct rkisp1_isr_data *isrs; + unsigned int isr_size; + enum rkisp1_cif_isp_version isp_ver; + unsigned int features; +}; + +/* + * struct rkisp1_sensor_async - A container for the v4l2_async_subdev to add to the notifier + * of the v4l2-async API + * + * @asd: async_subdev variable for the sensor + * @index: index of the sensor (counting sensor found in DT) + * @source_ep: fwnode for the sensor source endpoint + * @lanes: number of lanes + * @mbus_type: type of bus (currently only CSI2 is supported) + * @mbus_flags: media bus (V4L2_MBUS_*) flags + * @sd: a pointer to v4l2_subdev struct of the sensor + * @pixel_rate_ctrl: pixel rate of the sensor, used to initialize the phy + * @port: port number (0: MIPI, 1: Parallel) + */ +struct rkisp1_sensor_async { + struct v4l2_async_subdev asd; + unsigned int index; + struct fwnode_handle *source_ep; + unsigned int lanes; + enum v4l2_mbus_type mbus_type; + unsigned int mbus_flags; + struct v4l2_subdev *sd; + struct v4l2_ctrl *pixel_rate_ctrl; + unsigned int port; +}; + +/* + * struct rkisp1_csi - CSI receiver subdev + * + * @rkisp1: pointer to the rkisp1 device + * @dphy: a pointer to the phy + * @is_dphy_errctrl_disabled: if dphy errctrl is disabled (avoid endless interrupt) + * @sd: v4l2_subdev variable + * @pads: media pads + * @pad_cfg: configurations for the pads + * @sink_fmt: input format + * @lock: protects pad_cfg and sink_fmt + * @source: source in-use, set when starting streaming + */ +struct rkisp1_csi { + struct rkisp1_device *rkisp1; + struct phy *dphy; + bool is_dphy_errctrl_disabled; + struct v4l2_subdev sd; + struct media_pad pads[RKISP1_CSI_PAD_NUM]; + struct v4l2_subdev_pad_config pad_cfg[RKISP1_CSI_PAD_NUM]; + const struct rkisp1_mbus_info *sink_fmt; + struct mutex lock; + struct v4l2_subdev *source; +}; + +/* + * struct rkisp1_isp - ISP subdev entity + * + * @sd: v4l2_subdev variable + * @rkisp1: pointer to rkisp1_device + * @pads: media pads + * @pad_cfg: pads configurations + * @sink_fmt: input format + * @src_fmt: output format + * @ops_lock: ops serialization + * @frame_sequence: used to synchronize frame_id between video devices. + */ +struct rkisp1_isp { + struct v4l2_subdev sd; + struct rkisp1_device *rkisp1; + struct media_pad pads[RKISP1_ISP_PAD_MAX]; + struct v4l2_subdev_pad_config pad_cfg[RKISP1_ISP_PAD_MAX]; + const struct rkisp1_mbus_info *sink_fmt; + const struct rkisp1_mbus_info *src_fmt; + struct mutex ops_lock; /* serialize the subdevice ops */ + __u32 frame_sequence; +}; + +/* + * struct rkisp1_vdev_node - Container for the video nodes: params, stats, mainpath, selfpath + * + * @buf_queue: queue of buffers + * @vlock: lock of the video node + * @vdev: video node + * @pad: media pad + */ +struct rkisp1_vdev_node { + struct vb2_queue buf_queue; + struct mutex vlock; /* ioctl serialization mutex */ + struct video_device vdev; + struct media_pad pad; +}; + +/* + * struct rkisp1_buffer - A container for the vb2 buffers used by the video devices: + * params, stats, mainpath, selfpath + * + * @vb: vb2 buffer + * @queue: entry of the buffer in the queue + * @buff_addr: dma addresses of each plane, used only by the capture devices: selfpath, mainpath + */ +struct rkisp1_buffer { + struct vb2_v4l2_buffer vb; + struct list_head queue; + u32 buff_addr[VIDEO_MAX_PLANES]; +}; + +/* + * struct rkisp1_dummy_buffer - A buffer to write the next frame to in case + * there are no vb2 buffers available. + * + * @vaddr: return value of call to dma_alloc_attrs. + * @dma_addr: dma address of the buffer. + * @size: size of the buffer. + */ +struct rkisp1_dummy_buffer { + void *vaddr; + dma_addr_t dma_addr; + u32 size; +}; + +struct rkisp1_device; + +/* + * struct rkisp1_capture - ISP capture video device + * + * @vnode: video node + * @rkisp1: pointer to rkisp1_device + * @id: id of the capture, one of RKISP1_SELFPATH, RKISP1_MAINPATH + * @ops: list of callbacks to configure the capture device. + * @config: a pointer to the list of registers to configure the capture format. + * @is_streaming: device is streaming + * @is_stopping: stop_streaming callback was called and the device is in the process of + * stopping the streaming. + * @done: when stop_streaming callback is called, the device waits for the next irq + * handler to stop the streaming by waiting on the 'done' wait queue. + * If the irq handler is not called, the stream is stopped by the callback + * after timeout. + * @sp_y_stride: the selfpath allows to configure a y stride that is longer than the image width. + * @buf.lock: lock to protect buf.queue + * @buf.queue: queued buffer list + * @buf.dummy: dummy space to store dropped data + * + * rkisp1 uses shadow registers, so it needs two buffers at a time + * @buf.curr: the buffer used for current frame + * @buf.next: the buffer used for next frame + * @pix.cfg: pixel configuration + * @pix.info: a pointer to the v4l2_format_info of the pixel format + * @pix.fmt: buffer format + */ +struct rkisp1_capture { + struct rkisp1_vdev_node vnode; + struct rkisp1_device *rkisp1; + enum rkisp1_stream_id id; + const struct rkisp1_capture_ops *ops; + const struct rkisp1_capture_config *config; + bool is_streaming; + bool is_stopping; + wait_queue_head_t done; + unsigned int sp_y_stride; + struct { + /* protects queue, curr and next */ + spinlock_t lock; + struct list_head queue; + struct rkisp1_dummy_buffer dummy; + struct rkisp1_buffer *curr; + struct rkisp1_buffer *next; + } buf; + struct { + const struct rkisp1_capture_fmt_cfg *cfg; + const struct v4l2_format_info *info; + struct v4l2_pix_format_mplane fmt; + } pix; +}; + +struct rkisp1_stats; +struct rkisp1_stats_ops { + void (*get_awb_meas)(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf); + void (*get_aec_meas)(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf); + void (*get_hst_meas)(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf); +}; + +/* + * struct rkisp1_stats - ISP Statistics device + * + * @vnode: video node + * @rkisp1: pointer to the rkisp1 device + * @lock: locks the buffer list 'stat' + * @stat: queue of rkisp1_buffer + * @vdev_fmt: v4l2_format of the metadata format + */ +struct rkisp1_stats { + struct rkisp1_vdev_node vnode; + struct rkisp1_device *rkisp1; + const struct rkisp1_stats_ops *ops; + + spinlock_t lock; /* locks the buffers list 'stats' */ + struct list_head stat; + struct v4l2_format vdev_fmt; +}; + +struct rkisp1_params; +struct rkisp1_params_ops { + void (*lsc_matrix_config)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_lsc_config *pconfig); + void (*goc_config)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_goc_config *arg); + void (*awb_meas_config)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_meas_config *arg); + void (*awb_meas_enable)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_meas_config *arg, + bool en); + void (*awb_gain_config)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_gain_config *arg); + void (*aec_config)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_aec_config *arg); + void (*hst_config)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_hst_config *arg); + void (*hst_enable)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_hst_config *arg, bool en); + void (*afm_config)(struct rkisp1_params *params, + const struct rkisp1_cif_isp_afc_config *arg); +}; + +/* + * struct rkisp1_params - ISP input parameters device + * + * @vnode: video node + * @rkisp1: pointer to the rkisp1 device + * @ops: pointer to the variant-specific operations + * @config_lock: locks the buffer list 'params' + * @params: queue of rkisp1_buffer + * @vdev_fmt: v4l2_format of the metadata format + * @quantization: the quantization configured on the isp's src pad + * @raw_type: the bayer pattern on the isp video sink pad + */ +struct rkisp1_params { + struct rkisp1_vdev_node vnode; + struct rkisp1_device *rkisp1; + const struct rkisp1_params_ops *ops; + + spinlock_t config_lock; /* locks the buffers list 'params' */ + struct list_head params; + struct v4l2_format vdev_fmt; + + enum v4l2_quantization quantization; + enum v4l2_ycbcr_encoding ycbcr_encoding; + enum rkisp1_fmt_raw_pat_type raw_type; +}; + +/* + * struct rkisp1_resizer - Resizer subdev + * + * @sd: v4l2_subdev variable + * @regs_base: base register address offset + * @id: id of the resizer, one of RKISP1_SELFPATH, RKISP1_MAINPATH + * @rkisp1: pointer to the rkisp1 device + * @pads: media pads + * @pad_cfg: configurations for the pads + * @config: the set of registers to configure the resizer + * @pixel_enc: pixel encoding of the resizer + * @ops_lock: a lock for the subdev ops + */ +struct rkisp1_resizer { + struct v4l2_subdev sd; + u32 regs_base; + enum rkisp1_stream_id id; + struct rkisp1_device *rkisp1; + struct media_pad pads[RKISP1_RSZ_PAD_MAX]; + struct v4l2_subdev_pad_config pad_cfg[RKISP1_RSZ_PAD_MAX]; + const struct rkisp1_rsz_config *config; + enum v4l2_pixel_encoding pixel_enc; + struct mutex ops_lock; /* serialize the subdevice ops */ +}; + +/* + * struct rkisp1_debug - Values to be exposed on debugfs. + * The parameters are counters of the number of times the + * event occurred since the driver was loaded. + * + * @data_loss: loss of data occurred within a line, processing failure + * @outform_size_error: size error is generated in outmux submodule + * @img_stabilization_size_error: size error is generated in image stabilization submodule + * @inform_size_err: size error is generated in inform submodule + * @mipi_error: mipi error occurred + * @stats_error: writing to the 'Interrupt clear register' did not clear + * it in the register 'Masked interrupt status' + * @stop_timeout: upon stream stop, the capture waits 1 second for the isr to stop + * the stream. This param is incremented in case of timeout. + * @frame_drop: a frame was ready but the buffer queue was empty so the frame + * was not sent to userspace + */ +struct rkisp1_debug { + struct dentry *debugfs_dir; + unsigned long data_loss; + unsigned long outform_size_error; + unsigned long img_stabilization_size_error; + unsigned long inform_size_error; + unsigned long irq_delay; + unsigned long mipi_error; + unsigned long stats_error; + unsigned long stop_timeout[2]; + unsigned long frame_drop[2]; +}; + +/* + * struct rkisp1_device - ISP platform device + * + * @base_addr: base register address + * @irq: the irq number + * @dev: a pointer to the struct device + * @clk_size: number of clocks + * @clks: array of clocks + * @v4l2_dev: v4l2_device variable + * @media_dev: media_device variable + * @notifier: a notifier to register on the v4l2-async API to be notified on the sensor + * @source: source subdev in-use, set when starting streaming + * @csi: internal CSI-2 receiver + * @isp: ISP sub-device + * @resizer_devs: resizer sub-devices + * @capture_devs: capture devices + * @stats: ISP statistics metadata capture device + * @params: ISP parameters metadata output device + * @pipe: media pipeline + * @stream_lock: serializes {start/stop}_streaming callbacks between the capture devices. + * @debug: debug params to be exposed on debugfs + * @info: version-specific ISP information + */ +struct rkisp1_device { + void __iomem *base_addr; + struct device *dev; + unsigned int clk_size; + struct clk_bulk_data clks[RKISP1_MAX_BUS_CLK]; + struct v4l2_device v4l2_dev; + struct media_device media_dev; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *source; + struct rkisp1_csi csi; + struct rkisp1_isp isp; + struct rkisp1_resizer resizer_devs[2]; + struct rkisp1_capture capture_devs[2]; + struct rkisp1_stats stats; + struct rkisp1_params params; + struct media_pipeline pipe; + struct mutex stream_lock; /* serialize {start/stop}_streaming cb between capture devices */ + struct rkisp1_debug debug; + const struct rkisp1_info *info; +}; + +/* + * struct rkisp1_mbus_info - ISP media bus info, Translates media bus code to hardware + * format values + * + * @mbus_code: media bus code + * @pixel_enc: pixel encoding + * @mipi_dt: mipi data type + * @yuv_seq: the order of the Y, Cb, Cr values + * @bus_width: bus width + * @bayer_pat: bayer pattern + * @direction: a bitmask of the flags indicating on which pad the format is supported on + */ +struct rkisp1_mbus_info { + u32 mbus_code; + enum v4l2_pixel_encoding pixel_enc; + u32 mipi_dt; + u32 yuv_seq; + u8 bus_width; + enum rkisp1_fmt_raw_pat_type bayer_pat; + unsigned int direction; +}; + +static inline void +rkisp1_write(struct rkisp1_device *rkisp1, unsigned int addr, u32 val) +{ + writel(val, rkisp1->base_addr + addr); +} + +static inline u32 rkisp1_read(struct rkisp1_device *rkisp1, unsigned int addr) +{ + return readl(rkisp1->base_addr + addr); +} + +/* + * rkisp1_cap_enum_mbus_codes - A helper function that return the i'th supported mbus code + * of the capture entity. This is used to enumerate the supported + * mbus codes on the source pad of the resizer. + * + * @cap: the capture entity + * @code: the mbus code, the function reads the code->index and fills the code->code + */ +int rkisp1_cap_enum_mbus_codes(struct rkisp1_capture *cap, + struct v4l2_subdev_mbus_code_enum *code); + +/* + * rkisp1_mbus_info_get_by_index - Retrieve the ith supported mbus info + * + * @index: index of the mbus info to fetch + */ +const struct rkisp1_mbus_info *rkisp1_mbus_info_get_by_index(unsigned int index); + +/* + * rkisp1_sd_adjust_crop_rect - adjust a rectangle to fit into another rectangle. + * + * @crop: rectangle to adjust. + * @bounds: rectangle used as bounds. + */ +void rkisp1_sd_adjust_crop_rect(struct v4l2_rect *crop, + const struct v4l2_rect *bounds); + +/* + * rkisp1_sd_adjust_crop - adjust a rectangle to fit into media bus format + * + * @crop: rectangle to adjust. + * @bounds: media bus format used as bounds. + */ +void rkisp1_sd_adjust_crop(struct v4l2_rect *crop, + const struct v4l2_mbus_framefmt *bounds); + +/* + * rkisp1_mbus_info_get_by_code - get the isp info of the media bus code + * + * @mbus_code: the media bus code + */ +const struct rkisp1_mbus_info *rkisp1_mbus_info_get_by_code(u32 mbus_code); + +/* + * rkisp1_params_pre_configure - Configure the params before stream start + * + * @params: pointer to rkisp1_params + * @bayer_pat: the bayer pattern on the isp video sink pad + * @quantization: the quantization configured on the isp's src pad + * @ycbcr_encoding: the ycbcr_encoding configured on the isp's src pad + * + * This function is called by the ISP entity just before the ISP gets started. + * It applies the initial ISP parameters from the first params buffer, but + * skips LSC as it needs to be configured after the ISP is started. + */ +void rkisp1_params_pre_configure(struct rkisp1_params *params, + enum rkisp1_fmt_raw_pat_type bayer_pat, + enum v4l2_quantization quantization, + enum v4l2_ycbcr_encoding ycbcr_encoding); + +/* + * rkisp1_params_post_configure - Configure the params after stream start + * + * @params: pointer to rkisp1_params + * + * This function is called by the ISP entity just after the ISP gets started. + * It applies the initial ISP LSC parameters from the first params buffer. + */ +void rkisp1_params_post_configure(struct rkisp1_params *params); + +/* rkisp1_params_disable - disable all parameters. + * This function is called by the isp entity upon stream start + * when capturing bayer format. + * + * @params: pointer to rkisp1_params. + */ +void rkisp1_params_disable(struct rkisp1_params *params); + +/* irq handlers */ +irqreturn_t rkisp1_isp_isr(int irq, void *ctx); +irqreturn_t rkisp1_csi_isr(int irq, void *ctx); +irqreturn_t rkisp1_capture_isr(int irq, void *ctx); +void rkisp1_stats_isr(struct rkisp1_stats *stats, u32 isp_ris); +void rkisp1_params_isr(struct rkisp1_device *rkisp1); + +/* register/unregisters functions of the entities */ +int rkisp1_capture_devs_register(struct rkisp1_device *rkisp1); +void rkisp1_capture_devs_unregister(struct rkisp1_device *rkisp1); + +int rkisp1_isp_register(struct rkisp1_device *rkisp1); +void rkisp1_isp_unregister(struct rkisp1_device *rkisp1); + +int rkisp1_resizer_devs_register(struct rkisp1_device *rkisp1); +void rkisp1_resizer_devs_unregister(struct rkisp1_device *rkisp1); + +int rkisp1_stats_register(struct rkisp1_device *rkisp1); +void rkisp1_stats_unregister(struct rkisp1_device *rkisp1); + +int rkisp1_params_register(struct rkisp1_device *rkisp1); +void rkisp1_params_unregister(struct rkisp1_device *rkisp1); + +#if IS_ENABLED(CONFIG_DEBUG_FS) +void rkisp1_debug_init(struct rkisp1_device *rkisp1); +void rkisp1_debug_cleanup(struct rkisp1_device *rkisp1); +#else +static inline void rkisp1_debug_init(struct rkisp1_device *rkisp1) +{ +} +static inline void rkisp1_debug_cleanup(struct rkisp1_device *rkisp1) +{ +} +#endif + +#endif /* _RKISP1_COMMON_H */ diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c new file mode 100644 index 000000000..d7acc94e1 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.c @@ -0,0 +1,536 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - CSI-2 Receiver + * + * Copyright (C) 2019 Collabora, Ltd. + * Copyright (C) 2022 Ideas on Board + * + * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd. + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include "rkisp1-common.h" +#include "rkisp1-csi.h" + +#define RKISP1_CSI_DEV_NAME RKISP1_DRIVER_NAME "_csi" + +#define RKISP1_CSI_DEF_FMT MEDIA_BUS_FMT_SRGGB10_1X10 + +static inline struct rkisp1_csi *to_rkisp1_csi(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rkisp1_csi, sd); +} + +static struct v4l2_mbus_framefmt * +rkisp1_csi_get_pad_fmt(struct rkisp1_csi *csi, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + struct v4l2_subdev_state state = { + .pads = csi->pad_cfg + }; + + lockdep_assert_held(&csi->lock); + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&csi->sd, sd_state, pad); + else + return v4l2_subdev_get_try_format(&csi->sd, &state, pad); +} + +int rkisp1_csi_link_sensor(struct rkisp1_device *rkisp1, struct v4l2_subdev *sd, + struct rkisp1_sensor_async *s_asd, + unsigned int source_pad) +{ + struct rkisp1_csi *csi = &rkisp1->csi; + int ret; + + s_asd->pixel_rate_ctrl = v4l2_ctrl_find(sd->ctrl_handler, + V4L2_CID_PIXEL_RATE); + if (!s_asd->pixel_rate_ctrl) { + dev_err(rkisp1->dev, "No pixel rate control in subdev %s\n", + sd->name); + return -EINVAL; + } + + /* Create the link from the sensor to the CSI receiver. */ + ret = media_create_pad_link(&sd->entity, source_pad, + &csi->sd.entity, RKISP1_CSI_PAD_SINK, + !s_asd->index ? MEDIA_LNK_FL_ENABLED : 0); + if (ret) { + dev_err(csi->rkisp1->dev, "failed to link src pad of %s\n", + sd->name); + return ret; + } + + return 0; +} + +static int rkisp1_csi_config(struct rkisp1_csi *csi, + const struct rkisp1_sensor_async *sensor) +{ + struct rkisp1_device *rkisp1 = csi->rkisp1; + unsigned int lanes = sensor->lanes; + u32 mipi_ctrl; + + if (lanes < 1 || lanes > 4) + return -EINVAL; + + mipi_ctrl = RKISP1_CIF_MIPI_CTRL_NUM_LANES(lanes - 1) | + RKISP1_CIF_MIPI_CTRL_SHUTDOWNLANES(0xf) | + RKISP1_CIF_MIPI_CTRL_ERR_SOT_SYNC_HS_SKIP | + RKISP1_CIF_MIPI_CTRL_CLOCKLANE_ENA; + + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_CTRL, mipi_ctrl); + + /* V12 could also use a newer csi2-host, but we don't want that yet */ + if (rkisp1->info->isp_ver == RKISP1_V12) + rkisp1_write(rkisp1, RKISP1_CIF_ISP_CSI0_CTRL0, 0); + + /* Configure Data Type and Virtual Channel */ + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_IMG_DATA_SEL, + RKISP1_CIF_MIPI_DATA_SEL_DT(csi->sink_fmt->mipi_dt) | + RKISP1_CIF_MIPI_DATA_SEL_VC(0)); + + /* Clear MIPI interrupts */ + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_ICR, ~0); + + /* + * Disable RKISP1_CIF_MIPI_ERR_DPHY interrupt here temporary for + * isp bus may be dead when switch isp. + */ + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_IMSC, + RKISP1_CIF_MIPI_FRAME_END | RKISP1_CIF_MIPI_ERR_CSI | + RKISP1_CIF_MIPI_ERR_DPHY | + RKISP1_CIF_MIPI_SYNC_FIFO_OVFLW(0x03) | + RKISP1_CIF_MIPI_ADD_DATA_OVFLW); + + dev_dbg(rkisp1->dev, "\n MIPI_CTRL 0x%08x\n" + " MIPI_IMG_DATA_SEL 0x%08x\n" + " MIPI_STATUS 0x%08x\n" + " MIPI_IMSC 0x%08x\n", + rkisp1_read(rkisp1, RKISP1_CIF_MIPI_CTRL), + rkisp1_read(rkisp1, RKISP1_CIF_MIPI_IMG_DATA_SEL), + rkisp1_read(rkisp1, RKISP1_CIF_MIPI_STATUS), + rkisp1_read(rkisp1, RKISP1_CIF_MIPI_IMSC)); + + return 0; +} + +static void rkisp1_csi_enable(struct rkisp1_csi *csi) +{ + struct rkisp1_device *rkisp1 = csi->rkisp1; + u32 val; + + val = rkisp1_read(rkisp1, RKISP1_CIF_MIPI_CTRL); + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_CTRL, + val | RKISP1_CIF_MIPI_CTRL_OUTPUT_ENA); +} + +static void rkisp1_csi_disable(struct rkisp1_csi *csi) +{ + struct rkisp1_device *rkisp1 = csi->rkisp1; + u32 val; + + /* Mask and clear interrupts. */ + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_IMSC, 0); + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_ICR, ~0); + + val = rkisp1_read(rkisp1, RKISP1_CIF_MIPI_CTRL); + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_CTRL, + val & (~RKISP1_CIF_MIPI_CTRL_OUTPUT_ENA)); +} + +static int rkisp1_csi_start(struct rkisp1_csi *csi, + const struct rkisp1_sensor_async *sensor) +{ + struct rkisp1_device *rkisp1 = csi->rkisp1; + union phy_configure_opts opts; + struct phy_configure_opts_mipi_dphy *cfg = &opts.mipi_dphy; + s64 pixel_clock; + int ret; + + ret = rkisp1_csi_config(csi, sensor); + if (ret) + return ret; + + pixel_clock = v4l2_ctrl_g_ctrl_int64(sensor->pixel_rate_ctrl); + if (!pixel_clock) { + dev_err(rkisp1->dev, "Invalid pixel rate value\n"); + return -EINVAL; + } + + phy_mipi_dphy_get_default_config(pixel_clock, csi->sink_fmt->bus_width, + sensor->lanes, cfg); + phy_set_mode(csi->dphy, PHY_MODE_MIPI_DPHY); + phy_configure(csi->dphy, &opts); + phy_power_on(csi->dphy); + + rkisp1_csi_enable(csi); + + /* + * CIF spec says to wait for sufficient time after enabling + * the MIPI interface and before starting the sensor output. + */ + usleep_range(1000, 1200); + + return 0; +} + +static void rkisp1_csi_stop(struct rkisp1_csi *csi) +{ + rkisp1_csi_disable(csi); + + phy_power_off(csi->dphy); +} + +irqreturn_t rkisp1_csi_isr(int irq, void *ctx) +{ + struct device *dev = ctx; + struct rkisp1_device *rkisp1 = dev_get_drvdata(dev); + u32 val, status; + + status = rkisp1_read(rkisp1, RKISP1_CIF_MIPI_MIS); + if (!status) + return IRQ_NONE; + + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_ICR, status); + + /* + * Disable DPHY errctrl interrupt, because this dphy + * erctrl signal is asserted until the next changes + * of line state. This time is may be too long and cpu + * is hold in this interrupt. + */ + if (status & RKISP1_CIF_MIPI_ERR_CTRL(0x0f)) { + val = rkisp1_read(rkisp1, RKISP1_CIF_MIPI_IMSC); + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_IMSC, + val & ~RKISP1_CIF_MIPI_ERR_CTRL(0x0f)); + rkisp1->csi.is_dphy_errctrl_disabled = true; + } + + /* + * Enable DPHY errctrl interrupt again, if mipi have receive + * the whole frame without any error. + */ + if (status == RKISP1_CIF_MIPI_FRAME_END) { + /* + * Enable DPHY errctrl interrupt again, if mipi have receive + * the whole frame without any error. + */ + if (rkisp1->csi.is_dphy_errctrl_disabled) { + val = rkisp1_read(rkisp1, RKISP1_CIF_MIPI_IMSC); + val |= RKISP1_CIF_MIPI_ERR_CTRL(0x0f); + rkisp1_write(rkisp1, RKISP1_CIF_MIPI_IMSC, val); + rkisp1->csi.is_dphy_errctrl_disabled = false; + } + } else { + rkisp1->debug.mipi_error++; + } + + return IRQ_HANDLED; +} + +/* ---------------------------------------------------------------------------- + * Subdev pad operations + */ + +static int rkisp1_csi_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct rkisp1_csi *csi = to_rkisp1_csi(sd); + unsigned int i; + int pos = 0; + + if (code->pad == RKISP1_CSI_PAD_SRC) { + const struct v4l2_mbus_framefmt *sink_fmt; + + if (code->index) + return -EINVAL; + + mutex_lock(&csi->lock); + + sink_fmt = rkisp1_csi_get_pad_fmt(csi, sd_state, + RKISP1_CSI_PAD_SINK, + code->which); + code->code = sink_fmt->code; + + mutex_unlock(&csi->lock); + + return 0; + } + + for (i = 0; ; i++) { + const struct rkisp1_mbus_info *fmt = + rkisp1_mbus_info_get_by_index(i); + + if (!fmt) + return -EINVAL; + + if (!(fmt->direction & RKISP1_ISP_SD_SINK)) + continue; + + if (code->index == pos) { + code->code = fmt->mbus_code; + return 0; + } + + pos++; + } + + return -EINVAL; +} + +static int rkisp1_csi_init_config(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + + sink_fmt = v4l2_subdev_get_try_format(sd, sd_state, + RKISP1_CSI_PAD_SINK); + src_fmt = v4l2_subdev_get_try_format(sd, sd_state, + RKISP1_CSI_PAD_SRC); + + sink_fmt->width = RKISP1_DEFAULT_WIDTH; + sink_fmt->height = RKISP1_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = RKISP1_CSI_DEF_FMT; + + *src_fmt = *sink_fmt; + + return 0; +} + +static int rkisp1_csi_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct rkisp1_csi *csi = to_rkisp1_csi(sd); + + mutex_lock(&csi->lock); + fmt->format = *rkisp1_csi_get_pad_fmt(csi, sd_state, fmt->pad, + fmt->which); + mutex_unlock(&csi->lock); + + return 0; +} + +static int rkisp1_csi_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct rkisp1_csi *csi = to_rkisp1_csi(sd); + const struct rkisp1_mbus_info *mbus_info; + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + + /* The format on the source pad always matches the sink pad. */ + if (fmt->pad == RKISP1_CSI_PAD_SRC) + return rkisp1_csi_get_fmt(sd, sd_state, fmt); + + mutex_lock(&csi->lock); + + sink_fmt = rkisp1_csi_get_pad_fmt(csi, sd_state, RKISP1_CSI_PAD_SINK, + fmt->which); + + sink_fmt->code = fmt->format.code; + + mbus_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + if (!mbus_info || !(mbus_info->direction & RKISP1_ISP_SD_SINK)) { + sink_fmt->code = RKISP1_CSI_DEF_FMT; + mbus_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + } + + sink_fmt->width = clamp_t(u32, fmt->format.width, + RKISP1_ISP_MIN_WIDTH, + RKISP1_ISP_MAX_WIDTH); + sink_fmt->height = clamp_t(u32, fmt->format.height, + RKISP1_ISP_MIN_HEIGHT, + RKISP1_ISP_MAX_HEIGHT); + + fmt->format = *sink_fmt; + + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + csi->sink_fmt = mbus_info; + + /* Propagate the format to the source pad. */ + src_fmt = rkisp1_csi_get_pad_fmt(csi, sd_state, RKISP1_CSI_PAD_SRC, + fmt->which); + *src_fmt = *sink_fmt; + + mutex_unlock(&csi->lock); + + return 0; +} + +/* ---------------------------------------------------------------------------- + * Subdev video operations + */ + +static int rkisp1_csi_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rkisp1_csi *csi = to_rkisp1_csi(sd); + struct rkisp1_device *rkisp1 = csi->rkisp1; + struct rkisp1_sensor_async *source_asd; + struct media_pad *source_pad; + struct v4l2_subdev *source; + int ret; + + if (!enable) { + v4l2_subdev_call(csi->source, video, s_stream, false); + + rkisp1_csi_stop(csi); + + return 0; + } + + source_pad = media_entity_remote_source_pad_unique(&sd->entity); + if (IS_ERR(source_pad)) { + dev_dbg(rkisp1->dev, "Failed to get source for CSI: %ld\n", + PTR_ERR(source_pad)); + return -EPIPE; + } + + source = media_entity_to_v4l2_subdev(source_pad->entity); + if (!source) { + /* This should really not happen, so is not worth a message. */ + return -EPIPE; + } + + source_asd = container_of(source->asd, struct rkisp1_sensor_async, asd); + if (source_asd->mbus_type != V4L2_MBUS_CSI2_DPHY) + return -EINVAL; + + mutex_lock(&csi->lock); + ret = rkisp1_csi_start(csi, source_asd); + mutex_unlock(&csi->lock); + if (ret) + return ret; + + ret = v4l2_subdev_call(source, video, s_stream, true); + if (ret) { + rkisp1_csi_stop(csi); + return ret; + } + + csi->source = source; + + return 0; +} + +/* ---------------------------------------------------------------------------- + * Registration + */ + +static const struct media_entity_operations rkisp1_csi_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_video_ops rkisp1_csi_video_ops = { + .s_stream = rkisp1_csi_s_stream, +}; + +static const struct v4l2_subdev_pad_ops rkisp1_csi_pad_ops = { + .enum_mbus_code = rkisp1_csi_enum_mbus_code, + .init_cfg = rkisp1_csi_init_config, + .get_fmt = rkisp1_csi_get_fmt, + .set_fmt = rkisp1_csi_set_fmt, +}; + +static const struct v4l2_subdev_ops rkisp1_csi_ops = { + .video = &rkisp1_csi_video_ops, + .pad = &rkisp1_csi_pad_ops, +}; + +int rkisp1_csi_register(struct rkisp1_device *rkisp1) +{ + struct rkisp1_csi *csi = &rkisp1->csi; + struct v4l2_subdev_state state = {}; + struct media_pad *pads; + struct v4l2_subdev *sd; + int ret; + + csi->rkisp1 = rkisp1; + mutex_init(&csi->lock); + + sd = &csi->sd; + v4l2_subdev_init(sd, &rkisp1_csi_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->entity.ops = &rkisp1_csi_media_ops; + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->owner = THIS_MODULE; + strscpy(sd->name, RKISP1_CSI_DEV_NAME, sizeof(sd->name)); + + pads = csi->pads; + pads[RKISP1_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[RKISP1_CSI_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; + + csi->sink_fmt = rkisp1_mbus_info_get_by_code(RKISP1_CSI_DEF_FMT); + + ret = media_entity_pads_init(&sd->entity, RKISP1_CSI_PAD_NUM, pads); + if (ret) + goto error; + + state.pads = csi->pad_cfg; + rkisp1_csi_init_config(sd, &state); + + ret = v4l2_device_register_subdev(&csi->rkisp1->v4l2_dev, sd); + if (ret) { + dev_err(sd->dev, "Failed to register csi receiver subdev\n"); + goto error; + } + + return 0; + +error: + media_entity_cleanup(&sd->entity); + mutex_destroy(&csi->lock); + csi->rkisp1 = NULL; + return ret; +} + +void rkisp1_csi_unregister(struct rkisp1_device *rkisp1) +{ + struct rkisp1_csi *csi = &rkisp1->csi; + + if (!csi->rkisp1) + return; + + v4l2_device_unregister_subdev(&csi->sd); + media_entity_cleanup(&csi->sd.entity); + mutex_destroy(&csi->lock); +} + +int rkisp1_csi_init(struct rkisp1_device *rkisp1) +{ + struct rkisp1_csi *csi = &rkisp1->csi; + + csi->rkisp1 = rkisp1; + + csi->dphy = devm_phy_get(rkisp1->dev, "dphy"); + if (IS_ERR(csi->dphy)) + return dev_err_probe(rkisp1->dev, PTR_ERR(csi->dphy), + "Couldn't get the MIPI D-PHY\n"); + + phy_init(csi->dphy); + + return 0; +} + +void rkisp1_csi_cleanup(struct rkisp1_device *rkisp1) +{ + struct rkisp1_csi *csi = &rkisp1->csi; + + phy_exit(csi->dphy); +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.h b/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.h new file mode 100644 index 000000000..1f5f2af31 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-csi.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Rockchip ISP1 Driver - CSI-2 Receiver + * + * Copyright (C) 2019 Collabora, Ltd. + * Copyright (C) 2022 Ideas on Board + * + * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd. + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ +#ifndef _RKISP1_CSI_H +#define _RKISP1_CSI_H + +struct rkisp1_csi; +struct rkisp1_device; +struct rkisp1_sensor_async; + +int rkisp1_csi_init(struct rkisp1_device *rkisp1); +void rkisp1_csi_cleanup(struct rkisp1_device *rkisp1); + +int rkisp1_csi_register(struct rkisp1_device *rkisp1); +void rkisp1_csi_unregister(struct rkisp1_device *rkisp1); + +int rkisp1_csi_link_sensor(struct rkisp1_device *rkisp1, struct v4l2_subdev *sd, + struct rkisp1_sensor_async *s_asd, + unsigned int source_pad); + +#endif /* _RKISP1_CSI_H */ diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-debug.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-debug.c new file mode 100644 index 000000000..71df3dc95 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-debug.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - Base driver + * + * Copyright (C) 2019 Collabora, Ltd. + * + * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd. + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "rkisp1-common.h" +#include "rkisp1-regs.h" + +struct rkisp1_debug_register { + u32 reg; + u32 shd; + const char * const name; +}; + +#define RKISP1_DEBUG_REG(name) { RKISP1_CIF_##name, 0, #name } +#define RKISP1_DEBUG_SHD_REG(name) { \ + RKISP1_CIF_##name, RKISP1_CIF_##name##_SHD, #name \ +} + +/* Keep this up-to-date when adding new registers. */ +#define RKISP1_MAX_REG_LENGTH 21 + +static int rkisp1_debug_dump_regs(struct rkisp1_device *rkisp1, + struct seq_file *m, unsigned int offset, + const struct rkisp1_debug_register *regs) +{ + const int width = RKISP1_MAX_REG_LENGTH; + u32 val, shd; + int ret; + + ret = pm_runtime_get_if_in_use(rkisp1->dev); + if (ret <= 0) + return ret ? : -ENODATA; + + for (; regs->name; ++regs) { + val = rkisp1_read(rkisp1, offset + regs->reg); + + if (regs->shd) { + shd = rkisp1_read(rkisp1, offset + regs->shd); + seq_printf(m, "%*s: 0x%08x/0x%08x\n", width, regs->name, + val, shd); + } else { + seq_printf(m, "%*s: 0x%08x\n", width, regs->name, val); + } + } + + pm_runtime_put(rkisp1->dev); + + return 0; +} + +static int rkisp1_debug_dump_core_regs_show(struct seq_file *m, void *p) +{ + static const struct rkisp1_debug_register registers[] = { + RKISP1_DEBUG_REG(VI_CCL), + RKISP1_DEBUG_REG(VI_ICCL), + RKISP1_DEBUG_REG(VI_IRCL), + RKISP1_DEBUG_REG(VI_DPCL), + RKISP1_DEBUG_REG(MI_CTRL), + RKISP1_DEBUG_REG(MI_BYTE_CNT), + RKISP1_DEBUG_REG(MI_CTRL_SHD), + RKISP1_DEBUG_REG(MI_RIS), + RKISP1_DEBUG_REG(MI_STATUS), + RKISP1_DEBUG_REG(MI_DMA_CTRL), + RKISP1_DEBUG_REG(MI_DMA_STATUS), + { /* Sentinel */ }, + }; + struct rkisp1_device *rkisp1 = m->private; + + return rkisp1_debug_dump_regs(rkisp1, m, 0, registers); +} +DEFINE_SHOW_ATTRIBUTE(rkisp1_debug_dump_core_regs); + +static int rkisp1_debug_dump_isp_regs_show(struct seq_file *m, void *p) +{ + static const struct rkisp1_debug_register registers[] = { + RKISP1_DEBUG_REG(ISP_CTRL), + RKISP1_DEBUG_REG(ISP_ACQ_PROP), + RKISP1_DEBUG_REG(ISP_FLAGS_SHD), + RKISP1_DEBUG_REG(ISP_RIS), + RKISP1_DEBUG_REG(ISP_ERR), + { /* Sentinel */ }, + }; + struct rkisp1_device *rkisp1 = m->private; + + return rkisp1_debug_dump_regs(rkisp1, m, 0, registers); +} +DEFINE_SHOW_ATTRIBUTE(rkisp1_debug_dump_isp_regs); + +static int rkisp1_debug_dump_rsz_regs_show(struct seq_file *m, void *p) +{ + static const struct rkisp1_debug_register registers[] = { + RKISP1_DEBUG_SHD_REG(RSZ_CTRL), + RKISP1_DEBUG_SHD_REG(RSZ_SCALE_HY), + RKISP1_DEBUG_SHD_REG(RSZ_SCALE_HCB), + RKISP1_DEBUG_SHD_REG(RSZ_SCALE_HCR), + RKISP1_DEBUG_SHD_REG(RSZ_SCALE_VY), + RKISP1_DEBUG_SHD_REG(RSZ_SCALE_VC), + RKISP1_DEBUG_SHD_REG(RSZ_PHASE_HY), + RKISP1_DEBUG_SHD_REG(RSZ_PHASE_HC), + RKISP1_DEBUG_SHD_REG(RSZ_PHASE_VY), + RKISP1_DEBUG_SHD_REG(RSZ_PHASE_VC), + { /* Sentinel */ }, + }; + struct rkisp1_resizer *rsz = m->private; + + return rkisp1_debug_dump_regs(rsz->rkisp1, m, rsz->regs_base, registers); +} +DEFINE_SHOW_ATTRIBUTE(rkisp1_debug_dump_rsz_regs); + +static int rkisp1_debug_dump_mi_mp_show(struct seq_file *m, void *p) +{ + static const struct rkisp1_debug_register registers[] = { + RKISP1_DEBUG_REG(MI_MP_Y_BASE_AD_INIT), + RKISP1_DEBUG_REG(MI_MP_Y_BASE_AD_INIT2), + RKISP1_DEBUG_REG(MI_MP_Y_BASE_AD_SHD), + RKISP1_DEBUG_REG(MI_MP_Y_SIZE_INIT), + RKISP1_DEBUG_REG(MI_MP_Y_SIZE_INIT), + RKISP1_DEBUG_REG(MI_MP_Y_SIZE_SHD), + RKISP1_DEBUG_REG(MI_MP_Y_OFFS_CNT_SHD), + { /* Sentinel */ }, + }; + struct rkisp1_device *rkisp1 = m->private; + + return rkisp1_debug_dump_regs(rkisp1, m, 0, registers); +} +DEFINE_SHOW_ATTRIBUTE(rkisp1_debug_dump_mi_mp); + +#define RKISP1_DEBUG_DATA_COUNT_BINS 32 +#define RKISP1_DEBUG_DATA_COUNT_STEP (4096 / RKISP1_DEBUG_DATA_COUNT_BINS) + +static int rkisp1_debug_input_status_show(struct seq_file *m, void *p) +{ + struct rkisp1_device *rkisp1 = m->private; + u16 data_count[RKISP1_DEBUG_DATA_COUNT_BINS] = { }; + unsigned int hsync_count = 0; + unsigned int vsync_count = 0; + unsigned int i; + u32 data; + u32 val; + int ret; + + ret = pm_runtime_get_if_in_use(rkisp1->dev); + if (ret <= 0) + return ret ? : -ENODATA; + + /* Sample the ISP input port status 10000 times with a 1µs interval. */ + for (i = 0; i < 10000; ++i) { + val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_FLAGS_SHD); + + data = (val & RKISP1_CIF_ISP_FLAGS_SHD_S_DATA_MASK) + >> RKISP1_CIF_ISP_FLAGS_SHD_S_DATA_SHIFT; + data_count[data / RKISP1_DEBUG_DATA_COUNT_STEP]++; + + if (val & RKISP1_CIF_ISP_FLAGS_SHD_S_HSYNC) + hsync_count++; + if (val & RKISP1_CIF_ISP_FLAGS_SHD_S_VSYNC) + vsync_count++; + + udelay(1); + } + + pm_runtime_put(rkisp1->dev); + + seq_printf(m, "vsync: %u, hsync: %u\n", vsync_count, hsync_count); + seq_puts(m, "data:\n"); + for (i = 0; i < ARRAY_SIZE(data_count); ++i) + seq_printf(m, "- [%04u:%04u]: %u\n", + i * RKISP1_DEBUG_DATA_COUNT_STEP, + (i + 1) * RKISP1_DEBUG_DATA_COUNT_STEP - 1, + data_count[i]); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(rkisp1_debug_input_status); + +void rkisp1_debug_init(struct rkisp1_device *rkisp1) +{ + struct rkisp1_debug *debug = &rkisp1->debug; + struct dentry *regs_dir; + + debug->debugfs_dir = debugfs_create_dir(dev_name(rkisp1->dev), NULL); + + debugfs_create_ulong("data_loss", 0444, debug->debugfs_dir, + &debug->data_loss); + debugfs_create_ulong("outform_size_err", 0444, debug->debugfs_dir, + &debug->outform_size_error); + debugfs_create_ulong("img_stabilization_size_error", 0444, + debug->debugfs_dir, + &debug->img_stabilization_size_error); + debugfs_create_ulong("inform_size_error", 0444, debug->debugfs_dir, + &debug->inform_size_error); + debugfs_create_ulong("irq_delay", 0444, debug->debugfs_dir, + &debug->irq_delay); + debugfs_create_ulong("mipi_error", 0444, debug->debugfs_dir, + &debug->mipi_error); + debugfs_create_ulong("stats_error", 0444, debug->debugfs_dir, + &debug->stats_error); + debugfs_create_ulong("mp_stop_timeout", 0444, debug->debugfs_dir, + &debug->stop_timeout[RKISP1_MAINPATH]); + debugfs_create_ulong("sp_stop_timeout", 0444, debug->debugfs_dir, + &debug->stop_timeout[RKISP1_SELFPATH]); + debugfs_create_ulong("mp_frame_drop", 0444, debug->debugfs_dir, + &debug->frame_drop[RKISP1_MAINPATH]); + debugfs_create_ulong("sp_frame_drop", 0444, debug->debugfs_dir, + &debug->frame_drop[RKISP1_SELFPATH]); + debugfs_create_file("input_status", 0444, debug->debugfs_dir, rkisp1, + &rkisp1_debug_input_status_fops); + + regs_dir = debugfs_create_dir("regs", debug->debugfs_dir); + + debugfs_create_file("core", 0444, regs_dir, rkisp1, + &rkisp1_debug_dump_core_regs_fops); + debugfs_create_file("isp", 0444, regs_dir, rkisp1, + &rkisp1_debug_dump_isp_regs_fops); + debugfs_create_file("mrsz", 0444, regs_dir, + &rkisp1->resizer_devs[RKISP1_MAINPATH], + &rkisp1_debug_dump_rsz_regs_fops); + debugfs_create_file("srsz", 0444, regs_dir, + &rkisp1->resizer_devs[RKISP1_SELFPATH], + &rkisp1_debug_dump_rsz_regs_fops); + + debugfs_create_file("mi_mp", 0444, regs_dir, rkisp1, + &rkisp1_debug_dump_mi_mp_fops); +} + +void rkisp1_debug_cleanup(struct rkisp1_device *rkisp1) +{ + debugfs_remove_recursive(rkisp1->debug.debugfs_dir); +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c new file mode 100644 index 000000000..2b76339f9 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-dev.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - Base driver + * + * Copyright (C) 2019 Collabora, Ltd. + * + * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd. + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rkisp1-common.h" +#include "rkisp1-csi.h" + +/* + * ISP Details + * ----------- + * + * ISP Comprises with: + * MIPI serial camera interface + * Image Signal Processing + * Many Image Enhancement Blocks + * Crop + * Resizer + * RBG display ready image + * Image Rotation + * + * ISP Block Diagram + * ----------------- + * rkisp1-resizer.c rkisp1-capture.c + * |====================| |=======================| + * rkisp1-isp.c Main Picture Path + * |==========================| |===============================================| + * +-----------+ +--+--+--+--+ +--------+ +--------+ +-----------+ + * | | | | | | | | | | | | | + * +--------+ |\ | | | | | | | -->| Crop |->| RSZ |------------->| | + * | MIPI |--->| \ | | | | | | | | | | | | | | + * +--------+ | | | | |IE|IE|IE|IE| | +--------+ +--------+ | Memory | + * |MUX|--->| ISP |->|0 |1 |2 |3 |---+ | Interface | + * +--------+ | | | | | | | | | | +--------+ +--------+ +--------+ | | + * |Parallel|--->| / | | | | | | | | | | | | | | | | + * +--------+ |/ | | | | | | | -->| Crop |->| RSZ |->| RGB |->| | + * | | | | | | | | | | | | Rotate | | | + * +-----------+ +--+--+--+--+ +--------+ +--------+ +--------+ +-----------+ + * ^ + * +--------+ | |===============================================| + * | DMA |------------------------------------+ Self Picture Path + * +--------+ + * + * rkisp1-stats.c rkisp1-params.c + * |===============| |===============| + * +---------------+ +---------------+ + * | | | | + * | ISP | | ISP | + * | | | | + * +---------------+ +---------------+ + * + * + * Media Topology + * -------------- + * + * +----------+ +----------+ + * | Sensor 1 | | Sensor X | + * ------------ ... ------------ + * | 0 | | 0 | + * +----------+ +----------+ + * | | + * \----\ /----/ + * | | + * v v + * +-------------+ + * | 0 | + * --------------- + * | CSI-2 RX | + * --------------- +-----------+ + * | 1 | | params | + * +-------------+ | (output) | + * | +-----------+ + * v | + * +------+------+ | + * | 0 | 1 |<---------+ + * |------+------| + * | ISP | + * |------+------| + * +-------------| 2 | 3 |----------+ + * | +------+------+ | + * | | | + * v v v + * +- ---------+ +-----------+ +-----------+ + * | 0 | | 0 | | stats | + * ------------- ------------- | (capture) | + * | Resizer | | Resizer | +-----------+ + * ------------| ------------| + * | 1 | | 1 | + * +-----------+ +-----------+ + * | | + * v v + * +-----------+ +-----------+ + * | selfpath | | mainpath | + * | (capture) | | (capture) | + * +-----------+ +-----------+ + */ + +struct rkisp1_isr_data { + const char *name; + irqreturn_t (*isr)(int irq, void *ctx); +}; + +/* ---------------------------------------------------------------------------- + * Sensor DT bindings + */ + +static int rkisp1_subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct rkisp1_device *rkisp1 = + container_of(notifier, struct rkisp1_device, notifier); + struct rkisp1_sensor_async *s_asd = + container_of(asd, struct rkisp1_sensor_async, asd); + int source_pad; + int ret; + + s_asd->sd = sd; + + source_pad = media_entity_get_fwnode_pad(&sd->entity, s_asd->source_ep, + MEDIA_PAD_FL_SOURCE); + if (source_pad < 0) { + dev_err(rkisp1->dev, "failed to find source pad for %s\n", + sd->name); + return source_pad; + } + + if (s_asd->port == 0) + return rkisp1_csi_link_sensor(rkisp1, sd, s_asd, source_pad); + + ret = media_create_pad_link(&sd->entity, source_pad, + &rkisp1->isp.sd.entity, + RKISP1_ISP_PAD_SINK_VIDEO, + !s_asd->index ? MEDIA_LNK_FL_ENABLED : 0); + if (ret) { + dev_err(rkisp1->dev, "failed to link source pad of %s\n", + sd->name); + return ret; + } + + return 0; +} + +static int rkisp1_subdev_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct rkisp1_device *rkisp1 = + container_of(notifier, struct rkisp1_device, notifier); + + return v4l2_device_register_subdev_nodes(&rkisp1->v4l2_dev); +} + +static void rkisp1_subdev_notifier_destroy(struct v4l2_async_subdev *asd) +{ + struct rkisp1_sensor_async *rk_asd = + container_of(asd, struct rkisp1_sensor_async, asd); + + fwnode_handle_put(rk_asd->source_ep); +} + +static const struct v4l2_async_notifier_operations rkisp1_subdev_notifier_ops = { + .bound = rkisp1_subdev_notifier_bound, + .complete = rkisp1_subdev_notifier_complete, + .destroy = rkisp1_subdev_notifier_destroy, +}; + +static int rkisp1_subdev_notifier_register(struct rkisp1_device *rkisp1) +{ + struct v4l2_async_notifier *ntf = &rkisp1->notifier; + struct fwnode_handle *fwnode = dev_fwnode(rkisp1->dev); + struct fwnode_handle *ep; + unsigned int index = 0; + int ret = 0; + + v4l2_async_nf_init(ntf); + + ntf->ops = &rkisp1_subdev_notifier_ops; + + fwnode_graph_for_each_endpoint(fwnode, ep) { + struct fwnode_handle *port; + struct v4l2_fwnode_endpoint vep = { }; + struct rkisp1_sensor_async *rk_asd; + struct fwnode_handle *source; + u32 reg = 0; + + /* Select the bus type based on the port. */ + port = fwnode_get_parent(ep); + fwnode_property_read_u32(port, "reg", ®); + fwnode_handle_put(port); + + switch (reg) { + case 0: + /* MIPI CSI-2 port */ + if (!(rkisp1->info->features & RKISP1_FEATURE_MIPI_CSI2)) { + dev_err(rkisp1->dev, + "internal CSI must be available for port 0\n"); + ret = -EINVAL; + break; + } + + vep.bus_type = V4L2_MBUS_CSI2_DPHY; + break; + + case 1: + /* + * Parallel port. The bus-type property in DT is + * mandatory for port 1, it will be used to determine if + * it's PARALLEL or BT656. + */ + vep.bus_type = V4L2_MBUS_UNKNOWN; + break; + } + + /* Parse the endpoint and validate the bus type. */ + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) { + dev_err(rkisp1->dev, "failed to parse endpoint %pfw\n", + ep); + break; + } + + if (vep.base.port == 1) { + if (vep.bus_type != V4L2_MBUS_PARALLEL && + vep.bus_type != V4L2_MBUS_BT656) { + dev_err(rkisp1->dev, + "port 1 must be parallel or BT656\n"); + ret = -EINVAL; + break; + } + } + + /* Add the async subdev to the notifier. */ + source = fwnode_graph_get_remote_endpoint(ep); + if (!source) { + dev_err(rkisp1->dev, + "endpoint %pfw has no remote endpoint\n", + ep); + ret = -ENODEV; + break; + } + + rk_asd = v4l2_async_nf_add_fwnode(ntf, source, + struct rkisp1_sensor_async); + if (IS_ERR(rk_asd)) { + fwnode_handle_put(source); + ret = PTR_ERR(rk_asd); + break; + } + + rk_asd->index = index++; + rk_asd->source_ep = source; + rk_asd->mbus_type = vep.bus_type; + rk_asd->port = vep.base.port; + + if (vep.bus_type == V4L2_MBUS_CSI2_DPHY) { + rk_asd->mbus_flags = vep.bus.mipi_csi2.flags; + rk_asd->lanes = vep.bus.mipi_csi2.num_data_lanes; + } else { + rk_asd->mbus_flags = vep.bus.parallel.flags; + } + + dev_dbg(rkisp1->dev, "registered ep id %d, bus type %u, %u lanes\n", + vep.base.id, rk_asd->mbus_type, rk_asd->lanes); + } + + if (ret) { + fwnode_handle_put(ep); + v4l2_async_nf_cleanup(ntf); + return ret; + } + + if (!index) + dev_dbg(rkisp1->dev, "no remote subdevice found\n"); + + ret = v4l2_async_nf_register(&rkisp1->v4l2_dev, ntf); + if (ret) { + v4l2_async_nf_cleanup(ntf); + return ret; + } + + return 0; +} + +/* ---------------------------------------------------------------------------- + * Power + */ + +static int __maybe_unused rkisp1_runtime_suspend(struct device *dev) +{ + struct rkisp1_device *rkisp1 = dev_get_drvdata(dev); + + clk_bulk_disable_unprepare(rkisp1->clk_size, rkisp1->clks); + return pinctrl_pm_select_sleep_state(dev); +} + +static int __maybe_unused rkisp1_runtime_resume(struct device *dev) +{ + struct rkisp1_device *rkisp1 = dev_get_drvdata(dev); + int ret; + + ret = pinctrl_pm_select_default_state(dev); + if (ret) + return ret; + ret = clk_bulk_prepare_enable(rkisp1->clk_size, rkisp1->clks); + if (ret) + return ret; + + return 0; +} + +static const struct dev_pm_ops rkisp1_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(rkisp1_runtime_suspend, rkisp1_runtime_resume, NULL) +}; + +/* ---------------------------------------------------------------------------- + * Core + */ + +static int rkisp1_create_links(struct rkisp1_device *rkisp1) +{ + unsigned int i; + int ret; + + if (rkisp1->info->features & RKISP1_FEATURE_MIPI_CSI2) { + /* Link the CSI receiver to the ISP. */ + ret = media_create_pad_link(&rkisp1->csi.sd.entity, + RKISP1_CSI_PAD_SRC, + &rkisp1->isp.sd.entity, + RKISP1_ISP_PAD_SINK_VIDEO, + MEDIA_LNK_FL_ENABLED); + if (ret) + return ret; + } + + /* create ISP->RSZ->CAP links */ + for (i = 0; i < 2; i++) { + struct media_entity *resizer = + &rkisp1->resizer_devs[i].sd.entity; + struct media_entity *capture = + &rkisp1->capture_devs[i].vnode.vdev.entity; + + ret = media_create_pad_link(&rkisp1->isp.sd.entity, + RKISP1_ISP_PAD_SOURCE_VIDEO, + resizer, RKISP1_RSZ_PAD_SINK, + MEDIA_LNK_FL_ENABLED); + if (ret) + return ret; + + ret = media_create_pad_link(resizer, RKISP1_RSZ_PAD_SRC, + capture, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) + return ret; + } + + /* params links */ + ret = media_create_pad_link(&rkisp1->params.vnode.vdev.entity, 0, + &rkisp1->isp.sd.entity, + RKISP1_ISP_PAD_SINK_PARAMS, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) + return ret; + + /* 3A stats links */ + return media_create_pad_link(&rkisp1->isp.sd.entity, + RKISP1_ISP_PAD_SOURCE_STATS, + &rkisp1->stats.vnode.vdev.entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); +} + +static void rkisp1_entities_unregister(struct rkisp1_device *rkisp1) +{ + if (rkisp1->info->features & RKISP1_FEATURE_MIPI_CSI2) + rkisp1_csi_unregister(rkisp1); + rkisp1_params_unregister(rkisp1); + rkisp1_stats_unregister(rkisp1); + rkisp1_capture_devs_unregister(rkisp1); + rkisp1_resizer_devs_unregister(rkisp1); + rkisp1_isp_unregister(rkisp1); +} + +static int rkisp1_entities_register(struct rkisp1_device *rkisp1) +{ + int ret; + + ret = rkisp1_isp_register(rkisp1); + if (ret) + goto error; + + ret = rkisp1_resizer_devs_register(rkisp1); + if (ret) + goto error; + + ret = rkisp1_capture_devs_register(rkisp1); + if (ret) + goto error; + + ret = rkisp1_stats_register(rkisp1); + if (ret) + goto error; + + ret = rkisp1_params_register(rkisp1); + if (ret) + goto error; + + if (rkisp1->info->features & RKISP1_FEATURE_MIPI_CSI2) { + ret = rkisp1_csi_register(rkisp1); + if (ret) + goto error; + } + + ret = rkisp1_create_links(rkisp1); + if (ret) + goto error; + + return 0; + +error: + rkisp1_entities_unregister(rkisp1); + return ret; +} + +static irqreturn_t rkisp1_isr(int irq, void *ctx) +{ + /* + * Call rkisp1_capture_isr() first to handle the frame that + * potentially completed using the current frame_sequence number before + * it is potentially incremented by rkisp1_isp_isr() in the vertical + * sync. + */ + rkisp1_capture_isr(irq, ctx); + rkisp1_isp_isr(irq, ctx); + rkisp1_csi_isr(irq, ctx); + + return IRQ_HANDLED; +} + +static const char * const px30_isp_clks[] = { + "isp", + "aclk", + "hclk", + "pclk", +}; + +static const struct rkisp1_isr_data px30_isp_isrs[] = { + { "isp", rkisp1_isp_isr }, + { "mi", rkisp1_capture_isr }, + { "mipi", rkisp1_csi_isr }, +}; + +static const struct rkisp1_info px30_isp_info = { + .clks = px30_isp_clks, + .clk_size = ARRAY_SIZE(px30_isp_clks), + .isrs = px30_isp_isrs, + .isr_size = ARRAY_SIZE(px30_isp_isrs), + .isp_ver = RKISP1_V12, + .features = RKISP1_FEATURE_MIPI_CSI2, +}; + +static const char * const rk3399_isp_clks[] = { + "isp", + "aclk", + "hclk", +}; + +static const struct rkisp1_isr_data rk3399_isp_isrs[] = { + { NULL, rkisp1_isr }, +}; + +static const struct rkisp1_info rk3399_isp_info = { + .clks = rk3399_isp_clks, + .clk_size = ARRAY_SIZE(rk3399_isp_clks), + .isrs = rk3399_isp_isrs, + .isr_size = ARRAY_SIZE(rk3399_isp_isrs), + .isp_ver = RKISP1_V10, + .features = RKISP1_FEATURE_MIPI_CSI2, +}; + +static const struct of_device_id rkisp1_of_match[] = { + { + .compatible = "rockchip,px30-cif-isp", + .data = &px30_isp_info, + }, + { + .compatible = "rockchip,rk3399-cif-isp", + .data = &rk3399_isp_info, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, rkisp1_of_match); + +static int rkisp1_probe(struct platform_device *pdev) +{ + const struct rkisp1_info *info; + struct device *dev = &pdev->dev; + struct rkisp1_device *rkisp1; + struct v4l2_device *v4l2_dev; + unsigned int i; + int ret, irq; + u32 cif_id; + + rkisp1 = devm_kzalloc(dev, sizeof(*rkisp1), GFP_KERNEL); + if (!rkisp1) + return -ENOMEM; + + info = of_device_get_match_data(dev); + rkisp1->info = info; + + dev_set_drvdata(dev, rkisp1); + rkisp1->dev = dev; + + mutex_init(&rkisp1->stream_lock); + + rkisp1->base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(rkisp1->base_addr)) + return PTR_ERR(rkisp1->base_addr); + + for (i = 0; i < info->isr_size; i++) { + irq = info->isrs[i].name + ? platform_get_irq_byname(pdev, info->isrs[i].name) + : platform_get_irq(pdev, i); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, info->isrs[i].isr, IRQF_SHARED, + dev_driver_string(dev), dev); + if (ret) { + dev_err(dev, "request irq failed: %d\n", ret); + return ret; + } + } + + for (i = 0; i < info->clk_size; i++) + rkisp1->clks[i].id = info->clks[i]; + ret = devm_clk_bulk_get(dev, info->clk_size, rkisp1->clks); + if (ret) + return ret; + rkisp1->clk_size = info->clk_size; + + pm_runtime_enable(&pdev->dev); + + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret) + goto err_pm_runtime_disable; + + cif_id = rkisp1_read(rkisp1, RKISP1_CIF_VI_ID); + dev_dbg(rkisp1->dev, "CIF_ID 0x%08x\n", cif_id); + + pm_runtime_put(&pdev->dev); + + rkisp1->media_dev.hw_revision = info->isp_ver; + strscpy(rkisp1->media_dev.model, RKISP1_DRIVER_NAME, + sizeof(rkisp1->media_dev.model)); + rkisp1->media_dev.dev = &pdev->dev; + strscpy(rkisp1->media_dev.bus_info, RKISP1_BUS_INFO, + sizeof(rkisp1->media_dev.bus_info)); + media_device_init(&rkisp1->media_dev); + + v4l2_dev = &rkisp1->v4l2_dev; + v4l2_dev->mdev = &rkisp1->media_dev; + strscpy(v4l2_dev->name, RKISP1_DRIVER_NAME, sizeof(v4l2_dev->name)); + + ret = v4l2_device_register(rkisp1->dev, &rkisp1->v4l2_dev); + if (ret) + goto err_media_dev_cleanup; + + ret = media_device_register(&rkisp1->media_dev); + if (ret) { + dev_err(dev, "Failed to register media device: %d\n", ret); + goto err_unreg_v4l2_dev; + } + + if (rkisp1->info->features & RKISP1_FEATURE_MIPI_CSI2) { + ret = rkisp1_csi_init(rkisp1); + if (ret) + goto err_unreg_media_dev; + } + + ret = rkisp1_entities_register(rkisp1); + if (ret) + goto err_cleanup_csi; + + ret = rkisp1_subdev_notifier_register(rkisp1); + if (ret) + goto err_unreg_entities; + + rkisp1_debug_init(rkisp1); + + return 0; + +err_unreg_entities: + rkisp1_entities_unregister(rkisp1); +err_cleanup_csi: + if (rkisp1->info->features & RKISP1_FEATURE_MIPI_CSI2) + rkisp1_csi_cleanup(rkisp1); +err_unreg_media_dev: + media_device_unregister(&rkisp1->media_dev); +err_unreg_v4l2_dev: + v4l2_device_unregister(&rkisp1->v4l2_dev); +err_media_dev_cleanup: + media_device_cleanup(&rkisp1->media_dev); +err_pm_runtime_disable: + pm_runtime_disable(&pdev->dev); + return ret; +} + +static int rkisp1_remove(struct platform_device *pdev) +{ + struct rkisp1_device *rkisp1 = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&rkisp1->notifier); + v4l2_async_nf_cleanup(&rkisp1->notifier); + + rkisp1_entities_unregister(rkisp1); + if (rkisp1->info->features & RKISP1_FEATURE_MIPI_CSI2) + rkisp1_csi_cleanup(rkisp1); + rkisp1_debug_cleanup(rkisp1); + + media_device_unregister(&rkisp1->media_dev); + v4l2_device_unregister(&rkisp1->v4l2_dev); + + media_device_cleanup(&rkisp1->media_dev); + + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static struct platform_driver rkisp1_drv = { + .driver = { + .name = RKISP1_DRIVER_NAME, + .of_match_table = of_match_ptr(rkisp1_of_match), + .pm = &rkisp1_pm_ops, + }, + .probe = rkisp1_probe, + .remove = rkisp1_remove, +}; + +module_platform_driver(rkisp1_drv); +MODULE_DESCRIPTION("Rockchip ISP1 platform driver"); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c new file mode 100644 index 000000000..585cf3f53 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-isp.c @@ -0,0 +1,1058 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - ISP Subdevice + * + * Copyright (C) 2019 Collabora, Ltd. + * + * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd. + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include + +#include + +#include "rkisp1-common.h" + +#define RKISP1_DEF_SINK_PAD_FMT MEDIA_BUS_FMT_SRGGB10_1X10 +#define RKISP1_DEF_SRC_PAD_FMT MEDIA_BUS_FMT_YUYV8_2X8 + +#define RKISP1_ISP_DEV_NAME RKISP1_DRIVER_NAME "_isp" + +/* + * NOTE: MIPI controller and input MUX are also configured in this file. + * This is because ISP Subdev describes not only ISP submodule (input size, + * format, output size, format), but also a virtual route device. + */ + +/* + * There are many variables named with format/frame in below code, + * please see here for their meaning. + * Cropping in the sink pad defines the image region from the sensor. + * Cropping in the source pad defines the region for the Image Stabilizer (IS) + * + * Cropping regions of ISP + * + * +---------------------------------------------------------+ + * | Sensor image | + * | +---------------------------------------------------+ | + * | | CIF_ISP_ACQ (for black level) | | + * | | sink pad format | | + * | | +--------------------------------------------+ | | + * | | | CIF_ISP_OUT | | | + * | | | sink pad crop | | | + * | | | +---------------------------------+ | | | + * | | | | CIF_ISP_IS | | | | + * | | | | source pad crop and format | | | | + * | | | +---------------------------------+ | | | + * | | +--------------------------------------------+ | | + * | +---------------------------------------------------+ | + * +---------------------------------------------------------+ + */ + +/* ---------------------------------------------------------------------------- + * Helpers + */ + +static struct v4l2_mbus_framefmt * +rkisp1_isp_get_pad_fmt(struct rkisp1_isp *isp, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + struct v4l2_subdev_state state = { + .pads = isp->pad_cfg + }; + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&isp->sd, sd_state, pad); + else + return v4l2_subdev_get_try_format(&isp->sd, &state, pad); +} + +static struct v4l2_rect * +rkisp1_isp_get_pad_crop(struct rkisp1_isp *isp, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + struct v4l2_subdev_state state = { + .pads = isp->pad_cfg + }; + + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&isp->sd, sd_state, pad); + else + return v4l2_subdev_get_try_crop(&isp->sd, &state, pad); +} + +/* ---------------------------------------------------------------------------- + * Camera Interface registers configurations + */ + +/* + * Image Stabilization. + * This should only be called when configuring CIF + * or at the frame end interrupt + */ +static void rkisp1_config_ism(struct rkisp1_isp *isp) +{ + const struct v4l2_rect *src_crop = + rkisp1_isp_get_pad_crop(isp, NULL, + RKISP1_ISP_PAD_SOURCE_VIDEO, + V4L2_SUBDEV_FORMAT_ACTIVE); + struct rkisp1_device *rkisp1 = isp->rkisp1; + u32 val; + + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_RECENTER, 0); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_MAX_DX, 0); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_MAX_DY, 0); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_DISPLACE, 0); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_H_OFFS, src_crop->left); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_V_OFFS, src_crop->top); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_H_SIZE, src_crop->width); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_V_SIZE, src_crop->height); + + /* IS(Image Stabilization) is always on, working as output crop */ + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IS_CTRL, 1); + val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_CTRL); + val |= RKISP1_CIF_ISP_CTRL_ISP_CFG_UPD; + rkisp1_write(rkisp1, RKISP1_CIF_ISP_CTRL, val); +} + +/* + * configure ISP blocks with input format, size...... + */ +static int rkisp1_config_isp(struct rkisp1_isp *isp, + enum v4l2_mbus_type mbus_type, u32 mbus_flags) +{ + struct rkisp1_device *rkisp1 = isp->rkisp1; + u32 isp_ctrl = 0, irq_mask = 0, acq_mult = 0, acq_prop = 0; + const struct rkisp1_mbus_info *sink_fmt = isp->sink_fmt; + const struct rkisp1_mbus_info *src_fmt = isp->src_fmt; + const struct v4l2_mbus_framefmt *sink_frm; + const struct v4l2_rect *sink_crop; + + sink_frm = rkisp1_isp_get_pad_fmt(isp, NULL, + RKISP1_ISP_PAD_SINK_VIDEO, + V4L2_SUBDEV_FORMAT_ACTIVE); + sink_crop = rkisp1_isp_get_pad_crop(isp, NULL, + RKISP1_ISP_PAD_SINK_VIDEO, + V4L2_SUBDEV_FORMAT_ACTIVE); + + if (sink_fmt->pixel_enc == V4L2_PIXEL_ENC_BAYER) { + acq_mult = 1; + if (src_fmt->pixel_enc == V4L2_PIXEL_ENC_BAYER) { + if (mbus_type == V4L2_MBUS_BT656) + isp_ctrl = RKISP1_CIF_ISP_CTRL_ISP_MODE_RAW_PICT_ITU656; + else + isp_ctrl = RKISP1_CIF_ISP_CTRL_ISP_MODE_RAW_PICT; + } else { + rkisp1_write(rkisp1, RKISP1_CIF_ISP_DEMOSAIC, + RKISP1_CIF_ISP_DEMOSAIC_TH(0xc)); + + if (mbus_type == V4L2_MBUS_BT656) + isp_ctrl = RKISP1_CIF_ISP_CTRL_ISP_MODE_BAYER_ITU656; + else + isp_ctrl = RKISP1_CIF_ISP_CTRL_ISP_MODE_BAYER_ITU601; + } + } else if (sink_fmt->pixel_enc == V4L2_PIXEL_ENC_YUV) { + acq_mult = 2; + if (mbus_type == V4L2_MBUS_CSI2_DPHY) { + isp_ctrl = RKISP1_CIF_ISP_CTRL_ISP_MODE_ITU601; + } else { + if (mbus_type == V4L2_MBUS_BT656) + isp_ctrl = RKISP1_CIF_ISP_CTRL_ISP_MODE_ITU656; + else + isp_ctrl = RKISP1_CIF_ISP_CTRL_ISP_MODE_ITU601; + } + + irq_mask |= RKISP1_CIF_ISP_DATA_LOSS; + } + + /* Set up input acquisition properties */ + if (mbus_type == V4L2_MBUS_BT656 || mbus_type == V4L2_MBUS_PARALLEL) { + if (mbus_flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + acq_prop |= RKISP1_CIF_ISP_ACQ_PROP_POS_EDGE; + + switch (sink_fmt->bus_width) { + case 8: + acq_prop |= RKISP1_CIF_ISP_ACQ_PROP_IN_SEL_8B_ZERO; + break; + case 10: + acq_prop |= RKISP1_CIF_ISP_ACQ_PROP_IN_SEL_10B_ZERO; + break; + case 12: + acq_prop |= RKISP1_CIF_ISP_ACQ_PROP_IN_SEL_12B; + break; + default: + dev_err(rkisp1->dev, "Invalid bus width %u\n", + sink_fmt->bus_width); + return -EINVAL; + } + } + + if (mbus_type == V4L2_MBUS_PARALLEL) { + if (mbus_flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + acq_prop |= RKISP1_CIF_ISP_ACQ_PROP_VSYNC_LOW; + + if (mbus_flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + acq_prop |= RKISP1_CIF_ISP_ACQ_PROP_HSYNC_LOW; + } + + rkisp1_write(rkisp1, RKISP1_CIF_ISP_CTRL, isp_ctrl); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ACQ_PROP, + acq_prop | sink_fmt->yuv_seq | + RKISP1_CIF_ISP_ACQ_PROP_BAYER_PAT(sink_fmt->bayer_pat) | + RKISP1_CIF_ISP_ACQ_PROP_FIELD_SEL_ALL); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ACQ_NR_FRAMES, 0); + + /* Acquisition Size */ + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ACQ_H_OFFS, 0); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ACQ_V_OFFS, 0); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ACQ_H_SIZE, + acq_mult * sink_frm->width); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ACQ_V_SIZE, sink_frm->height); + + /* ISP Out Area */ + rkisp1_write(rkisp1, RKISP1_CIF_ISP_OUT_H_OFFS, sink_crop->left); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_OUT_V_OFFS, sink_crop->top); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_OUT_H_SIZE, sink_crop->width); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_OUT_V_SIZE, sink_crop->height); + + irq_mask |= RKISP1_CIF_ISP_FRAME | RKISP1_CIF_ISP_V_START | + RKISP1_CIF_ISP_PIC_SIZE_ERROR; + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IMSC, irq_mask); + + if (src_fmt->pixel_enc == V4L2_PIXEL_ENC_BAYER) { + rkisp1_params_disable(&rkisp1->params); + } else { + struct v4l2_mbus_framefmt *src_frm; + + src_frm = rkisp1_isp_get_pad_fmt(isp, NULL, + RKISP1_ISP_PAD_SOURCE_VIDEO, + V4L2_SUBDEV_FORMAT_ACTIVE); + rkisp1_params_pre_configure(&rkisp1->params, sink_fmt->bayer_pat, + src_frm->quantization, + src_frm->ycbcr_enc); + } + + return 0; +} + +/* Configure MUX */ +static void rkisp1_config_path(struct rkisp1_isp *isp, + enum v4l2_mbus_type mbus_type) +{ + struct rkisp1_device *rkisp1 = isp->rkisp1; + u32 dpcl = rkisp1_read(rkisp1, RKISP1_CIF_VI_DPCL); + + if (mbus_type == V4L2_MBUS_BT656 || mbus_type == V4L2_MBUS_PARALLEL) + dpcl |= RKISP1_CIF_VI_DPCL_IF_SEL_PARALLEL; + else if (mbus_type == V4L2_MBUS_CSI2_DPHY) + dpcl |= RKISP1_CIF_VI_DPCL_IF_SEL_MIPI; + + rkisp1_write(rkisp1, RKISP1_CIF_VI_DPCL, dpcl); +} + +/* Hardware configure Entry */ +static int rkisp1_config_cif(struct rkisp1_isp *isp, + enum v4l2_mbus_type mbus_type, u32 mbus_flags) +{ + int ret; + + ret = rkisp1_config_isp(isp, mbus_type, mbus_flags); + if (ret) + return ret; + + rkisp1_config_path(isp, mbus_type); + rkisp1_config_ism(isp); + + return 0; +} + +static void rkisp1_isp_stop(struct rkisp1_isp *isp) +{ + struct rkisp1_device *rkisp1 = isp->rkisp1; + u32 val; + + /* + * ISP(mi) stop in mi frame end -> Stop ISP(mipi) -> + * Stop ISP(isp) ->wait for ISP isp off + */ + /* stop and clear MI and ISP interrupts */ + rkisp1_write(rkisp1, RKISP1_CIF_ISP_IMSC, 0); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ICR, ~0); + + rkisp1_write(rkisp1, RKISP1_CIF_MI_IMSC, 0); + rkisp1_write(rkisp1, RKISP1_CIF_MI_ICR, ~0); + + /* stop ISP */ + val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_CTRL); + val &= ~(RKISP1_CIF_ISP_CTRL_ISP_INFORM_ENABLE | + RKISP1_CIF_ISP_CTRL_ISP_ENABLE); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_CTRL, val); + + val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_CTRL); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_CTRL, + val | RKISP1_CIF_ISP_CTRL_ISP_CFG_UPD); + + readx_poll_timeout(readl, rkisp1->base_addr + RKISP1_CIF_ISP_RIS, + val, val & RKISP1_CIF_ISP_OFF, 20, 100); + rkisp1_write(rkisp1, RKISP1_CIF_VI_IRCL, + RKISP1_CIF_VI_IRCL_MIPI_SW_RST | + RKISP1_CIF_VI_IRCL_ISP_SW_RST); + rkisp1_write(rkisp1, RKISP1_CIF_VI_IRCL, 0x0); +} + +static void rkisp1_config_clk(struct rkisp1_isp *isp) +{ + struct rkisp1_device *rkisp1 = isp->rkisp1; + + u32 val = RKISP1_CIF_VI_ICCL_ISP_CLK | RKISP1_CIF_VI_ICCL_CP_CLK | + RKISP1_CIF_VI_ICCL_MRSZ_CLK | RKISP1_CIF_VI_ICCL_SRSZ_CLK | + RKISP1_CIF_VI_ICCL_JPEG_CLK | RKISP1_CIF_VI_ICCL_MI_CLK | + RKISP1_CIF_VI_ICCL_IE_CLK | RKISP1_CIF_VI_ICCL_MIPI_CLK | + RKISP1_CIF_VI_ICCL_DCROP_CLK; + + rkisp1_write(rkisp1, RKISP1_CIF_VI_ICCL, val); + + /* ensure sp and mp can run at the same time in V12 */ + if (rkisp1->info->isp_ver == RKISP1_V12) { + val = RKISP1_CIF_CLK_CTRL_MI_Y12 | RKISP1_CIF_CLK_CTRL_MI_SP | + RKISP1_CIF_CLK_CTRL_MI_RAW0 | RKISP1_CIF_CLK_CTRL_MI_RAW1 | + RKISP1_CIF_CLK_CTRL_MI_READ | RKISP1_CIF_CLK_CTRL_MI_RAWRD | + RKISP1_CIF_CLK_CTRL_CP | RKISP1_CIF_CLK_CTRL_IE; + rkisp1_write(rkisp1, RKISP1_CIF_VI_ISP_CLK_CTRL_V12, val); + } +} + +static void rkisp1_isp_start(struct rkisp1_isp *isp) +{ + struct rkisp1_device *rkisp1 = isp->rkisp1; + u32 val; + + rkisp1_config_clk(isp); + + /* Activate ISP */ + val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_CTRL); + val |= RKISP1_CIF_ISP_CTRL_ISP_CFG_UPD | + RKISP1_CIF_ISP_CTRL_ISP_ENABLE | + RKISP1_CIF_ISP_CTRL_ISP_INFORM_ENABLE; + rkisp1_write(rkisp1, RKISP1_CIF_ISP_CTRL, val); + + if (isp->src_fmt->pixel_enc != V4L2_PIXEL_ENC_BAYER) + rkisp1_params_post_configure(&rkisp1->params); +} + +/* ---------------------------------------------------------------------------- + * Subdev pad operations + */ + +static inline struct rkisp1_isp *to_rkisp1_isp(struct v4l2_subdev *sd) +{ + return container_of(sd, struct rkisp1_isp, sd); +} + +static int rkisp1_isp_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + unsigned int i, dir; + int pos = 0; + + if (code->pad == RKISP1_ISP_PAD_SINK_VIDEO) { + dir = RKISP1_ISP_SD_SINK; + } else if (code->pad == RKISP1_ISP_PAD_SOURCE_VIDEO) { + dir = RKISP1_ISP_SD_SRC; + } else { + if (code->index > 0) + return -EINVAL; + code->code = MEDIA_BUS_FMT_METADATA_FIXED; + return 0; + } + + for (i = 0; ; i++) { + const struct rkisp1_mbus_info *fmt = + rkisp1_mbus_info_get_by_index(i); + + if (!fmt) + return -EINVAL; + + if (fmt->direction & dir) + pos++; + + if (code->index == pos - 1) { + code->code = fmt->mbus_code; + if (fmt->pixel_enc == V4L2_PIXEL_ENC_YUV && + dir == RKISP1_ISP_SD_SRC) + code->flags = + V4L2_SUBDEV_MBUS_CODE_CSC_QUANTIZATION; + return 0; + } + } + + return -EINVAL; +} + +static int rkisp1_isp_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct rkisp1_mbus_info *mbus_info; + + if (fse->pad == RKISP1_ISP_PAD_SINK_PARAMS || + fse->pad == RKISP1_ISP_PAD_SOURCE_STATS) + return -ENOTTY; + + if (fse->index > 0) + return -EINVAL; + + mbus_info = rkisp1_mbus_info_get_by_code(fse->code); + if (!mbus_info) + return -EINVAL; + + if (!(mbus_info->direction & RKISP1_ISP_SD_SINK) && + fse->pad == RKISP1_ISP_PAD_SINK_VIDEO) + return -EINVAL; + + if (!(mbus_info->direction & RKISP1_ISP_SD_SRC) && + fse->pad == RKISP1_ISP_PAD_SOURCE_VIDEO) + return -EINVAL; + + fse->min_width = RKISP1_ISP_MIN_WIDTH; + fse->max_width = RKISP1_ISP_MAX_WIDTH; + fse->min_height = RKISP1_ISP_MIN_HEIGHT; + fse->max_height = RKISP1_ISP_MAX_HEIGHT; + + return 0; +} + +static int rkisp1_isp_init_config(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *sink_crop, *src_crop; + + /* Video. */ + sink_fmt = v4l2_subdev_get_try_format(sd, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO); + sink_fmt->width = RKISP1_DEFAULT_WIDTH; + sink_fmt->height = RKISP1_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = RKISP1_DEF_SINK_PAD_FMT; + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; + sink_fmt->xfer_func = V4L2_XFER_FUNC_NONE; + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + + sink_crop = v4l2_subdev_get_try_crop(sd, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO); + sink_crop->width = RKISP1_DEFAULT_WIDTH; + sink_crop->height = RKISP1_DEFAULT_HEIGHT; + sink_crop->left = 0; + sink_crop->top = 0; + + src_fmt = v4l2_subdev_get_try_format(sd, sd_state, + RKISP1_ISP_PAD_SOURCE_VIDEO); + *src_fmt = *sink_fmt; + src_fmt->code = RKISP1_DEF_SRC_PAD_FMT; + src_fmt->colorspace = V4L2_COLORSPACE_SRGB; + src_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + src_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + src_crop = v4l2_subdev_get_try_crop(sd, sd_state, + RKISP1_ISP_PAD_SOURCE_VIDEO); + *src_crop = *sink_crop; + + /* Parameters and statistics. */ + sink_fmt = v4l2_subdev_get_try_format(sd, sd_state, + RKISP1_ISP_PAD_SINK_PARAMS); + src_fmt = v4l2_subdev_get_try_format(sd, sd_state, + RKISP1_ISP_PAD_SOURCE_STATS); + sink_fmt->width = 0; + sink_fmt->height = 0; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED; + *src_fmt = *sink_fmt; + + return 0; +} + +static void rkisp1_isp_set_src_fmt(struct rkisp1_isp *isp, + struct v4l2_subdev_state *sd_state, + struct v4l2_mbus_framefmt *format, + unsigned int which) +{ + const struct rkisp1_mbus_info *sink_info; + const struct rkisp1_mbus_info *src_info; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_mbus_framefmt *src_fmt; + const struct v4l2_rect *src_crop; + bool set_csc; + + sink_fmt = rkisp1_isp_get_pad_fmt(isp, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO, which); + src_fmt = rkisp1_isp_get_pad_fmt(isp, sd_state, + RKISP1_ISP_PAD_SOURCE_VIDEO, which); + src_crop = rkisp1_isp_get_pad_crop(isp, sd_state, + RKISP1_ISP_PAD_SOURCE_VIDEO, which); + + /* + * Media bus code. The ISP can operate in pass-through mode (Bayer in, + * Bayer out or YUV in, YUV out) or process Bayer data to YUV, but + * can't convert from YUV to Bayer. + */ + sink_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + + src_fmt->code = format->code; + src_info = rkisp1_mbus_info_get_by_code(src_fmt->code); + if (!src_info || !(src_info->direction & RKISP1_ISP_SD_SRC)) { + src_fmt->code = RKISP1_DEF_SRC_PAD_FMT; + src_info = rkisp1_mbus_info_get_by_code(src_fmt->code); + } + + if (sink_info->pixel_enc == V4L2_PIXEL_ENC_YUV && + src_info->pixel_enc == V4L2_PIXEL_ENC_BAYER) { + src_fmt->code = sink_fmt->code; + src_info = sink_info; + } + + /* + * The source width and height must be identical to the source crop + * size. + */ + src_fmt->width = src_crop->width; + src_fmt->height = src_crop->height; + + /* + * Copy the color space for the sink pad. When converting from Bayer to + * YUV, default to a limited quantization range. + */ + src_fmt->colorspace = sink_fmt->colorspace; + src_fmt->xfer_func = sink_fmt->xfer_func; + src_fmt->ycbcr_enc = sink_fmt->ycbcr_enc; + + if (sink_info->pixel_enc == V4L2_PIXEL_ENC_BAYER && + src_info->pixel_enc == V4L2_PIXEL_ENC_YUV) + src_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + else + src_fmt->quantization = sink_fmt->quantization; + + /* + * Allow setting the source color space fields when the SET_CSC flag is + * set and the source format is YUV. If the sink format is YUV, don't + * set the color primaries, transfer function or YCbCr encoding as the + * ISP is bypassed in that case and passes YUV data through without + * modifications. + * + * The color primaries and transfer function are configured through the + * cross-talk matrix and tone curve respectively. Settings for those + * hardware blocks are conveyed through the ISP parameters buffer, as + * they need to combine color space information with other image tuning + * characteristics and can't thus be computed by the kernel based on the + * color space. The source pad colorspace and xfer_func fields are thus + * ignored by the driver, but can be set by userspace to propagate + * accurate color space information down the pipeline. + */ + set_csc = format->flags & V4L2_MBUS_FRAMEFMT_SET_CSC; + + if (set_csc && src_info->pixel_enc == V4L2_PIXEL_ENC_YUV) { + if (sink_info->pixel_enc == V4L2_PIXEL_ENC_BAYER) { + if (format->colorspace != V4L2_COLORSPACE_DEFAULT) + src_fmt->colorspace = format->colorspace; + if (format->xfer_func != V4L2_XFER_FUNC_DEFAULT) + src_fmt->xfer_func = format->xfer_func; + if (format->ycbcr_enc != V4L2_YCBCR_ENC_DEFAULT) + src_fmt->ycbcr_enc = format->ycbcr_enc; + } + + if (format->quantization != V4L2_QUANTIZATION_DEFAULT) + src_fmt->quantization = format->quantization; + } + + *format = *src_fmt; + + /* + * Restore the SET_CSC flag if it was set to indicate support for the + * CSC setting API. + */ + if (set_csc) + format->flags |= V4L2_MBUS_FRAMEFMT_SET_CSC; + + /* Store the source format info when setting the active format. */ + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + isp->src_fmt = src_info; +} + +static void rkisp1_isp_set_src_crop(struct rkisp1_isp *isp, + struct v4l2_subdev_state *sd_state, + struct v4l2_rect *r, unsigned int which) +{ + struct v4l2_mbus_framefmt *src_fmt; + const struct v4l2_rect *sink_crop; + struct v4l2_rect *src_crop; + + src_crop = rkisp1_isp_get_pad_crop(isp, sd_state, + RKISP1_ISP_PAD_SOURCE_VIDEO, + which); + sink_crop = rkisp1_isp_get_pad_crop(isp, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO, + which); + + src_crop->left = ALIGN(r->left, 2); + src_crop->width = ALIGN(r->width, 2); + src_crop->top = r->top; + src_crop->height = r->height; + rkisp1_sd_adjust_crop_rect(src_crop, sink_crop); + + *r = *src_crop; + + /* Propagate to out format */ + src_fmt = rkisp1_isp_get_pad_fmt(isp, sd_state, + RKISP1_ISP_PAD_SOURCE_VIDEO, which); + rkisp1_isp_set_src_fmt(isp, sd_state, src_fmt, which); +} + +static void rkisp1_isp_set_sink_crop(struct rkisp1_isp *isp, + struct v4l2_subdev_state *sd_state, + struct v4l2_rect *r, unsigned int which) +{ + struct v4l2_rect *sink_crop, *src_crop; + const struct v4l2_mbus_framefmt *sink_fmt; + + sink_crop = rkisp1_isp_get_pad_crop(isp, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO, + which); + sink_fmt = rkisp1_isp_get_pad_fmt(isp, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO, + which); + + sink_crop->left = ALIGN(r->left, 2); + sink_crop->width = ALIGN(r->width, 2); + sink_crop->top = r->top; + sink_crop->height = r->height; + rkisp1_sd_adjust_crop(sink_crop, sink_fmt); + + *r = *sink_crop; + + /* Propagate to out crop */ + src_crop = rkisp1_isp_get_pad_crop(isp, sd_state, + RKISP1_ISP_PAD_SOURCE_VIDEO, which); + rkisp1_isp_set_src_crop(isp, sd_state, src_crop, which); +} + +static void rkisp1_isp_set_sink_fmt(struct rkisp1_isp *isp, + struct v4l2_subdev_state *sd_state, + struct v4l2_mbus_framefmt *format, + unsigned int which) +{ + const struct rkisp1_mbus_info *mbus_info; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *sink_crop; + bool is_yuv; + + sink_fmt = rkisp1_isp_get_pad_fmt(isp, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO, + which); + sink_fmt->code = format->code; + mbus_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + if (!mbus_info || !(mbus_info->direction & RKISP1_ISP_SD_SINK)) { + sink_fmt->code = RKISP1_DEF_SINK_PAD_FMT; + mbus_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + } + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + isp->sink_fmt = mbus_info; + + sink_fmt->width = clamp_t(u32, format->width, + RKISP1_ISP_MIN_WIDTH, + RKISP1_ISP_MAX_WIDTH); + sink_fmt->height = clamp_t(u32, format->height, + RKISP1_ISP_MIN_HEIGHT, + RKISP1_ISP_MAX_HEIGHT); + + /* + * Adjust the color space fields. Accept any color primaries and + * transfer function for both YUV and Bayer. For YUV any YCbCr encoding + * and quantization range is also accepted. For Bayer formats, the YCbCr + * encoding isn't applicable, and the quantization range can only be + * full. + */ + is_yuv = mbus_info->pixel_enc == V4L2_PIXEL_ENC_YUV; + + sink_fmt->colorspace = format->colorspace ? : + (is_yuv ? V4L2_COLORSPACE_SRGB : + V4L2_COLORSPACE_RAW); + sink_fmt->xfer_func = format->xfer_func ? : + V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace); + if (is_yuv) { + sink_fmt->ycbcr_enc = format->ycbcr_enc ? : + V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace); + sink_fmt->quantization = format->quantization ? : + V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace, + sink_fmt->ycbcr_enc); + } else { + /* + * The YCbCr encoding isn't applicable for non-YUV formats, but + * V4L2 has no "no encoding" value. Hardcode it to Rec. 601, it + * should be ignored by userspace. + */ + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + } + + *format = *sink_fmt; + + /* Propagate to in crop */ + sink_crop = rkisp1_isp_get_pad_crop(isp, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO, + which); + rkisp1_isp_set_sink_crop(isp, sd_state, sink_crop, which); +} + +static int rkisp1_isp_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct rkisp1_isp *isp = to_rkisp1_isp(sd); + + mutex_lock(&isp->ops_lock); + fmt->format = *rkisp1_isp_get_pad_fmt(isp, sd_state, fmt->pad, + fmt->which); + mutex_unlock(&isp->ops_lock); + return 0; +} + +static int rkisp1_isp_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct rkisp1_isp *isp = to_rkisp1_isp(sd); + + mutex_lock(&isp->ops_lock); + if (fmt->pad == RKISP1_ISP_PAD_SINK_VIDEO) + rkisp1_isp_set_sink_fmt(isp, sd_state, &fmt->format, + fmt->which); + else if (fmt->pad == RKISP1_ISP_PAD_SOURCE_VIDEO) + rkisp1_isp_set_src_fmt(isp, sd_state, &fmt->format, + fmt->which); + else + fmt->format = *rkisp1_isp_get_pad_fmt(isp, sd_state, fmt->pad, + fmt->which); + + mutex_unlock(&isp->ops_lock); + return 0; +} + +static int rkisp1_isp_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct rkisp1_isp *isp = to_rkisp1_isp(sd); + int ret = 0; + + if (sel->pad != RKISP1_ISP_PAD_SOURCE_VIDEO && + sel->pad != RKISP1_ISP_PAD_SINK_VIDEO) + return -EINVAL; + + mutex_lock(&isp->ops_lock); + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + if (sel->pad == RKISP1_ISP_PAD_SINK_VIDEO) { + struct v4l2_mbus_framefmt *fmt; + + fmt = rkisp1_isp_get_pad_fmt(isp, sd_state, sel->pad, + sel->which); + sel->r.height = fmt->height; + sel->r.width = fmt->width; + sel->r.left = 0; + sel->r.top = 0; + } else { + sel->r = *rkisp1_isp_get_pad_crop(isp, sd_state, + RKISP1_ISP_PAD_SINK_VIDEO, + sel->which); + } + break; + case V4L2_SEL_TGT_CROP: + sel->r = *rkisp1_isp_get_pad_crop(isp, sd_state, sel->pad, + sel->which); + break; + default: + ret = -EINVAL; + } + mutex_unlock(&isp->ops_lock); + return ret; +} + +static int rkisp1_isp_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct rkisp1_isp *isp = to_rkisp1_isp(sd); + int ret = 0; + + if (sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + dev_dbg(isp->rkisp1->dev, "%s: pad: %d sel(%d,%d)/%dx%d\n", __func__, + sel->pad, sel->r.left, sel->r.top, sel->r.width, sel->r.height); + mutex_lock(&isp->ops_lock); + if (sel->pad == RKISP1_ISP_PAD_SINK_VIDEO) + rkisp1_isp_set_sink_crop(isp, sd_state, &sel->r, sel->which); + else if (sel->pad == RKISP1_ISP_PAD_SOURCE_VIDEO) + rkisp1_isp_set_src_crop(isp, sd_state, &sel->r, sel->which); + else + ret = -EINVAL; + + mutex_unlock(&isp->ops_lock); + return ret; +} + +static int rkisp1_subdev_link_validate(struct media_link *link) +{ + if (link->sink->index == RKISP1_ISP_PAD_SINK_PARAMS) + return 0; + + return v4l2_subdev_link_validate(link); +} + +static const struct v4l2_subdev_pad_ops rkisp1_isp_pad_ops = { + .enum_mbus_code = rkisp1_isp_enum_mbus_code, + .enum_frame_size = rkisp1_isp_enum_frame_size, + .get_selection = rkisp1_isp_get_selection, + .set_selection = rkisp1_isp_set_selection, + .init_cfg = rkisp1_isp_init_config, + .get_fmt = rkisp1_isp_get_fmt, + .set_fmt = rkisp1_isp_set_fmt, + .link_validate = v4l2_subdev_link_validate_default, +}; + +/* ---------------------------------------------------------------------------- + * Stream operations + */ + +static int rkisp1_isp_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rkisp1_isp *isp = to_rkisp1_isp(sd); + struct rkisp1_device *rkisp1 = isp->rkisp1; + struct media_pad *source_pad; + struct media_pad *sink_pad; + enum v4l2_mbus_type mbus_type; + u32 mbus_flags; + int ret; + + if (!enable) { + v4l2_subdev_call(rkisp1->source, video, s_stream, false); + rkisp1_isp_stop(isp); + return 0; + } + + sink_pad = &isp->pads[RKISP1_ISP_PAD_SINK_VIDEO]; + source_pad = media_pad_remote_pad_unique(sink_pad); + if (IS_ERR(source_pad)) { + dev_dbg(rkisp1->dev, "Failed to get source for ISP: %ld\n", + PTR_ERR(source_pad)); + return -EPIPE; + } + + rkisp1->source = media_entity_to_v4l2_subdev(source_pad->entity); + if (!rkisp1->source) { + /* This should really not happen, so is not worth a message. */ + return -EPIPE; + } + + if (rkisp1->source == &rkisp1->csi.sd) { + mbus_type = V4L2_MBUS_CSI2_DPHY; + mbus_flags = 0; + } else { + const struct rkisp1_sensor_async *asd; + + asd = container_of(rkisp1->source->asd, + struct rkisp1_sensor_async, asd); + + mbus_type = asd->mbus_type; + mbus_flags = asd->mbus_flags; + } + + isp->frame_sequence = -1; + mutex_lock(&isp->ops_lock); + ret = rkisp1_config_cif(isp, mbus_type, mbus_flags); + if (ret) + goto mutex_unlock; + + rkisp1_isp_start(isp); + + ret = v4l2_subdev_call(rkisp1->source, video, s_stream, true); + if (ret) { + rkisp1_isp_stop(isp); + goto mutex_unlock; + } + +mutex_unlock: + mutex_unlock(&isp->ops_lock); + return ret; +} + +static int rkisp1_isp_subs_evt(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + if (sub->type != V4L2_EVENT_FRAME_SYNC) + return -EINVAL; + + /* V4L2_EVENT_FRAME_SYNC doesn't require an id, so zero should be set */ + if (sub->id != 0) + return -EINVAL; + + return v4l2_event_subscribe(fh, sub, 0, NULL); +} + +static const struct media_entity_operations rkisp1_isp_media_ops = { + .link_validate = rkisp1_subdev_link_validate, +}; + +static const struct v4l2_subdev_video_ops rkisp1_isp_video_ops = { + .s_stream = rkisp1_isp_s_stream, +}; + +static const struct v4l2_subdev_core_ops rkisp1_isp_core_ops = { + .subscribe_event = rkisp1_isp_subs_evt, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops rkisp1_isp_ops = { + .core = &rkisp1_isp_core_ops, + .video = &rkisp1_isp_video_ops, + .pad = &rkisp1_isp_pad_ops, +}; + +int rkisp1_isp_register(struct rkisp1_device *rkisp1) +{ + struct v4l2_subdev_state state = { + .pads = rkisp1->isp.pad_cfg + }; + struct rkisp1_isp *isp = &rkisp1->isp; + struct media_pad *pads = isp->pads; + struct v4l2_subdev *sd = &isp->sd; + int ret; + + isp->rkisp1 = rkisp1; + + v4l2_subdev_init(sd, &rkisp1_isp_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->entity.ops = &rkisp1_isp_media_ops; + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + sd->owner = THIS_MODULE; + strscpy(sd->name, RKISP1_ISP_DEV_NAME, sizeof(sd->name)); + + pads[RKISP1_ISP_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[RKISP1_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK; + pads[RKISP1_ISP_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE; + pads[RKISP1_ISP_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE; + + isp->sink_fmt = rkisp1_mbus_info_get_by_code(RKISP1_DEF_SINK_PAD_FMT); + isp->src_fmt = rkisp1_mbus_info_get_by_code(RKISP1_DEF_SRC_PAD_FMT); + + mutex_init(&isp->ops_lock); + ret = media_entity_pads_init(&sd->entity, RKISP1_ISP_PAD_MAX, pads); + if (ret) + goto error; + + ret = v4l2_device_register_subdev(&rkisp1->v4l2_dev, sd); + if (ret) { + dev_err(rkisp1->dev, "Failed to register isp subdev\n"); + goto error; + } + + rkisp1_isp_init_config(sd, &state); + + return 0; + +error: + media_entity_cleanup(&sd->entity); + mutex_destroy(&isp->ops_lock); + isp->sd.v4l2_dev = NULL; + return ret; +} + +void rkisp1_isp_unregister(struct rkisp1_device *rkisp1) +{ + struct rkisp1_isp *isp = &rkisp1->isp; + + if (!isp->sd.v4l2_dev) + return; + + v4l2_device_unregister_subdev(&isp->sd); + media_entity_cleanup(&isp->sd.entity); + mutex_destroy(&isp->ops_lock); +} + +/* ---------------------------------------------------------------------------- + * Interrupt handlers + */ + +static void rkisp1_isp_queue_event_sof(struct rkisp1_isp *isp) +{ + struct v4l2_event event = { + .type = V4L2_EVENT_FRAME_SYNC, + }; + + event.u.frame_sync.frame_sequence = isp->frame_sequence; + v4l2_event_queue(isp->sd.devnode, &event); +} + +irqreturn_t rkisp1_isp_isr(int irq, void *ctx) +{ + struct device *dev = ctx; + struct rkisp1_device *rkisp1 = dev_get_drvdata(dev); + u32 status, isp_err; + + status = rkisp1_read(rkisp1, RKISP1_CIF_ISP_MIS); + if (!status) + return IRQ_NONE; + + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ICR, status); + + /* Vertical sync signal, starting generating new frame */ + if (status & RKISP1_CIF_ISP_V_START) { + rkisp1->isp.frame_sequence++; + rkisp1_isp_queue_event_sof(&rkisp1->isp); + if (status & RKISP1_CIF_ISP_FRAME) { + WARN_ONCE(1, "irq delay is too long, buffers might not be in sync\n"); + rkisp1->debug.irq_delay++; + } + } + if (status & RKISP1_CIF_ISP_PIC_SIZE_ERROR) { + /* Clear pic_size_error */ + isp_err = rkisp1_read(rkisp1, RKISP1_CIF_ISP_ERR); + if (isp_err & RKISP1_CIF_ISP_ERR_INFORM_SIZE) + rkisp1->debug.inform_size_error++; + if (isp_err & RKISP1_CIF_ISP_ERR_IS_SIZE) + rkisp1->debug.img_stabilization_size_error++; + if (isp_err & RKISP1_CIF_ISP_ERR_OUTFORM_SIZE) + rkisp1->debug.outform_size_error++; + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ERR_CLR, isp_err); + } else if (status & RKISP1_CIF_ISP_DATA_LOSS) { + /* keep track of data_loss in debugfs */ + rkisp1->debug.data_loss++; + } + + if (status & RKISP1_CIF_ISP_FRAME) { + u32 isp_ris; + + /* New frame from the sensor received */ + isp_ris = rkisp1_read(rkisp1, RKISP1_CIF_ISP_RIS); + if (isp_ris & RKISP1_STATS_MEAS_MASK) + rkisp1_stats_isr(&rkisp1->stats, isp_ris); + /* + * Then update changed configs. Some of them involve + * lot of register writes. Do those only one per frame. + * Do the updates in the order of the processing flow. + */ + rkisp1_params_isr(rkisp1); + } + + return IRQ_HANDLED; +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c new file mode 100644 index 000000000..d8731ebbf --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c @@ -0,0 +1,1976 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - Params subdevice + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include /* for ISP params */ + +#include "rkisp1-common.h" + +#define RKISP1_PARAMS_DEV_NAME RKISP1_DRIVER_NAME "_params" + +#define RKISP1_ISP_PARAMS_REQ_BUFS_MIN 2 +#define RKISP1_ISP_PARAMS_REQ_BUFS_MAX 8 + +#define RKISP1_ISP_DPCC_METHODS_SET(n) \ + (RKISP1_CIF_ISP_DPCC_METHODS_SET_1 + 0x4 * (n)) +#define RKISP1_ISP_DPCC_LINE_THRESH(n) \ + (RKISP1_CIF_ISP_DPCC_LINE_THRESH_1 + 0x14 * (n)) +#define RKISP1_ISP_DPCC_LINE_MAD_FAC(n) \ + (RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_1 + 0x14 * (n)) +#define RKISP1_ISP_DPCC_PG_FAC(n) \ + (RKISP1_CIF_ISP_DPCC_PG_FAC_1 + 0x14 * (n)) +#define RKISP1_ISP_DPCC_RND_THRESH(n) \ + (RKISP1_CIF_ISP_DPCC_RND_THRESH_1 + 0x14 * (n)) +#define RKISP1_ISP_DPCC_RG_FAC(n) \ + (RKISP1_CIF_ISP_DPCC_RG_FAC_1 + 0x14 * (n)) +#define RKISP1_ISP_CC_COEFF(n) \ + (RKISP1_CIF_ISP_CC_COEFF_0 + (n) * 4) + +static inline void +rkisp1_param_set_bits(struct rkisp1_params *params, u32 reg, u32 bit_mask) +{ + u32 val; + + val = rkisp1_read(params->rkisp1, reg); + rkisp1_write(params->rkisp1, reg, val | bit_mask); +} + +static inline void +rkisp1_param_clear_bits(struct rkisp1_params *params, u32 reg, u32 bit_mask) +{ + u32 val; + + val = rkisp1_read(params->rkisp1, reg); + rkisp1_write(params->rkisp1, reg, val & ~bit_mask); +} + +/* ISP BP interface function */ +static void rkisp1_dpcc_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_dpcc_config *arg) +{ + unsigned int i; + u32 mode; + + /* + * The enable bit is controlled in rkisp1_isp_isr_other_config() and + * must be preserved. The grayscale mode should be configured + * automatically based on the media bus code on the ISP sink pad, so + * only the STAGE1_ENABLE bit can be set by userspace. + */ + mode = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_DPCC_MODE); + mode &= RKISP1_CIF_ISP_DPCC_MODE_DPCC_ENABLE; + mode |= arg->mode & RKISP1_CIF_ISP_DPCC_MODE_STAGE1_ENABLE; + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPCC_MODE, mode); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPCC_OUTPUT_MODE, + arg->output_mode & RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_MASK); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPCC_SET_USE, + arg->set_use & RKISP1_CIF_ISP_DPCC_SET_USE_MASK); + + for (i = 0; i < RKISP1_CIF_ISP_DPCC_METHODS_MAX; i++) { + rkisp1_write(params->rkisp1, RKISP1_ISP_DPCC_METHODS_SET(i), + arg->methods[i].method & + RKISP1_CIF_ISP_DPCC_METHODS_SET_MASK); + rkisp1_write(params->rkisp1, RKISP1_ISP_DPCC_LINE_THRESH(i), + arg->methods[i].line_thresh & + RKISP1_CIF_ISP_DPCC_LINE_THRESH_MASK); + rkisp1_write(params->rkisp1, RKISP1_ISP_DPCC_LINE_MAD_FAC(i), + arg->methods[i].line_mad_fac & + RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_MASK); + rkisp1_write(params->rkisp1, RKISP1_ISP_DPCC_PG_FAC(i), + arg->methods[i].pg_fac & + RKISP1_CIF_ISP_DPCC_PG_FAC_MASK); + rkisp1_write(params->rkisp1, RKISP1_ISP_DPCC_RND_THRESH(i), + arg->methods[i].rnd_thresh & + RKISP1_CIF_ISP_DPCC_RND_THRESH_MASK); + rkisp1_write(params->rkisp1, RKISP1_ISP_DPCC_RG_FAC(i), + arg->methods[i].rg_fac & + RKISP1_CIF_ISP_DPCC_RG_FAC_MASK); + } + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPCC_RND_OFFS, + arg->rnd_offs & RKISP1_CIF_ISP_DPCC_RND_OFFS_MASK); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPCC_RO_LIMITS, + arg->ro_limits & RKISP1_CIF_ISP_DPCC_RO_LIMIT_MASK); +} + +/* ISP black level subtraction interface function */ +static void rkisp1_bls_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_bls_config *arg) +{ + /* avoid to override the old enable value */ + u32 new_control; + + new_control = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_BLS_CTRL); + new_control &= RKISP1_CIF_ISP_BLS_ENA; + /* fixed subtraction values */ + if (!arg->enable_auto) { + const struct rkisp1_cif_isp_bls_fixed_val *pval = + &arg->fixed_val; + + switch (params->raw_type) { + case RKISP1_RAW_BGGR: + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_D_FIXED, + pval->r); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_C_FIXED, + pval->gr); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_B_FIXED, + pval->gb); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_A_FIXED, + pval->b); + break; + case RKISP1_RAW_GBRG: + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_C_FIXED, + pval->r); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_D_FIXED, + pval->gr); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_A_FIXED, + pval->gb); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_B_FIXED, + pval->b); + break; + case RKISP1_RAW_GRBG: + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_B_FIXED, + pval->r); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_A_FIXED, + pval->gr); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_D_FIXED, + pval->gb); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_C_FIXED, + pval->b); + break; + case RKISP1_RAW_RGGB: + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_A_FIXED, + pval->r); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_B_FIXED, + pval->gr); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_C_FIXED, + pval->gb); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_D_FIXED, + pval->b); + break; + default: + break; + } + + } else { + if (arg->en_windows & BIT(1)) { + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_H2_START, + arg->bls_window2.h_offs); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_H2_STOP, + arg->bls_window2.h_size); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_V2_START, + arg->bls_window2.v_offs); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_V2_STOP, + arg->bls_window2.v_size); + new_control |= RKISP1_CIF_ISP_BLS_WINDOW_2; + } + + if (arg->en_windows & BIT(0)) { + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_H1_START, + arg->bls_window1.h_offs); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_H1_STOP, + arg->bls_window1.h_size); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_V1_START, + arg->bls_window1.v_offs); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_V1_STOP, + arg->bls_window1.v_size); + new_control |= RKISP1_CIF_ISP_BLS_WINDOW_1; + } + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_SAMPLES, + arg->bls_samples); + + new_control |= RKISP1_CIF_ISP_BLS_MODE_MEASURED; + } + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_BLS_CTRL, new_control); +} + +/* ISP LS correction interface function */ +static void +rkisp1_lsc_matrix_config_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_lsc_config *pconfig) +{ + struct rkisp1_device *rkisp1 = params->rkisp1; + u32 lsc_status, sram_addr, lsc_table_sel; + unsigned int i, j; + + lsc_status = rkisp1_read(rkisp1, RKISP1_CIF_ISP_LSC_STATUS); + + /* RKISP1_CIF_ISP_LSC_TABLE_ADDRESS_153 = ( 17 * 18 ) >> 1 */ + sram_addr = lsc_status & RKISP1_CIF_ISP_LSC_ACTIVE_TABLE ? + RKISP1_CIF_ISP_LSC_TABLE_ADDRESS_0 : + RKISP1_CIF_ISP_LSC_TABLE_ADDRESS_153; + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_R_TABLE_ADDR, sram_addr); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GR_TABLE_ADDR, sram_addr); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GB_TABLE_ADDR, sram_addr); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_B_TABLE_ADDR, sram_addr); + + /* program data tables (table size is 9 * 17 = 153) */ + for (i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; i++) { + const __u16 *r_tbl = pconfig->r_data_tbl[i]; + const __u16 *gr_tbl = pconfig->gr_data_tbl[i]; + const __u16 *gb_tbl = pconfig->gb_data_tbl[i]; + const __u16 *b_tbl = pconfig->b_data_tbl[i]; + + /* + * 17 sectors with 2 values in one DWORD = 9 + * DWORDs (2nd value of last DWORD unused) + */ + for (j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX - 1; j += 2) { + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_R_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V10( + r_tbl[j], r_tbl[j + 1])); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GR_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V10( + gr_tbl[j], gr_tbl[j + 1])); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GB_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V10( + gb_tbl[j], gb_tbl[j + 1])); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_B_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V10( + b_tbl[j], b_tbl[j + 1])); + } + + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_R_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V10(r_tbl[j], 0)); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GR_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V10(gr_tbl[j], 0)); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GB_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V10(gb_tbl[j], 0)); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_B_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V10(b_tbl[j], 0)); + } + + lsc_table_sel = lsc_status & RKISP1_CIF_ISP_LSC_ACTIVE_TABLE ? + RKISP1_CIF_ISP_LSC_TABLE_0 : RKISP1_CIF_ISP_LSC_TABLE_1; + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_TABLE_SEL, lsc_table_sel); +} + +static void +rkisp1_lsc_matrix_config_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_lsc_config *pconfig) +{ + struct rkisp1_device *rkisp1 = params->rkisp1; + u32 lsc_status, sram_addr, lsc_table_sel; + unsigned int i, j; + + lsc_status = rkisp1_read(rkisp1, RKISP1_CIF_ISP_LSC_STATUS); + + /* RKISP1_CIF_ISP_LSC_TABLE_ADDRESS_153 = ( 17 * 18 ) >> 1 */ + sram_addr = lsc_status & RKISP1_CIF_ISP_LSC_ACTIVE_TABLE ? + RKISP1_CIF_ISP_LSC_TABLE_ADDRESS_0 : + RKISP1_CIF_ISP_LSC_TABLE_ADDRESS_153; + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_R_TABLE_ADDR, sram_addr); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GR_TABLE_ADDR, sram_addr); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GB_TABLE_ADDR, sram_addr); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_B_TABLE_ADDR, sram_addr); + + /* program data tables (table size is 9 * 17 = 153) */ + for (i = 0; i < RKISP1_CIF_ISP_LSC_SAMPLES_MAX; i++) { + const __u16 *r_tbl = pconfig->r_data_tbl[i]; + const __u16 *gr_tbl = pconfig->gr_data_tbl[i]; + const __u16 *gb_tbl = pconfig->gb_data_tbl[i]; + const __u16 *b_tbl = pconfig->b_data_tbl[i]; + + /* + * 17 sectors with 2 values in one DWORD = 9 + * DWORDs (2nd value of last DWORD unused) + */ + for (j = 0; j < RKISP1_CIF_ISP_LSC_SAMPLES_MAX - 1; j += 2) { + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_R_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V12( + r_tbl[j], r_tbl[j + 1])); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GR_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V12( + gr_tbl[j], gr_tbl[j + 1])); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GB_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V12( + gb_tbl[j], gb_tbl[j + 1])); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_B_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V12( + b_tbl[j], b_tbl[j + 1])); + } + + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_R_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V12(r_tbl[j], 0)); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GR_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V12(gr_tbl[j], 0)); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_GB_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V12(gb_tbl[j], 0)); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_B_TABLE_DATA, + RKISP1_CIF_ISP_LSC_TABLE_DATA_V12(b_tbl[j], 0)); + } + + lsc_table_sel = lsc_status & RKISP1_CIF_ISP_LSC_ACTIVE_TABLE ? + RKISP1_CIF_ISP_LSC_TABLE_0 : RKISP1_CIF_ISP_LSC_TABLE_1; + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_TABLE_SEL, lsc_table_sel); +} + +static void rkisp1_lsc_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_lsc_config *arg) +{ + struct rkisp1_device *rkisp1 = params->rkisp1; + u32 lsc_ctrl, data; + unsigned int i; + + /* To config must be off , store the current status firstly */ + lsc_ctrl = rkisp1_read(rkisp1, RKISP1_CIF_ISP_LSC_CTRL); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_LSC_CTRL, + RKISP1_CIF_ISP_LSC_CTRL_ENA); + params->ops->lsc_matrix_config(params, arg); + + for (i = 0; i < RKISP1_CIF_ISP_LSC_SECTORS_TBL_SIZE / 2; i++) { + /* program x size tables */ + data = RKISP1_CIF_ISP_LSC_SECT_SIZE(arg->x_size_tbl[i * 2], + arg->x_size_tbl[i * 2 + 1]); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_XSIZE(i), data); + + /* program x grad tables */ + data = RKISP1_CIF_ISP_LSC_SECT_GRAD(arg->x_grad_tbl[i * 2], + arg->x_grad_tbl[i * 2 + 1]); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_XGRAD(i), data); + + /* program y size tables */ + data = RKISP1_CIF_ISP_LSC_SECT_SIZE(arg->y_size_tbl[i * 2], + arg->y_size_tbl[i * 2 + 1]); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_YSIZE(i), data); + + /* program y grad tables */ + data = RKISP1_CIF_ISP_LSC_SECT_GRAD(arg->y_grad_tbl[i * 2], + arg->y_grad_tbl[i * 2 + 1]); + rkisp1_write(rkisp1, RKISP1_CIF_ISP_LSC_YGRAD(i), data); + } + + /* restore the lsc ctrl status */ + if (lsc_ctrl & RKISP1_CIF_ISP_LSC_CTRL_ENA) + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_LSC_CTRL, + RKISP1_CIF_ISP_LSC_CTRL_ENA); + else + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_LSC_CTRL, + RKISP1_CIF_ISP_LSC_CTRL_ENA); +} + +/* ISP Filtering function */ +static void rkisp1_flt_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_flt_config *arg) +{ + u32 filt_mode; + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_THRESH_BL0, + arg->thresh_bl0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_THRESH_BL1, + arg->thresh_bl1); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_THRESH_SH0, + arg->thresh_sh0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_THRESH_SH1, + arg->thresh_sh1); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_FAC_BL0, + arg->fac_bl0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_FAC_BL1, + arg->fac_bl1); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_FAC_MID, + arg->fac_mid); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_FAC_SH0, + arg->fac_sh0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_FAC_SH1, + arg->fac_sh1); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_LUM_WEIGHT, + arg->lum_weight); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_MODE, + (arg->mode ? RKISP1_CIF_ISP_FLT_MODE_DNR : 0) | + RKISP1_CIF_ISP_FLT_CHROMA_V_MODE(arg->chr_v_mode) | + RKISP1_CIF_ISP_FLT_CHROMA_H_MODE(arg->chr_h_mode) | + RKISP1_CIF_ISP_FLT_GREEN_STAGE1(arg->grn_stage1)); + + /* avoid to override the old enable value */ + filt_mode = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_FILT_MODE); + filt_mode &= RKISP1_CIF_ISP_FLT_ENA; + if (arg->mode) + filt_mode |= RKISP1_CIF_ISP_FLT_MODE_DNR; + filt_mode |= RKISP1_CIF_ISP_FLT_CHROMA_V_MODE(arg->chr_v_mode) | + RKISP1_CIF_ISP_FLT_CHROMA_H_MODE(arg->chr_h_mode) | + RKISP1_CIF_ISP_FLT_GREEN_STAGE1(arg->grn_stage1); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_FILT_MODE, filt_mode); +} + +/* ISP demosaic interface function */ +static int rkisp1_bdm_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_bdm_config *arg) +{ + u32 bdm_th; + + /* avoid to override the old enable value */ + bdm_th = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_DEMOSAIC); + bdm_th &= RKISP1_CIF_ISP_DEMOSAIC_BYPASS; + bdm_th |= arg->demosaic_th & ~RKISP1_CIF_ISP_DEMOSAIC_BYPASS; + /* set demosaic threshold */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DEMOSAIC, bdm_th); + return 0; +} + +/* ISP GAMMA correction interface function */ +static void rkisp1_sdg_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_sdg_config *arg) +{ + unsigned int i; + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_GAMMA_DX_LO, + arg->xa_pnts.gamma_dx0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_GAMMA_DX_HI, + arg->xa_pnts.gamma_dx1); + + for (i = 0; i < RKISP1_CIF_ISP_DEGAMMA_CURVE_SIZE; i++) { + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_GAMMA_R_Y0 + i * 4, + arg->curve_r.gamma_y[i]); + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_GAMMA_G_Y0 + i * 4, + arg->curve_g.gamma_y[i]); + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_GAMMA_B_Y0 + i * 4, + arg->curve_b.gamma_y[i]); + } +} + +/* ISP GAMMA correction interface function */ +static void rkisp1_goc_config_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_goc_config *arg) +{ + unsigned int i; + + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_GAMMA_OUT_ENA); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_GAMMA_OUT_MODE_V10, + arg->mode); + + for (i = 0; i < RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V10; i++) + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_GAMMA_OUT_Y_0_V10 + i * 4, + arg->gamma_y[i]); +} + +static void rkisp1_goc_config_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_goc_config *arg) +{ + unsigned int i; + u32 value; + + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_GAMMA_OUT_ENA); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_GAMMA_OUT_MODE_V12, + arg->mode); + + for (i = 0; i < RKISP1_CIF_ISP_GAMMA_OUT_MAX_SAMPLES_V12 / 2; i++) { + value = RKISP1_CIF_ISP_GAMMA_VALUE_V12( + arg->gamma_y[2 * i + 1], + arg->gamma_y[2 * i]); + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_GAMMA_OUT_Y_0_V12 + i * 4, value); + } +} + +/* ISP Cross Talk */ +static void rkisp1_ctk_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_ctk_config *arg) +{ + unsigned int i, j, k = 0; + + for (i = 0; i < 3; i++) + for (j = 0; j < 3; j++) + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_CT_COEFF_0 + 4 * k++, + arg->coeff[i][j]); + for (i = 0; i < 3; i++) + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_CT_OFFSET_R + i * 4, + arg->ct_offset[i]); +} + +static void rkisp1_ctk_enable(struct rkisp1_params *params, bool en) +{ + if (en) + return; + + /* Write back the default values. */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_0, 0x80); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_1, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_2, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_3, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_4, 0x80); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_5, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_6, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_7, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_COEFF_8, 0x80); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_OFFSET_R, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_OFFSET_G, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CT_OFFSET_B, 0); +} + +/* ISP White Balance Mode */ +static void rkisp1_awb_meas_config_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_meas_config *arg) +{ + u32 reg_val = 0; + /* based on the mode,configure the awb module */ + if (arg->awb_mode == RKISP1_CIF_ISP_AWB_MODE_YCBCR) { + /* Reference Cb and Cr */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_REF_V10, + RKISP1_CIF_ISP_AWB_REF_CR_SET(arg->awb_ref_cr) | + arg->awb_ref_cb); + /* Yc Threshold */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_THRESH_V10, + RKISP1_CIF_ISP_AWB_MAX_Y_SET(arg->max_y) | + RKISP1_CIF_ISP_AWB_MIN_Y_SET(arg->min_y) | + RKISP1_CIF_ISP_AWB_MAX_CS_SET(arg->max_csum) | + arg->min_c); + } + + reg_val = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V10); + if (arg->enable_ymax_cmp) + reg_val |= RKISP1_CIF_ISP_AWB_YMAX_CMP_EN; + else + reg_val &= ~RKISP1_CIF_ISP_AWB_YMAX_CMP_EN; + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V10, reg_val); + + /* window offset */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_WND_V_OFFS_V10, + arg->awb_wnd.v_offs); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_WND_H_OFFS_V10, + arg->awb_wnd.h_offs); + /* AWB window size */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_WND_V_SIZE_V10, + arg->awb_wnd.v_size); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_WND_H_SIZE_V10, + arg->awb_wnd.h_size); + /* Number of frames */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_FRAMES_V10, + arg->frames); +} + +static void rkisp1_awb_meas_config_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_meas_config *arg) +{ + u32 reg_val = 0; + /* based on the mode,configure the awb module */ + if (arg->awb_mode == RKISP1_CIF_ISP_AWB_MODE_YCBCR) { + /* Reference Cb and Cr */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_REF_V12, + RKISP1_CIF_ISP_AWB_REF_CR_SET(arg->awb_ref_cr) | + arg->awb_ref_cb); + /* Yc Threshold */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_THRESH_V12, + RKISP1_CIF_ISP_AWB_MAX_Y_SET(arg->max_y) | + RKISP1_CIF_ISP_AWB_MIN_Y_SET(arg->min_y) | + RKISP1_CIF_ISP_AWB_MAX_CS_SET(arg->max_csum) | + arg->min_c); + } + + reg_val = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V12); + if (arg->enable_ymax_cmp) + reg_val |= RKISP1_CIF_ISP_AWB_YMAX_CMP_EN; + else + reg_val &= ~RKISP1_CIF_ISP_AWB_YMAX_CMP_EN; + reg_val &= ~RKISP1_CIF_ISP_AWB_SET_FRAMES_MASK_V12; + reg_val |= RKISP1_CIF_ISP_AWB_SET_FRAMES_V12(arg->frames); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V12, reg_val); + + /* window offset */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_OFFS_V12, + arg->awb_wnd.v_offs << 16 | arg->awb_wnd.h_offs); + /* AWB window size */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_SIZE_V12, + arg->awb_wnd.v_size << 16 | arg->awb_wnd.h_size); +} + +static void +rkisp1_awb_meas_enable_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_meas_config *arg, + bool en) +{ + u32 reg_val = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V10); + + /* switch off */ + reg_val &= RKISP1_CIF_ISP_AWB_MODE_MASK_NONE; + + if (en) { + if (arg->awb_mode == RKISP1_CIF_ISP_AWB_MODE_RGB) + reg_val |= RKISP1_CIF_ISP_AWB_MODE_RGB_EN; + else + reg_val |= RKISP1_CIF_ISP_AWB_MODE_YCBCR_EN; + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V10, + reg_val); + + /* Measurements require AWB block be active. */ + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_AWB_ENA); + } else { + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V10, + reg_val); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_AWB_ENA); + } +} + +static void +rkisp1_awb_meas_enable_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_meas_config *arg, + bool en) +{ + u32 reg_val = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V12); + + /* switch off */ + reg_val &= RKISP1_CIF_ISP_AWB_MODE_MASK_NONE; + + if (en) { + if (arg->awb_mode == RKISP1_CIF_ISP_AWB_MODE_RGB) + reg_val |= RKISP1_CIF_ISP_AWB_MODE_RGB_EN; + else + reg_val |= RKISP1_CIF_ISP_AWB_MODE_YCBCR_EN; + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V12, + reg_val); + + /* Measurements require AWB block be active. */ + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_AWB_ENA); + } else { + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_PROP_V12, + reg_val); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_AWB_ENA); + } +} + +static void +rkisp1_awb_gain_config_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_gain_config *arg) +{ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_GAIN_G_V10, + RKISP1_CIF_ISP_AWB_GAIN_R_SET(arg->gain_green_r) | + arg->gain_green_b); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_GAIN_RB_V10, + RKISP1_CIF_ISP_AWB_GAIN_R_SET(arg->gain_red) | + arg->gain_blue); +} + +static void +rkisp1_awb_gain_config_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_awb_gain_config *arg) +{ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_GAIN_G_V12, + RKISP1_CIF_ISP_AWB_GAIN_R_SET(arg->gain_green_r) | + arg->gain_green_b); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AWB_GAIN_RB_V12, + RKISP1_CIF_ISP_AWB_GAIN_R_SET(arg->gain_red) | + arg->gain_blue); +} + +static void rkisp1_aec_config_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_aec_config *arg) +{ + unsigned int block_hsize, block_vsize; + u32 exp_ctrl; + + /* avoid to override the old enable value */ + exp_ctrl = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_EXP_CTRL); + exp_ctrl &= RKISP1_CIF_ISP_EXP_ENA; + if (arg->autostop) + exp_ctrl |= RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP; + if (arg->mode == RKISP1_CIF_ISP_EXP_MEASURING_MODE_1) + exp_ctrl |= RKISP1_CIF_ISP_EXP_CTRL_MEASMODE_1; + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_EXP_CTRL, exp_ctrl); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_EXP_H_OFFSET_V10, + arg->meas_window.h_offs); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_EXP_V_OFFSET_V10, + arg->meas_window.v_offs); + + block_hsize = arg->meas_window.h_size / + RKISP1_CIF_ISP_EXP_COLUMN_NUM_V10 - 1; + block_vsize = arg->meas_window.v_size / + RKISP1_CIF_ISP_EXP_ROW_NUM_V10 - 1; + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_EXP_H_SIZE_V10, + RKISP1_CIF_ISP_EXP_H_SIZE_SET_V10(block_hsize)); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_EXP_V_SIZE_V10, + RKISP1_CIF_ISP_EXP_V_SIZE_SET_V10(block_vsize)); +} + +static void rkisp1_aec_config_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_aec_config *arg) +{ + u32 exp_ctrl; + u32 block_hsize, block_vsize; + u32 wnd_num_idx = 1; + const u32 ae_wnd_num[] = { 5, 9, 15, 15 }; + + /* avoid to override the old enable value */ + exp_ctrl = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_EXP_CTRL); + exp_ctrl &= RKISP1_CIF_ISP_EXP_ENA; + if (arg->autostop) + exp_ctrl |= RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP; + if (arg->mode == RKISP1_CIF_ISP_EXP_MEASURING_MODE_1) + exp_ctrl |= RKISP1_CIF_ISP_EXP_CTRL_MEASMODE_1; + exp_ctrl |= RKISP1_CIF_ISP_EXP_CTRL_WNDNUM_SET_V12(wnd_num_idx); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_EXP_CTRL, exp_ctrl); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_EXP_OFFS_V12, + RKISP1_CIF_ISP_EXP_V_OFFSET_SET_V12(arg->meas_window.v_offs) | + RKISP1_CIF_ISP_EXP_H_OFFSET_SET_V12(arg->meas_window.h_offs)); + + block_hsize = arg->meas_window.h_size / ae_wnd_num[wnd_num_idx] - 1; + block_vsize = arg->meas_window.v_size / ae_wnd_num[wnd_num_idx] - 1; + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_EXP_SIZE_V12, + RKISP1_CIF_ISP_EXP_V_SIZE_SET_V12(block_vsize) | + RKISP1_CIF_ISP_EXP_H_SIZE_SET_V12(block_hsize)); +} + +static void rkisp1_cproc_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_cproc_config *arg) +{ + struct rkisp1_cif_isp_isp_other_cfg *cur_other_cfg = + container_of(arg, struct rkisp1_cif_isp_isp_other_cfg, cproc_config); + struct rkisp1_cif_isp_ie_config *cur_ie_config = + &cur_other_cfg->ie_config; + u32 effect = cur_ie_config->effect; + u32 quantization = params->quantization; + + rkisp1_write(params->rkisp1, RKISP1_CIF_C_PROC_CONTRAST, + arg->contrast); + rkisp1_write(params->rkisp1, RKISP1_CIF_C_PROC_HUE, arg->hue); + rkisp1_write(params->rkisp1, RKISP1_CIF_C_PROC_SATURATION, arg->sat); + rkisp1_write(params->rkisp1, RKISP1_CIF_C_PROC_BRIGHTNESS, + arg->brightness); + + if (quantization != V4L2_QUANTIZATION_FULL_RANGE || + effect != V4L2_COLORFX_NONE) { + rkisp1_param_clear_bits(params, RKISP1_CIF_C_PROC_CTRL, + RKISP1_CIF_C_PROC_YOUT_FULL | + RKISP1_CIF_C_PROC_YIN_FULL | + RKISP1_CIF_C_PROC_COUT_FULL); + } else { + rkisp1_param_set_bits(params, RKISP1_CIF_C_PROC_CTRL, + RKISP1_CIF_C_PROC_YOUT_FULL | + RKISP1_CIF_C_PROC_YIN_FULL | + RKISP1_CIF_C_PROC_COUT_FULL); + } +} + +static void rkisp1_hst_config_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_hst_config *arg) +{ + unsigned int block_hsize, block_vsize; + static const u32 hist_weight_regs[] = { + RKISP1_CIF_ISP_HIST_WEIGHT_00TO30_V10, + RKISP1_CIF_ISP_HIST_WEIGHT_40TO21_V10, + RKISP1_CIF_ISP_HIST_WEIGHT_31TO12_V10, + RKISP1_CIF_ISP_HIST_WEIGHT_22TO03_V10, + RKISP1_CIF_ISP_HIST_WEIGHT_13TO43_V10, + RKISP1_CIF_ISP_HIST_WEIGHT_04TO34_V10, + }; + const u8 *weight; + unsigned int i; + u32 hist_prop; + + /* avoid to override the old enable value */ + hist_prop = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_HIST_PROP_V10); + hist_prop &= RKISP1_CIF_ISP_HIST_PROP_MODE_MASK_V10; + hist_prop |= RKISP1_CIF_ISP_HIST_PREDIV_SET_V10(arg->histogram_predivider); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_PROP_V10, hist_prop); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_H_OFFS_V10, + arg->meas_window.h_offs); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_V_OFFS_V10, + arg->meas_window.v_offs); + + block_hsize = arg->meas_window.h_size / + RKISP1_CIF_ISP_HIST_COLUMN_NUM_V10 - 1; + block_vsize = arg->meas_window.v_size / RKISP1_CIF_ISP_HIST_ROW_NUM_V10 - 1; + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_H_SIZE_V10, + block_hsize); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_V_SIZE_V10, + block_vsize); + + weight = arg->hist_weight; + for (i = 0; i < ARRAY_SIZE(hist_weight_regs); ++i, weight += 4) + rkisp1_write(params->rkisp1, hist_weight_regs[i], + RKISP1_CIF_ISP_HIST_WEIGHT_SET_V10(weight[0], weight[1], + weight[2], weight[3])); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_WEIGHT_44_V10, + weight[0] & 0x1F); +} + +static void rkisp1_hst_config_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_hst_config *arg) +{ + unsigned int i, j; + u32 block_hsize, block_vsize; + u32 wnd_num_idx, hist_weight_num, hist_ctrl, value; + u8 weight15x15[RKISP1_CIF_ISP_HIST_WEIGHT_REG_SIZE_V12]; + const u32 hist_wnd_num[] = { 5, 9, 15, 15 }; + + /* now we just support 9x9 window */ + wnd_num_idx = 1; + memset(weight15x15, 0x00, sizeof(weight15x15)); + /* avoid to override the old enable value */ + hist_ctrl = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_HIST_CTRL_V12); + hist_ctrl &= RKISP1_CIF_ISP_HIST_CTRL_MODE_MASK_V12 | + RKISP1_CIF_ISP_HIST_CTRL_EN_MASK_V12; + hist_ctrl = hist_ctrl | + RKISP1_CIF_ISP_HIST_CTRL_INTRSEL_SET_V12(1) | + RKISP1_CIF_ISP_HIST_CTRL_DATASEL_SET_V12(0) | + RKISP1_CIF_ISP_HIST_CTRL_WATERLINE_SET_V12(0) | + RKISP1_CIF_ISP_HIST_CTRL_AUTOSTOP_SET_V12(0) | + RKISP1_CIF_ISP_HIST_CTRL_WNDNUM_SET_V12(1) | + RKISP1_CIF_ISP_HIST_CTRL_STEPSIZE_SET_V12(arg->histogram_predivider); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_CTRL_V12, hist_ctrl); + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_OFFS_V12, + RKISP1_CIF_ISP_HIST_OFFS_SET_V12(arg->meas_window.h_offs, + arg->meas_window.v_offs)); + + block_hsize = arg->meas_window.h_size / hist_wnd_num[wnd_num_idx] - 1; + block_vsize = arg->meas_window.v_size / hist_wnd_num[wnd_num_idx] - 1; + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_SIZE_V12, + RKISP1_CIF_ISP_HIST_SIZE_SET_V12(block_hsize, block_vsize)); + + for (i = 0; i < hist_wnd_num[wnd_num_idx]; i++) { + for (j = 0; j < hist_wnd_num[wnd_num_idx]; j++) { + weight15x15[i * RKISP1_CIF_ISP_HIST_ROW_NUM_V12 + j] = + arg->hist_weight[i * hist_wnd_num[wnd_num_idx] + j]; + } + } + + hist_weight_num = RKISP1_CIF_ISP_HIST_WEIGHT_REG_SIZE_V12; + for (i = 0; i < (hist_weight_num / 4); i++) { + value = RKISP1_CIF_ISP_HIST_WEIGHT_SET_V12( + weight15x15[4 * i + 0], + weight15x15[4 * i + 1], + weight15x15[4 * i + 2], + weight15x15[4 * i + 3]); + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_HIST_WEIGHT_V12 + 4 * i, value); + } + value = RKISP1_CIF_ISP_HIST_WEIGHT_SET_V12(weight15x15[4 * i + 0], 0, 0, 0); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_HIST_WEIGHT_V12 + 4 * i, + value); +} + +static void +rkisp1_hst_enable_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_hst_config *arg, bool en) +{ + if (en) { + u32 hist_prop = rkisp1_read(params->rkisp1, + RKISP1_CIF_ISP_HIST_PROP_V10); + + hist_prop &= ~RKISP1_CIF_ISP_HIST_PROP_MODE_MASK_V10; + hist_prop |= arg->mode; + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_HIST_PROP_V10, + hist_prop); + } else { + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_HIST_PROP_V10, + RKISP1_CIF_ISP_HIST_PROP_MODE_MASK_V10); + } +} + +static void +rkisp1_hst_enable_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_hst_config *arg, bool en) +{ + if (en) { + u32 hist_ctrl = rkisp1_read(params->rkisp1, + RKISP1_CIF_ISP_HIST_CTRL_V12); + + hist_ctrl &= ~RKISP1_CIF_ISP_HIST_CTRL_MODE_MASK_V12; + hist_ctrl |= RKISP1_CIF_ISP_HIST_CTRL_MODE_SET_V12(arg->mode); + hist_ctrl |= RKISP1_CIF_ISP_HIST_CTRL_EN_SET_V12(1); + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_HIST_CTRL_V12, + hist_ctrl); + } else { + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_HIST_CTRL_V12, + RKISP1_CIF_ISP_HIST_CTRL_MODE_MASK_V12 | + RKISP1_CIF_ISP_HIST_CTRL_EN_MASK_V12); + } +} + +static void rkisp1_afm_config_v10(struct rkisp1_params *params, + const struct rkisp1_cif_isp_afc_config *arg) +{ + size_t num_of_win = min_t(size_t, ARRAY_SIZE(arg->afm_win), + arg->num_afm_win); + u32 afm_ctrl = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_AFM_CTRL); + unsigned int i; + + /* Switch off to configure. */ + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_AFM_CTRL, + RKISP1_CIF_ISP_AFM_ENA); + + for (i = 0; i < num_of_win; i++) { + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_LT_A + i * 8, + RKISP1_CIF_ISP_AFM_WINDOW_X(arg->afm_win[i].h_offs) | + RKISP1_CIF_ISP_AFM_WINDOW_Y(arg->afm_win[i].v_offs)); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_RB_A + i * 8, + RKISP1_CIF_ISP_AFM_WINDOW_X(arg->afm_win[i].h_size + + arg->afm_win[i].h_offs) | + RKISP1_CIF_ISP_AFM_WINDOW_Y(arg->afm_win[i].v_size + + arg->afm_win[i].v_offs)); + } + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_THRES, arg->thres); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_VAR_SHIFT, + arg->var_shift); + /* restore afm status */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_CTRL, afm_ctrl); +} + +static void rkisp1_afm_config_v12(struct rkisp1_params *params, + const struct rkisp1_cif_isp_afc_config *arg) +{ + size_t num_of_win = min_t(size_t, ARRAY_SIZE(arg->afm_win), + arg->num_afm_win); + u32 afm_ctrl = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_AFM_CTRL); + u32 lum_var_shift, afm_var_shift; + unsigned int i; + + /* Switch off to configure. */ + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_AFM_CTRL, + RKISP1_CIF_ISP_AFM_ENA); + + for (i = 0; i < num_of_win; i++) { + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_LT_A + i * 8, + RKISP1_CIF_ISP_AFM_WINDOW_X(arg->afm_win[i].h_offs) | + RKISP1_CIF_ISP_AFM_WINDOW_Y(arg->afm_win[i].v_offs)); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_RB_A + i * 8, + RKISP1_CIF_ISP_AFM_WINDOW_X(arg->afm_win[i].h_size + + arg->afm_win[i].h_offs) | + RKISP1_CIF_ISP_AFM_WINDOW_Y(arg->afm_win[i].v_size + + arg->afm_win[i].v_offs)); + } + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_THRES, arg->thres); + + lum_var_shift = RKISP1_CIF_ISP_AFM_GET_LUM_SHIFT_a_V12(arg->var_shift); + afm_var_shift = RKISP1_CIF_ISP_AFM_GET_AFM_SHIFT_a_V12(arg->var_shift); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_VAR_SHIFT, + RKISP1_CIF_ISP_AFM_SET_SHIFT_a_V12(lum_var_shift, afm_var_shift) | + RKISP1_CIF_ISP_AFM_SET_SHIFT_b_V12(lum_var_shift, afm_var_shift) | + RKISP1_CIF_ISP_AFM_SET_SHIFT_c_V12(lum_var_shift, afm_var_shift)); + + /* restore afm status */ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_AFM_CTRL, afm_ctrl); +} + +static void rkisp1_ie_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_ie_config *arg) +{ + u32 eff_ctrl; + + eff_ctrl = rkisp1_read(params->rkisp1, RKISP1_CIF_IMG_EFF_CTRL); + eff_ctrl &= ~RKISP1_CIF_IMG_EFF_CTRL_MODE_MASK; + + if (params->quantization == V4L2_QUANTIZATION_FULL_RANGE) + eff_ctrl |= RKISP1_CIF_IMG_EFF_CTRL_YCBCR_FULL; + + switch (arg->effect) { + case V4L2_COLORFX_SEPIA: + eff_ctrl |= RKISP1_CIF_IMG_EFF_CTRL_MODE_SEPIA; + break; + case V4L2_COLORFX_SET_CBCR: + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_TINT, + arg->eff_tint); + eff_ctrl |= RKISP1_CIF_IMG_EFF_CTRL_MODE_SEPIA; + break; + /* + * Color selection is similar to water color(AQUA): + * grayscale + selected color w threshold + */ + case V4L2_COLORFX_AQUA: + eff_ctrl |= RKISP1_CIF_IMG_EFF_CTRL_MODE_COLOR_SEL; + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_COLOR_SEL, + arg->color_sel); + break; + case V4L2_COLORFX_EMBOSS: + eff_ctrl |= RKISP1_CIF_IMG_EFF_CTRL_MODE_EMBOSS; + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_MAT_1, + arg->eff_mat_1); + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_MAT_2, + arg->eff_mat_2); + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_MAT_3, + arg->eff_mat_3); + break; + case V4L2_COLORFX_SKETCH: + eff_ctrl |= RKISP1_CIF_IMG_EFF_CTRL_MODE_SKETCH; + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_MAT_3, + arg->eff_mat_3); + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_MAT_4, + arg->eff_mat_4); + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_MAT_5, + arg->eff_mat_5); + break; + case V4L2_COLORFX_BW: + eff_ctrl |= RKISP1_CIF_IMG_EFF_CTRL_MODE_BLACKWHITE; + break; + case V4L2_COLORFX_NEGATIVE: + eff_ctrl |= RKISP1_CIF_IMG_EFF_CTRL_MODE_NEGATIVE; + break; + default: + break; + } + + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_CTRL, eff_ctrl); +} + +static void rkisp1_ie_enable(struct rkisp1_params *params, bool en) +{ + if (en) { + rkisp1_param_set_bits(params, RKISP1_CIF_VI_ICCL, + RKISP1_CIF_VI_ICCL_IE_CLK); + rkisp1_write(params->rkisp1, RKISP1_CIF_IMG_EFF_CTRL, + RKISP1_CIF_IMG_EFF_CTRL_ENABLE); + rkisp1_param_set_bits(params, RKISP1_CIF_IMG_EFF_CTRL, + RKISP1_CIF_IMG_EFF_CTRL_CFG_UPD); + } else { + rkisp1_param_clear_bits(params, RKISP1_CIF_IMG_EFF_CTRL, + RKISP1_CIF_IMG_EFF_CTRL_ENABLE); + rkisp1_param_clear_bits(params, RKISP1_CIF_VI_ICCL, + RKISP1_CIF_VI_ICCL_IE_CLK); + } +} + +static void rkisp1_csm_config(struct rkisp1_params *params) +{ + struct csm_coeffs { + u16 limited[9]; + u16 full[9]; + }; + static const struct csm_coeffs rec601_coeffs = { + .limited = { + 0x0021, 0x0042, 0x000d, + 0x01ed, 0x01db, 0x0038, + 0x0038, 0x01d1, 0x01f7, + }, + .full = { + 0x0026, 0x004b, 0x000f, + 0x01ea, 0x01d6, 0x0040, + 0x0040, 0x01ca, 0x01f6, + }, + }; + static const struct csm_coeffs rec709_coeffs = { + .limited = { + 0x0018, 0x0050, 0x0008, + 0x01f3, 0x01d5, 0x0038, + 0x0038, 0x01cd, 0x01fb, + }, + .full = { + 0x001b, 0x005c, 0x0009, + 0x01f1, 0x01cf, 0x0040, + 0x0040, 0x01c6, 0x01fa, + }, + }; + static const struct csm_coeffs rec2020_coeffs = { + .limited = { + 0x001d, 0x004c, 0x0007, + 0x01f0, 0x01d8, 0x0038, + 0x0038, 0x01cd, 0x01fb, + }, + .full = { + 0x0022, 0x0057, 0x0008, + 0x01ee, 0x01d2, 0x0040, + 0x0040, 0x01c5, 0x01fb, + }, + }; + static const struct csm_coeffs smpte240m_coeffs = { + .limited = { + 0x0018, 0x004f, 0x000a, + 0x01f3, 0x01d5, 0x0038, + 0x0038, 0x01ce, 0x01fa, + }, + .full = { + 0x001b, 0x005a, 0x000b, + 0x01f1, 0x01cf, 0x0040, + 0x0040, 0x01c7, 0x01f9, + }, + }; + + const struct csm_coeffs *coeffs; + const u16 *csm; + unsigned int i; + + switch (params->ycbcr_encoding) { + case V4L2_YCBCR_ENC_601: + default: + coeffs = &rec601_coeffs; + break; + case V4L2_YCBCR_ENC_709: + coeffs = &rec709_coeffs; + break; + case V4L2_YCBCR_ENC_BT2020: + coeffs = &rec2020_coeffs; + break; + case V4L2_YCBCR_ENC_SMPTE240M: + coeffs = &smpte240m_coeffs; + break; + } + + if (params->quantization == V4L2_QUANTIZATION_FULL_RANGE) { + csm = coeffs->full; + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_CSM_Y_FULL_ENA | + RKISP1_CIF_ISP_CTRL_ISP_CSM_C_FULL_ENA); + } else { + csm = coeffs->limited; + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_CSM_Y_FULL_ENA | + RKISP1_CIF_ISP_CTRL_ISP_CSM_C_FULL_ENA); + } + + for (i = 0; i < 9; i++) + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_CC_COEFF_0 + i * 4, + csm[i]); +} + +/* ISP De-noise Pre-Filter(DPF) function */ +static void rkisp1_dpf_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_dpf_config *arg) +{ + unsigned int isp_dpf_mode, spatial_coeff, i; + + switch (arg->gain.mode) { + case RKISP1_CIF_ISP_DPF_GAIN_USAGE_NF_GAINS: + isp_dpf_mode = RKISP1_CIF_ISP_DPF_MODE_USE_NF_GAIN | + RKISP1_CIF_ISP_DPF_MODE_AWB_GAIN_COMP; + break; + case RKISP1_CIF_ISP_DPF_GAIN_USAGE_LSC_GAINS: + isp_dpf_mode = RKISP1_CIF_ISP_DPF_MODE_LSC_GAIN_COMP; + break; + case RKISP1_CIF_ISP_DPF_GAIN_USAGE_NF_LSC_GAINS: + isp_dpf_mode = RKISP1_CIF_ISP_DPF_MODE_USE_NF_GAIN | + RKISP1_CIF_ISP_DPF_MODE_AWB_GAIN_COMP | + RKISP1_CIF_ISP_DPF_MODE_LSC_GAIN_COMP; + break; + case RKISP1_CIF_ISP_DPF_GAIN_USAGE_AWB_GAINS: + isp_dpf_mode = RKISP1_CIF_ISP_DPF_MODE_AWB_GAIN_COMP; + break; + case RKISP1_CIF_ISP_DPF_GAIN_USAGE_AWB_LSC_GAINS: + isp_dpf_mode = RKISP1_CIF_ISP_DPF_MODE_LSC_GAIN_COMP | + RKISP1_CIF_ISP_DPF_MODE_AWB_GAIN_COMP; + break; + case RKISP1_CIF_ISP_DPF_GAIN_USAGE_DISABLED: + default: + isp_dpf_mode = 0; + break; + } + + if (arg->nll.scale_mode == RKISP1_CIF_ISP_NLL_SCALE_LOGARITHMIC) + isp_dpf_mode |= RKISP1_CIF_ISP_DPF_MODE_NLL_SEGMENTATION; + if (arg->rb_flt.fltsize == RKISP1_CIF_ISP_DPF_RB_FILTERSIZE_9x9) + isp_dpf_mode |= RKISP1_CIF_ISP_DPF_MODE_RB_FLTSIZE_9x9; + if (!arg->rb_flt.r_enable) + isp_dpf_mode |= RKISP1_CIF_ISP_DPF_MODE_R_FLT_DIS; + if (!arg->rb_flt.b_enable) + isp_dpf_mode |= RKISP1_CIF_ISP_DPF_MODE_B_FLT_DIS; + if (!arg->g_flt.gb_enable) + isp_dpf_mode |= RKISP1_CIF_ISP_DPF_MODE_GB_FLT_DIS; + if (!arg->g_flt.gr_enable) + isp_dpf_mode |= RKISP1_CIF_ISP_DPF_MODE_GR_FLT_DIS; + + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_DPF_MODE, + isp_dpf_mode); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_NF_GAIN_B, + arg->gain.nf_b_gain); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_NF_GAIN_R, + arg->gain.nf_r_gain); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_NF_GAIN_GB, + arg->gain.nf_gb_gain); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_NF_GAIN_GR, + arg->gain.nf_gr_gain); + + for (i = 0; i < RKISP1_CIF_ISP_DPF_MAX_NLF_COEFFS; i++) { + rkisp1_write(params->rkisp1, + RKISP1_CIF_ISP_DPF_NULL_COEFF_0 + i * 4, + arg->nll.coeff[i]); + } + + spatial_coeff = arg->g_flt.spatial_coeff[0] | + (arg->g_flt.spatial_coeff[1] << 8) | + (arg->g_flt.spatial_coeff[2] << 16) | + (arg->g_flt.spatial_coeff[3] << 24); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_S_WEIGHT_G_1_4, + spatial_coeff); + + spatial_coeff = arg->g_flt.spatial_coeff[4] | + (arg->g_flt.spatial_coeff[5] << 8); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_S_WEIGHT_G_5_6, + spatial_coeff); + + spatial_coeff = arg->rb_flt.spatial_coeff[0] | + (arg->rb_flt.spatial_coeff[1] << 8) | + (arg->rb_flt.spatial_coeff[2] << 16) | + (arg->rb_flt.spatial_coeff[3] << 24); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_S_WEIGHT_RB_1_4, + spatial_coeff); + + spatial_coeff = arg->rb_flt.spatial_coeff[4] | + (arg->rb_flt.spatial_coeff[5] << 8); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_S_WEIGHT_RB_5_6, + spatial_coeff); +} + +static void +rkisp1_dpf_strength_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_dpf_strength_config *arg) +{ + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_STRENGTH_B, arg->b); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_STRENGTH_G, arg->g); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_DPF_STRENGTH_R, arg->r); +} + +static void +rkisp1_isp_isr_other_config(struct rkisp1_params *params, + const struct rkisp1_params_cfg *new_params) +{ + unsigned int module_en_update, module_cfg_update, module_ens; + + module_en_update = new_params->module_en_update; + module_cfg_update = new_params->module_cfg_update; + module_ens = new_params->module_ens; + + /* update dpc config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_DPCC) + rkisp1_dpcc_config(params, + &new_params->others.dpcc_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_DPCC) { + if (module_ens & RKISP1_CIF_ISP_MODULE_DPCC) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_DPCC_MODE, + RKISP1_CIF_ISP_DPCC_MODE_DPCC_ENABLE); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_DPCC_MODE, + RKISP1_CIF_ISP_DPCC_MODE_DPCC_ENABLE); + } + + /* update bls config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_BLS) + rkisp1_bls_config(params, + &new_params->others.bls_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_BLS) { + if (module_ens & RKISP1_CIF_ISP_MODULE_BLS) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_BLS_CTRL, + RKISP1_CIF_ISP_BLS_ENA); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_BLS_CTRL, + RKISP1_CIF_ISP_BLS_ENA); + } + + /* update sdg config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_SDG) + rkisp1_sdg_config(params, + &new_params->others.sdg_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_SDG) { + if (module_ens & RKISP1_CIF_ISP_MODULE_SDG) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_GAMMA_IN_ENA); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_GAMMA_IN_ENA); + } + + /* update awb gains */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_AWB_GAIN) + params->ops->awb_gain_config(params, &new_params->others.awb_gain_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_AWB_GAIN) { + if (module_ens & RKISP1_CIF_ISP_MODULE_AWB_GAIN) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_AWB_ENA); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_AWB_ENA); + } + + /* update bdm config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_BDM) + rkisp1_bdm_config(params, + &new_params->others.bdm_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_BDM) { + if (module_ens & RKISP1_CIF_ISP_MODULE_BDM) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_DEMOSAIC, + RKISP1_CIF_ISP_DEMOSAIC_BYPASS); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_DEMOSAIC, + RKISP1_CIF_ISP_DEMOSAIC_BYPASS); + } + + /* update filter config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_FLT) + rkisp1_flt_config(params, + &new_params->others.flt_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_FLT) { + if (module_ens & RKISP1_CIF_ISP_MODULE_FLT) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_FILT_MODE, + RKISP1_CIF_ISP_FLT_ENA); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_FILT_MODE, + RKISP1_CIF_ISP_FLT_ENA); + } + + /* update ctk config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_CTK) + rkisp1_ctk_config(params, + &new_params->others.ctk_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_CTK) + rkisp1_ctk_enable(params, !!(module_ens & RKISP1_CIF_ISP_MODULE_CTK)); + + /* update goc config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_GOC) + params->ops->goc_config(params, &new_params->others.goc_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_GOC) { + if (module_ens & RKISP1_CIF_ISP_MODULE_GOC) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_GAMMA_OUT_ENA); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_GAMMA_OUT_ENA); + } + + /* update cproc config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_CPROC) + rkisp1_cproc_config(params, + &new_params->others.cproc_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_CPROC) { + if (module_ens & RKISP1_CIF_ISP_MODULE_CPROC) + rkisp1_param_set_bits(params, + RKISP1_CIF_C_PROC_CTRL, + RKISP1_CIF_C_PROC_CTR_ENABLE); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_C_PROC_CTRL, + RKISP1_CIF_C_PROC_CTR_ENABLE); + } + + /* update ie config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_IE) + rkisp1_ie_config(params, &new_params->others.ie_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_IE) + rkisp1_ie_enable(params, !!(module_ens & RKISP1_CIF_ISP_MODULE_IE)); + + /* update dpf config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_DPF) + rkisp1_dpf_config(params, &new_params->others.dpf_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_DPF) { + if (module_ens & RKISP1_CIF_ISP_MODULE_DPF) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_DPF_MODE, + RKISP1_CIF_ISP_DPF_MODE_EN); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_DPF_MODE, + RKISP1_CIF_ISP_DPF_MODE_EN); + } + + if ((module_en_update & RKISP1_CIF_ISP_MODULE_DPF_STRENGTH) || + (module_cfg_update & RKISP1_CIF_ISP_MODULE_DPF_STRENGTH)) { + /* update dpf strength config */ + rkisp1_dpf_strength_config(params, + &new_params->others.dpf_strength_config); + } +} + +static void +rkisp1_isp_isr_lsc_config(struct rkisp1_params *params, + const struct rkisp1_params_cfg *new_params) +{ + unsigned int module_en_update, module_cfg_update, module_ens; + + module_en_update = new_params->module_en_update; + module_cfg_update = new_params->module_cfg_update; + module_ens = new_params->module_ens; + + /* update lsc config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_LSC) + rkisp1_lsc_config(params, + &new_params->others.lsc_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_LSC) { + if (module_ens & RKISP1_CIF_ISP_MODULE_LSC) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_LSC_CTRL, + RKISP1_CIF_ISP_LSC_CTRL_ENA); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_LSC_CTRL, + RKISP1_CIF_ISP_LSC_CTRL_ENA); + } +} + +static void rkisp1_isp_isr_meas_config(struct rkisp1_params *params, + struct rkisp1_params_cfg *new_params) +{ + unsigned int module_en_update, module_cfg_update, module_ens; + + module_en_update = new_params->module_en_update; + module_cfg_update = new_params->module_cfg_update; + module_ens = new_params->module_ens; + + /* update awb config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_AWB) + params->ops->awb_meas_config(params, &new_params->meas.awb_meas_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_AWB) + params->ops->awb_meas_enable(params, + &new_params->meas.awb_meas_config, + !!(module_ens & RKISP1_CIF_ISP_MODULE_AWB)); + + /* update afc config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_AFC) + params->ops->afm_config(params, + &new_params->meas.afc_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_AFC) { + if (module_ens & RKISP1_CIF_ISP_MODULE_AFC) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_AFM_CTRL, + RKISP1_CIF_ISP_AFM_ENA); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_AFM_CTRL, + RKISP1_CIF_ISP_AFM_ENA); + } + + /* update hst config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_HST) + params->ops->hst_config(params, + &new_params->meas.hst_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_HST) + params->ops->hst_enable(params, + &new_params->meas.hst_config, + !!(module_ens & RKISP1_CIF_ISP_MODULE_HST)); + + /* update aec config */ + if (module_cfg_update & RKISP1_CIF_ISP_MODULE_AEC) + params->ops->aec_config(params, + &new_params->meas.aec_config); + + if (module_en_update & RKISP1_CIF_ISP_MODULE_AEC) { + if (module_ens & RKISP1_CIF_ISP_MODULE_AEC) + rkisp1_param_set_bits(params, + RKISP1_CIF_ISP_EXP_CTRL, + RKISP1_CIF_ISP_EXP_ENA); + else + rkisp1_param_clear_bits(params, + RKISP1_CIF_ISP_EXP_CTRL, + RKISP1_CIF_ISP_EXP_ENA); + } +} + +static bool rkisp1_params_get_buffer(struct rkisp1_params *params, + struct rkisp1_buffer **buf, + struct rkisp1_params_cfg **cfg) +{ + if (list_empty(¶ms->params)) + return false; + + *buf = list_first_entry(¶ms->params, struct rkisp1_buffer, queue); + *cfg = vb2_plane_vaddr(&(*buf)->vb.vb2_buf, 0); + + return true; +} + +static void rkisp1_params_complete_buffer(struct rkisp1_params *params, + struct rkisp1_buffer *buf, + unsigned int frame_sequence) +{ + list_del(&buf->queue); + + buf->vb.sequence = frame_sequence; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); +} + +void rkisp1_params_isr(struct rkisp1_device *rkisp1) +{ + struct rkisp1_params *params = &rkisp1->params; + struct rkisp1_params_cfg *new_params; + struct rkisp1_buffer *cur_buf; + + spin_lock(¶ms->config_lock); + + if (!rkisp1_params_get_buffer(params, &cur_buf, &new_params)) + goto unlock; + + rkisp1_isp_isr_other_config(params, new_params); + rkisp1_isp_isr_lsc_config(params, new_params); + rkisp1_isp_isr_meas_config(params, new_params); + + /* update shadow register immediately */ + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_CFG_UPD); + + /* + * This isr is called when the ISR finishes processing a frame + * (RKISP1_CIF_ISP_FRAME). Configurations performed here will be + * applied on the next frame. Since frame_sequence is updated on the + * vertical sync signal, we should use frame_sequence + 1 here to + * indicate to userspace on which frame these parameters are being + * applied. + */ + rkisp1_params_complete_buffer(params, cur_buf, + rkisp1->isp.frame_sequence + 1); + +unlock: + spin_unlock(¶ms->config_lock); +} + +static const struct rkisp1_cif_isp_awb_meas_config rkisp1_awb_params_default_config = { + { + 0, 0, RKISP1_DEFAULT_WIDTH, RKISP1_DEFAULT_HEIGHT + }, + RKISP1_CIF_ISP_AWB_MODE_YCBCR, 200, 30, 20, 20, 0, 128, 128 +}; + +static const struct rkisp1_cif_isp_aec_config rkisp1_aec_params_default_config = { + RKISP1_CIF_ISP_EXP_MEASURING_MODE_0, + RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP_0, + { + RKISP1_DEFAULT_WIDTH >> 2, RKISP1_DEFAULT_HEIGHT >> 2, + RKISP1_DEFAULT_WIDTH >> 1, RKISP1_DEFAULT_HEIGHT >> 1 + } +}; + +static const struct rkisp1_cif_isp_hst_config rkisp1_hst_params_default_config = { + RKISP1_CIF_ISP_HISTOGRAM_MODE_RGB_COMBINED, + 3, + { + RKISP1_DEFAULT_WIDTH >> 2, RKISP1_DEFAULT_HEIGHT >> 2, + RKISP1_DEFAULT_WIDTH >> 1, RKISP1_DEFAULT_HEIGHT >> 1 + }, + { + 0, /* To be filled in with 0x01 at runtime. */ + } +}; + +static const struct rkisp1_cif_isp_afc_config rkisp1_afc_params_default_config = { + 1, + { + { + 300, 225, 200, 150 + } + }, + 4, + 14 +}; + +void rkisp1_params_pre_configure(struct rkisp1_params *params, + enum rkisp1_fmt_raw_pat_type bayer_pat, + enum v4l2_quantization quantization, + enum v4l2_ycbcr_encoding ycbcr_encoding) +{ + struct rkisp1_cif_isp_hst_config hst = rkisp1_hst_params_default_config; + struct rkisp1_params_cfg *new_params; + struct rkisp1_buffer *cur_buf; + + params->quantization = quantization; + params->ycbcr_encoding = ycbcr_encoding; + params->raw_type = bayer_pat; + + params->ops->awb_meas_config(params, &rkisp1_awb_params_default_config); + params->ops->awb_meas_enable(params, &rkisp1_awb_params_default_config, + true); + + params->ops->aec_config(params, &rkisp1_aec_params_default_config); + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_EXP_CTRL, + RKISP1_CIF_ISP_EXP_ENA); + + params->ops->afm_config(params, &rkisp1_afc_params_default_config); + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_AFM_CTRL, + RKISP1_CIF_ISP_AFM_ENA); + + memset(hst.hist_weight, 0x01, sizeof(hst.hist_weight)); + params->ops->hst_config(params, &hst); + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_HIST_PROP_V10, + rkisp1_hst_params_default_config.mode); + + rkisp1_csm_config(params); + + spin_lock_irq(¶ms->config_lock); + + /* apply the first buffer if there is one already */ + + if (!rkisp1_params_get_buffer(params, &cur_buf, &new_params)) + goto unlock; + + rkisp1_isp_isr_other_config(params, new_params); + rkisp1_isp_isr_meas_config(params, new_params); + + /* update shadow register immediately */ + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_CFG_UPD); + +unlock: + spin_unlock_irq(¶ms->config_lock); +} + +void rkisp1_params_post_configure(struct rkisp1_params *params) +{ + struct rkisp1_params_cfg *new_params; + struct rkisp1_buffer *cur_buf; + + spin_lock_irq(¶ms->config_lock); + + /* + * Apply LSC parameters from the first buffer (if any is already + * available. This must be done after the ISP gets started in the + * ISP8000Nano v18.02 (found in the i.MX8MP) as access to the LSC RAM + * is gated by the ISP_CTRL.ISP_ENABLE bit. As this initialization + * ordering doesn't affect other ISP versions negatively, do so + * unconditionally. + */ + + if (!rkisp1_params_get_buffer(params, &cur_buf, &new_params)) + goto unlock; + + rkisp1_isp_isr_lsc_config(params, new_params); + + /* update shadow register immediately */ + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_CFG_UPD); + + rkisp1_params_complete_buffer(params, cur_buf, 0); + +unlock: + spin_unlock_irq(¶ms->config_lock); +} + +/* + * Not called when the camera is active, therefore there is no need to acquire + * a lock. + */ +void rkisp1_params_disable(struct rkisp1_params *params) +{ + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_DPCC_MODE, + RKISP1_CIF_ISP_DPCC_MODE_DPCC_ENABLE); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_LSC_CTRL, + RKISP1_CIF_ISP_LSC_CTRL_ENA); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_BLS_CTRL, + RKISP1_CIF_ISP_BLS_ENA); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_GAMMA_IN_ENA); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_GAMMA_OUT_ENA); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_DEMOSAIC, + RKISP1_CIF_ISP_DEMOSAIC_BYPASS); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_FILT_MODE, + RKISP1_CIF_ISP_FLT_ENA); + params->ops->awb_meas_enable(params, NULL, false); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_CTRL, + RKISP1_CIF_ISP_CTRL_ISP_AWB_ENA); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_EXP_CTRL, + RKISP1_CIF_ISP_EXP_ENA); + rkisp1_ctk_enable(params, false); + rkisp1_param_clear_bits(params, RKISP1_CIF_C_PROC_CTRL, + RKISP1_CIF_C_PROC_CTR_ENABLE); + params->ops->hst_enable(params, NULL, false); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_AFM_CTRL, + RKISP1_CIF_ISP_AFM_ENA); + rkisp1_ie_enable(params, false); + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_DPF_MODE, + RKISP1_CIF_ISP_DPF_MODE_EN); +} + +static const struct rkisp1_params_ops rkisp1_v10_params_ops = { + .lsc_matrix_config = rkisp1_lsc_matrix_config_v10, + .goc_config = rkisp1_goc_config_v10, + .awb_meas_config = rkisp1_awb_meas_config_v10, + .awb_meas_enable = rkisp1_awb_meas_enable_v10, + .awb_gain_config = rkisp1_awb_gain_config_v10, + .aec_config = rkisp1_aec_config_v10, + .hst_config = rkisp1_hst_config_v10, + .hst_enable = rkisp1_hst_enable_v10, + .afm_config = rkisp1_afm_config_v10, +}; + +static struct rkisp1_params_ops rkisp1_v12_params_ops = { + .lsc_matrix_config = rkisp1_lsc_matrix_config_v12, + .goc_config = rkisp1_goc_config_v12, + .awb_meas_config = rkisp1_awb_meas_config_v12, + .awb_meas_enable = rkisp1_awb_meas_enable_v12, + .awb_gain_config = rkisp1_awb_gain_config_v12, + .aec_config = rkisp1_aec_config_v12, + .hst_config = rkisp1_hst_config_v12, + .hst_enable = rkisp1_hst_enable_v12, + .afm_config = rkisp1_afm_config_v12, +}; + +static int rkisp1_params_enum_fmt_meta_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct video_device *video = video_devdata(file); + struct rkisp1_params *params = video_get_drvdata(video); + + if (f->index > 0 || f->type != video->queue->type) + return -EINVAL; + + f->pixelformat = params->vdev_fmt.fmt.meta.dataformat; + + return 0; +} + +static int rkisp1_params_g_fmt_meta_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct video_device *video = video_devdata(file); + struct rkisp1_params *params = video_get_drvdata(video); + struct v4l2_meta_format *meta = &f->fmt.meta; + + if (f->type != video->queue->type) + return -EINVAL; + + memset(meta, 0, sizeof(*meta)); + meta->dataformat = params->vdev_fmt.fmt.meta.dataformat; + meta->buffersize = params->vdev_fmt.fmt.meta.buffersize; + + return 0; +} + +static int rkisp1_params_querycap(struct file *file, + void *priv, struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + + strscpy(cap->driver, RKISP1_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, vdev->name, sizeof(cap->card)); + strscpy(cap->bus_info, RKISP1_BUS_INFO, sizeof(cap->bus_info)); + + return 0; +} + +/* ISP params video device IOCTLs */ +static const struct v4l2_ioctl_ops rkisp1_params_ioctl = { + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_fmt_meta_out = rkisp1_params_enum_fmt_meta_out, + .vidioc_g_fmt_meta_out = rkisp1_params_g_fmt_meta_out, + .vidioc_s_fmt_meta_out = rkisp1_params_g_fmt_meta_out, + .vidioc_try_fmt_meta_out = rkisp1_params_g_fmt_meta_out, + .vidioc_querycap = rkisp1_params_querycap, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int rkisp1_params_vb2_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + *num_buffers = clamp_t(u32, *num_buffers, + RKISP1_ISP_PARAMS_REQ_BUFS_MIN, + RKISP1_ISP_PARAMS_REQ_BUFS_MAX); + + *num_planes = 1; + + sizes[0] = sizeof(struct rkisp1_params_cfg); + + return 0; +} + +static void rkisp1_params_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rkisp1_buffer *params_buf = + container_of(vbuf, struct rkisp1_buffer, vb); + struct vb2_queue *vq = vb->vb2_queue; + struct rkisp1_params *params = vq->drv_priv; + + spin_lock_irq(¶ms->config_lock); + list_add_tail(¶ms_buf->queue, ¶ms->params); + spin_unlock_irq(¶ms->config_lock); +} + +static int rkisp1_params_vb2_buf_prepare(struct vb2_buffer *vb) +{ + if (vb2_plane_size(vb, 0) < sizeof(struct rkisp1_params_cfg)) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, sizeof(struct rkisp1_params_cfg)); + + return 0; +} + +static void rkisp1_params_vb2_stop_streaming(struct vb2_queue *vq) +{ + struct rkisp1_params *params = vq->drv_priv; + struct rkisp1_buffer *buf; + LIST_HEAD(tmp_list); + + /* + * we first move the buffers into a local list 'tmp_list' + * and then we can iterate it and call vb2_buffer_done + * without holding the lock + */ + spin_lock_irq(¶ms->config_lock); + list_splice_init(¶ms->params, &tmp_list); + spin_unlock_irq(¶ms->config_lock); + + list_for_each_entry(buf, &tmp_list, queue) + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops rkisp1_params_vb2_ops = { + .queue_setup = rkisp1_params_vb2_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_queue = rkisp1_params_vb2_buf_queue, + .buf_prepare = rkisp1_params_vb2_buf_prepare, + .stop_streaming = rkisp1_params_vb2_stop_streaming, + +}; + +static const struct v4l2_file_operations rkisp1_params_fops = { + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, + .poll = vb2_fop_poll, + .open = v4l2_fh_open, + .release = vb2_fop_release +}; + +static int rkisp1_params_init_vb2_queue(struct vb2_queue *q, + struct rkisp1_params *params) +{ + struct rkisp1_vdev_node *node; + + node = container_of(q, struct rkisp1_vdev_node, buf_queue); + + q->type = V4L2_BUF_TYPE_META_OUTPUT; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = params; + q->ops = &rkisp1_params_vb2_ops; + q->mem_ops = &vb2_vmalloc_memops; + q->buf_struct_size = sizeof(struct rkisp1_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &node->vlock; + + return vb2_queue_init(q); +} + +static void rkisp1_init_params(struct rkisp1_params *params) +{ + params->vdev_fmt.fmt.meta.dataformat = + V4L2_META_FMT_RK_ISP1_PARAMS; + params->vdev_fmt.fmt.meta.buffersize = + sizeof(struct rkisp1_params_cfg); + + if (params->rkisp1->info->isp_ver == RKISP1_V12) + params->ops = &rkisp1_v12_params_ops; + else + params->ops = &rkisp1_v10_params_ops; +} + +int rkisp1_params_register(struct rkisp1_device *rkisp1) +{ + struct rkisp1_params *params = &rkisp1->params; + struct rkisp1_vdev_node *node = ¶ms->vnode; + struct video_device *vdev = &node->vdev; + int ret; + + params->rkisp1 = rkisp1; + mutex_init(&node->vlock); + INIT_LIST_HEAD(¶ms->params); + spin_lock_init(¶ms->config_lock); + + strscpy(vdev->name, RKISP1_PARAMS_DEV_NAME, sizeof(vdev->name)); + + video_set_drvdata(vdev, params); + vdev->ioctl_ops = &rkisp1_params_ioctl; + vdev->fops = &rkisp1_params_fops; + vdev->release = video_device_release_empty; + /* + * Provide a mutex to v4l2 core. It will be used + * to protect all fops and v4l2 ioctls. + */ + vdev->lock = &node->vlock; + vdev->v4l2_dev = &rkisp1->v4l2_dev; + vdev->queue = &node->buf_queue; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_META_OUTPUT; + vdev->vfl_dir = VFL_DIR_TX; + rkisp1_params_init_vb2_queue(vdev->queue, params); + rkisp1_init_params(params); + video_set_drvdata(vdev, params); + + node->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&vdev->entity, 1, &node->pad); + if (ret) + goto error; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(rkisp1->dev, + "failed to register %s, ret=%d\n", vdev->name, ret); + goto error; + } + + return 0; + +error: + media_entity_cleanup(&vdev->entity); + mutex_destroy(&node->vlock); + return ret; +} + +void rkisp1_params_unregister(struct rkisp1_device *rkisp1) +{ + struct rkisp1_params *params = &rkisp1->params; + struct rkisp1_vdev_node *node = ¶ms->vnode; + struct video_device *vdev = &node->vdev; + + if (!video_is_registered(vdev)) + return; + + vb2_video_unregister_device(vdev); + media_entity_cleanup(&vdev->entity); + mutex_destroy(&node->vlock); +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-regs.h b/drivers/media/platform/rockchip/rkisp1/rkisp1-regs.h new file mode 100644 index 000000000..421cc7335 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-regs.h @@ -0,0 +1,1367 @@ +/* SPDX-License-Identifier: (GPL-2.0+ OR MIT) */ +/* + * Rockchip ISP1 Driver - Registers header + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#ifndef _RKISP1_REGS_H +#define _RKISP1_REGS_H + +/* ISP_CTRL */ +#define RKISP1_CIF_ISP_CTRL_ISP_ENABLE BIT(0) +#define RKISP1_CIF_ISP_CTRL_ISP_MODE_RAW_PICT (0 << 1) +#define RKISP1_CIF_ISP_CTRL_ISP_MODE_ITU656 (1 << 1) +#define RKISP1_CIF_ISP_CTRL_ISP_MODE_ITU601 (2 << 1) +#define RKISP1_CIF_ISP_CTRL_ISP_MODE_BAYER_ITU601 (3 << 1) +#define RKISP1_CIF_ISP_CTRL_ISP_MODE_DATA_MODE (4 << 1) +#define RKISP1_CIF_ISP_CTRL_ISP_MODE_BAYER_ITU656 (5 << 1) +#define RKISP1_CIF_ISP_CTRL_ISP_MODE_RAW_PICT_ITU656 (6 << 1) +#define RKISP1_CIF_ISP_CTRL_ISP_INFORM_ENABLE BIT(4) +#define RKISP1_CIF_ISP_CTRL_ISP_GAMMA_IN_ENA BIT(6) +#define RKISP1_CIF_ISP_CTRL_ISP_AWB_ENA BIT(7) +#define RKISP1_CIF_ISP_CTRL_ISP_CFG_UPD_PERMANENT BIT(8) +#define RKISP1_CIF_ISP_CTRL_ISP_CFG_UPD BIT(9) +#define RKISP1_CIF_ISP_CTRL_ISP_GEN_CFG_UPD BIT(10) +#define RKISP1_CIF_ISP_CTRL_ISP_GAMMA_OUT_ENA BIT(11) +#define RKISP1_CIF_ISP_CTRL_ISP_FLASH_MODE_ENA BIT(12) +#define RKISP1_CIF_ISP_CTRL_ISP_CSM_Y_FULL_ENA BIT(13) +#define RKISP1_CIF_ISP_CTRL_ISP_CSM_C_FULL_ENA BIT(14) + +/* ISP_ACQ_PROP */ +#define RKISP1_CIF_ISP_ACQ_PROP_POS_EDGE BIT(0) +#define RKISP1_CIF_ISP_ACQ_PROP_HSYNC_LOW BIT(1) +#define RKISP1_CIF_ISP_ACQ_PROP_VSYNC_LOW BIT(2) +#define RKISP1_CIF_ISP_ACQ_PROP_BAYER_PAT_RGGB (0 << 3) +#define RKISP1_CIF_ISP_ACQ_PROP_BAYER_PAT_GRBG (1 << 3) +#define RKISP1_CIF_ISP_ACQ_PROP_BAYER_PAT_GBRG (2 << 3) +#define RKISP1_CIF_ISP_ACQ_PROP_BAYER_PAT_BGGR (3 << 3) +#define RKISP1_CIF_ISP_ACQ_PROP_BAYER_PAT(pat) ((pat) << 3) +#define RKISP1_CIF_ISP_ACQ_PROP_YCBYCR (0 << 7) +#define RKISP1_CIF_ISP_ACQ_PROP_YCRYCB (1 << 7) +#define RKISP1_CIF_ISP_ACQ_PROP_CBYCRY (2 << 7) +#define RKISP1_CIF_ISP_ACQ_PROP_CRYCBY (3 << 7) +#define RKISP1_CIF_ISP_ACQ_PROP_FIELD_SEL_ALL (0 << 9) +#define RKISP1_CIF_ISP_ACQ_PROP_FIELD_SEL_EVEN (1 << 9) +#define RKISP1_CIF_ISP_ACQ_PROP_FIELD_SEL_ODD (2 << 9) +#define RKISP1_CIF_ISP_ACQ_PROP_IN_SEL_12B (0 << 12) +#define RKISP1_CIF_ISP_ACQ_PROP_IN_SEL_10B_ZERO (1 << 12) +#define RKISP1_CIF_ISP_ACQ_PROP_IN_SEL_10B_MSB (2 << 12) +#define RKISP1_CIF_ISP_ACQ_PROP_IN_SEL_8B_ZERO (3 << 12) +#define RKISP1_CIF_ISP_ACQ_PROP_IN_SEL_8B_MSB (4 << 12) + +/* VI_DPCL */ +#define RKISP1_CIF_VI_DPCL_DMA_JPEG (0 << 0) +#define RKISP1_CIF_VI_DPCL_MP_MUX_MRSZ_MI (1 << 0) +#define RKISP1_CIF_VI_DPCL_MP_MUX_MRSZ_JPEG (2 << 0) +#define RKISP1_CIF_VI_DPCL_CHAN_MODE_MP (1 << 2) +#define RKISP1_CIF_VI_DPCL_CHAN_MODE_SP (2 << 2) +#define RKISP1_CIF_VI_DPCL_CHAN_MODE_MPSP (3 << 2) +#define RKISP1_CIF_VI_DPCL_DMA_SW_SPMUX (0 << 4) +#define RKISP1_CIF_VI_DPCL_DMA_SW_SI (1 << 4) +#define RKISP1_CIF_VI_DPCL_DMA_SW_IE (2 << 4) +#define RKISP1_CIF_VI_DPCL_DMA_SW_JPEG (3 << 4) +#define RKISP1_CIF_VI_DPCL_DMA_SW_ISP (4 << 4) +#define RKISP1_CIF_VI_DPCL_IF_SEL_PARALLEL (0 << 8) +#define RKISP1_CIF_VI_DPCL_IF_SEL_SMIA (1 << 8) +#define RKISP1_CIF_VI_DPCL_IF_SEL_MIPI (2 << 8) +#define RKISP1_CIF_VI_DPCL_DMA_IE_MUX_DMA BIT(10) +#define RKISP1_CIF_VI_DPCL_DMA_SP_MUX_DMA BIT(11) + +/* ISP_IMSC - ISP_MIS - ISP_RIS - ISP_ICR - ISP_ISR */ +#define RKISP1_CIF_ISP_OFF BIT(0) +#define RKISP1_CIF_ISP_FRAME BIT(1) +#define RKISP1_CIF_ISP_DATA_LOSS BIT(2) +#define RKISP1_CIF_ISP_PIC_SIZE_ERROR BIT(3) +#define RKISP1_CIF_ISP_AWB_DONE BIT(4) +#define RKISP1_CIF_ISP_FRAME_IN BIT(5) +#define RKISP1_CIF_ISP_V_START BIT(6) +#define RKISP1_CIF_ISP_H_START BIT(7) +#define RKISP1_CIF_ISP_FLASH_ON BIT(8) +#define RKISP1_CIF_ISP_FLASH_OFF BIT(9) +#define RKISP1_CIF_ISP_SHUTTER_ON BIT(10) +#define RKISP1_CIF_ISP_SHUTTER_OFF BIT(11) +#define RKISP1_CIF_ISP_AFM_SUM_OF BIT(12) +#define RKISP1_CIF_ISP_AFM_LUM_OF BIT(13) +#define RKISP1_CIF_ISP_AFM_FIN BIT(14) +#define RKISP1_CIF_ISP_HIST_MEASURE_RDY BIT(15) +#define RKISP1_CIF_ISP_FLASH_CAP BIT(17) +#define RKISP1_CIF_ISP_EXP_END BIT(18) +#define RKISP1_CIF_ISP_VSM_END BIT(19) + +/* ISP_ERR */ +#define RKISP1_CIF_ISP_ERR_INFORM_SIZE BIT(0) +#define RKISP1_CIF_ISP_ERR_IS_SIZE BIT(1) +#define RKISP1_CIF_ISP_ERR_OUTFORM_SIZE BIT(2) + +/* MI_CTRL */ +#define RKISP1_CIF_MI_CTRL_MP_ENABLE BIT(0) +#define RKISP1_CIF_MI_CTRL_SP_ENABLE (2 << 0) +#define RKISP1_CIF_MI_CTRL_JPEG_ENABLE (4 << 0) +#define RKISP1_CIF_MI_CTRL_RAW_ENABLE (8 << 0) +#define RKISP1_CIF_MI_CTRL_HFLIP BIT(4) +#define RKISP1_CIF_MI_CTRL_VFLIP BIT(5) +#define RKISP1_CIF_MI_CTRL_ROT BIT(6) +#define RKISP1_CIF_MI_BYTE_SWAP BIT(7) +#define RKISP1_CIF_MI_SP_Y_FULL_YUV2RGB BIT(8) +#define RKISP1_CIF_MI_SP_CBCR_FULL_YUV2RGB BIT(9) +#define RKISP1_CIF_MI_SP_422NONCOSITEED BIT(10) +#define RKISP1_CIF_MI_MP_PINGPONG_ENABLE BIT(11) +#define RKISP1_CIF_MI_SP_PINGPONG_ENABLE BIT(12) +#define RKISP1_CIF_MI_MP_AUTOUPDATE_ENABLE BIT(13) +#define RKISP1_CIF_MI_SP_AUTOUPDATE_ENABLE BIT(14) +#define RKISP1_CIF_MI_LAST_PIXEL_SIG_ENABLE BIT(15) +#define RKISP1_CIF_MI_CTRL_BURST_LEN_LUM_16 (0 << 16) +#define RKISP1_CIF_MI_CTRL_BURST_LEN_LUM_32 (1 << 16) +#define RKISP1_CIF_MI_CTRL_BURST_LEN_LUM_64 (2 << 16) +#define RKISP1_CIF_MI_CTRL_BURST_LEN_CHROM_16 (0 << 18) +#define RKISP1_CIF_MI_CTRL_BURST_LEN_CHROM_32 (1 << 18) +#define RKISP1_CIF_MI_CTRL_BURST_LEN_CHROM_64 (2 << 18) +#define RKISP1_CIF_MI_CTRL_INIT_BASE_EN BIT(20) +#define RKISP1_CIF_MI_CTRL_INIT_OFFSET_EN BIT(21) +#define RKISP1_MI_CTRL_MP_WRITE_YUV_PLA_OR_RAW8 (0 << 22) +#define RKISP1_MI_CTRL_MP_WRITE_YUV_SPLA (1 << 22) +#define RKISP1_MI_CTRL_MP_WRITE_YUVINT (2 << 22) +#define RKISP1_MI_CTRL_MP_WRITE_RAW12 (2 << 22) +#define RKISP1_MI_CTRL_SP_WRITE_PLA (0 << 24) +#define RKISP1_MI_CTRL_SP_WRITE_SPLA (1 << 24) +#define RKISP1_MI_CTRL_SP_WRITE_INT (2 << 24) +#define RKISP1_MI_CTRL_SP_INPUT_YUV400 (0 << 26) +#define RKISP1_MI_CTRL_SP_INPUT_YUV420 (1 << 26) +#define RKISP1_MI_CTRL_SP_INPUT_YUV422 (2 << 26) +#define RKISP1_MI_CTRL_SP_INPUT_YUV444 (3 << 26) +#define RKISP1_MI_CTRL_SP_OUTPUT_YUV400 (0 << 28) +#define RKISP1_MI_CTRL_SP_OUTPUT_YUV420 (1 << 28) +#define RKISP1_MI_CTRL_SP_OUTPUT_YUV422 (2 << 28) +#define RKISP1_MI_CTRL_SP_OUTPUT_YUV444 (3 << 28) +#define RKISP1_MI_CTRL_SP_OUTPUT_RGB565 (4 << 28) +#define RKISP1_MI_CTRL_SP_OUTPUT_RGB666 (5 << 28) +#define RKISP1_MI_CTRL_SP_OUTPUT_RGB888 (6 << 28) + +#define RKISP1_MI_CTRL_MP_FMT_MASK GENMASK(23, 22) +#define RKISP1_MI_CTRL_SP_FMT_MASK GENMASK(30, 24) + +/* MI_INIT */ +#define RKISP1_CIF_MI_INIT_SKIP BIT(2) +#define RKISP1_CIF_MI_INIT_SOFT_UPD BIT(4) + +/* MI_CTRL_SHD */ +#define RKISP1_CIF_MI_CTRL_SHD_MP_IN_ENABLED BIT(0) +#define RKISP1_CIF_MI_CTRL_SHD_SP_IN_ENABLED BIT(1) +#define RKISP1_CIF_MI_CTRL_SHD_JPEG_IN_ENABLED BIT(2) +#define RKISP1_CIF_MI_CTRL_SHD_RAW_IN_ENABLED BIT(3) +#define RKISP1_CIF_MI_CTRL_SHD_MP_OUT_ENABLED BIT(16) +#define RKISP1_CIF_MI_CTRL_SHD_SP_OUT_ENABLED BIT(17) +#define RKISP1_CIF_MI_CTRL_SHD_JPEG_OUT_ENABLED BIT(18) +#define RKISP1_CIF_MI_CTRL_SHD_RAW_OUT_ENABLED BIT(19) + +/* RSZ_CTRL */ +#define RKISP1_CIF_RSZ_CTRL_SCALE_HY_ENABLE BIT(0) +#define RKISP1_CIF_RSZ_CTRL_SCALE_HC_ENABLE BIT(1) +#define RKISP1_CIF_RSZ_CTRL_SCALE_VY_ENABLE BIT(2) +#define RKISP1_CIF_RSZ_CTRL_SCALE_VC_ENABLE BIT(3) +#define RKISP1_CIF_RSZ_CTRL_SCALE_HY_UP BIT(4) +#define RKISP1_CIF_RSZ_CTRL_SCALE_HC_UP BIT(5) +#define RKISP1_CIF_RSZ_CTRL_SCALE_VY_UP BIT(6) +#define RKISP1_CIF_RSZ_CTRL_SCALE_VC_UP BIT(7) +#define RKISP1_CIF_RSZ_CTRL_CFG_UPD BIT(8) +#define RKISP1_CIF_RSZ_CTRL_CFG_UPD_AUTO BIT(9) +#define RKISP1_CIF_RSZ_SCALER_FACTOR BIT(16) + +/* MI_IMSC - MI_MIS - MI_RIS - MI_ICR - MI_ISR */ +#define RKISP1_CIF_MI_FRAME(stream) BIT((stream)->id) +#define RKISP1_CIF_MI_MBLK_LINE BIT(2) +#define RKISP1_CIF_MI_FILL_MP_Y BIT(3) +#define RKISP1_CIF_MI_WRAP_MP_Y BIT(4) +#define RKISP1_CIF_MI_WRAP_MP_CB BIT(5) +#define RKISP1_CIF_MI_WRAP_MP_CR BIT(6) +#define RKISP1_CIF_MI_WRAP_SP_Y BIT(7) +#define RKISP1_CIF_MI_WRAP_SP_CB BIT(8) +#define RKISP1_CIF_MI_WRAP_SP_CR BIT(9) +#define RKISP1_CIF_MI_DMA_READY BIT(11) + +/* MI_STATUS */ +#define RKISP1_CIF_MI_STATUS_MP_Y_FIFO_FULL BIT(0) +#define RKISP1_CIF_MI_STATUS_SP_Y_FIFO_FULL BIT(4) + +/* MI_DMA_CTRL */ +#define RKISP1_CIF_MI_DMA_CTRL_BURST_LEN_LUM_16 (0 << 0) +#define RKISP1_CIF_MI_DMA_CTRL_BURST_LEN_LUM_32 (1 << 0) +#define RKISP1_CIF_MI_DMA_CTRL_BURST_LEN_LUM_64 (2 << 0) +#define RKISP1_CIF_MI_DMA_CTRL_BURST_LEN_CHROM_16 (0 << 2) +#define RKISP1_CIF_MI_DMA_CTRL_BURST_LEN_CHROM_32 (1 << 2) +#define RKISP1_CIF_MI_DMA_CTRL_BURST_LEN_CHROM_64 (2 << 2) +#define RKISP1_CIF_MI_DMA_CTRL_READ_FMT_PLANAR (0 << 4) +#define RKISP1_CIF_MI_DMA_CTRL_READ_FMT_SPLANAR (1 << 4) +#define RKISP1_CIF_MI_DMA_CTRL_READ_FMT_PACKED (2 << 4) +#define RKISP1_CIF_MI_DMA_CTRL_FMT_YUV400 (0 << 6) +#define RKISP1_CIF_MI_DMA_CTRL_FMT_YUV420 (1 << 6) +#define RKISP1_CIF_MI_DMA_CTRL_FMT_YUV422 (2 << 6) +#define RKISP1_CIF_MI_DMA_CTRL_FMT_YUV444 (3 << 6) +#define RKISP1_CIF_MI_DMA_CTRL_BYTE_SWAP BIT(8) +#define RKISP1_CIF_MI_DMA_CTRL_CONTINUOUS_ENA BIT(9) +#define RKISP1_CIF_MI_DMA_CTRL_RGB_BAYER_NO (0 << 12) +#define RKISP1_CIF_MI_DMA_CTRL_RGB_BAYER_8BIT (1 << 12) +#define RKISP1_CIF_MI_DMA_CTRL_RGB_BAYER_16BIT (2 << 12) +/* MI_DMA_START */ +#define RKISP1_CIF_MI_DMA_START_ENABLE BIT(0) +/* MI_XTD_FORMAT_CTRL */ +#define RKISP1_CIF_MI_XTD_FMT_CTRL_MP_CB_CR_SWAP BIT(0) +#define RKISP1_CIF_MI_XTD_FMT_CTRL_SP_CB_CR_SWAP BIT(1) +#define RKISP1_CIF_MI_XTD_FMT_CTRL_DMA_CB_CR_SWAP BIT(2) + +/* VI_CCL */ +#define RKISP1_CIF_CCL_CIF_CLK_DIS BIT(2) +/* VI_ISP_CLK_CTRL */ +#define RKISP1_CIF_CLK_CTRL_ISP_RAW BIT(0) +#define RKISP1_CIF_CLK_CTRL_ISP_RGB BIT(1) +#define RKISP1_CIF_CLK_CTRL_ISP_YUV BIT(2) +#define RKISP1_CIF_CLK_CTRL_ISP_3A BIT(3) +#define RKISP1_CIF_CLK_CTRL_MIPI_RAW BIT(4) +#define RKISP1_CIF_CLK_CTRL_ISP_IE BIT(5) +#define RKISP1_CIF_CLK_CTRL_RSZ_RAM BIT(6) +#define RKISP1_CIF_CLK_CTRL_JPEG_RAM BIT(7) +#define RKISP1_CIF_CLK_CTRL_ACLK_ISP BIT(8) +#define RKISP1_CIF_CLK_CTRL_MI_IDC BIT(9) +#define RKISP1_CIF_CLK_CTRL_MI_MP BIT(10) +#define RKISP1_CIF_CLK_CTRL_MI_JPEG BIT(11) +#define RKISP1_CIF_CLK_CTRL_MI_DP BIT(12) +#define RKISP1_CIF_CLK_CTRL_MI_Y12 BIT(13) +#define RKISP1_CIF_CLK_CTRL_MI_SP BIT(14) +#define RKISP1_CIF_CLK_CTRL_MI_RAW0 BIT(15) +#define RKISP1_CIF_CLK_CTRL_MI_RAW1 BIT(16) +#define RKISP1_CIF_CLK_CTRL_MI_READ BIT(17) +#define RKISP1_CIF_CLK_CTRL_MI_RAWRD BIT(18) +#define RKISP1_CIF_CLK_CTRL_CP BIT(19) +#define RKISP1_CIF_CLK_CTRL_IE BIT(20) +#define RKISP1_CIF_CLK_CTRL_SI BIT(21) +#define RKISP1_CIF_CLK_CTRL_RSZM BIT(22) +#define RKISP1_CIF_CLK_CTRL_DPMUX BIT(23) +#define RKISP1_CIF_CLK_CTRL_JPEG BIT(24) +#define RKISP1_CIF_CLK_CTRL_RSZS BIT(25) +#define RKISP1_CIF_CLK_CTRL_MIPI BIT(26) +#define RKISP1_CIF_CLK_CTRL_MARVINMI BIT(27) +/* VI_ICCL */ +#define RKISP1_CIF_VI_ICCL_ISP_CLK BIT(0) +#define RKISP1_CIF_VI_ICCL_CP_CLK BIT(1) +#define RKISP1_CIF_VI_ICCL_RES_2 BIT(2) +#define RKISP1_CIF_VI_ICCL_MRSZ_CLK BIT(3) +#define RKISP1_CIF_VI_ICCL_SRSZ_CLK BIT(4) +#define RKISP1_CIF_VI_ICCL_JPEG_CLK BIT(5) +#define RKISP1_CIF_VI_ICCL_MI_CLK BIT(6) +#define RKISP1_CIF_VI_ICCL_RES_7 BIT(7) +#define RKISP1_CIF_VI_ICCL_IE_CLK BIT(8) +#define RKISP1_CIF_VI_ICCL_SIMP_CLK BIT(9) +#define RKISP1_CIF_VI_ICCL_SMIA_CLK BIT(10) +#define RKISP1_CIF_VI_ICCL_MIPI_CLK BIT(11) +#define RKISP1_CIF_VI_ICCL_DCROP_CLK BIT(12) +/* VI_IRCL */ +#define RKISP1_CIF_VI_IRCL_ISP_SW_RST BIT(0) +#define RKISP1_CIF_VI_IRCL_CP_SW_RST BIT(1) +#define RKISP1_CIF_VI_IRCL_YCS_SW_RST BIT(2) +#define RKISP1_CIF_VI_IRCL_MRSZ_SW_RST BIT(3) +#define RKISP1_CIF_VI_IRCL_SRSZ_SW_RST BIT(4) +#define RKISP1_CIF_VI_IRCL_JPEG_SW_RST BIT(5) +#define RKISP1_CIF_VI_IRCL_MI_SW_RST BIT(6) +#define RKISP1_CIF_VI_IRCL_CIF_SW_RST BIT(7) +#define RKISP1_CIF_VI_IRCL_IE_SW_RST BIT(8) +#define RKISP1_CIF_VI_IRCL_SI_SW_RST BIT(9) +#define RKISP1_CIF_VI_IRCL_MIPI_SW_RST BIT(11) + +/* C_PROC_CTR */ +#define RKISP1_CIF_C_PROC_CTR_ENABLE BIT(0) +#define RKISP1_CIF_C_PROC_YOUT_FULL BIT(1) +#define RKISP1_CIF_C_PROC_YIN_FULL BIT(2) +#define RKISP1_CIF_C_PROC_COUT_FULL BIT(3) +#define RKISP1_CIF_C_PROC_CTRL_RESERVED 0xFFFFFFFE +#define RKISP1_CIF_C_PROC_CONTRAST_RESERVED 0xFFFFFF00 +#define RKISP1_CIF_C_PROC_BRIGHTNESS_RESERVED 0xFFFFFF00 +#define RKISP1_CIF_C_PROC_HUE_RESERVED 0xFFFFFF00 +#define RKISP1_CIF_C_PROC_SATURATION_RESERVED 0xFFFFFF00 +#define RKISP1_CIF_C_PROC_MACC_RESERVED 0xE000E000 +#define RKISP1_CIF_C_PROC_TONE_RESERVED 0xF000 +/* DUAL_CROP_CTRL */ +#define RKISP1_CIF_DUAL_CROP_MP_MODE_BYPASS (0 << 0) +#define RKISP1_CIF_DUAL_CROP_MP_MODE_YUV (1 << 0) +#define RKISP1_CIF_DUAL_CROP_MP_MODE_RAW (2 << 0) +#define RKISP1_CIF_DUAL_CROP_SP_MODE_BYPASS (0 << 2) +#define RKISP1_CIF_DUAL_CROP_SP_MODE_YUV (1 << 2) +#define RKISP1_CIF_DUAL_CROP_SP_MODE_RAW (2 << 2) +#define RKISP1_CIF_DUAL_CROP_CFG_UPD_PERMANENT BIT(4) +#define RKISP1_CIF_DUAL_CROP_CFG_UPD BIT(5) +#define RKISP1_CIF_DUAL_CROP_GEN_CFG_UPD BIT(6) + +/* IMG_EFF_CTRL */ +#define RKISP1_CIF_IMG_EFF_CTRL_ENABLE BIT(0) +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_BLACKWHITE (0 << 1) +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_NEGATIVE (1 << 1) +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_SEPIA (2 << 1) +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_COLOR_SEL (3 << 1) +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_EMBOSS (4 << 1) +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_SKETCH (5 << 1) +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_SHARPEN (6 << 1) +#define RKISP1_CIF_IMG_EFF_CTRL_CFG_UPD BIT(4) +#define RKISP1_CIF_IMG_EFF_CTRL_YCBCR_FULL BIT(5) + +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_BLACKWHITE_SHIFT 0 +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_NEGATIVE_SHIFT 1 +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_SEPIA_SHIFT 2 +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_COLOR_SEL_SHIFT 3 +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_EMBOSS_SHIFT 4 +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_SKETCH_SHIFT 5 +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_SHARPEN_SHIFT 6 +#define RKISP1_CIF_IMG_EFF_CTRL_MODE_MASK 0xE + +/* IMG_EFF_COLOR_SEL */ +#define RKISP1_CIF_IMG_EFF_COLOR_RGB 0 +#define RKISP1_CIF_IMG_EFF_COLOR_B (1 << 0) +#define RKISP1_CIF_IMG_EFF_COLOR_G (2 << 0) +#define RKISP1_CIF_IMG_EFF_COLOR_GB (3 << 0) +#define RKISP1_CIF_IMG_EFF_COLOR_R (4 << 0) +#define RKISP1_CIF_IMG_EFF_COLOR_RB (5 << 0) +#define RKISP1_CIF_IMG_EFF_COLOR_RG (6 << 0) +#define RKISP1_CIF_IMG_EFF_COLOR_RGB2 (7 << 0) + +/* MIPI_CTRL */ +#define RKISP1_CIF_MIPI_CTRL_OUTPUT_ENA BIT(0) +#define RKISP1_CIF_MIPI_CTRL_SHUTDOWNLANES(a) (((a) & 0xF) << 8) +#define RKISP1_CIF_MIPI_CTRL_NUM_LANES(a) (((a) & 0x3) << 12) +#define RKISP1_CIF_MIPI_CTRL_ERR_SOT_HS_SKIP BIT(16) +#define RKISP1_CIF_MIPI_CTRL_ERR_SOT_SYNC_HS_SKIP BIT(17) +#define RKISP1_CIF_MIPI_CTRL_CLOCKLANE_ENA BIT(18) + +/* MIPI_DATA_SEL */ +#define RKISP1_CIF_MIPI_DATA_SEL_VC(a) (((a) & 0x3) << 6) +#define RKISP1_CIF_MIPI_DATA_SEL_DT(a) (((a) & 0x3F) << 0) + +/* MIPI_IMSC, MIPI_RIS, MIPI_MIS, MIPI_ICR, MIPI_ISR */ +#define RKISP1_CIF_MIPI_SYNC_FIFO_OVFLW(a) (((a) & 0xF) << 0) +#define RKISP1_CIF_MIPI_ERR_SOT(a) (((a) & 0xF) << 4) +#define RKISP1_CIF_MIPI_ERR_SOT_SYNC(a) (((a) & 0xF) << 8) +#define RKISP1_CIF_MIPI_ERR_EOT_SYNC(a) (((a) & 0xF) << 12) +#define RKISP1_CIF_MIPI_ERR_CTRL(a) (((a) & 0xF) << 16) +#define RKISP1_CIF_MIPI_ERR_PROTOCOL BIT(20) +#define RKISP1_CIF_MIPI_ERR_ECC1 BIT(21) +#define RKISP1_CIF_MIPI_ERR_ECC2 BIT(22) +#define RKISP1_CIF_MIPI_ERR_CS BIT(23) +#define RKISP1_CIF_MIPI_FRAME_END BIT(24) +#define RKISP1_CIF_MIPI_ADD_DATA_OVFLW BIT(25) +#define RKISP1_CIF_MIPI_ADD_DATA_WATER_MARK BIT(26) + +#define RKISP1_CIF_MIPI_ERR_CSI (RKISP1_CIF_MIPI_ERR_PROTOCOL | \ + RKISP1_CIF_MIPI_ERR_ECC1 | \ + RKISP1_CIF_MIPI_ERR_ECC2 | \ + RKISP1_CIF_MIPI_ERR_CS) + +#define RKISP1_CIF_MIPI_ERR_DPHY (RKISP1_CIF_MIPI_ERR_SOT(3) | \ + RKISP1_CIF_MIPI_ERR_SOT_SYNC(3) | \ + RKISP1_CIF_MIPI_ERR_EOT_SYNC(3) | \ + RKISP1_CIF_MIPI_ERR_CTRL(3)) + +/* SUPER_IMPOSE */ +#define RKISP1_CIF_SUPER_IMP_CTRL_NORMAL_MODE BIT(0) +#define RKISP1_CIF_SUPER_IMP_CTRL_REF_IMG_MEM BIT(1) +#define RKISP1_CIF_SUPER_IMP_CTRL_TRANSP_DIS BIT(2) + +/* ISP HISTOGRAM CALCULATION : ISP_HIST_PROP */ +#define RKISP1_CIF_ISP_HIST_PROP_MODE_DIS_V10 (0 << 0) +#define RKISP1_CIF_ISP_HIST_PROP_MODE_RGB_V10 (1 << 0) +#define RKISP1_CIF_ISP_HIST_PROP_MODE_RED_V10 (2 << 0) +#define RKISP1_CIF_ISP_HIST_PROP_MODE_GREEN_V10 (3 << 0) +#define RKISP1_CIF_ISP_HIST_PROP_MODE_BLUE_V10 (4 << 0) +#define RKISP1_CIF_ISP_HIST_PROP_MODE_LUM_V10 (5 << 0) +#define RKISP1_CIF_ISP_HIST_PROP_MODE_MASK_V10 0x7 +#define RKISP1_CIF_ISP_HIST_PREDIV_SET_V10(x) (((x) & 0x7F) << 3) +#define RKISP1_CIF_ISP_HIST_WEIGHT_SET_V10(v0, v1, v2, v3) \ + (((v0) & 0x1F) | (((v1) & 0x1F) << 8) |\ + (((v2) & 0x1F) << 16) | \ + (((v3) & 0x1F) << 24)) + +#define RKISP1_CIF_ISP_HIST_WINDOW_OFFSET_RESERVED_V10 0xFFFFF000 +#define RKISP1_CIF_ISP_HIST_WINDOW_SIZE_RESERVED_V10 0xFFFFF800 +#define RKISP1_CIF_ISP_HIST_WEIGHT_RESERVED_V10 0xE0E0E0E0 +#define RKISP1_CIF_ISP_MAX_HIST_PREDIVIDER_V10 0x0000007F +#define RKISP1_CIF_ISP_HIST_ROW_NUM_V10 5 +#define RKISP1_CIF_ISP_HIST_COLUMN_NUM_V10 5 +#define RKISP1_CIF_ISP_HIST_GET_BIN_V10(x) ((x) & 0x000FFFFF) + +/* ISP HISTOGRAM CALCULATION : CIF_ISP_HIST */ +#define RKISP1_CIF_ISP_HIST_CTRL_EN_SET_V12(x) (((x) & 0x01) << 0) +#define RKISP1_CIF_ISP_HIST_CTRL_EN_MASK_V12 RKISP1_CIF_ISP_HIST_CTRL_EN_SET_V12(0x01) +#define RKISP1_CIF_ISP_HIST_CTRL_STEPSIZE_SET_V12(x) (((x) & 0x7F) << 1) +#define RKISP1_CIF_ISP_HIST_CTRL_MODE_SET_V12(x) (((x) & 0x07) << 8) +#define RKISP1_CIF_ISP_HIST_CTRL_MODE_MASK_V12 RKISP1_CIF_ISP_HIST_CTRL_MODE_SET_V12(0x07) +#define RKISP1_CIF_ISP_HIST_CTRL_AUTOSTOP_SET_V12(x) (((x) & 0x01) << 11) +#define RKISP1_CIF_ISP_HIST_CTRL_WATERLINE_SET_V12(x) (((x) & 0xFFF) << 12) +#define RKISP1_CIF_ISP_HIST_CTRL_DATASEL_SET_V12(x) (((x) & 0x07) << 24) +#define RKISP1_CIF_ISP_HIST_CTRL_INTRSEL_SET_V12(x) (((x) & 0x01) << 27) +#define RKISP1_CIF_ISP_HIST_CTRL_WNDNUM_SET_V12(x) (((x) & 0x03) << 28) +#define RKISP1_CIF_ISP_HIST_CTRL_DBGEN_SET_V12(x) (((x) & 0x01) << 30) +#define RKISP1_CIF_ISP_HIST_ROW_NUM_V12 15 +#define RKISP1_CIF_ISP_HIST_COLUMN_NUM_V12 15 +#define RKISP1_CIF_ISP_HIST_WEIGHT_REG_SIZE_V12 \ + (RKISP1_CIF_ISP_HIST_ROW_NUM_V12 * RKISP1_CIF_ISP_HIST_COLUMN_NUM_V12) + +#define RKISP1_CIF_ISP_HIST_WEIGHT_SET_V12(v0, v1, v2, v3) \ + (((v0) & 0x3F) | (((v1) & 0x3F) << 8) |\ + (((v2) & 0x3F) << 16) |\ + (((v3) & 0x3F) << 24)) + +#define RKISP1_CIF_ISP_HIST_OFFS_SET_V12(v0, v1) \ + (((v0) & 0x1FFF) | (((v1) & 0x1FFF) << 16)) +#define RKISP1_CIF_ISP_HIST_SIZE_SET_V12(v0, v1) \ + (((v0) & 0x7FF) | (((v1) & 0x7FF) << 16)) + +#define RKISP1_CIF_ISP_HIST_GET_BIN0_V12(x) \ + ((x) & 0xFFFF) +#define RKISP1_CIF_ISP_HIST_GET_BIN1_V12(x) \ + (((x) >> 16) & 0xFFFF) + +/* AUTO FOCUS MEASUREMENT: ISP_AFM_CTRL */ +#define RKISP1_ISP_AFM_CTRL_ENABLE BIT(0) + +/* SHUTTER CONTROL */ +#define RKISP1_CIF_ISP_SH_CTRL_SH_ENA BIT(0) +#define RKISP1_CIF_ISP_SH_CTRL_REP_EN BIT(1) +#define RKISP1_CIF_ISP_SH_CTRL_SRC_SH_TRIG BIT(2) +#define RKISP1_CIF_ISP_SH_CTRL_EDGE_POS BIT(3) +#define RKISP1_CIF_ISP_SH_CTRL_POL_LOW BIT(4) + +/* FLASH MODULE */ +/* ISP_FLASH_CMD */ +#define RKISP1_CIFFLASH_CMD_PRELIGHT_ON BIT(0) +#define RKISP1_CIFFLASH_CMD_FLASH_ON BIT(1) +#define RKISP1_CIFFLASH_CMD_PRE_FLASH_ON BIT(2) +/* ISP_FLASH_CONFIG */ +#define RKISP1_CIFFLASH_CONFIG_PRELIGHT_END BIT(0) +#define RKISP1_CIFFLASH_CONFIG_VSYNC_POS BIT(1) +#define RKISP1_CIFFLASH_CONFIG_PRELIGHT_LOW BIT(2) +#define RKISP1_CIFFLASH_CONFIG_SRC_FL_TRIG BIT(3) +#define RKISP1_CIFFLASH_CONFIG_DELAY(a) (((a) & 0xF) << 4) + +/* Demosaic: ISP_DEMOSAIC */ +#define RKISP1_CIF_ISP_DEMOSAIC_BYPASS BIT(10) +#define RKISP1_CIF_ISP_DEMOSAIC_TH(x) ((x) & 0xFF) + +/* ISP_FLAGS_SHD */ +#define RKISP1_CIF_ISP_FLAGS_SHD_ISP_ENABLE_SHD BIT(0) +#define RKISP1_CIF_ISP_FLAGS_SHD_ISP_ENABLE_INFORM_SHD BIT(1) +#define RKISP1_CIF_ISP_FLAGS_SHD_INFORM_FIELD BIT(2) +#define RKISP1_CIF_ISP_FLAGS_SHD_S_DATA_MASK GENMASK(27, 16) +#define RKISP1_CIF_ISP_FLAGS_SHD_S_DATA_SHIFT 16 +#define RKISP1_CIF_ISP_FLAGS_SHD_S_VSYNC BIT(30) +#define RKISP1_CIF_ISP_FLAGS_SHD_S_HSYNC BIT(31) + +/* AWB */ +/* ISP_AWB_PROP */ +#define RKISP1_CIF_ISP_AWB_YMAX_CMP_EN BIT(2) +#define RKISP1_CIF_ISP_AWB_YMAX_READ(x) (((x) >> 2) & 1) +#define RKISP1_CIF_ISP_AWB_MODE_RGB_EN ((1 << 31) | (0x2 << 0)) +#define RKISP1_CIF_ISP_AWB_MODE_YCBCR_EN ((0 << 31) | (0x2 << 0)) +#define RKISP1_CIF_ISP_AWB_MODE_MASK_NONE 0xFFFFFFFC +#define RKISP1_CIF_ISP_AWB_MODE_READ(x) ((x) & 3) +#define RKISP1_CIF_ISP_AWB_SET_FRAMES_V12(x) (((x) & 0x07) << 28) +#define RKISP1_CIF_ISP_AWB_SET_FRAMES_MASK_V12 RKISP1_CIF_ISP_AWB_SET_FRAMES_V12(0x07) +/* ISP_AWB_GAIN_RB, ISP_AWB_GAIN_G */ +#define RKISP1_CIF_ISP_AWB_GAIN_R_SET(x) (((x) & 0x3FF) << 16) +#define RKISP1_CIF_ISP_AWB_GAIN_R_READ(x) (((x) >> 16) & 0x3FF) +#define RKISP1_CIF_ISP_AWB_GAIN_B_SET(x) ((x) & 0x3FFF) +#define RKISP1_CIF_ISP_AWB_GAIN_B_READ(x) ((x) & 0x3FFF) +/* ISP_AWB_REF */ +#define RKISP1_CIF_ISP_AWB_REF_CR_SET(x) (((x) & 0xFF) << 8) +#define RKISP1_CIF_ISP_AWB_REF_CR_READ(x) (((x) >> 8) & 0xFF) +#define RKISP1_CIF_ISP_AWB_REF_CB_READ(x) ((x) & 0xFF) +/* ISP_AWB_THRESH */ +#define RKISP1_CIF_ISP_AWB_MAX_CS_SET(x) (((x) & 0xFF) << 8) +#define RKISP1_CIF_ISP_AWB_MAX_CS_READ(x) (((x) >> 8) & 0xFF) +#define RKISP1_CIF_ISP_AWB_MIN_C_READ(x) ((x) & 0xFF) +#define RKISP1_CIF_ISP_AWB_MIN_Y_SET(x) (((x) & 0xFF) << 16) +#define RKISP1_CIF_ISP_AWB_MIN_Y_READ(x) (((x) >> 16) & 0xFF) +#define RKISP1_CIF_ISP_AWB_MAX_Y_SET(x) (((x) & 0xFF) << 24) +#define RKISP1_CIF_ISP_AWB_MAX_Y_READ(x) (((x) >> 24) & 0xFF) +/* ISP_AWB_MEAN */ +#define RKISP1_CIF_ISP_AWB_GET_MEAN_CR_R(x) ((x) & 0xFF) +#define RKISP1_CIF_ISP_AWB_GET_MEAN_CB_B(x) (((x) >> 8) & 0xFF) +#define RKISP1_CIF_ISP_AWB_GET_MEAN_Y_G(x) (((x) >> 16) & 0xFF) +/* ISP_AWB_WHITE_CNT */ +#define RKISP1_CIF_ISP_AWB_GET_PIXEL_CNT(x) ((x) & 0x3FFFFFF) + +#define RKISP1_CIF_ISP_AWB_GAINS_MAX_VAL 0x000003FF +#define RKISP1_CIF_ISP_AWB_WINDOW_OFFSET_MAX 0x00000FFF +#define RKISP1_CIF_ISP_AWB_WINDOW_MAX_SIZE 0x00001FFF +#define RKISP1_CIF_ISP_AWB_CBCR_MAX_REF 0x000000FF +#define RKISP1_CIF_ISP_AWB_THRES_MAX_YC 0x000000FF + +/* AE */ +/* ISP_EXP_CTRL */ +#define RKISP1_CIF_ISP_EXP_ENA BIT(0) +#define RKISP1_CIF_ISP_EXP_CTRL_AUTOSTOP BIT(1) +#define RKISP1_CIF_ISP_EXP_CTRL_WNDNUM_SET_V12(x) (((x) & 0x03) << 2) +/* + *'1' luminance calculation according to Y=(R+G+B) x 0.332 (85/256) + *'0' luminance calculation according to Y=16+0.25R+0.5G+0.1094B + */ +#define RKISP1_CIF_ISP_EXP_CTRL_MEASMODE_1 BIT(31) + +/* ISP_EXP_H_SIZE */ +#define RKISP1_CIF_ISP_EXP_H_SIZE_SET_V10(x) ((x) & 0x7FF) +#define RKISP1_CIF_ISP_EXP_HEIGHT_MASK_V10 0x000007FF +#define RKISP1_CIF_ISP_EXP_H_SIZE_SET_V12(x) ((x) & 0x7FF) +#define RKISP1_CIF_ISP_EXP_HEIGHT_MASK_V12 0x000007FF +/* ISP_EXP_V_SIZE : vertical size must be a multiple of 2). */ +#define RKISP1_CIF_ISP_EXP_V_SIZE_SET_V10(x) ((x) & 0x7FE) +#define RKISP1_CIF_ISP_EXP_V_SIZE_SET_V12(x) (((x) & 0x7FE) << 16) + +/* ISP_EXP_H_OFFSET */ +#define RKISP1_CIF_ISP_EXP_H_OFFSET_SET_V10(x) ((x) & 0x1FFF) +#define RKISP1_CIF_ISP_EXP_MAX_HOFFS_V10 2424 +#define RKISP1_CIF_ISP_EXP_H_OFFSET_SET_V12(x) ((x) & 0x1FFF) +#define RKISP1_CIF_ISP_EXP_MAX_HOFFS_V12 0x1FFF +/* ISP_EXP_V_OFFSET */ +#define RKISP1_CIF_ISP_EXP_V_OFFSET_SET_V10(x) ((x) & 0x1FFF) +#define RKISP1_CIF_ISP_EXP_MAX_VOFFS_V10 1806 +#define RKISP1_CIF_ISP_EXP_V_OFFSET_SET_V12(x) (((x) & 0x1FFF) << 16) +#define RKISP1_CIF_ISP_EXP_MAX_VOFFS_V12 0x1FFF + +#define RKISP1_CIF_ISP_EXP_ROW_NUM_V10 5 +#define RKISP1_CIF_ISP_EXP_COLUMN_NUM_V10 5 +#define RKISP1_CIF_ISP_EXP_NUM_LUMA_REGS_V10 \ + (RKISP1_CIF_ISP_EXP_ROW_NUM_V10 * RKISP1_CIF_ISP_EXP_COLUMN_NUM_V10) +#define RKISP1_CIF_ISP_EXP_BLOCK_MAX_HSIZE_V10 516 +#define RKISP1_CIF_ISP_EXP_BLOCK_MIN_HSIZE_V10 35 +#define RKISP1_CIF_ISP_EXP_BLOCK_MAX_VSIZE_V10 390 +#define RKISP1_CIF_ISP_EXP_BLOCK_MIN_VSIZE_V10 28 +#define RKISP1_CIF_ISP_EXP_MAX_HSIZE_V10 \ + (RKISP1_CIF_ISP_EXP_BLOCK_MAX_HSIZE_V10 * RKISP1_CIF_ISP_EXP_COLUMN_NUM_V10 + 1) +#define RKISP1_CIF_ISP_EXP_MIN_HSIZE_V10 \ + (RKISP1_CIF_ISP_EXP_BLOCK_MIN_HSIZE_V10 * RKISP1_CIF_ISP_EXP_COLUMN_NUM_V10 + 1) +#define RKISP1_CIF_ISP_EXP_MAX_VSIZE_V10 \ + (RKISP1_CIF_ISP_EXP_BLOCK_MAX_VSIZE_V10 * RKISP1_CIF_ISP_EXP_ROW_NUM_V10 + 1) +#define RKISP1_CIF_ISP_EXP_MIN_VSIZE_V10 \ + (RKISP1_CIF_ISP_EXP_BLOCK_MIN_VSIZE_V10 * RKISP1_CIF_ISP_EXP_ROW_NUM_V10 + 1) + +#define RKISP1_CIF_ISP_EXP_ROW_NUM_V12 15 +#define RKISP1_CIF_ISP_EXP_COLUMN_NUM_V12 15 +#define RKISP1_CIF_ISP_EXP_NUM_LUMA_REGS_V12 \ + (RKISP1_CIF_ISP_EXP_ROW_NUM_V12 * RKISP1_CIF_ISP_EXP_COLUMN_NUM_V12) + +#define RKISP1_CIF_ISP_EXP_BLOCK_MAX_HSIZE_V12 0x7FF +#define RKISP1_CIF_ISP_EXP_BLOCK_MIN_HSIZE_V12 0xE +#define RKISP1_CIF_ISP_EXP_BLOCK_MAX_VSIZE_V12 0x7FE +#define RKISP1_CIF_ISP_EXP_BLOCK_MIN_VSIZE_V12 0xE +#define RKISP1_CIF_ISP_EXP_MAX_HSIZE_V12 \ + (RKISP1_CIF_ISP_EXP_BLOCK_MAX_HSIZE_V12 * RKISP1_CIF_ISP_EXP_COLUMN_NUM_V12 + 1) +#define RKISP1_CIF_ISP_EXP_MIN_HSIZE_V12 \ + (RKISP1_CIF_ISP_EXP_BLOCK_MIN_HSIZE_V12 * RKISP1_CIF_ISP_EXP_COLUMN_NUM_V12 + 1) +#define RKISP1_CIF_ISP_EXP_MAX_VSIZE_V12 \ + (RKISP1_CIF_ISP_EXP_BLOCK_MAX_VSIZE_V12 * RKISP1_CIF_ISP_EXP_ROW_NUM_V12 + 1) +#define RKISP1_CIF_ISP_EXP_MIN_VSIZE_V12 \ + (RKISP1_CIF_ISP_EXP_BLOCK_MIN_VSIZE_V12 * RKISP1_CIF_ISP_EXP_ROW_NUM_V12 + 1) + +#define RKISP1_CIF_ISP_EXP_GET_MEAN_xy0_V12(x) ((x) & 0xFF) +#define RKISP1_CIF_ISP_EXP_GET_MEAN_xy1_V12(x) (((x) >> 8) & 0xFF) +#define RKISP1_CIF_ISP_EXP_GET_MEAN_xy2_V12(x) (((x) >> 16) & 0xFF) +#define RKISP1_CIF_ISP_EXP_GET_MEAN_xy3_V12(x) (((x) >> 24) & 0xFF) + +/* LSC: ISP_LSC_CTRL */ +#define RKISP1_CIF_ISP_LSC_CTRL_ENA BIT(0) +#define RKISP1_CIF_ISP_LSC_SECT_SIZE_RESERVED 0xFC00FC00 +#define RKISP1_CIF_ISP_LSC_GRAD_RESERVED_V10 0xF000F000 +#define RKISP1_CIF_ISP_LSC_SAMPLE_RESERVED_V10 0xF000F000 +#define RKISP1_CIF_ISP_LSC_GRAD_RESERVED_V12 0xE000E000 +#define RKISP1_CIF_ISP_LSC_SAMPLE_RESERVED_V12 0xE000E000 +#define RKISP1_CIF_ISP_LSC_TABLE_DATA_V10(v0, v1) \ + (((v0) & 0xFFF) | (((v1) & 0xFFF) << 12)) +#define RKISP1_CIF_ISP_LSC_TABLE_DATA_V12(v0, v1) \ + (((v0) & 0x1FFF) | (((v1) & 0x1FFF) << 13)) +#define RKISP1_CIF_ISP_LSC_SECT_SIZE(v0, v1) \ + (((v0) & 0xFFF) | (((v1) & 0xFFF) << 16)) +#define RKISP1_CIF_ISP_LSC_SECT_GRAD(v0, v1) \ + (((v0) & 0xFFF) | (((v1) & 0xFFF) << 16)) + +/* LSC: ISP_LSC_TABLE_SEL */ +#define RKISP1_CIF_ISP_LSC_TABLE_0 0 +#define RKISP1_CIF_ISP_LSC_TABLE_1 1 + +/* LSC: ISP_LSC_STATUS */ +#define RKISP1_CIF_ISP_LSC_ACTIVE_TABLE BIT(1) +#define RKISP1_CIF_ISP_LSC_TABLE_ADDRESS_0 0 +#define RKISP1_CIF_ISP_LSC_TABLE_ADDRESS_153 153 + +/* FLT */ +/* ISP_FILT_MODE */ +#define RKISP1_CIF_ISP_FLT_ENA BIT(0) + +/* + * 0: green filter static mode (active filter factor = FILT_FAC_MID) + * 1: dynamic noise reduction/sharpen Default + */ +#define RKISP1_CIF_ISP_FLT_MODE_DNR BIT(1) +#define RKISP1_CIF_ISP_FLT_MODE_MAX 1 +#define RKISP1_CIF_ISP_FLT_CHROMA_V_MODE(x) (((x) & 0x3) << 4) +#define RKISP1_CIF_ISP_FLT_CHROMA_H_MODE(x) (((x) & 0x3) << 6) +#define RKISP1_CIF_ISP_FLT_CHROMA_MODE_MAX 3 +#define RKISP1_CIF_ISP_FLT_GREEN_STAGE1(x) (((x) & 0xF) << 8) +#define RKISP1_CIF_ISP_FLT_GREEN_STAGE1_MAX 8 +#define RKISP1_CIF_ISP_FLT_THREAD_RESERVED 0xFFFFFC00 +#define RKISP1_CIF_ISP_FLT_FAC_RESERVED 0xFFFFFFC0 +#define RKISP1_CIF_ISP_FLT_LUM_WEIGHT_RESERVED 0xFFF80000 + +#define RKISP1_CIF_ISP_CTK_COEFF_RESERVED 0xFFFFF800 +#define RKISP1_CIF_ISP_XTALK_OFFSET_RESERVED 0xFFFFF000 + +/* GOC */ +#define RKISP1_CIF_ISP_GAMMA_OUT_MODE_EQU BIT(0) +#define RKISP1_CIF_ISP_GOC_MODE_MAX 1 +#define RKISP1_CIF_ISP_GOC_RESERVED 0xFFFFF800 +/* ISP_CTRL BIT 11*/ +#define RKISP1_CIF_ISP_CTRL_ISP_GAMMA_OUT_ENA_READ(x) (((x) >> 11) & 1) + +/* DPCC */ +#define RKISP1_CIF_ISP_DPCC_MODE_DPCC_ENABLE BIT(0) +#define RKISP1_CIF_ISP_DPCC_MODE_GRAYSCALE_MODE BIT(1) +#define RKISP1_CIF_ISP_DPCC_OUTPUT_MODE_MASK GENMASK(3, 0) +#define RKISP1_CIF_ISP_DPCC_SET_USE_MASK GENMASK(3, 0) +#define RKISP1_CIF_ISP_DPCC_METHODS_SET_MASK 0x00001f1f +#define RKISP1_CIF_ISP_DPCC_LINE_THRESH_MASK 0x0000ffff +#define RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_MASK 0x00003f3f +#define RKISP1_CIF_ISP_DPCC_PG_FAC_MASK 0x00003f3f +#define RKISP1_CIF_ISP_DPCC_RND_THRESH_MASK 0x0000ffff +#define RKISP1_CIF_ISP_DPCC_RG_FAC_MASK 0x00003f3f +#define RKISP1_CIF_ISP_DPCC_RO_LIMIT_MASK 0x00000fff +#define RKISP1_CIF_ISP_DPCC_RND_OFFS_MASK 0x00000fff + +/* BLS */ +/* ISP_BLS_CTRL */ +#define RKISP1_CIF_ISP_BLS_ENA BIT(0) +#define RKISP1_CIF_ISP_BLS_MODE_MEASURED BIT(1) +#define RKISP1_CIF_ISP_BLS_MODE_FIXED 0 +#define RKISP1_CIF_ISP_BLS_WINDOW_1 (1 << 2) +#define RKISP1_CIF_ISP_BLS_WINDOW_2 (2 << 2) + +/* GAMMA-IN */ +#define RKISP1_CIFISP_DEGAMMA_X_RESERVED \ + ((1 << 31) | (1 << 27) | (1 << 23) | (1 << 19) |\ + (1 << 15) | (1 << 11) | (1 << 7) | (1 << 3)) +#define RKISP1_CIFISP_DEGAMMA_Y_RESERVED 0xFFFFF000 + +/* GAMMA-OUT */ +#define RKISP1_CIF_ISP_GAMMA_VALUE_V12(x, y) \ + (((x) & 0xFFF) << 16 | ((y) & 0xFFF) << 0) + +/* AFM */ +#define RKISP1_CIF_ISP_AFM_ENA BIT(0) +#define RKISP1_CIF_ISP_AFM_THRES_RESERVED 0xFFFF0000 +#define RKISP1_CIF_ISP_AFM_VAR_SHIFT_RESERVED 0xFFF8FFF8 +#define RKISP1_CIF_ISP_AFM_WINDOW_X_RESERVED 0xE000 +#define RKISP1_CIF_ISP_AFM_WINDOW_Y_RESERVED 0xF000 +#define RKISP1_CIF_ISP_AFM_WINDOW_X_MIN 0x5 +#define RKISP1_CIF_ISP_AFM_WINDOW_Y_MIN 0x2 +#define RKISP1_CIF_ISP_AFM_WINDOW_X(x) (((x) & 0x1FFF) << 16) +#define RKISP1_CIF_ISP_AFM_WINDOW_Y(x) ((x) & 0x1FFF) +#define RKISP1_CIF_ISP_AFM_SET_SHIFT_a_V12(x, y) (((x) & 0x7) << 16 | ((y) & 0x7) << 0) +#define RKISP1_CIF_ISP_AFM_SET_SHIFT_b_V12(x, y) (((x) & 0x7) << 20 | ((y) & 0x7) << 4) +#define RKISP1_CIF_ISP_AFM_SET_SHIFT_c_V12(x, y) (((x) & 0x7) << 24 | ((y) & 0x7) << 8) +#define RKISP1_CIF_ISP_AFM_GET_LUM_SHIFT_a_V12(x) (((x) & 0x70000) >> 16) +#define RKISP1_CIF_ISP_AFM_GET_AFM_SHIFT_a_V12(x) ((x) & 0x7) + +/* DPF */ +#define RKISP1_CIF_ISP_DPF_MODE_EN BIT(0) +#define RKISP1_CIF_ISP_DPF_MODE_B_FLT_DIS BIT(1) +#define RKISP1_CIF_ISP_DPF_MODE_GB_FLT_DIS BIT(2) +#define RKISP1_CIF_ISP_DPF_MODE_GR_FLT_DIS BIT(3) +#define RKISP1_CIF_ISP_DPF_MODE_R_FLT_DIS BIT(4) +#define RKISP1_CIF_ISP_DPF_MODE_RB_FLTSIZE_9x9 BIT(5) +#define RKISP1_CIF_ISP_DPF_MODE_NLL_SEGMENTATION BIT(6) +#define RKISP1_CIF_ISP_DPF_MODE_AWB_GAIN_COMP BIT(7) +#define RKISP1_CIF_ISP_DPF_MODE_LSC_GAIN_COMP BIT(8) +#define RKISP1_CIF_ISP_DPF_MODE_USE_NF_GAIN BIT(9) +#define RKISP1_CIF_ISP_DPF_NF_GAIN_RESERVED 0xFFFFF000 +#define RKISP1_CIF_ISP_DPF_SPATIAL_COEFF_MAX 0x1F +#define RKISP1_CIF_ISP_DPF_NLL_COEFF_N_MAX 0x3FF + +/* =================================================================== */ +/* CIF Registers */ +/* =================================================================== */ +#define RKISP1_CIF_CTRL_BASE 0x00000000 +#define RKISP1_CIF_VI_CCL (RKISP1_CIF_CTRL_BASE + 0x00000000) +#define RKISP1_CIF_VI_ID (RKISP1_CIF_CTRL_BASE + 0x00000008) +#define RKISP1_CIF_VI_ISP_CLK_CTRL_V12 (RKISP1_CIF_CTRL_BASE + 0x0000000C) +#define RKISP1_CIF_VI_ICCL (RKISP1_CIF_CTRL_BASE + 0x00000010) +#define RKISP1_CIF_VI_IRCL (RKISP1_CIF_CTRL_BASE + 0x00000014) +#define RKISP1_CIF_VI_DPCL (RKISP1_CIF_CTRL_BASE + 0x00000018) + +#define RKISP1_CIF_IMG_EFF_BASE 0x00000200 +#define RKISP1_CIF_IMG_EFF_CTRL (RKISP1_CIF_IMG_EFF_BASE + 0x00000000) +#define RKISP1_CIF_IMG_EFF_COLOR_SEL (RKISP1_CIF_IMG_EFF_BASE + 0x00000004) +#define RKISP1_CIF_IMG_EFF_MAT_1 (RKISP1_CIF_IMG_EFF_BASE + 0x00000008) +#define RKISP1_CIF_IMG_EFF_MAT_2 (RKISP1_CIF_IMG_EFF_BASE + 0x0000000C) +#define RKISP1_CIF_IMG_EFF_MAT_3 (RKISP1_CIF_IMG_EFF_BASE + 0x00000010) +#define RKISP1_CIF_IMG_EFF_MAT_4 (RKISP1_CIF_IMG_EFF_BASE + 0x00000014) +#define RKISP1_CIF_IMG_EFF_MAT_5 (RKISP1_CIF_IMG_EFF_BASE + 0x00000018) +#define RKISP1_CIF_IMG_EFF_TINT (RKISP1_CIF_IMG_EFF_BASE + 0x0000001C) +#define RKISP1_CIF_IMG_EFF_CTRL_SHD (RKISP1_CIF_IMG_EFF_BASE + 0x00000020) +#define RKISP1_CIF_IMG_EFF_SHARPEN (RKISP1_CIF_IMG_EFF_BASE + 0x00000024) + +#define RKISP1_CIF_SUPER_IMP_BASE 0x00000300 +#define RKISP1_CIF_SUPER_IMP_CTRL (RKISP1_CIF_SUPER_IMP_BASE + 0x00000000) +#define RKISP1_CIF_SUPER_IMP_OFFSET_X (RKISP1_CIF_SUPER_IMP_BASE + 0x00000004) +#define RKISP1_CIF_SUPER_IMP_OFFSET_Y (RKISP1_CIF_SUPER_IMP_BASE + 0x00000008) +#define RKISP1_CIF_SUPER_IMP_COLOR_Y (RKISP1_CIF_SUPER_IMP_BASE + 0x0000000C) +#define RKISP1_CIF_SUPER_IMP_COLOR_CB (RKISP1_CIF_SUPER_IMP_BASE + 0x00000010) +#define RKISP1_CIF_SUPER_IMP_COLOR_CR (RKISP1_CIF_SUPER_IMP_BASE + 0x00000014) + +#define RKISP1_CIF_ISP_BASE 0x00000400 +#define RKISP1_CIF_ISP_CTRL (RKISP1_CIF_ISP_BASE + 0x00000000) +#define RKISP1_CIF_ISP_ACQ_PROP (RKISP1_CIF_ISP_BASE + 0x00000004) +#define RKISP1_CIF_ISP_ACQ_H_OFFS (RKISP1_CIF_ISP_BASE + 0x00000008) +#define RKISP1_CIF_ISP_ACQ_V_OFFS (RKISP1_CIF_ISP_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_ACQ_H_SIZE (RKISP1_CIF_ISP_BASE + 0x00000010) +#define RKISP1_CIF_ISP_ACQ_V_SIZE (RKISP1_CIF_ISP_BASE + 0x00000014) +#define RKISP1_CIF_ISP_ACQ_NR_FRAMES (RKISP1_CIF_ISP_BASE + 0x00000018) +#define RKISP1_CIF_ISP_GAMMA_DX_LO (RKISP1_CIF_ISP_BASE + 0x0000001C) +#define RKISP1_CIF_ISP_GAMMA_DX_HI (RKISP1_CIF_ISP_BASE + 0x00000020) +#define RKISP1_CIF_ISP_GAMMA_R_Y0 (RKISP1_CIF_ISP_BASE + 0x00000024) +#define RKISP1_CIF_ISP_GAMMA_R_Y1 (RKISP1_CIF_ISP_BASE + 0x00000028) +#define RKISP1_CIF_ISP_GAMMA_R_Y2 (RKISP1_CIF_ISP_BASE + 0x0000002C) +#define RKISP1_CIF_ISP_GAMMA_R_Y3 (RKISP1_CIF_ISP_BASE + 0x00000030) +#define RKISP1_CIF_ISP_GAMMA_R_Y4 (RKISP1_CIF_ISP_BASE + 0x00000034) +#define RKISP1_CIF_ISP_GAMMA_R_Y5 (RKISP1_CIF_ISP_BASE + 0x00000038) +#define RKISP1_CIF_ISP_GAMMA_R_Y6 (RKISP1_CIF_ISP_BASE + 0x0000003C) +#define RKISP1_CIF_ISP_GAMMA_R_Y7 (RKISP1_CIF_ISP_BASE + 0x00000040) +#define RKISP1_CIF_ISP_GAMMA_R_Y8 (RKISP1_CIF_ISP_BASE + 0x00000044) +#define RKISP1_CIF_ISP_GAMMA_R_Y9 (RKISP1_CIF_ISP_BASE + 0x00000048) +#define RKISP1_CIF_ISP_GAMMA_R_Y10 (RKISP1_CIF_ISP_BASE + 0x0000004C) +#define RKISP1_CIF_ISP_GAMMA_R_Y11 (RKISP1_CIF_ISP_BASE + 0x00000050) +#define RKISP1_CIF_ISP_GAMMA_R_Y12 (RKISP1_CIF_ISP_BASE + 0x00000054) +#define RKISP1_CIF_ISP_GAMMA_R_Y13 (RKISP1_CIF_ISP_BASE + 0x00000058) +#define RKISP1_CIF_ISP_GAMMA_R_Y14 (RKISP1_CIF_ISP_BASE + 0x0000005C) +#define RKISP1_CIF_ISP_GAMMA_R_Y15 (RKISP1_CIF_ISP_BASE + 0x00000060) +#define RKISP1_CIF_ISP_GAMMA_R_Y16 (RKISP1_CIF_ISP_BASE + 0x00000064) +#define RKISP1_CIF_ISP_GAMMA_G_Y0 (RKISP1_CIF_ISP_BASE + 0x00000068) +#define RKISP1_CIF_ISP_GAMMA_G_Y1 (RKISP1_CIF_ISP_BASE + 0x0000006C) +#define RKISP1_CIF_ISP_GAMMA_G_Y2 (RKISP1_CIF_ISP_BASE + 0x00000070) +#define RKISP1_CIF_ISP_GAMMA_G_Y3 (RKISP1_CIF_ISP_BASE + 0x00000074) +#define RKISP1_CIF_ISP_GAMMA_G_Y4 (RKISP1_CIF_ISP_BASE + 0x00000078) +#define RKISP1_CIF_ISP_GAMMA_G_Y5 (RKISP1_CIF_ISP_BASE + 0x0000007C) +#define RKISP1_CIF_ISP_GAMMA_G_Y6 (RKISP1_CIF_ISP_BASE + 0x00000080) +#define RKISP1_CIF_ISP_GAMMA_G_Y7 (RKISP1_CIF_ISP_BASE + 0x00000084) +#define RKISP1_CIF_ISP_GAMMA_G_Y8 (RKISP1_CIF_ISP_BASE + 0x00000088) +#define RKISP1_CIF_ISP_GAMMA_G_Y9 (RKISP1_CIF_ISP_BASE + 0x0000008C) +#define RKISP1_CIF_ISP_GAMMA_G_Y10 (RKISP1_CIF_ISP_BASE + 0x00000090) +#define RKISP1_CIF_ISP_GAMMA_G_Y11 (RKISP1_CIF_ISP_BASE + 0x00000094) +#define RKISP1_CIF_ISP_GAMMA_G_Y12 (RKISP1_CIF_ISP_BASE + 0x00000098) +#define RKISP1_CIF_ISP_GAMMA_G_Y13 (RKISP1_CIF_ISP_BASE + 0x0000009C) +#define RKISP1_CIF_ISP_GAMMA_G_Y14 (RKISP1_CIF_ISP_BASE + 0x000000A0) +#define RKISP1_CIF_ISP_GAMMA_G_Y15 (RKISP1_CIF_ISP_BASE + 0x000000A4) +#define RKISP1_CIF_ISP_GAMMA_G_Y16 (RKISP1_CIF_ISP_BASE + 0x000000A8) +#define RKISP1_CIF_ISP_GAMMA_B_Y0 (RKISP1_CIF_ISP_BASE + 0x000000AC) +#define RKISP1_CIF_ISP_GAMMA_B_Y1 (RKISP1_CIF_ISP_BASE + 0x000000B0) +#define RKISP1_CIF_ISP_GAMMA_B_Y2 (RKISP1_CIF_ISP_BASE + 0x000000B4) +#define RKISP1_CIF_ISP_GAMMA_B_Y3 (RKISP1_CIF_ISP_BASE + 0x000000B8) +#define RKISP1_CIF_ISP_GAMMA_B_Y4 (RKISP1_CIF_ISP_BASE + 0x000000BC) +#define RKISP1_CIF_ISP_GAMMA_B_Y5 (RKISP1_CIF_ISP_BASE + 0x000000C0) +#define RKISP1_CIF_ISP_GAMMA_B_Y6 (RKISP1_CIF_ISP_BASE + 0x000000C4) +#define RKISP1_CIF_ISP_GAMMA_B_Y7 (RKISP1_CIF_ISP_BASE + 0x000000C8) +#define RKISP1_CIF_ISP_GAMMA_B_Y8 (RKISP1_CIF_ISP_BASE + 0x000000CC) +#define RKISP1_CIF_ISP_GAMMA_B_Y9 (RKISP1_CIF_ISP_BASE + 0x000000D0) +#define RKISP1_CIF_ISP_GAMMA_B_Y10 (RKISP1_CIF_ISP_BASE + 0x000000D4) +#define RKISP1_CIF_ISP_GAMMA_B_Y11 (RKISP1_CIF_ISP_BASE + 0x000000D8) +#define RKISP1_CIF_ISP_GAMMA_B_Y12 (RKISP1_CIF_ISP_BASE + 0x000000DC) +#define RKISP1_CIF_ISP_GAMMA_B_Y13 (RKISP1_CIF_ISP_BASE + 0x000000E0) +#define RKISP1_CIF_ISP_GAMMA_B_Y14 (RKISP1_CIF_ISP_BASE + 0x000000E4) +#define RKISP1_CIF_ISP_GAMMA_B_Y15 (RKISP1_CIF_ISP_BASE + 0x000000E8) +#define RKISP1_CIF_ISP_GAMMA_B_Y16 (RKISP1_CIF_ISP_BASE + 0x000000EC) +#define RKISP1_CIF_ISP_AWB_PROP_V10 (RKISP1_CIF_ISP_BASE + 0x00000110) +#define RKISP1_CIF_ISP_AWB_WND_H_OFFS_V10 (RKISP1_CIF_ISP_BASE + 0x00000114) +#define RKISP1_CIF_ISP_AWB_WND_V_OFFS_V10 (RKISP1_CIF_ISP_BASE + 0x00000118) +#define RKISP1_CIF_ISP_AWB_WND_H_SIZE_V10 (RKISP1_CIF_ISP_BASE + 0x0000011C) +#define RKISP1_CIF_ISP_AWB_WND_V_SIZE_V10 (RKISP1_CIF_ISP_BASE + 0x00000120) +#define RKISP1_CIF_ISP_AWB_FRAMES_V10 (RKISP1_CIF_ISP_BASE + 0x00000124) +#define RKISP1_CIF_ISP_AWB_REF_V10 (RKISP1_CIF_ISP_BASE + 0x00000128) +#define RKISP1_CIF_ISP_AWB_THRESH_V10 (RKISP1_CIF_ISP_BASE + 0x0000012C) +#define RKISP1_CIF_ISP_AWB_GAIN_G_V10 (RKISP1_CIF_ISP_BASE + 0x00000138) +#define RKISP1_CIF_ISP_AWB_GAIN_RB_V10 (RKISP1_CIF_ISP_BASE + 0x0000013C) +#define RKISP1_CIF_ISP_AWB_WHITE_CNT_V10 (RKISP1_CIF_ISP_BASE + 0x00000140) +#define RKISP1_CIF_ISP_AWB_MEAN_V10 (RKISP1_CIF_ISP_BASE + 0x00000144) +#define RKISP1_CIF_ISP_AWB_PROP_V12 (RKISP1_CIF_ISP_BASE + 0x00000110) +#define RKISP1_CIF_ISP_AWB_SIZE_V12 (RKISP1_CIF_ISP_BASE + 0x00000114) +#define RKISP1_CIF_ISP_AWB_OFFS_V12 (RKISP1_CIF_ISP_BASE + 0x00000118) +#define RKISP1_CIF_ISP_AWB_REF_V12 (RKISP1_CIF_ISP_BASE + 0x0000011C) +#define RKISP1_CIF_ISP_AWB_THRESH_V12 (RKISP1_CIF_ISP_BASE + 0x00000120) +#define RKISP1_CIF_ISP_X_COOR12_V12 (RKISP1_CIF_ISP_BASE + 0x00000124) +#define RKISP1_CIF_ISP_X_COOR34_V12 (RKISP1_CIF_ISP_BASE + 0x00000128) +#define RKISP1_CIF_ISP_AWB_WHITE_CNT_V12 (RKISP1_CIF_ISP_BASE + 0x0000012C) +#define RKISP1_CIF_ISP_AWB_MEAN_V12 (RKISP1_CIF_ISP_BASE + 0x00000130) +#define RKISP1_CIF_ISP_DEGAIN_V12 (RKISP1_CIF_ISP_BASE + 0x00000134) +#define RKISP1_CIF_ISP_AWB_GAIN_G_V12 (RKISP1_CIF_ISP_BASE + 0x00000138) +#define RKISP1_CIF_ISP_AWB_GAIN_RB_V12 (RKISP1_CIF_ISP_BASE + 0x0000013C) +#define RKISP1_CIF_ISP_REGION_LINE_V12 (RKISP1_CIF_ISP_BASE + 0x00000140) +#define RKISP1_CIF_ISP_WP_CNT_REGION0_V12 (RKISP1_CIF_ISP_BASE + 0x00000160) +#define RKISP1_CIF_ISP_WP_CNT_REGION1_V12 (RKISP1_CIF_ISP_BASE + 0x00000164) +#define RKISP1_CIF_ISP_WP_CNT_REGION2_V12 (RKISP1_CIF_ISP_BASE + 0x00000168) +#define RKISP1_CIF_ISP_WP_CNT_REGION3_V12 (RKISP1_CIF_ISP_BASE + 0x0000016C) +#define RKISP1_CIF_ISP_CC_COEFF_0 (RKISP1_CIF_ISP_BASE + 0x00000170) +#define RKISP1_CIF_ISP_CC_COEFF_1 (RKISP1_CIF_ISP_BASE + 0x00000174) +#define RKISP1_CIF_ISP_CC_COEFF_2 (RKISP1_CIF_ISP_BASE + 0x00000178) +#define RKISP1_CIF_ISP_CC_COEFF_3 (RKISP1_CIF_ISP_BASE + 0x0000017C) +#define RKISP1_CIF_ISP_CC_COEFF_4 (RKISP1_CIF_ISP_BASE + 0x00000180) +#define RKISP1_CIF_ISP_CC_COEFF_5 (RKISP1_CIF_ISP_BASE + 0x00000184) +#define RKISP1_CIF_ISP_CC_COEFF_6 (RKISP1_CIF_ISP_BASE + 0x00000188) +#define RKISP1_CIF_ISP_CC_COEFF_7 (RKISP1_CIF_ISP_BASE + 0x0000018C) +#define RKISP1_CIF_ISP_CC_COEFF_8 (RKISP1_CIF_ISP_BASE + 0x00000190) +#define RKISP1_CIF_ISP_OUT_H_OFFS (RKISP1_CIF_ISP_BASE + 0x00000194) +#define RKISP1_CIF_ISP_OUT_V_OFFS (RKISP1_CIF_ISP_BASE + 0x00000198) +#define RKISP1_CIF_ISP_OUT_H_SIZE (RKISP1_CIF_ISP_BASE + 0x0000019C) +#define RKISP1_CIF_ISP_OUT_V_SIZE (RKISP1_CIF_ISP_BASE + 0x000001A0) +#define RKISP1_CIF_ISP_DEMOSAIC (RKISP1_CIF_ISP_BASE + 0x000001A4) +#define RKISP1_CIF_ISP_FLAGS_SHD (RKISP1_CIF_ISP_BASE + 0x000001A8) +#define RKISP1_CIF_ISP_OUT_H_OFFS_SHD (RKISP1_CIF_ISP_BASE + 0x000001AC) +#define RKISP1_CIF_ISP_OUT_V_OFFS_SHD (RKISP1_CIF_ISP_BASE + 0x000001B0) +#define RKISP1_CIF_ISP_OUT_H_SIZE_SHD (RKISP1_CIF_ISP_BASE + 0x000001B4) +#define RKISP1_CIF_ISP_OUT_V_SIZE_SHD (RKISP1_CIF_ISP_BASE + 0x000001B8) +#define RKISP1_CIF_ISP_IMSC (RKISP1_CIF_ISP_BASE + 0x000001BC) +#define RKISP1_CIF_ISP_RIS (RKISP1_CIF_ISP_BASE + 0x000001C0) +#define RKISP1_CIF_ISP_MIS (RKISP1_CIF_ISP_BASE + 0x000001C4) +#define RKISP1_CIF_ISP_ICR (RKISP1_CIF_ISP_BASE + 0x000001C8) +#define RKISP1_CIF_ISP_ISR (RKISP1_CIF_ISP_BASE + 0x000001CC) +#define RKISP1_CIF_ISP_CT_COEFF_0 (RKISP1_CIF_ISP_BASE + 0x000001D0) +#define RKISP1_CIF_ISP_CT_COEFF_1 (RKISP1_CIF_ISP_BASE + 0x000001D4) +#define RKISP1_CIF_ISP_CT_COEFF_2 (RKISP1_CIF_ISP_BASE + 0x000001D8) +#define RKISP1_CIF_ISP_CT_COEFF_3 (RKISP1_CIF_ISP_BASE + 0x000001DC) +#define RKISP1_CIF_ISP_CT_COEFF_4 (RKISP1_CIF_ISP_BASE + 0x000001E0) +#define RKISP1_CIF_ISP_CT_COEFF_5 (RKISP1_CIF_ISP_BASE + 0x000001E4) +#define RKISP1_CIF_ISP_CT_COEFF_6 (RKISP1_CIF_ISP_BASE + 0x000001E8) +#define RKISP1_CIF_ISP_CT_COEFF_7 (RKISP1_CIF_ISP_BASE + 0x000001EC) +#define RKISP1_CIF_ISP_CT_COEFF_8 (RKISP1_CIF_ISP_BASE + 0x000001F0) +#define RKISP1_CIF_ISP_GAMMA_OUT_MODE_V10 (RKISP1_CIF_ISP_BASE + 0x000001F4) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_0_V10 (RKISP1_CIF_ISP_BASE + 0x000001F8) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_1_V10 (RKISP1_CIF_ISP_BASE + 0x000001FC) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_2_V10 (RKISP1_CIF_ISP_BASE + 0x00000200) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_3_V10 (RKISP1_CIF_ISP_BASE + 0x00000204) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_4_V10 (RKISP1_CIF_ISP_BASE + 0x00000208) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_5_V10 (RKISP1_CIF_ISP_BASE + 0x0000020C) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_6_V10 (RKISP1_CIF_ISP_BASE + 0x00000210) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_7_V10 (RKISP1_CIF_ISP_BASE + 0x00000214) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_8_V10 (RKISP1_CIF_ISP_BASE + 0x00000218) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_9_V10 (RKISP1_CIF_ISP_BASE + 0x0000021C) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_10_V10 (RKISP1_CIF_ISP_BASE + 0x00000220) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_11_V10 (RKISP1_CIF_ISP_BASE + 0x00000224) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_12_V10 (RKISP1_CIF_ISP_BASE + 0x00000228) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_13_V10 (RKISP1_CIF_ISP_BASE + 0x0000022C) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_14_V10 (RKISP1_CIF_ISP_BASE + 0x00000230) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_15_V10 (RKISP1_CIF_ISP_BASE + 0x00000234) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_16_V10 (RKISP1_CIF_ISP_BASE + 0x00000238) +#define RKISP1_CIF_ISP_ERR (RKISP1_CIF_ISP_BASE + 0x0000023C) +#define RKISP1_CIF_ISP_ERR_CLR (RKISP1_CIF_ISP_BASE + 0x00000240) +#define RKISP1_CIF_ISP_FRAME_COUNT (RKISP1_CIF_ISP_BASE + 0x00000244) +#define RKISP1_CIF_ISP_CT_OFFSET_R (RKISP1_CIF_ISP_BASE + 0x00000248) +#define RKISP1_CIF_ISP_CT_OFFSET_G (RKISP1_CIF_ISP_BASE + 0x0000024C) +#define RKISP1_CIF_ISP_CT_OFFSET_B (RKISP1_CIF_ISP_BASE + 0x00000250) +#define RKISP1_CIF_ISP_GAMMA_OUT_MODE_V12 (RKISP1_CIF_ISP_BASE + 0x00000300) +#define RKISP1_CIF_ISP_GAMMA_OUT_Y_0_V12 (RKISP1_CIF_ISP_BASE + 0x00000304) + +#define RKISP1_CIF_ISP_FLASH_BASE 0x00000660 +#define RKISP1_CIF_ISP_FLASH_CMD (RKISP1_CIF_ISP_FLASH_BASE + 0x00000000) +#define RKISP1_CIF_ISP_FLASH_CONFIG (RKISP1_CIF_ISP_FLASH_BASE + 0x00000004) +#define RKISP1_CIF_ISP_FLASH_PREDIV (RKISP1_CIF_ISP_FLASH_BASE + 0x00000008) +#define RKISP1_CIF_ISP_FLASH_DELAY (RKISP1_CIF_ISP_FLASH_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_FLASH_TIME (RKISP1_CIF_ISP_FLASH_BASE + 0x00000010) +#define RKISP1_CIF_ISP_FLASH_MAXP (RKISP1_CIF_ISP_FLASH_BASE + 0x00000014) + +#define RKISP1_CIF_ISP_SH_BASE 0x00000680 +#define RKISP1_CIF_ISP_SH_CTRL (RKISP1_CIF_ISP_SH_BASE + 0x00000000) +#define RKISP1_CIF_ISP_SH_PREDIV (RKISP1_CIF_ISP_SH_BASE + 0x00000004) +#define RKISP1_CIF_ISP_SH_DELAY (RKISP1_CIF_ISP_SH_BASE + 0x00000008) +#define RKISP1_CIF_ISP_SH_TIME (RKISP1_CIF_ISP_SH_BASE + 0x0000000C) + +#define RKISP1_CIF_C_PROC_BASE 0x00000800 +#define RKISP1_CIF_C_PROC_CTRL (RKISP1_CIF_C_PROC_BASE + 0x00000000) +#define RKISP1_CIF_C_PROC_CONTRAST (RKISP1_CIF_C_PROC_BASE + 0x00000004) +#define RKISP1_CIF_C_PROC_BRIGHTNESS (RKISP1_CIF_C_PROC_BASE + 0x00000008) +#define RKISP1_CIF_C_PROC_SATURATION (RKISP1_CIF_C_PROC_BASE + 0x0000000C) +#define RKISP1_CIF_C_PROC_HUE (RKISP1_CIF_C_PROC_BASE + 0x00000010) + +#define RKISP1_CIF_DUAL_CROP_BASE 0x00000880 +#define RKISP1_CIF_DUAL_CROP_CTRL (RKISP1_CIF_DUAL_CROP_BASE + 0x00000000) +#define RKISP1_CIF_DUAL_CROP_M_H_OFFS (RKISP1_CIF_DUAL_CROP_BASE + 0x00000004) +#define RKISP1_CIF_DUAL_CROP_M_V_OFFS (RKISP1_CIF_DUAL_CROP_BASE + 0x00000008) +#define RKISP1_CIF_DUAL_CROP_M_H_SIZE (RKISP1_CIF_DUAL_CROP_BASE + 0x0000000C) +#define RKISP1_CIF_DUAL_CROP_M_V_SIZE (RKISP1_CIF_DUAL_CROP_BASE + 0x00000010) +#define RKISP1_CIF_DUAL_CROP_S_H_OFFS (RKISP1_CIF_DUAL_CROP_BASE + 0x00000014) +#define RKISP1_CIF_DUAL_CROP_S_V_OFFS (RKISP1_CIF_DUAL_CROP_BASE + 0x00000018) +#define RKISP1_CIF_DUAL_CROP_S_H_SIZE (RKISP1_CIF_DUAL_CROP_BASE + 0x0000001C) +#define RKISP1_CIF_DUAL_CROP_S_V_SIZE (RKISP1_CIF_DUAL_CROP_BASE + 0x00000020) +#define RKISP1_CIF_DUAL_CROP_M_H_OFFS_SHD (RKISP1_CIF_DUAL_CROP_BASE + 0x00000024) +#define RKISP1_CIF_DUAL_CROP_M_V_OFFS_SHD (RKISP1_CIF_DUAL_CROP_BASE + 0x00000028) +#define RKISP1_CIF_DUAL_CROP_M_H_SIZE_SHD (RKISP1_CIF_DUAL_CROP_BASE + 0x0000002C) +#define RKISP1_CIF_DUAL_CROP_M_V_SIZE_SHD (RKISP1_CIF_DUAL_CROP_BASE + 0x00000030) +#define RKISP1_CIF_DUAL_CROP_S_H_OFFS_SHD (RKISP1_CIF_DUAL_CROP_BASE + 0x00000034) +#define RKISP1_CIF_DUAL_CROP_S_V_OFFS_SHD (RKISP1_CIF_DUAL_CROP_BASE + 0x00000038) +#define RKISP1_CIF_DUAL_CROP_S_H_SIZE_SHD (RKISP1_CIF_DUAL_CROP_BASE + 0x0000003C) +#define RKISP1_CIF_DUAL_CROP_S_V_SIZE_SHD (RKISP1_CIF_DUAL_CROP_BASE + 0x00000040) + +#define RKISP1_CIF_MRSZ_BASE 0x00000C00 +#define RKISP1_CIF_SRSZ_BASE 0x00001000 +#define RKISP1_CIF_RSZ_CTRL 0x0000 +#define RKISP1_CIF_RSZ_SCALE_HY 0x0004 +#define RKISP1_CIF_RSZ_SCALE_HCB 0x0008 +#define RKISP1_CIF_RSZ_SCALE_HCR 0x000C +#define RKISP1_CIF_RSZ_SCALE_VY 0x0010 +#define RKISP1_CIF_RSZ_SCALE_VC 0x0014 +#define RKISP1_CIF_RSZ_PHASE_HY 0x0018 +#define RKISP1_CIF_RSZ_PHASE_HC 0x001C +#define RKISP1_CIF_RSZ_PHASE_VY 0x0020 +#define RKISP1_CIF_RSZ_PHASE_VC 0x0024 +#define RKISP1_CIF_RSZ_SCALE_LUT_ADDR 0x0028 +#define RKISP1_CIF_RSZ_SCALE_LUT 0x002C +#define RKISP1_CIF_RSZ_CTRL_SHD 0x0030 +#define RKISP1_CIF_RSZ_SCALE_HY_SHD 0x0034 +#define RKISP1_CIF_RSZ_SCALE_HCB_SHD 0x0038 +#define RKISP1_CIF_RSZ_SCALE_HCR_SHD 0x003C +#define RKISP1_CIF_RSZ_SCALE_VY_SHD 0x0040 +#define RKISP1_CIF_RSZ_SCALE_VC_SHD 0x0044 +#define RKISP1_CIF_RSZ_PHASE_HY_SHD 0x0048 +#define RKISP1_CIF_RSZ_PHASE_HC_SHD 0x004C +#define RKISP1_CIF_RSZ_PHASE_VY_SHD 0x0050 +#define RKISP1_CIF_RSZ_PHASE_VC_SHD 0x0054 + +#define RKISP1_CIF_MI_BASE 0x00001400 +#define RKISP1_CIF_MI_CTRL (RKISP1_CIF_MI_BASE + 0x00000000) +#define RKISP1_CIF_MI_INIT (RKISP1_CIF_MI_BASE + 0x00000004) +#define RKISP1_CIF_MI_MP_Y_BASE_AD_INIT (RKISP1_CIF_MI_BASE + 0x00000008) +#define RKISP1_CIF_MI_MP_Y_SIZE_INIT (RKISP1_CIF_MI_BASE + 0x0000000C) +#define RKISP1_CIF_MI_MP_Y_OFFS_CNT_INIT (RKISP1_CIF_MI_BASE + 0x00000010) +#define RKISP1_CIF_MI_MP_Y_OFFS_CNT_START (RKISP1_CIF_MI_BASE + 0x00000014) +#define RKISP1_CIF_MI_MP_Y_IRQ_OFFS_INIT (RKISP1_CIF_MI_BASE + 0x00000018) +#define RKISP1_CIF_MI_MP_CB_BASE_AD_INIT (RKISP1_CIF_MI_BASE + 0x0000001C) +#define RKISP1_CIF_MI_MP_CB_SIZE_INIT (RKISP1_CIF_MI_BASE + 0x00000020) +#define RKISP1_CIF_MI_MP_CB_OFFS_CNT_INIT (RKISP1_CIF_MI_BASE + 0x00000024) +#define RKISP1_CIF_MI_MP_CB_OFFS_CNT_START (RKISP1_CIF_MI_BASE + 0x00000028) +#define RKISP1_CIF_MI_MP_CR_BASE_AD_INIT (RKISP1_CIF_MI_BASE + 0x0000002C) +#define RKISP1_CIF_MI_MP_CR_SIZE_INIT (RKISP1_CIF_MI_BASE + 0x00000030) +#define RKISP1_CIF_MI_MP_CR_OFFS_CNT_INIT (RKISP1_CIF_MI_BASE + 0x00000034) +#define RKISP1_CIF_MI_MP_CR_OFFS_CNT_START (RKISP1_CIF_MI_BASE + 0x00000038) +#define RKISP1_CIF_MI_SP_Y_BASE_AD_INIT (RKISP1_CIF_MI_BASE + 0x0000003C) +#define RKISP1_CIF_MI_SP_Y_SIZE_INIT (RKISP1_CIF_MI_BASE + 0x00000040) +#define RKISP1_CIF_MI_SP_Y_OFFS_CNT_INIT (RKISP1_CIF_MI_BASE + 0x00000044) +#define RKISP1_CIF_MI_SP_Y_OFFS_CNT_START (RKISP1_CIF_MI_BASE + 0x00000048) +#define RKISP1_CIF_MI_SP_Y_LLENGTH (RKISP1_CIF_MI_BASE + 0x0000004C) +#define RKISP1_CIF_MI_SP_CB_BASE_AD_INIT (RKISP1_CIF_MI_BASE + 0x00000050) +#define RKISP1_CIF_MI_SP_CB_SIZE_INIT (RKISP1_CIF_MI_BASE + 0x00000054) +#define RKISP1_CIF_MI_SP_CB_OFFS_CNT_INIT (RKISP1_CIF_MI_BASE + 0x00000058) +#define RKISP1_CIF_MI_SP_CB_OFFS_CNT_START (RKISP1_CIF_MI_BASE + 0x0000005C) +#define RKISP1_CIF_MI_SP_CR_BASE_AD_INIT (RKISP1_CIF_MI_BASE + 0x00000060) +#define RKISP1_CIF_MI_SP_CR_SIZE_INIT (RKISP1_CIF_MI_BASE + 0x00000064) +#define RKISP1_CIF_MI_SP_CR_OFFS_CNT_INIT (RKISP1_CIF_MI_BASE + 0x00000068) +#define RKISP1_CIF_MI_SP_CR_OFFS_CNT_START (RKISP1_CIF_MI_BASE + 0x0000006C) +#define RKISP1_CIF_MI_BYTE_CNT (RKISP1_CIF_MI_BASE + 0x00000070) +#define RKISP1_CIF_MI_CTRL_SHD (RKISP1_CIF_MI_BASE + 0x00000074) +#define RKISP1_CIF_MI_MP_Y_BASE_AD_SHD (RKISP1_CIF_MI_BASE + 0x00000078) +#define RKISP1_CIF_MI_MP_Y_SIZE_SHD (RKISP1_CIF_MI_BASE + 0x0000007C) +#define RKISP1_CIF_MI_MP_Y_OFFS_CNT_SHD (RKISP1_CIF_MI_BASE + 0x00000080) +#define RKISP1_CIF_MI_MP_Y_IRQ_OFFS_SHD (RKISP1_CIF_MI_BASE + 0x00000084) +#define RKISP1_CIF_MI_MP_CB_BASE_AD_SHD (RKISP1_CIF_MI_BASE + 0x00000088) +#define RKISP1_CIF_MI_MP_CB_SIZE_SHD (RKISP1_CIF_MI_BASE + 0x0000008C) +#define RKISP1_CIF_MI_MP_CB_OFFS_CNT_SHD (RKISP1_CIF_MI_BASE + 0x00000090) +#define RKISP1_CIF_MI_MP_CR_BASE_AD_SHD (RKISP1_CIF_MI_BASE + 0x00000094) +#define RKISP1_CIF_MI_MP_CR_SIZE_SHD (RKISP1_CIF_MI_BASE + 0x00000098) +#define RKISP1_CIF_MI_MP_CR_OFFS_CNT_SHD (RKISP1_CIF_MI_BASE + 0x0000009C) +#define RKISP1_CIF_MI_SP_Y_BASE_AD_SHD (RKISP1_CIF_MI_BASE + 0x000000A0) +#define RKISP1_CIF_MI_SP_Y_SIZE_SHD (RKISP1_CIF_MI_BASE + 0x000000A4) +#define RKISP1_CIF_MI_SP_Y_OFFS_CNT_SHD (RKISP1_CIF_MI_BASE + 0x000000A8) +#define RKISP1_CIF_MI_SP_CB_BASE_AD_SHD (RKISP1_CIF_MI_BASE + 0x000000B0) +#define RKISP1_CIF_MI_SP_CB_SIZE_SHD (RKISP1_CIF_MI_BASE + 0x000000B4) +#define RKISP1_CIF_MI_SP_CB_OFFS_CNT_SHD (RKISP1_CIF_MI_BASE + 0x000000B8) +#define RKISP1_CIF_MI_SP_CR_BASE_AD_SHD (RKISP1_CIF_MI_BASE + 0x000000BC) +#define RKISP1_CIF_MI_SP_CR_SIZE_SHD (RKISP1_CIF_MI_BASE + 0x000000C0) +#define RKISP1_CIF_MI_SP_CR_OFFS_CNT_SHD (RKISP1_CIF_MI_BASE + 0x000000C4) +#define RKISP1_CIF_MI_DMA_Y_PIC_START_AD (RKISP1_CIF_MI_BASE + 0x000000C8) +#define RKISP1_CIF_MI_DMA_Y_PIC_WIDTH (RKISP1_CIF_MI_BASE + 0x000000CC) +#define RKISP1_CIF_MI_DMA_Y_LLENGTH (RKISP1_CIF_MI_BASE + 0x000000D0) +#define RKISP1_CIF_MI_DMA_Y_PIC_SIZE (RKISP1_CIF_MI_BASE + 0x000000D4) +#define RKISP1_CIF_MI_DMA_CB_PIC_START_AD (RKISP1_CIF_MI_BASE + 0x000000D8) +#define RKISP1_CIF_MI_DMA_CR_PIC_START_AD (RKISP1_CIF_MI_BASE + 0x000000E8) +#define RKISP1_CIF_MI_IMSC (RKISP1_CIF_MI_BASE + 0x000000F8) +#define RKISP1_CIF_MI_RIS (RKISP1_CIF_MI_BASE + 0x000000FC) +#define RKISP1_CIF_MI_MIS (RKISP1_CIF_MI_BASE + 0x00000100) +#define RKISP1_CIF_MI_ICR (RKISP1_CIF_MI_BASE + 0x00000104) +#define RKISP1_CIF_MI_ISR (RKISP1_CIF_MI_BASE + 0x00000108) +#define RKISP1_CIF_MI_STATUS (RKISP1_CIF_MI_BASE + 0x0000010C) +#define RKISP1_CIF_MI_STATUS_CLR (RKISP1_CIF_MI_BASE + 0x00000110) +#define RKISP1_CIF_MI_SP_Y_PIC_WIDTH (RKISP1_CIF_MI_BASE + 0x00000114) +#define RKISP1_CIF_MI_SP_Y_PIC_HEIGHT (RKISP1_CIF_MI_BASE + 0x00000118) +#define RKISP1_CIF_MI_SP_Y_PIC_SIZE (RKISP1_CIF_MI_BASE + 0x0000011C) +#define RKISP1_CIF_MI_DMA_CTRL (RKISP1_CIF_MI_BASE + 0x00000120) +#define RKISP1_CIF_MI_DMA_START (RKISP1_CIF_MI_BASE + 0x00000124) +#define RKISP1_CIF_MI_DMA_STATUS (RKISP1_CIF_MI_BASE + 0x00000128) +#define RKISP1_CIF_MI_PIXEL_COUNT (RKISP1_CIF_MI_BASE + 0x0000012C) +#define RKISP1_CIF_MI_MP_Y_BASE_AD_INIT2 (RKISP1_CIF_MI_BASE + 0x00000130) +#define RKISP1_CIF_MI_MP_CB_BASE_AD_INIT2 (RKISP1_CIF_MI_BASE + 0x00000134) +#define RKISP1_CIF_MI_MP_CR_BASE_AD_INIT2 (RKISP1_CIF_MI_BASE + 0x00000138) +#define RKISP1_CIF_MI_SP_Y_BASE_AD_INIT2 (RKISP1_CIF_MI_BASE + 0x0000013C) +#define RKISP1_CIF_MI_SP_CB_BASE_AD_INIT2 (RKISP1_CIF_MI_BASE + 0x00000140) +#define RKISP1_CIF_MI_SP_CR_BASE_AD_INIT2 (RKISP1_CIF_MI_BASE + 0x00000144) +#define RKISP1_CIF_MI_XTD_FORMAT_CTRL (RKISP1_CIF_MI_BASE + 0x00000148) + +#define RKISP1_CIF_SMIA_BASE 0x00001A00 +#define RKISP1_CIF_SMIA_CTRL (RKISP1_CIF_SMIA_BASE + 0x00000000) +#define RKISP1_CIF_SMIA_STATUS (RKISP1_CIF_SMIA_BASE + 0x00000004) +#define RKISP1_CIF_SMIA_IMSC (RKISP1_CIF_SMIA_BASE + 0x00000008) +#define RKISP1_CIF_SMIA_RIS (RKISP1_CIF_SMIA_BASE + 0x0000000C) +#define RKISP1_CIF_SMIA_MIS (RKISP1_CIF_SMIA_BASE + 0x00000010) +#define RKISP1_CIF_SMIA_ICR (RKISP1_CIF_SMIA_BASE + 0x00000014) +#define RKISP1_CIF_SMIA_ISR (RKISP1_CIF_SMIA_BASE + 0x00000018) +#define RKISP1_CIF_SMIA_DATA_FORMAT_SEL (RKISP1_CIF_SMIA_BASE + 0x0000001C) +#define RKISP1_CIF_SMIA_SOF_EMB_DATA_LINES (RKISP1_CIF_SMIA_BASE + 0x00000020) +#define RKISP1_CIF_SMIA_EMB_HSTART (RKISP1_CIF_SMIA_BASE + 0x00000024) +#define RKISP1_CIF_SMIA_EMB_HSIZE (RKISP1_CIF_SMIA_BASE + 0x00000028) +#define RKISP1_CIF_SMIA_EMB_VSTART (RKISP1_CIF_SMIA_BASE + 0x0000002c) +#define RKISP1_CIF_SMIA_NUM_LINES (RKISP1_CIF_SMIA_BASE + 0x00000030) +#define RKISP1_CIF_SMIA_EMB_DATA_FIFO (RKISP1_CIF_SMIA_BASE + 0x00000034) +#define RKISP1_CIF_SMIA_EMB_DATA_WATERMARK (RKISP1_CIF_SMIA_BASE + 0x00000038) + +#define RKISP1_CIF_MIPI_BASE 0x00001C00 +#define RKISP1_CIF_MIPI_CTRL (RKISP1_CIF_MIPI_BASE + 0x00000000) +#define RKISP1_CIF_MIPI_STATUS (RKISP1_CIF_MIPI_BASE + 0x00000004) +#define RKISP1_CIF_MIPI_IMSC (RKISP1_CIF_MIPI_BASE + 0x00000008) +#define RKISP1_CIF_MIPI_RIS (RKISP1_CIF_MIPI_BASE + 0x0000000C) +#define RKISP1_CIF_MIPI_MIS (RKISP1_CIF_MIPI_BASE + 0x00000010) +#define RKISP1_CIF_MIPI_ICR (RKISP1_CIF_MIPI_BASE + 0x00000014) +#define RKISP1_CIF_MIPI_ISR (RKISP1_CIF_MIPI_BASE + 0x00000018) +#define RKISP1_CIF_MIPI_CUR_DATA_ID (RKISP1_CIF_MIPI_BASE + 0x0000001C) +#define RKISP1_CIF_MIPI_IMG_DATA_SEL (RKISP1_CIF_MIPI_BASE + 0x00000020) +#define RKISP1_CIF_MIPI_ADD_DATA_SEL_1 (RKISP1_CIF_MIPI_BASE + 0x00000024) +#define RKISP1_CIF_MIPI_ADD_DATA_SEL_2 (RKISP1_CIF_MIPI_BASE + 0x00000028) +#define RKISP1_CIF_MIPI_ADD_DATA_SEL_3 (RKISP1_CIF_MIPI_BASE + 0x0000002C) +#define RKISP1_CIF_MIPI_ADD_DATA_SEL_4 (RKISP1_CIF_MIPI_BASE + 0x00000030) +#define RKISP1_CIF_MIPI_ADD_DATA_FIFO (RKISP1_CIF_MIPI_BASE + 0x00000034) +#define RKISP1_CIF_MIPI_FIFO_FILL_LEVEL (RKISP1_CIF_MIPI_BASE + 0x00000038) +#define RKISP1_CIF_MIPI_COMPRESSED_MODE (RKISP1_CIF_MIPI_BASE + 0x0000003C) +#define RKISP1_CIF_MIPI_FRAME (RKISP1_CIF_MIPI_BASE + 0x00000040) +#define RKISP1_CIF_MIPI_GEN_SHORT_DT (RKISP1_CIF_MIPI_BASE + 0x00000044) +#define RKISP1_CIF_MIPI_GEN_SHORT_8_9 (RKISP1_CIF_MIPI_BASE + 0x00000048) +#define RKISP1_CIF_MIPI_GEN_SHORT_A_B (RKISP1_CIF_MIPI_BASE + 0x0000004C) +#define RKISP1_CIF_MIPI_GEN_SHORT_C_D (RKISP1_CIF_MIPI_BASE + 0x00000050) +#define RKISP1_CIF_MIPI_GEN_SHORT_E_F (RKISP1_CIF_MIPI_BASE + 0x00000054) + +#define RKISP1_CIF_ISP_AFM_BASE 0x00002000 +#define RKISP1_CIF_ISP_AFM_CTRL (RKISP1_CIF_ISP_AFM_BASE + 0x00000000) +#define RKISP1_CIF_ISP_AFM_LT_A (RKISP1_CIF_ISP_AFM_BASE + 0x00000004) +#define RKISP1_CIF_ISP_AFM_RB_A (RKISP1_CIF_ISP_AFM_BASE + 0x00000008) +#define RKISP1_CIF_ISP_AFM_LT_B (RKISP1_CIF_ISP_AFM_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_AFM_RB_B (RKISP1_CIF_ISP_AFM_BASE + 0x00000010) +#define RKISP1_CIF_ISP_AFM_LT_C (RKISP1_CIF_ISP_AFM_BASE + 0x00000014) +#define RKISP1_CIF_ISP_AFM_RB_C (RKISP1_CIF_ISP_AFM_BASE + 0x00000018) +#define RKISP1_CIF_ISP_AFM_THRES (RKISP1_CIF_ISP_AFM_BASE + 0x0000001C) +#define RKISP1_CIF_ISP_AFM_VAR_SHIFT (RKISP1_CIF_ISP_AFM_BASE + 0x00000020) +#define RKISP1_CIF_ISP_AFM_SUM_A (RKISP1_CIF_ISP_AFM_BASE + 0x00000024) +#define RKISP1_CIF_ISP_AFM_SUM_B (RKISP1_CIF_ISP_AFM_BASE + 0x00000028) +#define RKISP1_CIF_ISP_AFM_SUM_C (RKISP1_CIF_ISP_AFM_BASE + 0x0000002C) +#define RKISP1_CIF_ISP_AFM_LUM_A (RKISP1_CIF_ISP_AFM_BASE + 0x00000030) +#define RKISP1_CIF_ISP_AFM_LUM_B (RKISP1_CIF_ISP_AFM_BASE + 0x00000034) +#define RKISP1_CIF_ISP_AFM_LUM_C (RKISP1_CIF_ISP_AFM_BASE + 0x00000038) + +#define RKISP1_CIF_ISP_LSC_BASE 0x00002200 +#define RKISP1_CIF_ISP_LSC_CTRL (RKISP1_CIF_ISP_LSC_BASE + 0x00000000) +#define RKISP1_CIF_ISP_LSC_R_TABLE_ADDR (RKISP1_CIF_ISP_LSC_BASE + 0x00000004) +#define RKISP1_CIF_ISP_LSC_GR_TABLE_ADDR (RKISP1_CIF_ISP_LSC_BASE + 0x00000008) +#define RKISP1_CIF_ISP_LSC_B_TABLE_ADDR (RKISP1_CIF_ISP_LSC_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_LSC_GB_TABLE_ADDR (RKISP1_CIF_ISP_LSC_BASE + 0x00000010) +#define RKISP1_CIF_ISP_LSC_R_TABLE_DATA (RKISP1_CIF_ISP_LSC_BASE + 0x00000014) +#define RKISP1_CIF_ISP_LSC_GR_TABLE_DATA (RKISP1_CIF_ISP_LSC_BASE + 0x00000018) +#define RKISP1_CIF_ISP_LSC_B_TABLE_DATA (RKISP1_CIF_ISP_LSC_BASE + 0x0000001C) +#define RKISP1_CIF_ISP_LSC_GB_TABLE_DATA (RKISP1_CIF_ISP_LSC_BASE + 0x00000020) +#define RKISP1_CIF_ISP_LSC_XGRAD(n) (RKISP1_CIF_ISP_LSC_BASE + 0x00000024 + (n) * 4) +#define RKISP1_CIF_ISP_LSC_YGRAD(n) (RKISP1_CIF_ISP_LSC_BASE + 0x00000034 + (n) * 4) +#define RKISP1_CIF_ISP_LSC_XSIZE(n) (RKISP1_CIF_ISP_LSC_BASE + 0x00000044 + (n) * 4) +#define RKISP1_CIF_ISP_LSC_YSIZE(n) (RKISP1_CIF_ISP_LSC_BASE + 0x00000054 + (n) * 4) +#define RKISP1_CIF_ISP_LSC_TABLE_SEL (RKISP1_CIF_ISP_LSC_BASE + 0x00000064) +#define RKISP1_CIF_ISP_LSC_STATUS (RKISP1_CIF_ISP_LSC_BASE + 0x00000068) + +#define RKISP1_CIF_ISP_IS_BASE 0x00002300 +#define RKISP1_CIF_ISP_IS_CTRL (RKISP1_CIF_ISP_IS_BASE + 0x00000000) +#define RKISP1_CIF_ISP_IS_RECENTER (RKISP1_CIF_ISP_IS_BASE + 0x00000004) +#define RKISP1_CIF_ISP_IS_H_OFFS (RKISP1_CIF_ISP_IS_BASE + 0x00000008) +#define RKISP1_CIF_ISP_IS_V_OFFS (RKISP1_CIF_ISP_IS_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_IS_H_SIZE (RKISP1_CIF_ISP_IS_BASE + 0x00000010) +#define RKISP1_CIF_ISP_IS_V_SIZE (RKISP1_CIF_ISP_IS_BASE + 0x00000014) +#define RKISP1_CIF_ISP_IS_MAX_DX (RKISP1_CIF_ISP_IS_BASE + 0x00000018) +#define RKISP1_CIF_ISP_IS_MAX_DY (RKISP1_CIF_ISP_IS_BASE + 0x0000001C) +#define RKISP1_CIF_ISP_IS_DISPLACE (RKISP1_CIF_ISP_IS_BASE + 0x00000020) +#define RKISP1_CIF_ISP_IS_H_OFFS_SHD (RKISP1_CIF_ISP_IS_BASE + 0x00000024) +#define RKISP1_CIF_ISP_IS_V_OFFS_SHD (RKISP1_CIF_ISP_IS_BASE + 0x00000028) +#define RKISP1_CIF_ISP_IS_H_SIZE_SHD (RKISP1_CIF_ISP_IS_BASE + 0x0000002C) +#define RKISP1_CIF_ISP_IS_V_SIZE_SHD (RKISP1_CIF_ISP_IS_BASE + 0x00000030) + +#define RKISP1_CIF_ISP_HIST_BASE_V10 0x00002400 +#define RKISP1_CIF_ISP_HIST_PROP_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000000) +#define RKISP1_CIF_ISP_HIST_H_OFFS_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000004) +#define RKISP1_CIF_ISP_HIST_V_OFFS_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000008) +#define RKISP1_CIF_ISP_HIST_H_SIZE_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x0000000C) +#define RKISP1_CIF_ISP_HIST_V_SIZE_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000010) +#define RKISP1_CIF_ISP_HIST_BIN_0_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000014) +#define RKISP1_CIF_ISP_HIST_BIN_1_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000018) +#define RKISP1_CIF_ISP_HIST_BIN_2_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x0000001C) +#define RKISP1_CIF_ISP_HIST_BIN_3_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000020) +#define RKISP1_CIF_ISP_HIST_BIN_4_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000024) +#define RKISP1_CIF_ISP_HIST_BIN_5_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000028) +#define RKISP1_CIF_ISP_HIST_BIN_6_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x0000002C) +#define RKISP1_CIF_ISP_HIST_BIN_7_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000030) +#define RKISP1_CIF_ISP_HIST_BIN_8_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000034) +#define RKISP1_CIF_ISP_HIST_BIN_9_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000038) +#define RKISP1_CIF_ISP_HIST_BIN_10_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x0000003C) +#define RKISP1_CIF_ISP_HIST_BIN_11_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000040) +#define RKISP1_CIF_ISP_HIST_BIN_12_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000044) +#define RKISP1_CIF_ISP_HIST_BIN_13_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000048) +#define RKISP1_CIF_ISP_HIST_BIN_14_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x0000004C) +#define RKISP1_CIF_ISP_HIST_BIN_15_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000050) +#define RKISP1_CIF_ISP_HIST_WEIGHT_00TO30_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000054) +#define RKISP1_CIF_ISP_HIST_WEIGHT_40TO21_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000058) +#define RKISP1_CIF_ISP_HIST_WEIGHT_31TO12_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x0000005C) +#define RKISP1_CIF_ISP_HIST_WEIGHT_22TO03_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000060) +#define RKISP1_CIF_ISP_HIST_WEIGHT_13TO43_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000064) +#define RKISP1_CIF_ISP_HIST_WEIGHT_04TO34_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x00000068) +#define RKISP1_CIF_ISP_HIST_WEIGHT_44_V10 (RKISP1_CIF_ISP_HIST_BASE_V10 + 0x0000006C) + +#define RKISP1_CIF_ISP_FILT_BASE 0x00002500 +#define RKISP1_CIF_ISP_FILT_MODE (RKISP1_CIF_ISP_FILT_BASE + 0x00000000) +#define RKISP1_CIF_ISP_FILT_THRESH_BL0 (RKISP1_CIF_ISP_FILT_BASE + 0x00000028) +#define RKISP1_CIF_ISP_FILT_THRESH_BL1 (RKISP1_CIF_ISP_FILT_BASE + 0x0000002c) +#define RKISP1_CIF_ISP_FILT_THRESH_SH0 (RKISP1_CIF_ISP_FILT_BASE + 0x00000030) +#define RKISP1_CIF_ISP_FILT_THRESH_SH1 (RKISP1_CIF_ISP_FILT_BASE + 0x00000034) +#define RKISP1_CIF_ISP_FILT_LUM_WEIGHT (RKISP1_CIF_ISP_FILT_BASE + 0x00000038) +#define RKISP1_CIF_ISP_FILT_FAC_SH1 (RKISP1_CIF_ISP_FILT_BASE + 0x0000003c) +#define RKISP1_CIF_ISP_FILT_FAC_SH0 (RKISP1_CIF_ISP_FILT_BASE + 0x00000040) +#define RKISP1_CIF_ISP_FILT_FAC_MID (RKISP1_CIF_ISP_FILT_BASE + 0x00000044) +#define RKISP1_CIF_ISP_FILT_FAC_BL0 (RKISP1_CIF_ISP_FILT_BASE + 0x00000048) +#define RKISP1_CIF_ISP_FILT_FAC_BL1 (RKISP1_CIF_ISP_FILT_BASE + 0x0000004C) + +#define RKISP1_CIF_ISP_CAC_BASE 0x00002580 +#define RKISP1_CIF_ISP_CAC_CTRL (RKISP1_CIF_ISP_CAC_BASE + 0x00000000) +#define RKISP1_CIF_ISP_CAC_COUNT_START (RKISP1_CIF_ISP_CAC_BASE + 0x00000004) +#define RKISP1_CIF_ISP_CAC_A (RKISP1_CIF_ISP_CAC_BASE + 0x00000008) +#define RKISP1_CIF_ISP_CAC_B (RKISP1_CIF_ISP_CAC_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_CAC_C (RKISP1_CIF_ISP_CAC_BASE + 0x00000010) +#define RKISP1_CIF_ISP_X_NORM (RKISP1_CIF_ISP_CAC_BASE + 0x00000014) +#define RKISP1_CIF_ISP_Y_NORM (RKISP1_CIF_ISP_CAC_BASE + 0x00000018) + +#define RKISP1_CIF_ISP_EXP_BASE 0x00002600 +#define RKISP1_CIF_ISP_EXP_CTRL (RKISP1_CIF_ISP_EXP_BASE + 0x00000000) +#define RKISP1_CIF_ISP_EXP_H_OFFSET_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000004) +#define RKISP1_CIF_ISP_EXP_V_OFFSET_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000008) +#define RKISP1_CIF_ISP_EXP_H_SIZE_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_EXP_V_SIZE_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000010) +#define RKISP1_CIF_ISP_EXP_MEAN_00_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000014) +#define RKISP1_CIF_ISP_EXP_MEAN_10_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000018) +#define RKISP1_CIF_ISP_EXP_MEAN_20_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x0000001c) +#define RKISP1_CIF_ISP_EXP_MEAN_30_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000020) +#define RKISP1_CIF_ISP_EXP_MEAN_40_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000024) +#define RKISP1_CIF_ISP_EXP_MEAN_01_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000028) +#define RKISP1_CIF_ISP_EXP_MEAN_11_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x0000002c) +#define RKISP1_CIF_ISP_EXP_MEAN_21_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000030) +#define RKISP1_CIF_ISP_EXP_MEAN_31_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000034) +#define RKISP1_CIF_ISP_EXP_MEAN_41_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000038) +#define RKISP1_CIF_ISP_EXP_MEAN_02_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x0000003c) +#define RKISP1_CIF_ISP_EXP_MEAN_12_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000040) +#define RKISP1_CIF_ISP_EXP_MEAN_22_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000044) +#define RKISP1_CIF_ISP_EXP_MEAN_32_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000048) +#define RKISP1_CIF_ISP_EXP_MEAN_42_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x0000004c) +#define RKISP1_CIF_ISP_EXP_MEAN_03_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000050) +#define RKISP1_CIF_ISP_EXP_MEAN_13_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000054) +#define RKISP1_CIF_ISP_EXP_MEAN_23_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000058) +#define RKISP1_CIF_ISP_EXP_MEAN_33_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x0000005c) +#define RKISP1_CIF_ISP_EXP_MEAN_43_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000060) +#define RKISP1_CIF_ISP_EXP_MEAN_04_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000064) +#define RKISP1_CIF_ISP_EXP_MEAN_14_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000068) +#define RKISP1_CIF_ISP_EXP_MEAN_24_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x0000006c) +#define RKISP1_CIF_ISP_EXP_MEAN_34_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000070) +#define RKISP1_CIF_ISP_EXP_MEAN_44_V10 (RKISP1_CIF_ISP_EXP_BASE + 0x00000074) +#define RKISP1_CIF_ISP_EXP_SIZE_V12 (RKISP1_CIF_ISP_EXP_BASE + 0x00000004) +#define RKISP1_CIF_ISP_EXP_OFFS_V12 (RKISP1_CIF_ISP_EXP_BASE + 0x00000008) +#define RKISP1_CIF_ISP_EXP_MEAN_V12 (RKISP1_CIF_ISP_EXP_BASE + 0x0000000c) + +#define RKISP1_CIF_ISP_BLS_BASE 0x00002700 +#define RKISP1_CIF_ISP_BLS_CTRL (RKISP1_CIF_ISP_BLS_BASE + 0x00000000) +#define RKISP1_CIF_ISP_BLS_SAMPLES (RKISP1_CIF_ISP_BLS_BASE + 0x00000004) +#define RKISP1_CIF_ISP_BLS_H1_START (RKISP1_CIF_ISP_BLS_BASE + 0x00000008) +#define RKISP1_CIF_ISP_BLS_H1_STOP (RKISP1_CIF_ISP_BLS_BASE + 0x0000000c) +#define RKISP1_CIF_ISP_BLS_V1_START (RKISP1_CIF_ISP_BLS_BASE + 0x00000010) +#define RKISP1_CIF_ISP_BLS_V1_STOP (RKISP1_CIF_ISP_BLS_BASE + 0x00000014) +#define RKISP1_CIF_ISP_BLS_H2_START (RKISP1_CIF_ISP_BLS_BASE + 0x00000018) +#define RKISP1_CIF_ISP_BLS_H2_STOP (RKISP1_CIF_ISP_BLS_BASE + 0x0000001c) +#define RKISP1_CIF_ISP_BLS_V2_START (RKISP1_CIF_ISP_BLS_BASE + 0x00000020) +#define RKISP1_CIF_ISP_BLS_V2_STOP (RKISP1_CIF_ISP_BLS_BASE + 0x00000024) +#define RKISP1_CIF_ISP_BLS_A_FIXED (RKISP1_CIF_ISP_BLS_BASE + 0x00000028) +#define RKISP1_CIF_ISP_BLS_B_FIXED (RKISP1_CIF_ISP_BLS_BASE + 0x0000002c) +#define RKISP1_CIF_ISP_BLS_C_FIXED (RKISP1_CIF_ISP_BLS_BASE + 0x00000030) +#define RKISP1_CIF_ISP_BLS_D_FIXED (RKISP1_CIF_ISP_BLS_BASE + 0x00000034) +#define RKISP1_CIF_ISP_BLS_A_MEASURED (RKISP1_CIF_ISP_BLS_BASE + 0x00000038) +#define RKISP1_CIF_ISP_BLS_B_MEASURED (RKISP1_CIF_ISP_BLS_BASE + 0x0000003c) +#define RKISP1_CIF_ISP_BLS_C_MEASURED (RKISP1_CIF_ISP_BLS_BASE + 0x00000040) +#define RKISP1_CIF_ISP_BLS_D_MEASURED (RKISP1_CIF_ISP_BLS_BASE + 0x00000044) + +#define RKISP1_CIF_ISP_DPF_BASE 0x00002800 +#define RKISP1_CIF_ISP_DPF_MODE (RKISP1_CIF_ISP_DPF_BASE + 0x00000000) +#define RKISP1_CIF_ISP_DPF_STRENGTH_R (RKISP1_CIF_ISP_DPF_BASE + 0x00000004) +#define RKISP1_CIF_ISP_DPF_STRENGTH_G (RKISP1_CIF_ISP_DPF_BASE + 0x00000008) +#define RKISP1_CIF_ISP_DPF_STRENGTH_B (RKISP1_CIF_ISP_DPF_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_DPF_S_WEIGHT_G_1_4 (RKISP1_CIF_ISP_DPF_BASE + 0x00000010) +#define RKISP1_CIF_ISP_DPF_S_WEIGHT_G_5_6 (RKISP1_CIF_ISP_DPF_BASE + 0x00000014) +#define RKISP1_CIF_ISP_DPF_S_WEIGHT_RB_1_4 (RKISP1_CIF_ISP_DPF_BASE + 0x00000018) +#define RKISP1_CIF_ISP_DPF_S_WEIGHT_RB_5_6 (RKISP1_CIF_ISP_DPF_BASE + 0x0000001C) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_0 (RKISP1_CIF_ISP_DPF_BASE + 0x00000020) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_1 (RKISP1_CIF_ISP_DPF_BASE + 0x00000024) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_2 (RKISP1_CIF_ISP_DPF_BASE + 0x00000028) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_3 (RKISP1_CIF_ISP_DPF_BASE + 0x0000002C) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_4 (RKISP1_CIF_ISP_DPF_BASE + 0x00000030) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_5 (RKISP1_CIF_ISP_DPF_BASE + 0x00000034) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_6 (RKISP1_CIF_ISP_DPF_BASE + 0x00000038) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_7 (RKISP1_CIF_ISP_DPF_BASE + 0x0000003C) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_8 (RKISP1_CIF_ISP_DPF_BASE + 0x00000040) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_9 (RKISP1_CIF_ISP_DPF_BASE + 0x00000044) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_10 (RKISP1_CIF_ISP_DPF_BASE + 0x00000048) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_11 (RKISP1_CIF_ISP_DPF_BASE + 0x0000004C) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_12 (RKISP1_CIF_ISP_DPF_BASE + 0x00000050) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_13 (RKISP1_CIF_ISP_DPF_BASE + 0x00000054) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_14 (RKISP1_CIF_ISP_DPF_BASE + 0x00000058) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_15 (RKISP1_CIF_ISP_DPF_BASE + 0x0000005C) +#define RKISP1_CIF_ISP_DPF_NULL_COEFF_16 (RKISP1_CIF_ISP_DPF_BASE + 0x00000060) +#define RKISP1_CIF_ISP_DPF_NF_GAIN_R (RKISP1_CIF_ISP_DPF_BASE + 0x00000064) +#define RKISP1_CIF_ISP_DPF_NF_GAIN_GR (RKISP1_CIF_ISP_DPF_BASE + 0x00000068) +#define RKISP1_CIF_ISP_DPF_NF_GAIN_GB (RKISP1_CIF_ISP_DPF_BASE + 0x0000006C) +#define RKISP1_CIF_ISP_DPF_NF_GAIN_B (RKISP1_CIF_ISP_DPF_BASE + 0x00000070) + +#define RKISP1_CIF_ISP_DPCC_BASE 0x00002900 +#define RKISP1_CIF_ISP_DPCC_MODE (RKISP1_CIF_ISP_DPCC_BASE + 0x00000000) +#define RKISP1_CIF_ISP_DPCC_OUTPUT_MODE (RKISP1_CIF_ISP_DPCC_BASE + 0x00000004) +#define RKISP1_CIF_ISP_DPCC_SET_USE (RKISP1_CIF_ISP_DPCC_BASE + 0x00000008) +#define RKISP1_CIF_ISP_DPCC_METHODS_SET_1 (RKISP1_CIF_ISP_DPCC_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_DPCC_METHODS_SET_2 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000010) +#define RKISP1_CIF_ISP_DPCC_METHODS_SET_3 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000014) +#define RKISP1_CIF_ISP_DPCC_LINE_THRESH_1 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000018) +#define RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_1 (RKISP1_CIF_ISP_DPCC_BASE + 0x0000001C) +#define RKISP1_CIF_ISP_DPCC_PG_FAC_1 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000020) +#define RKISP1_CIF_ISP_DPCC_RND_THRESH_1 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000024) +#define RKISP1_CIF_ISP_DPCC_RG_FAC_1 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000028) +#define RKISP1_CIF_ISP_DPCC_LINE_THRESH_2 (RKISP1_CIF_ISP_DPCC_BASE + 0x0000002C) +#define RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_2 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000030) +#define RKISP1_CIF_ISP_DPCC_PG_FAC_2 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000034) +#define RKISP1_CIF_ISP_DPCC_RND_THRESH_2 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000038) +#define RKISP1_CIF_ISP_DPCC_RG_FAC_2 (RKISP1_CIF_ISP_DPCC_BASE + 0x0000003C) +#define RKISP1_CIF_ISP_DPCC_LINE_THRESH_3 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000040) +#define RKISP1_CIF_ISP_DPCC_LINE_MAD_FAC_3 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000044) +#define RKISP1_CIF_ISP_DPCC_PG_FAC_3 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000048) +#define RKISP1_CIF_ISP_DPCC_RND_THRESH_3 (RKISP1_CIF_ISP_DPCC_BASE + 0x0000004C) +#define RKISP1_CIF_ISP_DPCC_RG_FAC_3 (RKISP1_CIF_ISP_DPCC_BASE + 0x00000050) +#define RKISP1_CIF_ISP_DPCC_RO_LIMITS (RKISP1_CIF_ISP_DPCC_BASE + 0x00000054) +#define RKISP1_CIF_ISP_DPCC_RND_OFFS (RKISP1_CIF_ISP_DPCC_BASE + 0x00000058) +#define RKISP1_CIF_ISP_DPCC_BPT_CTRL (RKISP1_CIF_ISP_DPCC_BASE + 0x0000005C) +#define RKISP1_CIF_ISP_DPCC_BPT_NUMBER (RKISP1_CIF_ISP_DPCC_BASE + 0x00000060) +#define RKISP1_CIF_ISP_DPCC_BPT_ADDR (RKISP1_CIF_ISP_DPCC_BASE + 0x00000064) +#define RKISP1_CIF_ISP_DPCC_BPT_DATA (RKISP1_CIF_ISP_DPCC_BASE + 0x00000068) + +#define RKISP1_CIF_ISP_WDR_BASE 0x00002A00 +#define RKISP1_CIF_ISP_WDR_CTRL (RKISP1_CIF_ISP_WDR_BASE + 0x00000000) +#define RKISP1_CIF_ISP_WDR_TONECURVE_1 (RKISP1_CIF_ISP_WDR_BASE + 0x00000004) +#define RKISP1_CIF_ISP_WDR_TONECURVE_2 (RKISP1_CIF_ISP_WDR_BASE + 0x00000008) +#define RKISP1_CIF_ISP_WDR_TONECURVE_3 (RKISP1_CIF_ISP_WDR_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_4 (RKISP1_CIF_ISP_WDR_BASE + 0x00000010) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_0 (RKISP1_CIF_ISP_WDR_BASE + 0x00000014) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_1 (RKISP1_CIF_ISP_WDR_BASE + 0x00000018) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_2 (RKISP1_CIF_ISP_WDR_BASE + 0x0000001C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_3 (RKISP1_CIF_ISP_WDR_BASE + 0x00000020) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_4 (RKISP1_CIF_ISP_WDR_BASE + 0x00000024) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_5 (RKISP1_CIF_ISP_WDR_BASE + 0x00000028) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_6 (RKISP1_CIF_ISP_WDR_BASE + 0x0000002C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_7 (RKISP1_CIF_ISP_WDR_BASE + 0x00000030) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_8 (RKISP1_CIF_ISP_WDR_BASE + 0x00000034) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_9 (RKISP1_CIF_ISP_WDR_BASE + 0x00000038) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_10 (RKISP1_CIF_ISP_WDR_BASE + 0x0000003C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_11 (RKISP1_CIF_ISP_WDR_BASE + 0x00000040) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_12 (RKISP1_CIF_ISP_WDR_BASE + 0x00000044) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_13 (RKISP1_CIF_ISP_WDR_BASE + 0x00000048) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_14 (RKISP1_CIF_ISP_WDR_BASE + 0x0000004C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_15 (RKISP1_CIF_ISP_WDR_BASE + 0x00000050) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_16 (RKISP1_CIF_ISP_WDR_BASE + 0x00000054) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_17 (RKISP1_CIF_ISP_WDR_BASE + 0x00000058) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_18 (RKISP1_CIF_ISP_WDR_BASE + 0x0000005C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_19 (RKISP1_CIF_ISP_WDR_BASE + 0x00000060) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_20 (RKISP1_CIF_ISP_WDR_BASE + 0x00000064) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_21 (RKISP1_CIF_ISP_WDR_BASE + 0x00000068) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_22 (RKISP1_CIF_ISP_WDR_BASE + 0x0000006C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_23 (RKISP1_CIF_ISP_WDR_BASE + 0x00000070) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_24 (RKISP1_CIF_ISP_WDR_BASE + 0x00000074) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_25 (RKISP1_CIF_ISP_WDR_BASE + 0x00000078) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_26 (RKISP1_CIF_ISP_WDR_BASE + 0x0000007C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_27 (RKISP1_CIF_ISP_WDR_BASE + 0x00000080) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_28 (RKISP1_CIF_ISP_WDR_BASE + 0x00000084) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_29 (RKISP1_CIF_ISP_WDR_BASE + 0x00000088) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_30 (RKISP1_CIF_ISP_WDR_BASE + 0x0000008C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_31 (RKISP1_CIF_ISP_WDR_BASE + 0x00000090) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_32 (RKISP1_CIF_ISP_WDR_BASE + 0x00000094) +#define RKISP1_CIF_ISP_WDR_OFFSET (RKISP1_CIF_ISP_WDR_BASE + 0x00000098) +#define RKISP1_CIF_ISP_WDR_DELTAMIN (RKISP1_CIF_ISP_WDR_BASE + 0x0000009C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_1_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000A0) +#define RKISP1_CIF_ISP_WDR_TONECURVE_2_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000A4) +#define RKISP1_CIF_ISP_WDR_TONECURVE_3_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000A8) +#define RKISP1_CIF_ISP_WDR_TONECURVE_4_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000AC) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_0_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000B0) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_1_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000B4) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_2_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000B8) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_3_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000BC) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_4_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000C0) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_5_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000C4) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_6_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000C8) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_7_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000CC) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_8_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000D0) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_9_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000D4) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_10_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000D8) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_11_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000DC) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_12_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000E0) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_13_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000E4) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_14_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000E8) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_15_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000EC) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_16_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000F0) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_17_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000F4) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_18_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000F8) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_19_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000FC) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_20_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000100) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_21_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000104) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_22_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000108) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_23_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x0000010C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_24_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000110) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_25_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000114) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_26_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000118) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_27_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x0000011C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_28_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000120) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_29_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000124) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_30_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000128) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_31_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x0000012C) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_32_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000130) + +#define RKISP1_CIF_ISP_HIST_BASE_V12 0x00002C00 +#define RKISP1_CIF_ISP_HIST_CTRL_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x00000000) +#define RKISP1_CIF_ISP_HIST_SIZE_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x00000004) +#define RKISP1_CIF_ISP_HIST_OFFS_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x00000008) +#define RKISP1_CIF_ISP_HIST_DBG1_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x0000000C) +#define RKISP1_CIF_ISP_HIST_DBG2_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x0000001C) +#define RKISP1_CIF_ISP_HIST_DBG3_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x0000002C) +#define RKISP1_CIF_ISP_HIST_WEIGHT_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x0000003C) +#define RKISP1_CIF_ISP_HIST_BIN_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x00000120) + +#define RKISP1_CIF_ISP_VSM_BASE 0x00002F00 +#define RKISP1_CIF_ISP_VSM_MODE (RKISP1_CIF_ISP_VSM_BASE + 0x00000000) +#define RKISP1_CIF_ISP_VSM_H_OFFS (RKISP1_CIF_ISP_VSM_BASE + 0x00000004) +#define RKISP1_CIF_ISP_VSM_V_OFFS (RKISP1_CIF_ISP_VSM_BASE + 0x00000008) +#define RKISP1_CIF_ISP_VSM_H_SIZE (RKISP1_CIF_ISP_VSM_BASE + 0x0000000C) +#define RKISP1_CIF_ISP_VSM_V_SIZE (RKISP1_CIF_ISP_VSM_BASE + 0x00000010) +#define RKISP1_CIF_ISP_VSM_H_SEGMENTS (RKISP1_CIF_ISP_VSM_BASE + 0x00000014) +#define RKISP1_CIF_ISP_VSM_V_SEGMENTS (RKISP1_CIF_ISP_VSM_BASE + 0x00000018) +#define RKISP1_CIF_ISP_VSM_DELTA_H (RKISP1_CIF_ISP_VSM_BASE + 0x0000001C) +#define RKISP1_CIF_ISP_VSM_DELTA_V (RKISP1_CIF_ISP_VSM_BASE + 0x00000020) + +#define RKISP1_CIF_ISP_CSI0_BASE 0x00007000 +#define RKISP1_CIF_ISP_CSI0_CTRL0 (RKISP1_CIF_ISP_CSI0_BASE + 0x00000000) + +#endif /* _RKISP1_REGS_H */ diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c new file mode 100644 index 000000000..f76afd811 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-resizer.c @@ -0,0 +1,817 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - V4l resizer device + * + * Copyright (C) 2019 Collabora, Ltd. + * + * Based on Rockchip ISP1 driver by Rockchip Electronics Co., Ltd. + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include "rkisp1-common.h" + +#define RKISP1_RSZ_SP_DEV_NAME RKISP1_DRIVER_NAME "_resizer_selfpath" +#define RKISP1_RSZ_MP_DEV_NAME RKISP1_DRIVER_NAME "_resizer_mainpath" + +#define RKISP1_DEF_FMT MEDIA_BUS_FMT_YUYV8_2X8 +#define RKISP1_DEF_PIXEL_ENC V4L2_PIXEL_ENC_YUV + +struct rkisp1_rsz_yuv_mbus_info { + u32 mbus_code; + u32 hdiv; + u32 vdiv; +}; + +static const struct rkisp1_rsz_yuv_mbus_info rkisp1_rsz_yuv_src_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, /* YUV422 */ + .hdiv = 2, + .vdiv = 1, + }, + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1_5X8, /* YUV420 */ + .hdiv = 2, + .vdiv = 2, + }, +}; + +static const struct rkisp1_rsz_yuv_mbus_info *rkisp1_rsz_get_yuv_mbus_info(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rkisp1_rsz_yuv_src_formats); i++) { + if (rkisp1_rsz_yuv_src_formats[i].mbus_code == mbus_code) + return &rkisp1_rsz_yuv_src_formats[i]; + } + + return NULL; +} + +enum rkisp1_shadow_regs_when { + RKISP1_SHADOW_REGS_SYNC, + RKISP1_SHADOW_REGS_ASYNC, +}; + +struct rkisp1_rsz_config { + /* constrains */ + const int max_rsz_width; + const int max_rsz_height; + const int min_rsz_width; + const int min_rsz_height; + /* registers */ + struct { + u32 ctrl; + u32 yuvmode_mask; + u32 rawmode_mask; + u32 h_offset; + u32 v_offset; + u32 h_size; + u32 v_size; + } dual_crop; +}; + +static const struct rkisp1_rsz_config rkisp1_rsz_config_mp = { + /* constraints */ + .max_rsz_width = RKISP1_RSZ_MP_SRC_MAX_WIDTH, + .max_rsz_height = RKISP1_RSZ_MP_SRC_MAX_HEIGHT, + .min_rsz_width = RKISP1_RSZ_SRC_MIN_WIDTH, + .min_rsz_height = RKISP1_RSZ_SRC_MIN_HEIGHT, + /* registers */ + .dual_crop = { + .ctrl = RKISP1_CIF_DUAL_CROP_CTRL, + .yuvmode_mask = RKISP1_CIF_DUAL_CROP_MP_MODE_YUV, + .rawmode_mask = RKISP1_CIF_DUAL_CROP_MP_MODE_RAW, + .h_offset = RKISP1_CIF_DUAL_CROP_M_H_OFFS, + .v_offset = RKISP1_CIF_DUAL_CROP_M_V_OFFS, + .h_size = RKISP1_CIF_DUAL_CROP_M_H_SIZE, + .v_size = RKISP1_CIF_DUAL_CROP_M_V_SIZE, + }, +}; + +static const struct rkisp1_rsz_config rkisp1_rsz_config_sp = { + /* constraints */ + .max_rsz_width = RKISP1_RSZ_SP_SRC_MAX_WIDTH, + .max_rsz_height = RKISP1_RSZ_SP_SRC_MAX_HEIGHT, + .min_rsz_width = RKISP1_RSZ_SRC_MIN_WIDTH, + .min_rsz_height = RKISP1_RSZ_SRC_MIN_HEIGHT, + /* registers */ + .dual_crop = { + .ctrl = RKISP1_CIF_DUAL_CROP_CTRL, + .yuvmode_mask = RKISP1_CIF_DUAL_CROP_SP_MODE_YUV, + .rawmode_mask = RKISP1_CIF_DUAL_CROP_SP_MODE_RAW, + .h_offset = RKISP1_CIF_DUAL_CROP_S_H_OFFS, + .v_offset = RKISP1_CIF_DUAL_CROP_S_V_OFFS, + .h_size = RKISP1_CIF_DUAL_CROP_S_H_SIZE, + .v_size = RKISP1_CIF_DUAL_CROP_S_V_SIZE, + }, +}; + +static inline u32 rkisp1_rsz_read(struct rkisp1_resizer *rsz, u32 offset) +{ + return rkisp1_read(rsz->rkisp1, rsz->regs_base + offset); +} + +static inline void rkisp1_rsz_write(struct rkisp1_resizer *rsz, u32 offset, + u32 value) +{ + rkisp1_write(rsz->rkisp1, rsz->regs_base + offset, value); +} + +static struct v4l2_mbus_framefmt * +rkisp1_rsz_get_pad_fmt(struct rkisp1_resizer *rsz, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + struct v4l2_subdev_state state = { + .pads = rsz->pad_cfg + }; + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_format(&rsz->sd, sd_state, pad); + else + return v4l2_subdev_get_try_format(&rsz->sd, &state, pad); +} + +static struct v4l2_rect * +rkisp1_rsz_get_pad_crop(struct rkisp1_resizer *rsz, + struct v4l2_subdev_state *sd_state, + unsigned int pad, u32 which) +{ + struct v4l2_subdev_state state = { + .pads = rsz->pad_cfg + }; + if (which == V4L2_SUBDEV_FORMAT_TRY) + return v4l2_subdev_get_try_crop(&rsz->sd, sd_state, pad); + else + return v4l2_subdev_get_try_crop(&rsz->sd, &state, pad); +} + +/* ---------------------------------------------------------------------------- + * Dual crop hw configs + */ + +static void rkisp1_dcrop_disable(struct rkisp1_resizer *rsz, + enum rkisp1_shadow_regs_when when) +{ + u32 dc_ctrl = rkisp1_read(rsz->rkisp1, rsz->config->dual_crop.ctrl); + u32 mask = ~(rsz->config->dual_crop.yuvmode_mask | + rsz->config->dual_crop.rawmode_mask); + + dc_ctrl &= mask; + if (when == RKISP1_SHADOW_REGS_ASYNC) + dc_ctrl |= RKISP1_CIF_DUAL_CROP_GEN_CFG_UPD; + else + dc_ctrl |= RKISP1_CIF_DUAL_CROP_CFG_UPD; + rkisp1_write(rsz->rkisp1, rsz->config->dual_crop.ctrl, dc_ctrl); +} + +/* configure dual-crop unit */ +static void rkisp1_dcrop_config(struct rkisp1_resizer *rsz) +{ + struct rkisp1_device *rkisp1 = rsz->rkisp1; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *sink_crop; + u32 dc_ctrl; + + sink_crop = rkisp1_rsz_get_pad_crop(rsz, NULL, RKISP1_RSZ_PAD_SINK, + V4L2_SUBDEV_FORMAT_ACTIVE); + sink_fmt = rkisp1_rsz_get_pad_fmt(rsz, NULL, RKISP1_RSZ_PAD_SINK, + V4L2_SUBDEV_FORMAT_ACTIVE); + + if (sink_crop->width == sink_fmt->width && + sink_crop->height == sink_fmt->height && + sink_crop->left == 0 && sink_crop->top == 0) { + rkisp1_dcrop_disable(rsz, RKISP1_SHADOW_REGS_SYNC); + dev_dbg(rkisp1->dev, "capture %d crop disabled\n", rsz->id); + return; + } + + dc_ctrl = rkisp1_read(rkisp1, rsz->config->dual_crop.ctrl); + rkisp1_write(rkisp1, rsz->config->dual_crop.h_offset, sink_crop->left); + rkisp1_write(rkisp1, rsz->config->dual_crop.v_offset, sink_crop->top); + rkisp1_write(rkisp1, rsz->config->dual_crop.h_size, sink_crop->width); + rkisp1_write(rkisp1, rsz->config->dual_crop.v_size, sink_crop->height); + dc_ctrl |= rsz->config->dual_crop.yuvmode_mask; + dc_ctrl |= RKISP1_CIF_DUAL_CROP_CFG_UPD; + rkisp1_write(rkisp1, rsz->config->dual_crop.ctrl, dc_ctrl); + + dev_dbg(rkisp1->dev, "stream %d crop: %dx%d -> %dx%d\n", rsz->id, + sink_fmt->width, sink_fmt->height, + sink_crop->width, sink_crop->height); +} + +/* ---------------------------------------------------------------------------- + * Resizer hw configs + */ + +static void rkisp1_rsz_update_shadow(struct rkisp1_resizer *rsz, + enum rkisp1_shadow_regs_when when) +{ + u32 ctrl_cfg = rkisp1_rsz_read(rsz, RKISP1_CIF_RSZ_CTRL); + + if (when == RKISP1_SHADOW_REGS_ASYNC) + ctrl_cfg |= RKISP1_CIF_RSZ_CTRL_CFG_UPD_AUTO; + else + ctrl_cfg |= RKISP1_CIF_RSZ_CTRL_CFG_UPD; + + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_CTRL, ctrl_cfg); +} + +static u32 rkisp1_rsz_calc_ratio(u32 len_sink, u32 len_src) +{ + if (len_sink < len_src) + return ((len_sink - 1) * RKISP1_CIF_RSZ_SCALER_FACTOR) / + (len_src - 1); + + return ((len_src - 1) * RKISP1_CIF_RSZ_SCALER_FACTOR) / + (len_sink - 1) + 1; +} + +static void rkisp1_rsz_disable(struct rkisp1_resizer *rsz, + enum rkisp1_shadow_regs_when when) +{ + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_CTRL, 0); + + if (when == RKISP1_SHADOW_REGS_SYNC) + rkisp1_rsz_update_shadow(rsz, when); +} + +static void rkisp1_rsz_config_regs(struct rkisp1_resizer *rsz, + struct v4l2_rect *sink_y, + struct v4l2_rect *sink_c, + struct v4l2_rect *src_y, + struct v4l2_rect *src_c, + enum rkisp1_shadow_regs_when when) +{ + u32 ratio, rsz_ctrl = 0; + unsigned int i; + + /* No phase offset */ + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_PHASE_HY, 0); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_PHASE_HC, 0); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_PHASE_VY, 0); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_PHASE_VC, 0); + + /* Linear interpolation */ + for (i = 0; i < 64; i++) { + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_SCALE_LUT_ADDR, i); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_SCALE_LUT, i); + } + + if (sink_y->width != src_y->width) { + rsz_ctrl |= RKISP1_CIF_RSZ_CTRL_SCALE_HY_ENABLE; + if (sink_y->width < src_y->width) + rsz_ctrl |= RKISP1_CIF_RSZ_CTRL_SCALE_HY_UP; + ratio = rkisp1_rsz_calc_ratio(sink_y->width, src_y->width); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_SCALE_HY, ratio); + } + + if (sink_c->width != src_c->width) { + rsz_ctrl |= RKISP1_CIF_RSZ_CTRL_SCALE_HC_ENABLE; + if (sink_c->width < src_c->width) + rsz_ctrl |= RKISP1_CIF_RSZ_CTRL_SCALE_HC_UP; + ratio = rkisp1_rsz_calc_ratio(sink_c->width, src_c->width); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_SCALE_HCB, ratio); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_SCALE_HCR, ratio); + } + + if (sink_y->height != src_y->height) { + rsz_ctrl |= RKISP1_CIF_RSZ_CTRL_SCALE_VY_ENABLE; + if (sink_y->height < src_y->height) + rsz_ctrl |= RKISP1_CIF_RSZ_CTRL_SCALE_VY_UP; + ratio = rkisp1_rsz_calc_ratio(sink_y->height, src_y->height); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_SCALE_VY, ratio); + } + + if (sink_c->height != src_c->height) { + rsz_ctrl |= RKISP1_CIF_RSZ_CTRL_SCALE_VC_ENABLE; + if (sink_c->height < src_c->height) + rsz_ctrl |= RKISP1_CIF_RSZ_CTRL_SCALE_VC_UP; + ratio = rkisp1_rsz_calc_ratio(sink_c->height, src_c->height); + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_SCALE_VC, ratio); + } + + rkisp1_rsz_write(rsz, RKISP1_CIF_RSZ_CTRL, rsz_ctrl); + + rkisp1_rsz_update_shadow(rsz, when); +} + +static void rkisp1_rsz_config(struct rkisp1_resizer *rsz, + enum rkisp1_shadow_regs_when when) +{ + const struct rkisp1_rsz_yuv_mbus_info *sink_yuv_info, *src_yuv_info; + struct v4l2_rect sink_y, sink_c, src_y, src_c; + struct v4l2_mbus_framefmt *src_fmt, *sink_fmt; + struct v4l2_rect *sink_crop; + + sink_crop = rkisp1_rsz_get_pad_crop(rsz, NULL, RKISP1_RSZ_PAD_SINK, + V4L2_SUBDEV_FORMAT_ACTIVE); + src_fmt = rkisp1_rsz_get_pad_fmt(rsz, NULL, RKISP1_RSZ_PAD_SRC, + V4L2_SUBDEV_FORMAT_ACTIVE); + src_yuv_info = rkisp1_rsz_get_yuv_mbus_info(src_fmt->code); + sink_fmt = rkisp1_rsz_get_pad_fmt(rsz, NULL, RKISP1_RSZ_PAD_SINK, + V4L2_SUBDEV_FORMAT_ACTIVE); + sink_yuv_info = rkisp1_rsz_get_yuv_mbus_info(sink_fmt->code); + + /* + * The resizer only works on yuv formats, + * so return if it is bayer format. + */ + if (rsz->pixel_enc == V4L2_PIXEL_ENC_BAYER) { + rkisp1_rsz_disable(rsz, when); + return; + } + + sink_y.width = sink_crop->width; + sink_y.height = sink_crop->height; + src_y.width = src_fmt->width; + src_y.height = src_fmt->height; + + sink_c.width = sink_y.width / sink_yuv_info->hdiv; + sink_c.height = sink_y.height / sink_yuv_info->vdiv; + + /* + * The resizer is used not only to change the dimensions of the frame + * but also to change the scale for YUV formats, + * (4:2:2 -> 4:2:0 for example). So the width/height of the CbCr + * streams should be set according to the media bus format in the src pad. + */ + src_c.width = src_y.width / src_yuv_info->hdiv; + src_c.height = src_y.height / src_yuv_info->vdiv; + + if (sink_c.width == src_c.width && sink_c.height == src_c.height) { + rkisp1_rsz_disable(rsz, when); + return; + } + + dev_dbg(rsz->rkisp1->dev, "stream %d rsz/scale: %dx%d -> %dx%d\n", + rsz->id, sink_crop->width, sink_crop->height, + src_fmt->width, src_fmt->height); + dev_dbg(rsz->rkisp1->dev, "chroma scaling %dx%d -> %dx%d\n", + sink_c.width, sink_c.height, src_c.width, src_c.height); + + /* set values in the hw */ + rkisp1_rsz_config_regs(rsz, &sink_y, &sink_c, &src_y, &src_c, when); +} + +/* ---------------------------------------------------------------------------- + * Subdev pad operations + */ + +static int rkisp1_rsz_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct rkisp1_resizer *rsz = + container_of(sd, struct rkisp1_resizer, sd); + struct v4l2_subdev_pad_config dummy_cfg; + struct v4l2_subdev_state pad_state = { + .pads = &dummy_cfg + }; + u32 pad = code->pad; + int ret; + + if (code->pad == RKISP1_RSZ_PAD_SRC) { + /* supported mbus codes on the src are the same as in the capture */ + struct rkisp1_capture *cap = &rsz->rkisp1->capture_devs[rsz->id]; + + return rkisp1_cap_enum_mbus_codes(cap, code); + } + + /* + * The selfpath capture doesn't support bayer formats. Therefore the selfpath resizer + * should support only YUV422 on the sink pad + */ + if (rsz->id == RKISP1_SELFPATH) { + if (code->index > 0) + return -EINVAL; + code->code = MEDIA_BUS_FMT_YUYV8_2X8; + return 0; + } + + /* supported mbus codes on the sink pad are the same as isp src pad */ + code->pad = RKISP1_ISP_PAD_SOURCE_VIDEO; + ret = v4l2_subdev_call(&rsz->rkisp1->isp.sd, pad, enum_mbus_code, + &pad_state, code); + + /* restore pad */ + code->pad = pad; + code->flags = 0; + return ret; +} + +static int rkisp1_rsz_init_config(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *sink_crop; + + sink_fmt = v4l2_subdev_get_try_format(sd, sd_state, + RKISP1_RSZ_PAD_SRC); + sink_fmt->width = RKISP1_DEFAULT_WIDTH; + sink_fmt->height = RKISP1_DEFAULT_HEIGHT; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = RKISP1_DEF_FMT; + sink_fmt->colorspace = V4L2_COLORSPACE_SRGB; + sink_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + + sink_crop = v4l2_subdev_get_try_crop(sd, sd_state, + RKISP1_RSZ_PAD_SINK); + sink_crop->width = RKISP1_DEFAULT_WIDTH; + sink_crop->height = RKISP1_DEFAULT_HEIGHT; + sink_crop->left = 0; + sink_crop->top = 0; + + src_fmt = v4l2_subdev_get_try_format(sd, sd_state, + RKISP1_RSZ_PAD_SINK); + *src_fmt = *sink_fmt; + + /* NOTE: there is no crop in the source pad, only in the sink */ + + return 0; +} + +static void rkisp1_rsz_set_src_fmt(struct rkisp1_resizer *rsz, + struct v4l2_subdev_state *sd_state, + struct v4l2_mbus_framefmt *format, + unsigned int which) +{ + const struct rkisp1_mbus_info *sink_mbus_info; + struct v4l2_mbus_framefmt *src_fmt, *sink_fmt; + + sink_fmt = rkisp1_rsz_get_pad_fmt(rsz, sd_state, RKISP1_RSZ_PAD_SINK, + which); + src_fmt = rkisp1_rsz_get_pad_fmt(rsz, sd_state, RKISP1_RSZ_PAD_SRC, + which); + sink_mbus_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + + /* for YUV formats, userspace can change the mbus code on the src pad if it is supported */ + if (sink_mbus_info->pixel_enc == V4L2_PIXEL_ENC_YUV && + rkisp1_rsz_get_yuv_mbus_info(format->code)) + src_fmt->code = format->code; + + src_fmt->width = clamp_t(u32, format->width, + rsz->config->min_rsz_width, + rsz->config->max_rsz_width); + src_fmt->height = clamp_t(u32, format->height, + rsz->config->min_rsz_height, + rsz->config->max_rsz_height); + + *format = *src_fmt; +} + +static void rkisp1_rsz_set_sink_crop(struct rkisp1_resizer *rsz, + struct v4l2_subdev_state *sd_state, + struct v4l2_rect *r, + unsigned int which) +{ + const struct rkisp1_mbus_info *mbus_info; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *sink_crop; + + sink_fmt = rkisp1_rsz_get_pad_fmt(rsz, sd_state, RKISP1_RSZ_PAD_SINK, + which); + sink_crop = rkisp1_rsz_get_pad_crop(rsz, sd_state, + RKISP1_RSZ_PAD_SINK, + which); + + /* Not crop for MP bayer raw data */ + mbus_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + + if (rsz->id == RKISP1_MAINPATH && + mbus_info->pixel_enc == V4L2_PIXEL_ENC_BAYER) { + sink_crop->left = 0; + sink_crop->top = 0; + sink_crop->width = sink_fmt->width; + sink_crop->height = sink_fmt->height; + + *r = *sink_crop; + return; + } + + sink_crop->left = ALIGN(r->left, 2); + sink_crop->width = ALIGN(r->width, 2); + sink_crop->top = r->top; + sink_crop->height = r->height; + rkisp1_sd_adjust_crop(sink_crop, sink_fmt); + + *r = *sink_crop; +} + +static void rkisp1_rsz_set_sink_fmt(struct rkisp1_resizer *rsz, + struct v4l2_subdev_state *sd_state, + struct v4l2_mbus_framefmt *format, + unsigned int which) +{ + const struct rkisp1_mbus_info *mbus_info; + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *sink_crop; + bool is_yuv; + + sink_fmt = rkisp1_rsz_get_pad_fmt(rsz, sd_state, RKISP1_RSZ_PAD_SINK, + which); + src_fmt = rkisp1_rsz_get_pad_fmt(rsz, sd_state, RKISP1_RSZ_PAD_SRC, + which); + sink_crop = rkisp1_rsz_get_pad_crop(rsz, sd_state, + RKISP1_RSZ_PAD_SINK, + which); + if (rsz->id == RKISP1_SELFPATH) + sink_fmt->code = MEDIA_BUS_FMT_YUYV8_2X8; + else + sink_fmt->code = format->code; + + mbus_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + if (!mbus_info || !(mbus_info->direction & RKISP1_ISP_SD_SRC)) { + sink_fmt->code = RKISP1_DEF_FMT; + mbus_info = rkisp1_mbus_info_get_by_code(sink_fmt->code); + } + if (which == V4L2_SUBDEV_FORMAT_ACTIVE) + rsz->pixel_enc = mbus_info->pixel_enc; + + sink_fmt->width = clamp_t(u32, format->width, + RKISP1_ISP_MIN_WIDTH, + RKISP1_ISP_MAX_WIDTH); + sink_fmt->height = clamp_t(u32, format->height, + RKISP1_ISP_MIN_HEIGHT, + RKISP1_ISP_MAX_HEIGHT); + + /* + * Adjust the color space fields. Accept any color primaries and + * transfer function for both YUV and Bayer. For YUV any YCbCr encoding + * and quantization range is also accepted. For Bayer formats, the YCbCr + * encoding isn't applicable, and the quantization range can only be + * full. + */ + is_yuv = mbus_info->pixel_enc == V4L2_PIXEL_ENC_YUV; + + sink_fmt->colorspace = format->colorspace ? : + (is_yuv ? V4L2_COLORSPACE_SRGB : + V4L2_COLORSPACE_RAW); + sink_fmt->xfer_func = format->xfer_func ? : + V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace); + if (is_yuv) { + sink_fmt->ycbcr_enc = format->ycbcr_enc ? : + V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace); + sink_fmt->quantization = format->quantization ? : + V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace, + sink_fmt->ycbcr_enc); + } else { + /* + * The YCbCr encoding isn't applicable for non-YUV formats, but + * V4L2 has no "no encoding" value. Hardcode it to Rec. 601, it + * should be ignored by userspace. + */ + sink_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + sink_fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE; + } + + *format = *sink_fmt; + + /* Propagate the media bus code and color space to the source pad. */ + src_fmt->code = sink_fmt->code; + src_fmt->colorspace = sink_fmt->colorspace; + src_fmt->xfer_func = sink_fmt->xfer_func; + src_fmt->ycbcr_enc = sink_fmt->ycbcr_enc; + src_fmt->quantization = sink_fmt->quantization; + + /* Update sink crop */ + rkisp1_rsz_set_sink_crop(rsz, sd_state, sink_crop, which); +} + +static int rkisp1_rsz_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct rkisp1_resizer *rsz = + container_of(sd, struct rkisp1_resizer, sd); + + mutex_lock(&rsz->ops_lock); + fmt->format = *rkisp1_rsz_get_pad_fmt(rsz, sd_state, fmt->pad, + fmt->which); + mutex_unlock(&rsz->ops_lock); + return 0; +} + +static int rkisp1_rsz_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct rkisp1_resizer *rsz = + container_of(sd, struct rkisp1_resizer, sd); + + mutex_lock(&rsz->ops_lock); + if (fmt->pad == RKISP1_RSZ_PAD_SINK) + rkisp1_rsz_set_sink_fmt(rsz, sd_state, &fmt->format, + fmt->which); + else + rkisp1_rsz_set_src_fmt(rsz, sd_state, &fmt->format, + fmt->which); + + mutex_unlock(&rsz->ops_lock); + return 0; +} + +static int rkisp1_rsz_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct rkisp1_resizer *rsz = + container_of(sd, struct rkisp1_resizer, sd); + struct v4l2_mbus_framefmt *mf_sink; + int ret = 0; + + if (sel->pad == RKISP1_RSZ_PAD_SRC) + return -EINVAL; + + mutex_lock(&rsz->ops_lock); + switch (sel->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + mf_sink = rkisp1_rsz_get_pad_fmt(rsz, sd_state, + RKISP1_RSZ_PAD_SINK, + sel->which); + sel->r.height = mf_sink->height; + sel->r.width = mf_sink->width; + sel->r.left = 0; + sel->r.top = 0; + break; + case V4L2_SEL_TGT_CROP: + sel->r = *rkisp1_rsz_get_pad_crop(rsz, sd_state, + RKISP1_RSZ_PAD_SINK, + sel->which); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&rsz->ops_lock); + return ret; +} + +static int rkisp1_rsz_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct rkisp1_resizer *rsz = + container_of(sd, struct rkisp1_resizer, sd); + + if (sel->target != V4L2_SEL_TGT_CROP || sel->pad == RKISP1_RSZ_PAD_SRC) + return -EINVAL; + + dev_dbg(rsz->rkisp1->dev, "%s: pad: %d sel(%d,%d)/%dx%d\n", __func__, + sel->pad, sel->r.left, sel->r.top, sel->r.width, sel->r.height); + + mutex_lock(&rsz->ops_lock); + rkisp1_rsz_set_sink_crop(rsz, sd_state, &sel->r, sel->which); + mutex_unlock(&rsz->ops_lock); + + return 0; +} + +static const struct media_entity_operations rkisp1_rsz_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static const struct v4l2_subdev_pad_ops rkisp1_rsz_pad_ops = { + .enum_mbus_code = rkisp1_rsz_enum_mbus_code, + .get_selection = rkisp1_rsz_get_selection, + .set_selection = rkisp1_rsz_set_selection, + .init_cfg = rkisp1_rsz_init_config, + .get_fmt = rkisp1_rsz_get_fmt, + .set_fmt = rkisp1_rsz_set_fmt, + .link_validate = v4l2_subdev_link_validate_default, +}; + +/* ---------------------------------------------------------------------------- + * Stream operations + */ + +static int rkisp1_rsz_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct rkisp1_resizer *rsz = + container_of(sd, struct rkisp1_resizer, sd); + struct rkisp1_device *rkisp1 = rsz->rkisp1; + struct rkisp1_capture *other = &rkisp1->capture_devs[rsz->id ^ 1]; + enum rkisp1_shadow_regs_when when = RKISP1_SHADOW_REGS_SYNC; + + if (!enable) { + rkisp1_dcrop_disable(rsz, RKISP1_SHADOW_REGS_ASYNC); + rkisp1_rsz_disable(rsz, RKISP1_SHADOW_REGS_ASYNC); + return 0; + } + + if (other->is_streaming) + when = RKISP1_SHADOW_REGS_ASYNC; + + mutex_lock(&rsz->ops_lock); + rkisp1_rsz_config(rsz, when); + rkisp1_dcrop_config(rsz); + + mutex_unlock(&rsz->ops_lock); + return 0; +} + +static const struct v4l2_subdev_video_ops rkisp1_rsz_video_ops = { + .s_stream = rkisp1_rsz_s_stream, +}; + +static const struct v4l2_subdev_ops rkisp1_rsz_ops = { + .video = &rkisp1_rsz_video_ops, + .pad = &rkisp1_rsz_pad_ops, +}; + +static void rkisp1_rsz_unregister(struct rkisp1_resizer *rsz) +{ + if (!rsz->rkisp1) + return; + + v4l2_device_unregister_subdev(&rsz->sd); + media_entity_cleanup(&rsz->sd.entity); + mutex_destroy(&rsz->ops_lock); +} + +static int rkisp1_rsz_register(struct rkisp1_resizer *rsz) +{ + struct v4l2_subdev_state state = { + .pads = rsz->pad_cfg + }; + static const char * const dev_names[] = { + RKISP1_RSZ_MP_DEV_NAME, + RKISP1_RSZ_SP_DEV_NAME + }; + struct media_pad *pads = rsz->pads; + struct v4l2_subdev *sd = &rsz->sd; + int ret; + + if (rsz->id == RKISP1_SELFPATH) { + rsz->regs_base = RKISP1_CIF_SRSZ_BASE; + rsz->config = &rkisp1_rsz_config_sp; + } else { + rsz->regs_base = RKISP1_CIF_MRSZ_BASE; + rsz->config = &rkisp1_rsz_config_mp; + } + + v4l2_subdev_init(sd, &rkisp1_rsz_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sd->entity.ops = &rkisp1_rsz_media_ops; + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + sd->owner = THIS_MODULE; + strscpy(sd->name, dev_names[rsz->id], sizeof(sd->name)); + + pads[RKISP1_RSZ_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[RKISP1_RSZ_PAD_SRC].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; + + rsz->pixel_enc = RKISP1_DEF_PIXEL_ENC; + + mutex_init(&rsz->ops_lock); + ret = media_entity_pads_init(&sd->entity, RKISP1_RSZ_PAD_MAX, pads); + if (ret) + goto error; + + ret = v4l2_device_register_subdev(&rsz->rkisp1->v4l2_dev, sd); + if (ret) { + dev_err(sd->dev, "Failed to register resizer subdev\n"); + goto error; + } + + rkisp1_rsz_init_config(sd, &state); + return 0; + +error: + media_entity_cleanup(&sd->entity); + mutex_destroy(&rsz->ops_lock); + return ret; +} + +int rkisp1_resizer_devs_register(struct rkisp1_device *rkisp1) +{ + unsigned int i; + int ret; + + for (i = 0; i < ARRAY_SIZE(rkisp1->resizer_devs); i++) { + struct rkisp1_resizer *rsz = &rkisp1->resizer_devs[i]; + + rsz->rkisp1 = rkisp1; + rsz->id = i; + + ret = rkisp1_rsz_register(rsz); + if (ret) { + rsz->rkisp1 = NULL; + rkisp1_resizer_devs_unregister(rkisp1); + return ret; + } + } + + return 0; +} + +void rkisp1_resizer_devs_unregister(struct rkisp1_device *rkisp1) +{ + struct rkisp1_resizer *mp = &rkisp1->resizer_devs[RKISP1_MAINPATH]; + struct rkisp1_resizer *sp = &rkisp1->resizer_devs[RKISP1_SELFPATH]; + + rkisp1_rsz_unregister(mp); + rkisp1_rsz_unregister(sp); +} diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-stats.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-stats.c new file mode 100644 index 000000000..2795eef91 --- /dev/null +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-stats.c @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: (GPL-2.0+ OR MIT) +/* + * Rockchip ISP1 Driver - Stats subdevice + * + * Copyright (C) 2017 Rockchip Electronics Co., Ltd. + */ + +#include +#include +#include +#include +#include /* for ISP statistics */ + +#include "rkisp1-common.h" + +#define RKISP1_STATS_DEV_NAME RKISP1_DRIVER_NAME "_stats" + +#define RKISP1_ISP_STATS_REQ_BUFS_MIN 2 +#define RKISP1_ISP_STATS_REQ_BUFS_MAX 8 + +static int rkisp1_stats_enum_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct video_device *video = video_devdata(file); + struct rkisp1_stats *stats = video_get_drvdata(video); + + if (f->index > 0 || f->type != video->queue->type) + return -EINVAL; + + f->pixelformat = stats->vdev_fmt.fmt.meta.dataformat; + return 0; +} + +static int rkisp1_stats_g_fmt_meta_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct video_device *video = video_devdata(file); + struct rkisp1_stats *stats = video_get_drvdata(video); + struct v4l2_meta_format *meta = &f->fmt.meta; + + if (f->type != video->queue->type) + return -EINVAL; + + memset(meta, 0, sizeof(*meta)); + meta->dataformat = stats->vdev_fmt.fmt.meta.dataformat; + meta->buffersize = stats->vdev_fmt.fmt.meta.buffersize; + + return 0; +} + +static int rkisp1_stats_querycap(struct file *file, + void *priv, struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + + strscpy(cap->driver, RKISP1_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, vdev->name, sizeof(cap->card)); + strscpy(cap->bus_info, RKISP1_BUS_INFO, sizeof(cap->bus_info)); + + return 0; +} + +/* ISP video device IOCTLs */ +static const struct v4l2_ioctl_ops rkisp1_stats_ioctl = { + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_fmt_meta_cap = rkisp1_stats_enum_fmt_meta_cap, + .vidioc_g_fmt_meta_cap = rkisp1_stats_g_fmt_meta_cap, + .vidioc_s_fmt_meta_cap = rkisp1_stats_g_fmt_meta_cap, + .vidioc_try_fmt_meta_cap = rkisp1_stats_g_fmt_meta_cap, + .vidioc_querycap = rkisp1_stats_querycap, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations rkisp1_stats_fops = { + .mmap = vb2_fop_mmap, + .unlocked_ioctl = video_ioctl2, + .poll = vb2_fop_poll, + .open = v4l2_fh_open, + .release = vb2_fop_release +}; + +static int rkisp1_stats_vb2_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + *num_planes = 1; + + *num_buffers = clamp_t(u32, *num_buffers, RKISP1_ISP_STATS_REQ_BUFS_MIN, + RKISP1_ISP_STATS_REQ_BUFS_MAX); + + sizes[0] = sizeof(struct rkisp1_stat_buffer); + + return 0; +} + +static void rkisp1_stats_vb2_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rkisp1_buffer *stats_buf = + container_of(vbuf, struct rkisp1_buffer, vb); + struct vb2_queue *vq = vb->vb2_queue; + struct rkisp1_stats *stats_dev = vq->drv_priv; + + + spin_lock_irq(&stats_dev->lock); + list_add_tail(&stats_buf->queue, &stats_dev->stat); + spin_unlock_irq(&stats_dev->lock); +} + +static int rkisp1_stats_vb2_buf_prepare(struct vb2_buffer *vb) +{ + if (vb2_plane_size(vb, 0) < sizeof(struct rkisp1_stat_buffer)) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, sizeof(struct rkisp1_stat_buffer)); + + return 0; +} + +static void rkisp1_stats_vb2_stop_streaming(struct vb2_queue *vq) +{ + struct rkisp1_stats *stats = vq->drv_priv; + struct rkisp1_buffer *buf; + unsigned int i; + + spin_lock_irq(&stats->lock); + for (i = 0; i < RKISP1_ISP_STATS_REQ_BUFS_MAX; i++) { + if (list_empty(&stats->stat)) + break; + buf = list_first_entry(&stats->stat, + struct rkisp1_buffer, queue); + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + spin_unlock_irq(&stats->lock); +} + +static const struct vb2_ops rkisp1_stats_vb2_ops = { + .queue_setup = rkisp1_stats_vb2_queue_setup, + .buf_queue = rkisp1_stats_vb2_buf_queue, + .buf_prepare = rkisp1_stats_vb2_buf_prepare, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = rkisp1_stats_vb2_stop_streaming, +}; + +static int +rkisp1_stats_init_vb2_queue(struct vb2_queue *q, struct rkisp1_stats *stats) +{ + struct rkisp1_vdev_node *node; + + node = container_of(q, struct rkisp1_vdev_node, buf_queue); + + q->type = V4L2_BUF_TYPE_META_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = stats; + q->ops = &rkisp1_stats_vb2_ops; + q->mem_ops = &vb2_vmalloc_memops; + q->buf_struct_size = sizeof(struct rkisp1_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &node->vlock; + + return vb2_queue_init(q); +} + +static void rkisp1_stats_get_awb_meas_v10(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf) +{ + /* Protect against concurrent access from ISR? */ + struct rkisp1_device *rkisp1 = stats->rkisp1; + u32 reg_val; + + pbuf->meas_type |= RKISP1_CIF_ISP_STAT_AWB; + reg_val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AWB_WHITE_CNT_V10); + pbuf->params.awb.awb_mean[0].cnt = + RKISP1_CIF_ISP_AWB_GET_PIXEL_CNT(reg_val); + reg_val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AWB_MEAN_V10); + + pbuf->params.awb.awb_mean[0].mean_cr_or_r = + RKISP1_CIF_ISP_AWB_GET_MEAN_CR_R(reg_val); + pbuf->params.awb.awb_mean[0].mean_cb_or_b = + RKISP1_CIF_ISP_AWB_GET_MEAN_CB_B(reg_val); + pbuf->params.awb.awb_mean[0].mean_y_or_g = + RKISP1_CIF_ISP_AWB_GET_MEAN_Y_G(reg_val); +} + +static void rkisp1_stats_get_awb_meas_v12(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf) +{ + /* Protect against concurrent access from ISR? */ + struct rkisp1_device *rkisp1 = stats->rkisp1; + u32 reg_val; + + pbuf->meas_type |= RKISP1_CIF_ISP_STAT_AWB; + reg_val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AWB_WHITE_CNT_V12); + pbuf->params.awb.awb_mean[0].cnt = + RKISP1_CIF_ISP_AWB_GET_PIXEL_CNT(reg_val); + reg_val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AWB_MEAN_V12); + + pbuf->params.awb.awb_mean[0].mean_cr_or_r = + RKISP1_CIF_ISP_AWB_GET_MEAN_CR_R(reg_val); + pbuf->params.awb.awb_mean[0].mean_cb_or_b = + RKISP1_CIF_ISP_AWB_GET_MEAN_CB_B(reg_val); + pbuf->params.awb.awb_mean[0].mean_y_or_g = + RKISP1_CIF_ISP_AWB_GET_MEAN_Y_G(reg_val); +} + +static void rkisp1_stats_get_aec_meas_v10(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf) +{ + struct rkisp1_device *rkisp1 = stats->rkisp1; + unsigned int i; + + pbuf->meas_type |= RKISP1_CIF_ISP_STAT_AUTOEXP; + for (i = 0; i < RKISP1_CIF_ISP_AE_MEAN_MAX_V10; i++) + pbuf->params.ae.exp_mean[i] = + (u8)rkisp1_read(rkisp1, + RKISP1_CIF_ISP_EXP_MEAN_00_V10 + i * 4); +} + +static void rkisp1_stats_get_aec_meas_v12(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf) +{ + struct rkisp1_device *rkisp1 = stats->rkisp1; + u32 value; + int i; + + pbuf->meas_type |= RKISP1_CIF_ISP_STAT_AUTOEXP; + for (i = 0; i < RKISP1_CIF_ISP_AE_MEAN_MAX_V12 / 4; i++) { + value = rkisp1_read(rkisp1, RKISP1_CIF_ISP_EXP_MEAN_V12 + i * 4); + pbuf->params.ae.exp_mean[4 * i + 0] = + RKISP1_CIF_ISP_EXP_GET_MEAN_xy0_V12(value); + pbuf->params.ae.exp_mean[4 * i + 1] = + RKISP1_CIF_ISP_EXP_GET_MEAN_xy1_V12(value); + pbuf->params.ae.exp_mean[4 * i + 2] = + RKISP1_CIF_ISP_EXP_GET_MEAN_xy2_V12(value); + pbuf->params.ae.exp_mean[4 * i + 3] = + RKISP1_CIF_ISP_EXP_GET_MEAN_xy3_V12(value); + } + + value = rkisp1_read(rkisp1, RKISP1_CIF_ISP_EXP_MEAN_V12 + i * 4); + pbuf->params.ae.exp_mean[4 * i + 0] = RKISP1_CIF_ISP_EXP_GET_MEAN_xy0_V12(value); +} + +static void rkisp1_stats_get_afc_meas(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf) +{ + struct rkisp1_device *rkisp1 = stats->rkisp1; + struct rkisp1_cif_isp_af_stat *af; + + pbuf->meas_type |= RKISP1_CIF_ISP_STAT_AFM; + + af = &pbuf->params.af; + af->window[0].sum = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AFM_SUM_A); + af->window[0].lum = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AFM_LUM_A); + af->window[1].sum = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AFM_SUM_B); + af->window[1].lum = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AFM_LUM_B); + af->window[2].sum = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AFM_SUM_C); + af->window[2].lum = rkisp1_read(rkisp1, RKISP1_CIF_ISP_AFM_LUM_C); +} + +static void rkisp1_stats_get_hst_meas_v10(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf) +{ + struct rkisp1_device *rkisp1 = stats->rkisp1; + unsigned int i; + + pbuf->meas_type |= RKISP1_CIF_ISP_STAT_HIST; + for (i = 0; i < RKISP1_CIF_ISP_HIST_BIN_N_MAX_V10; i++) { + u32 reg_val = rkisp1_read(rkisp1, RKISP1_CIF_ISP_HIST_BIN_0_V10 + i * 4); + + pbuf->params.hist.hist_bins[i] = RKISP1_CIF_ISP_HIST_GET_BIN_V10(reg_val); + } +} + +static void rkisp1_stats_get_hst_meas_v12(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf) +{ + struct rkisp1_device *rkisp1 = stats->rkisp1; + u32 value; + int i; + + pbuf->meas_type |= RKISP1_CIF_ISP_STAT_HIST; + for (i = 0; i < RKISP1_CIF_ISP_HIST_BIN_N_MAX_V12 / 2; i++) { + value = rkisp1_read(rkisp1, RKISP1_CIF_ISP_HIST_BIN_V12 + i * 4); + pbuf->params.hist.hist_bins[2 * i] = + RKISP1_CIF_ISP_HIST_GET_BIN0_V12(value); + pbuf->params.hist.hist_bins[2 * i + 1] = + RKISP1_CIF_ISP_HIST_GET_BIN1_V12(value); + } +} + +static void rkisp1_stats_get_bls_meas(struct rkisp1_stats *stats, + struct rkisp1_stat_buffer *pbuf) +{ + struct rkisp1_device *rkisp1 = stats->rkisp1; + const struct rkisp1_mbus_info *in_fmt = rkisp1->isp.sink_fmt; + struct rkisp1_cif_isp_bls_meas_val *bls_val; + + bls_val = &pbuf->params.ae.bls_val; + if (in_fmt->bayer_pat == RKISP1_RAW_BGGR) { + bls_val->meas_b = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_A_MEASURED); + bls_val->meas_gb = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_B_MEASURED); + bls_val->meas_gr = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_C_MEASURED); + bls_val->meas_r = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_D_MEASURED); + } else if (in_fmt->bayer_pat == RKISP1_RAW_GBRG) { + bls_val->meas_gb = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_A_MEASURED); + bls_val->meas_b = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_B_MEASURED); + bls_val->meas_r = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_C_MEASURED); + bls_val->meas_gr = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_D_MEASURED); + } else if (in_fmt->bayer_pat == RKISP1_RAW_GRBG) { + bls_val->meas_gr = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_A_MEASURED); + bls_val->meas_r = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_B_MEASURED); + bls_val->meas_b = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_C_MEASURED); + bls_val->meas_gb = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_D_MEASURED); + } else if (in_fmt->bayer_pat == RKISP1_RAW_RGGB) { + bls_val->meas_r = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_A_MEASURED); + bls_val->meas_gr = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_B_MEASURED); + bls_val->meas_gb = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_C_MEASURED); + bls_val->meas_b = + rkisp1_read(rkisp1, RKISP1_CIF_ISP_BLS_D_MEASURED); + } +} + +static const struct rkisp1_stats_ops rkisp1_v10_stats_ops = { + .get_awb_meas = rkisp1_stats_get_awb_meas_v10, + .get_aec_meas = rkisp1_stats_get_aec_meas_v10, + .get_hst_meas = rkisp1_stats_get_hst_meas_v10, +}; + +static struct rkisp1_stats_ops rkisp1_v12_stats_ops = { + .get_awb_meas = rkisp1_stats_get_awb_meas_v12, + .get_aec_meas = rkisp1_stats_get_aec_meas_v12, + .get_hst_meas = rkisp1_stats_get_hst_meas_v12, +}; + +static void +rkisp1_stats_send_measurement(struct rkisp1_stats *stats, u32 isp_ris) +{ + struct rkisp1_stat_buffer *cur_stat_buf; + struct rkisp1_buffer *cur_buf = NULL; + unsigned int frame_sequence = stats->rkisp1->isp.frame_sequence; + u64 timestamp = ktime_get_ns(); + + /* get one empty buffer */ + if (!list_empty(&stats->stat)) { + cur_buf = list_first_entry(&stats->stat, + struct rkisp1_buffer, queue); + list_del(&cur_buf->queue); + } + + if (!cur_buf) + return; + + cur_stat_buf = (struct rkisp1_stat_buffer *) + vb2_plane_vaddr(&cur_buf->vb.vb2_buf, 0); + if (isp_ris & RKISP1_CIF_ISP_AWB_DONE) + stats->ops->get_awb_meas(stats, cur_stat_buf); + + if (isp_ris & RKISP1_CIF_ISP_AFM_FIN) + rkisp1_stats_get_afc_meas(stats, cur_stat_buf); + + if (isp_ris & RKISP1_CIF_ISP_EXP_END) { + stats->ops->get_aec_meas(stats, cur_stat_buf); + rkisp1_stats_get_bls_meas(stats, cur_stat_buf); + } + + if (isp_ris & RKISP1_CIF_ISP_HIST_MEASURE_RDY) + stats->ops->get_hst_meas(stats, cur_stat_buf); + + vb2_set_plane_payload(&cur_buf->vb.vb2_buf, 0, + sizeof(struct rkisp1_stat_buffer)); + cur_buf->vb.sequence = frame_sequence; + cur_buf->vb.vb2_buf.timestamp = timestamp; + vb2_buffer_done(&cur_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); +} + +void rkisp1_stats_isr(struct rkisp1_stats *stats, u32 isp_ris) +{ + struct rkisp1_device *rkisp1 = stats->rkisp1; + unsigned int isp_mis_tmp = 0; + + spin_lock(&stats->lock); + + rkisp1_write(rkisp1, RKISP1_CIF_ISP_ICR, RKISP1_STATS_MEAS_MASK); + + isp_mis_tmp = rkisp1_read(rkisp1, RKISP1_CIF_ISP_MIS); + if (isp_mis_tmp & RKISP1_STATS_MEAS_MASK) + rkisp1->debug.stats_error++; + + if (isp_ris & RKISP1_STATS_MEAS_MASK) + rkisp1_stats_send_measurement(stats, isp_ris); + + spin_unlock(&stats->lock); +} + +static void rkisp1_init_stats(struct rkisp1_stats *stats) +{ + stats->vdev_fmt.fmt.meta.dataformat = + V4L2_META_FMT_RK_ISP1_STAT_3A; + stats->vdev_fmt.fmt.meta.buffersize = + sizeof(struct rkisp1_stat_buffer); + + if (stats->rkisp1->info->isp_ver == RKISP1_V12) + stats->ops = &rkisp1_v12_stats_ops; + else + stats->ops = &rkisp1_v10_stats_ops; +} + +int rkisp1_stats_register(struct rkisp1_device *rkisp1) +{ + struct rkisp1_stats *stats = &rkisp1->stats; + struct rkisp1_vdev_node *node = &stats->vnode; + struct video_device *vdev = &node->vdev; + int ret; + + stats->rkisp1 = rkisp1; + mutex_init(&node->vlock); + INIT_LIST_HEAD(&stats->stat); + spin_lock_init(&stats->lock); + + strscpy(vdev->name, RKISP1_STATS_DEV_NAME, sizeof(vdev->name)); + + video_set_drvdata(vdev, stats); + vdev->ioctl_ops = &rkisp1_stats_ioctl; + vdev->fops = &rkisp1_stats_fops; + vdev->release = video_device_release_empty; + vdev->lock = &node->vlock; + vdev->v4l2_dev = &rkisp1->v4l2_dev; + vdev->queue = &node->buf_queue; + vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING; + vdev->vfl_dir = VFL_DIR_RX; + rkisp1_stats_init_vb2_queue(vdev->queue, stats); + rkisp1_init_stats(stats); + video_set_drvdata(vdev, stats); + + node->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, &node->pad); + if (ret) + goto error; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(&vdev->dev, + "failed to register %s, ret=%d\n", vdev->name, ret); + goto error; + } + + return 0; + +error: + media_entity_cleanup(&vdev->entity); + mutex_destroy(&node->vlock); + stats->rkisp1 = NULL; + return ret; +} + +void rkisp1_stats_unregister(struct rkisp1_device *rkisp1) +{ + struct rkisp1_stats *stats = &rkisp1->stats; + struct rkisp1_vdev_node *node = &stats->vnode; + struct video_device *vdev = &node->vdev; + + if (!stats->rkisp1) + return; + + vb2_video_unregister_device(vdev); + media_entity_cleanup(&vdev->entity); + mutex_destroy(&node->vlock); +} diff --git a/drivers/media/platform/samsung/Kconfig b/drivers/media/platform/samsung/Kconfig new file mode 100644 index 000000000..0e34c5fc1 --- /dev/null +++ b/drivers/media/platform/samsung/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Samsung media platform drivers" + +source "drivers/media/platform/samsung/exynos-gsc/Kconfig" +source "drivers/media/platform/samsung/exynos4-is/Kconfig" +source "drivers/media/platform/samsung/s3c-camif/Kconfig" +source "drivers/media/platform/samsung/s5p-g2d/Kconfig" +source "drivers/media/platform/samsung/s5p-jpeg/Kconfig" +source "drivers/media/platform/samsung/s5p-mfc/Kconfig" diff --git a/drivers/media/platform/samsung/Makefile b/drivers/media/platform/samsung/Makefile new file mode 100644 index 000000000..21fea3330 --- /dev/null +++ b/drivers/media/platform/samsung/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += exynos-gsc/ +obj-y += exynos4-is/ +obj-y += s3c-camif/ +obj-y += s5p-g2d/ +obj-y += s5p-jpeg/ +obj-y += s5p-mfc/ diff --git a/drivers/media/platform/samsung/exynos-gsc/Kconfig b/drivers/media/platform/samsung/exynos-gsc/Kconfig new file mode 100644 index 000000000..7244d63c9 --- /dev/null +++ b/drivers/media/platform/samsung/exynos-gsc/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SAMSUNG_EXYNOS_GSC + tristate "Samsung Exynos G-Scaler driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_EXYNOS || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a v4l2 driver for Samsung EXYNOS5 SoC G-Scaler. diff --git a/drivers/media/platform/samsung/exynos-gsc/Makefile b/drivers/media/platform/samsung/exynos-gsc/Makefile new file mode 100644 index 000000000..bcefbad17 --- /dev/null +++ b/drivers/media/platform/samsung/exynos-gsc/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +exynos-gsc-objs := gsc-core.o gsc-m2m.o gsc-regs.o + +obj-$(CONFIG_VIDEO_SAMSUNG_EXYNOS_GSC) += exynos-gsc.o diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-core.c b/drivers/media/platform/samsung/exynos-gsc/gsc-core.c new file mode 100644 index 000000000..b147c645a --- /dev/null +++ b/drivers/media/platform/samsung/exynos-gsc/gsc-core.c @@ -0,0 +1,1326 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung EXYNOS5 SoC series G-Scaler driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gsc-core.h" + +static const struct gsc_fmt gsc_formats[] = { + { + .pixelformat = V4L2_PIX_FMT_RGB565X, + .depth = { 16 }, + .color = GSC_RGB, + .num_planes = 1, + .num_comp = 1, + }, { + .pixelformat = V4L2_PIX_FMT_BGR32, + .depth = { 32 }, + .color = GSC_RGB, + .num_planes = 1, + .num_comp = 1, + }, { + .pixelformat = V4L2_PIX_FMT_YUYV, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 1, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + }, { + .pixelformat = V4L2_PIX_FMT_UYVY, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_C, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 1, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + }, { + .pixelformat = V4L2_PIX_FMT_VYUY, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_C, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 1, + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + }, { + .pixelformat = V4L2_PIX_FMT_YVYU, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 1, + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + }, { + .pixelformat = V4L2_PIX_FMT_YUV32, + .depth = { 32 }, + .color = GSC_YUV444, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 1, + }, { + .pixelformat = V4L2_PIX_FMT_YUV422P, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 3, + }, { + .pixelformat = V4L2_PIX_FMT_NV16, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 2, + }, { + .pixelformat = V4L2_PIX_FMT_NV16M, + .depth = { 8, 8 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 2, + .num_comp = 2, + }, { + .pixelformat = V4L2_PIX_FMT_NV61, + .depth = { 16 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 2, + }, { + .pixelformat = V4L2_PIX_FMT_NV61M, + .depth = { 8, 8 }, + .color = GSC_YUV422, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 2, + .num_comp = 2, + }, { + .pixelformat = V4L2_PIX_FMT_YUV420, + .depth = { 12 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 3, + }, { + .pixelformat = V4L2_PIX_FMT_YVU420, + .depth = { 12 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 3, + + }, { + .pixelformat = V4L2_PIX_FMT_NV12, + .depth = { 12 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 1, + .num_comp = 2, + }, { + .pixelformat = V4L2_PIX_FMT_NV21, + .depth = { 12 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 1, + .num_comp = 2, + }, { + .pixelformat = V4L2_PIX_FMT_NV21M, + .depth = { 8, 4 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 2, + .num_comp = 2, + }, { + .pixelformat = V4L2_PIX_FMT_NV12M, + .depth = { 8, 4 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 2, + .num_comp = 2, + }, { + .pixelformat = V4L2_PIX_FMT_YUV420M, + .depth = { 8, 2, 2 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 3, + .num_comp = 3, + }, { + .pixelformat = V4L2_PIX_FMT_YVU420M, + .depth = { 8, 2, 2 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CRCB, + .num_planes = 3, + .num_comp = 3, + }, { + .pixelformat = V4L2_PIX_FMT_NV12MT_16X16, + .depth = { 8, 4 }, + .color = GSC_YUV420, + .yorder = GSC_LSB_Y, + .corder = GSC_CBCR, + .num_planes = 2, + .num_comp = 2, + } +}; + +const struct gsc_fmt *get_format(int index) +{ + if (index >= ARRAY_SIZE(gsc_formats)) + return NULL; + + return (struct gsc_fmt *)&gsc_formats[index]; +} + +const struct gsc_fmt *find_fmt(u32 *pixelformat, u32 *mbus_code, u32 index) +{ + const struct gsc_fmt *fmt, *def_fmt = NULL; + unsigned int i; + + if (index >= ARRAY_SIZE(gsc_formats)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(gsc_formats); ++i) { + fmt = get_format(i); + if (pixelformat && fmt->pixelformat == *pixelformat) + return fmt; + if (mbus_code && fmt->mbus_code == *mbus_code) + return fmt; + if (index == i) + def_fmt = fmt; + } + return def_fmt; + +} + +void gsc_set_frame_size(struct gsc_frame *frame, int width, int height) +{ + frame->f_width = width; + frame->f_height = height; + frame->crop.width = width; + frame->crop.height = height; + frame->crop.left = 0; + frame->crop.top = 0; +} + +int gsc_cal_prescaler_ratio(struct gsc_variant *var, u32 src, u32 dst, + u32 *ratio) +{ + if ((dst > src) || (dst >= src / var->poly_sc_down_max)) { + *ratio = 1; + return 0; + } + + if ((src / var->poly_sc_down_max / var->pre_sc_down_max) > dst) { + pr_err("Exceeded maximum downscaling ratio (1/16))"); + return -EINVAL; + } + + *ratio = (dst > (src / 8)) ? 2 : 4; + + return 0; +} + +void gsc_get_prescaler_shfactor(u32 hratio, u32 vratio, u32 *sh) +{ + if (hratio == 4 && vratio == 4) + *sh = 4; + else if ((hratio == 4 && vratio == 2) || + (hratio == 2 && vratio == 4)) + *sh = 3; + else if ((hratio == 4 && vratio == 1) || + (hratio == 1 && vratio == 4) || + (hratio == 2 && vratio == 2)) + *sh = 2; + else if (hratio == 1 && vratio == 1) + *sh = 0; + else + *sh = 1; +} + +void gsc_check_src_scale_info(struct gsc_variant *var, + struct gsc_frame *s_frame, u32 *wratio, + u32 tx, u32 ty, u32 *hratio) +{ + int remainder = 0, walign, halign; + + if (is_yuv420(s_frame->fmt->color)) { + walign = GSC_SC_ALIGN_4; + halign = GSC_SC_ALIGN_4; + } else if (is_yuv422(s_frame->fmt->color)) { + walign = GSC_SC_ALIGN_4; + halign = GSC_SC_ALIGN_2; + } else { + walign = GSC_SC_ALIGN_2; + halign = GSC_SC_ALIGN_2; + } + + remainder = s_frame->crop.width % (*wratio * walign); + if (remainder) { + s_frame->crop.width -= remainder; + gsc_cal_prescaler_ratio(var, s_frame->crop.width, tx, wratio); + pr_info("cropped src width size is recalculated from %d to %d", + s_frame->crop.width + remainder, s_frame->crop.width); + } + + remainder = s_frame->crop.height % (*hratio * halign); + if (remainder) { + s_frame->crop.height -= remainder; + gsc_cal_prescaler_ratio(var, s_frame->crop.height, ty, hratio); + pr_info("cropped src height size is recalculated from %d to %d", + s_frame->crop.height + remainder, s_frame->crop.height); + } +} + +int gsc_enum_fmt(struct v4l2_fmtdesc *f) +{ + const struct gsc_fmt *fmt; + + fmt = find_fmt(NULL, NULL, f->index); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixelformat; + + return 0; +} + +static int get_plane_info(struct gsc_frame *frm, u32 addr, u32 *index, u32 *ret_addr) +{ + if (frm->addr.y == addr) { + *index = 0; + *ret_addr = frm->addr.y; + } else if (frm->addr.cb == addr) { + *index = 1; + *ret_addr = frm->addr.cb; + } else if (frm->addr.cr == addr) { + *index = 2; + *ret_addr = frm->addr.cr; + } else { + pr_err("Plane address is wrong"); + return -EINVAL; + } + return 0; +} + +void gsc_set_prefbuf(struct gsc_dev *gsc, struct gsc_frame *frm) +{ + u32 f_chk_addr, f_chk_len, s_chk_addr = 0, s_chk_len = 0; + + f_chk_addr = frm->addr.y; + f_chk_len = frm->payload[0]; + if (frm->fmt->num_planes == 2) { + s_chk_addr = frm->addr.cb; + s_chk_len = frm->payload[1]; + } else if (frm->fmt->num_planes == 3) { + u32 low_addr, low_plane, mid_addr, mid_plane; + u32 high_addr, high_plane; + u32 t_min, t_max; + + t_min = min3(frm->addr.y, frm->addr.cb, frm->addr.cr); + if (get_plane_info(frm, t_min, &low_plane, &low_addr)) + return; + t_max = max3(frm->addr.y, frm->addr.cb, frm->addr.cr); + if (get_plane_info(frm, t_max, &high_plane, &high_addr)) + return; + + mid_plane = 3 - (low_plane + high_plane); + if (mid_plane == 0) + mid_addr = frm->addr.y; + else if (mid_plane == 1) + mid_addr = frm->addr.cb; + else if (mid_plane == 2) + mid_addr = frm->addr.cr; + else + return; + + f_chk_addr = low_addr; + if (mid_addr + frm->payload[mid_plane] - low_addr > + high_addr + frm->payload[high_plane] - mid_addr) { + f_chk_len = frm->payload[low_plane]; + s_chk_addr = mid_addr; + s_chk_len = high_addr + + frm->payload[high_plane] - mid_addr; + } else { + f_chk_len = mid_addr + + frm->payload[mid_plane] - low_addr; + s_chk_addr = high_addr; + s_chk_len = frm->payload[high_plane]; + } + } + pr_debug("f_addr = 0x%08x, f_len = %d, s_addr = 0x%08x, s_len = %d\n", + f_chk_addr, f_chk_len, s_chk_addr, s_chk_len); +} + +int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) +{ + struct gsc_dev *gsc = ctx->gsc_dev; + struct gsc_variant *variant = gsc->variant; + struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp; + const struct gsc_fmt *fmt; + u32 max_w, max_h, mod_x, mod_y; + u32 min_w, min_h, tmp_w, tmp_h; + int i; + + pr_debug("user put w: %d, h: %d", pix_mp->width, pix_mp->height); + + fmt = find_fmt(&pix_mp->pixelformat, NULL, 0); + if (!fmt) { + pr_err("pixelformat format (0x%X) invalid\n", + pix_mp->pixelformat); + return -EINVAL; + } + + if (pix_mp->field == V4L2_FIELD_ANY) + pix_mp->field = V4L2_FIELD_NONE; + else if (pix_mp->field != V4L2_FIELD_NONE) { + pr_debug("Not supported field order(%d)\n", pix_mp->field); + return -EINVAL; + } + + max_w = variant->pix_max->target_rot_dis_w; + max_h = variant->pix_max->target_rot_dis_h; + + mod_x = ffs(variant->pix_align->org_w) - 1; + if (is_yuv420(fmt->color)) + mod_y = ffs(variant->pix_align->org_h) - 1; + else + mod_y = ffs(variant->pix_align->org_h) - 2; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) { + min_w = variant->pix_min->org_w; + min_h = variant->pix_min->org_h; + } else { + min_w = variant->pix_min->target_rot_dis_w; + min_h = variant->pix_min->target_rot_dis_h; + pix_mp->colorspace = ctx->out_colorspace; + } + + pr_debug("mod_x: %d, mod_y: %d, max_w: %d, max_h = %d", + mod_x, mod_y, max_w, max_h); + + /* To check if image size is modified to adjust parameter against + hardware abilities */ + tmp_w = pix_mp->width; + tmp_h = pix_mp->height; + + v4l_bound_align_image(&pix_mp->width, min_w, max_w, mod_x, + &pix_mp->height, min_h, max_h, mod_y, 0); + if (tmp_w != pix_mp->width || tmp_h != pix_mp->height) + pr_debug("Image size has been modified from %dx%d to %dx%d\n", + tmp_w, tmp_h, pix_mp->width, pix_mp->height); + + pix_mp->num_planes = fmt->num_planes; + + if (V4L2_TYPE_IS_OUTPUT(f->type)) + ctx->out_colorspace = pix_mp->colorspace; + + for (i = 0; i < pix_mp->num_planes; ++i) { + struct v4l2_plane_pix_format *plane_fmt = &pix_mp->plane_fmt[i]; + u32 bpl = plane_fmt->bytesperline; + + if (fmt->num_comp == 1 && /* Packed */ + (bpl == 0 || (bpl * 8 / fmt->depth[i]) < pix_mp->width)) + bpl = pix_mp->width * fmt->depth[i] / 8; + + if (fmt->num_comp > 1 && /* Planar */ + (bpl == 0 || bpl < pix_mp->width)) + bpl = pix_mp->width; + + if (i != 0 && fmt->num_comp == 3) + bpl /= 2; + + plane_fmt->bytesperline = bpl; + plane_fmt->sizeimage = max(pix_mp->width * pix_mp->height * + fmt->depth[i] / 8, + plane_fmt->sizeimage); + pr_debug("[%d]: bpl: %d, sizeimage: %d", + i, bpl, pix_mp->plane_fmt[i].sizeimage); + } + + return 0; +} + +int gsc_g_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f) +{ + struct gsc_frame *frame; + struct v4l2_pix_format_mplane *pix_mp; + int i; + + frame = ctx_get_frame(ctx, f->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + pix_mp = &f->fmt.pix_mp; + + pix_mp->width = frame->f_width; + pix_mp->height = frame->f_height; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->pixelformat = frame->fmt->pixelformat; + pix_mp->num_planes = frame->fmt->num_planes; + pix_mp->colorspace = ctx->out_colorspace; + + for (i = 0; i < pix_mp->num_planes; ++i) { + pix_mp->plane_fmt[i].bytesperline = (frame->f_width * + frame->fmt->depth[i]) / 8; + pix_mp->plane_fmt[i].sizeimage = + pix_mp->plane_fmt[i].bytesperline * frame->f_height; + } + + return 0; +} + +void gsc_check_crop_change(u32 tmp_w, u32 tmp_h, u32 *w, u32 *h) +{ + if (tmp_w != *w || tmp_h != *h) { + pr_info("Cropped size has been modified from %dx%d to %dx%d", + *w, *h, tmp_w, tmp_h); + *w = tmp_w; + *h = tmp_h; + } +} + +int gsc_try_selection(struct gsc_ctx *ctx, struct v4l2_selection *s) +{ + struct gsc_frame *f; + struct gsc_dev *gsc = ctx->gsc_dev; + struct gsc_variant *variant = gsc->variant; + u32 mod_x = 0, mod_y = 0, tmp_w, tmp_h; + u32 min_w, min_h, max_w, max_h; + + if (s->r.top < 0 || s->r.left < 0) { + pr_err("doesn't support negative values for top & left\n"); + return -EINVAL; + } + pr_debug("user put w: %d, h: %d", s->r.width, s->r.height); + + if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + f = &ctx->d_frame; + else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + f = &ctx->s_frame; + else + return -EINVAL; + + max_w = f->f_width; + max_h = f->f_height; + tmp_w = s->r.width; + tmp_h = s->r.height; + + if (V4L2_TYPE_IS_OUTPUT(s->type)) { + if ((is_yuv422(f->fmt->color) && f->fmt->num_comp == 1) || + is_rgb(f->fmt->color)) + min_w = 32; + else + min_w = 64; + if ((is_yuv422(f->fmt->color) && f->fmt->num_comp == 3) || + is_yuv420(f->fmt->color)) + min_h = 32; + else + min_h = 16; + } else { + if (is_yuv420(f->fmt->color) || is_yuv422(f->fmt->color)) + mod_x = ffs(variant->pix_align->target_w) - 1; + if (is_yuv420(f->fmt->color)) + mod_y = ffs(variant->pix_align->target_h) - 1; + if (ctx->gsc_ctrls.rotate->val == 90 || + ctx->gsc_ctrls.rotate->val == 270) { + max_w = f->f_height; + max_h = f->f_width; + min_w = variant->pix_min->target_rot_en_w; + min_h = variant->pix_min->target_rot_en_h; + tmp_w = s->r.height; + tmp_h = s->r.width; + } else { + min_w = variant->pix_min->target_rot_dis_w; + min_h = variant->pix_min->target_rot_dis_h; + } + } + pr_debug("mod_x: %d, mod_y: %d, min_w: %d, min_h = %d", + mod_x, mod_y, min_w, min_h); + pr_debug("tmp_w : %d, tmp_h : %d", tmp_w, tmp_h); + + v4l_bound_align_image(&tmp_w, min_w, max_w, mod_x, + &tmp_h, min_h, max_h, mod_y, 0); + + if (V4L2_TYPE_IS_CAPTURE(s->type) && + (ctx->gsc_ctrls.rotate->val == 90 || + ctx->gsc_ctrls.rotate->val == 270)) + gsc_check_crop_change(tmp_h, tmp_w, + &s->r.width, &s->r.height); + else + gsc_check_crop_change(tmp_w, tmp_h, + &s->r.width, &s->r.height); + + + /* adjust left/top if cropping rectangle is out of bounds */ + /* Need to add code to algin left value with 2's multiple */ + if (s->r.left + tmp_w > max_w) + s->r.left = max_w - tmp_w; + if (s->r.top + tmp_h > max_h) + s->r.top = max_h - tmp_h; + + if ((is_yuv420(f->fmt->color) || is_yuv422(f->fmt->color)) && + s->r.left & 1) + s->r.left -= 1; + + pr_debug("Aligned l:%d, t:%d, w:%d, h:%d, f_w: %d, f_h: %d", + s->r.left, s->r.top, s->r.width, s->r.height, max_w, max_h); + + return 0; +} + +int gsc_check_scaler_ratio(struct gsc_variant *var, int sw, int sh, int dw, + int dh, int rot, int out_path) +{ + int tmp_w, tmp_h, sc_down_max; + + if (out_path == GSC_DMA) + sc_down_max = var->sc_down_max; + else + sc_down_max = var->local_sc_down; + + if (rot == 90 || rot == 270) { + tmp_w = dh; + tmp_h = dw; + } else { + tmp_w = dw; + tmp_h = dh; + } + + if ((sw / tmp_w) > sc_down_max || + (sh / tmp_h) > sc_down_max || + (tmp_w / sw) > var->sc_up_max || + (tmp_h / sh) > var->sc_up_max) + return -EINVAL; + + return 0; +} + +int gsc_set_scaler_info(struct gsc_ctx *ctx) +{ + struct gsc_scaler *sc = &ctx->scaler; + struct gsc_frame *s_frame = &ctx->s_frame; + struct gsc_frame *d_frame = &ctx->d_frame; + struct gsc_variant *variant = ctx->gsc_dev->variant; + struct device *dev = &ctx->gsc_dev->pdev->dev; + int tx, ty; + int ret; + + ret = gsc_check_scaler_ratio(variant, s_frame->crop.width, + s_frame->crop.height, d_frame->crop.width, d_frame->crop.height, + ctx->gsc_ctrls.rotate->val, ctx->out_path); + if (ret) { + pr_err("out of scaler range"); + return ret; + } + + if (ctx->gsc_ctrls.rotate->val == 90 || + ctx->gsc_ctrls.rotate->val == 270) { + ty = d_frame->crop.width; + tx = d_frame->crop.height; + } else { + tx = d_frame->crop.width; + ty = d_frame->crop.height; + } + + if (tx <= 0 || ty <= 0) { + dev_err(dev, "Invalid target size: %dx%d", tx, ty); + return -EINVAL; + } + + ret = gsc_cal_prescaler_ratio(variant, s_frame->crop.width, + tx, &sc->pre_hratio); + if (ret) { + pr_err("Horizontal scale ratio is out of range"); + return ret; + } + + ret = gsc_cal_prescaler_ratio(variant, s_frame->crop.height, + ty, &sc->pre_vratio); + if (ret) { + pr_err("Vertical scale ratio is out of range"); + return ret; + } + + gsc_check_src_scale_info(variant, s_frame, &sc->pre_hratio, + tx, ty, &sc->pre_vratio); + + gsc_get_prescaler_shfactor(sc->pre_hratio, sc->pre_vratio, + &sc->pre_shfactor); + + sc->main_hratio = (s_frame->crop.width << 16) / tx; + sc->main_vratio = (s_frame->crop.height << 16) / ty; + + pr_debug("scaler input/output size : sx = %d, sy = %d, tx = %d, ty = %d", + s_frame->crop.width, s_frame->crop.height, tx, ty); + pr_debug("scaler ratio info : pre_shfactor : %d, pre_h : %d", + sc->pre_shfactor, sc->pre_hratio); + pr_debug("pre_v :%d, main_h : %d, main_v : %d", + sc->pre_vratio, sc->main_hratio, sc->main_vratio); + + return 0; +} + +static int __gsc_s_ctrl(struct gsc_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + struct gsc_dev *gsc = ctx->gsc_dev; + struct gsc_variant *variant = gsc->variant; + unsigned int flags = GSC_DST_FMT | GSC_SRC_FMT; + int ret = 0; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->hflip = ctrl->val; + break; + + case V4L2_CID_VFLIP: + ctx->vflip = ctrl->val; + break; + + case V4L2_CID_ROTATE: + if ((ctx->state & flags) == flags) { + ret = gsc_check_scaler_ratio(variant, + ctx->s_frame.crop.width, + ctx->s_frame.crop.height, + ctx->d_frame.crop.width, + ctx->d_frame.crop.height, + ctx->gsc_ctrls.rotate->val, + ctx->out_path); + + if (ret) + return -EINVAL; + } + + ctx->rotation = ctrl->val; + break; + + case V4L2_CID_ALPHA_COMPONENT: + ctx->d_frame.alpha = ctrl->val; + break; + } + + ctx->state |= GSC_PARAMS; + return 0; +} + +static int gsc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct gsc_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ctx->gsc_dev->slock, flags); + ret = __gsc_s_ctrl(ctx, ctrl); + spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags); + + return ret; +} + +static const struct v4l2_ctrl_ops gsc_ctrl_ops = { + .s_ctrl = gsc_s_ctrl, +}; + +int gsc_ctrls_create(struct gsc_ctx *ctx) +{ + if (ctx->ctrls_rdy) { + pr_err("Control handler of this context was created already"); + return 0; + } + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, GSC_MAX_CTRL_NUM); + + ctx->gsc_ctrls.rotate = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &gsc_ctrl_ops, V4L2_CID_ROTATE, 0, 270, 90, 0); + ctx->gsc_ctrls.hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &gsc_ctrl_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + ctx->gsc_ctrls.vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &gsc_ctrl_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + ctx->gsc_ctrls.global_alpha = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &gsc_ctrl_ops, V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0); + + ctx->ctrls_rdy = ctx->ctrl_handler.error == 0; + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + pr_err("Failed to create G-Scaler control handlers"); + return err; + } + + return 0; +} + +void gsc_ctrls_delete(struct gsc_ctx *ctx) +{ + if (ctx->ctrls_rdy) { + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + ctx->ctrls_rdy = false; + } +} + +/* The color format (num_comp, num_planes) must be already configured. */ +int gsc_prepare_addr(struct gsc_ctx *ctx, struct vb2_buffer *vb, + struct gsc_frame *frame, struct gsc_addr *addr) +{ + int ret = 0; + u32 pix_size; + + if ((vb == NULL) || (frame == NULL)) + return -EINVAL; + + pix_size = frame->f_width * frame->f_height; + + pr_debug("num_planes= %d, num_comp= %d, pix_size= %d", + frame->fmt->num_planes, frame->fmt->num_comp, pix_size); + + addr->y = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (frame->fmt->num_planes == 1) { + switch (frame->fmt->num_comp) { + case 1: + addr->cb = 0; + addr->cr = 0; + break; + case 2: + /* decompose Y into Y/Cb */ + addr->cb = (dma_addr_t)(addr->y + pix_size); + addr->cr = 0; + break; + case 3: + /* decompose Y into Y/Cb/Cr */ + addr->cb = (dma_addr_t)(addr->y + pix_size); + if (GSC_YUV420 == frame->fmt->color) + addr->cr = (dma_addr_t)(addr->cb + + (pix_size >> 2)); + else /* 422 */ + addr->cr = (dma_addr_t)(addr->cb + + (pix_size >> 1)); + break; + default: + pr_err("Invalid the number of color planes"); + return -EINVAL; + } + } else { + if (frame->fmt->num_planes >= 2) + addr->cb = vb2_dma_contig_plane_dma_addr(vb, 1); + + if (frame->fmt->num_planes == 3) + addr->cr = vb2_dma_contig_plane_dma_addr(vb, 2); + } + + if ((frame->fmt->pixelformat == V4L2_PIX_FMT_VYUY) || + (frame->fmt->pixelformat == V4L2_PIX_FMT_YVYU) || + (frame->fmt->pixelformat == V4L2_PIX_FMT_YVU420) || + (frame->fmt->pixelformat == V4L2_PIX_FMT_YVU420M)) + swap(addr->cb, addr->cr); + + pr_debug("ADDR: y= %pad cb= %pad cr= %pad ret= %d", + &addr->y, &addr->cb, &addr->cr, ret); + + return ret; +} + +static irqreturn_t gsc_irq_handler(int irq, void *priv) +{ + struct gsc_dev *gsc = priv; + struct gsc_ctx *ctx; + int gsc_irq; + + gsc_irq = gsc_hw_get_irq_status(gsc); + gsc_hw_clear_irq(gsc, gsc_irq); + + if (gsc_irq == GSC_IRQ_OVERRUN) { + pr_err("Local path input over-run interrupt has occurred!\n"); + return IRQ_HANDLED; + } + + spin_lock(&gsc->slock); + + if (test_and_clear_bit(ST_M2M_PEND, &gsc->state)) { + + gsc_hw_enable_control(gsc, false); + + if (test_and_clear_bit(ST_M2M_SUSPENDING, &gsc->state)) { + set_bit(ST_M2M_SUSPENDED, &gsc->state); + wake_up(&gsc->irq_queue); + goto isr_unlock; + } + ctx = v4l2_m2m_get_curr_priv(gsc->m2m.m2m_dev); + + if (!ctx || !ctx->m2m_ctx) + goto isr_unlock; + + spin_unlock(&gsc->slock); + gsc_m2m_job_finish(ctx, VB2_BUF_STATE_DONE); + + /* wake_up job_abort, stop_streaming */ + if (ctx->state & GSC_CTX_STOP_REQ) { + ctx->state &= ~GSC_CTX_STOP_REQ; + wake_up(&gsc->irq_queue); + } + return IRQ_HANDLED; + } + +isr_unlock: + spin_unlock(&gsc->slock); + return IRQ_HANDLED; +} + +static struct gsc_pix_max gsc_v_100_max = { + .org_scaler_bypass_w = 8192, + .org_scaler_bypass_h = 8192, + .org_scaler_input_w = 4800, + .org_scaler_input_h = 3344, + .real_rot_dis_w = 4800, + .real_rot_dis_h = 3344, + .real_rot_en_w = 2047, + .real_rot_en_h = 2047, + .target_rot_dis_w = 4800, + .target_rot_dis_h = 3344, + .target_rot_en_w = 2016, + .target_rot_en_h = 2016, +}; + +static struct gsc_pix_max gsc_v_5250_max = { + .org_scaler_bypass_w = 8192, + .org_scaler_bypass_h = 8192, + .org_scaler_input_w = 4800, + .org_scaler_input_h = 3344, + .real_rot_dis_w = 4800, + .real_rot_dis_h = 3344, + .real_rot_en_w = 2016, + .real_rot_en_h = 2016, + .target_rot_dis_w = 4800, + .target_rot_dis_h = 3344, + .target_rot_en_w = 2016, + .target_rot_en_h = 2016, +}; + +static struct gsc_pix_max gsc_v_5420_max = { + .org_scaler_bypass_w = 8192, + .org_scaler_bypass_h = 8192, + .org_scaler_input_w = 4800, + .org_scaler_input_h = 3344, + .real_rot_dis_w = 4800, + .real_rot_dis_h = 3344, + .real_rot_en_w = 2048, + .real_rot_en_h = 2048, + .target_rot_dis_w = 4800, + .target_rot_dis_h = 3344, + .target_rot_en_w = 2016, + .target_rot_en_h = 2016, +}; + +static struct gsc_pix_max gsc_v_5433_max = { + .org_scaler_bypass_w = 8192, + .org_scaler_bypass_h = 8192, + .org_scaler_input_w = 4800, + .org_scaler_input_h = 3344, + .real_rot_dis_w = 4800, + .real_rot_dis_h = 3344, + .real_rot_en_w = 2047, + .real_rot_en_h = 2047, + .target_rot_dis_w = 4800, + .target_rot_dis_h = 3344, + .target_rot_en_w = 2016, + .target_rot_en_h = 2016, +}; + +static struct gsc_pix_min gsc_v_100_min = { + .org_w = 64, + .org_h = 32, + .real_w = 64, + .real_h = 32, + .target_rot_dis_w = 64, + .target_rot_dis_h = 32, + .target_rot_en_w = 32, + .target_rot_en_h = 16, +}; + +static struct gsc_pix_align gsc_v_100_align = { + .org_h = 16, + .org_w = 16, /* yuv420 : 16, others : 8 */ + .offset_h = 2, /* yuv420/422 : 2, others : 1 */ + .real_w = 16, /* yuv420/422 : 4~16, others : 2~8 */ + .real_h = 16, /* yuv420 : 4~16, others : 1 */ + .target_w = 2, /* yuv420/422 : 2, others : 1 */ + .target_h = 2, /* yuv420 : 2, others : 1 */ +}; + +static struct gsc_variant gsc_v_100_variant = { + .pix_max = &gsc_v_100_max, + .pix_min = &gsc_v_100_min, + .pix_align = &gsc_v_100_align, + .in_buf_cnt = 32, + .out_buf_cnt = 32, + .sc_up_max = 8, + .sc_down_max = 16, + .poly_sc_down_max = 4, + .pre_sc_down_max = 4, + .local_sc_down = 2, +}; + +static struct gsc_variant gsc_v_5250_variant = { + .pix_max = &gsc_v_5250_max, + .pix_min = &gsc_v_100_min, + .pix_align = &gsc_v_100_align, + .in_buf_cnt = 32, + .out_buf_cnt = 32, + .sc_up_max = 8, + .sc_down_max = 16, + .poly_sc_down_max = 4, + .pre_sc_down_max = 4, + .local_sc_down = 2, +}; + +static struct gsc_variant gsc_v_5420_variant = { + .pix_max = &gsc_v_5420_max, + .pix_min = &gsc_v_100_min, + .pix_align = &gsc_v_100_align, + .in_buf_cnt = 32, + .out_buf_cnt = 32, + .sc_up_max = 8, + .sc_down_max = 16, + .poly_sc_down_max = 4, + .pre_sc_down_max = 4, + .local_sc_down = 2, +}; + +static struct gsc_variant gsc_v_5433_variant = { + .pix_max = &gsc_v_5433_max, + .pix_min = &gsc_v_100_min, + .pix_align = &gsc_v_100_align, + .in_buf_cnt = 32, + .out_buf_cnt = 32, + .sc_up_max = 8, + .sc_down_max = 16, + .poly_sc_down_max = 4, + .pre_sc_down_max = 4, + .local_sc_down = 2, +}; + +static struct gsc_driverdata gsc_v_100_drvdata = { + .variant = { + [0] = &gsc_v_100_variant, + [1] = &gsc_v_100_variant, + [2] = &gsc_v_100_variant, + [3] = &gsc_v_100_variant, + }, + .num_entities = 4, + .clk_names = { "gscl" }, + .num_clocks = 1, +}; + +static struct gsc_driverdata gsc_v_5250_drvdata = { + .variant = { + [0] = &gsc_v_5250_variant, + [1] = &gsc_v_5250_variant, + [2] = &gsc_v_5250_variant, + [3] = &gsc_v_5250_variant, + }, + .num_entities = 4, + .clk_names = { "gscl" }, + .num_clocks = 1, +}; + +static struct gsc_driverdata gsc_v_5420_drvdata = { + .variant = { + [0] = &gsc_v_5420_variant, + [1] = &gsc_v_5420_variant, + }, + .num_entities = 2, + .clk_names = { "gscl" }, + .num_clocks = 1, +}; + +static struct gsc_driverdata gsc_5433_drvdata = { + .variant = { + [0] = &gsc_v_5433_variant, + [1] = &gsc_v_5433_variant, + [2] = &gsc_v_5433_variant, + }, + .num_entities = 3, + .clk_names = { "pclk", "aclk", "aclk_xiu", "aclk_gsclbend" }, + .num_clocks = 4, +}; + +static const struct of_device_id exynos_gsc_match[] = { + { + .compatible = "samsung,exynos5250-gsc", + .data = &gsc_v_5250_drvdata, + }, + { + .compatible = "samsung,exynos5420-gsc", + .data = &gsc_v_5420_drvdata, + }, + { + .compatible = "samsung,exynos5433-gsc", + .data = &gsc_5433_drvdata, + }, + { + .compatible = "samsung,exynos5-gsc", + .data = &gsc_v_100_drvdata, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_gsc_match); + +static int gsc_probe(struct platform_device *pdev) +{ + struct gsc_dev *gsc; + struct device *dev = &pdev->dev; + const struct gsc_driverdata *drv_data = of_device_get_match_data(dev); + int irq; + int ret; + int i; + + gsc = devm_kzalloc(dev, sizeof(struct gsc_dev), GFP_KERNEL); + if (!gsc) + return -ENOMEM; + + ret = of_alias_get_id(pdev->dev.of_node, "gsc"); + if (ret < 0) + return ret; + + if (drv_data == &gsc_v_100_drvdata) + dev_info(dev, "compatible 'exynos5-gsc' is deprecated\n"); + + gsc->id = ret; + if (gsc->id >= drv_data->num_entities) { + dev_err(dev, "Invalid platform device id: %d\n", gsc->id); + return -EINVAL; + } + + gsc->num_clocks = drv_data->num_clocks; + gsc->variant = drv_data->variant[gsc->id]; + gsc->pdev = pdev; + + init_waitqueue_head(&gsc->irq_queue); + spin_lock_init(&gsc->slock); + mutex_init(&gsc->lock); + + gsc->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(gsc->regs)) + return PTR_ERR(gsc->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + for (i = 0; i < gsc->num_clocks; i++) { + gsc->clock[i] = devm_clk_get(dev, drv_data->clk_names[i]); + if (IS_ERR(gsc->clock[i])) { + dev_err(dev, "failed to get clock: %s\n", + drv_data->clk_names[i]); + return PTR_ERR(gsc->clock[i]); + } + } + + for (i = 0; i < gsc->num_clocks; i++) { + ret = clk_prepare_enable(gsc->clock[i]); + if (ret) { + dev_err(dev, "clock prepare failed for clock: %s\n", + drv_data->clk_names[i]); + while (--i >= 0) + clk_disable_unprepare(gsc->clock[i]); + return ret; + } + } + + ret = devm_request_irq(dev, irq, gsc_irq_handler, + 0, pdev->name, gsc); + if (ret) { + dev_err(dev, "failed to install irq (%d)\n", ret); + goto err_clk; + } + + ret = v4l2_device_register(dev, &gsc->v4l2_dev); + if (ret) + goto err_clk; + + ret = gsc_register_m2m_device(gsc); + if (ret) + goto err_v4l2; + + platform_set_drvdata(pdev, gsc); + + gsc_hw_set_sw_reset(gsc); + gsc_wait_reset(gsc); + + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); + + dev_dbg(dev, "gsc-%d registered successfully\n", gsc->id); + + pm_runtime_set_active(dev); + pm_runtime_enable(dev); + + return 0; + +err_v4l2: + v4l2_device_unregister(&gsc->v4l2_dev); +err_clk: + for (i = gsc->num_clocks - 1; i >= 0; i--) + clk_disable_unprepare(gsc->clock[i]); + return ret; +} + +static int gsc_remove(struct platform_device *pdev) +{ + struct gsc_dev *gsc = platform_get_drvdata(pdev); + int i; + + gsc_unregister_m2m_device(gsc); + v4l2_device_unregister(&gsc->v4l2_dev); + + vb2_dma_contig_clear_max_seg_size(&pdev->dev); + + pm_runtime_disable(&pdev->dev); + + if (!pm_runtime_status_suspended(&pdev->dev)) + for (i = 0; i < gsc->num_clocks; i++) + clk_disable_unprepare(gsc->clock[i]); + + pm_runtime_set_suspended(&pdev->dev); + + dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name); + return 0; +} + +#ifdef CONFIG_PM +static int gsc_m2m_suspend(struct gsc_dev *gsc) +{ + unsigned long flags; + int timeout; + + spin_lock_irqsave(&gsc->slock, flags); + if (!gsc_m2m_pending(gsc)) { + spin_unlock_irqrestore(&gsc->slock, flags); + return 0; + } + clear_bit(ST_M2M_SUSPENDED, &gsc->state); + set_bit(ST_M2M_SUSPENDING, &gsc->state); + spin_unlock_irqrestore(&gsc->slock, flags); + + timeout = wait_event_timeout(gsc->irq_queue, + test_bit(ST_M2M_SUSPENDED, &gsc->state), + GSC_SHUTDOWN_TIMEOUT); + + clear_bit(ST_M2M_SUSPENDING, &gsc->state); + return timeout == 0 ? -EAGAIN : 0; +} + +static void gsc_m2m_resume(struct gsc_dev *gsc) +{ + struct gsc_ctx *ctx; + unsigned long flags; + + spin_lock_irqsave(&gsc->slock, flags); + /* Clear for full H/W setup in first run after resume */ + ctx = gsc->m2m.ctx; + gsc->m2m.ctx = NULL; + spin_unlock_irqrestore(&gsc->slock, flags); + + if (test_and_clear_bit(ST_M2M_SUSPENDED, &gsc->state)) + gsc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); +} + +static int gsc_runtime_resume(struct device *dev) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + int ret = 0; + int i; + + pr_debug("gsc%d: state: 0x%lx\n", gsc->id, gsc->state); + + for (i = 0; i < gsc->num_clocks; i++) { + ret = clk_prepare_enable(gsc->clock[i]); + if (ret) { + while (--i >= 0) + clk_disable_unprepare(gsc->clock[i]); + return ret; + } + } + + gsc_hw_set_sw_reset(gsc); + gsc_wait_reset(gsc); + gsc_m2m_resume(gsc); + + return 0; +} + +static int gsc_runtime_suspend(struct device *dev) +{ + struct gsc_dev *gsc = dev_get_drvdata(dev); + int ret = 0; + int i; + + ret = gsc_m2m_suspend(gsc); + if (ret) + return ret; + + for (i = gsc->num_clocks - 1; i >= 0; i--) + clk_disable_unprepare(gsc->clock[i]); + + pr_debug("gsc%d: state: 0x%lx\n", gsc->id, gsc->state); + return ret; +} +#endif + +static const struct dev_pm_ops gsc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(gsc_runtime_suspend, gsc_runtime_resume, NULL) +}; + +static struct platform_driver gsc_driver = { + .probe = gsc_probe, + .remove = gsc_remove, + .driver = { + .name = GSC_MODULE_NAME, + .pm = &gsc_pm_ops, + .of_match_table = exynos_gsc_match, + } +}; + +module_platform_driver(gsc_driver); + +MODULE_AUTHOR("Hyunwong Kim "); +MODULE_DESCRIPTION("Samsung EXYNOS5 Soc series G-Scaler driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-core.h b/drivers/media/platform/samsung/exynos-gsc/gsc-core.h new file mode 100644 index 000000000..1ea5fa1bf --- /dev/null +++ b/drivers/media/platform/samsung/exynos-gsc/gsc-core.h @@ -0,0 +1,521 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * header file for Samsung EXYNOS5 SoC series G-Scaler driver + + */ + +#ifndef GSC_CORE_H_ +#define GSC_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gsc-regs.h" + +#define CONFIG_VB2_GSC_DMA_CONTIG 1 +#define GSC_MODULE_NAME "exynos-gsc" + +#define GSC_SHUTDOWN_TIMEOUT ((100*HZ)/1000) +#define GSC_MAX_DEVS 4 +#define GSC_MAX_CLOCKS 4 +#define GSC_M2M_BUF_NUM 0 +#define GSC_MAX_CTRL_NUM 10 +#define GSC_SC_ALIGN_4 4 +#define GSC_SC_ALIGN_2 2 +#define DEFAULT_CSC_EQ 1 +#define DEFAULT_CSC_RANGE 1 + +#define GSC_PARAMS (1 << 0) +#define GSC_SRC_FMT (1 << 1) +#define GSC_DST_FMT (1 << 2) +#define GSC_CTX_M2M (1 << 3) +#define GSC_CTX_STOP_REQ (1 << 6) +#define GSC_CTX_ABORT (1 << 7) + +enum gsc_dev_flags { + /* for m2m node */ + ST_M2M_OPEN, + ST_M2M_RUN, + ST_M2M_PEND, + ST_M2M_SUSPENDED, + ST_M2M_SUSPENDING, +}; + +enum gsc_irq { + GSC_IRQ_DONE, + GSC_IRQ_OVERRUN +}; + +/** + * enum gsc_datapath - the path of data used for G-Scaler + * @GSC_CAMERA: from camera + * @GSC_DMA: from/to DMA + * @GSC_WRITEBACK: from FIMD + */ +enum gsc_datapath { + GSC_CAMERA = 0x1, + GSC_DMA, + GSC_WRITEBACK, +}; + +enum gsc_color_fmt { + GSC_RGB = 0x1, + GSC_YUV420 = 0x2, + GSC_YUV422 = 0x4, + GSC_YUV444 = 0x8, +}; + +enum gsc_yuv_fmt { + GSC_LSB_Y = 0x10, + GSC_LSB_C, + GSC_CBCR = 0x20, + GSC_CRCB, +}; + +#define fh_to_ctx(__fh) container_of(__fh, struct gsc_ctx, fh) +#define is_rgb(x) (!!((x) & 0x1)) +#define is_yuv420(x) (!!((x) & 0x2)) +#define is_yuv422(x) (!!((x) & 0x4)) + +#define gsc_m2m_active(dev) test_bit(ST_M2M_RUN, &(dev)->state) +#define gsc_m2m_pending(dev) test_bit(ST_M2M_PEND, &(dev)->state) +#define gsc_m2m_opened(dev) test_bit(ST_M2M_OPEN, &(dev)->state) + +#define ctrl_to_ctx(__ctrl) \ + container_of((__ctrl)->handler, struct gsc_ctx, ctrl_handler) +/** + * struct gsc_fmt - the driver's internal color format data + * @mbus_code: Media Bus pixel code, -1 if not applicable + * @pixelformat: the fourcc code for this format, 0 if not applicable + * @color: color encoding + * @yorder: Y/C order + * @corder: Chrominance order control + * @num_planes: number of physically non-contiguous data planes + * @num_comp: number of physically contiguous data planes + * @depth: per plane driver's private 'number of bits per pixel' + * @flags: flags indicating which operation mode format applies to + */ +struct gsc_fmt { + u32 mbus_code; + u32 pixelformat; + u32 color; + u32 yorder; + u32 corder; + u16 num_planes; + u16 num_comp; + u8 depth[VIDEO_MAX_PLANES]; + u32 flags; +}; + +/** + * struct gsc_input_buf - the driver's video buffer + * @vb: videobuf2 buffer + * @list : linked list structure for buffer queue + * @idx : index of G-Scaler input buffer + */ +struct gsc_input_buf { + struct vb2_v4l2_buffer vb; + struct list_head list; + int idx; +}; + +/** + * struct gsc_addr - the G-Scaler physical address set + * @y: luminance plane address + * @cb: Cb plane address + * @cr: Cr plane address + */ +struct gsc_addr { + dma_addr_t y; + dma_addr_t cb; + dma_addr_t cr; +}; + +/* struct gsc_ctrls - the G-Scaler control set + * @rotate: rotation degree + * @hflip: horizontal flip + * @vflip: vertical flip + * @global_alpha: the alpha value of current frame + */ +struct gsc_ctrls { + struct v4l2_ctrl *rotate; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *global_alpha; +}; + +/** + * struct gsc_scaler - the configuration data for G-Scaler inetrnal scaler + * @pre_shfactor: pre sclaer shift factor + * @pre_hratio: horizontal ratio of the prescaler + * @pre_vratio: vertical ratio of the prescaler + * @main_hratio: the main scaler's horizontal ratio + * @main_vratio: the main scaler's vertical ratio + */ +struct gsc_scaler { + u32 pre_shfactor; + u32 pre_hratio; + u32 pre_vratio; + u32 main_hratio; + u32 main_vratio; +}; + +struct gsc_dev; + +struct gsc_ctx; + +/** + * struct gsc_frame - source/target frame properties + * @f_width: SRC : SRCIMG_WIDTH, DST : OUTPUTDMA_WHOLE_IMG_WIDTH + * @f_height: SRC : SRCIMG_HEIGHT, DST : OUTPUTDMA_WHOLE_IMG_HEIGHT + * @crop: cropped(source)/scaled(destination) size + * @payload: image size in bytes (w x h x bpp) + * @addr: image frame buffer physical addresses + * @fmt: G-Scaler color format pointer + * @colorspace: value indicating v4l2_colorspace + * @alpha: frame's alpha value + */ +struct gsc_frame { + u32 f_width; + u32 f_height; + struct v4l2_rect crop; + unsigned long payload[VIDEO_MAX_PLANES]; + struct gsc_addr addr; + const struct gsc_fmt *fmt; + u32 colorspace; + u8 alpha; +}; + +/** + * struct gsc_m2m_device - v4l2 memory-to-memory device data + * @vfd: the video device node for v4l2 m2m mode + * @m2m_dev: v4l2 memory-to-memory device data + * @ctx: hardware context data + * @refcnt: the reference counter + */ +struct gsc_m2m_device { + struct video_device *vfd; + struct v4l2_m2m_dev *m2m_dev; + struct gsc_ctx *ctx; + int refcnt; +}; + +/** + * struct gsc_pix_max - image pixel size limits in various IP configurations + * + * @org_scaler_bypass_w: max pixel width when the scaler is disabled + * @org_scaler_bypass_h: max pixel height when the scaler is disabled + * @org_scaler_input_w: max pixel width when the scaler is enabled + * @org_scaler_input_h: max pixel height when the scaler is enabled + * @real_rot_dis_w: max pixel src cropped height with the rotator is off + * @real_rot_dis_h: max pixel src cropped width with the rotator is off + * @real_rot_en_w: max pixel src cropped width with the rotator is on + * @real_rot_en_h: max pixel src cropped height with the rotator is on + * @target_rot_dis_w: max pixel dst scaled width with the rotator is off + * @target_rot_dis_h: max pixel dst scaled height with the rotator is off + * @target_rot_en_w: max pixel dst scaled width with the rotator is on + * @target_rot_en_h: max pixel dst scaled height with the rotator is on + */ +struct gsc_pix_max { + u16 org_scaler_bypass_w; + u16 org_scaler_bypass_h; + u16 org_scaler_input_w; + u16 org_scaler_input_h; + u16 real_rot_dis_w; + u16 real_rot_dis_h; + u16 real_rot_en_w; + u16 real_rot_en_h; + u16 target_rot_dis_w; + u16 target_rot_dis_h; + u16 target_rot_en_w; + u16 target_rot_en_h; +}; + +/** + * struct gsc_pix_min - image pixel size limits in various IP configurations + * + * @org_w: minimum source pixel width + * @org_h: minimum source pixel height + * @real_w: minimum input crop pixel width + * @real_h: minimum input crop pixel height + * @target_rot_dis_w: minimum output scaled pixel height when rotator is off + * @target_rot_dis_h: minimum output scaled pixel height when rotator is off + * @target_rot_en_w: minimum output scaled pixel height when rotator is on + * @target_rot_en_h: minimum output scaled pixel height when rotator is on + */ +struct gsc_pix_min { + u16 org_w; + u16 org_h; + u16 real_w; + u16 real_h; + u16 target_rot_dis_w; + u16 target_rot_dis_h; + u16 target_rot_en_w; + u16 target_rot_en_h; +}; + +struct gsc_pix_align { + u16 org_h; + u16 org_w; + u16 offset_h; + u16 real_w; + u16 real_h; + u16 target_w; + u16 target_h; +}; + +/* + * struct gsc_variant - G-Scaler variant information + */ +struct gsc_variant { + struct gsc_pix_max *pix_max; + struct gsc_pix_min *pix_min; + struct gsc_pix_align *pix_align; + u16 in_buf_cnt; + u16 out_buf_cnt; + u16 sc_up_max; + u16 sc_down_max; + u16 poly_sc_down_max; + u16 pre_sc_down_max; + u16 local_sc_down; +}; + +/** + * struct gsc_driverdata - per device type driver data for init time. + * + * @variant: the variant information for this driver. + * @num_entities: the number of g-scalers + * @clk_names: clock names + * @num_clocks: the number of clocks in @clk_names + * @num_entities: the number of g-scalers + */ +struct gsc_driverdata { + struct gsc_variant *variant[GSC_MAX_DEVS]; + const char *clk_names[GSC_MAX_CLOCKS]; + int num_clocks; + int num_entities; +}; + +/** + * struct gsc_dev - abstraction for G-Scaler entity + * @slock: the spinlock protecting this data structure + * @lock: the mutex protecting this data structure + * @pdev: pointer to the G-Scaler platform device + * @variant: the IP variant information + * @id: G-Scaler device index (0..GSC_MAX_DEVS) + * @num_clocks: number of clocks required for G-Scaler operation + * @clock: clocks required for G-Scaler operation + * @regs: the mapped hardware registers + * @irq_queue: interrupt handler waitqueue + * @m2m: memory-to-memory V4L2 device information + * @state: flags used to synchronize m2m and capture mode operation + * @vdev: video device for G-Scaler instance + * @v4l2_dev: v4l2_device for G-Scaler instance + */ +struct gsc_dev { + spinlock_t slock; + struct mutex lock; + struct platform_device *pdev; + struct gsc_variant *variant; + u16 id; + int num_clocks; + struct clk *clock[GSC_MAX_CLOCKS]; + void __iomem *regs; + wait_queue_head_t irq_queue; + struct gsc_m2m_device m2m; + unsigned long state; + struct video_device vdev; + struct v4l2_device v4l2_dev; +}; + +/** + * struct gsc_ctx - the device context data + * @s_frame: source frame properties + * @d_frame: destination frame properties + * @in_path: input mode (DMA or camera) + * @out_path: output mode (DMA or FIFO) + * @scaler: image scaler properties + * @flags: additional flags for image conversion + * @state: flags to keep track of user configuration + * @rotation: rotation + * @hflip: horizontal flip + * @vflip: vertical flip + * @gsc_dev: the G-Scaler device this context applies to + * @m2m_ctx: memory-to-memory device context + * @fh: v4l2 file handle + * @ctrl_handler: v4l2 controls handler + * @gsc_ctrls: G-Scaler control set + * @ctrls_rdy: true if the control handler is initialized + * @out_colorspace: the colorspace of the OUTPUT queue + */ +struct gsc_ctx { + struct gsc_frame s_frame; + struct gsc_frame d_frame; + enum gsc_datapath in_path; + enum gsc_datapath out_path; + struct gsc_scaler scaler; + u32 flags; + u32 state; + int rotation; + unsigned int hflip:1; + unsigned int vflip:1; + struct gsc_dev *gsc_dev; + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; + struct gsc_ctrls gsc_ctrls; + bool ctrls_rdy; + enum v4l2_colorspace out_colorspace; +}; + +void gsc_set_prefbuf(struct gsc_dev *gsc, struct gsc_frame *frm); +int gsc_register_m2m_device(struct gsc_dev *gsc); +void gsc_unregister_m2m_device(struct gsc_dev *gsc); +void gsc_m2m_job_finish(struct gsc_ctx *ctx, int vb_state); + +u32 get_plane_size(struct gsc_frame *fr, unsigned int plane); +const struct gsc_fmt *get_format(int index); +const struct gsc_fmt *find_fmt(u32 *pixelformat, u32 *mbus_code, u32 index); +int gsc_enum_fmt(struct v4l2_fmtdesc *f); +int gsc_try_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f); +void gsc_set_frame_size(struct gsc_frame *frame, int width, int height); +int gsc_g_fmt_mplane(struct gsc_ctx *ctx, struct v4l2_format *f); +void gsc_check_crop_change(u32 tmp_w, u32 tmp_h, u32 *w, u32 *h); +int gsc_try_selection(struct gsc_ctx *ctx, struct v4l2_selection *s); +int gsc_cal_prescaler_ratio(struct gsc_variant *var, u32 src, u32 dst, + u32 *ratio); +void gsc_get_prescaler_shfactor(u32 hratio, u32 vratio, u32 *sh); +void gsc_check_src_scale_info(struct gsc_variant *var, + struct gsc_frame *s_frame, + u32 *wratio, u32 tx, u32 ty, u32 *hratio); +int gsc_check_scaler_ratio(struct gsc_variant *var, int sw, int sh, int dw, + int dh, int rot, int out_path); +int gsc_set_scaler_info(struct gsc_ctx *ctx); +int gsc_ctrls_create(struct gsc_ctx *ctx); +void gsc_ctrls_delete(struct gsc_ctx *ctx); +int gsc_prepare_addr(struct gsc_ctx *ctx, struct vb2_buffer *vb, + struct gsc_frame *frame, struct gsc_addr *addr); + +static inline void gsc_ctx_state_lock_set(u32 state, struct gsc_ctx *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->gsc_dev->slock, flags); + ctx->state |= state; + spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags); +} + +static inline void gsc_ctx_state_lock_clear(u32 state, struct gsc_ctx *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->gsc_dev->slock, flags); + ctx->state &= ~state; + spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags); +} + +static inline int is_tiled(const struct gsc_fmt *fmt) +{ + return fmt->pixelformat == V4L2_PIX_FMT_NV12MT_16X16; +} + +static inline void gsc_hw_enable_control(struct gsc_dev *dev, bool on) +{ + u32 cfg = readl(dev->regs + GSC_ENABLE); + + if (on) + cfg |= GSC_ENABLE_ON; + else + cfg &= ~GSC_ENABLE_ON; + + writel(cfg, dev->regs + GSC_ENABLE); +} + +static inline int gsc_hw_get_irq_status(struct gsc_dev *dev) +{ + u32 cfg = readl(dev->regs + GSC_IRQ); + if (cfg & GSC_IRQ_STATUS_OR_IRQ) + return GSC_IRQ_OVERRUN; + else + return GSC_IRQ_DONE; + +} + +static inline void gsc_hw_clear_irq(struct gsc_dev *dev, int irq) +{ + u32 cfg = readl(dev->regs + GSC_IRQ); + if (irq == GSC_IRQ_OVERRUN) + cfg |= GSC_IRQ_STATUS_OR_IRQ; + else if (irq == GSC_IRQ_DONE) + cfg |= GSC_IRQ_STATUS_FRM_DONE_IRQ; + writel(cfg, dev->regs + GSC_IRQ); +} + +static inline bool gsc_ctx_state_is_set(u32 mask, struct gsc_ctx *ctx) +{ + unsigned long flags; + bool ret; + + spin_lock_irqsave(&ctx->gsc_dev->slock, flags); + ret = (ctx->state & mask) == mask; + spin_unlock_irqrestore(&ctx->gsc_dev->slock, flags); + return ret; +} + +static inline struct gsc_frame *ctx_get_frame(struct gsc_ctx *ctx, + enum v4l2_buf_type type) +{ + struct gsc_frame *frame; + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE == type) { + frame = &ctx->s_frame; + } else if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == type) { + frame = &ctx->d_frame; + } else { + pr_err("Wrong buffer/video queue type (%d)", type); + return ERR_PTR(-EINVAL); + } + + return frame; +} + +void gsc_hw_set_sw_reset(struct gsc_dev *dev); +int gsc_wait_reset(struct gsc_dev *dev); + +void gsc_hw_set_frm_done_irq_mask(struct gsc_dev *dev, bool mask); +void gsc_hw_set_gsc_irq_enable(struct gsc_dev *dev, bool mask); +void gsc_hw_set_input_buf_masking(struct gsc_dev *dev, u32 shift, bool enable); +void gsc_hw_set_output_buf_masking(struct gsc_dev *dev, u32 shift, bool enable); +void gsc_hw_set_input_addr(struct gsc_dev *dev, struct gsc_addr *addr, + int index); +void gsc_hw_set_output_addr(struct gsc_dev *dev, struct gsc_addr *addr, + int index); +void gsc_hw_set_input_path(struct gsc_ctx *ctx); +void gsc_hw_set_in_size(struct gsc_ctx *ctx); +void gsc_hw_set_in_image_rgb(struct gsc_ctx *ctx); +void gsc_hw_set_in_image_format(struct gsc_ctx *ctx); +void gsc_hw_set_output_path(struct gsc_ctx *ctx); +void gsc_hw_set_out_size(struct gsc_ctx *ctx); +void gsc_hw_set_out_image_rgb(struct gsc_ctx *ctx); +void gsc_hw_set_out_image_format(struct gsc_ctx *ctx); +void gsc_hw_set_prescaler(struct gsc_ctx *ctx); +void gsc_hw_set_mainscaler(struct gsc_ctx *ctx); +void gsc_hw_set_rotation(struct gsc_ctx *ctx); +void gsc_hw_set_global_alpha(struct gsc_ctx *ctx); +void gsc_hw_set_sfr_update(struct gsc_ctx *ctx); + +#endif /* GSC_CORE_H_ */ diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-m2m.c b/drivers/media/platform/samsung/exynos-gsc/gsc-m2m.c new file mode 100644 index 000000000..b7854ce5f --- /dev/null +++ b/drivers/media/platform/samsung/exynos-gsc/gsc-m2m.c @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung EXYNOS5 SoC series G-Scaler driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "gsc-core.h" + +static int gsc_m2m_ctx_stop_req(struct gsc_ctx *ctx) +{ + struct gsc_ctx *curr_ctx; + struct gsc_dev *gsc = ctx->gsc_dev; + int ret; + + curr_ctx = v4l2_m2m_get_curr_priv(gsc->m2m.m2m_dev); + if (!gsc_m2m_pending(gsc) || (curr_ctx != ctx)) + return 0; + + gsc_ctx_state_lock_set(GSC_CTX_STOP_REQ, ctx); + ret = wait_event_timeout(gsc->irq_queue, + !gsc_ctx_state_is_set(GSC_CTX_STOP_REQ, ctx), + GSC_SHUTDOWN_TIMEOUT); + + return ret == 0 ? -ETIMEDOUT : ret; +} + +static void __gsc_m2m_job_abort(struct gsc_ctx *ctx) +{ + int ret; + + ret = gsc_m2m_ctx_stop_req(ctx); + if ((ret == -ETIMEDOUT) || (ctx->state & GSC_CTX_ABORT)) { + gsc_ctx_state_lock_clear(GSC_CTX_STOP_REQ | GSC_CTX_ABORT, ctx); + gsc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); + } +} + +static int gsc_m2m_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct gsc_ctx *ctx = q->drv_priv; + + return pm_runtime_resume_and_get(&ctx->gsc_dev->pdev->dev); +} + +static void __gsc_m2m_cleanup_queue(struct gsc_ctx *ctx) +{ + struct vb2_v4l2_buffer *src_vb, *dst_vb; + + while (v4l2_m2m_num_src_bufs_ready(ctx->m2m_ctx) > 0) { + src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_ERROR); + } + + while (v4l2_m2m_num_dst_bufs_ready(ctx->m2m_ctx) > 0) { + dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_ERROR); + } +} + +static void gsc_m2m_stop_streaming(struct vb2_queue *q) +{ + struct gsc_ctx *ctx = q->drv_priv; + + __gsc_m2m_job_abort(ctx); + + __gsc_m2m_cleanup_queue(ctx); + + pm_runtime_put(&ctx->gsc_dev->pdev->dev); +} + +void gsc_m2m_job_finish(struct gsc_ctx *ctx, int vb_state) +{ + struct vb2_v4l2_buffer *src_vb, *dst_vb; + + if (!ctx || !ctx->m2m_ctx) + return; + + src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + + if (src_vb && dst_vb) { + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + dst_vb->timecode = src_vb->timecode; + dst_vb->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->flags |= + src_vb->flags + & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + + v4l2_m2m_buf_done(src_vb, vb_state); + v4l2_m2m_buf_done(dst_vb, vb_state); + + v4l2_m2m_job_finish(ctx->gsc_dev->m2m.m2m_dev, + ctx->m2m_ctx); + } +} + +static void gsc_m2m_job_abort(void *priv) +{ + __gsc_m2m_job_abort((struct gsc_ctx *)priv); +} + +static int gsc_get_bufs(struct gsc_ctx *ctx) +{ + struct gsc_frame *s_frame, *d_frame; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + int ret; + + s_frame = &ctx->s_frame; + d_frame = &ctx->d_frame; + + src_vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + ret = gsc_prepare_addr(ctx, &src_vb->vb2_buf, s_frame, &s_frame->addr); + if (ret) + return ret; + + dst_vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + ret = gsc_prepare_addr(ctx, &dst_vb->vb2_buf, d_frame, &d_frame->addr); + if (ret) + return ret; + + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + + return 0; +} + +static void gsc_m2m_device_run(void *priv) +{ + struct gsc_ctx *ctx = priv; + struct gsc_dev *gsc; + unsigned long flags; + int ret; + bool is_set = false; + + if (WARN(!ctx, "null hardware context\n")) + return; + + gsc = ctx->gsc_dev; + spin_lock_irqsave(&gsc->slock, flags); + + set_bit(ST_M2M_PEND, &gsc->state); + + /* Reconfigure hardware if the context has changed. */ + if (gsc->m2m.ctx != ctx) { + pr_debug("gsc->m2m.ctx = 0x%p, current_ctx = 0x%p", + gsc->m2m.ctx, ctx); + ctx->state |= GSC_PARAMS; + gsc->m2m.ctx = ctx; + } + + is_set = ctx->state & GSC_CTX_STOP_REQ; + if (is_set) { + ctx->state &= ~GSC_CTX_STOP_REQ; + ctx->state |= GSC_CTX_ABORT; + wake_up(&gsc->irq_queue); + goto put_device; + } + + ret = gsc_get_bufs(ctx); + if (ret) { + pr_err("Wrong address"); + goto put_device; + } + + gsc_set_prefbuf(gsc, &ctx->s_frame); + gsc_hw_set_input_addr(gsc, &ctx->s_frame.addr, GSC_M2M_BUF_NUM); + gsc_hw_set_output_addr(gsc, &ctx->d_frame.addr, GSC_M2M_BUF_NUM); + + if (ctx->state & GSC_PARAMS) { + gsc_hw_set_input_buf_masking(gsc, GSC_M2M_BUF_NUM, false); + gsc_hw_set_output_buf_masking(gsc, GSC_M2M_BUF_NUM, false); + gsc_hw_set_frm_done_irq_mask(gsc, false); + gsc_hw_set_gsc_irq_enable(gsc, true); + + if (gsc_set_scaler_info(ctx)) { + pr_err("Scaler setup error"); + goto put_device; + } + + gsc_hw_set_input_path(ctx); + gsc_hw_set_in_size(ctx); + gsc_hw_set_in_image_format(ctx); + + gsc_hw_set_output_path(ctx); + gsc_hw_set_out_size(ctx); + gsc_hw_set_out_image_format(ctx); + + gsc_hw_set_prescaler(ctx); + gsc_hw_set_mainscaler(ctx); + gsc_hw_set_rotation(ctx); + gsc_hw_set_global_alpha(ctx); + } + + /* update shadow registers */ + gsc_hw_set_sfr_update(ctx); + + ctx->state &= ~GSC_PARAMS; + gsc_hw_enable_control(gsc, true); + + spin_unlock_irqrestore(&gsc->slock, flags); + return; + +put_device: + ctx->state &= ~GSC_PARAMS; + spin_unlock_irqrestore(&gsc->slock, flags); +} + +static int gsc_m2m_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct gsc_ctx *ctx = vb2_get_drv_priv(vq); + struct gsc_frame *frame; + int i; + + frame = ctx_get_frame(ctx, vq->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + if (!frame->fmt) + return -EINVAL; + + *num_planes = frame->fmt->num_planes; + for (i = 0; i < frame->fmt->num_planes; i++) + sizes[i] = frame->payload[i]; + return 0; +} + +static int gsc_m2m_buf_prepare(struct vb2_buffer *vb) +{ + struct gsc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct gsc_frame *frame; + int i; + + frame = ctx_get_frame(ctx, vb->vb2_queue->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) { + for (i = 0; i < frame->fmt->num_planes; i++) + vb2_set_plane_payload(vb, i, frame->payload[i]); + } + + return 0; +} + +static void gsc_m2m_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct gsc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + pr_debug("ctx: %p, ctx->state: 0x%x", ctx, ctx->state); + + if (ctx->m2m_ctx) + v4l2_m2m_buf_queue(ctx->m2m_ctx, vbuf); +} + +static const struct vb2_ops gsc_m2m_qops = { + .queue_setup = gsc_m2m_queue_setup, + .buf_prepare = gsc_m2m_buf_prepare, + .buf_queue = gsc_m2m_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = gsc_m2m_stop_streaming, + .start_streaming = gsc_m2m_start_streaming, +}; + +static int gsc_m2m_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, GSC_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, GSC_MODULE_NAME " gscaler", sizeof(cap->card)); + return 0; +} + +static int gsc_m2m_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return gsc_enum_fmt(f); +} + +static int gsc_m2m_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + + return gsc_g_fmt_mplane(ctx, f); +} + +static int gsc_m2m_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + + return gsc_try_fmt_mplane(ctx, f); +} + +static int gsc_m2m_s_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + struct vb2_queue *vq; + struct gsc_frame *frame; + struct v4l2_pix_format_mplane *pix; + int i, ret = 0; + + ret = gsc_m2m_try_fmt_mplane(file, fh, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); + + if (vb2_is_streaming(vq)) { + pr_err("queue (%d) busy", f->type); + return -EBUSY; + } + + if (V4L2_TYPE_IS_OUTPUT(f->type)) + frame = &ctx->s_frame; + else + frame = &ctx->d_frame; + + pix = &f->fmt.pix_mp; + frame->fmt = find_fmt(&pix->pixelformat, NULL, 0); + frame->colorspace = pix->colorspace; + if (!frame->fmt) + return -EINVAL; + + for (i = 0; i < frame->fmt->num_planes; i++) + frame->payload[i] = pix->plane_fmt[i].sizeimage; + + gsc_set_frame_size(frame, pix->width, pix->height); + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + gsc_ctx_state_lock_set(GSC_PARAMS | GSC_DST_FMT, ctx); + else + gsc_ctx_state_lock_set(GSC_PARAMS | GSC_SRC_FMT, ctx); + + pr_debug("f_w: %d, f_h: %d", frame->f_width, frame->f_height); + + return 0; +} + +static int gsc_m2m_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *reqbufs) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + struct gsc_dev *gsc = ctx->gsc_dev; + u32 max_cnt; + + max_cnt = (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ? + gsc->variant->in_buf_cnt : gsc->variant->out_buf_cnt; + if (reqbufs->count > max_cnt) + return -EINVAL; + + return v4l2_m2m_reqbufs(file, ctx->m2m_ctx, reqbufs); +} + +static int gsc_m2m_expbuf(struct file *file, void *fh, + struct v4l2_exportbuffer *eb) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_expbuf(file, ctx->m2m_ctx, eb); +} + +static int gsc_m2m_querybuf(struct file *file, void *fh, + struct v4l2_buffer *buf) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_querybuf(file, ctx->m2m_ctx, buf); +} + +static int gsc_m2m_qbuf(struct file *file, void *fh, + struct v4l2_buffer *buf) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_qbuf(file, ctx->m2m_ctx, buf); +} + +static int gsc_m2m_dqbuf(struct file *file, void *fh, + struct v4l2_buffer *buf) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_dqbuf(file, ctx->m2m_ctx, buf); +} + +static int gsc_m2m_streamon(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + + /* The source and target color format need to be set */ + if (V4L2_TYPE_IS_OUTPUT(type)) { + if (!gsc_ctx_state_is_set(GSC_SRC_FMT, ctx)) + return -EINVAL; + } else if (!gsc_ctx_state_is_set(GSC_DST_FMT, ctx)) { + return -EINVAL; + } + + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); +} + +static int gsc_m2m_streamoff(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct gsc_ctx *ctx = fh_to_ctx(fh); + return v4l2_m2m_streamoff(file, ctx->m2m_ctx, type); +} + +/* Return 1 if rectangle a is enclosed in rectangle b, or 0 otherwise. */ +static int is_rectangle_enclosed(struct v4l2_rect *a, struct v4l2_rect *b) +{ + if (a->left < b->left || a->top < b->top) + return 0; + + if (a->left + a->width > b->left + b->width) + return 0; + + if (a->top + a->height > b->top + b->height) + return 0; + + return 1; +} + +static int gsc_m2m_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct gsc_frame *frame; + struct gsc_ctx *ctx = fh_to_ctx(fh); + + if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + frame = ctx_get_frame(ctx, s->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->f_width; + s->r.height = frame->f_height; + return 0; + + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_CROP: + s->r.left = frame->crop.left; + s->r.top = frame->crop.top; + s->r.width = frame->crop.width; + s->r.height = frame->crop.height; + return 0; + } + + return -EINVAL; +} + +static int gsc_m2m_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct gsc_frame *frame; + struct gsc_ctx *ctx = fh_to_ctx(fh); + struct gsc_variant *variant = ctx->gsc_dev->variant; + struct v4l2_selection sel = *s; + int ret; + + if ((s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)) + return -EINVAL; + + ret = gsc_try_selection(ctx, &sel); + if (ret) + return ret; + + if (s->flags & V4L2_SEL_FLAG_LE && + !is_rectangle_enclosed(&sel.r, &s->r)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && + !is_rectangle_enclosed(&s->r, &sel.r)) + return -ERANGE; + + s->r = sel.r; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE: + frame = &ctx->s_frame; + break; + + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + frame = &ctx->d_frame; + break; + + default: + return -EINVAL; + } + + /* Check to see if scaling ratio is within supported range */ + if (gsc_ctx_state_is_set(GSC_DST_FMT | GSC_SRC_FMT, ctx)) { + if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = gsc_check_scaler_ratio(variant, sel.r.width, + sel.r.height, ctx->d_frame.crop.width, + ctx->d_frame.crop.height, + ctx->gsc_ctrls.rotate->val, ctx->out_path); + } else { + ret = gsc_check_scaler_ratio(variant, + ctx->s_frame.crop.width, + ctx->s_frame.crop.height, sel.r.width, + sel.r.height, ctx->gsc_ctrls.rotate->val, + ctx->out_path); + } + + if (ret) { + pr_err("Out of scaler range"); + return -EINVAL; + } + } + + frame->crop = sel.r; + + gsc_ctx_state_lock_set(GSC_PARAMS, ctx); + return 0; +} + +static const struct v4l2_ioctl_ops gsc_m2m_ioctl_ops = { + .vidioc_querycap = gsc_m2m_querycap, + .vidioc_enum_fmt_vid_cap = gsc_m2m_enum_fmt, + .vidioc_enum_fmt_vid_out = gsc_m2m_enum_fmt, + .vidioc_g_fmt_vid_cap_mplane = gsc_m2m_g_fmt_mplane, + .vidioc_g_fmt_vid_out_mplane = gsc_m2m_g_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = gsc_m2m_try_fmt_mplane, + .vidioc_try_fmt_vid_out_mplane = gsc_m2m_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = gsc_m2m_s_fmt_mplane, + .vidioc_s_fmt_vid_out_mplane = gsc_m2m_s_fmt_mplane, + .vidioc_reqbufs = gsc_m2m_reqbufs, + .vidioc_expbuf = gsc_m2m_expbuf, + .vidioc_querybuf = gsc_m2m_querybuf, + .vidioc_qbuf = gsc_m2m_qbuf, + .vidioc_dqbuf = gsc_m2m_dqbuf, + .vidioc_streamon = gsc_m2m_streamon, + .vidioc_streamoff = gsc_m2m_streamoff, + .vidioc_g_selection = gsc_m2m_g_selection, + .vidioc_s_selection = gsc_m2m_s_selection +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct gsc_ctx *ctx = priv; + int ret; + + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->ops = &gsc_m2m_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->gsc_dev->lock; + src_vq->dev = &ctx->gsc_dev->pdev->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->ops = &gsc_m2m_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->gsc_dev->lock; + dst_vq->dev = &ctx->gsc_dev->pdev->dev; + + return vb2_queue_init(dst_vq); +} + +static int gsc_m2m_open(struct file *file) +{ + struct gsc_dev *gsc = video_drvdata(file); + struct gsc_ctx *ctx = NULL; + int ret; + + pr_debug("pid: %d, state: 0x%lx", task_pid_nr(current), gsc->state); + + if (mutex_lock_interruptible(&gsc->lock)) + return -ERESTARTSYS; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto unlock; + } + + v4l2_fh_init(&ctx->fh, gsc->m2m.vfd); + ret = gsc_ctrls_create(ctx); + if (ret) + goto error_fh; + + /* Use separate control handler per file handle */ + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->gsc_dev = gsc; + /* Default color format */ + ctx->s_frame.fmt = get_format(0); + ctx->d_frame.fmt = get_format(0); + /* Setup the device context for mem2mem mode. */ + ctx->state = GSC_CTX_M2M; + ctx->flags = 0; + ctx->in_path = GSC_DMA; + ctx->out_path = GSC_DMA; + + ctx->m2m_ctx = v4l2_m2m_ctx_init(gsc->m2m.m2m_dev, ctx, queue_init); + if (IS_ERR(ctx->m2m_ctx)) { + pr_err("Failed to initialize m2m context"); + ret = PTR_ERR(ctx->m2m_ctx); + goto error_ctrls; + } + + if (gsc->m2m.refcnt++ == 0) + set_bit(ST_M2M_OPEN, &gsc->state); + + pr_debug("gsc m2m driver is opened, ctx(0x%p)", ctx); + + mutex_unlock(&gsc->lock); + return 0; + +error_ctrls: + gsc_ctrls_delete(ctx); + v4l2_fh_del(&ctx->fh); +error_fh: + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +unlock: + mutex_unlock(&gsc->lock); + return ret; +} + +static int gsc_m2m_release(struct file *file) +{ + struct gsc_ctx *ctx = fh_to_ctx(file->private_data); + struct gsc_dev *gsc = ctx->gsc_dev; + + pr_debug("pid: %d, state: 0x%lx, refcnt= %d", + task_pid_nr(current), gsc->state, gsc->m2m.refcnt); + + mutex_lock(&gsc->lock); + + v4l2_m2m_ctx_release(ctx->m2m_ctx); + gsc_ctrls_delete(ctx); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + if (--gsc->m2m.refcnt <= 0) + clear_bit(ST_M2M_OPEN, &gsc->state); + kfree(ctx); + + mutex_unlock(&gsc->lock); + return 0; +} + +static __poll_t gsc_m2m_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct gsc_ctx *ctx = fh_to_ctx(file->private_data); + struct gsc_dev *gsc = ctx->gsc_dev; + __poll_t ret; + + if (mutex_lock_interruptible(&gsc->lock)) + return EPOLLERR; + + ret = v4l2_m2m_poll(file, ctx->m2m_ctx, wait); + mutex_unlock(&gsc->lock); + + return ret; +} + +static int gsc_m2m_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct gsc_ctx *ctx = fh_to_ctx(file->private_data); + struct gsc_dev *gsc = ctx->gsc_dev; + int ret; + + if (mutex_lock_interruptible(&gsc->lock)) + return -ERESTARTSYS; + + ret = v4l2_m2m_mmap(file, ctx->m2m_ctx, vma); + mutex_unlock(&gsc->lock); + + return ret; +} + +static const struct v4l2_file_operations gsc_m2m_fops = { + .owner = THIS_MODULE, + .open = gsc_m2m_open, + .release = gsc_m2m_release, + .poll = gsc_m2m_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = gsc_m2m_mmap, +}; + +static const struct v4l2_m2m_ops gsc_m2m_ops = { + .device_run = gsc_m2m_device_run, + .job_abort = gsc_m2m_job_abort, +}; + +int gsc_register_m2m_device(struct gsc_dev *gsc) +{ + struct platform_device *pdev; + int ret; + + if (!gsc) + return -ENODEV; + + pdev = gsc->pdev; + + gsc->vdev.fops = &gsc_m2m_fops; + gsc->vdev.ioctl_ops = &gsc_m2m_ioctl_ops; + gsc->vdev.release = video_device_release_empty; + gsc->vdev.lock = &gsc->lock; + gsc->vdev.vfl_dir = VFL_DIR_M2M; + gsc->vdev.v4l2_dev = &gsc->v4l2_dev; + gsc->vdev.device_caps = V4L2_CAP_STREAMING | + V4L2_CAP_VIDEO_M2M_MPLANE; + snprintf(gsc->vdev.name, sizeof(gsc->vdev.name), "%s.%d:m2m", + GSC_MODULE_NAME, gsc->id); + + video_set_drvdata(&gsc->vdev, gsc); + + gsc->m2m.vfd = &gsc->vdev; + gsc->m2m.m2m_dev = v4l2_m2m_init(&gsc_m2m_ops); + if (IS_ERR(gsc->m2m.m2m_dev)) { + dev_err(&pdev->dev, "failed to initialize v4l2-m2m device\n"); + return PTR_ERR(gsc->m2m.m2m_dev); + } + + ret = video_register_device(&gsc->vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(&pdev->dev, + "%s(): failed to register video device\n", __func__); + goto err_m2m_release; + } + + pr_debug("gsc m2m driver registered as /dev/video%d", gsc->vdev.num); + return 0; + +err_m2m_release: + v4l2_m2m_release(gsc->m2m.m2m_dev); + + return ret; +} + +void gsc_unregister_m2m_device(struct gsc_dev *gsc) +{ + if (gsc) { + v4l2_m2m_release(gsc->m2m.m2m_dev); + video_unregister_device(&gsc->vdev); + } +} diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-regs.c b/drivers/media/platform/samsung/exynos-gsc/gsc-regs.c new file mode 100644 index 000000000..995a1f0f8 --- /dev/null +++ b/drivers/media/platform/samsung/exynos-gsc/gsc-regs.c @@ -0,0 +1,426 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Samsung EXYNOS5 SoC series G-Scaler driver + */ + +#include +#include + +#include "gsc-core.h" + +void gsc_hw_set_sw_reset(struct gsc_dev *dev) +{ + writel(GSC_SW_RESET_SRESET, dev->regs + GSC_SW_RESET); +} + +int gsc_wait_reset(struct gsc_dev *dev) +{ + unsigned long end = jiffies + msecs_to_jiffies(50); + u32 cfg; + + while (time_before(jiffies, end)) { + cfg = readl(dev->regs + GSC_SW_RESET); + if (!cfg) + return 0; + usleep_range(10, 20); + } + + return -EBUSY; +} + +void gsc_hw_set_frm_done_irq_mask(struct gsc_dev *dev, bool mask) +{ + u32 cfg; + + cfg = readl(dev->regs + GSC_IRQ); + if (mask) + cfg |= GSC_IRQ_FRMDONE_MASK; + else + cfg &= ~GSC_IRQ_FRMDONE_MASK; + writel(cfg, dev->regs + GSC_IRQ); +} + +void gsc_hw_set_gsc_irq_enable(struct gsc_dev *dev, bool mask) +{ + u32 cfg; + + cfg = readl(dev->regs + GSC_IRQ); + if (mask) + cfg |= GSC_IRQ_ENABLE; + else + cfg &= ~GSC_IRQ_ENABLE; + writel(cfg, dev->regs + GSC_IRQ); +} + +void gsc_hw_set_input_buf_masking(struct gsc_dev *dev, u32 shift, + bool enable) +{ + u32 cfg = readl(dev->regs + GSC_IN_BASE_ADDR_Y_MASK); + u32 mask = 1 << shift; + + cfg &= ~mask; + cfg |= enable << shift; + + writel(cfg, dev->regs + GSC_IN_BASE_ADDR_Y_MASK); + writel(cfg, dev->regs + GSC_IN_BASE_ADDR_CB_MASK); + writel(cfg, dev->regs + GSC_IN_BASE_ADDR_CR_MASK); +} + +void gsc_hw_set_output_buf_masking(struct gsc_dev *dev, u32 shift, + bool enable) +{ + u32 cfg = readl(dev->regs + GSC_OUT_BASE_ADDR_Y_MASK); + u32 mask = 1 << shift; + + cfg &= ~mask; + cfg |= enable << shift; + + writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_Y_MASK); + writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_CB_MASK); + writel(cfg, dev->regs + GSC_OUT_BASE_ADDR_CR_MASK); +} + +void gsc_hw_set_input_addr(struct gsc_dev *dev, struct gsc_addr *addr, + int index) +{ + pr_debug("src_buf[%d]: %pad, cb: %pad, cr: %pad", index, + &addr->y, &addr->cb, &addr->cr); + writel(addr->y, dev->regs + GSC_IN_BASE_ADDR_Y(index)); + writel(addr->cb, dev->regs + GSC_IN_BASE_ADDR_CB(index)); + writel(addr->cr, dev->regs + GSC_IN_BASE_ADDR_CR(index)); + +} + +void gsc_hw_set_output_addr(struct gsc_dev *dev, + struct gsc_addr *addr, int index) +{ + pr_debug("dst_buf[%d]: %pad, cb: %pad, cr: %pad", + index, &addr->y, &addr->cb, &addr->cr); + writel(addr->y, dev->regs + GSC_OUT_BASE_ADDR_Y(index)); + writel(addr->cb, dev->regs + GSC_OUT_BASE_ADDR_CB(index)); + writel(addr->cr, dev->regs + GSC_OUT_BASE_ADDR_CR(index)); +} + +void gsc_hw_set_input_path(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + + u32 cfg = readl(dev->regs + GSC_IN_CON); + cfg &= ~(GSC_IN_PATH_MASK | GSC_IN_LOCAL_SEL_MASK); + + if (ctx->in_path == GSC_DMA) + cfg |= GSC_IN_PATH_MEMORY; + + writel(cfg, dev->regs + GSC_IN_CON); +} + +void gsc_hw_set_in_size(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->s_frame; + u32 cfg; + + /* Set input pixel offset */ + cfg = GSC_SRCIMG_OFFSET_X(frame->crop.left); + cfg |= GSC_SRCIMG_OFFSET_Y(frame->crop.top); + writel(cfg, dev->regs + GSC_SRCIMG_OFFSET); + + /* Set input original size */ + cfg = GSC_SRCIMG_WIDTH(frame->f_width); + cfg |= GSC_SRCIMG_HEIGHT(frame->f_height); + writel(cfg, dev->regs + GSC_SRCIMG_SIZE); + + /* Set input cropped size */ + cfg = GSC_CROPPED_WIDTH(frame->crop.width); + cfg |= GSC_CROPPED_HEIGHT(frame->crop.height); + writel(cfg, dev->regs + GSC_CROPPED_SIZE); +} + +void gsc_hw_set_in_image_rgb(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->s_frame; + u32 cfg; + + cfg = readl(dev->regs + GSC_IN_CON); + if (frame->colorspace == V4L2_COLORSPACE_REC709) + cfg |= GSC_IN_RGB_HD_WIDE; + else + cfg |= GSC_IN_RGB_SD_WIDE; + + if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB565X) + cfg |= GSC_IN_RGB565; + else if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB32) + cfg |= GSC_IN_XRGB8888; + + writel(cfg, dev->regs + GSC_IN_CON); +} + +void gsc_hw_set_in_image_format(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->s_frame; + u32 i, depth = 0; + u32 cfg; + + cfg = readl(dev->regs + GSC_IN_CON); + cfg &= ~(GSC_IN_RGB_TYPE_MASK | GSC_IN_YUV422_1P_ORDER_MASK | + GSC_IN_CHROMA_ORDER_MASK | GSC_IN_FORMAT_MASK | + GSC_IN_TILE_TYPE_MASK | GSC_IN_TILE_MODE); + writel(cfg, dev->regs + GSC_IN_CON); + + if (is_rgb(frame->fmt->color)) { + gsc_hw_set_in_image_rgb(ctx); + return; + } + for (i = 0; i < frame->fmt->num_planes; i++) + depth += frame->fmt->depth[i]; + + switch (frame->fmt->num_comp) { + case 1: + cfg |= GSC_IN_YUV422_1P; + if (frame->fmt->yorder == GSC_LSB_Y) + cfg |= GSC_IN_YUV422_1P_ORDER_LSB_Y; + else + cfg |= GSC_IN_YUV422_1P_OEDER_LSB_C; + if (frame->fmt->corder == GSC_CBCR) + cfg |= GSC_IN_CHROMA_ORDER_CBCR; + else + cfg |= GSC_IN_CHROMA_ORDER_CRCB; + break; + case 2: + if (depth == 12) + cfg |= GSC_IN_YUV420_2P; + else + cfg |= GSC_IN_YUV422_2P; + if (frame->fmt->corder == GSC_CBCR) + cfg |= GSC_IN_CHROMA_ORDER_CBCR; + else + cfg |= GSC_IN_CHROMA_ORDER_CRCB; + break; + case 3: + if (depth == 12) + cfg |= GSC_IN_YUV420_3P; + else + cfg |= GSC_IN_YUV422_3P; + break; + } + + if (is_tiled(frame->fmt)) + cfg |= GSC_IN_TILE_C_16x8 | GSC_IN_TILE_MODE; + + writel(cfg, dev->regs + GSC_IN_CON); +} + +void gsc_hw_set_output_path(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + + u32 cfg = readl(dev->regs + GSC_OUT_CON); + cfg &= ~GSC_OUT_PATH_MASK; + + if (ctx->out_path == GSC_DMA) + cfg |= GSC_OUT_PATH_MEMORY; + else + cfg |= GSC_OUT_PATH_LOCAL; + + writel(cfg, dev->regs + GSC_OUT_CON); +} + +void gsc_hw_set_out_size(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->d_frame; + u32 cfg; + + /* Set output original size */ + if (ctx->out_path == GSC_DMA) { + cfg = GSC_DSTIMG_OFFSET_X(frame->crop.left); + cfg |= GSC_DSTIMG_OFFSET_Y(frame->crop.top); + writel(cfg, dev->regs + GSC_DSTIMG_OFFSET); + + cfg = GSC_DSTIMG_WIDTH(frame->f_width); + cfg |= GSC_DSTIMG_HEIGHT(frame->f_height); + writel(cfg, dev->regs + GSC_DSTIMG_SIZE); + } + + /* Set output scaled size */ + if (ctx->gsc_ctrls.rotate->val == 90 || + ctx->gsc_ctrls.rotate->val == 270) { + cfg = GSC_SCALED_WIDTH(frame->crop.height); + cfg |= GSC_SCALED_HEIGHT(frame->crop.width); + } else { + cfg = GSC_SCALED_WIDTH(frame->crop.width); + cfg |= GSC_SCALED_HEIGHT(frame->crop.height); + } + writel(cfg, dev->regs + GSC_SCALED_SIZE); +} + +void gsc_hw_set_out_image_rgb(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->d_frame; + u32 cfg; + + cfg = readl(dev->regs + GSC_OUT_CON); + if (frame->colorspace == V4L2_COLORSPACE_REC709) + cfg |= GSC_OUT_RGB_HD_WIDE; + else + cfg |= GSC_OUT_RGB_SD_WIDE; + + if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB565X) + cfg |= GSC_OUT_RGB565; + else if (frame->fmt->pixelformat == V4L2_PIX_FMT_RGB32) + cfg |= GSC_OUT_XRGB8888; + + writel(cfg, dev->regs + GSC_OUT_CON); +} + +void gsc_hw_set_out_image_format(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->d_frame; + u32 i, depth = 0; + u32 cfg; + + cfg = readl(dev->regs + GSC_OUT_CON); + cfg &= ~(GSC_OUT_RGB_TYPE_MASK | GSC_OUT_YUV422_1P_ORDER_MASK | + GSC_OUT_CHROMA_ORDER_MASK | GSC_OUT_FORMAT_MASK | + GSC_OUT_TILE_TYPE_MASK | GSC_OUT_TILE_MODE); + writel(cfg, dev->regs + GSC_OUT_CON); + + if (is_rgb(frame->fmt->color)) { + gsc_hw_set_out_image_rgb(ctx); + return; + } + + if (ctx->out_path != GSC_DMA) { + cfg |= GSC_OUT_YUV444; + goto end_set; + } + + for (i = 0; i < frame->fmt->num_planes; i++) + depth += frame->fmt->depth[i]; + + switch (frame->fmt->num_comp) { + case 1: + cfg |= GSC_OUT_YUV422_1P; + if (frame->fmt->yorder == GSC_LSB_Y) + cfg |= GSC_OUT_YUV422_1P_ORDER_LSB_Y; + else + cfg |= GSC_OUT_YUV422_1P_OEDER_LSB_C; + if (frame->fmt->corder == GSC_CBCR) + cfg |= GSC_OUT_CHROMA_ORDER_CBCR; + else + cfg |= GSC_OUT_CHROMA_ORDER_CRCB; + break; + case 2: + if (depth == 12) + cfg |= GSC_OUT_YUV420_2P; + else + cfg |= GSC_OUT_YUV422_2P; + if (frame->fmt->corder == GSC_CBCR) + cfg |= GSC_OUT_CHROMA_ORDER_CBCR; + else + cfg |= GSC_OUT_CHROMA_ORDER_CRCB; + break; + case 3: + cfg |= GSC_OUT_YUV420_3P; + break; + } + + if (is_tiled(frame->fmt)) + cfg |= GSC_OUT_TILE_C_16x8 | GSC_OUT_TILE_MODE; + +end_set: + writel(cfg, dev->regs + GSC_OUT_CON); +} + +void gsc_hw_set_prescaler(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_scaler *sc = &ctx->scaler; + u32 cfg; + + cfg = GSC_PRESC_SHFACTOR(sc->pre_shfactor); + cfg |= GSC_PRESC_H_RATIO(sc->pre_hratio); + cfg |= GSC_PRESC_V_RATIO(sc->pre_vratio); + writel(cfg, dev->regs + GSC_PRE_SCALE_RATIO); +} + +void gsc_hw_set_mainscaler(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_scaler *sc = &ctx->scaler; + u32 cfg; + + cfg = GSC_MAIN_H_RATIO_VALUE(sc->main_hratio); + writel(cfg, dev->regs + GSC_MAIN_H_RATIO); + + cfg = GSC_MAIN_V_RATIO_VALUE(sc->main_vratio); + writel(cfg, dev->regs + GSC_MAIN_V_RATIO); +} + +void gsc_hw_set_rotation(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + u32 cfg; + + cfg = readl(dev->regs + GSC_IN_CON); + cfg &= ~GSC_IN_ROT_MASK; + + switch (ctx->gsc_ctrls.rotate->val) { + case 270: + cfg |= GSC_IN_ROT_270; + break; + case 180: + cfg |= GSC_IN_ROT_180; + break; + case 90: + if (ctx->gsc_ctrls.hflip->val) + cfg |= GSC_IN_ROT_90_XFLIP; + else if (ctx->gsc_ctrls.vflip->val) + cfg |= GSC_IN_ROT_90_YFLIP; + else + cfg |= GSC_IN_ROT_90; + break; + case 0: + if (ctx->gsc_ctrls.hflip->val) + cfg |= GSC_IN_ROT_XFLIP; + else if (ctx->gsc_ctrls.vflip->val) + cfg |= GSC_IN_ROT_YFLIP; + } + + writel(cfg, dev->regs + GSC_IN_CON); +} + +void gsc_hw_set_global_alpha(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + struct gsc_frame *frame = &ctx->d_frame; + u32 cfg; + + if (!is_rgb(frame->fmt->color)) { + pr_debug("Not a RGB format"); + return; + } + + cfg = readl(dev->regs + GSC_OUT_CON); + cfg &= ~GSC_OUT_GLOBAL_ALPHA_MASK; + + cfg |= GSC_OUT_GLOBAL_ALPHA(ctx->gsc_ctrls.global_alpha->val); + writel(cfg, dev->regs + GSC_OUT_CON); +} + +void gsc_hw_set_sfr_update(struct gsc_ctx *ctx) +{ + struct gsc_dev *dev = ctx->gsc_dev; + u32 cfg; + + cfg = readl(dev->regs + GSC_ENABLE); + cfg |= GSC_ENABLE_SFR_UPDATE; + writel(cfg, dev->regs + GSC_ENABLE); +} diff --git a/drivers/media/platform/samsung/exynos-gsc/gsc-regs.h b/drivers/media/platform/samsung/exynos-gsc/gsc-regs.h new file mode 100644 index 000000000..d4f7ead6b --- /dev/null +++ b/drivers/media/platform/samsung/exynos-gsc/gsc-regs.h @@ -0,0 +1,169 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2011 - 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Register definition file for Samsung G-Scaler driver + */ + +#ifndef REGS_GSC_H_ +#define REGS_GSC_H_ + +/* G-Scaler enable */ +#define GSC_ENABLE 0x00 +#define GSC_ENABLE_OP_STATUS (1 << 2) +#define GSC_ENABLE_SFR_UPDATE (1 << 1) +#define GSC_ENABLE_ON (1 << 0) + +/* G-Scaler S/W reset */ +#define GSC_SW_RESET 0x04 +#define GSC_SW_RESET_SRESET (1 << 0) + +/* G-Scaler IRQ */ +#define GSC_IRQ 0x08 +#define GSC_IRQ_STATUS_OR_IRQ (1 << 17) +#define GSC_IRQ_STATUS_FRM_DONE_IRQ (1 << 16) +#define GSC_IRQ_FRMDONE_MASK (1 << 1) +#define GSC_IRQ_ENABLE (1 << 0) + +/* G-Scaler input control */ +#define GSC_IN_CON 0x10 +#define GSC_IN_ROT_MASK (7 << 16) +#define GSC_IN_ROT_270 (7 << 16) +#define GSC_IN_ROT_90_YFLIP (6 << 16) +#define GSC_IN_ROT_90_XFLIP (5 << 16) +#define GSC_IN_ROT_90 (4 << 16) +#define GSC_IN_ROT_180 (3 << 16) +#define GSC_IN_ROT_YFLIP (2 << 16) +#define GSC_IN_ROT_XFLIP (1 << 16) +#define GSC_IN_RGB_TYPE_MASK (3 << 14) +#define GSC_IN_RGB_HD_NARROW (3 << 14) +#define GSC_IN_RGB_HD_WIDE (2 << 14) +#define GSC_IN_RGB_SD_NARROW (1 << 14) +#define GSC_IN_RGB_SD_WIDE (0 << 14) +#define GSC_IN_YUV422_1P_ORDER_MASK (1 << 13) +#define GSC_IN_YUV422_1P_ORDER_LSB_Y (0 << 13) +#define GSC_IN_YUV422_1P_OEDER_LSB_C (1 << 13) +#define GSC_IN_CHROMA_ORDER_MASK (1 << 12) +#define GSC_IN_CHROMA_ORDER_CBCR (0 << 12) +#define GSC_IN_CHROMA_ORDER_CRCB (1 << 12) +#define GSC_IN_FORMAT_MASK (7 << 8) +#define GSC_IN_XRGB8888 (0 << 8) +#define GSC_IN_RGB565 (1 << 8) +#define GSC_IN_YUV420_2P (2 << 8) +#define GSC_IN_YUV420_3P (3 << 8) +#define GSC_IN_YUV422_1P (4 << 8) +#define GSC_IN_YUV422_2P (5 << 8) +#define GSC_IN_YUV422_3P (6 << 8) +#define GSC_IN_TILE_TYPE_MASK (1 << 4) +#define GSC_IN_TILE_C_16x8 (0 << 4) +#define GSC_IN_TILE_MODE (1 << 3) +#define GSC_IN_LOCAL_SEL_MASK (3 << 1) +#define GSC_IN_PATH_MASK (1 << 0) +#define GSC_IN_PATH_MEMORY (0 << 0) + +/* G-Scaler source image size */ +#define GSC_SRCIMG_SIZE 0x14 +#define GSC_SRCIMG_HEIGHT(x) ((x) << 16) +#define GSC_SRCIMG_WIDTH(x) ((x) << 0) + +/* G-Scaler source image offset */ +#define GSC_SRCIMG_OFFSET 0x18 +#define GSC_SRCIMG_OFFSET_Y(x) ((x) << 16) +#define GSC_SRCIMG_OFFSET_X(x) ((x) << 0) + +/* G-Scaler cropped source image size */ +#define GSC_CROPPED_SIZE 0x1c +#define GSC_CROPPED_HEIGHT(x) ((x) << 16) +#define GSC_CROPPED_WIDTH(x) ((x) << 0) + +/* G-Scaler output control */ +#define GSC_OUT_CON 0x20 +#define GSC_OUT_GLOBAL_ALPHA_MASK (0xff << 24) +#define GSC_OUT_GLOBAL_ALPHA(x) ((x) << 24) +#define GSC_OUT_RGB_TYPE_MASK (3 << 10) +#define GSC_OUT_RGB_HD_WIDE (3 << 10) +#define GSC_OUT_RGB_HD_NARROW (2 << 10) +#define GSC_OUT_RGB_SD_WIDE (1 << 10) +#define GSC_OUT_RGB_SD_NARROW (0 << 10) +#define GSC_OUT_YUV422_1P_ORDER_MASK (1 << 9) +#define GSC_OUT_YUV422_1P_ORDER_LSB_Y (0 << 9) +#define GSC_OUT_YUV422_1P_OEDER_LSB_C (1 << 9) +#define GSC_OUT_CHROMA_ORDER_MASK (1 << 8) +#define GSC_OUT_CHROMA_ORDER_CBCR (0 << 8) +#define GSC_OUT_CHROMA_ORDER_CRCB (1 << 8) +#define GSC_OUT_FORMAT_MASK (7 << 4) +#define GSC_OUT_XRGB8888 (0 << 4) +#define GSC_OUT_RGB565 (1 << 4) +#define GSC_OUT_YUV420_2P (2 << 4) +#define GSC_OUT_YUV420_3P (3 << 4) +#define GSC_OUT_YUV422_1P (4 << 4) +#define GSC_OUT_YUV422_2P (5 << 4) +#define GSC_OUT_YUV444 (7 << 4) +#define GSC_OUT_TILE_TYPE_MASK (1 << 2) +#define GSC_OUT_TILE_C_16x8 (0 << 2) +#define GSC_OUT_TILE_MODE (1 << 1) +#define GSC_OUT_PATH_MASK (1 << 0) +#define GSC_OUT_PATH_LOCAL (1 << 0) +#define GSC_OUT_PATH_MEMORY (0 << 0) + +/* G-Scaler scaled destination image size */ +#define GSC_SCALED_SIZE 0x24 +#define GSC_SCALED_HEIGHT(x) ((x) << 16) +#define GSC_SCALED_WIDTH(x) ((x) << 0) + +/* G-Scaler pre scale ratio */ +#define GSC_PRE_SCALE_RATIO 0x28 +#define GSC_PRESC_SHFACTOR(x) ((x) << 28) +#define GSC_PRESC_V_RATIO(x) ((x) << 16) +#define GSC_PRESC_H_RATIO(x) ((x) << 0) + +/* G-Scaler main scale horizontal ratio */ +#define GSC_MAIN_H_RATIO 0x2c +#define GSC_MAIN_H_RATIO_VALUE(x) ((x) << 0) + +/* G-Scaler main scale vertical ratio */ +#define GSC_MAIN_V_RATIO 0x30 +#define GSC_MAIN_V_RATIO_VALUE(x) ((x) << 0) + +/* G-Scaler destination image size */ +#define GSC_DSTIMG_SIZE 0x40 +#define GSC_DSTIMG_HEIGHT(x) ((x) << 16) +#define GSC_DSTIMG_WIDTH(x) ((x) << 0) + +/* G-Scaler destination image offset */ +#define GSC_DSTIMG_OFFSET 0x44 +#define GSC_DSTIMG_OFFSET_Y(x) ((x) << 16) +#define GSC_DSTIMG_OFFSET_X(x) ((x) << 0) + +/* G-Scaler input y address mask */ +#define GSC_IN_BASE_ADDR_Y_MASK 0x4c +/* G-Scaler input y base address */ +#define GSC_IN_BASE_ADDR_Y(n) (0x50 + (n) * 0x4) + +/* G-Scaler input cb address mask */ +#define GSC_IN_BASE_ADDR_CB_MASK 0x7c +/* G-Scaler input cb base address */ +#define GSC_IN_BASE_ADDR_CB(n) (0x80 + (n) * 0x4) + +/* G-Scaler input cr address mask */ +#define GSC_IN_BASE_ADDR_CR_MASK 0xac +/* G-Scaler input cr base address */ +#define GSC_IN_BASE_ADDR_CR(n) (0xb0 + (n) * 0x4) + +/* G-Scaler output y address mask */ +#define GSC_OUT_BASE_ADDR_Y_MASK 0x10c +/* G-Scaler output y base address */ +#define GSC_OUT_BASE_ADDR_Y(n) (0x110 + (n) * 0x4) + +/* G-Scaler output cb address mask */ +#define GSC_OUT_BASE_ADDR_CB_MASK 0x15c +/* G-Scaler output cb base address */ +#define GSC_OUT_BASE_ADDR_CB(n) (0x160 + (n) * 0x4) + +/* G-Scaler output cr address mask */ +#define GSC_OUT_BASE_ADDR_CR_MASK 0x1ac +/* G-Scaler output cr base address */ +#define GSC_OUT_BASE_ADDR_CR(n) (0x1b0 + (n) * 0x4) + +#endif /* REGS_GSC_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/Kconfig b/drivers/media/platform/samsung/exynos4-is/Kconfig new file mode 100644 index 000000000..da33faa71 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/Kconfig @@ -0,0 +1,84 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_SAMSUNG_EXYNOS4_IS + tristate "Samsung S5P/EXYNOS4 SoC series Camera Subsystem driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF && COMMON_CLK + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + help + Say Y here to enable camera host interface devices for + Samsung S5P and EXYNOS SoC series. + +if VIDEO_SAMSUNG_EXYNOS4_IS + +config VIDEO_EXYNOS4_IS_COMMON + tristate + +config VIDEO_S5P_FIMC + tristate "S5P/EXYNOS4 FIMC/CAMIF camera interface driver" + depends on I2C + depends on HAS_DMA + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select MFD_SYSCON + select VIDEO_EXYNOS4_IS_COMMON + help + This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC camera host + interface and video postprocessor (FIMC) devices. + + To compile this driver as a module, choose M here: the + module will be called s5p-fimc. + +config VIDEO_S5P_MIPI_CSIS + tristate "S5P/EXYNOS MIPI-CSI2 receiver (MIPI-CSIS) driver" + depends on REGULATOR + select GENERIC_PHY + select V4L2_FWNODE + help + This is a V4L2 driver for Samsung S5P and EXYNOS4 SoC MIPI-CSI2 + receiver (MIPI-CSIS) devices. + + To compile this driver as a module, choose M here: the + module will be called s5p-csis. + +config VIDEO_EXYNOS_FIMC_LITE + tristate "EXYNOS FIMC-LITE camera interface driver" + depends on I2C + depends on SOC_EXYNOS4412 || SOC_EXYNOS5250 || COMPILE_TEST + depends on HAS_DMA + select VIDEOBUF2_DMA_CONTIG + select VIDEO_EXYNOS4_IS_COMMON + help + This is a V4L2 driver for Samsung EXYNOS4/5 SoC FIMC-LITE camera + host interface. + + To compile this driver as a module, choose M here: the + module will be called exynos-fimc-lite. + +config VIDEO_EXYNOS4_FIMC_IS + tristate "EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver" + depends on I2C + depends on HAS_DMA + select VIDEOBUF2_DMA_CONTIG + depends on OF + select FW_LOADER + help + This is a V4L2 driver for Samsung EXYNOS4x12 SoC series + FIMC-IS (Imaging Subsystem). + + To compile this driver as a module, choose M here: the + module will be called exynos4-fimc-is. + +config VIDEO_EXYNOS4_ISP_DMA_CAPTURE + bool "EXYNOS4x12 FIMC-IS ISP Direct DMA capture support" + depends on VIDEO_EXYNOS4_FIMC_IS + select VIDEO_EXYNOS4_IS_COMMON + default y + help + This option enables an additional video device node exposing a V4L2 + video capture interface for the FIMC-IS ISP raw (Bayer) capture DMA. + +endif # VIDEO_SAMSUNG_EXYNOS4_IS diff --git a/drivers/media/platform/samsung/exynos4-is/Makefile b/drivers/media/platform/samsung/exynos4-is/Makefile new file mode 100644 index 000000000..a5ab01c73 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/Makefile @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: GPL-2.0 +s5p-fimc-objs := fimc-core.o fimc-reg.o fimc-m2m.o fimc-capture.o media-dev.o +exynos-fimc-lite-objs += fimc-lite-reg.o fimc-lite.o +s5p-csis-objs := mipi-csis.o +exynos4-is-common-objs := common.o + +exynos-fimc-is-objs := fimc-is.o fimc-isp.o fimc-is-sensor.o fimc-is-regs.o +exynos-fimc-is-objs += fimc-is-param.o fimc-is-errno.o fimc-is-i2c.o + +ifeq ($(CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE),y) +exynos-fimc-is-objs += fimc-isp-video.o +endif + +obj-$(CONFIG_VIDEO_S5P_MIPI_CSIS) += s5p-csis.o +obj-$(CONFIG_VIDEO_EXYNOS_FIMC_LITE) += exynos-fimc-lite.o +obj-$(CONFIG_VIDEO_EXYNOS4_FIMC_IS) += exynos-fimc-is.o +obj-$(CONFIG_VIDEO_S5P_FIMC) += s5p-fimc.o +obj-$(CONFIG_VIDEO_EXYNOS4_IS_COMMON) += exynos4-is-common.o diff --git a/drivers/media/platform/samsung/exynos4-is/common.c b/drivers/media/platform/samsung/exynos4-is/common.c new file mode 100644 index 000000000..e41333535 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/common.c @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung S5P/EXYNOS4 SoC Camera Subsystem driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + */ + +#include +#include +#include "common.h" + +/* + * Called with the media graph mutex held or media_entity_is_streaming(entity) + * true. + */ +struct v4l2_subdev *fimc_find_remote_sensor(struct media_entity *entity) +{ + struct media_pad *pad = &entity->pads[0]; + struct v4l2_subdev *sd; + + while (pad->flags & MEDIA_PAD_FL_SINK) { + /* source pad */ + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + sd = media_entity_to_v4l2_subdev(pad->entity); + + if (sd->grp_id == GRP_ID_FIMC_IS_SENSOR || + sd->grp_id == GRP_ID_SENSOR) + return sd; + /* sink pad */ + pad = &sd->entity.pads[0]; + } + return NULL; +} +EXPORT_SYMBOL(fimc_find_remote_sensor); + +void __fimc_vidioc_querycap(struct device *dev, struct v4l2_capability *cap) +{ + strscpy(cap->driver, dev->driver->name, sizeof(cap->driver)); + strscpy(cap->card, dev->driver->name, sizeof(cap->card)); +} +EXPORT_SYMBOL(__fimc_vidioc_querycap); + +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/samsung/exynos4-is/common.h b/drivers/media/platform/samsung/exynos4-is/common.h new file mode 100644 index 000000000..0389b66e5 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/common.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + */ + +#include +#include +#include +#include + +struct v4l2_subdev *fimc_find_remote_sensor(struct media_entity *entity); +void __fimc_vidioc_querycap(struct device *dev, struct v4l2_capability *cap); diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-capture.c b/drivers/media/platform/samsung/exynos4-is/fimc-capture.c new file mode 100644 index 000000000..beaee54ee --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-capture.c @@ -0,0 +1,1894 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung S5P/EXYNOS4 SoC series camera interface (camera capture) driver + * + * Copyright (C) 2010 - 2012 Samsung Electronics Co., Ltd. + * Sylwester Nawrocki + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "fimc-core.h" +#include "fimc-reg.h" +#include "media-dev.h" + +static int fimc_capture_hw_init(struct fimc_dev *fimc) +{ + struct fimc_source_info *si = &fimc->vid_cap.source_config; + struct fimc_ctx *ctx = fimc->vid_cap.ctx; + int ret; + unsigned long flags; + + if (ctx == NULL || ctx->s_frame.fmt == NULL) + return -EINVAL; + + if (si->fimc_bus_type == FIMC_BUS_TYPE_ISP_WRITEBACK) { + ret = fimc_hw_camblk_cfg_writeback(fimc); + if (ret < 0) + return ret; + } + + spin_lock_irqsave(&fimc->slock, flags); + fimc_prepare_dma_offset(ctx, &ctx->d_frame); + fimc_set_yuv_order(ctx); + + fimc_hw_set_camera_polarity(fimc, si); + fimc_hw_set_camera_type(fimc, si); + fimc_hw_set_camera_source(fimc, si); + fimc_hw_set_camera_offset(fimc, &ctx->s_frame); + + ret = fimc_set_scaler_info(ctx); + if (!ret) { + fimc_hw_set_input_path(ctx); + fimc_hw_set_prescaler(ctx); + fimc_hw_set_mainscaler(ctx); + fimc_hw_set_target_format(ctx); + fimc_hw_set_rotation(ctx); + fimc_hw_set_effect(ctx); + fimc_hw_set_output_path(ctx); + fimc_hw_set_out_dma(ctx); + if (fimc->drv_data->alpha_color) + fimc_hw_set_rgb_alpha(ctx); + clear_bit(ST_CAPT_APPLY_CFG, &fimc->state); + } + spin_unlock_irqrestore(&fimc->slock, flags); + return ret; +} + +/* + * Reinitialize the driver so it is ready to start the streaming again. + * Set fimc->state to indicate stream off and the hardware shut down state. + * If not suspending (@suspend is false), return any buffers to videobuf2. + * Otherwise put any owned buffers onto the pending buffers queue, so they + * can be re-spun when the device is being resumed. Also perform FIMC + * software reset and disable streaming on the whole pipeline if required. + */ +static int fimc_capture_state_cleanup(struct fimc_dev *fimc, bool suspend) +{ + struct fimc_vid_cap *cap = &fimc->vid_cap; + struct fimc_vid_buffer *buf; + unsigned long flags; + bool streaming; + + spin_lock_irqsave(&fimc->slock, flags); + streaming = fimc->state & (1 << ST_CAPT_ISP_STREAM); + + fimc->state &= ~(1 << ST_CAPT_RUN | 1 << ST_CAPT_SHUT | + 1 << ST_CAPT_STREAM | 1 << ST_CAPT_ISP_STREAM); + if (suspend) + fimc->state |= (1 << ST_CAPT_SUSPENDED); + else + fimc->state &= ~(1 << ST_CAPT_PEND | 1 << ST_CAPT_SUSPENDED); + + /* Release unused buffers */ + while (!suspend && !list_empty(&cap->pending_buf_q)) { + buf = fimc_pending_queue_pop(cap); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + /* If suspending put unused buffers onto pending queue */ + while (!list_empty(&cap->active_buf_q)) { + buf = fimc_active_queue_pop(cap); + if (suspend) + fimc_pending_queue_add(cap, buf); + else + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + fimc_hw_reset(fimc); + cap->buf_index = 0; + + spin_unlock_irqrestore(&fimc->slock, flags); + + if (streaming) + return fimc_pipeline_call(&cap->ve, set_stream, 0); + else + return 0; +} + +static int fimc_stop_capture(struct fimc_dev *fimc, bool suspend) +{ + unsigned long flags; + + if (!fimc_capture_active(fimc)) + return 0; + + spin_lock_irqsave(&fimc->slock, flags); + set_bit(ST_CAPT_SHUT, &fimc->state); + fimc_deactivate_capture(fimc); + spin_unlock_irqrestore(&fimc->slock, flags); + + wait_event_timeout(fimc->irq_queue, + !test_bit(ST_CAPT_SHUT, &fimc->state), + (2*HZ/10)); /* 200 ms */ + + return fimc_capture_state_cleanup(fimc, suspend); +} + +/** + * fimc_capture_config_update - apply the camera interface configuration + * @ctx: FIMC capture context + * + * To be called from within the interrupt handler with fimc.slock + * spinlock held. It updates the camera pixel crop, rotation and + * image flip in H/W. + */ +static int fimc_capture_config_update(struct fimc_ctx *ctx) +{ + struct fimc_dev *fimc = ctx->fimc_dev; + int ret; + + fimc_hw_set_camera_offset(fimc, &ctx->s_frame); + + ret = fimc_set_scaler_info(ctx); + if (ret) + return ret; + + fimc_hw_set_prescaler(ctx); + fimc_hw_set_mainscaler(ctx); + fimc_hw_set_target_format(ctx); + fimc_hw_set_rotation(ctx); + fimc_hw_set_effect(ctx); + fimc_prepare_dma_offset(ctx, &ctx->d_frame); + fimc_hw_set_out_dma(ctx); + if (fimc->drv_data->alpha_color) + fimc_hw_set_rgb_alpha(ctx); + + clear_bit(ST_CAPT_APPLY_CFG, &fimc->state); + return ret; +} + +void fimc_capture_irq_handler(struct fimc_dev *fimc, int deq_buf) +{ + struct fimc_vid_cap *cap = &fimc->vid_cap; + struct fimc_pipeline *p = to_fimc_pipeline(cap->ve.pipe); + struct v4l2_subdev *csis = p->subdevs[IDX_CSIS]; + struct fimc_frame *f = &cap->ctx->d_frame; + struct fimc_vid_buffer *v_buf; + + if (test_and_clear_bit(ST_CAPT_SHUT, &fimc->state)) { + wake_up(&fimc->irq_queue); + goto done; + } + + if (!list_empty(&cap->active_buf_q) && + test_bit(ST_CAPT_RUN, &fimc->state) && deq_buf) { + v_buf = fimc_active_queue_pop(cap); + + v_buf->vb.vb2_buf.timestamp = ktime_get_ns(); + v_buf->vb.sequence = cap->frame_count++; + + vb2_buffer_done(&v_buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + + if (!list_empty(&cap->pending_buf_q)) { + + v_buf = fimc_pending_queue_pop(cap); + fimc_hw_set_output_addr(fimc, &v_buf->addr, cap->buf_index); + v_buf->index = cap->buf_index; + + /* Move the buffer to the capture active queue */ + fimc_active_queue_add(cap, v_buf); + + dbg("next frame: %d, done frame: %d", + fimc_hw_get_frame_index(fimc), v_buf->index); + + if (++cap->buf_index >= FIMC_MAX_OUT_BUFS) + cap->buf_index = 0; + } + /* + * Set up a buffer at MIPI-CSIS if current image format + * requires the frame embedded data capture. + */ + if (f->fmt->mdataplanes && !list_empty(&cap->active_buf_q)) { + unsigned int plane = ffs(f->fmt->mdataplanes) - 1; + unsigned int size = f->payload[plane]; + s32 index = fimc_hw_get_frame_index(fimc); + void *vaddr; + + list_for_each_entry(v_buf, &cap->active_buf_q, list) { + if (v_buf->index != index) + continue; + vaddr = vb2_plane_vaddr(&v_buf->vb.vb2_buf, plane); + v4l2_subdev_call(csis, video, s_rx_buffer, + vaddr, &size); + break; + } + } + + if (cap->active_buf_cnt == 0) { + if (deq_buf) + clear_bit(ST_CAPT_RUN, &fimc->state); + + if (++cap->buf_index >= FIMC_MAX_OUT_BUFS) + cap->buf_index = 0; + } else { + set_bit(ST_CAPT_RUN, &fimc->state); + } + + if (test_bit(ST_CAPT_APPLY_CFG, &fimc->state)) + fimc_capture_config_update(cap->ctx); +done: + if (cap->active_buf_cnt == 1) { + fimc_deactivate_capture(fimc); + clear_bit(ST_CAPT_STREAM, &fimc->state); + } + + dbg("frame: %d, active_buf_cnt: %d", + fimc_hw_get_frame_index(fimc), cap->active_buf_cnt); +} + + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct fimc_ctx *ctx = q->drv_priv; + struct fimc_dev *fimc = ctx->fimc_dev; + struct fimc_vid_cap *vid_cap = &fimc->vid_cap; + int min_bufs; + int ret; + + vid_cap->frame_count = 0; + + ret = fimc_capture_hw_init(fimc); + if (ret) { + fimc_capture_state_cleanup(fimc, false); + return ret; + } + + set_bit(ST_CAPT_PEND, &fimc->state); + + min_bufs = fimc->vid_cap.reqbufs_count > 1 ? 2 : 1; + + if (vid_cap->active_buf_cnt >= min_bufs && + !test_and_set_bit(ST_CAPT_STREAM, &fimc->state)) { + fimc_activate_capture(ctx); + + if (!test_and_set_bit(ST_CAPT_ISP_STREAM, &fimc->state)) + return fimc_pipeline_call(&vid_cap->ve, set_stream, 1); + } + + return 0; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct fimc_ctx *ctx = q->drv_priv; + struct fimc_dev *fimc = ctx->fimc_dev; + + if (!fimc_capture_active(fimc)) + return; + + fimc_stop_capture(fimc, false); +} + +int fimc_capture_suspend(struct fimc_dev *fimc) +{ + bool suspend = fimc_capture_busy(fimc); + + int ret = fimc_stop_capture(fimc, suspend); + if (ret) + return ret; + return fimc_pipeline_call(&fimc->vid_cap.ve, close); +} + +static void buffer_queue(struct vb2_buffer *vb); + +int fimc_capture_resume(struct fimc_dev *fimc) +{ + struct fimc_vid_cap *vid_cap = &fimc->vid_cap; + struct exynos_video_entity *ve = &vid_cap->ve; + struct fimc_vid_buffer *buf; + int i; + + if (!test_and_clear_bit(ST_CAPT_SUSPENDED, &fimc->state)) + return 0; + + INIT_LIST_HEAD(&fimc->vid_cap.active_buf_q); + vid_cap->buf_index = 0; + fimc_pipeline_call(ve, open, &ve->vdev.entity, false); + fimc_capture_hw_init(fimc); + + clear_bit(ST_CAPT_SUSPENDED, &fimc->state); + + for (i = 0; i < vid_cap->reqbufs_count; i++) { + if (list_empty(&vid_cap->pending_buf_q)) + break; + buf = fimc_pending_queue_pop(vid_cap); + buffer_queue(&buf->vb.vb2_buf); + } + return 0; + +} + +static int queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct fimc_ctx *ctx = vq->drv_priv; + struct fimc_frame *frame = &ctx->d_frame; + struct fimc_fmt *fmt = frame->fmt; + unsigned long wh = frame->f_width * frame->f_height; + int i; + + if (fmt == NULL) + return -EINVAL; + + if (*num_planes) { + if (*num_planes != fmt->memplanes) + return -EINVAL; + for (i = 0; i < *num_planes; i++) + if (sizes[i] < (wh * fmt->depth[i]) / 8) + return -EINVAL; + return 0; + } + + *num_planes = fmt->memplanes; + + for (i = 0; i < fmt->memplanes; i++) { + unsigned int size = (wh * fmt->depth[i]) / 8; + + if (fimc_fmt_is_user_defined(fmt->color)) + sizes[i] = frame->payload[i]; + else + sizes[i] = max_t(u32, size, frame->payload[i]); + } + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct fimc_ctx *ctx = vq->drv_priv; + int i; + + if (ctx->d_frame.fmt == NULL) + return -EINVAL; + + for (i = 0; i < ctx->d_frame.fmt->memplanes; i++) { + unsigned long size = ctx->d_frame.payload[i]; + + if (vb2_plane_size(vb, i) < size) { + v4l2_err(&ctx->fimc_dev->vid_cap.ve.vdev, + "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct fimc_vid_buffer *buf + = container_of(vbuf, struct fimc_vid_buffer, vb); + struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct fimc_dev *fimc = ctx->fimc_dev; + struct fimc_vid_cap *vid_cap = &fimc->vid_cap; + struct exynos_video_entity *ve = &vid_cap->ve; + unsigned long flags; + int min_bufs; + + spin_lock_irqsave(&fimc->slock, flags); + fimc_prepare_addr(ctx, &buf->vb.vb2_buf, &ctx->d_frame, &buf->addr); + + if (!test_bit(ST_CAPT_SUSPENDED, &fimc->state) && + !test_bit(ST_CAPT_STREAM, &fimc->state) && + vid_cap->active_buf_cnt < FIMC_MAX_OUT_BUFS) { + /* Setup the buffer directly for processing. */ + int buf_id = (vid_cap->reqbufs_count == 1) ? -1 : + vid_cap->buf_index; + + fimc_hw_set_output_addr(fimc, &buf->addr, buf_id); + buf->index = vid_cap->buf_index; + fimc_active_queue_add(vid_cap, buf); + + if (++vid_cap->buf_index >= FIMC_MAX_OUT_BUFS) + vid_cap->buf_index = 0; + } else { + fimc_pending_queue_add(vid_cap, buf); + } + + min_bufs = vid_cap->reqbufs_count > 1 ? 2 : 1; + + + if (vb2_is_streaming(&vid_cap->vbq) && + vid_cap->active_buf_cnt >= min_bufs && + !test_and_set_bit(ST_CAPT_STREAM, &fimc->state)) { + int ret; + + fimc_activate_capture(ctx); + spin_unlock_irqrestore(&fimc->slock, flags); + + if (test_and_set_bit(ST_CAPT_ISP_STREAM, &fimc->state)) + return; + + ret = fimc_pipeline_call(ve, set_stream, 1); + if (ret < 0) + v4l2_err(&ve->vdev, "stream on failed: %d\n", ret); + return; + } + spin_unlock_irqrestore(&fimc->slock, flags); +} + +static const struct vb2_ops fimc_capture_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +static int fimc_capture_set_default_format(struct fimc_dev *fimc); + +static int fimc_capture_open(struct file *file) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_vid_cap *vc = &fimc->vid_cap; + struct exynos_video_entity *ve = &vc->ve; + int ret = -EBUSY; + + dbg("pid: %d, state: 0x%lx", task_pid_nr(current), fimc->state); + + mutex_lock(&fimc->lock); + + if (fimc_m2m_active(fimc)) + goto unlock; + + set_bit(ST_CAPT_BUSY, &fimc->state); + ret = pm_runtime_resume_and_get(&fimc->pdev->dev); + if (ret < 0) + goto unlock; + + ret = v4l2_fh_open(file); + if (ret) { + pm_runtime_put_sync(&fimc->pdev->dev); + goto unlock; + } + + if (v4l2_fh_is_singular_file(file)) { + fimc_md_graph_lock(ve); + + ret = fimc_pipeline_call(ve, open, &ve->vdev.entity, true); + + if (ret == 0) + ve->vdev.entity.use_count++; + + fimc_md_graph_unlock(ve); + + if (ret == 0) + ret = fimc_capture_set_default_format(fimc); + + if (ret < 0) { + clear_bit(ST_CAPT_BUSY, &fimc->state); + pm_runtime_put_sync(&fimc->pdev->dev); + v4l2_fh_release(file); + } + } +unlock: + mutex_unlock(&fimc->lock); + return ret; +} + +static int fimc_capture_release(struct file *file) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_vid_cap *vc = &fimc->vid_cap; + bool close = v4l2_fh_is_singular_file(file); + int ret; + + dbg("pid: %d, state: 0x%lx", task_pid_nr(current), fimc->state); + + mutex_lock(&fimc->lock); + + if (close && vc->streaming) { + video_device_pipeline_stop(&vc->ve.vdev); + vc->streaming = false; + } + + ret = _vb2_fop_release(file, NULL); + + if (close) { + clear_bit(ST_CAPT_BUSY, &fimc->state); + fimc_pipeline_call(&vc->ve, close); + clear_bit(ST_CAPT_SUSPENDED, &fimc->state); + + fimc_md_graph_lock(&vc->ve); + vc->ve.vdev.entity.use_count--; + fimc_md_graph_unlock(&vc->ve); + } + + pm_runtime_put_sync(&fimc->pdev->dev); + mutex_unlock(&fimc->lock); + + return ret; +} + +static const struct v4l2_file_operations fimc_capture_fops = { + .owner = THIS_MODULE, + .open = fimc_capture_open, + .release = fimc_capture_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* + * Format and crop negotiation helpers + */ + +static struct fimc_fmt *fimc_capture_try_format(struct fimc_ctx *ctx, + u32 *width, u32 *height, + u32 *code, u32 *fourcc, int pad) +{ + bool rotation = ctx->rotation == 90 || ctx->rotation == 270; + struct fimc_dev *fimc = ctx->fimc_dev; + const struct fimc_variant *var = fimc->variant; + const struct fimc_pix_limit *pl = var->pix_limit; + struct fimc_frame *dst = &ctx->d_frame; + u32 depth, min_w, max_w, min_h, align_h = 3; + u32 mask = FMT_FLAGS_CAM; + struct fimc_fmt *ffmt; + + /* Conversion from/to JPEG or User Defined format is not supported */ + if (code && ctx->s_frame.fmt && pad == FIMC_SD_PAD_SOURCE && + fimc_fmt_is_user_defined(ctx->s_frame.fmt->color)) + *code = ctx->s_frame.fmt->mbus_code; + + if (fourcc && *fourcc != V4L2_PIX_FMT_JPEG && pad == FIMC_SD_PAD_SOURCE) + mask |= FMT_FLAGS_M2M; + + if (pad == FIMC_SD_PAD_SINK_FIFO) + mask = FMT_FLAGS_WRITEBACK; + + ffmt = fimc_find_format(fourcc, code, mask, 0); + if (WARN_ON(!ffmt)) + return NULL; + + if (code) + *code = ffmt->mbus_code; + if (fourcc) + *fourcc = ffmt->fourcc; + + if (pad != FIMC_SD_PAD_SOURCE) { + max_w = fimc_fmt_is_user_defined(ffmt->color) ? + pl->scaler_dis_w : pl->scaler_en_w; + /* Apply the camera input interface pixel constraints */ + v4l_bound_align_image(width, max_t(u32, *width, 32), max_w, 4, + height, max_t(u32, *height, 32), + FIMC_CAMIF_MAX_HEIGHT, + fimc_fmt_is_user_defined(ffmt->color) ? + 3 : 1, + 0); + return ffmt; + } + /* Can't scale or crop in transparent (JPEG) transfer mode */ + if (fimc_fmt_is_user_defined(ffmt->color)) { + *width = ctx->s_frame.f_width; + *height = ctx->s_frame.f_height; + return ffmt; + } + /* Apply the scaler and the output DMA constraints */ + max_w = rotation ? pl->out_rot_en_w : pl->out_rot_dis_w; + if (ctx->state & FIMC_COMPOSE) { + min_w = dst->offs_h + dst->width; + min_h = dst->offs_v + dst->height; + } else { + min_w = var->min_out_pixsize; + min_h = var->min_out_pixsize; + } + if (var->min_vsize_align == 1 && !rotation) + align_h = fimc_fmt_is_rgb(ffmt->color) ? 0 : 1; + + depth = fimc_get_format_depth(ffmt); + v4l_bound_align_image(width, min_w, max_w, + ffs(var->min_out_pixsize) - 1, + height, min_h, FIMC_CAMIF_MAX_HEIGHT, + align_h, + 64/(ALIGN(depth, 8))); + + dbg("pad%d: code: 0x%x, %dx%d. dst fmt: %dx%d", + pad, code ? *code : 0, *width, *height, + dst->f_width, dst->f_height); + + return ffmt; +} + +static void fimc_capture_try_selection(struct fimc_ctx *ctx, + struct v4l2_rect *r, + int target) +{ + bool rotate = ctx->rotation == 90 || ctx->rotation == 270; + struct fimc_dev *fimc = ctx->fimc_dev; + const struct fimc_variant *var = fimc->variant; + const struct fimc_pix_limit *pl = var->pix_limit; + struct fimc_frame *sink = &ctx->s_frame; + u32 max_w, max_h, min_w = 0, min_h = 0, min_sz; + u32 align_sz = 0, align_h = 4; + u32 max_sc_h, max_sc_v; + + /* In JPEG transparent transfer mode cropping is not supported */ + if (fimc_fmt_is_user_defined(ctx->d_frame.fmt->color)) { + r->width = sink->f_width; + r->height = sink->f_height; + r->left = r->top = 0; + return; + } + if (target == V4L2_SEL_TGT_COMPOSE) { + u32 tmp_min_h = ffs(sink->width) - 3; + u32 tmp_min_v = ffs(sink->height) - 1; + + if (ctx->rotation != 90 && ctx->rotation != 270) + align_h = 1; + max_sc_h = min(SCALER_MAX_HRATIO, 1 << tmp_min_h); + max_sc_v = min(SCALER_MAX_VRATIO, 1 << tmp_min_v); + min_sz = var->min_out_pixsize; + } else { + u32 depth = fimc_get_format_depth(sink->fmt); + align_sz = 64/ALIGN(depth, 8); + min_sz = var->min_inp_pixsize; + min_w = min_h = min_sz; + max_sc_h = max_sc_v = 1; + } + /* + * For the compose rectangle the following constraints must be met: + * - it must fit in the sink pad format rectangle (f_width/f_height); + * - maximum downscaling ratio is 64; + * - maximum crop size depends if the rotator is used or not; + * - the sink pad format width/height must be 4 multiple of the + * prescaler ratios determined by sink pad size and source pad crop, + * the prescaler ratio is returned by fimc_get_scaler_factor(). + */ + max_w = min_t(u32, + rotate ? pl->out_rot_en_w : pl->out_rot_dis_w, + rotate ? sink->f_height : sink->f_width); + max_h = min_t(u32, FIMC_CAMIF_MAX_HEIGHT, sink->f_height); + + if (target == V4L2_SEL_TGT_COMPOSE) { + min_w = min_t(u32, max_w, sink->f_width / max_sc_h); + min_h = min_t(u32, max_h, sink->f_height / max_sc_v); + if (rotate) { + swap(max_sc_h, max_sc_v); + swap(min_w, min_h); + } + } + v4l_bound_align_image(&r->width, min_w, max_w, ffs(min_sz) - 1, + &r->height, min_h, max_h, align_h, + align_sz); + /* Adjust left/top if crop/compose rectangle is out of bounds */ + r->left = clamp_t(u32, r->left, 0, sink->f_width - r->width); + r->top = clamp_t(u32, r->top, 0, sink->f_height - r->height); + r->left = round_down(r->left, var->hor_offs_align); + + dbg("target %#x: (%d,%d)/%dx%d, sink fmt: %dx%d", + target, r->left, r->top, r->width, r->height, + sink->f_width, sink->f_height); +} + +/* + * The video node ioctl operations + */ +static int fimc_cap_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct fimc_dev *fimc = video_drvdata(file); + + __fimc_vidioc_querycap(&fimc->pdev->dev, cap); + return 0; +} + +static int fimc_cap_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct fimc_fmt *fmt; + + fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_CAM | FMT_FLAGS_M2M, + f->index); + if (!fmt) + return -EINVAL; + f->pixelformat = fmt->fourcc; + return 0; +} + +static struct media_entity *fimc_pipeline_get_head(struct media_entity *me) +{ + struct media_pad *pad = &me->pads[0]; + + while (!(pad->flags & MEDIA_PAD_FL_SOURCE)) { + pad = media_pad_remote_pad_first(pad); + if (!pad) + break; + me = pad->entity; + pad = &me->pads[0]; + } + + return me; +} + +/** + * fimc_pipeline_try_format - negotiate and/or set formats at pipeline + * elements + * @ctx: FIMC capture context + * @tfmt: media bus format to try/set on subdevs + * @fmt_id: fimc pixel format id corresponding to returned @tfmt (output) + * @set: true to set format on subdevs, false to try only + */ +static int fimc_pipeline_try_format(struct fimc_ctx *ctx, + struct v4l2_mbus_framefmt *tfmt, + struct fimc_fmt **fmt_id, + bool set) +{ + struct fimc_dev *fimc = ctx->fimc_dev; + struct fimc_pipeline *p = to_fimc_pipeline(fimc->vid_cap.ve.pipe); + struct v4l2_subdev *sd = p->subdevs[IDX_SENSOR]; + struct v4l2_subdev_format sfmt = { + .which = set ? V4L2_SUBDEV_FORMAT_ACTIVE + : V4L2_SUBDEV_FORMAT_TRY, + }; + struct v4l2_mbus_framefmt *mf = &sfmt.format; + struct media_entity *me; + struct fimc_fmt *ffmt; + struct media_pad *pad; + int ret, i = 1; + u32 fcc; + + if (WARN_ON(!sd || !tfmt)) + return -EINVAL; + + sfmt.format = *tfmt; + + me = fimc_pipeline_get_head(&sd->entity); + + while (1) { + ffmt = fimc_find_format(NULL, mf->code != 0 ? &mf->code : NULL, + FMT_FLAGS_CAM, i++); + if (ffmt == NULL) { + /* + * Notify user-space if common pixel code for + * host and sensor does not exist. + */ + return -EINVAL; + } + mf->code = tfmt->code = ffmt->mbus_code; + + /* set format on all pipeline subdevs */ + while (me != &fimc->vid_cap.subdev.entity) { + sd = media_entity_to_v4l2_subdev(me); + + sfmt.pad = 0; + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &sfmt); + if (ret) + return ret; + + if (me->pads[0].flags & MEDIA_PAD_FL_SINK) { + sfmt.pad = me->num_pads - 1; + mf->code = tfmt->code; + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, + &sfmt); + if (ret) + return ret; + } + + pad = media_pad_remote_pad_first(&me->pads[sfmt.pad]); + if (!pad) + return -EINVAL; + me = pad->entity; + } + + if (mf->code != tfmt->code) + continue; + + fcc = ffmt->fourcc; + tfmt->width = mf->width; + tfmt->height = mf->height; + ffmt = fimc_capture_try_format(ctx, &tfmt->width, &tfmt->height, + NULL, &fcc, FIMC_SD_PAD_SINK_CAM); + ffmt = fimc_capture_try_format(ctx, &tfmt->width, &tfmt->height, + NULL, &fcc, FIMC_SD_PAD_SOURCE); + if (ffmt && ffmt->mbus_code) + mf->code = ffmt->mbus_code; + if (mf->width != tfmt->width || mf->height != tfmt->height) + continue; + tfmt->code = mf->code; + break; + } + + if (fmt_id && ffmt) + *fmt_id = ffmt; + *tfmt = *mf; + + return 0; +} + +/** + * fimc_get_sensor_frame_desc - query the sensor for media bus frame parameters + * @sensor: pointer to the sensor subdev + * @plane_fmt: provides plane sizes corresponding to the frame layout entries + * @num_planes: number of planes + * @try: true to set the frame parameters, false to query only + * + * This function is used by this driver only for compressed/blob data formats. + */ +static int fimc_get_sensor_frame_desc(struct v4l2_subdev *sensor, + struct v4l2_plane_pix_format *plane_fmt, + unsigned int num_planes, bool try) +{ + struct v4l2_mbus_frame_desc fd; + int i, ret; + int pad; + + for (i = 0; i < num_planes; i++) + fd.entry[i].length = plane_fmt[i].sizeimage; + + pad = sensor->entity.num_pads - 1; + if (try) + ret = v4l2_subdev_call(sensor, pad, set_frame_desc, pad, &fd); + else + ret = v4l2_subdev_call(sensor, pad, get_frame_desc, pad, &fd); + + if (ret < 0) + return ret; + + if (num_planes != fd.num_entries) + return -EINVAL; + + for (i = 0; i < num_planes; i++) + plane_fmt[i].sizeimage = fd.entry[i].length; + + if (fd.entry[0].length > FIMC_MAX_JPEG_BUF_SIZE) { + v4l2_err(sensor->v4l2_dev, "Unsupported buffer size: %u\n", + fd.entry[0].length); + + return -EINVAL; + } + + return 0; +} + +static int fimc_cap_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_dev *fimc = video_drvdata(file); + + __fimc_get_format(&fimc->vid_cap.ctx->d_frame, f); + return 0; +} + +/* + * Try or set format on the fimc.X.capture video node and additionally + * on the whole pipeline if @try is false. + * Locking: the caller must _not_ hold the graph mutex. + */ +static int __video_try_or_set_format(struct fimc_dev *fimc, + struct v4l2_format *f, bool try, + struct fimc_fmt **inp_fmt, + struct fimc_fmt **out_fmt) +{ + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct fimc_vid_cap *vc = &fimc->vid_cap; + struct exynos_video_entity *ve = &vc->ve; + struct fimc_ctx *ctx = vc->ctx; + unsigned int width = 0, height = 0; + int ret = 0; + + /* Pre-configure format at the camera input interface, for JPEG only */ + if (fimc_jpeg_fourcc(pix->pixelformat)) { + fimc_capture_try_format(ctx, &pix->width, &pix->height, + NULL, &pix->pixelformat, + FIMC_SD_PAD_SINK_CAM); + if (try) { + width = pix->width; + height = pix->height; + } else { + ctx->s_frame.f_width = pix->width; + ctx->s_frame.f_height = pix->height; + } + } + + /* Try the format at the scaler and the DMA output */ + *out_fmt = fimc_capture_try_format(ctx, &pix->width, &pix->height, + NULL, &pix->pixelformat, + FIMC_SD_PAD_SOURCE); + if (*out_fmt == NULL) + return -EINVAL; + + /* Restore image width/height for JPEG (no resizing supported). */ + if (try && fimc_jpeg_fourcc(pix->pixelformat)) { + pix->width = width; + pix->height = height; + } + + /* Try to match format at the host and the sensor */ + if (!vc->user_subdev_api) { + struct v4l2_mbus_framefmt mbus_fmt; + struct v4l2_mbus_framefmt *mf; + + mf = try ? &mbus_fmt : &fimc->vid_cap.ci_fmt; + + mf->code = (*out_fmt)->mbus_code; + mf->width = pix->width; + mf->height = pix->height; + + fimc_md_graph_lock(ve); + ret = fimc_pipeline_try_format(ctx, mf, inp_fmt, try); + fimc_md_graph_unlock(ve); + + if (ret < 0) + return ret; + + pix->width = mf->width; + pix->height = mf->height; + } + + fimc_adjust_mplane_format(*out_fmt, pix->width, pix->height, pix); + + if ((*out_fmt)->flags & FMT_FLAGS_COMPRESSED) { + struct v4l2_subdev *sensor; + + fimc_md_graph_lock(ve); + + sensor = __fimc_md_get_subdev(ve->pipe, IDX_SENSOR); + if (sensor) + fimc_get_sensor_frame_desc(sensor, pix->plane_fmt, + (*out_fmt)->memplanes, try); + else + ret = -EPIPE; + + fimc_md_graph_unlock(ve); + } + + return ret; +} + +static int fimc_cap_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_fmt *out_fmt = NULL, *inp_fmt = NULL; + + return __video_try_or_set_format(fimc, f, true, &inp_fmt, &out_fmt); +} + +static void fimc_capture_mark_jpeg_xfer(struct fimc_ctx *ctx, + enum fimc_color_fmt color) +{ + bool jpeg = fimc_fmt_is_user_defined(color); + + ctx->scaler.enabled = !jpeg; + fimc_ctrls_activate(ctx, !jpeg); + + if (jpeg) + set_bit(ST_CAPT_JPEG, &ctx->fimc_dev->state); + else + clear_bit(ST_CAPT_JPEG, &ctx->fimc_dev->state); +} + +static int __fimc_capture_set_format(struct fimc_dev *fimc, + struct v4l2_format *f) +{ + struct fimc_vid_cap *vc = &fimc->vid_cap; + struct fimc_ctx *ctx = vc->ctx; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct fimc_frame *ff = &ctx->d_frame; + struct fimc_fmt *inp_fmt = NULL; + int ret, i; + + if (vb2_is_busy(&fimc->vid_cap.vbq)) + return -EBUSY; + + ret = __video_try_or_set_format(fimc, f, false, &inp_fmt, &ff->fmt); + if (ret < 0) + return ret; + + /* Update RGB Alpha control state and value range */ + fimc_alpha_ctrl_update(ctx); + + for (i = 0; i < ff->fmt->memplanes; i++) { + ff->bytesperline[i] = pix->plane_fmt[i].bytesperline; + ff->payload[i] = pix->plane_fmt[i].sizeimage; + } + + set_frame_bounds(ff, pix->width, pix->height); + /* Reset the composition rectangle if not yet configured */ + if (!(ctx->state & FIMC_COMPOSE)) + set_frame_crop(ff, 0, 0, pix->width, pix->height); + + fimc_capture_mark_jpeg_xfer(ctx, ff->fmt->color); + + /* Reset cropping and set format at the camera interface input */ + if (!vc->user_subdev_api) { + ctx->s_frame.fmt = inp_fmt; + set_frame_bounds(&ctx->s_frame, pix->width, pix->height); + set_frame_crop(&ctx->s_frame, 0, 0, pix->width, pix->height); + } + + return ret; +} + +static int fimc_cap_s_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct fimc_dev *fimc = video_drvdata(file); + + return __fimc_capture_set_format(fimc, f); +} + +static int fimc_cap_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct exynos_video_entity *ve = &fimc->vid_cap.ve; + struct v4l2_subdev *sd; + + if (i->index != 0) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + fimc_md_graph_lock(ve); + sd = __fimc_md_get_subdev(ve->pipe, IDX_SENSOR); + fimc_md_graph_unlock(ve); + + if (sd) + strscpy(i->name, sd->name, sizeof(i->name)); + + return 0; +} + +static int fimc_cap_s_input(struct file *file, void *priv, unsigned int i) +{ + return i == 0 ? i : -EINVAL; +} + +static int fimc_cap_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +/** + * fimc_pipeline_validate - check for formats inconsistencies + * between source and sink pad of each link + * @fimc: the FIMC device this context applies to + * + * Return 0 if all formats match or -EPIPE otherwise. + */ +static int fimc_pipeline_validate(struct fimc_dev *fimc) +{ + struct v4l2_subdev_format sink_fmt, src_fmt; + struct fimc_vid_cap *vc = &fimc->vid_cap; + struct v4l2_subdev *sd = &vc->subdev; + struct fimc_pipeline *p = to_fimc_pipeline(vc->ve.pipe); + struct media_pad *sink_pad, *src_pad; + int i, ret; + + while (1) { + /* + * Find current entity sink pad and any remote sink pad linked + * to it. We stop if there is no sink pad in current entity or + * it is not linked to any other remote entity. + */ + src_pad = NULL; + + for (i = 0; i < sd->entity.num_pads; i++) { + struct media_pad *p = &sd->entity.pads[i]; + + if (p->flags & MEDIA_PAD_FL_SINK) { + sink_pad = p; + src_pad = media_pad_remote_pad_first(sink_pad); + if (src_pad) + break; + } + } + + if (!src_pad || !is_media_entity_v4l2_subdev(src_pad->entity)) + break; + + /* Don't call FIMC subdev operation to avoid nested locking */ + if (sd == &vc->subdev) { + struct fimc_frame *ff = &vc->ctx->s_frame; + sink_fmt.format.width = ff->f_width; + sink_fmt.format.height = ff->f_height; + sink_fmt.format.code = ff->fmt ? ff->fmt->mbus_code : 0; + } else { + sink_fmt.pad = sink_pad->index; + sink_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sink_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + } + + /* Retrieve format at the source pad */ + sd = media_entity_to_v4l2_subdev(src_pad->entity); + src_fmt.pad = src_pad->index; + src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &src_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + if (src_fmt.format.width != sink_fmt.format.width || + src_fmt.format.height != sink_fmt.format.height || + src_fmt.format.code != sink_fmt.format.code) + return -EPIPE; + + if (sd == p->subdevs[IDX_SENSOR] && + fimc_user_defined_mbus_fmt(src_fmt.format.code)) { + struct v4l2_plane_pix_format plane_fmt[FIMC_MAX_PLANES]; + struct fimc_frame *frame = &vc->ctx->d_frame; + unsigned int i; + + ret = fimc_get_sensor_frame_desc(sd, plane_fmt, + frame->fmt->memplanes, + false); + if (ret < 0) + return -EPIPE; + + for (i = 0; i < frame->fmt->memplanes; i++) + if (frame->payload[i] < plane_fmt[i].sizeimage) + return -EPIPE; + } + } + return 0; +} + +static int fimc_cap_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_vid_cap *vc = &fimc->vid_cap; + struct fimc_source_info *si = NULL; + struct v4l2_subdev *sd; + int ret; + + if (fimc_capture_active(fimc)) + return -EBUSY; + + ret = video_device_pipeline_start(&vc->ve.vdev, &vc->ve.pipe->mp); + if (ret < 0) + return ret; + + sd = __fimc_md_get_subdev(vc->ve.pipe, IDX_SENSOR); + if (sd) + si = v4l2_get_subdev_hostdata(sd); + + if (si == NULL) { + ret = -EPIPE; + goto err_p_stop; + } + /* + * Save configuration data related to currently attached image + * sensor or other data source, e.g. FIMC-IS. + */ + vc->source_config = *si; + + if (vc->input == GRP_ID_FIMC_IS) + vc->source_config.fimc_bus_type = FIMC_BUS_TYPE_ISP_WRITEBACK; + + if (vc->user_subdev_api) { + ret = fimc_pipeline_validate(fimc); + if (ret < 0) + goto err_p_stop; + } + + ret = vb2_ioctl_streamon(file, priv, type); + if (!ret) { + vc->streaming = true; + return ret; + } + +err_p_stop: + video_device_pipeline_stop(&vc->ve.vdev); + return ret; +} + +static int fimc_cap_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_vid_cap *vc = &fimc->vid_cap; + int ret; + + ret = vb2_ioctl_streamoff(file, priv, type); + if (ret < 0) + return ret; + + if (vc->streaming) { + video_device_pipeline_stop(&vc->ve.vdev); + vc->streaming = false; + } + + return 0; +} + +static int fimc_cap_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct fimc_dev *fimc = video_drvdata(file); + int ret; + + ret = vb2_ioctl_reqbufs(file, priv, reqbufs); + + if (!ret) + fimc->vid_cap.reqbufs_count = reqbufs->count; + + return ret; +} + +static int fimc_cap_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_ctx *ctx = fimc->vid_cap.ctx; + struct fimc_frame *f = &ctx->s_frame; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + f = &ctx->d_frame; + fallthrough; + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = f->o_width; + s->r.height = f->o_height; + return 0; + + case V4L2_SEL_TGT_COMPOSE: + f = &ctx->d_frame; + fallthrough; + case V4L2_SEL_TGT_CROP: + s->r.left = f->offs_h; + s->r.top = f->offs_v; + s->r.width = f->width; + s->r.height = f->height; + return 0; + } + + return -EINVAL; +} + +static int fimc_cap_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_ctx *ctx = fimc->vid_cap.ctx; + struct v4l2_rect rect = s->r; + struct fimc_frame *f; + unsigned long flags; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (s->target == V4L2_SEL_TGT_COMPOSE) + f = &ctx->d_frame; + else if (s->target == V4L2_SEL_TGT_CROP) + f = &ctx->s_frame; + else + return -EINVAL; + + fimc_capture_try_selection(ctx, &rect, s->target); + + if (s->flags & V4L2_SEL_FLAG_LE && + !v4l2_rect_enclosed(&rect, &s->r)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && + !v4l2_rect_enclosed(&s->r, &rect)) + return -ERANGE; + + s->r = rect; + spin_lock_irqsave(&fimc->slock, flags); + set_frame_crop(f, s->r.left, s->r.top, s->r.width, + s->r.height); + spin_unlock_irqrestore(&fimc->slock, flags); + + set_bit(ST_CAPT_APPLY_CFG, &fimc->state); + return 0; +} + +static const struct v4l2_ioctl_ops fimc_capture_ioctl_ops = { + .vidioc_querycap = fimc_cap_querycap, + + .vidioc_enum_fmt_vid_cap = fimc_cap_enum_fmt, + .vidioc_try_fmt_vid_cap_mplane = fimc_cap_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = fimc_cap_s_fmt_mplane, + .vidioc_g_fmt_vid_cap_mplane = fimc_cap_g_fmt_mplane, + + .vidioc_reqbufs = fimc_cap_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + + .vidioc_streamon = fimc_cap_streamon, + .vidioc_streamoff = fimc_cap_streamoff, + + .vidioc_g_selection = fimc_cap_g_selection, + .vidioc_s_selection = fimc_cap_s_selection, + + .vidioc_enum_input = fimc_cap_enum_input, + .vidioc_s_input = fimc_cap_s_input, + .vidioc_g_input = fimc_cap_g_input, +}; + +/* Capture subdev media entity operations */ +static int fimc_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct fimc_dev *fimc = v4l2_get_subdevdata(sd); + struct fimc_vid_cap *vc = &fimc->vid_cap; + struct v4l2_subdev *sensor; + + if (!is_media_entity_v4l2_subdev(remote->entity)) + return -EINVAL; + + if (WARN_ON(fimc == NULL)) + return 0; + + dbg("%s --> %s, flags: 0x%x. input: 0x%x", + local->entity->name, remote->entity->name, flags, + fimc->vid_cap.input); + + if (!(flags & MEDIA_LNK_FL_ENABLED)) { + fimc->vid_cap.input = 0; + return 0; + } + + if (vc->input != 0) + return -EBUSY; + + vc->input = sd->grp_id; + + if (vc->user_subdev_api) + return 0; + + /* Inherit V4L2 controls from the image sensor subdev. */ + sensor = fimc_find_remote_sensor(&vc->subdev.entity); + if (sensor == NULL) + return 0; + + return v4l2_ctrl_add_handler(&vc->ctx->ctrls.handler, + sensor->ctrl_handler, NULL, true); +} + +static const struct media_entity_operations fimc_sd_media_ops = { + .link_setup = fimc_link_setup, +}; + +/** + * fimc_sensor_notify - v4l2_device notification from a sensor subdev + * @sd: pointer to a subdev generating the notification + * @notification: the notification type, must be S5P_FIMC_TX_END_NOTIFY + * @arg: pointer to an u32 type integer that stores the frame payload value + * + * The End Of Frame notification sent by sensor subdev in its still capture + * mode. If there is only a single VSYNC generated by the sensor at the + * beginning of a frame transmission, FIMC does not issue the LastIrq + * (end of frame) interrupt. And this notification is used to complete the + * frame capture and returning a buffer to user-space. Subdev drivers should + * call this notification from their last 'End of frame capture' interrupt. + */ +void fimc_sensor_notify(struct v4l2_subdev *sd, unsigned int notification, + void *arg) +{ + struct fimc_source_info *si; + struct fimc_vid_buffer *buf; + struct fimc_md *fmd; + struct fimc_dev *fimc; + unsigned long flags; + + if (sd == NULL) + return; + + si = v4l2_get_subdev_hostdata(sd); + fmd = entity_to_fimc_mdev(&sd->entity); + + spin_lock_irqsave(&fmd->slock, flags); + + fimc = si ? source_to_sensor_info(si)->host : NULL; + + if (fimc && arg && notification == S5P_FIMC_TX_END_NOTIFY && + test_bit(ST_CAPT_PEND, &fimc->state)) { + unsigned long irq_flags; + spin_lock_irqsave(&fimc->slock, irq_flags); + if (!list_empty(&fimc->vid_cap.active_buf_q)) { + buf = list_entry(fimc->vid_cap.active_buf_q.next, + struct fimc_vid_buffer, list); + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, + *((u32 *)arg)); + } + fimc_capture_irq_handler(fimc, 1); + fimc_deactivate_capture(fimc); + spin_unlock_irqrestore(&fimc->slock, irq_flags); + } + spin_unlock_irqrestore(&fmd->slock, flags); +} + +static int fimc_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct fimc_fmt *fmt; + + fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_CAM, code->index); + if (!fmt) + return -EINVAL; + code->code = fmt->mbus_code; + return 0; +} + +static int fimc_subdev_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct fimc_dev *fimc = v4l2_get_subdevdata(sd); + struct fimc_ctx *ctx = fimc->vid_cap.ctx; + struct fimc_frame *ff = &ctx->s_frame; + struct v4l2_mbus_framefmt *mf; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + fmt->format = *mf; + return 0; + } + + mf = &fmt->format; + mutex_lock(&fimc->lock); + + switch (fmt->pad) { + case FIMC_SD_PAD_SOURCE: + if (!WARN_ON(ff->fmt == NULL)) + mf->code = ff->fmt->mbus_code; + /* Sink pads crop rectangle size */ + mf->width = ff->width; + mf->height = ff->height; + break; + case FIMC_SD_PAD_SINK_FIFO: + *mf = fimc->vid_cap.wb_fmt; + break; + case FIMC_SD_PAD_SINK_CAM: + default: + *mf = fimc->vid_cap.ci_fmt; + break; + } + + mutex_unlock(&fimc->lock); + mf->colorspace = V4L2_COLORSPACE_JPEG; + + return 0; +} + +static int fimc_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct fimc_dev *fimc = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct fimc_vid_cap *vc = &fimc->vid_cap; + struct fimc_ctx *ctx = vc->ctx; + struct fimc_frame *ff; + struct fimc_fmt *ffmt; + + dbg("pad%d: code: 0x%x, %dx%d", + fmt->pad, mf->code, mf->width, mf->height); + + if (fmt->pad == FIMC_SD_PAD_SOURCE && vb2_is_busy(&vc->vbq)) + return -EBUSY; + + mutex_lock(&fimc->lock); + ffmt = fimc_capture_try_format(ctx, &mf->width, &mf->height, + &mf->code, NULL, fmt->pad); + mutex_unlock(&fimc->lock); + mf->colorspace = V4L2_COLORSPACE_JPEG; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + *mf = fmt->format; + return 0; + } + /* There must be a bug in the driver if this happens */ + if (WARN_ON(ffmt == NULL)) + return -EINVAL; + + /* Update RGB Alpha control state and value range */ + fimc_alpha_ctrl_update(ctx); + + fimc_capture_mark_jpeg_xfer(ctx, ffmt->color); + if (fmt->pad == FIMC_SD_PAD_SOURCE) { + ff = &ctx->d_frame; + /* Sink pads crop rectangle size */ + mf->width = ctx->s_frame.width; + mf->height = ctx->s_frame.height; + } else { + ff = &ctx->s_frame; + } + + mutex_lock(&fimc->lock); + set_frame_bounds(ff, mf->width, mf->height); + + if (fmt->pad == FIMC_SD_PAD_SINK_FIFO) + vc->wb_fmt = *mf; + else if (fmt->pad == FIMC_SD_PAD_SINK_CAM) + vc->ci_fmt = *mf; + + ff->fmt = ffmt; + + /* Reset the crop rectangle if required. */ + if (!(fmt->pad == FIMC_SD_PAD_SOURCE && (ctx->state & FIMC_COMPOSE))) + set_frame_crop(ff, 0, 0, mf->width, mf->height); + + if (fmt->pad != FIMC_SD_PAD_SOURCE) + ctx->state &= ~FIMC_COMPOSE; + + mutex_unlock(&fimc->lock); + return 0; +} + +static int fimc_subdev_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct fimc_dev *fimc = v4l2_get_subdevdata(sd); + struct fimc_ctx *ctx = fimc->vid_cap.ctx; + struct fimc_frame *f = &ctx->s_frame; + struct v4l2_rect *r = &sel->r; + struct v4l2_rect *try_sel; + + if (sel->pad == FIMC_SD_PAD_SOURCE) + return -EINVAL; + + mutex_lock(&fimc->lock); + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + f = &ctx->d_frame; + fallthrough; + case V4L2_SEL_TGT_CROP_BOUNDS: + r->width = f->o_width; + r->height = f->o_height; + r->left = 0; + r->top = 0; + mutex_unlock(&fimc->lock); + return 0; + + case V4L2_SEL_TGT_CROP: + try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); + break; + case V4L2_SEL_TGT_COMPOSE: + try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad); + f = &ctx->d_frame; + break; + default: + mutex_unlock(&fimc->lock); + return -EINVAL; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + sel->r = *try_sel; + } else { + r->left = f->offs_h; + r->top = f->offs_v; + r->width = f->width; + r->height = f->height; + } + + dbg("target %#x: l:%d, t:%d, %dx%d, f_w: %d, f_h: %d", + sel->pad, r->left, r->top, r->width, r->height, + f->f_width, f->f_height); + + mutex_unlock(&fimc->lock); + return 0; +} + +static int fimc_subdev_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct fimc_dev *fimc = v4l2_get_subdevdata(sd); + struct fimc_ctx *ctx = fimc->vid_cap.ctx; + struct fimc_frame *f = &ctx->s_frame; + struct v4l2_rect *r = &sel->r; + struct v4l2_rect *try_sel; + unsigned long flags; + + if (sel->pad == FIMC_SD_PAD_SOURCE) + return -EINVAL; + + mutex_lock(&fimc->lock); + fimc_capture_try_selection(ctx, r, V4L2_SEL_TGT_CROP); + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + try_sel = v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); + break; + case V4L2_SEL_TGT_COMPOSE: + try_sel = v4l2_subdev_get_try_compose(sd, sd_state, sel->pad); + f = &ctx->d_frame; + break; + default: + mutex_unlock(&fimc->lock); + return -EINVAL; + } + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + *try_sel = sel->r; + } else { + spin_lock_irqsave(&fimc->slock, flags); + set_frame_crop(f, r->left, r->top, r->width, r->height); + set_bit(ST_CAPT_APPLY_CFG, &fimc->state); + if (sel->target == V4L2_SEL_TGT_COMPOSE) + ctx->state |= FIMC_COMPOSE; + spin_unlock_irqrestore(&fimc->slock, flags); + } + + dbg("target %#x: (%d,%d)/%dx%d", sel->target, r->left, r->top, + r->width, r->height); + + mutex_unlock(&fimc->lock); + return 0; +} + +static const struct v4l2_subdev_pad_ops fimc_subdev_pad_ops = { + .enum_mbus_code = fimc_subdev_enum_mbus_code, + .get_selection = fimc_subdev_get_selection, + .set_selection = fimc_subdev_set_selection, + .get_fmt = fimc_subdev_get_fmt, + .set_fmt = fimc_subdev_set_fmt, +}; + +static const struct v4l2_subdev_ops fimc_subdev_ops = { + .pad = &fimc_subdev_pad_ops, +}; + +/* Set default format at the sensor and host interface */ +static int fimc_capture_set_default_format(struct fimc_dev *fimc) +{ + struct v4l2_format fmt = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, + .fmt.pix_mp = { + .width = FIMC_DEFAULT_WIDTH, + .height = FIMC_DEFAULT_HEIGHT, + .pixelformat = V4L2_PIX_FMT_YUYV, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_JPEG, + }, + }; + + return __fimc_capture_set_format(fimc, &fmt); +} + +/* fimc->lock must be already initialized */ +static int fimc_register_capture_device(struct fimc_dev *fimc, + struct v4l2_device *v4l2_dev) +{ + struct video_device *vfd = &fimc->vid_cap.ve.vdev; + struct vb2_queue *q = &fimc->vid_cap.vbq; + struct fimc_ctx *ctx; + struct fimc_vid_cap *vid_cap; + struct fimc_fmt *fmt; + int ret = -ENOMEM; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctx->fimc_dev = fimc; + ctx->in_path = FIMC_IO_CAMERA; + ctx->out_path = FIMC_IO_DMA; + ctx->state = FIMC_CTX_CAP; + ctx->s_frame.fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_CAM, 0); + ctx->d_frame.fmt = ctx->s_frame.fmt; + + memset(vfd, 0, sizeof(*vfd)); + snprintf(vfd->name, sizeof(vfd->name), "fimc.%d.capture", fimc->id); + + vfd->fops = &fimc_capture_fops; + vfd->ioctl_ops = &fimc_capture_ioctl_ops; + vfd->v4l2_dev = v4l2_dev; + vfd->minor = -1; + vfd->release = video_device_release_empty; + vfd->queue = q; + vfd->lock = &fimc->lock; + vfd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE; + + video_set_drvdata(vfd, fimc); + vid_cap = &fimc->vid_cap; + vid_cap->active_buf_cnt = 0; + vid_cap->reqbufs_count = 0; + vid_cap->ctx = ctx; + + INIT_LIST_HEAD(&vid_cap->pending_buf_q); + INIT_LIST_HEAD(&vid_cap->active_buf_q); + + memset(q, 0, sizeof(*q)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = ctx; + q->ops = &fimc_capture_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct fimc_vid_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &fimc->lock; + q->dev = &fimc->pdev->dev; + + ret = vb2_queue_init(q); + if (ret) + goto err_free_ctx; + + /* Default format configuration */ + fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_CAM, 0); + vid_cap->ci_fmt.width = FIMC_DEFAULT_WIDTH; + vid_cap->ci_fmt.height = FIMC_DEFAULT_HEIGHT; + vid_cap->ci_fmt.code = fmt->mbus_code; + + ctx->s_frame.width = FIMC_DEFAULT_WIDTH; + ctx->s_frame.height = FIMC_DEFAULT_HEIGHT; + ctx->s_frame.fmt = fmt; + + fmt = fimc_find_format(NULL, NULL, FMT_FLAGS_WRITEBACK, 0); + vid_cap->wb_fmt = vid_cap->ci_fmt; + vid_cap->wb_fmt.code = fmt->mbus_code; + + vid_cap->vd_pad.flags = MEDIA_PAD_FL_SINK; + vfd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + ret = media_entity_pads_init(&vfd->entity, 1, &vid_cap->vd_pad); + if (ret) + goto err_free_ctx; + + ret = fimc_ctrls_create(ctx); + if (ret) + goto err_me_cleanup; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) + goto err_ctrl_free; + + v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n", + vfd->name, video_device_node_name(vfd)); + + vfd->ctrl_handler = &ctx->ctrls.handler; + return 0; + +err_ctrl_free: + fimc_ctrls_delete(ctx); +err_me_cleanup: + media_entity_cleanup(&vfd->entity); +err_free_ctx: + kfree(ctx); + return ret; +} + +static int fimc_capture_subdev_registered(struct v4l2_subdev *sd) +{ + struct fimc_dev *fimc = v4l2_get_subdevdata(sd); + int ret; + + if (fimc == NULL) + return -ENXIO; + + ret = fimc_register_m2m_device(fimc, sd->v4l2_dev); + if (ret) + return ret; + + fimc->vid_cap.ve.pipe = v4l2_get_subdev_hostdata(sd); + + ret = fimc_register_capture_device(fimc, sd->v4l2_dev); + if (ret) { + fimc_unregister_m2m_device(fimc); + fimc->vid_cap.ve.pipe = NULL; + } + + return ret; +} + +static void fimc_capture_subdev_unregistered(struct v4l2_subdev *sd) +{ + struct fimc_dev *fimc = v4l2_get_subdevdata(sd); + struct video_device *vdev; + + if (fimc == NULL) + return; + + mutex_lock(&fimc->lock); + + fimc_unregister_m2m_device(fimc); + vdev = &fimc->vid_cap.ve.vdev; + + if (video_is_registered(vdev)) { + video_unregister_device(vdev); + media_entity_cleanup(&vdev->entity); + fimc_ctrls_delete(fimc->vid_cap.ctx); + fimc->vid_cap.ve.pipe = NULL; + } + kfree(fimc->vid_cap.ctx); + fimc->vid_cap.ctx = NULL; + + mutex_unlock(&fimc->lock); +} + +static const struct v4l2_subdev_internal_ops fimc_capture_sd_internal_ops = { + .registered = fimc_capture_subdev_registered, + .unregistered = fimc_capture_subdev_unregistered, +}; + +int fimc_initialize_capture_subdev(struct fimc_dev *fimc) +{ + struct v4l2_subdev *sd = &fimc->vid_cap.subdev; + int ret; + + v4l2_subdev_init(sd, &fimc_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, sizeof(sd->name), "FIMC.%d", fimc->id); + + fimc->vid_cap.sd_pads[FIMC_SD_PAD_SINK_CAM].flags = MEDIA_PAD_FL_SINK; + fimc->vid_cap.sd_pads[FIMC_SD_PAD_SINK_FIFO].flags = MEDIA_PAD_FL_SINK; + fimc->vid_cap.sd_pads[FIMC_SD_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, FIMC_SD_PADS_NUM, + fimc->vid_cap.sd_pads); + if (ret) + return ret; + + sd->entity.ops = &fimc_sd_media_ops; + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + sd->internal_ops = &fimc_capture_sd_internal_ops; + v4l2_set_subdevdata(sd, fimc); + return 0; +} + +void fimc_unregister_capture_subdev(struct fimc_dev *fimc) +{ + struct v4l2_subdev *sd = &fimc->vid_cap.subdev; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_set_subdevdata(sd, NULL); +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-core.c b/drivers/media/platform/samsung/exynos4-is/fimc-core.c new file mode 100644 index 000000000..1791100b6 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-core.c @@ -0,0 +1,1179 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Samsung S5P/EXYNOS4 SoC series FIMC (CAMIF) driver + * + * Copyright (C) 2010-2012 Samsung Electronics Co., Ltd. + * Sylwester Nawrocki + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fimc-core.h" +#include "fimc-reg.h" +#include "media-dev.h" + +static char *fimc_clocks[MAX_FIMC_CLOCKS] = { + "sclk_fimc", "fimc" +}; + +static struct fimc_fmt fimc_formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = { 16 }, + .color = FIMC_FMT_RGB565, + .memplanes = 1, + .colplanes = 1, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_BGR666, + .depth = { 32 }, + .color = FIMC_FMT_RGB666, + .memplanes = 1, + .colplanes = 1, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .depth = { 32 }, + .color = FIMC_FMT_RGB888, + .memplanes = 1, + .colplanes = 1, + .flags = FMT_FLAGS_M2M | FMT_HAS_ALPHA, + }, { + .fourcc = V4L2_PIX_FMT_RGB555, + .depth = { 16 }, + .color = FIMC_FMT_RGB555, + .memplanes = 1, + .colplanes = 1, + .flags = FMT_FLAGS_M2M_OUT | FMT_HAS_ALPHA, + }, { + .fourcc = V4L2_PIX_FMT_RGB444, + .depth = { 16 }, + .color = FIMC_FMT_RGB444, + .memplanes = 1, + .colplanes = 1, + .flags = FMT_FLAGS_M2M_OUT | FMT_HAS_ALPHA, + }, { + .mbus_code = MEDIA_BUS_FMT_YUV10_1X30, + .flags = FMT_FLAGS_WRITEBACK, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = { 16 }, + .color = FIMC_FMT_YCBYCR422, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = { 16 }, + .color = FIMC_FMT_CBYCRY422, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = { 16 }, + .color = FIMC_FMT_CRYCBY422, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = { 16 }, + .color = FIMC_FMT_YCRYCB422, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = { 16 }, + .color = FIMC_FMT_YCBYCR422, + .memplanes = 1, + .colplanes = 3, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .depth = { 16 }, + .color = FIMC_FMT_YCBYCR422, + .memplanes = 1, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .depth = { 16 }, + .color = FIMC_FMT_YCRYCB422, + .memplanes = 1, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = { 12 }, + .color = FIMC_FMT_YCBCR420, + .memplanes = 1, + .colplanes = 3, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = { 12 }, + .color = FIMC_FMT_YCBCR420, + .memplanes = 1, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_NV12M, + .color = FIMC_FMT_YCBCR420, + .depth = { 8, 4 }, + .memplanes = 2, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_YUV420M, + .color = FIMC_FMT_YCBCR420, + .depth = { 8, 2, 2 }, + .memplanes = 3, + .colplanes = 3, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_NV12MT, + .color = FIMC_FMT_YCBCR420, + .depth = { 8, 4 }, + .memplanes = 2, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, + }, { + .fourcc = V4L2_PIX_FMT_JPEG, + .color = FIMC_FMT_JPEG, + .depth = { 8 }, + .memplanes = 1, + .colplanes = 1, + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + .flags = FMT_FLAGS_CAM | FMT_FLAGS_COMPRESSED, + }, { + .fourcc = V4L2_PIX_FMT_S5C_UYVY_JPG, + .color = FIMC_FMT_YUYV_JPEG, + .depth = { 8 }, + .memplanes = 2, + .colplanes = 1, + .mdataplanes = 0x2, /* plane 1 holds frame meta data */ + .mbus_code = MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8, + .flags = FMT_FLAGS_CAM | FMT_FLAGS_COMPRESSED, + }, +}; + +struct fimc_fmt *fimc_get_format(unsigned int index) +{ + if (index >= ARRAY_SIZE(fimc_formats)) + return NULL; + + return &fimc_formats[index]; +} + +int fimc_check_scaler_ratio(struct fimc_ctx *ctx, int sw, int sh, + int dw, int dh, int rotation) +{ + if (rotation == 90 || rotation == 270) + swap(dw, dh); + + if (!ctx->scaler.enabled) + return (sw == dw && sh == dh) ? 0 : -EINVAL; + + if ((sw >= SCALER_MAX_HRATIO * dw) || (sh >= SCALER_MAX_VRATIO * dh)) + return -EINVAL; + + return 0; +} + +static int fimc_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift) +{ + u32 sh = 6; + + if (src >= 64 * tar) + return -EINVAL; + + while (sh--) { + u32 tmp = 1 << sh; + if (src >= tar * tmp) { + *shift = sh; + *ratio = tmp; + return 0; + } + } + *shift = 0; + *ratio = 1; + return 0; +} + +int fimc_set_scaler_info(struct fimc_ctx *ctx) +{ + const struct fimc_variant *variant = ctx->fimc_dev->variant; + struct device *dev = &ctx->fimc_dev->pdev->dev; + struct fimc_scaler *sc = &ctx->scaler; + struct fimc_frame *s_frame = &ctx->s_frame; + struct fimc_frame *d_frame = &ctx->d_frame; + int tx, ty, sx, sy; + int ret; + + if (ctx->rotation == 90 || ctx->rotation == 270) { + ty = d_frame->width; + tx = d_frame->height; + } else { + tx = d_frame->width; + ty = d_frame->height; + } + if (tx <= 0 || ty <= 0) { + dev_err(dev, "Invalid target size: %dx%d\n", tx, ty); + return -EINVAL; + } + + sx = s_frame->width; + sy = s_frame->height; + if (sx <= 0 || sy <= 0) { + dev_err(dev, "Invalid source size: %dx%d\n", sx, sy); + return -EINVAL; + } + sc->real_width = sx; + sc->real_height = sy; + + ret = fimc_get_scaler_factor(sx, tx, &sc->pre_hratio, &sc->hfactor); + if (ret) + return ret; + + ret = fimc_get_scaler_factor(sy, ty, &sc->pre_vratio, &sc->vfactor); + if (ret) + return ret; + + sc->pre_dst_width = sx / sc->pre_hratio; + sc->pre_dst_height = sy / sc->pre_vratio; + + if (variant->has_mainscaler_ext) { + sc->main_hratio = (sx << 14) / (tx << sc->hfactor); + sc->main_vratio = (sy << 14) / (ty << sc->vfactor); + } else { + sc->main_hratio = (sx << 8) / (tx << sc->hfactor); + sc->main_vratio = (sy << 8) / (ty << sc->vfactor); + + } + + sc->scaleup_h = (tx >= sx) ? 1 : 0; + sc->scaleup_v = (ty >= sy) ? 1 : 0; + + /* check to see if input and output size/format differ */ + if (s_frame->fmt->color == d_frame->fmt->color + && s_frame->width == d_frame->width + && s_frame->height == d_frame->height) + sc->copy_mode = 1; + else + sc->copy_mode = 0; + + return 0; +} + +static irqreturn_t fimc_irq_handler(int irq, void *priv) +{ + struct fimc_dev *fimc = priv; + struct fimc_ctx *ctx; + + fimc_hw_clear_irq(fimc); + + spin_lock(&fimc->slock); + + if (test_and_clear_bit(ST_M2M_PEND, &fimc->state)) { + if (test_and_clear_bit(ST_M2M_SUSPENDING, &fimc->state)) { + set_bit(ST_M2M_SUSPENDED, &fimc->state); + wake_up(&fimc->irq_queue); + goto out; + } + ctx = v4l2_m2m_get_curr_priv(fimc->m2m.m2m_dev); + if (ctx != NULL) { + spin_unlock(&fimc->slock); + fimc_m2m_job_finish(ctx, VB2_BUF_STATE_DONE); + + if (ctx->state & FIMC_CTX_SHUT) { + ctx->state &= ~FIMC_CTX_SHUT; + wake_up(&fimc->irq_queue); + } + return IRQ_HANDLED; + } + } else if (test_bit(ST_CAPT_PEND, &fimc->state)) { + int last_buf = test_bit(ST_CAPT_JPEG, &fimc->state) && + fimc->vid_cap.reqbufs_count == 1; + fimc_capture_irq_handler(fimc, !last_buf); + } +out: + spin_unlock(&fimc->slock); + return IRQ_HANDLED; +} + +/* The color format (colplanes, memplanes) must be already configured. */ +int fimc_prepare_addr(struct fimc_ctx *ctx, struct vb2_buffer *vb, + struct fimc_frame *frame, struct fimc_addr *addr) +{ + int ret = 0; + u32 pix_size; + + if (vb == NULL || frame == NULL) + return -EINVAL; + + pix_size = frame->width * frame->height; + + dbg("memplanes= %d, colplanes= %d, pix_size= %d", + frame->fmt->memplanes, frame->fmt->colplanes, pix_size); + + addr->y = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (frame->fmt->memplanes == 1) { + switch (frame->fmt->colplanes) { + case 1: + addr->cb = 0; + addr->cr = 0; + break; + case 2: + /* decompose Y into Y/Cb */ + addr->cb = (u32)(addr->y + pix_size); + addr->cr = 0; + break; + case 3: + addr->cb = (u32)(addr->y + pix_size); + /* decompose Y into Y/Cb/Cr */ + if (FIMC_FMT_YCBCR420 == frame->fmt->color) + addr->cr = (u32)(addr->cb + (pix_size >> 2)); + else /* 422 */ + addr->cr = (u32)(addr->cb + (pix_size >> 1)); + break; + default: + return -EINVAL; + } + } else if (!frame->fmt->mdataplanes) { + if (frame->fmt->memplanes >= 2) + addr->cb = vb2_dma_contig_plane_dma_addr(vb, 1); + + if (frame->fmt->memplanes == 3) + addr->cr = vb2_dma_contig_plane_dma_addr(vb, 2); + } + + dbg("DMA ADDR: y= 0x%X cb= 0x%X cr= 0x%X ret= %d", + addr->y, addr->cb, addr->cr, ret); + + return ret; +} + +/* Set order for 1 and 2 plane YCBCR 4:2:2 formats. */ +void fimc_set_yuv_order(struct fimc_ctx *ctx) +{ + /* The one only mode supported in SoC. */ + ctx->in_order_2p = FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB; + ctx->out_order_2p = FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB; + + /* Set order for 1 plane input formats. */ + switch (ctx->s_frame.fmt->color) { + case FIMC_FMT_YCRYCB422: + ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_YCRYCB; + break; + case FIMC_FMT_CBYCRY422: + ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_CBYCRY; + break; + case FIMC_FMT_CRYCBY422: + ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_CRYCBY; + break; + case FIMC_FMT_YCBYCR422: + default: + ctx->in_order_1p = FIMC_REG_MSCTRL_ORDER422_YCBYCR; + break; + } + dbg("ctx->in_order_1p= %d", ctx->in_order_1p); + + switch (ctx->d_frame.fmt->color) { + case FIMC_FMT_YCRYCB422: + ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_YCRYCB; + break; + case FIMC_FMT_CBYCRY422: + ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_CBYCRY; + break; + case FIMC_FMT_CRYCBY422: + ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_CRYCBY; + break; + case FIMC_FMT_YCBYCR422: + default: + ctx->out_order_1p = FIMC_REG_CIOCTRL_ORDER422_YCBYCR; + break; + } + dbg("ctx->out_order_1p= %d", ctx->out_order_1p); +} + +void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f) +{ + bool pix_hoff = ctx->fimc_dev->drv_data->dma_pix_hoff; + u32 i, depth = 0; + + for (i = 0; i < f->fmt->memplanes; i++) + depth += f->fmt->depth[i]; + + f->dma_offset.y_h = f->offs_h; + if (!pix_hoff) + f->dma_offset.y_h *= (depth >> 3); + + f->dma_offset.y_v = f->offs_v; + + f->dma_offset.cb_h = f->offs_h; + f->dma_offset.cb_v = f->offs_v; + + f->dma_offset.cr_h = f->offs_h; + f->dma_offset.cr_v = f->offs_v; + + if (!pix_hoff) { + if (f->fmt->colplanes == 3) { + f->dma_offset.cb_h >>= 1; + f->dma_offset.cr_h >>= 1; + } + if (f->fmt->color == FIMC_FMT_YCBCR420) { + f->dma_offset.cb_v >>= 1; + f->dma_offset.cr_v >>= 1; + } + } + + dbg("in_offset: color= %d, y_h= %d, y_v= %d", + f->fmt->color, f->dma_offset.y_h, f->dma_offset.y_v); +} + +static int fimc_set_color_effect(struct fimc_ctx *ctx, enum v4l2_colorfx colorfx) +{ + struct fimc_effect *effect = &ctx->effect; + + switch (colorfx) { + case V4L2_COLORFX_NONE: + effect->type = FIMC_REG_CIIMGEFF_FIN_BYPASS; + break; + case V4L2_COLORFX_BW: + effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY; + effect->pat_cb = 128; + effect->pat_cr = 128; + break; + case V4L2_COLORFX_SEPIA: + effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY; + effect->pat_cb = 115; + effect->pat_cr = 145; + break; + case V4L2_COLORFX_NEGATIVE: + effect->type = FIMC_REG_CIIMGEFF_FIN_NEGATIVE; + break; + case V4L2_COLORFX_EMBOSS: + effect->type = FIMC_REG_CIIMGEFF_FIN_EMBOSSING; + break; + case V4L2_COLORFX_ART_FREEZE: + effect->type = FIMC_REG_CIIMGEFF_FIN_ARTFREEZE; + break; + case V4L2_COLORFX_SILHOUETTE: + effect->type = FIMC_REG_CIIMGEFF_FIN_SILHOUETTE; + break; + case V4L2_COLORFX_SET_CBCR: + effect->type = FIMC_REG_CIIMGEFF_FIN_ARBITRARY; + effect->pat_cb = ctx->ctrls.colorfx_cbcr->val >> 8; + effect->pat_cr = ctx->ctrls.colorfx_cbcr->val & 0xff; + break; + default: + return -EINVAL; + } + + return 0; +} + +/* + * V4L2 controls handling + */ +#define ctrl_to_ctx(__ctrl) \ + container_of((__ctrl)->handler, struct fimc_ctx, ctrls.handler) + +static int __fimc_s_ctrl(struct fimc_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + struct fimc_dev *fimc = ctx->fimc_dev; + const struct fimc_variant *variant = fimc->variant; + int ret = 0; + + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->hflip = ctrl->val; + break; + + case V4L2_CID_VFLIP: + ctx->vflip = ctrl->val; + break; + + case V4L2_CID_ROTATE: + if (fimc_capture_pending(fimc)) { + ret = fimc_check_scaler_ratio(ctx, ctx->s_frame.width, + ctx->s_frame.height, ctx->d_frame.width, + ctx->d_frame.height, ctrl->val); + if (ret) + return -EINVAL; + } + if ((ctrl->val == 90 || ctrl->val == 270) && + !variant->has_out_rot) + return -EINVAL; + + ctx->rotation = ctrl->val; + break; + + case V4L2_CID_ALPHA_COMPONENT: + ctx->d_frame.alpha = ctrl->val; + break; + + case V4L2_CID_COLORFX: + ret = fimc_set_color_effect(ctx, ctrl->val); + if (ret) + return ret; + break; + } + + ctx->state |= FIMC_PARAMS; + set_bit(ST_CAPT_APPLY_CFG, &fimc->state); + return 0; +} + +static int fimc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct fimc_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ctx->fimc_dev->slock, flags); + ret = __fimc_s_ctrl(ctx, ctrl); + spin_unlock_irqrestore(&ctx->fimc_dev->slock, flags); + + return ret; +} + +static const struct v4l2_ctrl_ops fimc_ctrl_ops = { + .s_ctrl = fimc_s_ctrl, +}; + +int fimc_ctrls_create(struct fimc_ctx *ctx) +{ + unsigned int max_alpha = fimc_get_alpha_mask(ctx->d_frame.fmt); + struct fimc_ctrls *ctrls = &ctx->ctrls; + struct v4l2_ctrl_handler *handler = &ctrls->handler; + + if (ctx->ctrls.ready) + return 0; + + v4l2_ctrl_handler_init(handler, 6); + + ctrls->rotate = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops, + V4L2_CID_ROTATE, 0, 270, 90, 0); + ctrls->hflip = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + ctrls->vflip = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (ctx->fimc_dev->drv_data->alpha_color) + ctrls->alpha = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops, + V4L2_CID_ALPHA_COMPONENT, + 0, max_alpha, 1, 0); + else + ctrls->alpha = NULL; + + ctrls->colorfx = v4l2_ctrl_new_std_menu(handler, &fimc_ctrl_ops, + V4L2_CID_COLORFX, V4L2_COLORFX_SET_CBCR, + ~0x983f, V4L2_COLORFX_NONE); + + ctrls->colorfx_cbcr = v4l2_ctrl_new_std(handler, &fimc_ctrl_ops, + V4L2_CID_COLORFX_CBCR, 0, 0xffff, 1, 0); + + ctx->effect.type = FIMC_REG_CIIMGEFF_FIN_BYPASS; + + if (!handler->error) { + v4l2_ctrl_cluster(2, &ctrls->colorfx); + ctrls->ready = true; + } + + return handler->error; +} + +void fimc_ctrls_delete(struct fimc_ctx *ctx) +{ + struct fimc_ctrls *ctrls = &ctx->ctrls; + + if (ctrls->ready) { + v4l2_ctrl_handler_free(&ctrls->handler); + ctrls->ready = false; + ctrls->alpha = NULL; + } +} + +void fimc_ctrls_activate(struct fimc_ctx *ctx, bool active) +{ + unsigned int has_alpha = ctx->d_frame.fmt->flags & FMT_HAS_ALPHA; + struct fimc_ctrls *ctrls = &ctx->ctrls; + + if (!ctrls->ready) + return; + + mutex_lock(ctrls->handler.lock); + v4l2_ctrl_activate(ctrls->rotate, active); + v4l2_ctrl_activate(ctrls->hflip, active); + v4l2_ctrl_activate(ctrls->vflip, active); + v4l2_ctrl_activate(ctrls->colorfx, active); + if (ctrls->alpha) + v4l2_ctrl_activate(ctrls->alpha, active && has_alpha); + + if (active) { + fimc_set_color_effect(ctx, ctrls->colorfx->cur.val); + ctx->rotation = ctrls->rotate->val; + ctx->hflip = ctrls->hflip->val; + ctx->vflip = ctrls->vflip->val; + } else { + ctx->effect.type = FIMC_REG_CIIMGEFF_FIN_BYPASS; + ctx->rotation = 0; + ctx->hflip = 0; + ctx->vflip = 0; + } + mutex_unlock(ctrls->handler.lock); +} + +/* Update maximum value of the alpha color control */ +void fimc_alpha_ctrl_update(struct fimc_ctx *ctx) +{ + struct fimc_dev *fimc = ctx->fimc_dev; + struct v4l2_ctrl *ctrl = ctx->ctrls.alpha; + + if (ctrl == NULL || !fimc->drv_data->alpha_color) + return; + + v4l2_ctrl_lock(ctrl); + ctrl->maximum = fimc_get_alpha_mask(ctx->d_frame.fmt); + + if (ctrl->cur.val > ctrl->maximum) + ctrl->cur.val = ctrl->maximum; + + v4l2_ctrl_unlock(ctrl); +} + +void __fimc_get_format(struct fimc_frame *frame, struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp; + int i; + + pixm->width = frame->o_width; + pixm->height = frame->o_height; + pixm->field = V4L2_FIELD_NONE; + pixm->pixelformat = frame->fmt->fourcc; + pixm->colorspace = V4L2_COLORSPACE_JPEG; + pixm->num_planes = frame->fmt->memplanes; + + for (i = 0; i < pixm->num_planes; ++i) { + pixm->plane_fmt[i].bytesperline = frame->bytesperline[i]; + pixm->plane_fmt[i].sizeimage = frame->payload[i]; + } +} + +/** + * fimc_adjust_mplane_format - adjust bytesperline/sizeimage for each plane + * @fmt: fimc pixel format description (input) + * @width: requested pixel width + * @height: requested pixel height + * @pix: multi-plane format to adjust + */ +void fimc_adjust_mplane_format(struct fimc_fmt *fmt, u32 width, u32 height, + struct v4l2_pix_format_mplane *pix) +{ + u32 bytesperline = 0; + int i; + + pix->colorspace = V4L2_COLORSPACE_JPEG; + pix->field = V4L2_FIELD_NONE; + pix->num_planes = fmt->memplanes; + pix->pixelformat = fmt->fourcc; + pix->height = height; + pix->width = width; + + for (i = 0; i < pix->num_planes; ++i) { + struct v4l2_plane_pix_format *plane_fmt = &pix->plane_fmt[i]; + u32 bpl = plane_fmt->bytesperline; + u32 sizeimage; + + if (fmt->colplanes > 1 && (bpl == 0 || bpl < pix->width)) + bpl = pix->width; /* Planar */ + + if (fmt->colplanes == 1 && /* Packed */ + (bpl == 0 || ((bpl * 8) / fmt->depth[i]) < pix->width)) + bpl = (pix->width * fmt->depth[0]) / 8; + /* + * Currently bytesperline for each plane is same, except + * V4L2_PIX_FMT_YUV420M format. This calculation may need + * to be changed when other multi-planar formats are added + * to the fimc_formats[] array. + */ + if (i == 0) + bytesperline = bpl; + else if (i == 1 && fmt->memplanes == 3) + bytesperline /= 2; + + plane_fmt->bytesperline = bytesperline; + sizeimage = pix->width * pix->height * fmt->depth[i] / 8; + + /* Ensure full last row for tiled formats */ + if (tiled_fmt(fmt)) { + /* 64 * 32 * plane_fmt->bytesperline / 64 */ + u32 row_size = plane_fmt->bytesperline * 32; + + sizeimage = roundup(sizeimage, row_size); + } + + plane_fmt->sizeimage = max(sizeimage, plane_fmt->sizeimage); + } +} + +/** + * fimc_find_format - lookup fimc color format by fourcc or media bus format + * @pixelformat: fourcc to match, ignored if null + * @mbus_code: media bus code to match, ignored if null + * @mask: the color flags to match + * @index: offset in the fimc_formats array, ignored if negative + */ +struct fimc_fmt *fimc_find_format(const u32 *pixelformat, const u32 *mbus_code, + unsigned int mask, int index) +{ + struct fimc_fmt *fmt, *def_fmt = NULL; + unsigned int i; + int id = 0; + + if (index >= (int)ARRAY_SIZE(fimc_formats)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(fimc_formats); ++i) { + fmt = &fimc_formats[i]; + if (!(fmt->flags & mask)) + continue; + if (pixelformat && fmt->fourcc == *pixelformat) + return fmt; + if (mbus_code && fmt->mbus_code == *mbus_code) + return fmt; + if (index == id) + def_fmt = fmt; + id++; + } + return def_fmt; +} + +static void fimc_clk_put(struct fimc_dev *fimc) +{ + int i; + for (i = 0; i < MAX_FIMC_CLOCKS; i++) { + if (IS_ERR(fimc->clock[i])) + continue; + clk_unprepare(fimc->clock[i]); + clk_put(fimc->clock[i]); + fimc->clock[i] = ERR_PTR(-EINVAL); + } +} + +static int fimc_clk_get(struct fimc_dev *fimc) +{ + int i, ret; + + for (i = 0; i < MAX_FIMC_CLOCKS; i++) + fimc->clock[i] = ERR_PTR(-EINVAL); + + for (i = 0; i < MAX_FIMC_CLOCKS; i++) { + fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clocks[i]); + if (IS_ERR(fimc->clock[i])) { + ret = PTR_ERR(fimc->clock[i]); + goto err; + } + ret = clk_prepare(fimc->clock[i]); + if (ret < 0) { + clk_put(fimc->clock[i]); + fimc->clock[i] = ERR_PTR(-EINVAL); + goto err; + } + } + return 0; +err: + fimc_clk_put(fimc); + dev_err(&fimc->pdev->dev, "failed to get clock: %s\n", + fimc_clocks[i]); + return -ENXIO; +} + +#ifdef CONFIG_PM +static int fimc_m2m_suspend(struct fimc_dev *fimc) +{ + unsigned long flags; + int timeout; + + spin_lock_irqsave(&fimc->slock, flags); + if (!fimc_m2m_pending(fimc)) { + spin_unlock_irqrestore(&fimc->slock, flags); + return 0; + } + clear_bit(ST_M2M_SUSPENDED, &fimc->state); + set_bit(ST_M2M_SUSPENDING, &fimc->state); + spin_unlock_irqrestore(&fimc->slock, flags); + + timeout = wait_event_timeout(fimc->irq_queue, + test_bit(ST_M2M_SUSPENDED, &fimc->state), + FIMC_SHUTDOWN_TIMEOUT); + + clear_bit(ST_M2M_SUSPENDING, &fimc->state); + return timeout == 0 ? -EAGAIN : 0; +} + +static int fimc_m2m_resume(struct fimc_dev *fimc) +{ + struct fimc_ctx *ctx; + unsigned long flags; + + spin_lock_irqsave(&fimc->slock, flags); + /* Clear for full H/W setup in first run after resume */ + ctx = fimc->m2m.ctx; + fimc->m2m.ctx = NULL; + spin_unlock_irqrestore(&fimc->slock, flags); + + if (test_and_clear_bit(ST_M2M_SUSPENDED, &fimc->state)) + fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct of_device_id fimc_of_match[]; + +static int fimc_parse_dt(struct fimc_dev *fimc, u32 *clk_freq) +{ + struct device *dev = &fimc->pdev->dev; + struct device_node *node = dev->of_node; + const struct of_device_id *of_id; + struct fimc_variant *v; + struct fimc_pix_limit *lim; + u32 args[FIMC_PIX_LIMITS_MAX]; + int ret; + + if (of_property_read_bool(node, "samsung,lcd-wb")) + return -ENODEV; + + v = devm_kzalloc(dev, sizeof(*v) + sizeof(*lim), GFP_KERNEL); + if (!v) + return -ENOMEM; + + of_id = of_match_node(fimc_of_match, node); + if (!of_id) + return -EINVAL; + fimc->drv_data = of_id->data; + ret = of_property_read_u32_array(node, "samsung,pix-limits", + args, FIMC_PIX_LIMITS_MAX); + if (ret < 0) + return ret; + + lim = (struct fimc_pix_limit *)&v[1]; + + lim->scaler_en_w = args[0]; + lim->scaler_dis_w = args[1]; + lim->out_rot_en_w = args[2]; + lim->out_rot_dis_w = args[3]; + v->pix_limit = lim; + + ret = of_property_read_u32_array(node, "samsung,min-pix-sizes", + args, 2); + v->min_inp_pixsize = ret ? FIMC_DEF_MIN_SIZE : args[0]; + v->min_out_pixsize = ret ? FIMC_DEF_MIN_SIZE : args[1]; + ret = of_property_read_u32_array(node, "samsung,min-pix-alignment", + args, 2); + v->min_vsize_align = ret ? FIMC_DEF_HEIGHT_ALIGN : args[0]; + v->hor_offs_align = ret ? FIMC_DEF_HOR_OFFS_ALIGN : args[1]; + + ret = of_property_read_u32(node, "samsung,rotators", &args[1]); + v->has_inp_rot = ret ? 1 : args[1] & 0x01; + v->has_out_rot = ret ? 1 : args[1] & 0x10; + v->has_mainscaler_ext = of_property_read_bool(node, + "samsung,mainscaler-ext"); + + v->has_isp_wb = of_property_read_bool(node, "samsung,isp-wb"); + v->has_cam_if = of_property_read_bool(node, "samsung,cam-if"); + of_property_read_u32(node, "clock-frequency", clk_freq); + fimc->id = of_alias_get_id(node, "fimc"); + + fimc->variant = v; + return 0; +} + +static int fimc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + u32 lclk_freq = 0; + struct fimc_dev *fimc; + struct resource *res; + int ret = 0; + int irq; + + fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL); + if (!fimc) + return -ENOMEM; + + fimc->pdev = pdev; + + if (dev->of_node) { + ret = fimc_parse_dt(fimc, &lclk_freq); + if (ret < 0) + return ret; + } else { + fimc->drv_data = fimc_get_drvdata(pdev); + fimc->id = pdev->id; + } + if (!fimc->drv_data || fimc->id >= fimc->drv_data->num_entities || + fimc->id < 0) { + dev_err(dev, "Invalid driver data or device id (%d)\n", + fimc->id); + return -EINVAL; + } + if (!dev->of_node) + fimc->variant = fimc->drv_data->variant[fimc->id]; + + init_waitqueue_head(&fimc->irq_queue); + spin_lock_init(&fimc->slock); + mutex_init(&fimc->lock); + + if (fimc->variant->has_isp_wb) { + fimc->sysreg = fimc_get_sysreg_regmap(dev->of_node); + if (IS_ERR(fimc->sysreg)) + return PTR_ERR(fimc->sysreg); + } + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + fimc->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(fimc->regs)) + return PTR_ERR(fimc->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = fimc_clk_get(fimc); + if (ret) + return ret; + + if (lclk_freq == 0) + lclk_freq = fimc->drv_data->lclk_frequency; + + ret = clk_set_rate(fimc->clock[CLK_BUS], lclk_freq); + if (ret < 0) + return ret; + + ret = clk_enable(fimc->clock[CLK_BUS]); + if (ret < 0) + return ret; + + ret = devm_request_irq(dev, irq, fimc_irq_handler, + 0, dev_name(dev), fimc); + if (ret < 0) { + dev_err(dev, "failed to install irq (%d)\n", ret); + goto err_sclk; + } + + ret = fimc_initialize_capture_subdev(fimc); + if (ret < 0) + goto err_sclk; + + platform_set_drvdata(pdev, fimc); + pm_runtime_enable(dev); + + if (!pm_runtime_enabled(dev)) { + ret = clk_enable(fimc->clock[CLK_GATE]); + if (ret < 0) + goto err_sd; + } + + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); + + dev_dbg(dev, "FIMC.%d registered successfully\n", fimc->id); + return 0; + +err_sd: + fimc_unregister_capture_subdev(fimc); +err_sclk: + clk_disable(fimc->clock[CLK_BUS]); + fimc_clk_put(fimc); + return ret; +} + +#ifdef CONFIG_PM +static int fimc_runtime_resume(struct device *dev) +{ + struct fimc_dev *fimc = dev_get_drvdata(dev); + + dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); + + /* Enable clocks and perform basic initialization */ + clk_enable(fimc->clock[CLK_GATE]); + fimc_hw_reset(fimc); + + /* Resume the capture or mem-to-mem device */ + if (fimc_capture_busy(fimc)) + return fimc_capture_resume(fimc); + + return fimc_m2m_resume(fimc); +} + +static int fimc_runtime_suspend(struct device *dev) +{ + struct fimc_dev *fimc = dev_get_drvdata(dev); + int ret = 0; + + if (fimc_capture_busy(fimc)) + ret = fimc_capture_suspend(fimc); + else + ret = fimc_m2m_suspend(fimc); + if (!ret) + clk_disable(fimc->clock[CLK_GATE]); + + dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); + return ret; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int fimc_resume(struct device *dev) +{ + struct fimc_dev *fimc = dev_get_drvdata(dev); + unsigned long flags; + + dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); + + /* Do not resume if the device was idle before system suspend */ + spin_lock_irqsave(&fimc->slock, flags); + if (!test_and_clear_bit(ST_LPM, &fimc->state) || + (!fimc_m2m_active(fimc) && !fimc_capture_busy(fimc))) { + spin_unlock_irqrestore(&fimc->slock, flags); + return 0; + } + fimc_hw_reset(fimc); + spin_unlock_irqrestore(&fimc->slock, flags); + + if (fimc_capture_busy(fimc)) + return fimc_capture_resume(fimc); + + return fimc_m2m_resume(fimc); +} + +static int fimc_suspend(struct device *dev) +{ + struct fimc_dev *fimc = dev_get_drvdata(dev); + + dbg("fimc%d: state: 0x%lx", fimc->id, fimc->state); + + if (test_and_set_bit(ST_LPM, &fimc->state)) + return 0; + if (fimc_capture_busy(fimc)) + return fimc_capture_suspend(fimc); + + return fimc_m2m_suspend(fimc); +} +#endif /* CONFIG_PM_SLEEP */ + +static int fimc_remove(struct platform_device *pdev) +{ + struct fimc_dev *fimc = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + if (!pm_runtime_status_suspended(&pdev->dev)) + clk_disable(fimc->clock[CLK_GATE]); + pm_runtime_set_suspended(&pdev->dev); + + fimc_unregister_capture_subdev(fimc); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); + + clk_disable(fimc->clock[CLK_BUS]); + fimc_clk_put(fimc); + + dev_info(&pdev->dev, "driver unloaded\n"); + return 0; +} + +/* S5PV210, S5PC110 */ +static const struct fimc_drvdata fimc_drvdata_s5pv210 = { + .num_entities = 3, + .lclk_frequency = 166000000UL, + .out_buf_count = 4, + .dma_pix_hoff = 1, +}; + +/* EXYNOS4210, S5PV310, S5PC210 */ +static const struct fimc_drvdata fimc_drvdata_exynos4210 = { + .num_entities = 4, + .lclk_frequency = 166000000UL, + .dma_pix_hoff = 1, + .cistatus2 = 1, + .alpha_color = 1, + .out_buf_count = 32, +}; + +/* EXYNOS4412 */ +static const struct fimc_drvdata fimc_drvdata_exynos4x12 = { + .num_entities = 4, + .lclk_frequency = 166000000UL, + .dma_pix_hoff = 1, + .cistatus2 = 1, + .alpha_color = 1, + .out_buf_count = 32, +}; + +static const struct of_device_id fimc_of_match[] = { + { + .compatible = "samsung,s5pv210-fimc", + .data = &fimc_drvdata_s5pv210, + }, { + .compatible = "samsung,exynos4210-fimc", + .data = &fimc_drvdata_exynos4210, + }, { + .compatible = "samsung,exynos4212-fimc", + .data = &fimc_drvdata_exynos4x12, + }, + { /* sentinel */ }, +}; + +static const struct dev_pm_ops fimc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fimc_suspend, fimc_resume) + SET_RUNTIME_PM_OPS(fimc_runtime_suspend, fimc_runtime_resume, NULL) +}; + +static struct platform_driver fimc_driver = { + .probe = fimc_probe, + .remove = fimc_remove, + .driver = { + .of_match_table = fimc_of_match, + .name = FIMC_DRIVER_NAME, + .pm = &fimc_pm_ops, + } +}; + +int __init fimc_register_driver(void) +{ + return platform_driver_register(&fimc_driver); +} + +void fimc_unregister_driver(void) +{ + platform_driver_unregister(&fimc_driver); +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-core.h b/drivers/media/platform/samsung/exynos4-is/fimc-core.h new file mode 100644 index 000000000..2b0760add --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-core.h @@ -0,0 +1,725 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2010 - 2012 Samsung Electronics Co., Ltd. + */ + +#ifndef FIMC_CORE_H_ +#define FIMC_CORE_H_ + +/*#define DEBUG*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define dbg(fmt, args...) \ + pr_debug("%s:%d: " fmt "\n", __func__, __LINE__, ##args) + +/* Time to wait for next frame VSYNC interrupt while stopping operation. */ +#define FIMC_SHUTDOWN_TIMEOUT ((100*HZ)/1000) +#define MAX_FIMC_CLOCKS 2 +#define FIMC_DRIVER_NAME "exynos4-fimc" +#define FIMC_MAX_DEVS 4 +#define FIMC_MAX_OUT_BUFS 4 +#define SCALER_MAX_HRATIO 64 +#define SCALER_MAX_VRATIO 64 +#define DMA_MIN_SIZE 8 +#define FIMC_CAMIF_MAX_HEIGHT 0x2000 +#define FIMC_MAX_JPEG_BUF_SIZE (10 * SZ_1M) +#define FIMC_MAX_PLANES 3 +#define FIMC_PIX_LIMITS_MAX 4 +#define FIMC_DEF_MIN_SIZE 16 +#define FIMC_DEF_HEIGHT_ALIGN 2 +#define FIMC_DEF_HOR_OFFS_ALIGN 1 +#define FIMC_DEFAULT_WIDTH 640 +#define FIMC_DEFAULT_HEIGHT 480 + +/* indices to the clocks array */ +enum { + CLK_BUS, + CLK_GATE, +}; + +enum fimc_dev_flags { + ST_LPM, + /* m2m node */ + ST_M2M_RUN, + ST_M2M_PEND, + ST_M2M_SUSPENDING, + ST_M2M_SUSPENDED, + /* capture node */ + ST_CAPT_PEND, + ST_CAPT_RUN, + ST_CAPT_STREAM, + ST_CAPT_ISP_STREAM, + ST_CAPT_SUSPENDED, + ST_CAPT_SHUT, + ST_CAPT_BUSY, + ST_CAPT_APPLY_CFG, + ST_CAPT_JPEG, +}; + +#define fimc_m2m_active(dev) test_bit(ST_M2M_RUN, &(dev)->state) +#define fimc_m2m_pending(dev) test_bit(ST_M2M_PEND, &(dev)->state) + +#define fimc_capture_running(dev) test_bit(ST_CAPT_RUN, &(dev)->state) +#define fimc_capture_pending(dev) test_bit(ST_CAPT_PEND, &(dev)->state) +#define fimc_capture_busy(dev) test_bit(ST_CAPT_BUSY, &(dev)->state) + +enum fimc_datapath { + FIMC_IO_NONE, + FIMC_IO_CAMERA, + FIMC_IO_DMA, + FIMC_IO_LCDFIFO, + FIMC_IO_WRITEBACK, + FIMC_IO_ISP, +}; + +enum fimc_color_fmt { + FIMC_FMT_RGB444 = 0x10, + FIMC_FMT_RGB555, + FIMC_FMT_RGB565, + FIMC_FMT_RGB666, + FIMC_FMT_RGB888, + FIMC_FMT_RGB30_LOCAL, + FIMC_FMT_YCBCR420 = 0x20, + FIMC_FMT_YCBYCR422, + FIMC_FMT_YCRYCB422, + FIMC_FMT_CBYCRY422, + FIMC_FMT_CRYCBY422, + FIMC_FMT_YCBCR444_LOCAL, + FIMC_FMT_RAW8 = 0x40, + FIMC_FMT_RAW10, + FIMC_FMT_RAW12, + FIMC_FMT_JPEG = 0x80, + FIMC_FMT_YUYV_JPEG = 0x100, +}; + +#define fimc_fmt_is_user_defined(x) (!!((x) & 0x180)) +#define fimc_fmt_is_rgb(x) (!!((x) & 0x10)) + +#define IS_M2M(__strt) ((__strt) == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || \ + __strt == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + +/* The hardware context state. */ +#define FIMC_PARAMS (1 << 0) +#define FIMC_COMPOSE (1 << 1) +#define FIMC_CTX_M2M (1 << 16) +#define FIMC_CTX_CAP (1 << 17) +#define FIMC_CTX_SHUT (1 << 18) + +/* Image conversion flags */ +#define FIMC_IN_DMA_ACCESS_TILED (1 << 0) +#define FIMC_IN_DMA_ACCESS_LINEAR (0 << 0) +#define FIMC_OUT_DMA_ACCESS_TILED (1 << 1) +#define FIMC_OUT_DMA_ACCESS_LINEAR (0 << 1) +#define FIMC_SCAN_MODE_PROGRESSIVE (0 << 2) +#define FIMC_SCAN_MODE_INTERLACED (1 << 2) +/* + * YCbCr data dynamic range for RGB-YUV color conversion. + * Y/Cb/Cr: (0 ~ 255) */ +#define FIMC_COLOR_RANGE_WIDE (0 << 3) +/* Y (16 ~ 235), Cb/Cr (16 ~ 240) */ +#define FIMC_COLOR_RANGE_NARROW (1 << 3) + +/** + * struct fimc_dma_offset - pixel offset information for DMA + * @y_h: y value horizontal offset + * @y_v: y value vertical offset + * @cb_h: cb value horizontal offset + * @cb_v: cb value vertical offset + * @cr_h: cr value horizontal offset + * @cr_v: cr value vertical offset + */ +struct fimc_dma_offset { + int y_h; + int y_v; + int cb_h; + int cb_v; + int cr_h; + int cr_v; +}; + +/** + * struct fimc_effect - color effect information + * @type: effect type + * @pat_cb: cr value when type is "arbitrary" + * @pat_cr: cr value when type is "arbitrary" + */ +struct fimc_effect { + u32 type; + u8 pat_cb; + u8 pat_cr; +}; + +/** + * struct fimc_scaler - the configuration data for FIMC inetrnal scaler + * @scaleup_h: flag indicating scaling up horizontally + * @scaleup_v: flag indicating scaling up vertically + * @copy_mode: flag indicating transparent DMA transfer (no scaling + * and color format conversion) + * @enabled: flag indicating if the scaler is used + * @hfactor: horizontal shift factor + * @vfactor: vertical shift factor + * @pre_hratio: horizontal ratio of the prescaler + * @pre_vratio: vertical ratio of the prescaler + * @pre_dst_width: the prescaler's destination width + * @pre_dst_height: the prescaler's destination height + * @main_hratio: the main scaler's horizontal ratio + * @main_vratio: the main scaler's vertical ratio + * @real_width: source pixel (width - offset) + * @real_height: source pixel (height - offset) + */ +struct fimc_scaler { + unsigned int scaleup_h:1; + unsigned int scaleup_v:1; + unsigned int copy_mode:1; + unsigned int enabled:1; + u32 hfactor; + u32 vfactor; + u32 pre_hratio; + u32 pre_vratio; + u32 pre_dst_width; + u32 pre_dst_height; + u32 main_hratio; + u32 main_vratio; + u32 real_width; + u32 real_height; +}; + +/** + * struct fimc_addr - the FIMC address set for DMA + * @y: luminance plane address + * @cb: Cb plane address + * @cr: Cr plane address + */ +struct fimc_addr { + u32 y; + u32 cb; + u32 cr; +}; + +/** + * struct fimc_vid_buffer - the driver's video buffer + * @vb: v4l vb2 buffer + * @list: linked list structure for buffer queue + * @addr: precalculated DMA address set + * @index: buffer index for the output DMA engine + */ +struct fimc_vid_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; + struct fimc_addr addr; + int index; +}; + +/** + * struct fimc_frame - source/target frame properties + * @f_width: image full width (virtual screen size) + * @f_height: image full height (virtual screen size) + * @o_width: original image width as set by S_FMT + * @o_height: original image height as set by S_FMT + * @offs_h: image horizontal pixel offset + * @offs_v: image vertical pixel offset + * @width: image pixel width + * @height: image pixel weight + * @payload: image size in bytes (w x h x bpp) + * @bytesperline: bytesperline value for each plane + * @addr: image frame buffer DMA addresses + * @dma_offset: DMA offset in bytes + * @fmt: fimc color format pointer + * @alpha: alpha value + */ +struct fimc_frame { + u32 f_width; + u32 f_height; + u32 o_width; + u32 o_height; + u32 offs_h; + u32 offs_v; + u32 width; + u32 height; + unsigned int payload[VIDEO_MAX_PLANES]; + unsigned int bytesperline[VIDEO_MAX_PLANES]; + struct fimc_addr addr; + struct fimc_dma_offset dma_offset; + struct fimc_fmt *fmt; + u8 alpha; +}; + +/** + * struct fimc_m2m_device - v4l2 memory-to-memory device data + * @vfd: the video device node for v4l2 m2m mode + * @m2m_dev: v4l2 memory-to-memory device data + * @ctx: hardware context data + * @refcnt: the reference counter + */ +struct fimc_m2m_device { + struct video_device vfd; + struct v4l2_m2m_dev *m2m_dev; + struct fimc_ctx *ctx; + int refcnt; +}; + +#define FIMC_SD_PAD_SINK_CAM 0 +#define FIMC_SD_PAD_SINK_FIFO 1 +#define FIMC_SD_PAD_SOURCE 2 +#define FIMC_SD_PADS_NUM 3 + +/** + * struct fimc_vid_cap - camera capture device information + * @ctx: hardware context data + * @subdev: subdev exposing the FIMC processing block + * @ve: exynos video device entity structure + * @vd_pad: fimc video capture node pad + * @sd_pads: fimc video processing block pads + * @ci_fmt: image format at the FIMC camera input (and the scaler output) + * @wb_fmt: image format at the FIMC ISP Writeback input + * @source_config: external image source related configuration structure + * @pending_buf_q: the pending buffer queue head + * @active_buf_q: the queue head of buffers scheduled in hardware + * @vbq: the capture am video buffer queue + * @active_buf_cnt: number of video buffers scheduled in hardware + * @buf_index: index for managing the output DMA buffers + * @frame_count: the frame counter for statistics + * @reqbufs_count: the number of buffers requested in REQBUFS ioctl + * @streaming: is streaming in progress? + * @input: capture input type, grp_id of the attached subdev + * @user_subdev_api: true if subdevs are not configured by the host driver + */ +struct fimc_vid_cap { + struct fimc_ctx *ctx; + struct v4l2_subdev subdev; + struct exynos_video_entity ve; + struct media_pad vd_pad; + struct media_pad sd_pads[FIMC_SD_PADS_NUM]; + struct v4l2_mbus_framefmt ci_fmt; + struct v4l2_mbus_framefmt wb_fmt; + struct fimc_source_info source_config; + struct list_head pending_buf_q; + struct list_head active_buf_q; + struct vb2_queue vbq; + int active_buf_cnt; + int buf_index; + unsigned int frame_count; + unsigned int reqbufs_count; + bool streaming; + u32 input; + bool user_subdev_api; +}; + +/** + * struct fimc_pix_limit - image pixel size limits in various IP configurations + * + * @scaler_en_w: max input pixel width when the scaler is enabled + * @scaler_dis_w: max input pixel width when the scaler is disabled + * @in_rot_en_h: max input width with the input rotator is on + * @in_rot_dis_w: max input width with the input rotator is off + * @out_rot_en_w: max output width with the output rotator on + * @out_rot_dis_w: max output width with the output rotator off + */ +struct fimc_pix_limit { + u16 scaler_en_w; + u16 scaler_dis_w; + u16 in_rot_en_h; + u16 in_rot_dis_w; + u16 out_rot_en_w; + u16 out_rot_dis_w; +}; + +/** + * struct fimc_variant - FIMC device variant information + * @has_inp_rot: set if has input rotator + * @has_out_rot: set if has output rotator + * @has_mainscaler_ext: 1 if extended mainscaler ratios in CIEXTEN register + * are present in this IP revision + * @has_cam_if: set if this instance has a camera input interface + * @has_isp_wb: set if this instance has ISP writeback input + * @pix_limit: pixel size constraints for the scaler + * @min_inp_pixsize: minimum input pixel size + * @min_out_pixsize: minimum output pixel size + * @hor_offs_align: horizontal pixel offset alignment + * @min_vsize_align: minimum vertical pixel size alignment + */ +struct fimc_variant { + unsigned int has_inp_rot:1; + unsigned int has_out_rot:1; + unsigned int has_mainscaler_ext:1; + unsigned int has_cam_if:1; + unsigned int has_isp_wb:1; + const struct fimc_pix_limit *pix_limit; + u16 min_inp_pixsize; + u16 min_out_pixsize; + u16 hor_offs_align; + u16 min_vsize_align; +}; + +/** + * struct fimc_drvdata - per device type driver data + * @variant: variant information for this device + * @num_entities: number of fimc instances available in a SoC + * @lclk_frequency: local bus clock frequency + * @cistatus2: 1 if the FIMC IPs have CISTATUS2 register + * @dma_pix_hoff: the horizontal DMA offset unit: 1 - pixels, 0 - bytes + * @alpha_color: 1 if alpha color component is supported + * @out_buf_count: maximum number of output DMA buffers supported + */ +struct fimc_drvdata { + const struct fimc_variant *variant[FIMC_MAX_DEVS]; + int num_entities; + unsigned long lclk_frequency; + /* Fields common to all FIMC IP instances */ + u8 cistatus2; + u8 dma_pix_hoff; + u8 alpha_color; + u8 out_buf_count; +}; + +#define fimc_get_drvdata(_pdev) \ + ((struct fimc_drvdata *) platform_get_device_id(_pdev)->driver_data) + +struct fimc_ctx; + +/** + * struct fimc_dev - abstraction for FIMC entity + * @slock: the spinlock protecting this data structure + * @lock: the mutex protecting this data structure + * @pdev: pointer to the FIMC platform device + * @pdata: pointer to the device platform data + * @sysreg: pointer to the SYSREG regmap + * @variant: the IP variant information + * @drv_data: driver data + * @id: FIMC device index (0..FIMC_MAX_DEVS) + * @clock: clocks required for FIMC operation + * @regs: the mapped hardware registers + * @irq_queue: interrupt handler waitqueue + * @v4l2_dev: root v4l2_device + * @m2m: memory-to-memory V4L2 device information + * @vid_cap: camera capture device information + * @state: flags used to synchronize m2m and capture mode operation + */ +struct fimc_dev { + spinlock_t slock; + struct mutex lock; + struct platform_device *pdev; + struct s5p_platform_fimc *pdata; + struct regmap *sysreg; + const struct fimc_variant *variant; + const struct fimc_drvdata *drv_data; + int id; + struct clk *clock[MAX_FIMC_CLOCKS]; + void __iomem *regs; + wait_queue_head_t irq_queue; + struct v4l2_device *v4l2_dev; + struct fimc_m2m_device m2m; + struct fimc_vid_cap vid_cap; + unsigned long state; +}; + +/** + * struct fimc_ctrls - v4l2 controls structure + * @handler: the control handler + * @colorfx: image effect control + * @colorfx_cbcr: Cb/Cr coefficients control + * @rotate: image rotation control + * @hflip: horizontal flip control + * @vflip: vertical flip control + * @alpha: RGB alpha control + * @ready: true if @handler is initialized + */ +struct fimc_ctrls { + struct v4l2_ctrl_handler handler; + struct { + struct v4l2_ctrl *colorfx; + struct v4l2_ctrl *colorfx_cbcr; + }; + struct v4l2_ctrl *rotate; + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; + struct v4l2_ctrl *alpha; + bool ready; +}; + +/** + * struct fimc_ctx - the device context data + * @s_frame: source frame properties + * @d_frame: destination frame properties + * @out_order_1p: output 1-plane YCBCR order + * @out_order_2p: output 2-plane YCBCR order + * @in_order_1p: input 1-plane YCBCR order + * @in_order_2p: input 2-plane YCBCR order + * @in_path: input mode (DMA or camera) + * @out_path: output mode (DMA or FIFO) + * @scaler: image scaler properties + * @effect: image effect + * @rotation: image clockwise rotation in degrees + * @hflip: indicates image horizontal flip if set + * @vflip: indicates image vertical flip if set + * @flags: additional flags for image conversion + * @state: flags to keep track of user configuration + * @fimc_dev: the FIMC device this context applies to + * @fh: v4l2 file handle + * @ctrls: v4l2 controls structure + */ +struct fimc_ctx { + struct fimc_frame s_frame; + struct fimc_frame d_frame; + u32 out_order_1p; + u32 out_order_2p; + u32 in_order_1p; + u32 in_order_2p; + enum fimc_datapath in_path; + enum fimc_datapath out_path; + struct fimc_scaler scaler; + struct fimc_effect effect; + int rotation; + unsigned int hflip:1; + unsigned int vflip:1; + u32 flags; + u32 state; + struct fimc_dev *fimc_dev; + struct v4l2_fh fh; + struct fimc_ctrls ctrls; +}; + +#define fh_to_ctx(__fh) container_of(__fh, struct fimc_ctx, fh) + +static inline void set_frame_bounds(struct fimc_frame *f, u32 width, u32 height) +{ + f->o_width = width; + f->o_height = height; + f->f_width = width; + f->f_height = height; +} + +static inline void set_frame_crop(struct fimc_frame *f, + u32 left, u32 top, u32 width, u32 height) +{ + f->offs_h = left; + f->offs_v = top; + f->width = width; + f->height = height; +} + +static inline u32 fimc_get_format_depth(struct fimc_fmt *ff) +{ + u32 i, depth = 0; + + if (ff != NULL) + for (i = 0; i < ff->colplanes; i++) + depth += ff->depth[i]; + return depth; +} + +static inline bool fimc_capture_active(struct fimc_dev *fimc) +{ + unsigned long flags; + bool ret; + + spin_lock_irqsave(&fimc->slock, flags); + ret = !!(fimc->state & (1 << ST_CAPT_RUN) || + fimc->state & (1 << ST_CAPT_PEND)); + spin_unlock_irqrestore(&fimc->slock, flags); + return ret; +} + +static inline void fimc_ctx_state_set(u32 state, struct fimc_ctx *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->fimc_dev->slock, flags); + ctx->state |= state; + spin_unlock_irqrestore(&ctx->fimc_dev->slock, flags); +} + +static inline bool fimc_ctx_state_is_set(u32 mask, struct fimc_ctx *ctx) +{ + unsigned long flags; + bool ret; + + spin_lock_irqsave(&ctx->fimc_dev->slock, flags); + ret = (ctx->state & mask) == mask; + spin_unlock_irqrestore(&ctx->fimc_dev->slock, flags); + return ret; +} + +static inline int tiled_fmt(struct fimc_fmt *fmt) +{ + return fmt->fourcc == V4L2_PIX_FMT_NV12MT; +} + +static inline bool fimc_jpeg_fourcc(u32 pixelformat) +{ + return (pixelformat == V4L2_PIX_FMT_JPEG || + pixelformat == V4L2_PIX_FMT_S5C_UYVY_JPG); +} + +static inline bool fimc_user_defined_mbus_fmt(u32 code) +{ + return (code == MEDIA_BUS_FMT_JPEG_1X8 || + code == MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8); +} + +/* Return the alpha component bit mask */ +static inline int fimc_get_alpha_mask(struct fimc_fmt *fmt) +{ + switch (fmt->color) { + case FIMC_FMT_RGB444: return 0x0f; + case FIMC_FMT_RGB555: return 0x01; + case FIMC_FMT_RGB888: return 0xff; + default: return 0; + }; +} + +static inline struct fimc_frame *ctx_get_frame(struct fimc_ctx *ctx, + enum v4l2_buf_type type) +{ + struct fimc_frame *frame; + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE || + type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (fimc_ctx_state_is_set(FIMC_CTX_M2M, ctx)) + frame = &ctx->s_frame; + else + return ERR_PTR(-EINVAL); + } else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE || + type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + frame = &ctx->d_frame; + } else { + v4l2_err(ctx->fimc_dev->v4l2_dev, + "Wrong buffer/video queue type (%d)\n", type); + return ERR_PTR(-EINVAL); + } + + return frame; +} + +/* -----------------------------------------------------*/ +/* fimc-core.c */ +int fimc_vidioc_enum_fmt_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f); +int fimc_ctrls_create(struct fimc_ctx *ctx); +void fimc_ctrls_delete(struct fimc_ctx *ctx); +void fimc_ctrls_activate(struct fimc_ctx *ctx, bool active); +void fimc_alpha_ctrl_update(struct fimc_ctx *ctx); +void __fimc_get_format(struct fimc_frame *frame, struct v4l2_format *f); +void fimc_adjust_mplane_format(struct fimc_fmt *fmt, u32 width, u32 height, + struct v4l2_pix_format_mplane *pix); +struct fimc_fmt *fimc_find_format(const u32 *pixelformat, const u32 *mbus_code, + unsigned int mask, int index); +struct fimc_fmt *fimc_get_format(unsigned int index); + +int fimc_check_scaler_ratio(struct fimc_ctx *ctx, int sw, int sh, + int dw, int dh, int rotation); +int fimc_set_scaler_info(struct fimc_ctx *ctx); +int fimc_prepare_config(struct fimc_ctx *ctx, u32 flags); +int fimc_prepare_addr(struct fimc_ctx *ctx, struct vb2_buffer *vb, + struct fimc_frame *frame, struct fimc_addr *addr); +void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f); +void fimc_set_yuv_order(struct fimc_ctx *ctx); +void fimc_capture_irq_handler(struct fimc_dev *fimc, int deq_buf); + +int fimc_register_m2m_device(struct fimc_dev *fimc, + struct v4l2_device *v4l2_dev); +void fimc_unregister_m2m_device(struct fimc_dev *fimc); +int fimc_register_driver(void); +void fimc_unregister_driver(void); + +#ifdef CONFIG_MFD_SYSCON +static inline struct regmap * fimc_get_sysreg_regmap(struct device_node *node) +{ + return syscon_regmap_lookup_by_phandle(node, "samsung,sysreg"); +} +#else +#define fimc_get_sysreg_regmap(node) (NULL) +#endif + +/* -----------------------------------------------------*/ +/* fimc-m2m.c */ +void fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state); + +/* -----------------------------------------------------*/ +/* fimc-capture.c */ +int fimc_initialize_capture_subdev(struct fimc_dev *fimc); +void fimc_unregister_capture_subdev(struct fimc_dev *fimc); +int fimc_capture_ctrls_create(struct fimc_dev *fimc); +void fimc_sensor_notify(struct v4l2_subdev *sd, unsigned int notification, + void *arg); +int fimc_capture_suspend(struct fimc_dev *fimc); +int fimc_capture_resume(struct fimc_dev *fimc); + +/* + * Buffer list manipulation functions. Must be called with fimc.slock held. + */ + +/** + * fimc_active_queue_add - add buffer to the capture active buffers queue + * @vid_cap: camera capture device information + * @buf: buffer to add to the active buffers list + */ +static inline void fimc_active_queue_add(struct fimc_vid_cap *vid_cap, + struct fimc_vid_buffer *buf) +{ + list_add_tail(&buf->list, &vid_cap->active_buf_q); + vid_cap->active_buf_cnt++; +} + +/** + * fimc_active_queue_pop - pop buffer from the capture active buffers queue + * @vid_cap: camera capture device information + * + * The caller must assure the active_buf_q list is not empty. + */ +static inline struct fimc_vid_buffer *fimc_active_queue_pop( + struct fimc_vid_cap *vid_cap) +{ + struct fimc_vid_buffer *buf; + buf = list_entry(vid_cap->active_buf_q.next, + struct fimc_vid_buffer, list); + list_del(&buf->list); + vid_cap->active_buf_cnt--; + return buf; +} + +/** + * fimc_pending_queue_add - add buffer to the capture pending buffers queue + * @vid_cap: camera capture device information + * @buf: buffer to add to the pending buffers list + */ +static inline void fimc_pending_queue_add(struct fimc_vid_cap *vid_cap, + struct fimc_vid_buffer *buf) +{ + list_add_tail(&buf->list, &vid_cap->pending_buf_q); +} + +/** + * fimc_pending_queue_pop - pop buffer from the capture pending buffers queue + * @vid_cap: camera capture device information + * + * The caller must assure the pending_buf_q list is not empty. + */ +static inline struct fimc_vid_buffer *fimc_pending_queue_pop( + struct fimc_vid_cap *vid_cap) +{ + struct fimc_vid_buffer *buf; + buf = list_entry(vid_cap->pending_buf_q.next, + struct fimc_vid_buffer, list); + list_del(&buf->list); + return buf; +} + +#endif /* FIMC_CORE_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-command.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-command.h new file mode 100644 index 000000000..87978609a --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-command.h @@ -0,0 +1,134 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung Exynos4x12 FIMC-IS (Imaging Subsystem) driver + * + * FIMC-IS command set definitions + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Authors: Younghwan Joo + * Sylwester Nawrocki + */ + +#ifndef FIMC_IS_CMD_H_ +#define FIMC_IS_CMD_H_ + +#define FIMC_IS_COMMAND_VER 110 /* FIMC-IS command set version 1.10 */ + +/* Enumeration of commands between the FIMC-IS and the host processor. */ + +/* HOST to FIMC-IS */ +#define HIC_PREVIEW_STILL 0x0001 +#define HIC_PREVIEW_VIDEO 0x0002 +#define HIC_CAPTURE_STILL 0x0003 +#define HIC_CAPTURE_VIDEO 0x0004 +#define HIC_STREAM_ON 0x0005 +#define HIC_STREAM_OFF 0x0006 +#define HIC_SET_PARAMETER 0x0007 +#define HIC_GET_PARAMETER 0x0008 +#define HIC_SET_TUNE 0x0009 +#define HIC_GET_STATUS 0x000b +/* Sensor part */ +#define HIC_OPEN_SENSOR 0x000c +#define HIC_CLOSE_SENSOR 0x000d +#define HIC_SIMMIAN_INIT 0x000e +#define HIC_SIMMIAN_WRITE 0x000f +#define HIC_SIMMIAN_READ 0x0010 +#define HIC_POWER_DOWN 0x0011 +#define HIC_GET_SET_FILE_ADDR 0x0012 +#define HIC_LOAD_SET_FILE 0x0013 +#define HIC_MSG_CONFIG 0x0014 +#define HIC_MSG_TEST 0x0015 +/* FIMC-IS to HOST */ +#define IHC_GET_SENSOR_NUM 0x1000 +#define IHC_SET_SHOT_MARK 0x1001 +/* parameter1: frame number */ +/* parameter2: confidence level (smile 0~100) */ +/* parameter3: confidence level (blink 0~100) */ +#define IHC_SET_FACE_MARK 0x1002 +/* parameter1: coordinate count */ +/* parameter2: coordinate buffer address */ +#define IHC_FRAME_DONE 0x1003 +/* parameter1: frame start number */ +/* parameter2: frame count */ +#define IHC_AA_DONE 0x1004 +#define IHC_NOT_READY 0x1005 + +#define IH_REPLY_DONE 0x2000 +#define IH_REPLY_NOT_DONE 0x2001 + +enum fimc_is_scenario { + IS_SC_PREVIEW_STILL, + IS_SC_PREVIEW_VIDEO, + IS_SC_CAPTURE_STILL, + IS_SC_CAPTURE_VIDEO, + IS_SC_MAX +}; + +enum fimc_is_sub_scenario { + IS_SC_SUB_DEFAULT, + IS_SC_SUB_PS_VTCALL, + IS_SC_SUB_CS_VTCALL, + IS_SC_SUB_PV_VTCALL, + IS_SC_SUB_CV_VTCALL, +}; + +struct is_common_regs { + u32 hicmd; + u32 hic_sensorid; + u32 hic_param[4]; + u32 reserved1[4]; + + u32 ihcmd; + u32 ihc_sensorid; + u32 ihc_param[4]; + u32 reserved2[4]; + + u32 isp_sensor_id; + u32 isp_param[2]; + u32 reserved3[1]; + + u32 scc_sensor_id; + u32 scc_param[2]; + u32 reserved4[1]; + + u32 dnr_sensor_id; + u32 dnr_param[2]; + u32 reserved5[1]; + + u32 scp_sensor_id; + u32 scp_param[2]; + u32 reserved6[29]; +} __packed; + +struct is_mcuctl_reg { + u32 mcuctl; + u32 bboar; + + u32 intgr0; + u32 intcr0; + u32 intmr0; + u32 intsr0; + u32 intmsr0; + + u32 intgr1; + u32 intcr1; + u32 intmr1; + u32 intsr1; + u32 intmsr1; + + u32 intcr2; + u32 intmr2; + u32 intsr2; + u32 intmsr2; + + u32 gpoctrl; + u32 cpoenctlr; + u32 gpictlr; + + u32 reserved[0xd]; + + struct is_common_regs common; +} __packed; + +#endif /* FIMC_IS_CMD_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.c new file mode 100644 index 000000000..5d9f4c1cd --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.c @@ -0,0 +1,269 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung Exynos4 SoC series FIMC-IS slave interface driver + * + * Error log interface functions + * + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * + * Authors: Younghwan Joo + * Sylwester Nawrocki + */ + +#include "fimc-is-errno.h" + +const char *fimc_is_param_strerr(unsigned int error) +{ + switch (error) { + case ERROR_COMMON_CMD: + return "ERROR_COMMON_CMD: Invalid Command"; + case ERROR_COMMON_PARAMETER: + return "ERROR_COMMON_PARAMETER: Invalid Parameter"; + case ERROR_COMMON_SETFILE_LOAD: + return "ERROR_COMMON_SETFILE_LOAD: Illegal Setfile Loading"; + case ERROR_COMMON_SETFILE_ADJUST: + return "ERROR_COMMON_SETFILE_ADJUST: Setfile isn't adjusted"; + case ERROR_COMMON_SETFILE_INDEX: + return "ERROR_COMMON_SETFILE_INDEX: Invalid setfile index"; + case ERROR_COMMON_INPUT_PATH: + return "ERROR_COMMON_INPUT_PATH: Input path can be changed in ready state"; + case ERROR_COMMON_INPUT_INIT: + return "ERROR_COMMON_INPUT_INIT: IP can not start if input path is not set"; + case ERROR_COMMON_OUTPUT_PATH: + return "ERROR_COMMON_OUTPUT_PATH: Output path can be changed in ready state (stop)"; + case ERROR_COMMON_OUTPUT_INIT: + return "ERROR_COMMON_OUTPUT_INIT: IP can not start if output path is not set"; + case ERROR_CONTROL_BYPASS: + return "ERROR_CONTROL_BYPASS"; + case ERROR_OTF_INPUT_FORMAT: + return "ERROR_OTF_INPUT_FORMAT: Invalid format (DRC: YUV444, FD: YUV444, 422, 420)"; + case ERROR_OTF_INPUT_WIDTH: + return "ERROR_OTF_INPUT_WIDTH: Invalid width (DRC: 128~8192, FD: 32~8190)"; + case ERROR_OTF_INPUT_HEIGHT: + return "ERROR_OTF_INPUT_HEIGHT: Invalid bit-width (DRC: 8~12bits, FD: 8bit)"; + case ERROR_OTF_INPUT_BIT_WIDTH: + return "ERROR_OTF_INPUT_BIT_WIDTH: Invalid bit-width (DRC: 8~12bits, FD: 8bit)"; + case ERROR_DMA_INPUT_WIDTH: + return "ERROR_DMA_INPUT_WIDTH: Invalid width (DRC: 128~8192, FD: 32~8190)"; + case ERROR_DMA_INPUT_HEIGHT: + return "ERROR_DMA_INPUT_HEIGHT: Invalid height (DRC: 64~8192, FD: 16~8190)"; + case ERROR_DMA_INPUT_FORMAT: + return "ERROR_DMA_INPUT_FORMAT: Invalid format (DRC: YUV444 or YUV422, FD: YUV444,422,420)"; + case ERROR_DMA_INPUT_BIT_WIDTH: + return "ERROR_DMA_INPUT_BIT_WIDTH: Invalid bit-width (DRC: 8~12bits, FD: 8bit)"; + case ERROR_DMA_INPUT_ORDER: + return "ERROR_DMA_INPUT_ORDER: Invalid order(DRC: YYCbCr,YCbYCr,FD:NO,YYCbCr,YCbYCr,CbCr,CrCb)"; + case ERROR_DMA_INPUT_PLANE: + return "ERROR_DMA_INPUT_PLANE: Invalid palne (DRC: 3, FD: 1, 2, 3)"; + case ERROR_OTF_OUTPUT_WIDTH: + return "ERROR_OTF_OUTPUT_WIDTH: Invalid width (DRC: 128~8192)"; + case ERROR_OTF_OUTPUT_HEIGHT: + return "ERROR_OTF_OUTPUT_HEIGHT: Invalid height (DRC: 64~8192)"; + case ERROR_OTF_OUTPUT_FORMAT: + return "ERROR_OTF_OUTPUT_FORMAT: Invalid format (DRC: YUV444)"; + case ERROR_OTF_OUTPUT_BIT_WIDTH: + return "ERROR_OTF_OUTPUT_BIT_WIDTH: Invalid bit-width (DRC: 8~12bits, FD: 8bit)"; + case ERROR_DMA_OUTPUT_WIDTH: + return "ERROR_DMA_OUTPUT_WIDTH"; + case ERROR_DMA_OUTPUT_HEIGHT: + return "ERROR_DMA_OUTPUT_HEIGHT"; + case ERROR_DMA_OUTPUT_FORMAT: + return "ERROR_DMA_OUTPUT_FORMAT"; + case ERROR_DMA_OUTPUT_BIT_WIDTH: + return "ERROR_DMA_OUTPUT_BIT_WIDTH"; + case ERROR_DMA_OUTPUT_PLANE: + return "ERROR_DMA_OUTPUT_PLANE"; + case ERROR_DMA_OUTPUT_ORDER: + return "ERROR_DMA_OUTPUT_ORDER"; + + /* Sensor Error(100~199) */ + case ERROR_SENSOR_I2C_FAIL: + return "ERROR_SENSOR_I2C_FAIL"; + case ERROR_SENSOR_INVALID_FRAMERATE: + return "ERROR_SENSOR_INVALID_FRAMERATE"; + case ERROR_SENSOR_INVALID_EXPOSURETIME: + return "ERROR_SENSOR_INVALID_EXPOSURETIME"; + case ERROR_SENSOR_INVALID_SIZE: + return "ERROR_SENSOR_INVALID_SIZE"; + case ERROR_SENSOR_INVALID_SETTING: + return "ERROR_SENSOR_INVALID_SETTING"; + case ERROR_SENSOR_ACTUATOR_INIT_FAIL: + return "ERROR_SENSOR_ACTUATOR_INIT_FAIL"; + case ERROR_SENSOR_INVALID_AF_POS: + return "ERROR_SENSOR_INVALID_AF_POS"; + case ERROR_SENSOR_UNSUPPORT_FUNC: + return "ERROR_SENSOR_UNSUPPORT_FUNC"; + case ERROR_SENSOR_UNSUPPORT_PERI: + return "ERROR_SENSOR_UNSUPPORT_PERI"; + case ERROR_SENSOR_UNSUPPORT_AF: + return "ERROR_SENSOR_UNSUPPORT_AF"; + + /* ISP Error (200~299) */ + case ERROR_ISP_AF_BUSY: + return "ERROR_ISP_AF_BUSY"; + case ERROR_ISP_AF_INVALID_COMMAND: + return "ERROR_ISP_AF_INVALID_COMMAND"; + case ERROR_ISP_AF_INVALID_MODE: + return "ERROR_ISP_AF_INVALID_MODE"; + + /* DRC Error (300~399) */ + /* FD Error (400~499) */ + case ERROR_FD_CONFIG_MAX_NUMBER_STATE: + return "ERROR_FD_CONFIG_MAX_NUMBER_STATE"; + case ERROR_FD_CONFIG_MAX_NUMBER_INVALID: + return "ERROR_FD_CONFIG_MAX_NUMBER_INVALID"; + case ERROR_FD_CONFIG_YAW_ANGLE_STATE: + return "ERROR_FD_CONFIG_YAW_ANGLE_STATE"; + case ERROR_FD_CONFIG_YAW_ANGLE_INVALID: + return "ERROR_FD_CONFIG_YAW_ANGLE_INVALID\n"; + case ERROR_FD_CONFIG_ROLL_ANGLE_STATE: + return "ERROR_FD_CONFIG_ROLL_ANGLE_STATE"; + case ERROR_FD_CONFIG_ROLL_ANGLE_INVALID: + return "ERROR_FD_CONFIG_ROLL_ANGLE_INVALID"; + case ERROR_FD_CONFIG_SMILE_MODE_INVALID: + return "ERROR_FD_CONFIG_SMILE_MODE_INVALID"; + case ERROR_FD_CONFIG_BLINK_MODE_INVALID: + return "ERROR_FD_CONFIG_BLINK_MODE_INVALID"; + case ERROR_FD_CONFIG_EYES_DETECT_INVALID: + return "ERROR_FD_CONFIG_EYES_DETECT_INVALID"; + case ERROR_FD_CONFIG_MOUTH_DETECT_INVALID: + return "ERROR_FD_CONFIG_MOUTH_DETECT_INVALID"; + case ERROR_FD_CONFIG_ORIENTATION_STATE: + return "ERROR_FD_CONFIG_ORIENTATION_STATE"; + case ERROR_FD_CONFIG_ORIENTATION_INVALID: + return "ERROR_FD_CONFIG_ORIENTATION_INVALID"; + case ERROR_FD_CONFIG_ORIENTATION_VALUE_INVALID: + return "ERROR_FD_CONFIG_ORIENTATION_VALUE_INVALID"; + case ERROR_FD_RESULT: + return "ERROR_FD_RESULT"; + case ERROR_FD_MODE: + return "ERROR_FD_MODE"; + default: + return "Unknown"; + } +} + +const char *fimc_is_strerr(unsigned int error) +{ + error &= ~IS_ERROR_TIME_OUT_FLAG; + + switch (error) { + /* General */ + case IS_ERROR_INVALID_COMMAND: + return "IS_ERROR_INVALID_COMMAND"; + case IS_ERROR_REQUEST_FAIL: + return "IS_ERROR_REQUEST_FAIL"; + case IS_ERROR_INVALID_SCENARIO: + return "IS_ERROR_INVALID_SCENARIO"; + case IS_ERROR_INVALID_SENSORID: + return "IS_ERROR_INVALID_SENSORID"; + case IS_ERROR_INVALID_MODE_CHANGE: + return "IS_ERROR_INVALID_MODE_CHANGE"; + case IS_ERROR_INVALID_MAGIC_NUMBER: + return "IS_ERROR_INVALID_MAGIC_NUMBER"; + case IS_ERROR_INVALID_SETFILE_HDR: + return "IS_ERROR_INVALID_SETFILE_HDR"; + case IS_ERROR_BUSY: + return "IS_ERROR_BUSY"; + case IS_ERROR_SET_PARAMETER: + return "IS_ERROR_SET_PARAMETER"; + case IS_ERROR_INVALID_PATH: + return "IS_ERROR_INVALID_PATH"; + case IS_ERROR_OPEN_SENSOR_FAIL: + return "IS_ERROR_OPEN_SENSOR_FAIL"; + case IS_ERROR_ENTRY_MSG_THREAD_DOWN: + return "IS_ERROR_ENTRY_MSG_THREAD_DOWN"; + case IS_ERROR_ISP_FRAME_END_NOT_DONE: + return "IS_ERROR_ISP_FRAME_END_NOT_DONE"; + case IS_ERROR_DRC_FRAME_END_NOT_DONE: + return "IS_ERROR_DRC_FRAME_END_NOT_DONE"; + case IS_ERROR_SCALERC_FRAME_END_NOT_DONE: + return "IS_ERROR_SCALERC_FRAME_END_NOT_DONE"; + case IS_ERROR_ODC_FRAME_END_NOT_DONE: + return "IS_ERROR_ODC_FRAME_END_NOT_DONE"; + case IS_ERROR_DIS_FRAME_END_NOT_DONE: + return "IS_ERROR_DIS_FRAME_END_NOT_DONE"; + case IS_ERROR_TDNR_FRAME_END_NOT_DONE: + return "IS_ERROR_TDNR_FRAME_END_NOT_DONE"; + case IS_ERROR_SCALERP_FRAME_END_NOT_DONE: + return "IS_ERROR_SCALERP_FRAME_END_NOT_DONE"; + case IS_ERROR_WAIT_STREAM_OFF_NOT_DONE: + return "IS_ERROR_WAIT_STREAM_OFF_NOT_DONE"; + case IS_ERROR_NO_MSG_IS_RECEIVED: + return "IS_ERROR_NO_MSG_IS_RECEIVED"; + case IS_ERROR_SENSOR_MSG_FAIL: + return "IS_ERROR_SENSOR_MSG_FAIL"; + case IS_ERROR_ISP_MSG_FAIL: + return "IS_ERROR_ISP_MSG_FAIL"; + case IS_ERROR_DRC_MSG_FAIL: + return "IS_ERROR_DRC_MSG_FAIL"; + case IS_ERROR_LHFD_MSG_FAIL: + return "IS_ERROR_LHFD_MSG_FAIL"; + case IS_ERROR_UNKNOWN: + return "IS_ERROR_UNKNOWN"; + + /* Sensor */ + case IS_ERROR_SENSOR_PWRDN_FAIL: + return "IS_ERROR_SENSOR_PWRDN_FAIL"; + + /* ISP */ + case IS_ERROR_ISP_PWRDN_FAIL: + return "IS_ERROR_ISP_PWRDN_FAIL"; + case IS_ERROR_ISP_MULTIPLE_INPUT: + return "IS_ERROR_ISP_MULTIPLE_INPUT"; + case IS_ERROR_ISP_ABSENT_INPUT: + return "IS_ERROR_ISP_ABSENT_INPUT"; + case IS_ERROR_ISP_ABSENT_OUTPUT: + return "IS_ERROR_ISP_ABSENT_OUTPUT"; + case IS_ERROR_ISP_NONADJACENT_OUTPUT: + return "IS_ERROR_ISP_NONADJACENT_OUTPUT"; + case IS_ERROR_ISP_FORMAT_MISMATCH: + return "IS_ERROR_ISP_FORMAT_MISMATCH"; + case IS_ERROR_ISP_WIDTH_MISMATCH: + return "IS_ERROR_ISP_WIDTH_MISMATCH"; + case IS_ERROR_ISP_HEIGHT_MISMATCH: + return "IS_ERROR_ISP_HEIGHT_MISMATCH"; + case IS_ERROR_ISP_BITWIDTH_MISMATCH: + return "IS_ERROR_ISP_BITWIDTH_MISMATCH"; + case IS_ERROR_ISP_FRAME_END_TIME_OUT: + return "IS_ERROR_ISP_FRAME_END_TIME_OUT"; + + /* DRC */ + case IS_ERROR_DRC_PWRDN_FAIL: + return "IS_ERROR_DRC_PWRDN_FAIL"; + case IS_ERROR_DRC_MULTIPLE_INPUT: + return "IS_ERROR_DRC_MULTIPLE_INPUT"; + case IS_ERROR_DRC_ABSENT_INPUT: + return "IS_ERROR_DRC_ABSENT_INPUT"; + case IS_ERROR_DRC_NONADJACENT_INPUT: + return "IS_ERROR_DRC_NONADJACENT_INPUT"; + case IS_ERROR_DRC_ABSENT_OUTPUT: + return "IS_ERROR_DRC_ABSENT_OUTPUT"; + case IS_ERROR_DRC_NONADJACENT_OUTPUT: + return "IS_ERROR_DRC_NONADJACENT_OUTPUT"; + case IS_ERROR_DRC_FORMAT_MISMATCH: + return "IS_ERROR_DRC_FORMAT_MISMATCH"; + case IS_ERROR_DRC_WIDTH_MISMATCH: + return "IS_ERROR_DRC_WIDTH_MISMATCH"; + case IS_ERROR_DRC_HEIGHT_MISMATCH: + return "IS_ERROR_DRC_HEIGHT_MISMATCH"; + case IS_ERROR_DRC_BITWIDTH_MISMATCH: + return "IS_ERROR_DRC_BITWIDTH_MISMATCH"; + case IS_ERROR_DRC_FRAME_END_TIME_OUT: + return "IS_ERROR_DRC_FRAME_END_TIME_OUT"; + + /* FD */ + case IS_ERROR_FD_PWRDN_FAIL: + return "IS_ERROR_FD_PWRDN_FAIL"; + case IS_ERROR_FD_MULTIPLE_INPUT: + return "IS_ERROR_FD_MULTIPLE_INPUT"; + case IS_ERROR_FD_ABSENT_INPUT: + return "IS_ERROR_FD_ABSENT_INPUT"; + case IS_ERROR_FD_NONADJACENT_INPUT: + return "IS_ERROR_FD_NONADJACENT_INPUT"; + case IS_ERROR_LHFD_FRAME_END_TIME_OUT: + return "IS_ERROR_LHFD_FRAME_END_TIME_OUT"; + default: + return "Unknown"; + } +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.h new file mode 100644 index 000000000..9dcbb9853 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-errno.h @@ -0,0 +1,245 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung Exynos4 SoC series FIMC-IS slave interface driver + * + * FIMC-IS error code definition + * + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * + * Authors: Younghwan Joo + * Sylwester Nawrocki +*/ + +#ifndef FIMC_IS_ERR_H_ +#define FIMC_IS_ERR_H_ + +#define IS_ERROR_VER 011 /* IS ERROR VERSION 0.11 */ + +enum { + IS_ERROR_NONE, + + /* General 1 ~ 99 */ + IS_ERROR_INVALID_COMMAND, + IS_ERROR_REQUEST_FAIL, + IS_ERROR_INVALID_SCENARIO, + IS_ERROR_INVALID_SENSORID, + IS_ERROR_INVALID_MODE_CHANGE, + IS_ERROR_INVALID_MAGIC_NUMBER, + IS_ERROR_INVALID_SETFILE_HDR, + IS_ERROR_BUSY, + IS_ERROR_SET_PARAMETER, + IS_ERROR_INVALID_PATH, + IS_ERROR_OPEN_SENSOR_FAIL, + IS_ERROR_ENTRY_MSG_THREAD_DOWN, + IS_ERROR_ISP_FRAME_END_NOT_DONE, + IS_ERROR_DRC_FRAME_END_NOT_DONE, + IS_ERROR_SCALERC_FRAME_END_NOT_DONE, + IS_ERROR_ODC_FRAME_END_NOT_DONE, + IS_ERROR_DIS_FRAME_END_NOT_DONE, + IS_ERROR_TDNR_FRAME_END_NOT_DONE, + IS_ERROR_SCALERP_FRAME_END_NOT_DONE, + IS_ERROR_WAIT_STREAM_OFF_NOT_DONE, + IS_ERROR_NO_MSG_IS_RECEIVED, + IS_ERROR_SENSOR_MSG_FAIL, + IS_ERROR_ISP_MSG_FAIL, + IS_ERROR_DRC_MSG_FAIL, + IS_ERROR_SCALERC_MSG_FAIL, + IS_ERROR_ODC_MSG_FAIL, + IS_ERROR_DIS_MSG_FAIL, + IS_ERROR_TDNR_MSG_FAIL, + IS_ERROR_SCALERP_MSG_FAIL, + IS_ERROR_LHFD_MSG_FAIL, + IS_ERROR_LHFD_INTERNAL_STOP, + + /* Sensor 100 ~ 199 */ + IS_ERROR_SENSOR_PWRDN_FAIL = 100, + IS_ERROR_SENSOR_STREAM_ON_FAIL, + IS_ERROR_SENSOR_STREAM_OFF_FAIL, + + /* ISP 200 ~ 299 */ + IS_ERROR_ISP_PWRDN_FAIL = 200, + IS_ERROR_ISP_MULTIPLE_INPUT, + IS_ERROR_ISP_ABSENT_INPUT, + IS_ERROR_ISP_ABSENT_OUTPUT, + IS_ERROR_ISP_NONADJACENT_OUTPUT, + IS_ERROR_ISP_FORMAT_MISMATCH, + IS_ERROR_ISP_WIDTH_MISMATCH, + IS_ERROR_ISP_HEIGHT_MISMATCH, + IS_ERROR_ISP_BITWIDTH_MISMATCH, + IS_ERROR_ISP_FRAME_END_TIME_OUT, + + /* DRC 300 ~ 399 */ + IS_ERROR_DRC_PWRDN_FAIL = 300, + IS_ERROR_DRC_MULTIPLE_INPUT, + IS_ERROR_DRC_ABSENT_INPUT, + IS_ERROR_DRC_NONADJACENT_INPUT, + IS_ERROR_DRC_ABSENT_OUTPUT, + IS_ERROR_DRC_NONADJACENT_OUTPUT, + IS_ERROR_DRC_FORMAT_MISMATCH, + IS_ERROR_DRC_WIDTH_MISMATCH, + IS_ERROR_DRC_HEIGHT_MISMATCH, + IS_ERROR_DRC_BITWIDTH_MISMATCH, + IS_ERROR_DRC_FRAME_END_TIME_OUT, + + /* SCALERC 400 ~ 499 */ + IS_ERROR_SCALERC_PWRDN_FAIL = 400, + + /* ODC 500 ~ 599 */ + IS_ERROR_ODC_PWRDN_FAIL = 500, + + /* DIS 600 ~ 699 */ + IS_ERROR_DIS_PWRDN_FAIL = 600, + + /* TDNR 700 ~ 799 */ + IS_ERROR_TDNR_PWRDN_FAIL = 700, + + /* SCALERC 800 ~ 899 */ + IS_ERROR_SCALERP_PWRDN_FAIL = 800, + + /* FD 900 ~ 999 */ + IS_ERROR_FD_PWRDN_FAIL = 900, + IS_ERROR_FD_MULTIPLE_INPUT, + IS_ERROR_FD_ABSENT_INPUT, + IS_ERROR_FD_NONADJACENT_INPUT, + IS_ERROR_LHFD_FRAME_END_TIME_OUT, + + IS_ERROR_UNKNOWN = 1000, +}; + +#define IS_ERROR_TIME_OUT_FLAG 0x80000000 + +/* Set parameter error enum */ +enum fimc_is_error { + /* Common error (0~99) */ + ERROR_COMMON_NONE = 0, + ERROR_COMMON_CMD = 1, /* Invalid command */ + ERROR_COMMON_PARAMETER = 2, /* Invalid parameter */ + /* setfile is not loaded before adjusting */ + ERROR_COMMON_SETFILE_LOAD = 3, + /* setfile is not Adjusted before running. */ + ERROR_COMMON_SETFILE_ADJUST = 4, + /* Index of setfile is not valid (0~MAX_SETFILE_NUM-1) */ + ERROR_COMMON_SETFILE_INDEX = 5, + /* Input path can be changed in ready state(stop) */ + ERROR_COMMON_INPUT_PATH = 6, + /* IP can not start if input path is not set */ + ERROR_COMMON_INPUT_INIT = 7, + /* Output path can be changed in ready state (stop) */ + ERROR_COMMON_OUTPUT_PATH = 8, + /* IP can not start if output path is not set */ + ERROR_COMMON_OUTPUT_INIT = 9, + + ERROR_CONTROL_NONE = ERROR_COMMON_NONE, + ERROR_CONTROL_BYPASS = 11, /* Enable or Disable */ + + ERROR_OTF_INPUT_NONE = ERROR_COMMON_NONE, + ERROR_OTF_INPUT_CMD = 21, + /* invalid format (DRC: YUV444, FD: YUV444, 422, 420) */ + ERROR_OTF_INPUT_FORMAT = 22, + /* invalid width (DRC: 128~8192, FD: 32~8190) */ + ERROR_OTF_INPUT_WIDTH = 23, + /* invalid height (DRC: 64~8192, FD: 16~8190) */ + ERROR_OTF_INPUT_HEIGHT = 24, + /* invalid bit-width (DRC: 8~12bits, FD: 8bit) */ + ERROR_OTF_INPUT_BIT_WIDTH = 25, + /* invalid FrameTime for ISP */ + ERROR_OTF_INPUT_USER_FRAMETIIME = 26, + + ERROR_DMA_INPUT_NONE = ERROR_COMMON_NONE, + /* invalid width (DRC: 128~8192, FD: 32~8190) */ + ERROR_DMA_INPUT_WIDTH = 31, + /* invalid height (DRC: 64~8192, FD: 16~8190) */ + ERROR_DMA_INPUT_HEIGHT = 32, + /* invalid format (DRC: YUV444 or YUV422, FD: YUV444, 422, 420) */ + ERROR_DMA_INPUT_FORMAT = 33, + /* invalid bit-width (DRC: 8~12bit, FD: 8bit) */ + ERROR_DMA_INPUT_BIT_WIDTH = 34, + /* invalid order(DRC: YYCbCrorYCbYCr, FD:NO,YYCbCr,YCbYCr,CbCr,CrCb) */ + ERROR_DMA_INPUT_ORDER = 35, + /* invalid palne (DRC: 3, FD: 1, 2, 3) */ + ERROR_DMA_INPUT_PLANE = 36, + + ERROR_OTF_OUTPUT_NONE = ERROR_COMMON_NONE, + /* invalid width (DRC: 128~8192) */ + ERROR_OTF_OUTPUT_WIDTH = 41, + /* invalid height (DRC: 64~8192) */ + ERROR_OTF_OUTPUT_HEIGHT = 42, + /* invalid format (DRC: YUV444) */ + ERROR_OTF_OUTPUT_FORMAT = 43, + /* invalid bit-width (DRC: 8~12bits) */ + ERROR_OTF_OUTPUT_BIT_WIDTH = 44, + + ERROR_DMA_OUTPUT_NONE = ERROR_COMMON_NONE, + ERROR_DMA_OUTPUT_WIDTH = 51, /* invalid width */ + ERROR_DMA_OUTPUT_HEIGHT = 52, /* invalid height */ + ERROR_DMA_OUTPUT_FORMAT = 53, /* invalid format */ + ERROR_DMA_OUTPUT_BIT_WIDTH = 54, /* invalid bit-width */ + ERROR_DMA_OUTPUT_PLANE = 55, /* invalid plane */ + ERROR_DMA_OUTPUT_ORDER = 56, /* invalid order */ + + ERROR_GLOBAL_SHOTMODE_NONE = ERROR_COMMON_NONE, + + /* SENSOR Error(100~199) */ + ERROR_SENSOR_NONE = ERROR_COMMON_NONE, + ERROR_SENSOR_I2C_FAIL = 101, + ERROR_SENSOR_INVALID_FRAMERATE, + ERROR_SENSOR_INVALID_EXPOSURETIME, + ERROR_SENSOR_INVALID_SIZE, + ERROR_SENSOR_INVALID_SETTING, + ERROR_SENSOR_ACTUATOR_INIT_FAIL, + ERROR_SENSOR_INVALID_AF_POS, + ERROR_SENSOR_UNSUPPORT_FUNC, + ERROR_SENSOR_UNSUPPORT_PERI, + ERROR_SENSOR_UNSUPPORT_AF, + + /* ISP Error (200~299) */ + ERROR_ISP_AF_NONE = ERROR_COMMON_NONE, + ERROR_ISP_AF_BUSY = 201, + ERROR_ISP_AF_INVALID_COMMAND = 202, + ERROR_ISP_AF_INVALID_MODE = 203, + ERROR_ISP_FLASH_NONE = ERROR_COMMON_NONE, + ERROR_ISP_AWB_NONE = ERROR_COMMON_NONE, + ERROR_ISP_IMAGE_EFFECT_NONE = ERROR_COMMON_NONE, + ERROR_ISP_ISO_NONE = ERROR_COMMON_NONE, + ERROR_ISP_ADJUST_NONE = ERROR_COMMON_NONE, + ERROR_ISP_METERING_NONE = ERROR_COMMON_NONE, + ERROR_ISP_AFC_NONE = ERROR_COMMON_NONE, + + /* DRC Error (300~399) */ + + /* FD Error (400~499) */ + ERROR_FD_NONE = ERROR_COMMON_NONE, + /* Invalid max number (1~16) */ + ERROR_FD_CONFIG_MAX_NUMBER_STATE = 401, + ERROR_FD_CONFIG_MAX_NUMBER_INVALID = 402, + ERROR_FD_CONFIG_YAW_ANGLE_STATE = 403, + ERROR_FD_CONFIG_YAW_ANGLE_INVALID = 404, + ERROR_FD_CONFIG_ROLL_ANGLE_STATE = 405, + ERROR_FD_CONFIG_ROLL_ANGLE_INVALID = 406, + ERROR_FD_CONFIG_SMILE_MODE_INVALID = 407, + ERROR_FD_CONFIG_BLINK_MODE_INVALID = 408, + ERROR_FD_CONFIG_EYES_DETECT_INVALID = 409, + ERROR_FD_CONFIG_MOUTH_DETECT_INVALID = 410, + ERROR_FD_CONFIG_ORIENTATION_STATE = 411, + ERROR_FD_CONFIG_ORIENTATION_INVALID = 412, + ERROR_FD_CONFIG_ORIENTATION_VALUE_INVALID = 413, + /* PARAM_FdResultStr can be only applied in ready-state or stream off */ + ERROR_FD_RESULT = 414, + /* PARAM_FdModeStr can be only applied in ready-state or stream off */ + ERROR_FD_MODE = 415, + /* Scaler Error (500 ~ 599) */ + ERROR_SCALER_NO_NONE = ERROR_COMMON_NONE, + ERROR_SCALER_DMA_OUTSEL = 501, + ERROR_SCALER_H_RATIO = 502, + ERROR_SCALER_V_RATIO = 503, + + ERROR_SCALER_IMAGE_EFFECT = 510, + + ERROR_SCALER_ROTATE = 520, + ERROR_SCALER_FLIP = 521, +}; + +const char *fimc_is_strerr(unsigned int error); +const char *fimc_is_param_strerr(unsigned int error); + +#endif /* FIMC_IS_ERR_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c new file mode 100644 index 000000000..83a28ef8e --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Author: Sylwester Nawrocki + */ + +#include +#include +#include +#include +#include +#include +#include "fimc-is-i2c.h" + +struct fimc_is_i2c { + struct i2c_adapter adapter; + struct clk *clock; +}; + +/* + * An empty algorithm is used as the actual I2C bus controller driver + * is implemented in the FIMC-IS subsystem firmware and the host CPU + * doesn't access the I2C bus controller. + */ +static u32 is_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm fimc_is_i2c_algorithm = { + .functionality = is_i2c_func, +}; + +static int fimc_is_i2c_probe(struct platform_device *pdev) +{ + struct device_node *node = pdev->dev.of_node; + struct fimc_is_i2c *isp_i2c; + struct i2c_adapter *i2c_adap; + int ret; + + isp_i2c = devm_kzalloc(&pdev->dev, sizeof(*isp_i2c), GFP_KERNEL); + if (!isp_i2c) + return -ENOMEM; + + isp_i2c->clock = devm_clk_get(&pdev->dev, "i2c_isp"); + if (IS_ERR(isp_i2c->clock)) { + dev_err(&pdev->dev, "failed to get the clock\n"); + return PTR_ERR(isp_i2c->clock); + } + + i2c_adap = &isp_i2c->adapter; + i2c_adap->dev.of_node = node; + i2c_adap->dev.parent = &pdev->dev; + strscpy(i2c_adap->name, "exynos4x12-isp-i2c", sizeof(i2c_adap->name)); + i2c_adap->owner = THIS_MODULE; + i2c_adap->algo = &fimc_is_i2c_algorithm; + i2c_adap->class = I2C_CLASS_SPD; + + platform_set_drvdata(pdev, isp_i2c); + pm_runtime_enable(&pdev->dev); + + ret = i2c_add_adapter(i2c_adap); + if (ret < 0) + goto err_pm_dis; + /* + * Client drivers of this adapter don't do any I2C transfers as that + * is handled by the ISP firmware. But we rely on the runtime PM + * state propagation from the clients up to the adapter driver so + * clear the ignore_children flags here. PM rutnime calls are not + * used in probe() handler of clients of this adapter so there is + * no issues with clearing the flag right after registering the I2C + * adapter. + */ + pm_suspend_ignore_children(&i2c_adap->dev, false); + return 0; + +err_pm_dis: + pm_runtime_disable(&pdev->dev); + return ret; +} + +static int fimc_is_i2c_remove(struct platform_device *pdev) +{ + struct fimc_is_i2c *isp_i2c = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + i2c_del_adapter(&isp_i2c->adapter); + + return 0; +} + +#ifdef CONFIG_PM +static int fimc_is_i2c_runtime_suspend(struct device *dev) +{ + struct fimc_is_i2c *isp_i2c = dev_get_drvdata(dev); + + clk_disable_unprepare(isp_i2c->clock); + return 0; +} + +static int fimc_is_i2c_runtime_resume(struct device *dev) +{ + struct fimc_is_i2c *isp_i2c = dev_get_drvdata(dev); + + return clk_prepare_enable(isp_i2c->clock); +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int fimc_is_i2c_suspend(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return fimc_is_i2c_runtime_suspend(dev); +} + +static int fimc_is_i2c_resume(struct device *dev) +{ + if (pm_runtime_suspended(dev)) + return 0; + + return fimc_is_i2c_runtime_resume(dev); +} +#endif + +static const struct dev_pm_ops fimc_is_i2c_pm_ops = { + SET_RUNTIME_PM_OPS(fimc_is_i2c_runtime_suspend, + fimc_is_i2c_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(fimc_is_i2c_suspend, fimc_is_i2c_resume) +}; + +static const struct of_device_id fimc_is_i2c_of_match[] = { + { .compatible = FIMC_IS_I2C_COMPATIBLE }, + { }, +}; + +static struct platform_driver fimc_is_i2c_driver = { + .probe = fimc_is_i2c_probe, + .remove = fimc_is_i2c_remove, + .driver = { + .of_match_table = fimc_is_i2c_of_match, + .name = "fimc-isp-i2c", + .pm = &fimc_is_i2c_pm_ops, + } +}; + +int fimc_is_register_i2c_driver(void) +{ + return platform_driver_register(&fimc_is_i2c_driver); +} + +void fimc_is_unregister_i2c_driver(void) +{ + platform_driver_unregister(&fimc_is_i2c_driver); +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h new file mode 100644 index 000000000..a23bd20be --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Sylwester Nawrocki + */ + +#define FIMC_IS_I2C_COMPATIBLE "samsung,exynos4212-i2c-isp" + +int fimc_is_register_i2c_driver(void); +void fimc_is_unregister_i2c_driver(void); diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-param.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-param.c new file mode 100644 index 000000000..9c816ae3b --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-param.c @@ -0,0 +1,893 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Authors: Younghwan Joo + * Sylwester Nawrocki + */ +#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "fimc-is.h" +#include "fimc-is-command.h" +#include "fimc-is-errno.h" +#include "fimc-is-param.h" +#include "fimc-is-regs.h" +#include "fimc-is-sensor.h" + +static void __hw_param_copy(void *dst, void *src) +{ + memcpy(dst, src, FIMC_IS_PARAM_MAX_SIZE); +} + +static void __fimc_is_hw_update_param_global_shotmode(struct fimc_is *is) +{ + struct param_global_shotmode *dst, *src; + + dst = &is->is_p_region->parameter.global.shotmode; + src = &is->config[is->config_index].global.shotmode; + __hw_param_copy(dst, src); +} + +static void __fimc_is_hw_update_param_sensor_framerate(struct fimc_is *is) +{ + struct param_sensor_framerate *dst, *src; + + dst = &is->is_p_region->parameter.sensor.frame_rate; + src = &is->config[is->config_index].sensor.frame_rate; + __hw_param_copy(dst, src); +} + +int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset) +{ + struct is_param_region *par = &is->is_p_region->parameter; + struct chain_config *cfg = &is->config[is->config_index]; + + switch (offset) { + case PARAM_ISP_CONTROL: + __hw_param_copy(&par->isp.control, &cfg->isp.control); + break; + + case PARAM_ISP_OTF_INPUT: + __hw_param_copy(&par->isp.otf_input, &cfg->isp.otf_input); + break; + + case PARAM_ISP_DMA1_INPUT: + __hw_param_copy(&par->isp.dma1_input, &cfg->isp.dma1_input); + break; + + case PARAM_ISP_DMA2_INPUT: + __hw_param_copy(&par->isp.dma2_input, &cfg->isp.dma2_input); + break; + + case PARAM_ISP_AA: + __hw_param_copy(&par->isp.aa, &cfg->isp.aa); + break; + + case PARAM_ISP_FLASH: + __hw_param_copy(&par->isp.flash, &cfg->isp.flash); + break; + + case PARAM_ISP_AWB: + __hw_param_copy(&par->isp.awb, &cfg->isp.awb); + break; + + case PARAM_ISP_IMAGE_EFFECT: + __hw_param_copy(&par->isp.effect, &cfg->isp.effect); + break; + + case PARAM_ISP_ISO: + __hw_param_copy(&par->isp.iso, &cfg->isp.iso); + break; + + case PARAM_ISP_ADJUST: + __hw_param_copy(&par->isp.adjust, &cfg->isp.adjust); + break; + + case PARAM_ISP_METERING: + __hw_param_copy(&par->isp.metering, &cfg->isp.metering); + break; + + case PARAM_ISP_AFC: + __hw_param_copy(&par->isp.afc, &cfg->isp.afc); + break; + + case PARAM_ISP_OTF_OUTPUT: + __hw_param_copy(&par->isp.otf_output, &cfg->isp.otf_output); + break; + + case PARAM_ISP_DMA1_OUTPUT: + __hw_param_copy(&par->isp.dma1_output, &cfg->isp.dma1_output); + break; + + case PARAM_ISP_DMA2_OUTPUT: + __hw_param_copy(&par->isp.dma2_output, &cfg->isp.dma2_output); + break; + + case PARAM_DRC_CONTROL: + __hw_param_copy(&par->drc.control, &cfg->drc.control); + break; + + case PARAM_DRC_OTF_INPUT: + __hw_param_copy(&par->drc.otf_input, &cfg->drc.otf_input); + break; + + case PARAM_DRC_DMA_INPUT: + __hw_param_copy(&par->drc.dma_input, &cfg->drc.dma_input); + break; + + case PARAM_DRC_OTF_OUTPUT: + __hw_param_copy(&par->drc.otf_output, &cfg->drc.otf_output); + break; + + case PARAM_FD_CONTROL: + __hw_param_copy(&par->fd.control, &cfg->fd.control); + break; + + case PARAM_FD_OTF_INPUT: + __hw_param_copy(&par->fd.otf_input, &cfg->fd.otf_input); + break; + + case PARAM_FD_DMA_INPUT: + __hw_param_copy(&par->fd.dma_input, &cfg->fd.dma_input); + break; + + case PARAM_FD_CONFIG: + __hw_param_copy(&par->fd.config, &cfg->fd.config); + break; + + default: + return -EINVAL; + } + + return 0; +} + +unsigned int __get_pending_param_count(struct fimc_is *is) +{ + struct chain_config *config = &is->config[is->config_index]; + unsigned long flags; + unsigned int count; + + spin_lock_irqsave(&is->slock, flags); + count = hweight32(config->p_region_index[0]); + count += hweight32(config->p_region_index[1]); + spin_unlock_irqrestore(&is->slock, flags); + + return count; +} + +int __is_hw_update_params(struct fimc_is *is) +{ + unsigned long *p_index; + int i, id, ret = 0; + + id = is->config_index; + p_index = &is->config[id].p_region_index[0]; + + if (test_bit(PARAM_GLOBAL_SHOTMODE, p_index)) + __fimc_is_hw_update_param_global_shotmode(is); + + if (test_bit(PARAM_SENSOR_FRAME_RATE, p_index)) + __fimc_is_hw_update_param_sensor_framerate(is); + + for (i = PARAM_ISP_CONTROL; i < PARAM_DRC_CONTROL; i++) { + if (test_bit(i, p_index)) + ret = __fimc_is_hw_update_param(is, i); + } + + for (i = PARAM_DRC_CONTROL; i < PARAM_SCALERC_CONTROL; i++) { + if (test_bit(i, p_index)) + ret = __fimc_is_hw_update_param(is, i); + } + + for (i = PARAM_FD_CONTROL; i <= PARAM_FD_CONFIG; i++) { + if (test_bit(i, p_index)) + ret = __fimc_is_hw_update_param(is, i); + } + + return ret; +} + +void __is_get_frame_size(struct fimc_is *is, struct v4l2_mbus_framefmt *mf) +{ + struct isp_param *isp; + + isp = &is->config[is->config_index].isp; + mf->width = isp->otf_input.width; + mf->height = isp->otf_input.height; +} + +void __is_set_frame_size(struct fimc_is *is, struct v4l2_mbus_framefmt *mf) +{ + unsigned int index = is->config_index; + struct isp_param *isp; + struct drc_param *drc; + struct fd_param *fd; + + isp = &is->config[index].isp; + drc = &is->config[index].drc; + fd = &is->config[index].fd; + + /* Update isp size info (OTF only) */ + isp->otf_input.width = mf->width; + isp->otf_input.height = mf->height; + isp->otf_output.width = mf->width; + isp->otf_output.height = mf->height; + /* Update drc size info (OTF only) */ + drc->otf_input.width = mf->width; + drc->otf_input.height = mf->height; + drc->otf_output.width = mf->width; + drc->otf_output.height = mf->height; + /* Update fd size info (OTF only) */ + fd->otf_input.width = mf->width; + fd->otf_input.height = mf->height; + + if (test_bit(PARAM_ISP_OTF_INPUT, + &is->config[index].p_region_index[0])) + return; + + /* Update field */ + fimc_is_set_param_bit(is, PARAM_ISP_OTF_INPUT); + fimc_is_set_param_bit(is, PARAM_ISP_OTF_OUTPUT); + fimc_is_set_param_bit(is, PARAM_DRC_OTF_INPUT); + fimc_is_set_param_bit(is, PARAM_DRC_OTF_OUTPUT); + fimc_is_set_param_bit(is, PARAM_FD_OTF_INPUT); +} + +int fimc_is_hw_get_sensor_max_framerate(struct fimc_is *is) +{ + switch (is->sensor->drvdata->id) { + case FIMC_IS_SENSOR_ID_S5K6A3: + return 30; + default: + return 15; + } +} + +void __is_set_sensor(struct fimc_is *is, int fps) +{ + unsigned int index = is->config_index; + struct sensor_param *sensor; + struct isp_param *isp; + + sensor = &is->config[index].sensor; + isp = &is->config[index].isp; + + if (fps == 0) { + sensor->frame_rate.frame_rate = + fimc_is_hw_get_sensor_max_framerate(is); + isp->otf_input.frametime_min = 0; + isp->otf_input.frametime_max = 66666; + } else { + sensor->frame_rate.frame_rate = fps; + isp->otf_input.frametime_min = 0; + isp->otf_input.frametime_max = (u32)1000000 / fps; + } + + fimc_is_set_param_bit(is, PARAM_SENSOR_FRAME_RATE); + fimc_is_set_param_bit(is, PARAM_ISP_OTF_INPUT); +} + +static void __maybe_unused __is_set_init_isp_aa(struct fimc_is *is) +{ + struct isp_param *isp; + + isp = &is->config[is->config_index].isp; + + isp->aa.cmd = ISP_AA_COMMAND_START; + isp->aa.target = ISP_AA_TARGET_AF | ISP_AA_TARGET_AE | + ISP_AA_TARGET_AWB; + isp->aa.mode = 0; + isp->aa.scene = 0; + isp->aa.sleep = 0; + isp->aa.face = 0; + isp->aa.touch_x = 0; + isp->aa.touch_y = 0; + isp->aa.manual_af_setting = 0; + isp->aa.err = ISP_AF_ERROR_NONE; + + fimc_is_set_param_bit(is, PARAM_ISP_AA); +} + +void __is_set_isp_flash(struct fimc_is *is, u32 cmd, u32 redeye) +{ + unsigned int index = is->config_index; + struct isp_param *isp = &is->config[index].isp; + + isp->flash.cmd = cmd; + isp->flash.redeye = redeye; + isp->flash.err = ISP_FLASH_ERROR_NONE; + + fimc_is_set_param_bit(is, PARAM_ISP_FLASH); +} + +void __is_set_isp_awb(struct fimc_is *is, u32 cmd, u32 val) +{ + unsigned int index = is->config_index; + struct isp_param *isp; + + isp = &is->config[index].isp; + + isp->awb.cmd = cmd; + isp->awb.illumination = val; + isp->awb.err = ISP_AWB_ERROR_NONE; + + fimc_is_set_param_bit(is, PARAM_ISP_AWB); +} + +void __is_set_isp_effect(struct fimc_is *is, u32 cmd) +{ + unsigned int index = is->config_index; + struct isp_param *isp; + + isp = &is->config[index].isp; + + isp->effect.cmd = cmd; + isp->effect.err = ISP_IMAGE_EFFECT_ERROR_NONE; + + fimc_is_set_param_bit(is, PARAM_ISP_IMAGE_EFFECT); +} + +void __is_set_isp_iso(struct fimc_is *is, u32 cmd, u32 val) +{ + unsigned int index = is->config_index; + struct isp_param *isp; + + isp = &is->config[index].isp; + + isp->iso.cmd = cmd; + isp->iso.value = val; + isp->iso.err = ISP_ISO_ERROR_NONE; + + fimc_is_set_param_bit(is, PARAM_ISP_ISO); +} + +void __is_set_isp_adjust(struct fimc_is *is, u32 cmd, u32 val) +{ + unsigned int index = is->config_index; + unsigned long *p_index; + struct isp_param *isp; + + p_index = &is->config[index].p_region_index[0]; + isp = &is->config[index].isp; + + switch (cmd) { + case ISP_ADJUST_COMMAND_MANUAL_CONTRAST: + isp->adjust.contrast = val; + break; + case ISP_ADJUST_COMMAND_MANUAL_SATURATION: + isp->adjust.saturation = val; + break; + case ISP_ADJUST_COMMAND_MANUAL_SHARPNESS: + isp->adjust.sharpness = val; + break; + case ISP_ADJUST_COMMAND_MANUAL_EXPOSURE: + isp->adjust.exposure = val; + break; + case ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS: + isp->adjust.brightness = val; + break; + case ISP_ADJUST_COMMAND_MANUAL_HUE: + isp->adjust.hue = val; + break; + case ISP_ADJUST_COMMAND_AUTO: + isp->adjust.contrast = 0; + isp->adjust.saturation = 0; + isp->adjust.sharpness = 0; + isp->adjust.exposure = 0; + isp->adjust.brightness = 0; + isp->adjust.hue = 0; + break; + } + + if (!test_bit(PARAM_ISP_ADJUST, p_index)) { + isp->adjust.cmd = cmd; + isp->adjust.err = ISP_ADJUST_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_ISP_ADJUST); + } else { + isp->adjust.cmd |= cmd; + } +} + +void __is_set_isp_metering(struct fimc_is *is, u32 id, u32 val) +{ + unsigned int index = is->config_index; + struct isp_param *isp; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[0]; + isp = &is->config[index].isp; + + switch (id) { + case IS_METERING_CONFIG_CMD: + isp->metering.cmd = val; + break; + case IS_METERING_CONFIG_WIN_POS_X: + isp->metering.win_pos_x = val; + break; + case IS_METERING_CONFIG_WIN_POS_Y: + isp->metering.win_pos_y = val; + break; + case IS_METERING_CONFIG_WIN_WIDTH: + isp->metering.win_width = val; + break; + case IS_METERING_CONFIG_WIN_HEIGHT: + isp->metering.win_height = val; + break; + default: + return; + } + + if (!test_bit(PARAM_ISP_METERING, p_index)) { + isp->metering.err = ISP_METERING_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_ISP_METERING); + } +} + +void __is_set_isp_afc(struct fimc_is *is, u32 cmd, u32 val) +{ + unsigned int index = is->config_index; + struct isp_param *isp; + + isp = &is->config[index].isp; + + isp->afc.cmd = cmd; + isp->afc.manual = val; + isp->afc.err = ISP_AFC_ERROR_NONE; + + fimc_is_set_param_bit(is, PARAM_ISP_AFC); +} + +void __is_set_drc_control(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct drc_param *drc; + + drc = &is->config[index].drc; + + drc->control.bypass = val; + + fimc_is_set_param_bit(is, PARAM_DRC_CONTROL); +} + +void __is_set_fd_control(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->control.cmd = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) + fimc_is_set_param_bit(is, PARAM_FD_CONTROL); +} + +void __is_set_fd_config_maxface(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.max_number = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_MAXIMUM_NUMBER; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_MAXIMUM_NUMBER; + } +} + +void __is_set_fd_config_rollangle(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.roll_angle = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_ROLL_ANGLE; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_ROLL_ANGLE; + } +} + +void __is_set_fd_config_yawangle(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.yaw_angle = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_YAW_ANGLE; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_YAW_ANGLE; + } +} + +void __is_set_fd_config_smilemode(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.smile_mode = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_SMILE_MODE; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_SMILE_MODE; + } +} + +void __is_set_fd_config_blinkmode(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.blink_mode = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_BLINK_MODE; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_BLINK_MODE; + } +} + +void __is_set_fd_config_eyedetect(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.eye_detect = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_EYES_DETECT; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_EYES_DETECT; + } +} + +void __is_set_fd_config_mouthdetect(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.mouth_detect = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_MOUTH_DETECT; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_MOUTH_DETECT; + } +} + +void __is_set_fd_config_orientation(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.orientation = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_ORIENTATION; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_ORIENTATION; + } +} + +void __is_set_fd_config_orientation_val(struct fimc_is *is, u32 val) +{ + unsigned int index = is->config_index; + struct fd_param *fd; + unsigned long *p_index; + + p_index = &is->config[index].p_region_index[1]; + fd = &is->config[index].fd; + + fd->config.orientation_value = val; + + if (!test_bit((PARAM_FD_CONFIG - 32), p_index)) { + fd->config.cmd = FD_CONFIG_COMMAND_ORIENTATION_VALUE; + fd->config.err = ERROR_FD_NONE; + fimc_is_set_param_bit(is, PARAM_FD_CONFIG); + } else { + fd->config.cmd |= FD_CONFIG_COMMAND_ORIENTATION_VALUE; + } +} + +void fimc_is_set_initial_params(struct fimc_is *is) +{ + struct global_param *global; + struct isp_param *isp; + struct drc_param *drc; + struct fd_param *fd; + unsigned long *p_index; + unsigned int index; + + index = is->config_index; + global = &is->config[index].global; + isp = &is->config[index].isp; + drc = &is->config[index].drc; + fd = &is->config[index].fd; + p_index = &is->config[index].p_region_index[0]; + + /* Global */ + global->shotmode.cmd = 1; + fimc_is_set_param_bit(is, PARAM_GLOBAL_SHOTMODE); + + /* ISP */ + isp->control.cmd = CONTROL_COMMAND_START; + isp->control.bypass = CONTROL_BYPASS_DISABLE; + isp->control.err = CONTROL_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_ISP_CONTROL); + + isp->otf_input.cmd = OTF_INPUT_COMMAND_ENABLE; + if (!test_bit(PARAM_ISP_OTF_INPUT, p_index)) { + isp->otf_input.width = DEFAULT_PREVIEW_STILL_WIDTH; + isp->otf_input.height = DEFAULT_PREVIEW_STILL_HEIGHT; + fimc_is_set_param_bit(is, PARAM_ISP_OTF_INPUT); + } + if (is->sensor->test_pattern) + isp->otf_input.format = OTF_INPUT_FORMAT_STRGEN_COLORBAR_BAYER; + else + isp->otf_input.format = OTF_INPUT_FORMAT_BAYER; + isp->otf_input.bitwidth = 10; + isp->otf_input.order = OTF_INPUT_ORDER_BAYER_GR_BG; + isp->otf_input.crop_offset_x = 0; + isp->otf_input.crop_offset_y = 0; + isp->otf_input.err = OTF_INPUT_ERROR_NONE; + + isp->dma1_input.cmd = DMA_INPUT_COMMAND_DISABLE; + isp->dma1_input.width = 0; + isp->dma1_input.height = 0; + isp->dma1_input.format = 0; + isp->dma1_input.bitwidth = 0; + isp->dma1_input.plane = 0; + isp->dma1_input.order = 0; + isp->dma1_input.buffer_number = 0; + isp->dma1_input.width = 0; + isp->dma1_input.err = DMA_INPUT_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_ISP_DMA1_INPUT); + + isp->dma2_input.cmd = DMA_INPUT_COMMAND_DISABLE; + isp->dma2_input.width = 0; + isp->dma2_input.height = 0; + isp->dma2_input.format = 0; + isp->dma2_input.bitwidth = 0; + isp->dma2_input.plane = 0; + isp->dma2_input.order = 0; + isp->dma2_input.buffer_number = 0; + isp->dma2_input.width = 0; + isp->dma2_input.err = DMA_INPUT_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_ISP_DMA2_INPUT); + + isp->aa.cmd = ISP_AA_COMMAND_START; + isp->aa.target = ISP_AA_TARGET_AE | ISP_AA_TARGET_AWB; + fimc_is_set_param_bit(is, PARAM_ISP_AA); + + if (!test_bit(PARAM_ISP_FLASH, p_index)) + __is_set_isp_flash(is, ISP_FLASH_COMMAND_DISABLE, + ISP_FLASH_REDEYE_DISABLE); + + if (!test_bit(PARAM_ISP_AWB, p_index)) + __is_set_isp_awb(is, ISP_AWB_COMMAND_AUTO, 0); + + if (!test_bit(PARAM_ISP_IMAGE_EFFECT, p_index)) + __is_set_isp_effect(is, ISP_IMAGE_EFFECT_DISABLE); + + if (!test_bit(PARAM_ISP_ISO, p_index)) + __is_set_isp_iso(is, ISP_ISO_COMMAND_AUTO, 0); + + if (!test_bit(PARAM_ISP_ADJUST, p_index)) { + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_CONTRAST, 0); + __is_set_isp_adjust(is, + ISP_ADJUST_COMMAND_MANUAL_SATURATION, 0); + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_SHARPNESS, 0); + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_EXPOSURE, 0); + __is_set_isp_adjust(is, + ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS, 0); + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_HUE, 0); + } + + if (!test_bit(PARAM_ISP_METERING, p_index)) { + __is_set_isp_metering(is, 0, ISP_METERING_COMMAND_CENTER); + __is_set_isp_metering(is, 1, 0); + __is_set_isp_metering(is, 2, 0); + __is_set_isp_metering(is, 3, 0); + __is_set_isp_metering(is, 4, 0); + } + + if (!test_bit(PARAM_ISP_AFC, p_index)) + __is_set_isp_afc(is, ISP_AFC_COMMAND_AUTO, 0); + + isp->otf_output.cmd = OTF_OUTPUT_COMMAND_ENABLE; + if (!test_bit(PARAM_ISP_OTF_OUTPUT, p_index)) { + isp->otf_output.width = DEFAULT_PREVIEW_STILL_WIDTH; + isp->otf_output.height = DEFAULT_PREVIEW_STILL_HEIGHT; + fimc_is_set_param_bit(is, PARAM_ISP_OTF_OUTPUT); + } + isp->otf_output.format = OTF_OUTPUT_FORMAT_YUV444; + isp->otf_output.bitwidth = 12; + isp->otf_output.order = 0; + isp->otf_output.err = OTF_OUTPUT_ERROR_NONE; + + if (!test_bit(PARAM_ISP_DMA1_OUTPUT, p_index)) { + isp->dma1_output.cmd = DMA_OUTPUT_COMMAND_DISABLE; + isp->dma1_output.width = 0; + isp->dma1_output.height = 0; + isp->dma1_output.format = 0; + isp->dma1_output.bitwidth = 0; + isp->dma1_output.plane = 0; + isp->dma1_output.order = 0; + isp->dma1_output.buffer_number = 0; + isp->dma1_output.buffer_address = 0; + isp->dma1_output.notify_dma_done = 0; + isp->dma1_output.dma_out_mask = 0; + isp->dma1_output.err = DMA_OUTPUT_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_ISP_DMA1_OUTPUT); + } + + if (!test_bit(PARAM_ISP_DMA2_OUTPUT, p_index)) { + isp->dma2_output.cmd = DMA_OUTPUT_COMMAND_DISABLE; + isp->dma2_output.width = 0; + isp->dma2_output.height = 0; + isp->dma2_output.format = 0; + isp->dma2_output.bitwidth = 0; + isp->dma2_output.plane = 0; + isp->dma2_output.order = 0; + isp->dma2_output.buffer_number = 0; + isp->dma2_output.buffer_address = 0; + isp->dma2_output.notify_dma_done = 0; + isp->dma2_output.dma_out_mask = 0; + isp->dma2_output.err = DMA_OUTPUT_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_ISP_DMA2_OUTPUT); + } + + /* Sensor */ + if (!test_bit(PARAM_SENSOR_FRAME_RATE, p_index)) { + if (is->config_index == 0) + __is_set_sensor(is, 0); + } + + /* DRC */ + drc->control.cmd = CONTROL_COMMAND_START; + __is_set_drc_control(is, CONTROL_BYPASS_ENABLE); + + drc->otf_input.cmd = OTF_INPUT_COMMAND_ENABLE; + if (!test_bit(PARAM_DRC_OTF_INPUT, p_index)) { + drc->otf_input.width = DEFAULT_PREVIEW_STILL_WIDTH; + drc->otf_input.height = DEFAULT_PREVIEW_STILL_HEIGHT; + fimc_is_set_param_bit(is, PARAM_DRC_OTF_INPUT); + } + drc->otf_input.format = OTF_INPUT_FORMAT_YUV444; + drc->otf_input.bitwidth = 12; + drc->otf_input.order = 0; + drc->otf_input.err = OTF_INPUT_ERROR_NONE; + + drc->dma_input.cmd = DMA_INPUT_COMMAND_DISABLE; + drc->dma_input.width = 0; + drc->dma_input.height = 0; + drc->dma_input.format = 0; + drc->dma_input.bitwidth = 0; + drc->dma_input.plane = 0; + drc->dma_input.order = 0; + drc->dma_input.buffer_number = 0; + drc->dma_input.width = 0; + drc->dma_input.err = DMA_INPUT_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_DRC_DMA_INPUT); + + drc->otf_output.cmd = OTF_OUTPUT_COMMAND_ENABLE; + if (!test_bit(PARAM_DRC_OTF_OUTPUT, p_index)) { + drc->otf_output.width = DEFAULT_PREVIEW_STILL_WIDTH; + drc->otf_output.height = DEFAULT_PREVIEW_STILL_HEIGHT; + fimc_is_set_param_bit(is, PARAM_DRC_OTF_OUTPUT); + } + drc->otf_output.format = OTF_OUTPUT_FORMAT_YUV444; + drc->otf_output.bitwidth = 8; + drc->otf_output.order = 0; + drc->otf_output.err = OTF_OUTPUT_ERROR_NONE; + + /* FD */ + __is_set_fd_control(is, CONTROL_COMMAND_STOP); + fd->control.bypass = CONTROL_BYPASS_DISABLE; + + fd->otf_input.cmd = OTF_INPUT_COMMAND_ENABLE; + if (!test_bit(PARAM_FD_OTF_INPUT, p_index)) { + fd->otf_input.width = DEFAULT_PREVIEW_STILL_WIDTH; + fd->otf_input.height = DEFAULT_PREVIEW_STILL_HEIGHT; + fimc_is_set_param_bit(is, PARAM_FD_OTF_INPUT); + } + + fd->otf_input.format = OTF_INPUT_FORMAT_YUV444; + fd->otf_input.bitwidth = 8; + fd->otf_input.order = 0; + fd->otf_input.err = OTF_INPUT_ERROR_NONE; + + fd->dma_input.cmd = DMA_INPUT_COMMAND_DISABLE; + fd->dma_input.width = 0; + fd->dma_input.height = 0; + fd->dma_input.format = 0; + fd->dma_input.bitwidth = 0; + fd->dma_input.plane = 0; + fd->dma_input.order = 0; + fd->dma_input.buffer_number = 0; + fd->dma_input.width = 0; + fd->dma_input.err = DMA_INPUT_ERROR_NONE; + fimc_is_set_param_bit(is, PARAM_FD_DMA_INPUT); + + __is_set_fd_config_maxface(is, 5); + __is_set_fd_config_rollangle(is, FD_CONFIG_ROLL_ANGLE_FULL); + __is_set_fd_config_yawangle(is, FD_CONFIG_YAW_ANGLE_45_90); + __is_set_fd_config_smilemode(is, FD_CONFIG_SMILE_MODE_DISABLE); + __is_set_fd_config_blinkmode(is, FD_CONFIG_BLINK_MODE_DISABLE); + __is_set_fd_config_eyedetect(is, FD_CONFIG_EYES_DETECT_ENABLE); + __is_set_fd_config_mouthdetect(is, FD_CONFIG_MOUTH_DETECT_DISABLE); + __is_set_fd_config_orientation(is, FD_CONFIG_ORIENTATION_DISABLE); + __is_set_fd_config_orientation_val(is, 0); +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-param.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-param.h new file mode 100644 index 000000000..206904674 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-param.h @@ -0,0 +1,1022 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * + * Authors: Younghwan Joo + * Sylwester Nawrocki + */ +#ifndef FIMC_IS_PARAM_H_ +#define FIMC_IS_PARAM_H_ + +#include + +#define FIMC_IS_CONFIG_TIMEOUT 3000 /* ms */ +#define IS_DEFAULT_WIDTH 1280 +#define IS_DEFAULT_HEIGHT 720 + +#define DEFAULT_PREVIEW_STILL_WIDTH IS_DEFAULT_WIDTH +#define DEFAULT_PREVIEW_STILL_HEIGHT IS_DEFAULT_HEIGHT +#define DEFAULT_CAPTURE_STILL_WIDTH IS_DEFAULT_WIDTH +#define DEFAULT_CAPTURE_STILL_HEIGHT IS_DEFAULT_HEIGHT +#define DEFAULT_PREVIEW_VIDEO_WIDTH IS_DEFAULT_WIDTH +#define DEFAULT_PREVIEW_VIDEO_HEIGHT IS_DEFAULT_HEIGHT +#define DEFAULT_CAPTURE_VIDEO_WIDTH IS_DEFAULT_WIDTH +#define DEFAULT_CAPTURE_VIDEO_HEIGHT IS_DEFAULT_HEIGHT + +#define DEFAULT_PREVIEW_STILL_FRAMERATE 30 +#define DEFAULT_CAPTURE_STILL_FRAMERATE 15 +#define DEFAULT_PREVIEW_VIDEO_FRAMERATE 30 +#define DEFAULT_CAPTURE_VIDEO_FRAMERATE 30 + +#define FIMC_IS_REGION_VER 124 /* IS REGION VERSION 1.24 */ +#define FIMC_IS_PARAM_SIZE (FIMC_IS_REGION_SIZE + 1) +#define FIMC_IS_MAGIC_NUMBER 0x01020304 +#define FIMC_IS_PARAM_MAX_SIZE 64 /* in bytes */ +#define FIMC_IS_PARAM_MAX_ENTRIES (FIMC_IS_PARAM_MAX_SIZE / 4) + +/* The parameter bitmask bit definitions. */ +enum is_param_bit { + PARAM_GLOBAL_SHOTMODE, + PARAM_SENSOR_CONTROL, + PARAM_SENSOR_OTF_OUTPUT, + PARAM_SENSOR_FRAME_RATE, + PARAM_BUFFER_CONTROL, + PARAM_BUFFER_OTF_INPUT, + PARAM_BUFFER_OTF_OUTPUT, + PARAM_ISP_CONTROL, + PARAM_ISP_OTF_INPUT, + PARAM_ISP_DMA1_INPUT, + /* 10 */ + PARAM_ISP_DMA2_INPUT, + PARAM_ISP_AA, + PARAM_ISP_FLASH, + PARAM_ISP_AWB, + PARAM_ISP_IMAGE_EFFECT, + PARAM_ISP_ISO, + PARAM_ISP_ADJUST, + PARAM_ISP_METERING, + PARAM_ISP_AFC, + PARAM_ISP_OTF_OUTPUT, + /* 20 */ + PARAM_ISP_DMA1_OUTPUT, + PARAM_ISP_DMA2_OUTPUT, + PARAM_DRC_CONTROL, + PARAM_DRC_OTF_INPUT, + PARAM_DRC_DMA_INPUT, + PARAM_DRC_OTF_OUTPUT, + PARAM_SCALERC_CONTROL, + PARAM_SCALERC_OTF_INPUT, + PARAM_SCALERC_IMAGE_EFFECT, + PARAM_SCALERC_INPUT_CROP, + /* 30 */ + PARAM_SCALERC_OUTPUT_CROP, + PARAM_SCALERC_OTF_OUTPUT, + PARAM_SCALERC_DMA_OUTPUT, + PARAM_ODC_CONTROL, + PARAM_ODC_OTF_INPUT, + PARAM_ODC_OTF_OUTPUT, + PARAM_DIS_CONTROL, + PARAM_DIS_OTF_INPUT, + PARAM_DIS_OTF_OUTPUT, + PARAM_TDNR_CONTROL, + /* 40 */ + PARAM_TDNR_OTF_INPUT, + PARAM_TDNR_1ST_FRAME, + PARAM_TDNR_OTF_OUTPUT, + PARAM_TDNR_DMA_OUTPUT, + PARAM_SCALERP_CONTROL, + PARAM_SCALERP_OTF_INPUT, + PARAM_SCALERP_IMAGE_EFFECT, + PARAM_SCALERP_INPUT_CROP, + PARAM_SCALERP_OUTPUT_CROP, + PARAM_SCALERP_ROTATION, + /* 50 */ + PARAM_SCALERP_FLIP, + PARAM_SCALERP_OTF_OUTPUT, + PARAM_SCALERP_DMA_OUTPUT, + PARAM_FD_CONTROL, + PARAM_FD_OTF_INPUT, + PARAM_FD_DMA_INPUT, + PARAM_FD_CONFIG, +}; + +/* Interrupt map */ +#define FIMC_IS_INT_GENERAL 0 +#define FIMC_IS_INT_FRAME_DONE_ISP 1 + +/* Input */ + +#define CONTROL_COMMAND_STOP 0 +#define CONTROL_COMMAND_START 1 + +#define CONTROL_BYPASS_DISABLE 0 +#define CONTROL_BYPASS_ENABLE 1 + +#define CONTROL_ERROR_NONE 0 + +/* OTF (On-The-Fly) input interface commands */ +#define OTF_INPUT_COMMAND_DISABLE 0 +#define OTF_INPUT_COMMAND_ENABLE 1 + +/* OTF input interface color formats */ +enum oft_input_fmt { + OTF_INPUT_FORMAT_BAYER = 0, /* 1 channel */ + OTF_INPUT_FORMAT_YUV444 = 1, /* 3 channels */ + OTF_INPUT_FORMAT_YUV422 = 2, /* 3 channels */ + OTF_INPUT_FORMAT_YUV420 = 3, /* 3 channels */ + OTF_INPUT_FORMAT_STRGEN_COLORBAR_BAYER = 10, + OTF_INPUT_FORMAT_BAYER_DMA = 11, +}; + +#define OTF_INPUT_ORDER_BAYER_GR_BG 0 + +/* OTF input error codes */ +#define OTF_INPUT_ERROR_NONE 0 /* Input setting is done */ + +/* DMA input commands */ +#define DMA_INPUT_COMMAND_DISABLE 0 +#define DMA_INPUT_COMMAND_ENABLE 1 + +/* DMA input color formats */ +enum dma_input_fmt { + DMA_INPUT_FORMAT_BAYER = 0, + DMA_INPUT_FORMAT_YUV444 = 1, + DMA_INPUT_FORMAT_YUV422 = 2, + DMA_INPUT_FORMAT_YUV420 = 3, +}; + +enum dma_input_order { + /* (for DMA_INPUT_PLANE_3) */ + DMA_INPUT_ORDER_NO = 0, + /* (only valid at DMA_INPUT_PLANE_2) */ + DMA_INPUT_ORDER_CBCR = 1, + /* (only valid at DMA_INPUT_PLANE_2) */ + DMA_INPUT_ORDER_CRCB = 2, + /* (only valid at DMA_INPUT_PLANE_1 & DMA_INPUT_FORMAT_YUV444) */ + DMA_INPUT_ORDER_YCBCR = 3, + /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */ + DMA_INPUT_ORDER_YYCBCR = 4, + /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */ + DMA_INPUT_ORDER_YCBYCR = 5, + /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */ + DMA_INPUT_ORDER_YCRYCB = 6, + /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */ + DMA_INPUT_ORDER_CBYCRY = 7, + /* (only valid at DMA_INPUT_FORMAT_YUV422 & DMA_INPUT_PLANE_1) */ + DMA_INPUT_ORDER_CRYCBY = 8, + /* (only valid at DMA_INPUT_FORMAT_BAYER) */ + DMA_INPUT_ORDER_GR_BG = 9 +}; + +#define DMA_INPUT_ERROR_NONE 0 /* DMA input setting + is done */ +/* + * Data output parameter definitions + */ +#define OTF_OUTPUT_CROP_DISABLE 0 +#define OTF_OUTPUT_CROP_ENABLE 1 + +#define OTF_OUTPUT_COMMAND_DISABLE 0 +#define OTF_OUTPUT_COMMAND_ENABLE 1 + +enum otf_output_fmt { + OTF_OUTPUT_FORMAT_YUV444 = 1, + OTF_OUTPUT_FORMAT_YUV422 = 2, + OTF_OUTPUT_FORMAT_YUV420 = 3, + OTF_OUTPUT_FORMAT_RGB = 4, +}; + +#define OTF_OUTPUT_ORDER_BAYER_GR_BG 0 + +#define OTF_OUTPUT_ERROR_NONE 0 /* Output Setting is done */ + +#define DMA_OUTPUT_COMMAND_DISABLE 0 +#define DMA_OUTPUT_COMMAND_ENABLE 1 + +enum dma_output_fmt { + DMA_OUTPUT_FORMAT_BAYER = 0, + DMA_OUTPUT_FORMAT_YUV444 = 1, + DMA_OUTPUT_FORMAT_YUV422 = 2, + DMA_OUTPUT_FORMAT_YUV420 = 3, + DMA_OUTPUT_FORMAT_RGB = 4, +}; + +enum dma_output_order { + DMA_OUTPUT_ORDER_NO = 0, + /* for DMA_OUTPUT_PLANE_3 */ + DMA_OUTPUT_ORDER_CBCR = 1, + /* only valid at DMA_INPUT_PLANE_2) */ + DMA_OUTPUT_ORDER_CRCB = 2, + /* only valid at DMA_OUTPUT_PLANE_2) */ + DMA_OUTPUT_ORDER_YYCBCR = 3, + /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_YCBYCR = 4, + /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_YCRYCB = 5, + /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_CBYCRY = 6, + /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_CRYCBY = 7, + /* only valid at DMA_OUTPUT_FORMAT_YUV422 & DMA_OUTPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_YCBCR = 8, + /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_CRYCB = 9, + /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_CRCBY = 10, + /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_CBYCR = 11, + /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_YCRCB = 12, + /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_CBCRY = 13, + /* only valid at DMA_OUTPUT_FORMAT_YUV444 & DMA_OUPUT_PLANE_1 */ + DMA_OUTPUT_ORDER_BGR = 14, + /* only valid at DMA_OUTPUT_FORMAT_RGB */ + DMA_OUTPUT_ORDER_GB_BG = 15 + /* only valid at DMA_OUTPUT_FORMAT_BAYER */ +}; + +/* enum dma_output_notify_dma_done */ +#define DMA_OUTPUT_NOTIFY_DMA_DONE_DISABLE 0 +#define DMA_OUTPUT_NOTIFY_DMA_DONE_ENABLE 1 + +/* DMA output error codes */ +#define DMA_OUTPUT_ERROR_NONE 0 /* DMA output setting + is done */ + +/* ---------------------- Global ----------------------------------- */ +#define GLOBAL_SHOTMODE_ERROR_NONE 0 /* shot-mode setting + is done */ +/* 3A lock commands */ +#define ISP_AA_COMMAND_START 0 +#define ISP_AA_COMMAND_STOP 1 + +/* 3A lock target */ +#define ISP_AA_TARGET_AF 1 +#define ISP_AA_TARGET_AE 2 +#define ISP_AA_TARGET_AWB 4 + +enum isp_af_mode { + ISP_AF_MODE_MANUAL = 0, + ISP_AF_MODE_SINGLE = 1, + ISP_AF_MODE_CONTINUOUS = 2, + ISP_AF_MODE_TOUCH = 3, + ISP_AF_MODE_SLEEP = 4, + ISP_AF_MODE_INIT = 5, + ISP_AF_MODE_SET_CENTER_WINDOW = 6, + ISP_AF_MODE_SET_TOUCH_WINDOW = 7 +}; + +/* Face AF commands */ +#define ISP_AF_FACE_DISABLE 0 +#define ISP_AF_FACE_ENABLE 1 + +/* AF range */ +#define ISP_AF_RANGE_NORMAL 0 +#define ISP_AF_RANGE_MACRO 1 + +/* AF sleep */ +#define ISP_AF_SLEEP_OFF 0 +#define ISP_AF_SLEEP_ON 1 + +/* Continuous AF commands */ +#define ISP_AF_CONTINUOUS_DISABLE 0 +#define ISP_AF_CONTINUOUS_ENABLE 1 + +/* ISP AF error codes */ +#define ISP_AF_ERROR_NONE 0 /* AF mode change is done */ +#define ISP_AF_ERROR_NONE_LOCK_DONE 1 /* AF lock is done */ + +/* Flash commands */ +#define ISP_FLASH_COMMAND_DISABLE 0 +#define ISP_FLASH_COMMAND_MANUAL_ON 1 /* (forced flash) */ +#define ISP_FLASH_COMMAND_AUTO 2 +#define ISP_FLASH_COMMAND_TORCH 3 /* 3 sec */ + +/* Flash red-eye commands */ +#define ISP_FLASH_REDEYE_DISABLE 0 +#define ISP_FLASH_REDEYE_ENABLE 1 + +/* Flash error codes */ +#define ISP_FLASH_ERROR_NONE 0 /* Flash setting is done */ + +/* -------------------------- AWB ------------------------------------ */ +enum isp_awb_command { + ISP_AWB_COMMAND_AUTO = 0, + ISP_AWB_COMMAND_ILLUMINATION = 1, + ISP_AWB_COMMAND_MANUAL = 2 +}; + +enum isp_awb_illumination { + ISP_AWB_ILLUMINATION_DAYLIGHT = 0, + ISP_AWB_ILLUMINATION_CLOUDY = 1, + ISP_AWB_ILLUMINATION_TUNGSTEN = 2, + ISP_AWB_ILLUMINATION_FLUORESCENT = 3 +}; + +/* ISP AWN error codes */ +#define ISP_AWB_ERROR_NONE 0 /* AWB setting is done */ + +/* -------------------------- Effect ----------------------------------- */ +enum isp_imageeffect_command { + ISP_IMAGE_EFFECT_DISABLE = 0, + ISP_IMAGE_EFFECT_MONOCHROME = 1, + ISP_IMAGE_EFFECT_NEGATIVE_MONO = 2, + ISP_IMAGE_EFFECT_NEGATIVE_COLOR = 3, + ISP_IMAGE_EFFECT_SEPIA = 4 +}; + +/* Image effect error codes */ +#define ISP_IMAGE_EFFECT_ERROR_NONE 0 /* Image effect setting + is done */ +/* ISO commands */ +#define ISP_ISO_COMMAND_AUTO 0 +#define ISP_ISO_COMMAND_MANUAL 1 + +/* ISO error codes */ +#define ISP_ISO_ERROR_NONE 0 /* ISO setting is done */ + +/* ISP adjust commands */ +#define ISP_ADJUST_COMMAND_AUTO (0 << 0) +#define ISP_ADJUST_COMMAND_MANUAL_CONTRAST (1 << 0) +#define ISP_ADJUST_COMMAND_MANUAL_SATURATION (1 << 1) +#define ISP_ADJUST_COMMAND_MANUAL_SHARPNESS (1 << 2) +#define ISP_ADJUST_COMMAND_MANUAL_EXPOSURE (1 << 3) +#define ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS (1 << 4) +#define ISP_ADJUST_COMMAND_MANUAL_HUE (1 << 5) +#define ISP_ADJUST_COMMAND_MANUAL_ALL 0x7f + +/* ISP adjustment error codes */ +#define ISP_ADJUST_ERROR_NONE 0 /* Adjust setting is done */ + +/* + * Exposure metering + */ +enum isp_metering_command { + ISP_METERING_COMMAND_AVERAGE = 0, + ISP_METERING_COMMAND_SPOT = 1, + ISP_METERING_COMMAND_MATRIX = 2, + ISP_METERING_COMMAND_CENTER = 3 +}; + +/* ISP metering error codes */ +#define ISP_METERING_ERROR_NONE 0 /* Metering setting is done */ + +/* + * AFC + */ +enum isp_afc_command { + ISP_AFC_COMMAND_DISABLE = 0, + ISP_AFC_COMMAND_AUTO = 1, + ISP_AFC_COMMAND_MANUAL = 2, +}; + +#define ISP_AFC_MANUAL_50HZ 50 +#define ISP_AFC_MANUAL_60HZ 60 + +/* ------------------------ SCENE MODE--------------------------------- */ +enum isp_scene_mode { + ISP_SCENE_NONE = 0, + ISP_SCENE_PORTRAIT = 1, + ISP_SCENE_LANDSCAPE = 2, + ISP_SCENE_SPORTS = 3, + ISP_SCENE_PARTYINDOOR = 4, + ISP_SCENE_BEACHSNOW = 5, + ISP_SCENE_SUNSET = 6, + ISP_SCENE_DAWN = 7, + ISP_SCENE_FALL = 8, + ISP_SCENE_NIGHT = 9, + ISP_SCENE_AGAINSTLIGHTWLIGHT = 10, + ISP_SCENE_AGAINSTLIGHTWOLIGHT = 11, + ISP_SCENE_FIRE = 12, + ISP_SCENE_TEXT = 13, + ISP_SCENE_CANDLE = 14 +}; + +/* AFC error codes */ +#define ISP_AFC_ERROR_NONE 0 /* AFC setting is done */ + +/* ---------------------------- FD ------------------------------------- */ +enum fd_config_command { + FD_CONFIG_COMMAND_MAXIMUM_NUMBER = 0x1, + FD_CONFIG_COMMAND_ROLL_ANGLE = 0x2, + FD_CONFIG_COMMAND_YAW_ANGLE = 0x4, + FD_CONFIG_COMMAND_SMILE_MODE = 0x8, + FD_CONFIG_COMMAND_BLINK_MODE = 0x10, + FD_CONFIG_COMMAND_EYES_DETECT = 0x20, + FD_CONFIG_COMMAND_MOUTH_DETECT = 0x40, + FD_CONFIG_COMMAND_ORIENTATION = 0x80, + FD_CONFIG_COMMAND_ORIENTATION_VALUE = 0x100 +}; + +enum fd_config_roll_angle { + FD_CONFIG_ROLL_ANGLE_BASIC = 0, + FD_CONFIG_ROLL_ANGLE_PRECISE_BASIC = 1, + FD_CONFIG_ROLL_ANGLE_SIDES = 2, + FD_CONFIG_ROLL_ANGLE_PRECISE_SIDES = 3, + FD_CONFIG_ROLL_ANGLE_FULL = 4, + FD_CONFIG_ROLL_ANGLE_PRECISE_FULL = 5, +}; + +enum fd_config_yaw_angle { + FD_CONFIG_YAW_ANGLE_0 = 0, + FD_CONFIG_YAW_ANGLE_45 = 1, + FD_CONFIG_YAW_ANGLE_90 = 2, + FD_CONFIG_YAW_ANGLE_45_90 = 3, +}; + +/* Smile mode configuration */ +#define FD_CONFIG_SMILE_MODE_DISABLE 0 +#define FD_CONFIG_SMILE_MODE_ENABLE 1 + +/* Blink mode configuration */ +#define FD_CONFIG_BLINK_MODE_DISABLE 0 +#define FD_CONFIG_BLINK_MODE_ENABLE 1 + +/* Eyes detection configuration */ +#define FD_CONFIG_EYES_DETECT_DISABLE 0 +#define FD_CONFIG_EYES_DETECT_ENABLE 1 + +/* Mouth detection configuration */ +#define FD_CONFIG_MOUTH_DETECT_DISABLE 0 +#define FD_CONFIG_MOUTH_DETECT_ENABLE 1 + +#define FD_CONFIG_ORIENTATION_DISABLE 0 +#define FD_CONFIG_ORIENTATION_ENABLE 1 + +struct param_control { + u32 cmd; + u32 bypass; + u32 buffer_address; + u32 buffer_size; + u32 skip_frames; /* only valid at ISP */ + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 6]; + u32 err; +}; + +struct param_otf_input { + u32 cmd; + u32 width; + u32 height; + u32 format; + u32 bitwidth; + u32 order; + u32 crop_offset_x; + u32 crop_offset_y; + u32 crop_width; + u32 crop_height; + u32 frametime_min; + u32 frametime_max; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 13]; + u32 err; +}; + +struct param_dma_input { + u32 cmd; + u32 width; + u32 height; + u32 format; + u32 bitwidth; + u32 plane; + u32 order; + u32 buffer_number; + u32 buffer_address; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 10]; + u32 err; +}; + +struct param_otf_output { + u32 cmd; + u32 width; + u32 height; + u32 format; + u32 bitwidth; + u32 order; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 7]; + u32 err; +}; + +struct param_dma_output { + u32 cmd; + u32 width; + u32 height; + u32 format; + u32 bitwidth; + u32 plane; + u32 order; + u32 buffer_number; + u32 buffer_address; + u32 notify_dma_done; + u32 dma_out_mask; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 12]; + u32 err; +}; + +struct param_global_shotmode { + u32 cmd; + u32 skip_frames; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3]; + u32 err; +}; + +struct param_sensor_framerate { + u32 frame_rate; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2]; + u32 err; +}; + +struct param_isp_aa { + u32 cmd; + u32 target; + u32 mode; + u32 scene; + u32 sleep; + u32 face; + u32 touch_x; + u32 touch_y; + u32 manual_af_setting; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 10]; + u32 err; +}; + +struct param_isp_flash { + u32 cmd; + u32 redeye; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3]; + u32 err; +}; + +struct param_isp_awb { + u32 cmd; + u32 illumination; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3]; + u32 err; +}; + +struct param_isp_imageeffect { + u32 cmd; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2]; + u32 err; +}; + +struct param_isp_iso { + u32 cmd; + u32 value; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3]; + u32 err; +}; + +struct param_isp_adjust { + u32 cmd; + s32 contrast; + s32 saturation; + s32 sharpness; + s32 exposure; + s32 brightness; + s32 hue; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 8]; + u32 err; +}; + +struct param_isp_metering { + u32 cmd; + u32 win_pos_x; + u32 win_pos_y; + u32 win_width; + u32 win_height; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 6]; + u32 err; +}; + +struct param_isp_afc { + u32 cmd; + u32 manual; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 3]; + u32 err; +}; + +struct param_scaler_imageeffect { + u32 cmd; + u32 arbitrary_cb; + u32 arbitrary_cr; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 4]; + u32 err; +}; + +struct param_scaler_input_crop { + u32 cmd; + u32 crop_offset_x; + u32 crop_offset_y; + u32 crop_width; + u32 crop_height; + u32 in_width; + u32 in_height; + u32 out_width; + u32 out_height; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 10]; + u32 err; +}; + +struct param_scaler_output_crop { + u32 cmd; + u32 crop_offset_x; + u32 crop_offset_y; + u32 crop_width; + u32 crop_height; + u32 out_format; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 7]; + u32 err; +}; + +struct param_scaler_rotation { + u32 cmd; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2]; + u32 err; +}; + +struct param_scaler_flip { + u32 cmd; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2]; + u32 err; +}; + +struct param_3dnr_1stframe { + u32 cmd; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 2]; + u32 err; +}; + +struct param_fd_config { + u32 cmd; + u32 max_number; + u32 roll_angle; + u32 yaw_angle; + u32 smile_mode; + u32 blink_mode; + u32 eye_detect; + u32 mouth_detect; + u32 orientation; + u32 orientation_value; + u32 reserved[FIMC_IS_PARAM_MAX_ENTRIES - 11]; + u32 err; +}; + +struct global_param { + struct param_global_shotmode shotmode; +}; + +struct sensor_param { + struct param_control control; + struct param_otf_output otf_output; + struct param_sensor_framerate frame_rate; +} __packed; + +struct buffer_param { + struct param_control control; + struct param_otf_input otf_input; + struct param_otf_output otf_output; +} __packed; + +struct isp_param { + struct param_control control; + struct param_otf_input otf_input; + struct param_dma_input dma1_input; + struct param_dma_input dma2_input; + struct param_isp_aa aa; + struct param_isp_flash flash; + struct param_isp_awb awb; + struct param_isp_imageeffect effect; + struct param_isp_iso iso; + struct param_isp_adjust adjust; + struct param_isp_metering metering; + struct param_isp_afc afc; + struct param_otf_output otf_output; + struct param_dma_output dma1_output; + struct param_dma_output dma2_output; +} __packed; + +struct drc_param { + struct param_control control; + struct param_otf_input otf_input; + struct param_dma_input dma_input; + struct param_otf_output otf_output; +} __packed; + +struct scalerc_param { + struct param_control control; + struct param_otf_input otf_input; + struct param_scaler_imageeffect effect; + struct param_scaler_input_crop input_crop; + struct param_scaler_output_crop output_crop; + struct param_otf_output otf_output; + struct param_dma_output dma_output; +} __packed; + +struct odc_param { + struct param_control control; + struct param_otf_input otf_input; + struct param_otf_output otf_output; +} __packed; + +struct dis_param { + struct param_control control; + struct param_otf_output otf_input; + struct param_otf_output otf_output; +} __packed; + +struct tdnr_param { + struct param_control control; + struct param_otf_input otf_input; + struct param_3dnr_1stframe frame; + struct param_otf_output otf_output; + struct param_dma_output dma_output; +} __packed; + +struct scalerp_param { + struct param_control control; + struct param_otf_input otf_input; + struct param_scaler_imageeffect effect; + struct param_scaler_input_crop input_crop; + struct param_scaler_output_crop output_crop; + struct param_scaler_rotation rotation; + struct param_scaler_flip flip; + struct param_otf_output otf_output; + struct param_dma_output dma_output; +} __packed; + +struct fd_param { + struct param_control control; + struct param_otf_input otf_input; + struct param_dma_input dma_input; + struct param_fd_config config; +} __packed; + +struct is_param_region { + struct global_param global; + struct sensor_param sensor; + struct buffer_param buf; + struct isp_param isp; + struct drc_param drc; + struct scalerc_param scalerc; + struct odc_param odc; + struct dis_param dis; + struct tdnr_param tdnr; + struct scalerp_param scalerp; + struct fd_param fd; +} __packed; + +#define NUMBER_OF_GAMMA_CURVE_POINTS 32 + +struct is_tune_sensor { + u32 exposure; + u32 analog_gain; + u32 frame_rate; + u32 actuator_position; +}; + +struct is_tune_gammacurve { + u32 num_pts_x[NUMBER_OF_GAMMA_CURVE_POINTS]; + u32 num_pts_y_r[NUMBER_OF_GAMMA_CURVE_POINTS]; + u32 num_pts_y_g[NUMBER_OF_GAMMA_CURVE_POINTS]; + u32 num_pts_y_b[NUMBER_OF_GAMMA_CURVE_POINTS]; +}; + +struct is_tune_isp { + /* Brightness level: range 0...100, default 7. */ + u32 brightness_level; + /* Contrast level: range -127...127, default 0. */ + s32 contrast_level; + /* Saturation level: range -127...127, default 0. */ + s32 saturation_level; + s32 gamma_level; + struct is_tune_gammacurve gamma_curve[4]; + /* Hue: range -127...127, default 0. */ + s32 hue; + /* Sharpness blur: range -127...127, default 0. */ + s32 sharpness_blur; + /* Despeckle : range -127~127, default : 0 */ + s32 despeckle; + /* Edge color supression: range -127...127, default 0. */ + s32 edge_color_supression; + /* Noise reduction: range -127...127, default 0. */ + s32 noise_reduction; + /* (32 * 4 + 9) * 4 = 548 bytes */ +} __packed; + +struct is_tune_region { + struct is_tune_sensor sensor; + struct is_tune_isp isp; +} __packed; + +struct rational { + u32 num; + u32 den; +}; + +struct srational { + s32 num; + s32 den; +}; + +#define FLASH_FIRED_SHIFT 0 +#define FLASH_NOT_FIRED 0 +#define FLASH_FIRED 1 + +#define FLASH_STROBE_SHIFT 1 +#define FLASH_STROBE_NO_DETECTION 0 +#define FLASH_STROBE_RESERVED 1 +#define FLASH_STROBE_RETURN_LIGHT_NOT_DETECTED 2 +#define FLASH_STROBE_RETURN_LIGHT_DETECTED 3 + +#define FLASH_MODE_SHIFT 3 +#define FLASH_MODE_UNKNOWN 0 +#define FLASH_MODE_COMPULSORY_FLASH_FIRING 1 +#define FLASH_MODE_COMPULSORY_FLASH_SUPPRESSION 2 +#define FLASH_MODE_AUTO_MODE 3 + +#define FLASH_FUNCTION_SHIFT 5 +#define FLASH_FUNCTION_PRESENT 0 +#define FLASH_FUNCTION_NONE 1 + +#define FLASH_RED_EYE_SHIFT 6 +#define FLASH_RED_EYE_DISABLED 0 +#define FLASH_RED_EYE_SUPPORTED 1 + +enum apex_aperture_value { + F1_0 = 0, + F1_4 = 1, + F2_0 = 2, + F2_8 = 3, + F4_0 = 4, + F5_6 = 5, + F8_9 = 6, + F11_0 = 7, + F16_0 = 8, + F22_0 = 9, + F32_0 = 10, +}; + +struct exif_attribute { + struct rational exposure_time; + struct srational shutter_speed; + u32 iso_speed_rating; + u32 flash; + struct srational brightness; +} __packed; + +struct is_frame_header { + u32 valid; + u32 bad_mark; + u32 captured; + u32 frame_number; + struct exif_attribute exif; +} __packed; + +struct is_fd_rect { + u32 offset_x; + u32 offset_y; + u32 width; + u32 height; +}; + +struct is_face_marker { + u32 frame_number; + struct is_fd_rect face; + struct is_fd_rect left_eye; + struct is_fd_rect right_eye; + struct is_fd_rect mouth; + u32 roll_angle; + u32 yaw_angle; + u32 confidence; + s32 smile_level; + s32 blink_level; +} __packed; + +#define MAX_FRAME_COUNT 8 +#define MAX_FRAME_COUNT_PREVIEW 4 +#define MAX_FRAME_COUNT_CAPTURE 1 +#define MAX_FACE_COUNT 16 +#define MAX_SHARED_COUNT 500 + +struct is_region { + struct is_param_region parameter; + struct is_tune_region tune; + struct is_frame_header header[MAX_FRAME_COUNT]; + struct is_face_marker face[MAX_FACE_COUNT]; + u32 shared[MAX_SHARED_COUNT]; +} __packed; + +/* Offset to the ISP DMA2 output buffer address array. */ +#define DMA2_OUTPUT_ADDR_ARRAY_OFFS \ + (offsetof(struct is_region, shared) + 32 * sizeof(u32)) + +struct is_debug_frame_descriptor { + u32 sensor_frame_time; + u32 sensor_exposure_time; + s32 sensor_analog_gain; + /* monitor for AA */ + u32 req_lei; + + u32 next_next_lei_exp; + u32 next_next_lei_a_gain; + u32 next_next_lei_d_gain; + u32 next_next_lei_statlei; + u32 next_next_lei_lei; + + u32 dummy0; +}; + +#define MAX_FRAMEDESCRIPTOR_CONTEXT_NUM (30*20) /* 600 frames */ +#define MAX_VERSION_DISPLAY_BUF 32 + +struct is_share_region { + u32 frame_time; + u32 exposure_time; + s32 analog_gain; + + u32 r_gain; + u32 g_gain; + u32 b_gain; + + u32 af_position; + u32 af_status; + /* 0 : SIRC_ISP_CAMERA_AUTOFOCUSMESSAGE_NOMESSAGE */ + /* 1 : SIRC_ISP_CAMERA_AUTOFOCUSMESSAGE_REACHED */ + /* 2 : SIRC_ISP_CAMERA_AUTOFOCUSMESSAGE_UNABLETOREACH */ + /* 3 : SIRC_ISP_CAMERA_AUTOFOCUSMESSAGE_LOST */ + /* default : unknown */ + u32 af_scene_type; + + u32 frame_descp_onoff_control; + u32 frame_descp_update_done; + u32 frame_descp_idx; + u32 frame_descp_max_idx; + struct is_debug_frame_descriptor + dbg_frame_descp_ctx[MAX_FRAMEDESCRIPTOR_CONTEXT_NUM]; + + u32 chip_id; + u32 chip_rev_no; + u8 isp_fw_ver_no[MAX_VERSION_DISPLAY_BUF]; + u8 isp_fw_ver_date[MAX_VERSION_DISPLAY_BUF]; + u8 sirc_sdk_ver_no[MAX_VERSION_DISPLAY_BUF]; + u8 sirc_sdk_rev_no[MAX_VERSION_DISPLAY_BUF]; + u8 sirc_sdk_rev_date[MAX_VERSION_DISPLAY_BUF]; +} __packed; + +struct is_debug_control { + u32 write_point; /* 0~ 500KB boundary */ + u32 assert_flag; /* 0: Not invoked, 1: Invoked */ + u32 pabort_flag; /* 0: Not invoked, 1: Invoked */ + u32 dabort_flag; /* 0: Not invoked, 1: Invoked */ +}; + +struct sensor_open_extended { + u32 actuator_type; + u32 mclk; + u32 mipi_lane_num; + u32 mipi_speed; + /* Skip setfile loading when fast_open_sensor is not 0 */ + u32 fast_open_sensor; + /* Activating sensor self calibration mode (6A3) */ + u32 self_calibration_mode; + /* This field is to adjust I2c clock based on ACLK200 */ + /* This value is varied in case of rev 0.2 */ + u32 i2c_sclk; +}; + +struct fimc_is; + +int fimc_is_hw_get_sensor_max_framerate(struct fimc_is *is); +int __fimc_is_hw_update_param(struct fimc_is *is, u32 offset); +void fimc_is_set_initial_params(struct fimc_is *is); +unsigned int __get_pending_param_count(struct fimc_is *is); + +int __is_hw_update_params(struct fimc_is *is); +void __is_get_frame_size(struct fimc_is *is, struct v4l2_mbus_framefmt *mf); +void __is_set_frame_size(struct fimc_is *is, struct v4l2_mbus_framefmt *mf); +void __is_set_sensor(struct fimc_is *is, int fps); +void __is_set_isp_aa_ae(struct fimc_is *is); +void __is_set_isp_flash(struct fimc_is *is, u32 cmd, u32 redeye); +void __is_set_isp_awb(struct fimc_is *is, u32 cmd, u32 val); +void __is_set_isp_effect(struct fimc_is *is, u32 cmd); +void __is_set_isp_iso(struct fimc_is *is, u32 cmd, u32 val); +void __is_set_isp_adjust(struct fimc_is *is, u32 cmd, u32 val); +void __is_set_isp_metering(struct fimc_is *is, u32 id, u32 val); +void __is_set_isp_afc(struct fimc_is *is, u32 cmd, u32 val); +void __is_set_drc_control(struct fimc_is *is, u32 val); +void __is_set_fd_control(struct fimc_is *is, u32 val); +void __is_set_fd_config_maxface(struct fimc_is *is, u32 val); +void __is_set_fd_config_rollangle(struct fimc_is *is, u32 val); +void __is_set_fd_config_yawangle(struct fimc_is *is, u32 val); +void __is_set_fd_config_smilemode(struct fimc_is *is, u32 val); +void __is_set_fd_config_blinkmode(struct fimc_is *is, u32 val); +void __is_set_fd_config_eyedetect(struct fimc_is *is, u32 val); +void __is_set_fd_config_mouthdetect(struct fimc_is *is, u32 val); +void __is_set_fd_config_orientation(struct fimc_is *is, u32 val); +void __is_set_fd_config_orientation_val(struct fimc_is *is, u32 val); +void __is_set_isp_aa_af_mode(struct fimc_is *is, int cmd); +void __is_set_isp_aa_af_start_stop(struct fimc_is *is, int cmd); + +#endif diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.c new file mode 100644 index 000000000..366e63938 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd. + * + * Authors: Younghwan Joo + * Sylwester Nawrocki + */ +#include + +#include "fimc-is.h" +#include "fimc-is-command.h" +#include "fimc-is-regs.h" +#include "fimc-is-sensor.h" + +void fimc_is_fw_clear_irq1(struct fimc_is *is, unsigned int nr) +{ + mcuctl_write(1UL << nr, is, MCUCTL_REG_INTCR1); +} + +void fimc_is_fw_clear_irq2(struct fimc_is *is) +{ + u32 cfg = mcuctl_read(is, MCUCTL_REG_INTSR2); + mcuctl_write(cfg, is, MCUCTL_REG_INTCR2); +} + +void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is) +{ + mcuctl_write(INTGR0_INTGD(0), is, MCUCTL_REG_INTGR0); +} + +int fimc_is_hw_wait_intmsr0_intmsd0(struct fimc_is *is) +{ + unsigned int timeout = 2000; + u32 cfg, status; + + do { + cfg = mcuctl_read(is, MCUCTL_REG_INTMSR0); + status = INTMSR0_GET_INTMSD(0, cfg); + + if (--timeout == 0) { + dev_warn(&is->pdev->dev, "%s timeout\n", + __func__); + return -ETIMEDOUT; + } + udelay(1); + } while (status != 0); + + return 0; +} + +int fimc_is_hw_set_param(struct fimc_is *is) +{ + struct chain_config *config = &is->config[is->config_index]; + unsigned int param_count = __get_pending_param_count(is); + + fimc_is_hw_wait_intmsr0_intmsd0(is); + + mcuctl_write(HIC_SET_PARAMETER, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + mcuctl_write(is->config_index, is, MCUCTL_REG_ISSR(2)); + + mcuctl_write(param_count, is, MCUCTL_REG_ISSR(3)); + mcuctl_write(config->p_region_index[0], is, MCUCTL_REG_ISSR(4)); + mcuctl_write(config->p_region_index[1], is, MCUCTL_REG_ISSR(5)); + + fimc_is_hw_set_intgr0_gd0(is); + return 0; +} + +static int __maybe_unused fimc_is_hw_set_tune(struct fimc_is *is) +{ + fimc_is_hw_wait_intmsr0_intmsd0(is); + + mcuctl_write(HIC_SET_TUNE, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + mcuctl_write(is->h2i_cmd.entry_id, is, MCUCTL_REG_ISSR(2)); + + fimc_is_hw_set_intgr0_gd0(is); + return 0; +} + +#define FIMC_IS_MAX_PARAMS 4 + +int fimc_is_hw_get_params(struct fimc_is *is, unsigned int num_args) +{ + int i; + + if (num_args > FIMC_IS_MAX_PARAMS) + return -EINVAL; + + is->i2h_cmd.num_args = num_args; + + for (i = 0; i < FIMC_IS_MAX_PARAMS; i++) { + if (i < num_args) + is->i2h_cmd.args[i] = mcuctl_read(is, + MCUCTL_REG_ISSR(12 + i)); + else + is->i2h_cmd.args[i] = 0; + } + return 0; +} + +void fimc_is_hw_set_isp_buf_mask(struct fimc_is *is, unsigned int mask) +{ + if (hweight32(mask) == 1) { + dev_err(&is->pdev->dev, "%s(): not enough buffers (mask %#x)\n", + __func__, mask); + return; + } + + if (mcuctl_read(is, MCUCTL_REG_ISSR(23)) != 0) + dev_dbg(&is->pdev->dev, "non-zero DMA buffer mask\n"); + + mcuctl_write(mask, is, MCUCTL_REG_ISSR(23)); +} + +void fimc_is_hw_set_sensor_num(struct fimc_is *is) +{ + pr_debug("setting sensor index to: %d\n", is->sensor_index); + + mcuctl_write(IH_REPLY_DONE, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + mcuctl_write(IHC_GET_SENSOR_NUM, is, MCUCTL_REG_ISSR(2)); + mcuctl_write(FIMC_IS_SENSORS_NUM, is, MCUCTL_REG_ISSR(3)); +} + +void fimc_is_hw_close_sensor(struct fimc_is *is, unsigned int index) +{ + if (is->sensor_index != index) + return; + + fimc_is_hw_wait_intmsr0_intmsd0(is); + mcuctl_write(HIC_CLOSE_SENSOR, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(2)); + fimc_is_hw_set_intgr0_gd0(is); +} + +void fimc_is_hw_get_setfile_addr(struct fimc_is *is) +{ + fimc_is_hw_wait_intmsr0_intmsd0(is); + mcuctl_write(HIC_GET_SET_FILE_ADDR, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + fimc_is_hw_set_intgr0_gd0(is); +} + +void fimc_is_hw_load_setfile(struct fimc_is *is) +{ + fimc_is_hw_wait_intmsr0_intmsd0(is); + mcuctl_write(HIC_LOAD_SET_FILE, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + fimc_is_hw_set_intgr0_gd0(is); +} + +int fimc_is_hw_change_mode(struct fimc_is *is) +{ + static const u8 cmd[] = { + HIC_PREVIEW_STILL, HIC_PREVIEW_VIDEO, + HIC_CAPTURE_STILL, HIC_CAPTURE_VIDEO, + }; + + if (WARN_ON(is->config_index >= ARRAY_SIZE(cmd))) + return -EINVAL; + + mcuctl_write(cmd[is->config_index], is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + mcuctl_write(is->setfile.sub_index, is, MCUCTL_REG_ISSR(2)); + fimc_is_hw_set_intgr0_gd0(is); + return 0; +} + +void fimc_is_hw_stream_on(struct fimc_is *is) +{ + fimc_is_hw_wait_intmsr0_intmsd0(is); + mcuctl_write(HIC_STREAM_ON, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + mcuctl_write(0, is, MCUCTL_REG_ISSR(2)); + fimc_is_hw_set_intgr0_gd0(is); +} + +void fimc_is_hw_stream_off(struct fimc_is *is) +{ + fimc_is_hw_wait_intmsr0_intmsd0(is); + mcuctl_write(HIC_STREAM_OFF, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + fimc_is_hw_set_intgr0_gd0(is); +} + +void fimc_is_hw_subip_power_off(struct fimc_is *is) +{ + fimc_is_hw_wait_intmsr0_intmsd0(is); + mcuctl_write(HIC_POWER_DOWN, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + fimc_is_hw_set_intgr0_gd0(is); +} + +int fimc_is_itf_s_param(struct fimc_is *is, bool update) +{ + int ret; + + if (update) + __is_hw_update_params(is); + + fimc_is_mem_barrier(); + + clear_bit(IS_ST_BLOCK_CMD_CLEARED, &is->state); + fimc_is_hw_set_param(is); + ret = fimc_is_wait_event(is, IS_ST_BLOCK_CMD_CLEARED, 1, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) + dev_err(&is->pdev->dev, "%s() timeout\n", __func__); + + return ret; +} + +int fimc_is_itf_mode_change(struct fimc_is *is) +{ + int ret; + + clear_bit(IS_ST_CHANGE_MODE, &is->state); + fimc_is_hw_change_mode(is); + ret = fimc_is_wait_event(is, IS_ST_CHANGE_MODE, 1, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) + dev_err(&is->pdev->dev, "%s(): mode change (%d) timeout\n", + __func__, is->config_index); + return ret; +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.h new file mode 100644 index 000000000..5d8b01bc8 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-regs.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Authors: Sylwester Nawrocki + * Younghwan Joo + */ +#ifndef FIMC_IS_REG_H_ +#define FIMC_IS_REG_H_ + +/* WDT_ISP register */ +#define REG_WDT_ISP 0x00170000 + +/* MCUCTL registers base offset */ +#define MCUCTL_BASE 0x00180000 + +/* MCU Controller Register */ +#define MCUCTL_REG_MCUCTRL (MCUCTL_BASE + 0x00) +#define MCUCTRL_MSWRST (1 << 0) + +/* Boot Base Offset Address Register */ +#define MCUCTL_REG_BBOAR (MCUCTL_BASE + 0x04) + +/* Interrupt Generation Register 0 from Host CPU to VIC */ +#define MCUCTL_REG_INTGR0 (MCUCTL_BASE + 0x08) +/* __n = 0...9 */ +#define INTGR0_INTGC(__n) (1 << ((__n) + 16)) +/* __n = 0...5 */ +#define INTGR0_INTGD(__n) (1 << (__n)) + +/* Interrupt Clear Register 0 from Host CPU to VIC */ +#define MCUCTL_REG_INTCR0 (MCUCTL_BASE + 0x0c) +/* __n = 0...9 */ +#define INTCR0_INTGC(__n) (1 << ((__n) + 16)) +/* __n = 0...5 */ +#define INTCR0_INTCD(__n) (1 << ((__n) + 16)) + +/* Interrupt Mask Register 0 from Host CPU to VIC */ +#define MCUCTL_REG_INTMR0 (MCUCTL_BASE + 0x10) +/* __n = 0...9 */ +#define INTMR0_INTMC(__n) (1 << ((__n) + 16)) +/* __n = 0...5 */ +#define INTMR0_INTMD(__n) (1 << (__n)) + +/* Interrupt Status Register 0 from Host CPU to VIC */ +#define MCUCTL_REG_INTSR0 (MCUCTL_BASE + 0x14) +/* __n (bit number) = 0...4 */ +#define INTSR0_GET_INTSD(x, __n) (((x) >> (__n)) & 0x1) +/* __n (bit number) = 0...9 */ +#define INTSR0_GET_INTSC(x, __n) (((x) >> ((__n) + 16)) & 0x1) + +/* Interrupt Mask Status Register 0 from Host CPU to VIC */ +#define MCUCTL_REG_INTMSR0 (MCUCTL_BASE + 0x18) +/* __n (bit number) = 0...4 */ +#define INTMSR0_GET_INTMSD(x, __n) (((x) >> (__n)) & 0x1) +/* __n (bit number) = 0...9 */ +#define INTMSR0_GET_INTMSC(x, __n) (((x) >> ((__n) + 16)) & 0x1) + +/* Interrupt Generation Register 1 from ISP CPU to Host IC */ +#define MCUCTL_REG_INTGR1 (MCUCTL_BASE + 0x1c) +/* __n = 0...9 */ +#define INTGR1_INTGC(__n) (1 << (__n)) + +/* Interrupt Clear Register 1 from ISP CPU to Host IC */ +#define MCUCTL_REG_INTCR1 (MCUCTL_BASE + 0x20) +/* __n = 0...9 */ +#define INTCR1_INTCC(__n) (1 << (__n)) + +/* Interrupt Mask Register 1 from ISP CPU to Host IC */ +#define MCUCTL_REG_INTMR1 (MCUCTL_BASE + 0x24) +/* __n = 0...9 */ +#define INTMR1_INTMC(__n) (1 << (__n)) + +/* Interrupt Status Register 1 from ISP CPU to Host IC */ +#define MCUCTL_REG_INTSR1 (MCUCTL_BASE + 0x28) +/* Interrupt Mask Status Register 1 from ISP CPU to Host IC */ +#define MCUCTL_REG_INTMSR1 (MCUCTL_BASE + 0x2c) + +/* Interrupt Clear Register 2 from ISP BLK's interrupts to Host IC */ +#define MCUCTL_REG_INTCR2 (MCUCTL_BASE + 0x30) +/* __n = 0...5 */ +#define INTCR2_INTCC(__n) (1 << ((__n) + 16)) + +/* Interrupt Mask Register 2 from ISP BLK's interrupts to Host IC */ +#define MCUCTL_REG_INTMR2 (MCUCTL_BASE + 0x34) +/* __n = 0...25 */ +#define INTMR2_INTMCIS(__n) (1 << (__n)) + +/* Interrupt Status Register 2 from ISP BLK's interrupts to Host IC */ +#define MCUCTL_REG_INTSR2 (MCUCTL_BASE + 0x38) +/* Interrupt Mask Status Register 2 from ISP BLK's interrupts to Host IC */ +#define MCUCTL_REG_INTMSR2 (MCUCTL_BASE + 0x3c) + +/* General Purpose Output Control Register (0~17) */ +#define MCUCTL_REG_GPOCTLR (MCUCTL_BASE + 0x40) +/* __n = 0...17 */ +#define GPOCTLR_GPOG(__n) (1 << (__n)) + +/* General Purpose Pad Output Enable Register (0~17) */ +#define MCUCTL_REG_GPOENCTLR (MCUCTL_BASE + 0x44) +/* __n = 0...17 */ +#define GPOENCTLR_GPOEN(__n) (1 << (__n)) + +/* General Purpose Input Control Register (0~17) */ +#define MCUCTL_REG_GPICTLR (MCUCTL_BASE + 0x48) + +/* Shared registers between ISP CPU and the host CPU - ISSRxx */ + +/* ISSR(1): Command Host -> IS */ +/* ISSR(1): Sensor ID for Command, ISSR2...5 = Parameter 1...4 */ + +/* ISSR(10): Reply IS -> Host */ +/* ISSR(11): Sensor ID for Reply, ISSR12...15 = Parameter 1...4 */ + +/* ISSR(20): ISP_FRAME_DONE : SENSOR ID */ +/* ISSR(21): ISP_FRAME_DONE : PARAMETER 1 */ + +/* ISSR(24): SCALERC_FRAME_DONE : SENSOR ID */ +/* ISSR(25): SCALERC_FRAME_DONE : PARAMETER 1 */ + +/* ISSR(28): 3DNR_FRAME_DONE : SENSOR ID */ +/* ISSR(29): 3DNR_FRAME_DONE : PARAMETER 1 */ + +/* ISSR(32): SCALERP_FRAME_DONE : SENSOR ID */ +/* ISSR(33): SCALERP_FRAME_DONE : PARAMETER 1 */ + +/* __n = 0...63 */ +#define MCUCTL_REG_ISSR(__n) (MCUCTL_BASE + 0x80 + ((__n) * 4)) + +/* PMU ISP register offsets */ +#define REG_CMU_RESET_ISP_SYS_PWR_REG 0x1174 +#define REG_CMU_SYSCLK_ISP_SYS_PWR_REG 0x13b8 +#define REG_PMU_ISP_ARM_SYS 0x1050 +#define REG_PMU_ISP_ARM_CONFIGURATION 0x2280 +#define REG_PMU_ISP_ARM_STATUS 0x2284 +#define REG_PMU_ISP_ARM_OPTION 0x2288 + +void fimc_is_fw_clear_irq1(struct fimc_is *is, unsigned int bit); +void fimc_is_fw_clear_irq2(struct fimc_is *is); +int fimc_is_hw_get_params(struct fimc_is *is, unsigned int num); + +void fimc_is_hw_set_intgr0_gd0(struct fimc_is *is); +int fimc_is_hw_wait_intmsr0_intmsd0(struct fimc_is *is); +void fimc_is_hw_set_sensor_num(struct fimc_is *is); +void fimc_is_hw_set_isp_buf_mask(struct fimc_is *is, unsigned int mask); +void fimc_is_hw_stream_on(struct fimc_is *is); +void fimc_is_hw_stream_off(struct fimc_is *is); +int fimc_is_hw_set_param(struct fimc_is *is); +int fimc_is_hw_change_mode(struct fimc_is *is); + +void fimc_is_hw_close_sensor(struct fimc_is *is, unsigned int index); +void fimc_is_hw_get_setfile_addr(struct fimc_is *is); +void fimc_is_hw_load_setfile(struct fimc_is *is); +void fimc_is_hw_subip_power_off(struct fimc_is *is); + +int fimc_is_itf_s_param(struct fimc_is *is, bool update); +int fimc_is_itf_mode_change(struct fimc_is *is); + +#endif /* FIMC_IS_REG_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.c new file mode 100644 index 000000000..0e5b9fede --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.c @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + */ + +#include "fimc-is-sensor.h" + +static const struct sensor_drv_data s5k6a3_drvdata = { + .id = FIMC_IS_SENSOR_ID_S5K6A3, + .open_timeout = S5K6A3_OPEN_TIMEOUT, +}; + +static const struct of_device_id fimc_is_sensor_of_ids[] = { + { + .compatible = "samsung,s5k6a3", + .data = &s5k6a3_drvdata, + }, + { } +}; + +const struct sensor_drv_data *fimc_is_sensor_get_drvdata( + struct device_node *node) +{ + const struct of_device_id *of_id; + + of_id = of_match_node(fimc_is_sensor_of_ids, node); + return of_id ? of_id->data : NULL; +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.h new file mode 100644 index 000000000..9aefc6388 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-sensor.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Authors: Sylwester Nawrocki + * Younghwan Joo + */ +#ifndef FIMC_IS_SENSOR_H_ +#define FIMC_IS_SENSOR_H_ + +#include +#include + +#define S5K6A3_OPEN_TIMEOUT 2000 /* ms */ +#define S5K6A3_SENSOR_WIDTH 1392 +#define S5K6A3_SENSOR_HEIGHT 1392 + +enum fimc_is_sensor_id { + FIMC_IS_SENSOR_ID_S5K3H2 = 1, + FIMC_IS_SENSOR_ID_S5K6A3, + FIMC_IS_SENSOR_ID_S5K4E5, + FIMC_IS_SENSOR_ID_S5K3H7, + FIMC_IS_SENSOR_ID_CUSTOM, + FIMC_IS_SENSOR_ID_END +}; + +#define IS_SENSOR_CTRL_BUS_I2C0 0 +#define IS_SENSOR_CTRL_BUS_I2C1 1 + +struct sensor_drv_data { + enum fimc_is_sensor_id id; + /* sensor open timeout in ms */ + unsigned short open_timeout; +}; + +/** + * struct fimc_is_sensor - fimc-is sensor data structure + * @drvdata: a pointer to the sensor's parameters data structure + * @i2c_bus: ISP I2C bus index (0...1) + * @test_pattern: true to enable video test pattern + */ +struct fimc_is_sensor { + const struct sensor_drv_data *drvdata; + unsigned int i2c_bus; + u8 test_pattern; +}; + +const struct sensor_drv_data *fimc_is_sensor_get_drvdata( + struct device_node *node); + +#endif /* FIMC_IS_SENSOR_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is.c b/drivers/media/platform/samsung/exynos4-is/fimc-is.c new file mode 100644 index 000000000..a7704ff06 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is.c @@ -0,0 +1,989 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Authors: Sylwester Nawrocki + * Younghwan Joo + */ +#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media-dev.h" +#include "fimc-is.h" +#include "fimc-is-command.h" +#include "fimc-is-errno.h" +#include "fimc-is-i2c.h" +#include "fimc-is-param.h" +#include "fimc-is-regs.h" + + +static char *fimc_is_clocks[ISS_CLKS_MAX] = { + [ISS_CLK_PPMUISPX] = "ppmuispx", + [ISS_CLK_PPMUISPMX] = "ppmuispmx", + [ISS_CLK_LITE0] = "lite0", + [ISS_CLK_LITE1] = "lite1", + [ISS_CLK_MPLL] = "mpll", + [ISS_CLK_ISP] = "isp", + [ISS_CLK_DRC] = "drc", + [ISS_CLK_FD] = "fd", + [ISS_CLK_MCUISP] = "mcuisp", + [ISS_CLK_GICISP] = "gicisp", + [ISS_CLK_PWM_ISP] = "pwm_isp", + [ISS_CLK_MCUCTL_ISP] = "mcuctl_isp", + [ISS_CLK_UART] = "uart", + [ISS_CLK_ISP_DIV0] = "ispdiv0", + [ISS_CLK_ISP_DIV1] = "ispdiv1", + [ISS_CLK_MCUISP_DIV0] = "mcuispdiv0", + [ISS_CLK_MCUISP_DIV1] = "mcuispdiv1", + [ISS_CLK_ACLK200] = "aclk200", + [ISS_CLK_ACLK200_DIV] = "div_aclk200", + [ISS_CLK_ACLK400MCUISP] = "aclk400mcuisp", + [ISS_CLK_ACLK400MCUISP_DIV] = "div_aclk400mcuisp", +}; + +static void fimc_is_put_clocks(struct fimc_is *is) +{ + int i; + + for (i = 0; i < ISS_CLKS_MAX; i++) { + if (IS_ERR(is->clocks[i])) + continue; + clk_put(is->clocks[i]); + is->clocks[i] = ERR_PTR(-EINVAL); + } +} + +static int fimc_is_get_clocks(struct fimc_is *is) +{ + int i, ret; + + for (i = 0; i < ISS_CLKS_MAX; i++) + is->clocks[i] = ERR_PTR(-EINVAL); + + for (i = 0; i < ISS_CLKS_MAX; i++) { + is->clocks[i] = clk_get(&is->pdev->dev, fimc_is_clocks[i]); + if (IS_ERR(is->clocks[i])) { + ret = PTR_ERR(is->clocks[i]); + goto err; + } + } + + return 0; +err: + fimc_is_put_clocks(is); + dev_err(&is->pdev->dev, "failed to get clock: %s\n", + fimc_is_clocks[i]); + return ret; +} + +static int fimc_is_setup_clocks(struct fimc_is *is) +{ + int ret; + + ret = clk_set_parent(is->clocks[ISS_CLK_ACLK200], + is->clocks[ISS_CLK_ACLK200_DIV]); + if (ret < 0) + return ret; + + ret = clk_set_parent(is->clocks[ISS_CLK_ACLK400MCUISP], + is->clocks[ISS_CLK_ACLK400MCUISP_DIV]); + if (ret < 0) + return ret; + + ret = clk_set_rate(is->clocks[ISS_CLK_ISP_DIV0], ACLK_AXI_FREQUENCY); + if (ret < 0) + return ret; + + ret = clk_set_rate(is->clocks[ISS_CLK_ISP_DIV1], ACLK_AXI_FREQUENCY); + if (ret < 0) + return ret; + + ret = clk_set_rate(is->clocks[ISS_CLK_MCUISP_DIV0], + ATCLK_MCUISP_FREQUENCY); + if (ret < 0) + return ret; + + return clk_set_rate(is->clocks[ISS_CLK_MCUISP_DIV1], + ATCLK_MCUISP_FREQUENCY); +} + +static int fimc_is_enable_clocks(struct fimc_is *is) +{ + int i, ret; + + for (i = 0; i < ISS_GATE_CLKS_MAX; i++) { + if (IS_ERR(is->clocks[i])) + continue; + ret = clk_prepare_enable(is->clocks[i]); + if (ret < 0) { + dev_err(&is->pdev->dev, "clock %s enable failed\n", + fimc_is_clocks[i]); + for (--i; i >= 0; i--) + clk_disable_unprepare(is->clocks[i]); + return ret; + } + pr_debug("enabled clock: %s\n", fimc_is_clocks[i]); + } + return 0; +} + +static void fimc_is_disable_clocks(struct fimc_is *is) +{ + int i; + + for (i = 0; i < ISS_GATE_CLKS_MAX; i++) { + if (!IS_ERR(is->clocks[i])) { + clk_disable_unprepare(is->clocks[i]); + pr_debug("disabled clock: %s\n", fimc_is_clocks[i]); + } + } +} + +static int fimc_is_parse_sensor_config(struct fimc_is *is, unsigned int index, + struct device_node *node) +{ + struct fimc_is_sensor *sensor = &is->sensor[index]; + struct device_node *ep, *port; + u32 tmp = 0; + int ret; + + sensor->drvdata = fimc_is_sensor_get_drvdata(node); + if (!sensor->drvdata) { + dev_err(&is->pdev->dev, "no driver data found for: %pOF\n", + node); + return -EINVAL; + } + + ep = of_graph_get_next_endpoint(node, NULL); + if (!ep) + return -ENXIO; + + port = of_graph_get_remote_port(ep); + of_node_put(ep); + if (!port) + return -ENXIO; + + /* Use MIPI-CSIS channel id to determine the ISP I2C bus index. */ + ret = of_property_read_u32(port, "reg", &tmp); + if (ret < 0) { + dev_err(&is->pdev->dev, "reg property not found at: %pOF\n", + port); + of_node_put(port); + return ret; + } + + of_node_put(port); + sensor->i2c_bus = tmp - FIMC_INPUT_MIPI_CSI2_0; + return 0; +} + +static int fimc_is_register_subdevs(struct fimc_is *is) +{ + struct device_node *i2c_bus, *child; + int ret, index = 0; + + ret = fimc_isp_subdev_create(&is->isp); + if (ret < 0) + return ret; + + for_each_compatible_node(i2c_bus, NULL, FIMC_IS_I2C_COMPATIBLE) { + for_each_available_child_of_node(i2c_bus, child) { + ret = fimc_is_parse_sensor_config(is, index, child); + + if (ret < 0 || index >= FIMC_IS_SENSORS_NUM) { + of_node_put(child); + of_node_put(i2c_bus); + return ret; + } + index++; + } + } + return 0; +} + +static int fimc_is_unregister_subdevs(struct fimc_is *is) +{ + fimc_isp_subdev_destroy(&is->isp); + return 0; +} + +static int fimc_is_load_setfile(struct fimc_is *is, char *file_name) +{ + const struct firmware *fw; + void *buf; + int ret; + + ret = request_firmware(&fw, file_name, &is->pdev->dev); + if (ret < 0) { + dev_err(&is->pdev->dev, "firmware request failed (%d)\n", ret); + return ret; + } + buf = is->memory.vaddr + is->setfile.base; + memcpy(buf, fw->data, fw->size); + fimc_is_mem_barrier(); + is->setfile.size = fw->size; + + pr_debug("mem vaddr: %p, setfile buf: %p\n", is->memory.vaddr, buf); + + memcpy(is->fw.setfile_info, + fw->data + fw->size - FIMC_IS_SETFILE_INFO_LEN, + FIMC_IS_SETFILE_INFO_LEN - 1); + + is->fw.setfile_info[FIMC_IS_SETFILE_INFO_LEN - 1] = '\0'; + is->setfile.state = 1; + + pr_debug("FIMC-IS setfile loaded: base: %#x, size: %zu B\n", + is->setfile.base, fw->size); + + release_firmware(fw); + return ret; +} + +int fimc_is_cpu_set_power(struct fimc_is *is, int on) +{ + unsigned int timeout = FIMC_IS_POWER_ON_TIMEOUT; + + if (on) { + /* Disable watchdog */ + mcuctl_write(0, is, REG_WDT_ISP); + + /* Cortex-A5 start address setting */ + mcuctl_write(is->memory.addr, is, MCUCTL_REG_BBOAR); + + /* Enable and start Cortex-A5 */ + pmuisp_write(0x18000, is, REG_PMU_ISP_ARM_OPTION); + pmuisp_write(0x1, is, REG_PMU_ISP_ARM_CONFIGURATION); + } else { + /* A5 power off */ + pmuisp_write(0x10000, is, REG_PMU_ISP_ARM_OPTION); + pmuisp_write(0x0, is, REG_PMU_ISP_ARM_CONFIGURATION); + + while (pmuisp_read(is, REG_PMU_ISP_ARM_STATUS) & 1) { + if (timeout == 0) + return -ETIME; + timeout--; + udelay(1); + } + } + + return 0; +} + +/* Wait until @bit of @is->state is set to @state in the interrupt handler. */ +int fimc_is_wait_event(struct fimc_is *is, unsigned long bit, + unsigned int state, unsigned int timeout) +{ + + int ret = wait_event_timeout(is->irq_queue, + !state ^ test_bit(bit, &is->state), + timeout); + if (ret == 0) { + dev_WARN(&is->pdev->dev, "%s() timed out\n", __func__); + return -ETIME; + } + return 0; +} + +int fimc_is_start_firmware(struct fimc_is *is) +{ + struct device *dev = &is->pdev->dev; + int ret; + + if (is->fw.f_w == NULL) { + dev_err(dev, "firmware is not loaded\n"); + return -EINVAL; + } + + memcpy(is->memory.vaddr, is->fw.f_w->data, is->fw.f_w->size); + wmb(); + + ret = fimc_is_cpu_set_power(is, 1); + if (ret < 0) + return ret; + + ret = fimc_is_wait_event(is, IS_ST_A5_PWR_ON, 1, + msecs_to_jiffies(FIMC_IS_FW_LOAD_TIMEOUT)); + if (ret < 0) + dev_err(dev, "FIMC-IS CPU power on failed\n"); + + return ret; +} + +/* Allocate working memory for the FIMC-IS CPU. */ +static int fimc_is_alloc_cpu_memory(struct fimc_is *is) +{ + struct device *dev = &is->pdev->dev; + + is->memory.vaddr = dma_alloc_coherent(dev, FIMC_IS_CPU_MEM_SIZE, + &is->memory.addr, GFP_KERNEL); + if (is->memory.vaddr == NULL) + return -ENOMEM; + + is->memory.size = FIMC_IS_CPU_MEM_SIZE; + + dev_info(dev, "FIMC-IS CPU memory base: %pad\n", &is->memory.addr); + + if (((u32)is->memory.addr) & FIMC_IS_FW_ADDR_MASK) { + dev_err(dev, "invalid firmware memory alignment: %#x\n", + (u32)is->memory.addr); + dma_free_coherent(dev, is->memory.size, is->memory.vaddr, + is->memory.addr); + return -EIO; + } + + is->is_p_region = (struct is_region *)(is->memory.vaddr + + FIMC_IS_CPU_MEM_SIZE - FIMC_IS_REGION_SIZE); + + is->is_dma_p_region = is->memory.addr + + FIMC_IS_CPU_MEM_SIZE - FIMC_IS_REGION_SIZE; + + is->is_shared_region = (struct is_share_region *)(is->memory.vaddr + + FIMC_IS_SHARED_REGION_OFFSET); + return 0; +} + +static void fimc_is_free_cpu_memory(struct fimc_is *is) +{ + struct device *dev = &is->pdev->dev; + + if (is->memory.vaddr == NULL) + return; + + dma_free_coherent(dev, is->memory.size, is->memory.vaddr, + is->memory.addr); +} + +static void fimc_is_load_firmware(const struct firmware *fw, void *context) +{ + struct fimc_is *is = context; + struct device *dev = &is->pdev->dev; + void *buf; + int ret; + + if (fw == NULL) { + dev_err(dev, "firmware request failed\n"); + return; + } + mutex_lock(&is->lock); + + if (fw->size < FIMC_IS_FW_SIZE_MIN || fw->size > FIMC_IS_FW_SIZE_MAX) { + dev_err(dev, "wrong firmware size: %zu\n", fw->size); + goto done; + } + + is->fw.size = fw->size; + + ret = fimc_is_alloc_cpu_memory(is); + if (ret < 0) { + dev_err(dev, "failed to allocate FIMC-IS CPU memory\n"); + goto done; + } + + memcpy(is->memory.vaddr, fw->data, fw->size); + wmb(); + + /* Read firmware description. */ + buf = (void *)(is->memory.vaddr + fw->size - FIMC_IS_FW_DESC_LEN); + memcpy(&is->fw.info, buf, FIMC_IS_FW_INFO_LEN); + is->fw.info[FIMC_IS_FW_INFO_LEN] = 0; + + buf = (void *)(is->memory.vaddr + fw->size - FIMC_IS_FW_VER_LEN); + memcpy(&is->fw.version, buf, FIMC_IS_FW_VER_LEN); + is->fw.version[FIMC_IS_FW_VER_LEN - 1] = 0; + + is->fw.state = 1; + + dev_info(dev, "loaded firmware: %s, rev. %s\n", + is->fw.info, is->fw.version); + dev_dbg(dev, "FW size: %zu, DMA addr: %pad\n", fw->size, &is->memory.addr); + + is->is_shared_region->chip_id = 0xe4412; + is->is_shared_region->chip_rev_no = 1; + + fimc_is_mem_barrier(); + + /* + * FIXME: The firmware is not being released for now, as it is + * needed around for copying to the IS working memory every + * time before the Cortex-A5 is restarted. + */ + release_firmware(is->fw.f_w); + is->fw.f_w = fw; +done: + mutex_unlock(&is->lock); +} + +static int fimc_is_request_firmware(struct fimc_is *is, const char *fw_name) +{ + return request_firmware_nowait(THIS_MODULE, + FW_ACTION_UEVENT, fw_name, &is->pdev->dev, + GFP_KERNEL, is, fimc_is_load_firmware); +} + +/* General IS interrupt handler */ +static void fimc_is_general_irq_handler(struct fimc_is *is) +{ + is->i2h_cmd.cmd = mcuctl_read(is, MCUCTL_REG_ISSR(10)); + + switch (is->i2h_cmd.cmd) { + case IHC_GET_SENSOR_NUM: + fimc_is_hw_get_params(is, 1); + fimc_is_hw_wait_intmsr0_intmsd0(is); + fimc_is_hw_set_sensor_num(is); + pr_debug("ISP FW version: %#x\n", is->i2h_cmd.args[0]); + break; + case IHC_SET_FACE_MARK: + case IHC_FRAME_DONE: + fimc_is_hw_get_params(is, 2); + break; + case IHC_SET_SHOT_MARK: + case IHC_AA_DONE: + case IH_REPLY_DONE: + fimc_is_hw_get_params(is, 3); + break; + case IH_REPLY_NOT_DONE: + fimc_is_hw_get_params(is, 4); + break; + case IHC_NOT_READY: + break; + default: + pr_info("unknown command: %#x\n", is->i2h_cmd.cmd); + } + + fimc_is_fw_clear_irq1(is, FIMC_IS_INT_GENERAL); + + switch (is->i2h_cmd.cmd) { + case IHC_GET_SENSOR_NUM: + fimc_is_hw_set_intgr0_gd0(is); + set_bit(IS_ST_A5_PWR_ON, &is->state); + break; + + case IHC_SET_SHOT_MARK: + break; + + case IHC_SET_FACE_MARK: + is->fd_header.count = is->i2h_cmd.args[0]; + is->fd_header.index = is->i2h_cmd.args[1]; + is->fd_header.offset = 0; + break; + + case IHC_FRAME_DONE: + break; + + case IHC_AA_DONE: + pr_debug("AA_DONE - %d, %d, %d\n", is->i2h_cmd.args[0], + is->i2h_cmd.args[1], is->i2h_cmd.args[2]); + break; + + case IH_REPLY_DONE: + pr_debug("ISR_DONE: args[0]: %#x\n", is->i2h_cmd.args[0]); + + switch (is->i2h_cmd.args[0]) { + case HIC_PREVIEW_STILL...HIC_CAPTURE_VIDEO: + /* Get CAC margin */ + set_bit(IS_ST_CHANGE_MODE, &is->state); + is->isp.cac_margin_x = is->i2h_cmd.args[1]; + is->isp.cac_margin_y = is->i2h_cmd.args[2]; + pr_debug("CAC margin (x,y): (%d,%d)\n", + is->isp.cac_margin_x, is->isp.cac_margin_y); + break; + + case HIC_STREAM_ON: + clear_bit(IS_ST_STREAM_OFF, &is->state); + set_bit(IS_ST_STREAM_ON, &is->state); + break; + + case HIC_STREAM_OFF: + clear_bit(IS_ST_STREAM_ON, &is->state); + set_bit(IS_ST_STREAM_OFF, &is->state); + break; + + case HIC_SET_PARAMETER: + is->config[is->config_index].p_region_index[0] = 0; + is->config[is->config_index].p_region_index[1] = 0; + set_bit(IS_ST_BLOCK_CMD_CLEARED, &is->state); + pr_debug("HIC_SET_PARAMETER\n"); + break; + + case HIC_GET_PARAMETER: + break; + + case HIC_SET_TUNE: + break; + + case HIC_GET_STATUS: + break; + + case HIC_OPEN_SENSOR: + set_bit(IS_ST_OPEN_SENSOR, &is->state); + pr_debug("data lanes: %d, settle line: %d\n", + is->i2h_cmd.args[2], is->i2h_cmd.args[1]); + break; + + case HIC_CLOSE_SENSOR: + clear_bit(IS_ST_OPEN_SENSOR, &is->state); + is->sensor_index = 0; + break; + + case HIC_MSG_TEST: + pr_debug("config MSG level completed\n"); + break; + + case HIC_POWER_DOWN: + clear_bit(IS_ST_PWR_SUBIP_ON, &is->state); + break; + + case HIC_GET_SET_FILE_ADDR: + is->setfile.base = is->i2h_cmd.args[1]; + set_bit(IS_ST_SETFILE_LOADED, &is->state); + break; + + case HIC_LOAD_SET_FILE: + set_bit(IS_ST_SETFILE_LOADED, &is->state); + break; + } + break; + + case IH_REPLY_NOT_DONE: + pr_err("ISR_NDONE: %d: %#x, %s\n", is->i2h_cmd.args[0], + is->i2h_cmd.args[1], + fimc_is_strerr(is->i2h_cmd.args[1])); + + if (is->i2h_cmd.args[1] & IS_ERROR_TIME_OUT_FLAG) + pr_err("IS_ERROR_TIME_OUT\n"); + + switch (is->i2h_cmd.args[1]) { + case IS_ERROR_SET_PARAMETER: + fimc_is_mem_barrier(); + } + + switch (is->i2h_cmd.args[0]) { + case HIC_SET_PARAMETER: + is->config[is->config_index].p_region_index[0] = 0; + is->config[is->config_index].p_region_index[1] = 0; + set_bit(IS_ST_BLOCK_CMD_CLEARED, &is->state); + break; + } + break; + + case IHC_NOT_READY: + pr_err("IS control sequence error: Not Ready\n"); + break; + } + + wake_up(&is->irq_queue); +} + +static irqreturn_t fimc_is_irq_handler(int irq, void *priv) +{ + struct fimc_is *is = priv; + unsigned long flags; + u32 status; + + spin_lock_irqsave(&is->slock, flags); + status = mcuctl_read(is, MCUCTL_REG_INTSR1); + + if (status & (1UL << FIMC_IS_INT_GENERAL)) + fimc_is_general_irq_handler(is); + + if (status & (1UL << FIMC_IS_INT_FRAME_DONE_ISP)) + fimc_isp_irq_handler(is); + + spin_unlock_irqrestore(&is->slock, flags); + return IRQ_HANDLED; +} + +static int fimc_is_hw_open_sensor(struct fimc_is *is, + struct fimc_is_sensor *sensor) +{ + struct sensor_open_extended *soe = (void *)&is->is_p_region->shared; + + fimc_is_hw_wait_intmsr0_intmsd0(is); + + soe->self_calibration_mode = 1; + soe->actuator_type = 0; + soe->mipi_lane_num = 0; + soe->mclk = 0; + soe->mipi_speed = 0; + soe->fast_open_sensor = 0; + soe->i2c_sclk = 88000000; + + fimc_is_mem_barrier(); + + /* + * Some user space use cases hang up here without this + * empirically chosen delay. + */ + udelay(100); + + mcuctl_write(HIC_OPEN_SENSOR, is, MCUCTL_REG_ISSR(0)); + mcuctl_write(is->sensor_index, is, MCUCTL_REG_ISSR(1)); + mcuctl_write(sensor->drvdata->id, is, MCUCTL_REG_ISSR(2)); + mcuctl_write(sensor->i2c_bus, is, MCUCTL_REG_ISSR(3)); + mcuctl_write(is->is_dma_p_region, is, MCUCTL_REG_ISSR(4)); + + fimc_is_hw_set_intgr0_gd0(is); + + return fimc_is_wait_event(is, IS_ST_OPEN_SENSOR, 1, + sensor->drvdata->open_timeout); +} + + +int fimc_is_hw_initialize(struct fimc_is *is) +{ + static const int config_ids[] = { + IS_SC_PREVIEW_STILL, IS_SC_PREVIEW_VIDEO, + IS_SC_CAPTURE_STILL, IS_SC_CAPTURE_VIDEO + }; + struct device *dev = &is->pdev->dev; + u32 prev_id; + int i, ret; + + /* Sensor initialization. Only one sensor is currently supported. */ + ret = fimc_is_hw_open_sensor(is, &is->sensor[0]); + if (ret < 0) + return ret; + + /* Get the setfile address. */ + fimc_is_hw_get_setfile_addr(is); + + ret = fimc_is_wait_event(is, IS_ST_SETFILE_LOADED, 1, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) { + dev_err(dev, "get setfile address timed out\n"); + return ret; + } + pr_debug("setfile.base: %#x\n", is->setfile.base); + + /* Load the setfile. */ + fimc_is_load_setfile(is, FIMC_IS_SETFILE_6A3); + clear_bit(IS_ST_SETFILE_LOADED, &is->state); + fimc_is_hw_load_setfile(is); + ret = fimc_is_wait_event(is, IS_ST_SETFILE_LOADED, 1, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) { + dev_err(dev, "loading setfile timed out\n"); + return ret; + } + + pr_debug("setfile: base: %#x, size: %d\n", + is->setfile.base, is->setfile.size); + pr_info("FIMC-IS Setfile info: %s\n", is->fw.setfile_info); + + /* Check magic number. */ + if (is->is_p_region->shared[MAX_SHARED_COUNT - 1] != + FIMC_IS_MAGIC_NUMBER) { + dev_err(dev, "magic number error!\n"); + return -EIO; + } + + pr_debug("shared region: %pad, parameter region: %pad\n", + &is->memory.addr + FIMC_IS_SHARED_REGION_OFFSET, + &is->is_dma_p_region); + + is->setfile.sub_index = 0; + + /* Stream off. */ + fimc_is_hw_stream_off(is); + ret = fimc_is_wait_event(is, IS_ST_STREAM_OFF, 1, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) { + dev_err(dev, "stream off timeout\n"); + return ret; + } + + /* Preserve previous mode. */ + prev_id = is->config_index; + + /* Set initial parameter values. */ + for (i = 0; i < ARRAY_SIZE(config_ids); i++) { + is->config_index = config_ids[i]; + fimc_is_set_initial_params(is); + ret = fimc_is_itf_s_param(is, true); + if (ret < 0) { + is->config_index = prev_id; + return ret; + } + } + is->config_index = prev_id; + + set_bit(IS_ST_INIT_DONE, &is->state); + dev_info(dev, "initialization sequence completed (%d)\n", + is->config_index); + return 0; +} + +static int fimc_is_show(struct seq_file *s, void *data) +{ + struct fimc_is *is = s->private; + const u8 *buf = is->memory.vaddr + FIMC_IS_DEBUG_REGION_OFFSET; + + if (is->memory.vaddr == NULL) { + dev_err(&is->pdev->dev, "firmware memory is not initialized\n"); + return -EIO; + } + + seq_printf(s, "%s\n", buf); + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(fimc_is); + +static void fimc_is_debugfs_remove(struct fimc_is *is) +{ + debugfs_remove_recursive(is->debugfs_entry); + is->debugfs_entry = NULL; +} + +static void fimc_is_debugfs_create(struct fimc_is *is) +{ + is->debugfs_entry = debugfs_create_dir("fimc_is", NULL); + + debugfs_create_file("fw_log", S_IRUGO, is->debugfs_entry, is, + &fimc_is_fops); +} + +static int fimc_is_runtime_resume(struct device *dev); +static int fimc_is_runtime_suspend(struct device *dev); + +static int fimc_is_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fimc_is *is; + struct resource res; + struct device_node *node; + int ret; + + is = devm_kzalloc(&pdev->dev, sizeof(*is), GFP_KERNEL); + if (!is) + return -ENOMEM; + + is->pdev = pdev; + is->isp.pdev = pdev; + + init_waitqueue_head(&is->irq_queue); + spin_lock_init(&is->slock); + mutex_init(&is->lock); + + ret = of_address_to_resource(dev->of_node, 0, &res); + if (ret < 0) + return ret; + + is->regs = devm_ioremap_resource(dev, &res); + if (IS_ERR(is->regs)) + return PTR_ERR(is->regs); + + node = of_get_child_by_name(dev->of_node, "pmu"); + if (!node) + return -ENODEV; + + is->pmu_regs = of_iomap(node, 0); + of_node_put(node); + if (!is->pmu_regs) + return -ENOMEM; + + is->irq = irq_of_parse_and_map(dev->of_node, 0); + if (!is->irq) { + dev_err(dev, "no irq found\n"); + ret = -EINVAL; + goto err_iounmap; + } + + ret = fimc_is_get_clocks(is); + if (ret < 0) + goto err_iounmap; + + platform_set_drvdata(pdev, is); + + ret = request_irq(is->irq, fimc_is_irq_handler, 0, dev_name(dev), is); + if (ret < 0) { + dev_err(dev, "irq request failed\n"); + goto err_clk; + } + pm_runtime_enable(dev); + + if (!pm_runtime_enabled(dev)) { + ret = fimc_is_runtime_resume(dev); + if (ret < 0) + goto err_irq; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto err_pm_disable; + + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); + + ret = devm_of_platform_populate(dev); + if (ret < 0) + goto err_pm; + + /* + * Register FIMC-IS V4L2 subdevs to this driver. The video nodes + * will be created within the subdev's registered() callback. + */ + ret = fimc_is_register_subdevs(is); + if (ret < 0) + goto err_pm; + + fimc_is_debugfs_create(is); + + ret = fimc_is_request_firmware(is, FIMC_IS_FW_FILENAME); + if (ret < 0) + goto err_dfs; + + pm_runtime_put_sync(dev); + + dev_dbg(dev, "FIMC-IS registered successfully\n"); + return 0; + +err_dfs: + fimc_is_debugfs_remove(is); + fimc_is_unregister_subdevs(is); +err_pm: + pm_runtime_put_noidle(dev); + if (!pm_runtime_enabled(dev)) + fimc_is_runtime_suspend(dev); +err_pm_disable: + pm_runtime_disable(dev); +err_irq: + free_irq(is->irq, is); +err_clk: + fimc_is_put_clocks(is); +err_iounmap: + iounmap(is->pmu_regs); + return ret; +} + +static int fimc_is_runtime_resume(struct device *dev) +{ + struct fimc_is *is = dev_get_drvdata(dev); + int ret; + + ret = fimc_is_setup_clocks(is); + if (ret) + return ret; + + return fimc_is_enable_clocks(is); +} + +static int fimc_is_runtime_suspend(struct device *dev) +{ + struct fimc_is *is = dev_get_drvdata(dev); + + fimc_is_disable_clocks(is); + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int fimc_is_resume(struct device *dev) +{ + /* TODO: */ + return 0; +} + +static int fimc_is_suspend(struct device *dev) +{ + struct fimc_is *is = dev_get_drvdata(dev); + + /* TODO: */ + if (test_bit(IS_ST_A5_PWR_ON, &is->state)) + return -EBUSY; + + return 0; +} +#endif /* CONFIG_PM_SLEEP */ + +static int fimc_is_remove(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct fimc_is *is = dev_get_drvdata(dev); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + if (!pm_runtime_status_suspended(dev)) + fimc_is_runtime_suspend(dev); + free_irq(is->irq, is); + fimc_is_unregister_subdevs(is); + vb2_dma_contig_clear_max_seg_size(dev); + fimc_is_put_clocks(is); + iounmap(is->pmu_regs); + fimc_is_debugfs_remove(is); + release_firmware(is->fw.f_w); + fimc_is_free_cpu_memory(is); + + return 0; +} + +static const struct of_device_id fimc_is_of_match[] = { + { .compatible = "samsung,exynos4212-fimc-is" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, fimc_is_of_match); + +static const struct dev_pm_ops fimc_is_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fimc_is_suspend, fimc_is_resume) + SET_RUNTIME_PM_OPS(fimc_is_runtime_suspend, fimc_is_runtime_resume, + NULL) +}; + +static struct platform_driver fimc_is_driver = { + .probe = fimc_is_probe, + .remove = fimc_is_remove, + .driver = { + .of_match_table = fimc_is_of_match, + .name = FIMC_IS_DRV_NAME, + .pm = &fimc_is_pm_ops, + } +}; + +static int fimc_is_module_init(void) +{ + int ret; + + ret = fimc_is_register_i2c_driver(); + if (ret < 0) + return ret; + + ret = platform_driver_register(&fimc_is_driver); + + if (ret < 0) + fimc_is_unregister_i2c_driver(); + + return ret; +} + +static void fimc_is_module_exit(void) +{ + fimc_is_unregister_i2c_driver(); + platform_driver_unregister(&fimc_is_driver); +} + +module_init(fimc_is_module_init); +module_exit(fimc_is_module_exit); + +MODULE_ALIAS("platform:" FIMC_IS_DRV_NAME); +MODULE_AUTHOR("Younghwan Joo "); +MODULE_AUTHOR("Sylwester Nawrocki "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is.h b/drivers/media/platform/samsung/exynos4-is/fimc-is.h new file mode 100644 index 000000000..06586e455 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is.h @@ -0,0 +1,359 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Authors: Younghwan Joo + * Sylwester Nawrocki + */ +#ifndef FIMC_IS_H_ +#define FIMC_IS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fimc-isp.h" +#include "fimc-is-command.h" +#include "fimc-is-sensor.h" +#include "fimc-is-param.h" +#include "fimc-is-regs.h" + +#define FIMC_IS_DRV_NAME "exynos4-fimc-is" + +#define FIMC_IS_FW_FILENAME "exynos4_fimc_is_fw.bin" +#define FIMC_IS_SETFILE_6A3 "exynos4_s5k6a3_setfile.bin" + +#define FIMC_IS_FW_LOAD_TIMEOUT 1000 /* ms */ +#define FIMC_IS_POWER_ON_TIMEOUT 1000 /* us */ + +#define FIMC_IS_SENSORS_NUM 2 + +/* Memory definitions */ +#define FIMC_IS_CPU_MEM_SIZE (0xa00000) +#define FIMC_IS_CPU_BASE_MASK ((1 << 26) - 1) +#define FIMC_IS_REGION_SIZE 0x5000 + +#define FIMC_IS_DEBUG_REGION_OFFSET 0x0084b000 +#define FIMC_IS_SHARED_REGION_OFFSET 0x008c0000 +#define FIMC_IS_FW_INFO_LEN 31 +#define FIMC_IS_FW_VER_LEN 7 +#define FIMC_IS_FW_DESC_LEN (FIMC_IS_FW_INFO_LEN + \ + FIMC_IS_FW_VER_LEN) +#define FIMC_IS_SETFILE_INFO_LEN 39 + +#define FIMC_IS_EXTRA_MEM_SIZE (FIMC_IS_EXTRA_FW_SIZE + \ + FIMC_IS_EXTRA_SETFILE_SIZE + 0x1000) +#define FIMC_IS_EXTRA_FW_SIZE 0x180000 +#define FIMC_IS_EXTRA_SETFILE_SIZE 0x4b000 + +/* TODO: revisit */ +#define FIMC_IS_FW_ADDR_MASK ((1 << 26) - 1) +#define FIMC_IS_FW_SIZE_MAX (SZ_4M) +#define FIMC_IS_FW_SIZE_MIN (SZ_32K) + +#define ATCLK_MCUISP_FREQUENCY 100000000UL +#define ACLK_AXI_FREQUENCY 100000000UL + +enum { + ISS_CLK_PPMUISPX, + ISS_CLK_PPMUISPMX, + ISS_CLK_LITE0, + ISS_CLK_LITE1, + ISS_CLK_MPLL, + ISS_CLK_ISP, + ISS_CLK_DRC, + ISS_CLK_FD, + ISS_CLK_MCUISP, + ISS_CLK_GICISP, + ISS_CLK_PWM_ISP, + ISS_CLK_MCUCTL_ISP, + ISS_CLK_UART, + ISS_GATE_CLKS_MAX, + ISS_CLK_ISP_DIV0 = ISS_GATE_CLKS_MAX, + ISS_CLK_ISP_DIV1, + ISS_CLK_MCUISP_DIV0, + ISS_CLK_MCUISP_DIV1, + ISS_CLK_ACLK200, + ISS_CLK_ACLK200_DIV, + ISS_CLK_ACLK400MCUISP, + ISS_CLK_ACLK400MCUISP_DIV, + ISS_CLKS_MAX +}; + +/* The driver's internal state flags */ +enum { + IS_ST_IDLE, + IS_ST_PWR_ON, + IS_ST_A5_PWR_ON, + IS_ST_FW_LOADED, + IS_ST_OPEN_SENSOR, + IS_ST_SETFILE_LOADED, + IS_ST_INIT_DONE, + IS_ST_STREAM_ON, + IS_ST_STREAM_OFF, + IS_ST_CHANGE_MODE, + IS_ST_BLOCK_CMD_CLEARED, + IS_ST_SET_ZOOM, + IS_ST_PWR_SUBIP_ON, + IS_ST_END, +}; + +enum af_state { + FIMC_IS_AF_IDLE = 0, + FIMC_IS_AF_SETCONFIG = 1, + FIMC_IS_AF_RUNNING = 2, + FIMC_IS_AF_LOCK = 3, + FIMC_IS_AF_ABORT = 4, + FIMC_IS_AF_FAILED = 5, +}; + +enum af_lock_state { + FIMC_IS_AF_UNLOCKED = 0, + FIMC_IS_AF_LOCKED = 2 +}; + +enum ae_lock_state { + FIMC_IS_AE_UNLOCKED = 0, + FIMC_IS_AE_LOCKED = 1 +}; + +enum awb_lock_state { + FIMC_IS_AWB_UNLOCKED = 0, + FIMC_IS_AWB_LOCKED = 1 +}; + +enum { + IS_METERING_CONFIG_CMD, + IS_METERING_CONFIG_WIN_POS_X, + IS_METERING_CONFIG_WIN_POS_Y, + IS_METERING_CONFIG_WIN_WIDTH, + IS_METERING_CONFIG_WIN_HEIGHT, + IS_METERING_CONFIG_MAX +}; + +struct is_setfile { + const struct firmware *info; + int state; + u32 sub_index; + u32 base; + size_t size; +}; + +struct is_fd_result_header { + u32 offset; + u32 count; + u32 index; + u32 curr_index; + u32 width; + u32 height; +}; + +struct is_af_info { + u16 mode; + u32 af_state; + u32 af_lock_state; + u32 ae_lock_state; + u32 awb_lock_state; + u16 pos_x; + u16 pos_y; + u16 prev_pos_x; + u16 prev_pos_y; + u16 use_af; +}; + +struct fimc_is_firmware { + const struct firmware *f_w; + + dma_addr_t addr; + void *vaddr; + unsigned int size; + + char info[FIMC_IS_FW_INFO_LEN + 1]; + char version[FIMC_IS_FW_VER_LEN + 1]; + char setfile_info[FIMC_IS_SETFILE_INFO_LEN + 1]; + u8 state; +}; + +struct fimc_is_memory { + /* DMA base address */ + dma_addr_t addr; + /* virtual base address */ + void *vaddr; + /* total length */ + unsigned int size; +}; + +#define FIMC_IS_I2H_MAX_ARGS 12 + +struct i2h_cmd { + u32 cmd; + u32 sensor_id; + u16 num_args; + u32 args[FIMC_IS_I2H_MAX_ARGS]; +}; + +struct h2i_cmd { + u16 cmd_type; + u32 entry_id; +}; + +#define FIMC_IS_DEBUG_MSG 0x3f +#define FIMC_IS_DEBUG_LEVEL 3 + +struct fimc_is_setfile { + const struct firmware *info; + unsigned int state; + unsigned int size; + u32 sub_index; + u32 base; +}; + +struct chain_config { + struct global_param global; + struct sensor_param sensor; + struct isp_param isp; + struct drc_param drc; + struct fd_param fd; + + unsigned long p_region_index[2]; +}; + +/** + * struct fimc_is - fimc-is data structure + * @pdev: pointer to FIMC-IS platform device + * @pctrl: pointer to pinctrl structure for this device + * @v4l2_dev: pointer to the top level v4l2_device + * @fw: data structure describing the FIMC-IS firmware binary + * @memory: memory region assigned for the FIMC-IS (firmware) + * @isp: the ISP block data structure + * @sensor: fimc-is sensor subdevice array + * @setfile: descriptor of the imaging pipeline calibration data + * @ctrl_handler: the v4l2 controls handler + * @lock: mutex serializing video device and the subdev operations + * @slock: spinlock protecting this data structure and the hw registers + * @clocks: FIMC-LITE gate clock + * @regs: MCUCTL mmapped registers region + * @pmu_regs: PMU ISP mmapped registers region + * @irq: FIMC-IS interrupt + * @irq_queue: interrupt handling waitqueue + * @lpm: low power mode flag + * @state: internal driver's state flags + * @sensor_index: image sensor index for the firmware + * @i2h_cmd: FIMC-IS to the host (CPU) mailbox command data structure + * @h2i_cmd: the host (CPU) to FIMC-IS mailbox command data structure + * @fd_header: the face detection result data structure + * @config: shared HW pipeline configuration data + * @config_index: index to the @config entry currently in use + * @is_p_region: pointer to the shared parameter memory region + * @is_dma_p_region: DMA address of the shared parameter memory region + * @is_shared_region: pointer to the IS shared region data structure + * @af: auto focus data + * @debugfs_entry: debugfs entry for the firmware log + */ +struct fimc_is { + struct platform_device *pdev; + struct pinctrl *pctrl; + struct v4l2_device *v4l2_dev; + + struct fimc_is_firmware fw; + struct fimc_is_memory memory; + + struct fimc_isp isp; + struct fimc_is_sensor sensor[FIMC_IS_SENSORS_NUM]; + struct fimc_is_setfile setfile; + + struct v4l2_ctrl_handler ctrl_handler; + + struct mutex lock; + spinlock_t slock; + + struct clk *clocks[ISS_CLKS_MAX]; + void __iomem *regs; + void __iomem *pmu_regs; + int irq; + wait_queue_head_t irq_queue; + u8 lpm; + + unsigned long state; + unsigned int sensor_index; + + struct i2h_cmd i2h_cmd; + struct h2i_cmd h2i_cmd; + struct is_fd_result_header fd_header; + + struct chain_config config[IS_SC_MAX]; + unsigned config_index; + + struct is_region *is_p_region; + dma_addr_t is_dma_p_region; + struct is_share_region *is_shared_region; + struct is_af_info af; + + struct dentry *debugfs_entry; +}; + +static inline struct fimc_is *fimc_isp_to_is(struct fimc_isp *isp) +{ + return container_of(isp, struct fimc_is, isp); +} + +static inline struct chain_config *__get_curr_is_config(struct fimc_is *is) +{ + return &is->config[is->config_index]; +} + +static inline void fimc_is_mem_barrier(void) +{ + mb(); +} + +static inline void fimc_is_set_param_bit(struct fimc_is *is, int num) +{ + struct chain_config *cfg = &is->config[is->config_index]; + + set_bit(num, &cfg->p_region_index[0]); +} + +static inline void fimc_is_set_param_ctrl_cmd(struct fimc_is *is, int cmd) +{ + is->is_p_region->parameter.isp.control.cmd = cmd; +} + +static inline void mcuctl_write(u32 v, struct fimc_is *is, unsigned int offset) +{ + writel(v, is->regs + offset); +} + +static inline u32 mcuctl_read(struct fimc_is *is, unsigned int offset) +{ + return readl(is->regs + offset); +} + +static inline void pmuisp_write(u32 v, struct fimc_is *is, unsigned int offset) +{ + writel(v, is->pmu_regs + offset); +} + +static inline u32 pmuisp_read(struct fimc_is *is, unsigned int offset) +{ + return readl(is->pmu_regs + offset); +} + +int fimc_is_wait_event(struct fimc_is *is, unsigned long bit, + unsigned int state, unsigned int timeout); +int fimc_is_cpu_set_power(struct fimc_is *is, int on); +int fimc_is_start_firmware(struct fimc_is *is); +int fimc_is_hw_initialize(struct fimc_is *is); +void fimc_is_log_dump(const char *level, const void *buf, size_t len); + +#endif /* FIMC_IS_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.c b/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.c new file mode 100644 index 000000000..f6a302fa8 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.c @@ -0,0 +1,655 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * FIMC-IS ISP video input and video output DMA interface driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + * + * The hardware handling code derived from a driver written by + * Younghwan Joo . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common.h" +#include "media-dev.h" +#include "fimc-is.h" +#include "fimc-isp-video.h" +#include "fimc-is-param.h" + +static int isp_video_capture_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct fimc_isp *isp = vb2_get_drv_priv(vq); + struct v4l2_pix_format_mplane *vid_fmt = &isp->video_capture.pixfmt; + const struct fimc_fmt *fmt = isp->video_capture.format; + unsigned int wh, i; + + wh = vid_fmt->width * vid_fmt->height; + + if (fmt == NULL) + return -EINVAL; + + *num_buffers = clamp_t(u32, *num_buffers, FIMC_ISP_REQ_BUFS_MIN, + FIMC_ISP_REQ_BUFS_MAX); + if (*num_planes) { + if (*num_planes != fmt->memplanes) + return -EINVAL; + for (i = 0; i < *num_planes; i++) + if (sizes[i] < (wh * fmt->depth[i]) / 8) + return -EINVAL; + return 0; + } + + *num_planes = fmt->memplanes; + + for (i = 0; i < fmt->memplanes; i++) + sizes[i] = (wh * fmt->depth[i]) / 8; + + return 0; +} + +static inline struct param_dma_output *__get_isp_dma2(struct fimc_is *is) +{ + return &__get_curr_is_config(is)->isp.dma2_output; +} + +static int isp_video_capture_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct fimc_isp *isp = vb2_get_drv_priv(q); + struct fimc_is *is = fimc_isp_to_is(isp); + struct param_dma_output *dma = __get_isp_dma2(is); + struct fimc_is_video *video = &isp->video_capture; + int ret; + + if (!test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state) || + test_bit(ST_ISP_VID_CAP_STREAMING, &isp->state)) + return 0; + + + dma->cmd = DMA_OUTPUT_COMMAND_ENABLE; + dma->notify_dma_done = DMA_OUTPUT_NOTIFY_DMA_DONE_ENABLE; + dma->buffer_address = is->is_dma_p_region + + DMA2_OUTPUT_ADDR_ARRAY_OFFS; + dma->buffer_number = video->reqbufs_count; + dma->dma_out_mask = video->buf_mask; + + isp_dbg(2, &video->ve.vdev, + "buf_count: %d, planes: %d, dma addr table: %#x\n", + video->buf_count, video->format->memplanes, + dma->buffer_address); + + fimc_is_mem_barrier(); + + fimc_is_set_param_bit(is, PARAM_ISP_DMA2_OUTPUT); + __fimc_is_hw_update_param(is, PARAM_ISP_DMA2_OUTPUT); + + ret = fimc_is_itf_s_param(is, false); + if (ret < 0) + return ret; + + ret = fimc_pipeline_call(&video->ve, set_stream, 1); + if (ret < 0) + return ret; + + set_bit(ST_ISP_VID_CAP_STREAMING, &isp->state); + return ret; +} + +static void isp_video_capture_stop_streaming(struct vb2_queue *q) +{ + struct fimc_isp *isp = vb2_get_drv_priv(q); + struct fimc_is *is = fimc_isp_to_is(isp); + struct param_dma_output *dma = __get_isp_dma2(is); + int ret; + + ret = fimc_pipeline_call(&isp->video_capture.ve, set_stream, 0); + if (ret < 0) + return; + + dma->cmd = DMA_OUTPUT_COMMAND_DISABLE; + dma->notify_dma_done = DMA_OUTPUT_NOTIFY_DMA_DONE_DISABLE; + dma->buffer_number = 0; + dma->buffer_address = 0; + dma->dma_out_mask = 0; + + fimc_is_set_param_bit(is, PARAM_ISP_DMA2_OUTPUT); + __fimc_is_hw_update_param(is, PARAM_ISP_DMA2_OUTPUT); + + ret = fimc_is_itf_s_param(is, false); + if (ret < 0) + dev_warn(&is->pdev->dev, "%s: DMA stop failed\n", __func__); + + fimc_is_hw_set_isp_buf_mask(is, 0); + + clear_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state); + clear_bit(ST_ISP_VID_CAP_STREAMING, &isp->state); + + isp->video_capture.buf_count = 0; +} + +static int isp_video_capture_buffer_prepare(struct vb2_buffer *vb) +{ + struct fimc_isp *isp = vb2_get_drv_priv(vb->vb2_queue); + struct fimc_is_video *video = &isp->video_capture; + int i; + + if (video->format == NULL) + return -EINVAL; + + for (i = 0; i < video->format->memplanes; i++) { + unsigned long size = video->pixfmt.plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + v4l2_err(&video->ve.vdev, + "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + vb2_set_plane_payload(vb, i, size); + } + + /* Check if we get one of the already known buffers. */ + if (test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state)) { + dma_addr_t dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); + int i; + + for (i = 0; i < video->buf_count; i++) + if (video->buffers[i]->dma_addr[0] == dma_addr) + return 0; + return -ENXIO; + } + + return 0; +} + +static void isp_video_capture_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct fimc_isp *isp = vb2_get_drv_priv(vb->vb2_queue); + struct fimc_is_video *video = &isp->video_capture; + struct fimc_is *is = fimc_isp_to_is(isp); + struct isp_video_buf *ivb = to_isp_video_buf(vbuf); + unsigned long flags; + unsigned int i; + + if (test_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state)) { + spin_lock_irqsave(&is->slock, flags); + video->buf_mask |= BIT(ivb->index); + spin_unlock_irqrestore(&is->slock, flags); + } else { + unsigned int num_planes = video->format->memplanes; + + ivb->index = video->buf_count; + video->buffers[ivb->index] = ivb; + + for (i = 0; i < num_planes; i++) { + int buf_index = ivb->index * num_planes + i; + + ivb->dma_addr[i] = vb2_dma_contig_plane_dma_addr(vb, i); + is->is_p_region->shared[32 + buf_index] = + ivb->dma_addr[i]; + + isp_dbg(2, &video->ve.vdev, + "dma_buf %d (%d/%d/%d) addr: %pad\n", + buf_index, ivb->index, i, vb->index, + &ivb->dma_addr[i]); + } + + if (++video->buf_count < video->reqbufs_count) + return; + + video->buf_mask = (1UL << video->buf_count) - 1; + set_bit(ST_ISP_VID_CAP_BUF_PREP, &isp->state); + } + + if (!test_bit(ST_ISP_VID_CAP_STREAMING, &isp->state)) + isp_video_capture_start_streaming(vb->vb2_queue, 0); +} + +/* + * FIMC-IS ISP input and output DMA interface interrupt handler. + * Locking: called with is->slock spinlock held. + */ +void fimc_isp_video_irq_handler(struct fimc_is *is) +{ + struct fimc_is_video *video = &is->isp.video_capture; + struct vb2_v4l2_buffer *vbuf; + int buf_index; + + /* TODO: Ensure the DMA is really stopped in stop_streaming callback */ + if (!test_bit(ST_ISP_VID_CAP_STREAMING, &is->isp.state)) + return; + + buf_index = (is->i2h_cmd.args[1] - 1) % video->buf_count; + vbuf = &video->buffers[buf_index]->vb; + + vbuf->vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); + + video->buf_mask &= ~BIT(buf_index); + fimc_is_hw_set_isp_buf_mask(is, video->buf_mask); +} + +static const struct vb2_ops isp_video_capture_qops = { + .queue_setup = isp_video_capture_queue_setup, + .buf_prepare = isp_video_capture_buffer_prepare, + .buf_queue = isp_video_capture_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = isp_video_capture_start_streaming, + .stop_streaming = isp_video_capture_stop_streaming, +}; + +static int isp_video_open(struct file *file) +{ + struct fimc_isp *isp = video_drvdata(file); + struct exynos_video_entity *ve = &isp->video_capture.ve; + struct media_entity *me = &ve->vdev.entity; + int ret; + + if (mutex_lock_interruptible(&isp->video_lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + ret = pm_runtime_resume_and_get(&isp->pdev->dev); + if (ret < 0) + goto rel_fh; + + if (v4l2_fh_is_singular_file(file)) { + mutex_lock(&me->graph_obj.mdev->graph_mutex); + + ret = fimc_pipeline_call(ve, open, me, true); + + /* Mark the video pipeline as in use. */ + if (ret == 0) + me->use_count++; + + mutex_unlock(&me->graph_obj.mdev->graph_mutex); + } + if (!ret) + goto unlock; +rel_fh: + v4l2_fh_release(file); +unlock: + mutex_unlock(&isp->video_lock); + return ret; +} + +static int isp_video_release(struct file *file) +{ + struct fimc_isp *isp = video_drvdata(file); + struct fimc_is_video *ivc = &isp->video_capture; + struct media_entity *entity = &ivc->ve.vdev.entity; + struct media_device *mdev = entity->graph_obj.mdev; + bool is_singular_file; + + mutex_lock(&isp->video_lock); + + is_singular_file = v4l2_fh_is_singular_file(file); + + if (is_singular_file && ivc->streaming) { + video_device_pipeline_stop(&ivc->ve.vdev); + ivc->streaming = 0; + } + + _vb2_fop_release(file, NULL); + + if (is_singular_file) { + fimc_pipeline_call(&ivc->ve, close); + + mutex_lock(&mdev->graph_mutex); + entity->use_count--; + mutex_unlock(&mdev->graph_mutex); + } + + pm_runtime_put(&isp->pdev->dev); + mutex_unlock(&isp->video_lock); + + return 0; +} + +static const struct v4l2_file_operations isp_video_fops = { + .owner = THIS_MODULE, + .open = isp_video_open, + .release = isp_video_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* + * Video node ioctl operations + */ +static int isp_video_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct fimc_isp *isp = video_drvdata(file); + + __fimc_vidioc_querycap(&isp->pdev->dev, cap); + return 0; +} + +static int isp_video_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct fimc_fmt *fmt; + + if (f->index >= FIMC_ISP_NUM_FORMATS) + return -EINVAL; + + fmt = fimc_isp_find_format(NULL, NULL, f->index); + if (WARN_ON(fmt == NULL)) + return -EINVAL; + + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int isp_video_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_isp *isp = video_drvdata(file); + + f->fmt.pix_mp = isp->video_capture.pixfmt; + return 0; +} + +static void __isp_video_try_fmt(struct fimc_isp *isp, + struct v4l2_pix_format_mplane *pixm, + const struct fimc_fmt **fmt) +{ + const struct fimc_fmt *__fmt; + + __fmt = fimc_isp_find_format(&pixm->pixelformat, NULL, 2); + + if (fmt) + *fmt = __fmt; + + pixm->colorspace = V4L2_COLORSPACE_SRGB; + pixm->field = V4L2_FIELD_NONE; + pixm->num_planes = __fmt->memplanes; + pixm->pixelformat = __fmt->fourcc; + /* + * TODO: double check with the docmentation these width/height + * constraints are correct. + */ + v4l_bound_align_image(&pixm->width, FIMC_ISP_SOURCE_WIDTH_MIN, + FIMC_ISP_SOURCE_WIDTH_MAX, 3, + &pixm->height, FIMC_ISP_SOURCE_HEIGHT_MIN, + FIMC_ISP_SOURCE_HEIGHT_MAX, 0, 0); +} + +static int isp_video_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_isp *isp = video_drvdata(file); + + __isp_video_try_fmt(isp, &f->fmt.pix_mp, NULL); + return 0; +} + +static int isp_video_s_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct fimc_isp *isp = video_drvdata(file); + struct fimc_is *is = fimc_isp_to_is(isp); + struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp; + const struct fimc_fmt *ifmt = NULL; + struct param_dma_output *dma = __get_isp_dma2(is); + + __isp_video_try_fmt(isp, pixm, &ifmt); + + if (WARN_ON(ifmt == NULL)) + return -EINVAL; + + dma->format = DMA_OUTPUT_FORMAT_BAYER; + dma->order = DMA_OUTPUT_ORDER_GB_BG; + dma->plane = ifmt->memplanes; + dma->bitwidth = ifmt->depth[0]; + dma->width = pixm->width; + dma->height = pixm->height; + + fimc_is_mem_barrier(); + + isp->video_capture.format = ifmt; + isp->video_capture.pixfmt = *pixm; + + return 0; +} + +/* + * Check for source/sink format differences at each link. + * Return 0 if the formats match or -EPIPE otherwise. + */ +static int isp_video_pipeline_validate(struct fimc_isp *isp) +{ + struct v4l2_subdev *sd = &isp->subdev; + struct v4l2_subdev_format sink_fmt, src_fmt; + struct media_pad *pad; + int ret; + + while (1) { + /* Retrieve format at the sink pad */ + pad = &sd->entity.pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + sink_fmt.pad = pad->index; + sink_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sink_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + /* Retrieve format at the source pad */ + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + sd = media_entity_to_v4l2_subdev(pad->entity); + src_fmt.pad = pad->index; + src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &src_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + if (src_fmt.format.width != sink_fmt.format.width || + src_fmt.format.height != sink_fmt.format.height || + src_fmt.format.code != sink_fmt.format.code) + return -EPIPE; + } + + return 0; +} + +static int isp_video_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_isp *isp = video_drvdata(file); + struct exynos_video_entity *ve = &isp->video_capture.ve; + int ret; + + ret = video_device_pipeline_start(&ve->vdev, &ve->pipe->mp); + if (ret < 0) + return ret; + + ret = isp_video_pipeline_validate(isp); + if (ret < 0) + goto p_stop; + + ret = vb2_ioctl_streamon(file, priv, type); + if (ret < 0) + goto p_stop; + + isp->video_capture.streaming = 1; + return 0; +p_stop: + video_device_pipeline_stop(&ve->vdev); + return ret; +} + +static int isp_video_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_isp *isp = video_drvdata(file); + struct fimc_is_video *video = &isp->video_capture; + int ret; + + ret = vb2_ioctl_streamoff(file, priv, type); + if (ret < 0) + return ret; + + video_device_pipeline_stop(&video->ve.vdev); + video->streaming = 0; + return 0; +} + +static int isp_video_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct fimc_isp *isp = video_drvdata(file); + int ret; + + ret = vb2_ioctl_reqbufs(file, priv, rb); + if (ret < 0) + return ret; + + if (rb->count && rb->count < FIMC_ISP_REQ_BUFS_MIN) { + rb->count = 0; + vb2_ioctl_reqbufs(file, priv, rb); + ret = -ENOMEM; + } + + isp->video_capture.reqbufs_count = rb->count; + return ret; +} + +static const struct v4l2_ioctl_ops isp_video_ioctl_ops = { + .vidioc_querycap = isp_video_querycap, + .vidioc_enum_fmt_vid_cap = isp_video_enum_fmt, + .vidioc_try_fmt_vid_cap_mplane = isp_video_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = isp_video_s_fmt_mplane, + .vidioc_g_fmt_vid_cap_mplane = isp_video_g_fmt_mplane, + .vidioc_reqbufs = isp_video_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = isp_video_streamon, + .vidioc_streamoff = isp_video_streamoff, +}; + +int fimc_isp_video_device_register(struct fimc_isp *isp, + struct v4l2_device *v4l2_dev, + enum v4l2_buf_type type) +{ + struct vb2_queue *q = &isp->video_capture.vb_queue; + struct fimc_is_video *iv; + struct video_device *vdev; + int ret; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + iv = &isp->video_capture; + else + return -ENOSYS; + + mutex_init(&isp->video_lock); + INIT_LIST_HEAD(&iv->pending_buf_q); + INIT_LIST_HEAD(&iv->active_buf_q); + iv->format = fimc_isp_find_format(NULL, NULL, 0); + iv->pixfmt.width = IS_DEFAULT_WIDTH; + iv->pixfmt.height = IS_DEFAULT_HEIGHT; + iv->pixfmt.pixelformat = iv->format->fourcc; + iv->pixfmt.colorspace = V4L2_COLORSPACE_SRGB; + iv->reqbufs_count = 0; + + memset(q, 0, sizeof(*q)); + q->type = type; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->ops = &isp_video_capture_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct isp_video_buf); + q->drv_priv = isp; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &isp->video_lock; + q->dev = &isp->pdev->dev; + + ret = vb2_queue_init(q); + if (ret < 0) + return ret; + + vdev = &iv->ve.vdev; + memset(vdev, 0, sizeof(*vdev)); + strscpy(vdev->name, "fimc-is-isp.capture", sizeof(vdev->name)); + vdev->queue = q; + vdev->fops = &isp_video_fops; + vdev->ioctl_ops = &isp_video_ioctl_ops; + vdev->v4l2_dev = v4l2_dev; + vdev->minor = -1; + vdev->release = video_device_release_empty; + vdev->lock = &isp->video_lock; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE; + + iv->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vdev->entity, 1, &iv->pad); + if (ret < 0) + return ret; + + video_set_drvdata(vdev, isp); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + media_entity_cleanup(&vdev->entity); + return ret; + } + + v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n", + vdev->name, video_device_node_name(vdev)); + + return 0; +} + +void fimc_isp_video_device_unregister(struct fimc_isp *isp, + enum v4l2_buf_type type) +{ + struct exynos_video_entity *ve; + + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + ve = &isp->video_capture.ve; + else + return; + + mutex_lock(&isp->video_lock); + + if (video_is_registered(&ve->vdev)) { + video_unregister_device(&ve->vdev); + media_entity_cleanup(&ve->vdev.entity); + ve->pipe = NULL; + } + + mutex_unlock(&isp->video_lock); +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.h b/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.h new file mode 100644 index 000000000..2dd4ddbc7 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp-video.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * Sylwester Nawrocki + */ +#ifndef FIMC_ISP_VIDEO__ +#define FIMC_ISP_VIDEO__ + +#include +#include "fimc-isp.h" + +#ifdef CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE +int fimc_isp_video_device_register(struct fimc_isp *isp, + struct v4l2_device *v4l2_dev, + enum v4l2_buf_type type); + +void fimc_isp_video_device_unregister(struct fimc_isp *isp, + enum v4l2_buf_type type); + +void fimc_isp_video_irq_handler(struct fimc_is *is); +#else +static inline void fimc_isp_video_irq_handler(struct fimc_is *is) +{ +} + +static inline int fimc_isp_video_device_register(struct fimc_isp *isp, + struct v4l2_device *v4l2_dev, + enum v4l2_buf_type type) +{ + return 0; +} + +static inline void fimc_isp_video_device_unregister(struct fimc_isp *isp, + enum v4l2_buf_type type) +{ +} +#endif /* !CONFIG_VIDEO_EXYNOS4_ISP_DMA_CAPTURE */ + +#endif /* FIMC_ISP_VIDEO__ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp.c b/drivers/media/platform/samsung/exynos4-is/fimc-isp.c new file mode 100644 index 000000000..b85986e50 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp.c @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Authors: Sylwester Nawrocki + * Younghwan Joo + */ +#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media-dev.h" +#include "fimc-isp-video.h" +#include "fimc-is-command.h" +#include "fimc-is-param.h" +#include "fimc-is-regs.h" +#include "fimc-is.h" + +int fimc_isp_debug; +module_param_named(debug_isp, fimc_isp_debug, int, S_IRUGO | S_IWUSR); + +static const struct fimc_fmt fimc_isp_formats[FIMC_ISP_NUM_FORMATS] = { + { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .depth = { 8 }, + .color = FIMC_FMT_RAW8, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .depth = { 10 }, + .color = FIMC_FMT_RAW10, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .depth = { 12 }, + .color = FIMC_FMT_RAW12, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + }, +}; + +/** + * fimc_isp_find_format - lookup color format by fourcc or media bus code + * @pixelformat: fourcc to match, ignored if null + * @mbus_code: media bus code to match, ignored if null + * @index: index to the fimc_isp_formats array, ignored if negative + */ +const struct fimc_fmt *fimc_isp_find_format(const u32 *pixelformat, + const u32 *mbus_code, int index) +{ + const struct fimc_fmt *fmt, *def_fmt = NULL; + unsigned int i; + int id = 0; + + if (index >= (int)ARRAY_SIZE(fimc_isp_formats)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(fimc_isp_formats); ++i) { + fmt = &fimc_isp_formats[i]; + if (pixelformat && fmt->fourcc == *pixelformat) + return fmt; + if (mbus_code && fmt->mbus_code == *mbus_code) + return fmt; + if (index == id) + def_fmt = fmt; + id++; + } + return def_fmt; +} + +void fimc_isp_irq_handler(struct fimc_is *is) +{ + is->i2h_cmd.args[0] = mcuctl_read(is, MCUCTL_REG_ISSR(20)); + is->i2h_cmd.args[1] = mcuctl_read(is, MCUCTL_REG_ISSR(21)); + + fimc_is_fw_clear_irq1(is, FIMC_IS_INT_FRAME_DONE_ISP); + fimc_isp_video_irq_handler(is); + + wake_up(&is->irq_queue); +} + +/* Capture subdev media entity operations */ +static int fimc_is_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + return 0; +} + +static const struct media_entity_operations fimc_is_subdev_media_ops = { + .link_setup = fimc_is_link_setup, +}; + +static int fimc_is_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct fimc_fmt *fmt; + + fmt = fimc_isp_find_format(NULL, NULL, code->index); + if (!fmt) + return -EINVAL; + code->code = fmt->mbus_code; + return 0; +} + +static int fimc_isp_subdev_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct fimc_isp *isp = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf = &fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + *mf = *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + return 0; + } + + mf->colorspace = V4L2_COLORSPACE_SRGB; + + mutex_lock(&isp->subdev_lock); + + if (fmt->pad == FIMC_ISP_SD_PAD_SINK) { + /* ISP OTF input image format */ + *mf = isp->sink_fmt; + } else { + /* ISP OTF output image format */ + *mf = isp->src_fmt; + + if (fmt->pad == FIMC_ISP_SD_PAD_SRC_FIFO) { + mf->colorspace = V4L2_COLORSPACE_JPEG; + mf->code = MEDIA_BUS_FMT_YUV10_1X30; + } + } + + mutex_unlock(&isp->subdev_lock); + + isp_dbg(1, sd, "%s: pad%d: fmt: 0x%x, %dx%d\n", __func__, + fmt->pad, mf->code, mf->width, mf->height); + + return 0; +} + +static void __isp_subdev_try_format(struct fimc_isp *isp, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct v4l2_mbus_framefmt *format; + + mf->colorspace = V4L2_COLORSPACE_SRGB; + + if (fmt->pad == FIMC_ISP_SD_PAD_SINK) { + v4l_bound_align_image(&mf->width, FIMC_ISP_SINK_WIDTH_MIN, + FIMC_ISP_SINK_WIDTH_MAX, 0, + &mf->height, FIMC_ISP_SINK_HEIGHT_MIN, + FIMC_ISP_SINK_HEIGHT_MAX, 0, 0); + mf->code = MEDIA_BUS_FMT_SGRBG10_1X10; + } else { + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + format = v4l2_subdev_get_try_format(&isp->subdev, + sd_state, + FIMC_ISP_SD_PAD_SINK); + else + format = &isp->sink_fmt; + + /* Allow changing format only on sink pad */ + mf->width = format->width - FIMC_ISP_CAC_MARGIN_WIDTH; + mf->height = format->height - FIMC_ISP_CAC_MARGIN_HEIGHT; + + if (fmt->pad == FIMC_ISP_SD_PAD_SRC_FIFO) { + mf->code = MEDIA_BUS_FMT_YUV10_1X30; + mf->colorspace = V4L2_COLORSPACE_JPEG; + } else { + mf->code = format->code; + } + } +} + +static int fimc_isp_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct fimc_isp *isp = v4l2_get_subdevdata(sd); + struct fimc_is *is = fimc_isp_to_is(isp); + struct v4l2_mbus_framefmt *mf = &fmt->format; + int ret = 0; + + isp_dbg(1, sd, "%s: pad%d: code: 0x%x, %dx%d\n", + __func__, fmt->pad, mf->code, mf->width, mf->height); + + mutex_lock(&isp->subdev_lock); + __isp_subdev_try_format(isp, sd_state, fmt); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + *mf = fmt->format; + + /* Propagate format to the source pads */ + if (fmt->pad == FIMC_ISP_SD_PAD_SINK) { + struct v4l2_subdev_format format = *fmt; + unsigned int pad; + + for (pad = FIMC_ISP_SD_PAD_SRC_FIFO; + pad < FIMC_ISP_SD_PADS_NUM; pad++) { + format.pad = pad; + __isp_subdev_try_format(isp, sd_state, + &format); + mf = v4l2_subdev_get_try_format(sd, sd_state, + pad); + *mf = format.format; + } + } + } else { + if (!media_entity_is_streaming(&sd->entity)) { + if (fmt->pad == FIMC_ISP_SD_PAD_SINK) { + struct v4l2_subdev_format format = *fmt; + + isp->sink_fmt = *mf; + + format.pad = FIMC_ISP_SD_PAD_SRC_DMA; + __isp_subdev_try_format(isp, sd_state, + &format); + + isp->src_fmt = format.format; + __is_set_frame_size(is, &isp->src_fmt); + } else { + isp->src_fmt = *mf; + } + } else { + ret = -EBUSY; + } + } + + mutex_unlock(&isp->subdev_lock); + return ret; +} + +static int fimc_isp_subdev_s_stream(struct v4l2_subdev *sd, int on) +{ + struct fimc_isp *isp = v4l2_get_subdevdata(sd); + struct fimc_is *is = fimc_isp_to_is(isp); + int ret; + + isp_dbg(1, sd, "%s: on: %d\n", __func__, on); + + if (!test_bit(IS_ST_INIT_DONE, &is->state)) + return -EBUSY; + + fimc_is_mem_barrier(); + + if (on) { + if (__get_pending_param_count(is)) { + ret = fimc_is_itf_s_param(is, true); + if (ret < 0) + return ret; + } + + isp_dbg(1, sd, "changing mode to %d\n", is->config_index); + + ret = fimc_is_itf_mode_change(is); + if (ret) + return -EINVAL; + + clear_bit(IS_ST_STREAM_ON, &is->state); + fimc_is_hw_stream_on(is); + ret = fimc_is_wait_event(is, IS_ST_STREAM_ON, 1, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) { + v4l2_err(sd, "stream on timeout\n"); + return ret; + } + } else { + clear_bit(IS_ST_STREAM_OFF, &is->state); + fimc_is_hw_stream_off(is); + ret = fimc_is_wait_event(is, IS_ST_STREAM_OFF, 1, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) { + v4l2_err(sd, "stream off timeout\n"); + return ret; + } + is->setfile.sub_index = 0; + } + + return 0; +} + +static int fimc_isp_subdev_s_power(struct v4l2_subdev *sd, int on) +{ + struct fimc_isp *isp = v4l2_get_subdevdata(sd); + struct fimc_is *is = fimc_isp_to_is(isp); + int ret = 0; + + pr_debug("on: %d\n", on); + + if (on) { + ret = pm_runtime_resume_and_get(&is->pdev->dev); + if (ret < 0) + return ret; + + set_bit(IS_ST_PWR_ON, &is->state); + + ret = fimc_is_start_firmware(is); + if (ret < 0) { + v4l2_err(sd, "firmware booting failed\n"); + pm_runtime_put(&is->pdev->dev); + return ret; + } + set_bit(IS_ST_PWR_SUBIP_ON, &is->state); + + ret = fimc_is_hw_initialize(is); + } else { + /* Close sensor */ + if (!test_bit(IS_ST_PWR_ON, &is->state)) { + fimc_is_hw_close_sensor(is, 0); + + ret = fimc_is_wait_event(is, IS_ST_OPEN_SENSOR, 0, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) { + v4l2_err(sd, "sensor close timeout\n"); + return ret; + } + } + + /* SUB IP power off */ + if (test_bit(IS_ST_PWR_SUBIP_ON, &is->state)) { + fimc_is_hw_subip_power_off(is); + ret = fimc_is_wait_event(is, IS_ST_PWR_SUBIP_ON, 0, + FIMC_IS_CONFIG_TIMEOUT); + if (ret < 0) { + v4l2_err(sd, "sub-IP power off timeout\n"); + return ret; + } + } + + fimc_is_cpu_set_power(is, 0); + pm_runtime_put_sync(&is->pdev->dev); + + clear_bit(IS_ST_PWR_ON, &is->state); + clear_bit(IS_ST_INIT_DONE, &is->state); + is->state = 0; + is->config[is->config_index].p_region_index[0] = 0; + is->config[is->config_index].p_region_index[1] = 0; + set_bit(IS_ST_IDLE, &is->state); + wmb(); + } + + return ret; +} + +static int fimc_isp_subdev_open(struct v4l2_subdev *sd, + struct v4l2_subdev_fh *fh) +{ + struct v4l2_mbus_framefmt *format; + struct v4l2_mbus_framefmt fmt = { + .colorspace = V4L2_COLORSPACE_SRGB, + .code = fimc_isp_formats[0].mbus_code, + .width = DEFAULT_PREVIEW_STILL_WIDTH + FIMC_ISP_CAC_MARGIN_WIDTH, + .height = DEFAULT_PREVIEW_STILL_HEIGHT + FIMC_ISP_CAC_MARGIN_HEIGHT, + .field = V4L2_FIELD_NONE, + }; + + format = v4l2_subdev_get_try_format(sd, fh->state, + FIMC_ISP_SD_PAD_SINK); + *format = fmt; + + format = v4l2_subdev_get_try_format(sd, fh->state, + FIMC_ISP_SD_PAD_SRC_FIFO); + fmt.width = DEFAULT_PREVIEW_STILL_WIDTH; + fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT; + *format = fmt; + + format = v4l2_subdev_get_try_format(sd, fh->state, + FIMC_ISP_SD_PAD_SRC_DMA); + *format = fmt; + + return 0; +} + +static int fimc_isp_subdev_registered(struct v4l2_subdev *sd) +{ + struct fimc_isp *isp = v4l2_get_subdevdata(sd); + int ret; + + /* Use pipeline object allocated by the media device. */ + isp->video_capture.ve.pipe = v4l2_get_subdev_hostdata(sd); + + ret = fimc_isp_video_device_register(isp, sd->v4l2_dev, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + if (ret < 0) + isp->video_capture.ve.pipe = NULL; + + return ret; +} + +static void fimc_isp_subdev_unregistered(struct v4l2_subdev *sd) +{ + struct fimc_isp *isp = v4l2_get_subdevdata(sd); + + fimc_isp_video_device_unregister(isp, + V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); +} + +static const struct v4l2_subdev_internal_ops fimc_is_subdev_internal_ops = { + .registered = fimc_isp_subdev_registered, + .unregistered = fimc_isp_subdev_unregistered, + .open = fimc_isp_subdev_open, +}; + +static const struct v4l2_subdev_pad_ops fimc_is_subdev_pad_ops = { + .enum_mbus_code = fimc_is_subdev_enum_mbus_code, + .get_fmt = fimc_isp_subdev_get_fmt, + .set_fmt = fimc_isp_subdev_set_fmt, +}; + +static const struct v4l2_subdev_video_ops fimc_is_subdev_video_ops = { + .s_stream = fimc_isp_subdev_s_stream, +}; + +static const struct v4l2_subdev_core_ops fimc_is_core_ops = { + .s_power = fimc_isp_subdev_s_power, +}; + +static const struct v4l2_subdev_ops fimc_is_subdev_ops = { + .core = &fimc_is_core_ops, + .video = &fimc_is_subdev_video_ops, + .pad = &fimc_is_subdev_pad_ops, +}; + +static int __ctrl_set_white_balance(struct fimc_is *is, int value) +{ + switch (value) { + case V4L2_WHITE_BALANCE_AUTO: + __is_set_isp_awb(is, ISP_AWB_COMMAND_AUTO, 0); + break; + case V4L2_WHITE_BALANCE_DAYLIGHT: + __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION, + ISP_AWB_ILLUMINATION_DAYLIGHT); + break; + case V4L2_WHITE_BALANCE_CLOUDY: + __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION, + ISP_AWB_ILLUMINATION_CLOUDY); + break; + case V4L2_WHITE_BALANCE_INCANDESCENT: + __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION, + ISP_AWB_ILLUMINATION_TUNGSTEN); + break; + case V4L2_WHITE_BALANCE_FLUORESCENT: + __is_set_isp_awb(is, ISP_AWB_COMMAND_ILLUMINATION, + ISP_AWB_ILLUMINATION_FLUORESCENT); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int __ctrl_set_aewb_lock(struct fimc_is *is, + struct v4l2_ctrl *ctrl) +{ + bool awb_lock = ctrl->val & V4L2_LOCK_WHITE_BALANCE; + bool ae_lock = ctrl->val & V4L2_LOCK_EXPOSURE; + struct isp_param *isp = &is->is_p_region->parameter.isp; + int cmd, ret; + + cmd = ae_lock ? ISP_AA_COMMAND_STOP : ISP_AA_COMMAND_START; + isp->aa.cmd = cmd; + isp->aa.target = ISP_AA_TARGET_AE; + fimc_is_set_param_bit(is, PARAM_ISP_AA); + is->af.ae_lock_state = ae_lock; + wmb(); + + ret = fimc_is_itf_s_param(is, false); + if (ret < 0) + return ret; + + cmd = awb_lock ? ISP_AA_COMMAND_STOP : ISP_AA_COMMAND_START; + isp->aa.cmd = cmd; + isp->aa.target = ISP_AA_TARGET_AE; + fimc_is_set_param_bit(is, PARAM_ISP_AA); + is->af.awb_lock_state = awb_lock; + wmb(); + + return fimc_is_itf_s_param(is, false); +} + +/* Supported manual ISO values */ +static const s64 iso_qmenu[] = { + 50, 100, 200, 400, 800, +}; + +static int __ctrl_set_iso(struct fimc_is *is, int value) +{ + unsigned int idx, iso; + + if (value == V4L2_ISO_SENSITIVITY_AUTO) { + __is_set_isp_iso(is, ISP_ISO_COMMAND_AUTO, 0); + return 0; + } + idx = is->isp.ctrls.iso->val; + if (idx >= ARRAY_SIZE(iso_qmenu)) + return -EINVAL; + + iso = iso_qmenu[idx]; + __is_set_isp_iso(is, ISP_ISO_COMMAND_MANUAL, iso); + return 0; +} + +static int __ctrl_set_metering(struct fimc_is *is, unsigned int value) +{ + unsigned int val; + + switch (value) { + case V4L2_EXPOSURE_METERING_AVERAGE: + val = ISP_METERING_COMMAND_AVERAGE; + break; + case V4L2_EXPOSURE_METERING_CENTER_WEIGHTED: + val = ISP_METERING_COMMAND_CENTER; + break; + case V4L2_EXPOSURE_METERING_SPOT: + val = ISP_METERING_COMMAND_SPOT; + break; + case V4L2_EXPOSURE_METERING_MATRIX: + val = ISP_METERING_COMMAND_MATRIX; + break; + default: + return -EINVAL; + } + + __is_set_isp_metering(is, IS_METERING_CONFIG_CMD, val); + return 0; +} + +static int __ctrl_set_afc(struct fimc_is *is, int value) +{ + switch (value) { + case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED: + __is_set_isp_afc(is, ISP_AFC_COMMAND_DISABLE, 0); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_50HZ: + __is_set_isp_afc(is, ISP_AFC_COMMAND_MANUAL, 50); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_60HZ: + __is_set_isp_afc(is, ISP_AFC_COMMAND_MANUAL, 60); + break; + case V4L2_CID_POWER_LINE_FREQUENCY_AUTO: + __is_set_isp_afc(is, ISP_AFC_COMMAND_AUTO, 0); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int __ctrl_set_image_effect(struct fimc_is *is, int value) +{ + static const u8 effects[][2] = { + { V4L2_COLORFX_NONE, ISP_IMAGE_EFFECT_DISABLE }, + { V4L2_COLORFX_BW, ISP_IMAGE_EFFECT_MONOCHROME }, + { V4L2_COLORFX_SEPIA, ISP_IMAGE_EFFECT_SEPIA }, + { V4L2_COLORFX_NEGATIVE, ISP_IMAGE_EFFECT_NEGATIVE_MONO }, + { 16 /* TODO */, ISP_IMAGE_EFFECT_NEGATIVE_COLOR }, + }; + int i; + + for (i = 0; i < ARRAY_SIZE(effects); i++) { + if (effects[i][0] != value) + continue; + + __is_set_isp_effect(is, effects[i][1]); + return 0; + } + + return -EINVAL; +} + +static int fimc_is_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct fimc_isp *isp = ctrl_to_fimc_isp(ctrl); + struct fimc_is *is = fimc_isp_to_is(isp); + bool set_param = true; + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_CONTRAST: + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_CONTRAST, + ctrl->val); + break; + + case V4L2_CID_SATURATION: + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_SATURATION, + ctrl->val); + break; + + case V4L2_CID_SHARPNESS: + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_SHARPNESS, + ctrl->val); + break; + + case V4L2_CID_EXPOSURE_ABSOLUTE: + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_EXPOSURE, + ctrl->val); + break; + + case V4L2_CID_BRIGHTNESS: + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_BRIGHTNESS, + ctrl->val); + break; + + case V4L2_CID_HUE: + __is_set_isp_adjust(is, ISP_ADJUST_COMMAND_MANUAL_HUE, + ctrl->val); + break; + + case V4L2_CID_EXPOSURE_METERING: + ret = __ctrl_set_metering(is, ctrl->val); + break; + + case V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE: + ret = __ctrl_set_white_balance(is, ctrl->val); + break; + + case V4L2_CID_3A_LOCK: + ret = __ctrl_set_aewb_lock(is, ctrl); + set_param = false; + break; + + case V4L2_CID_ISO_SENSITIVITY_AUTO: + ret = __ctrl_set_iso(is, ctrl->val); + break; + + case V4L2_CID_POWER_LINE_FREQUENCY: + ret = __ctrl_set_afc(is, ctrl->val); + break; + + case V4L2_CID_COLORFX: + __ctrl_set_image_effect(is, ctrl->val); + break; + + default: + ret = -EINVAL; + break; + } + + if (ret < 0) { + v4l2_err(&isp->subdev, "Failed to set control: %s (%d)\n", + ctrl->name, ctrl->val); + return ret; + } + + if (set_param && test_bit(IS_ST_STREAM_ON, &is->state)) + return fimc_is_itf_s_param(is, true); + + return 0; +} + +static const struct v4l2_ctrl_ops fimc_isp_ctrl_ops = { + .s_ctrl = fimc_is_s_ctrl, +}; + +static void __isp_subdev_set_default_format(struct fimc_isp *isp) +{ + struct fimc_is *is = fimc_isp_to_is(isp); + + isp->sink_fmt.width = DEFAULT_PREVIEW_STILL_WIDTH + + FIMC_ISP_CAC_MARGIN_WIDTH; + isp->sink_fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT + + FIMC_ISP_CAC_MARGIN_HEIGHT; + isp->sink_fmt.code = MEDIA_BUS_FMT_SGRBG10_1X10; + + isp->src_fmt.width = DEFAULT_PREVIEW_STILL_WIDTH; + isp->src_fmt.height = DEFAULT_PREVIEW_STILL_HEIGHT; + isp->src_fmt.code = MEDIA_BUS_FMT_SGRBG10_1X10; + __is_set_frame_size(is, &isp->src_fmt); +} + +int fimc_isp_subdev_create(struct fimc_isp *isp) +{ + const struct v4l2_ctrl_ops *ops = &fimc_isp_ctrl_ops; + struct v4l2_ctrl_handler *handler = &isp->ctrls.handler; + struct v4l2_subdev *sd = &isp->subdev; + struct fimc_isp_ctrls *ctrls = &isp->ctrls; + int ret; + + mutex_init(&isp->subdev_lock); + + v4l2_subdev_init(sd, &fimc_is_subdev_ops); + + sd->owner = THIS_MODULE; + sd->grp_id = GRP_ID_FIMC_IS; + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, sizeof(sd->name), "FIMC-IS-ISP"); + + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER; + isp->subdev_pads[FIMC_ISP_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + isp->subdev_pads[FIMC_ISP_SD_PAD_SRC_FIFO].flags = MEDIA_PAD_FL_SOURCE; + isp->subdev_pads[FIMC_ISP_SD_PAD_SRC_DMA].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, FIMC_ISP_SD_PADS_NUM, + isp->subdev_pads); + if (ret) + return ret; + + v4l2_ctrl_handler_init(handler, 20); + + ctrls->saturation = v4l2_ctrl_new_std(handler, ops, V4L2_CID_SATURATION, + -2, 2, 1, 0); + ctrls->brightness = v4l2_ctrl_new_std(handler, ops, V4L2_CID_BRIGHTNESS, + -4, 4, 1, 0); + ctrls->contrast = v4l2_ctrl_new_std(handler, ops, V4L2_CID_CONTRAST, + -2, 2, 1, 0); + ctrls->sharpness = v4l2_ctrl_new_std(handler, ops, V4L2_CID_SHARPNESS, + -2, 2, 1, 0); + ctrls->hue = v4l2_ctrl_new_std(handler, ops, V4L2_CID_HUE, + -2, 2, 1, 0); + + ctrls->auto_wb = v4l2_ctrl_new_std_menu(handler, ops, + V4L2_CID_AUTO_N_PRESET_WHITE_BALANCE, + 8, ~0x14e, V4L2_WHITE_BALANCE_AUTO); + + ctrls->exposure = v4l2_ctrl_new_std(handler, ops, + V4L2_CID_EXPOSURE_ABSOLUTE, + -4, 4, 1, 0); + + ctrls->exp_metering = v4l2_ctrl_new_std_menu(handler, ops, + V4L2_CID_EXPOSURE_METERING, 3, + ~0xf, V4L2_EXPOSURE_METERING_AVERAGE); + + v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_POWER_LINE_FREQUENCY, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0, + V4L2_CID_POWER_LINE_FREQUENCY_AUTO); + /* ISO sensitivity */ + ctrls->auto_iso = v4l2_ctrl_new_std_menu(handler, ops, + V4L2_CID_ISO_SENSITIVITY_AUTO, 1, 0, + V4L2_ISO_SENSITIVITY_AUTO); + + ctrls->iso = v4l2_ctrl_new_int_menu(handler, ops, + V4L2_CID_ISO_SENSITIVITY, ARRAY_SIZE(iso_qmenu) - 1, + ARRAY_SIZE(iso_qmenu)/2 - 1, iso_qmenu); + + ctrls->aewb_lock = v4l2_ctrl_new_std(handler, ops, + V4L2_CID_3A_LOCK, 0, 0x3, 0, 0); + + /* TODO: Add support for NEGATIVE_COLOR option */ + ctrls->colorfx = v4l2_ctrl_new_std_menu(handler, ops, V4L2_CID_COLORFX, + V4L2_COLORFX_SET_CBCR + 1, ~0x1000f, V4L2_COLORFX_NONE); + + if (handler->error) { + media_entity_cleanup(&sd->entity); + return handler->error; + } + + v4l2_ctrl_auto_cluster(2, &ctrls->auto_iso, + V4L2_ISO_SENSITIVITY_MANUAL, false); + + sd->ctrl_handler = handler; + sd->internal_ops = &fimc_is_subdev_internal_ops; + sd->entity.ops = &fimc_is_subdev_media_ops; + v4l2_set_subdevdata(sd, isp); + + __isp_subdev_set_default_format(isp); + + return 0; +} + +void fimc_isp_subdev_destroy(struct fimc_isp *isp) +{ + struct v4l2_subdev *sd = &isp->subdev; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&isp->ctrls.handler); + v4l2_set_subdevdata(sd, NULL); +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-isp.h b/drivers/media/platform/samsung/exynos4-is/fimc-isp.h new file mode 100644 index 000000000..12017cd92 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-isp.h @@ -0,0 +1,197 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung EXYNOS4x12 FIMC-IS (Imaging Subsystem) driver + * + * Copyright (C) 2013 Samsung Electronics Co., Ltd. + * + * Authors: Sylwester Nawrocki + * Younghwan Joo + */ +#ifndef FIMC_ISP_H_ +#define FIMC_ISP_H_ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +extern int fimc_isp_debug; + +#define isp_dbg(level, dev, fmt, arg...) \ + v4l2_dbg(level, fimc_isp_debug, dev, fmt, ## arg) + +/* FIXME: revisit these constraints */ +#define FIMC_ISP_SINK_WIDTH_MIN (16 + 8) +#define FIMC_ISP_SINK_HEIGHT_MIN (12 + 8) +#define FIMC_ISP_SOURCE_WIDTH_MIN 8 +#define FIMC_ISP_SOURCE_HEIGHT_MIN 8 +#define FIMC_ISP_CAC_MARGIN_WIDTH 16 +#define FIMC_ISP_CAC_MARGIN_HEIGHT 12 + +#define FIMC_ISP_SINK_WIDTH_MAX (4000 - 16) +#define FIMC_ISP_SINK_HEIGHT_MAX (4000 + 12) +#define FIMC_ISP_SOURCE_WIDTH_MAX 4000 +#define FIMC_ISP_SOURCE_HEIGHT_MAX 4000 + +#define FIMC_ISP_NUM_FORMATS 3 +#define FIMC_ISP_REQ_BUFS_MIN 2 +#define FIMC_ISP_REQ_BUFS_MAX 32 + +#define FIMC_ISP_SD_PAD_SINK 0 +#define FIMC_ISP_SD_PAD_SRC_FIFO 1 +#define FIMC_ISP_SD_PAD_SRC_DMA 2 +#define FIMC_ISP_SD_PADS_NUM 3 +#define FIMC_ISP_MAX_PLANES 1 + +/** + * struct fimc_isp_frame - source/target frame properties + * @width: full image width + * @height: full image height + * @rect: crop/composition rectangle + */ +struct fimc_isp_frame { + u16 width; + u16 height; + struct v4l2_rect rect; +}; + +struct fimc_isp_ctrls { + struct v4l2_ctrl_handler handler; + + /* Auto white balance */ + struct v4l2_ctrl *auto_wb; + /* Auto ISO control cluster */ + struct { + struct v4l2_ctrl *auto_iso; + struct v4l2_ctrl *iso; + }; + /* Adjust - contrast */ + struct v4l2_ctrl *contrast; + /* Adjust - saturation */ + struct v4l2_ctrl *saturation; + /* Adjust - sharpness */ + struct v4l2_ctrl *sharpness; + /* Adjust - brightness */ + struct v4l2_ctrl *brightness; + /* Adjust - hue */ + struct v4l2_ctrl *hue; + + /* Auto/manual exposure */ + struct v4l2_ctrl *auto_exp; + /* Manual exposure value */ + struct v4l2_ctrl *exposure; + /* AE/AWB lock/unlock */ + struct v4l2_ctrl *aewb_lock; + /* Exposure metering mode */ + struct v4l2_ctrl *exp_metering; + /* AFC */ + struct v4l2_ctrl *afc; + /* ISP image effect */ + struct v4l2_ctrl *colorfx; +}; + +struct isp_video_buf { + struct vb2_v4l2_buffer vb; + dma_addr_t dma_addr[FIMC_ISP_MAX_PLANES]; + unsigned int index; +}; + +#define to_isp_video_buf(_b) container_of(_b, struct isp_video_buf, vb) + +#define FIMC_ISP_MAX_BUFS 4 + +/** + * struct fimc_is_video - fimc-is video device structure + * @ve: video_device structure and media pipeline + * @type: video device type (CAPTURE/OUTPUT) + * @pad: video device media (sink) pad + * @pending_buf_q: pending buffers queue head + * @active_buf_q: a queue head of buffers scheduled in hardware + * @vb_queue: vb2 buffer queue + * @reqbufs_count: the number of buffers requested in REQBUFS ioctl + * @buf_count: number of video buffers scheduled in hardware + * @buf_mask: bitmask of the queued video buffer indices + * @frame_count: counter of frames dequeued to user space + * @streaming: is streaming in progress? + * @buffers: buffer info + * @format: current fimc pixel format + * @pixfmt: current pixel format + */ +struct fimc_is_video { + struct exynos_video_entity ve; + enum v4l2_buf_type type; + struct media_pad pad; + struct list_head pending_buf_q; + struct list_head active_buf_q; + struct vb2_queue vb_queue; + unsigned int reqbufs_count; + unsigned int buf_count; + unsigned int buf_mask; + unsigned int frame_count; + int streaming; + struct isp_video_buf *buffers[FIMC_ISP_MAX_BUFS]; + const struct fimc_fmt *format; + struct v4l2_pix_format_mplane pixfmt; +}; + +/* struct fimc_isp:state bit definitions */ +#define ST_ISP_VID_CAP_BUF_PREP 0 +#define ST_ISP_VID_CAP_STREAMING 1 + +/** + * struct fimc_isp - FIMC-IS ISP data structure + * @pdev: pointer to FIMC-IS platform device + * @subdev: ISP v4l2_subdev + * @subdev_pads: the ISP subdev media pads + * @src_fmt: source mediabus format + * @sink_fmt: sink mediabus format + * @test_pattern: test pattern controls + * @ctrls: v4l2 controls structure + * @video_lock: mutex serializing video device operations + * @subdev_lock: mutex serializing subdev operations + * @cac_margin_x: horizontal CAC margin in pixels + * @cac_margin_y: vertical CAC margin in pixels + * @state: driver state flags + * @video_capture: the ISP block video capture device + */ +struct fimc_isp { + struct platform_device *pdev; + struct v4l2_subdev subdev; + struct media_pad subdev_pads[FIMC_ISP_SD_PADS_NUM]; + struct v4l2_mbus_framefmt src_fmt; + struct v4l2_mbus_framefmt sink_fmt; + struct v4l2_ctrl *test_pattern; + struct fimc_isp_ctrls ctrls; + + struct mutex video_lock; + struct mutex subdev_lock; + + unsigned int cac_margin_x; + unsigned int cac_margin_y; + + unsigned long state; + + struct fimc_is_video video_capture; +}; + +#define ctrl_to_fimc_isp(_ctrl) \ + container_of(ctrl->handler, struct fimc_isp, ctrls.handler) + +struct fimc_is; + +int fimc_isp_subdev_create(struct fimc_isp *isp); +void fimc_isp_subdev_destroy(struct fimc_isp *isp); +void fimc_isp_irq_handler(struct fimc_is *is); +int fimc_is_create_controls(struct fimc_isp *isp); +int fimc_is_delete_controls(struct fimc_isp *isp); +const struct fimc_fmt *fimc_isp_find_format(const u32 *pixelformat, + const u32 *mbus_code, int index); +#endif /* FIMC_ISP_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.c b/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.c new file mode 100644 index 000000000..57996b410 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Register interface file for EXYNOS FIMC-LITE (camera interface) driver + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki +*/ + +#include +#include +#include +#include + +#include "fimc-lite-reg.h" +#include "fimc-lite.h" +#include "fimc-core.h" + +#define FLITE_RESET_TIMEOUT 50 /* in ms */ + +void flite_hw_reset(struct fimc_lite *dev) +{ + unsigned long end = jiffies + msecs_to_jiffies(FLITE_RESET_TIMEOUT); + u32 cfg; + + cfg = readl(dev->regs + FLITE_REG_CIGCTRL); + cfg |= FLITE_REG_CIGCTRL_SWRST_REQ; + writel(cfg, dev->regs + FLITE_REG_CIGCTRL); + + while (time_is_after_jiffies(end)) { + cfg = readl(dev->regs + FLITE_REG_CIGCTRL); + if (cfg & FLITE_REG_CIGCTRL_SWRST_RDY) + break; + usleep_range(1000, 5000); + } + + cfg |= FLITE_REG_CIGCTRL_SWRST; + writel(cfg, dev->regs + FLITE_REG_CIGCTRL); +} + +void flite_hw_clear_pending_irq(struct fimc_lite *dev) +{ + u32 cfg = readl(dev->regs + FLITE_REG_CISTATUS); + cfg &= ~FLITE_REG_CISTATUS_IRQ_CAM; + writel(cfg, dev->regs + FLITE_REG_CISTATUS); +} + +u32 flite_hw_get_interrupt_source(struct fimc_lite *dev) +{ + u32 intsrc = readl(dev->regs + FLITE_REG_CISTATUS); + return intsrc & FLITE_REG_CISTATUS_IRQ_MASK; +} + +void flite_hw_clear_last_capture_end(struct fimc_lite *dev) +{ + + u32 cfg = readl(dev->regs + FLITE_REG_CISTATUS2); + cfg &= ~FLITE_REG_CISTATUS2_LASTCAPEND; + writel(cfg, dev->regs + FLITE_REG_CISTATUS2); +} + +void flite_hw_set_interrupt_mask(struct fimc_lite *dev) +{ + u32 cfg, intsrc; + + /* Select interrupts to be enabled for each output mode */ + if (atomic_read(&dev->out_path) == FIMC_IO_DMA) { + intsrc = FLITE_REG_CIGCTRL_IRQ_OVFEN | + FLITE_REG_CIGCTRL_IRQ_LASTEN | + FLITE_REG_CIGCTRL_IRQ_STARTEN | + FLITE_REG_CIGCTRL_IRQ_ENDEN; + } else { + /* An output to the FIMC-IS */ + intsrc = FLITE_REG_CIGCTRL_IRQ_OVFEN | + FLITE_REG_CIGCTRL_IRQ_LASTEN; + } + + cfg = readl(dev->regs + FLITE_REG_CIGCTRL); + cfg |= FLITE_REG_CIGCTRL_IRQ_DISABLE_MASK; + cfg &= ~intsrc; + writel(cfg, dev->regs + FLITE_REG_CIGCTRL); +} + +void flite_hw_capture_start(struct fimc_lite *dev) +{ + u32 cfg = readl(dev->regs + FLITE_REG_CIIMGCPT); + cfg |= FLITE_REG_CIIMGCPT_IMGCPTEN; + writel(cfg, dev->regs + FLITE_REG_CIIMGCPT); +} + +void flite_hw_capture_stop(struct fimc_lite *dev) +{ + u32 cfg = readl(dev->regs + FLITE_REG_CIIMGCPT); + cfg &= ~FLITE_REG_CIIMGCPT_IMGCPTEN; + writel(cfg, dev->regs + FLITE_REG_CIIMGCPT); +} + +/* + * Test pattern (color bars) enable/disable. External sensor + * pixel clock must be active for the test pattern to work. + */ +void flite_hw_set_test_pattern(struct fimc_lite *dev, bool on) +{ + u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL); + if (on) + cfg |= FLITE_REG_CIGCTRL_TEST_PATTERN_COLORBAR; + else + cfg &= ~FLITE_REG_CIGCTRL_TEST_PATTERN_COLORBAR; + writel(cfg, dev->regs + FLITE_REG_CIGCTRL); +} + +static const u32 src_pixfmt_map[8][3] = { + { MEDIA_BUS_FMT_YUYV8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_YCBYCR, + FLITE_REG_CIGCTRL_YUV422_1P }, + { MEDIA_BUS_FMT_YVYU8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_YCRYCB, + FLITE_REG_CIGCTRL_YUV422_1P }, + { MEDIA_BUS_FMT_UYVY8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_CBYCRY, + FLITE_REG_CIGCTRL_YUV422_1P }, + { MEDIA_BUS_FMT_VYUY8_2X8, FLITE_REG_CISRCSIZE_ORDER422_IN_CRYCBY, + FLITE_REG_CIGCTRL_YUV422_1P }, + { MEDIA_BUS_FMT_SGRBG8_1X8, 0, FLITE_REG_CIGCTRL_RAW8 }, + { MEDIA_BUS_FMT_SGRBG10_1X10, 0, FLITE_REG_CIGCTRL_RAW10 }, + { MEDIA_BUS_FMT_SGRBG12_1X12, 0, FLITE_REG_CIGCTRL_RAW12 }, + { MEDIA_BUS_FMT_JPEG_1X8, 0, FLITE_REG_CIGCTRL_USER(1) }, +}; + +/* Set camera input pixel format and resolution */ +void flite_hw_set_source_format(struct fimc_lite *dev, struct flite_frame *f) +{ + u32 pixelcode = f->fmt->mbus_code; + int i = ARRAY_SIZE(src_pixfmt_map); + u32 cfg; + + while (--i) { + if (src_pixfmt_map[i][0] == pixelcode) + break; + } + + if (i == 0 && src_pixfmt_map[i][0] != pixelcode) { + v4l2_err(&dev->ve.vdev, + "Unsupported pixel code, falling back to %#08x\n", + src_pixfmt_map[i][0]); + } + + cfg = readl(dev->regs + FLITE_REG_CIGCTRL); + cfg &= ~FLITE_REG_CIGCTRL_FMT_MASK; + cfg |= src_pixfmt_map[i][2]; + writel(cfg, dev->regs + FLITE_REG_CIGCTRL); + + cfg = readl(dev->regs + FLITE_REG_CISRCSIZE); + cfg &= ~(FLITE_REG_CISRCSIZE_ORDER422_MASK | + FLITE_REG_CISRCSIZE_SIZE_CAM_MASK); + cfg |= (f->f_width << 16) | f->f_height; + cfg |= src_pixfmt_map[i][1]; + writel(cfg, dev->regs + FLITE_REG_CISRCSIZE); +} + +/* Set the camera host input window offsets (cropping) */ +void flite_hw_set_window_offset(struct fimc_lite *dev, struct flite_frame *f) +{ + u32 hoff2, voff2; + u32 cfg; + + cfg = readl(dev->regs + FLITE_REG_CIWDOFST); + cfg &= ~FLITE_REG_CIWDOFST_OFST_MASK; + cfg |= (f->rect.left << 16) | f->rect.top; + cfg |= FLITE_REG_CIWDOFST_WINOFSEN; + writel(cfg, dev->regs + FLITE_REG_CIWDOFST); + + hoff2 = f->f_width - f->rect.width - f->rect.left; + voff2 = f->f_height - f->rect.height - f->rect.top; + + cfg = (hoff2 << 16) | voff2; + writel(cfg, dev->regs + FLITE_REG_CIWDOFST2); +} + +/* Select camera port (A, B) */ +static void flite_hw_set_camera_port(struct fimc_lite *dev, int id) +{ + u32 cfg = readl(dev->regs + FLITE_REG_CIGENERAL); + if (id == 0) + cfg &= ~FLITE_REG_CIGENERAL_CAM_B; + else + cfg |= FLITE_REG_CIGENERAL_CAM_B; + writel(cfg, dev->regs + FLITE_REG_CIGENERAL); +} + +/* Select serial or parallel bus, camera port (A,B) and set signals polarity */ +void flite_hw_set_camera_bus(struct fimc_lite *dev, + struct fimc_source_info *si) +{ + u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL); + unsigned int flags = si->flags; + + if (si->sensor_bus_type != FIMC_BUS_TYPE_MIPI_CSI2) { + cfg &= ~(FLITE_REG_CIGCTRL_SELCAM_MIPI | + FLITE_REG_CIGCTRL_INVPOLPCLK | + FLITE_REG_CIGCTRL_INVPOLVSYNC | + FLITE_REG_CIGCTRL_INVPOLHREF); + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + cfg |= FLITE_REG_CIGCTRL_INVPOLPCLK; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + cfg |= FLITE_REG_CIGCTRL_INVPOLVSYNC; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + cfg |= FLITE_REG_CIGCTRL_INVPOLHREF; + } else { + cfg |= FLITE_REG_CIGCTRL_SELCAM_MIPI; + } + + writel(cfg, dev->regs + FLITE_REG_CIGCTRL); + + flite_hw_set_camera_port(dev, si->mux_id); +} + +static void flite_hw_set_pack12(struct fimc_lite *dev, int on) +{ + u32 cfg = readl(dev->regs + FLITE_REG_CIODMAFMT); + + cfg &= ~FLITE_REG_CIODMAFMT_PACK12; + + if (on) + cfg |= FLITE_REG_CIODMAFMT_PACK12; + + writel(cfg, dev->regs + FLITE_REG_CIODMAFMT); +} + +static void flite_hw_set_out_order(struct fimc_lite *dev, struct flite_frame *f) +{ + static const u32 pixcode[4][2] = { + { MEDIA_BUS_FMT_YUYV8_2X8, FLITE_REG_CIODMAFMT_YCBYCR }, + { MEDIA_BUS_FMT_YVYU8_2X8, FLITE_REG_CIODMAFMT_YCRYCB }, + { MEDIA_BUS_FMT_UYVY8_2X8, FLITE_REG_CIODMAFMT_CBYCRY }, + { MEDIA_BUS_FMT_VYUY8_2X8, FLITE_REG_CIODMAFMT_CRYCBY }, + }; + u32 cfg = readl(dev->regs + FLITE_REG_CIODMAFMT); + int i = ARRAY_SIZE(pixcode); + + while (--i) + if (pixcode[i][0] == f->fmt->mbus_code) + break; + cfg &= ~FLITE_REG_CIODMAFMT_YCBCR_ORDER_MASK; + writel(cfg | pixcode[i][1], dev->regs + FLITE_REG_CIODMAFMT); +} + +void flite_hw_set_dma_window(struct fimc_lite *dev, struct flite_frame *f) +{ + u32 cfg; + + /* Maximum output pixel size */ + cfg = readl(dev->regs + FLITE_REG_CIOCAN); + cfg &= ~FLITE_REG_CIOCAN_MASK; + cfg |= (f->f_height << 16) | f->f_width; + writel(cfg, dev->regs + FLITE_REG_CIOCAN); + + /* DMA offsets */ + cfg = readl(dev->regs + FLITE_REG_CIOOFF); + cfg &= ~FLITE_REG_CIOOFF_MASK; + cfg |= (f->rect.top << 16) | f->rect.left; + writel(cfg, dev->regs + FLITE_REG_CIOOFF); +} + +void flite_hw_set_dma_buffer(struct fimc_lite *dev, struct flite_buffer *buf) +{ + unsigned int index; + u32 cfg; + + if (dev->dd->max_dma_bufs == 1) + index = 0; + else + index = buf->index; + + if (index == 0) + writel(buf->addr, dev->regs + FLITE_REG_CIOSA); + else + writel(buf->addr, dev->regs + FLITE_REG_CIOSAN(index - 1)); + + cfg = readl(dev->regs + FLITE_REG_CIFCNTSEQ); + cfg |= BIT(index); + writel(cfg, dev->regs + FLITE_REG_CIFCNTSEQ); +} + +void flite_hw_mask_dma_buffer(struct fimc_lite *dev, u32 index) +{ + u32 cfg; + + if (dev->dd->max_dma_bufs == 1) + index = 0; + + cfg = readl(dev->regs + FLITE_REG_CIFCNTSEQ); + cfg &= ~BIT(index); + writel(cfg, dev->regs + FLITE_REG_CIFCNTSEQ); +} + +/* Enable/disable output DMA, set output pixel size and offsets (composition) */ +void flite_hw_set_output_dma(struct fimc_lite *dev, struct flite_frame *f, + bool enable) +{ + u32 cfg = readl(dev->regs + FLITE_REG_CIGCTRL); + + if (!enable) { + cfg |= FLITE_REG_CIGCTRL_ODMA_DISABLE; + writel(cfg, dev->regs + FLITE_REG_CIGCTRL); + return; + } + + cfg &= ~FLITE_REG_CIGCTRL_ODMA_DISABLE; + writel(cfg, dev->regs + FLITE_REG_CIGCTRL); + + flite_hw_set_out_order(dev, f); + flite_hw_set_dma_window(dev, f); + flite_hw_set_pack12(dev, 0); +} + +void flite_hw_dump_regs(struct fimc_lite *dev, const char *label) +{ + struct { + u32 offset; + const char * const name; + } registers[] = { + { 0x00, "CISRCSIZE" }, + { 0x04, "CIGCTRL" }, + { 0x08, "CIIMGCPT" }, + { 0x0c, "CICPTSEQ" }, + { 0x10, "CIWDOFST" }, + { 0x14, "CIWDOFST2" }, + { 0x18, "CIODMAFMT" }, + { 0x20, "CIOCAN" }, + { 0x24, "CIOOFF" }, + { 0x30, "CIOSA" }, + { 0x40, "CISTATUS" }, + { 0x44, "CISTATUS2" }, + { 0xf0, "CITHOLD" }, + { 0xfc, "CIGENERAL" }, + }; + u32 i; + + v4l2_info(&dev->subdev, "--- %s ---\n", label); + + for (i = 0; i < ARRAY_SIZE(registers); i++) { + u32 cfg = readl(dev->regs + registers[i].offset); + v4l2_info(&dev->subdev, "%9s: 0x%08x\n", + registers[i].name, cfg); + } +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.h b/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.h new file mode 100644 index 000000000..c5656e902 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite-reg.h @@ -0,0 +1,155 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + */ + +#ifndef FIMC_LITE_REG_H_ +#define FIMC_LITE_REG_H_ + +#include + +#include "fimc-lite.h" + +/* Camera Source size */ +#define FLITE_REG_CISRCSIZE 0x00 +#define FLITE_REG_CISRCSIZE_ORDER422_IN_YCBYCR (0 << 14) +#define FLITE_REG_CISRCSIZE_ORDER422_IN_YCRYCB (1 << 14) +#define FLITE_REG_CISRCSIZE_ORDER422_IN_CBYCRY (2 << 14) +#define FLITE_REG_CISRCSIZE_ORDER422_IN_CRYCBY (3 << 14) +#define FLITE_REG_CISRCSIZE_ORDER422_MASK (0x3 << 14) +#define FLITE_REG_CISRCSIZE_SIZE_CAM_MASK (0x3fff << 16 | 0x3fff) + +/* Global control */ +#define FLITE_REG_CIGCTRL 0x04 +#define FLITE_REG_CIGCTRL_YUV422_1P (0x1e << 24) +#define FLITE_REG_CIGCTRL_RAW8 (0x2a << 24) +#define FLITE_REG_CIGCTRL_RAW10 (0x2b << 24) +#define FLITE_REG_CIGCTRL_RAW12 (0x2c << 24) +#define FLITE_REG_CIGCTRL_RAW14 (0x2d << 24) +/* User defined formats. x = 0...15 */ +#define FLITE_REG_CIGCTRL_USER(x) ((0x30 + x - 1) << 24) +#define FLITE_REG_CIGCTRL_FMT_MASK (0x3f << 24) +#define FLITE_REG_CIGCTRL_SHADOWMASK_DISABLE BIT(21) +#define FLITE_REG_CIGCTRL_ODMA_DISABLE BIT(20) +#define FLITE_REG_CIGCTRL_SWRST_REQ BIT(19) +#define FLITE_REG_CIGCTRL_SWRST_RDY BIT(18) +#define FLITE_REG_CIGCTRL_SWRST BIT(17) +#define FLITE_REG_CIGCTRL_TEST_PATTERN_COLORBAR BIT(15) +#define FLITE_REG_CIGCTRL_INVPOLPCLK BIT(14) +#define FLITE_REG_CIGCTRL_INVPOLVSYNC BIT(13) +#define FLITE_REG_CIGCTRL_INVPOLHREF BIT(12) +/* Interrupts mask bits (1 disables an interrupt) */ +#define FLITE_REG_CIGCTRL_IRQ_LASTEN BIT(8) +#define FLITE_REG_CIGCTRL_IRQ_ENDEN BIT(7) +#define FLITE_REG_CIGCTRL_IRQ_STARTEN BIT(6) +#define FLITE_REG_CIGCTRL_IRQ_OVFEN BIT(5) +#define FLITE_REG_CIGCTRL_IRQ_DISABLE_MASK (0xf << 5) +#define FLITE_REG_CIGCTRL_SELCAM_MIPI BIT(3) + +/* Image Capture Enable */ +#define FLITE_REG_CIIMGCPT 0x08 +#define FLITE_REG_CIIMGCPT_IMGCPTEN BIT(31) +#define FLITE_REG_CIIMGCPT_CPT_FREN BIT(25) +#define FLITE_REG_CIIMGCPT_CPT_MOD_FRCNT (1 << 18) +#define FLITE_REG_CIIMGCPT_CPT_MOD_FREN (0 << 18) + +/* Capture Sequence */ +#define FLITE_REG_CICPTSEQ 0x0c + +/* Camera Window Offset */ +#define FLITE_REG_CIWDOFST 0x10 +#define FLITE_REG_CIWDOFST_WINOFSEN BIT(31) +#define FLITE_REG_CIWDOFST_CLROVIY BIT(31) +#define FLITE_REG_CIWDOFST_CLROVFICB BIT(15) +#define FLITE_REG_CIWDOFST_CLROVFICR BIT(14) +#define FLITE_REG_CIWDOFST_OFST_MASK ((0x1fff << 16) | 0x1fff) + +/* Camera Window Offset2 */ +#define FLITE_REG_CIWDOFST2 0x14 + +/* Camera Output DMA Format */ +#define FLITE_REG_CIODMAFMT 0x18 +#define FLITE_REG_CIODMAFMT_RAW_CON BIT(15) +#define FLITE_REG_CIODMAFMT_PACK12 BIT(14) +#define FLITE_REG_CIODMAFMT_YCBYCR (0 << 4) +#define FLITE_REG_CIODMAFMT_YCRYCB (1 << 4) +#define FLITE_REG_CIODMAFMT_CBYCRY (2 << 4) +#define FLITE_REG_CIODMAFMT_CRYCBY (3 << 4) +#define FLITE_REG_CIODMAFMT_YCBCR_ORDER_MASK (0x3 << 4) + +/* Camera Output Canvas */ +#define FLITE_REG_CIOCAN 0x20 +#define FLITE_REG_CIOCAN_MASK ((0x3fff << 16) | 0x3fff) + +/* Camera Output DMA Offset */ +#define FLITE_REG_CIOOFF 0x24 +#define FLITE_REG_CIOOFF_MASK ((0x3fff << 16) | 0x3fff) + +/* Camera Output DMA Start Address */ +#define FLITE_REG_CIOSA 0x30 + +/* Camera Status */ +#define FLITE_REG_CISTATUS 0x40 +#define FLITE_REG_CISTATUS_MIPI_VVALID BIT(22) +#define FLITE_REG_CISTATUS_MIPI_HVALID BIT(21) +#define FLITE_REG_CISTATUS_MIPI_DVALID BIT(20) +#define FLITE_REG_CISTATUS_ITU_VSYNC BIT(14) +#define FLITE_REG_CISTATUS_ITU_HREFF BIT(13) +#define FLITE_REG_CISTATUS_OVFIY BIT(10) +#define FLITE_REG_CISTATUS_OVFICB BIT(9) +#define FLITE_REG_CISTATUS_OVFICR BIT(8) +#define FLITE_REG_CISTATUS_IRQ_SRC_OVERFLOW BIT(7) +#define FLITE_REG_CISTATUS_IRQ_SRC_LASTCAPEND BIT(6) +#define FLITE_REG_CISTATUS_IRQ_SRC_FRMSTART BIT(5) +#define FLITE_REG_CISTATUS_IRQ_SRC_FRMEND BIT(4) +#define FLITE_REG_CISTATUS_IRQ_CAM BIT(0) +#define FLITE_REG_CISTATUS_IRQ_MASK (0xf << 4) + +/* Camera Status2 */ +#define FLITE_REG_CISTATUS2 0x44 +#define FLITE_REG_CISTATUS2_LASTCAPEND BIT(1) +#define FLITE_REG_CISTATUS2_FRMEND BIT(0) + +/* Qos Threshold */ +#define FLITE_REG_CITHOLD 0xf0 +#define FLITE_REG_CITHOLD_W_QOS_EN BIT(30) + +/* Camera General Purpose */ +#define FLITE_REG_CIGENERAL 0xfc +/* b0: 1 - camera B, 0 - camera A */ +#define FLITE_REG_CIGENERAL_CAM_B BIT(0) + +#define FLITE_REG_CIFCNTSEQ 0x100 +#define FLITE_REG_CIOSAN(x) (0x200 + (4 * (x))) + +/* ---------------------------------------------------------------------------- + * Function declarations + */ +void flite_hw_reset(struct fimc_lite *dev); +void flite_hw_clear_pending_irq(struct fimc_lite *dev); +u32 flite_hw_get_interrupt_source(struct fimc_lite *dev); +void flite_hw_clear_last_capture_end(struct fimc_lite *dev); +void flite_hw_set_interrupt_mask(struct fimc_lite *dev); +void flite_hw_capture_start(struct fimc_lite *dev); +void flite_hw_capture_stop(struct fimc_lite *dev); +void flite_hw_set_camera_bus(struct fimc_lite *dev, + struct fimc_source_info *s_info); +void flite_hw_set_camera_polarity(struct fimc_lite *dev, + struct fimc_source_info *cam); +void flite_hw_set_window_offset(struct fimc_lite *dev, struct flite_frame *f); +void flite_hw_set_source_format(struct fimc_lite *dev, struct flite_frame *f); + +void flite_hw_set_output_dma(struct fimc_lite *dev, struct flite_frame *f, + bool enable); +void flite_hw_set_dma_window(struct fimc_lite *dev, struct flite_frame *f); +void flite_hw_set_test_pattern(struct fimc_lite *dev, bool on); +void flite_hw_dump_regs(struct fimc_lite *dev, const char *label); +void flite_hw_set_dma_buffer(struct fimc_lite *dev, struct flite_buffer *buf); +void flite_hw_mask_dma_buffer(struct fimc_lite *dev, u32 index); + +static inline void flite_hw_set_dma_buf_mask(struct fimc_lite *dev, u32 mask) +{ + writel(mask, dev->regs + FLITE_REG_CIFCNTSEQ); +} + +#endif /* FIMC_LITE_REG_H */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite.c b/drivers/media/platform/samsung/exynos4-is/fimc-lite.c new file mode 100644 index 000000000..e185a4030 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite.c @@ -0,0 +1,1668 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung EXYNOS FIMC-LITE (camera host interface) driver +* + * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + */ +#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "fimc-core.h" +#include "fimc-lite.h" +#include "fimc-lite-reg.h" + +static int debug; +module_param(debug, int, 0644); + +static const struct fimc_fmt fimc_lite_formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUYV, + .colorspace = V4L2_COLORSPACE_JPEG, + .depth = { 16 }, + .color = FIMC_FMT_YCBYCR422, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .flags = FMT_FLAGS_YUV, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .colorspace = V4L2_COLORSPACE_JPEG, + .depth = { 16 }, + .color = FIMC_FMT_CBYCRY422, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .flags = FMT_FLAGS_YUV, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .colorspace = V4L2_COLORSPACE_JPEG, + .depth = { 16 }, + .color = FIMC_FMT_CRYCBY422, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .flags = FMT_FLAGS_YUV, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .colorspace = V4L2_COLORSPACE_JPEG, + .depth = { 16 }, + .color = FIMC_FMT_YCRYCB422, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .flags = FMT_FLAGS_YUV, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = { 8 }, + .color = FIMC_FMT_RAW8, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .flags = FMT_FLAGS_RAW_BAYER, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = { 16 }, + .color = FIMC_FMT_RAW10, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .flags = FMT_FLAGS_RAW_BAYER, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .colorspace = V4L2_COLORSPACE_SRGB, + .depth = { 16 }, + .color = FIMC_FMT_RAW12, + .memplanes = 1, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .flags = FMT_FLAGS_RAW_BAYER, + }, +}; + +/** + * fimc_lite_find_format - lookup fimc color format by fourcc or media bus code + * @pixelformat: fourcc to match, ignored if null + * @mbus_code: media bus code to match, ignored if null + * @mask: the color format flags to match + * @index: index to the fimc_lite_formats array, ignored if negative + */ +static const struct fimc_fmt *fimc_lite_find_format(const u32 *pixelformat, + const u32 *mbus_code, unsigned int mask, int index) +{ + const struct fimc_fmt *fmt, *def_fmt = NULL; + unsigned int i; + int id = 0; + + if (index >= (int)ARRAY_SIZE(fimc_lite_formats)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(fimc_lite_formats); ++i) { + fmt = &fimc_lite_formats[i]; + if (mask && !(fmt->flags & mask)) + continue; + if (pixelformat && fmt->fourcc == *pixelformat) + return fmt; + if (mbus_code && fmt->mbus_code == *mbus_code) + return fmt; + if (index == id) + def_fmt = fmt; + id++; + } + return def_fmt; +} + +static int fimc_lite_hw_init(struct fimc_lite *fimc, bool isp_output) +{ + struct fimc_source_info *si; + unsigned long flags; + + if (fimc->sensor == NULL) + return -ENXIO; + + if (fimc->inp_frame.fmt == NULL || fimc->out_frame.fmt == NULL) + return -EINVAL; + + /* Get sensor configuration data from the sensor subdev */ + si = v4l2_get_subdev_hostdata(fimc->sensor); + if (!si) + return -EINVAL; + + spin_lock_irqsave(&fimc->slock, flags); + + flite_hw_set_camera_bus(fimc, si); + flite_hw_set_source_format(fimc, &fimc->inp_frame); + flite_hw_set_window_offset(fimc, &fimc->inp_frame); + flite_hw_set_dma_buf_mask(fimc, 0); + flite_hw_set_output_dma(fimc, &fimc->out_frame, !isp_output); + flite_hw_set_interrupt_mask(fimc); + flite_hw_set_test_pattern(fimc, fimc->test_pattern->val); + + if (debug > 0) + flite_hw_dump_regs(fimc, __func__); + + spin_unlock_irqrestore(&fimc->slock, flags); + return 0; +} + +/* + * Reinitialize the driver so it is ready to start the streaming again. + * Set fimc->state to indicate stream off and the hardware shut down state. + * If not suspending (@suspend is false), return any buffers to videobuf2. + * Otherwise put any owned buffers onto the pending buffers queue, so they + * can be re-spun when the device is being resumed. Also perform FIMC + * software reset and disable streaming on the whole pipeline if required. + */ +static int fimc_lite_reinit(struct fimc_lite *fimc, bool suspend) +{ + struct flite_buffer *buf; + unsigned long flags; + bool streaming; + + spin_lock_irqsave(&fimc->slock, flags); + streaming = fimc->state & (1 << ST_SENSOR_STREAM); + + fimc->state &= ~(1 << ST_FLITE_RUN | 1 << ST_FLITE_OFF | + 1 << ST_FLITE_STREAM | 1 << ST_SENSOR_STREAM); + if (suspend) + fimc->state |= (1 << ST_FLITE_SUSPENDED); + else + fimc->state &= ~(1 << ST_FLITE_PENDING | + 1 << ST_FLITE_SUSPENDED); + + /* Release unused buffers */ + while (!suspend && !list_empty(&fimc->pending_buf_q)) { + buf = fimc_lite_pending_queue_pop(fimc); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + /* If suspending put unused buffers onto pending queue */ + while (!list_empty(&fimc->active_buf_q)) { + buf = fimc_lite_active_queue_pop(fimc); + if (suspend) + fimc_lite_pending_queue_add(fimc, buf); + else + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + spin_unlock_irqrestore(&fimc->slock, flags); + + flite_hw_reset(fimc); + + if (!streaming) + return 0; + + return fimc_pipeline_call(&fimc->ve, set_stream, 0); +} + +static int fimc_lite_stop_capture(struct fimc_lite *fimc, bool suspend) +{ + unsigned long flags; + + if (!fimc_lite_active(fimc)) + return 0; + + spin_lock_irqsave(&fimc->slock, flags); + set_bit(ST_FLITE_OFF, &fimc->state); + flite_hw_capture_stop(fimc); + spin_unlock_irqrestore(&fimc->slock, flags); + + wait_event_timeout(fimc->irq_queue, + !test_bit(ST_FLITE_OFF, &fimc->state), + (2*HZ/10)); /* 200 ms */ + + return fimc_lite_reinit(fimc, suspend); +} + +/* Must be called with fimc.slock spinlock held. */ +static void fimc_lite_config_update(struct fimc_lite *fimc) +{ + flite_hw_set_window_offset(fimc, &fimc->inp_frame); + flite_hw_set_dma_window(fimc, &fimc->out_frame); + flite_hw_set_test_pattern(fimc, fimc->test_pattern->val); + clear_bit(ST_FLITE_CONFIG, &fimc->state); +} + +static irqreturn_t flite_irq_handler(int irq, void *priv) +{ + struct fimc_lite *fimc = priv; + struct flite_buffer *vbuf; + unsigned long flags; + u32 intsrc; + + spin_lock_irqsave(&fimc->slock, flags); + + intsrc = flite_hw_get_interrupt_source(fimc); + flite_hw_clear_pending_irq(fimc); + + if (test_and_clear_bit(ST_FLITE_OFF, &fimc->state)) { + wake_up(&fimc->irq_queue); + goto done; + } + + if (intsrc & FLITE_REG_CISTATUS_IRQ_SRC_OVERFLOW) { + clear_bit(ST_FLITE_RUN, &fimc->state); + fimc->events.data_overflow++; + } + + if (intsrc & FLITE_REG_CISTATUS_IRQ_SRC_LASTCAPEND) { + flite_hw_clear_last_capture_end(fimc); + clear_bit(ST_FLITE_STREAM, &fimc->state); + wake_up(&fimc->irq_queue); + } + + if (atomic_read(&fimc->out_path) != FIMC_IO_DMA) + goto done; + + if ((intsrc & FLITE_REG_CISTATUS_IRQ_SRC_FRMSTART) && + test_bit(ST_FLITE_RUN, &fimc->state) && + !list_empty(&fimc->pending_buf_q)) { + vbuf = fimc_lite_pending_queue_pop(fimc); + flite_hw_set_dma_buffer(fimc, vbuf); + fimc_lite_active_queue_add(fimc, vbuf); + } + + if ((intsrc & FLITE_REG_CISTATUS_IRQ_SRC_FRMEND) && + test_bit(ST_FLITE_RUN, &fimc->state) && + !list_empty(&fimc->active_buf_q)) { + vbuf = fimc_lite_active_queue_pop(fimc); + vbuf->vb.vb2_buf.timestamp = ktime_get_ns(); + vbuf->vb.sequence = fimc->frame_count++; + flite_hw_mask_dma_buffer(fimc, vbuf->index); + vb2_buffer_done(&vbuf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } + + if (test_bit(ST_FLITE_CONFIG, &fimc->state)) + fimc_lite_config_update(fimc); + + if (list_empty(&fimc->pending_buf_q)) { + flite_hw_capture_stop(fimc); + clear_bit(ST_FLITE_STREAM, &fimc->state); + } +done: + set_bit(ST_FLITE_RUN, &fimc->state); + spin_unlock_irqrestore(&fimc->slock, flags); + return IRQ_HANDLED; +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct fimc_lite *fimc = q->drv_priv; + unsigned long flags; + int ret; + + spin_lock_irqsave(&fimc->slock, flags); + + fimc->buf_index = 0; + fimc->frame_count = 0; + + spin_unlock_irqrestore(&fimc->slock, flags); + + ret = fimc_lite_hw_init(fimc, false); + if (ret) { + fimc_lite_reinit(fimc, false); + return ret; + } + + set_bit(ST_FLITE_PENDING, &fimc->state); + + if (!list_empty(&fimc->active_buf_q) && + !test_and_set_bit(ST_FLITE_STREAM, &fimc->state)) { + flite_hw_capture_start(fimc); + + if (!test_and_set_bit(ST_SENSOR_STREAM, &fimc->state)) + fimc_pipeline_call(&fimc->ve, set_stream, 1); + } + if (debug > 0) + flite_hw_dump_regs(fimc, __func__); + + return 0; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct fimc_lite *fimc = q->drv_priv; + + if (!fimc_lite_active(fimc)) + return; + + fimc_lite_stop_capture(fimc, false); +} + +static int queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct fimc_lite *fimc = vq->drv_priv; + struct flite_frame *frame = &fimc->out_frame; + const struct fimc_fmt *fmt = frame->fmt; + unsigned long wh = frame->f_width * frame->f_height; + int i; + + if (fmt == NULL) + return -EINVAL; + + if (*num_planes) { + if (*num_planes != fmt->memplanes) + return -EINVAL; + for (i = 0; i < *num_planes; i++) + if (sizes[i] < (wh * fmt->depth[i]) / 8) + return -EINVAL; + return 0; + } + + *num_planes = fmt->memplanes; + + for (i = 0; i < fmt->memplanes; i++) + sizes[i] = (wh * fmt->depth[i]) / 8; + + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct fimc_lite *fimc = vq->drv_priv; + int i; + + if (fimc->out_frame.fmt == NULL) + return -EINVAL; + + for (i = 0; i < fimc->out_frame.fmt->memplanes; i++) { + unsigned long size = fimc->payload[i]; + + if (vb2_plane_size(vb, i) < size) { + v4l2_err(&fimc->ve.vdev, + "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct flite_buffer *buf + = container_of(vbuf, struct flite_buffer, vb); + struct fimc_lite *fimc = vb2_get_drv_priv(vb->vb2_queue); + unsigned long flags; + + spin_lock_irqsave(&fimc->slock, flags); + buf->addr = vb2_dma_contig_plane_dma_addr(vb, 0); + + buf->index = fimc->buf_index++; + if (fimc->buf_index >= fimc->reqbufs_count) + fimc->buf_index = 0; + + if (!test_bit(ST_FLITE_SUSPENDED, &fimc->state) && + !test_bit(ST_FLITE_STREAM, &fimc->state) && + list_empty(&fimc->active_buf_q)) { + flite_hw_set_dma_buffer(fimc, buf); + fimc_lite_active_queue_add(fimc, buf); + } else { + fimc_lite_pending_queue_add(fimc, buf); + } + + if (vb2_is_streaming(&fimc->vb_queue) && + !list_empty(&fimc->pending_buf_q) && + !test_and_set_bit(ST_FLITE_STREAM, &fimc->state)) { + flite_hw_capture_start(fimc); + spin_unlock_irqrestore(&fimc->slock, flags); + + if (!test_and_set_bit(ST_SENSOR_STREAM, &fimc->state)) + fimc_pipeline_call(&fimc->ve, set_stream, 1); + return; + } + spin_unlock_irqrestore(&fimc->slock, flags); +} + +static const struct vb2_ops fimc_lite_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +static void fimc_lite_clear_event_counters(struct fimc_lite *fimc) +{ + unsigned long flags; + + spin_lock_irqsave(&fimc->slock, flags); + memset(&fimc->events, 0, sizeof(fimc->events)); + spin_unlock_irqrestore(&fimc->slock, flags); +} + +static int fimc_lite_open(struct file *file) +{ + struct fimc_lite *fimc = video_drvdata(file); + struct media_entity *me = &fimc->ve.vdev.entity; + int ret; + + mutex_lock(&fimc->lock); + if (atomic_read(&fimc->out_path) != FIMC_IO_DMA) { + ret = -EBUSY; + goto unlock; + } + + set_bit(ST_FLITE_IN_USE, &fimc->state); + ret = pm_runtime_resume_and_get(&fimc->pdev->dev); + if (ret < 0) + goto err_in_use; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto err_pm; + + if (!v4l2_fh_is_singular_file(file) || + atomic_read(&fimc->out_path) != FIMC_IO_DMA) + goto unlock; + + mutex_lock(&me->graph_obj.mdev->graph_mutex); + + ret = fimc_pipeline_call(&fimc->ve, open, me, true); + + /* Mark video pipeline ending at this video node as in use. */ + if (ret == 0) + me->use_count++; + + mutex_unlock(&me->graph_obj.mdev->graph_mutex); + + if (!ret) { + fimc_lite_clear_event_counters(fimc); + goto unlock; + } + + v4l2_fh_release(file); +err_pm: + pm_runtime_put_sync(&fimc->pdev->dev); +err_in_use: + clear_bit(ST_FLITE_IN_USE, &fimc->state); +unlock: + mutex_unlock(&fimc->lock); + return ret; +} + +static int fimc_lite_release(struct file *file) +{ + struct fimc_lite *fimc = video_drvdata(file); + struct media_entity *entity = &fimc->ve.vdev.entity; + + mutex_lock(&fimc->lock); + + if (v4l2_fh_is_singular_file(file) && + atomic_read(&fimc->out_path) == FIMC_IO_DMA) { + if (fimc->streaming) { + video_device_pipeline_stop(&fimc->ve.vdev); + fimc->streaming = false; + } + fimc_lite_stop_capture(fimc, false); + fimc_pipeline_call(&fimc->ve, close); + clear_bit(ST_FLITE_IN_USE, &fimc->state); + + mutex_lock(&entity->graph_obj.mdev->graph_mutex); + entity->use_count--; + mutex_unlock(&entity->graph_obj.mdev->graph_mutex); + } + + _vb2_fop_release(file, NULL); + pm_runtime_put(&fimc->pdev->dev); + clear_bit(ST_FLITE_SUSPENDED, &fimc->state); + + mutex_unlock(&fimc->lock); + return 0; +} + +static const struct v4l2_file_operations fimc_lite_fops = { + .owner = THIS_MODULE, + .open = fimc_lite_open, + .release = fimc_lite_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* + * Format and crop negotiation helpers + */ + +static const struct fimc_fmt *fimc_lite_subdev_try_fmt(struct fimc_lite *fimc, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *format) +{ + struct flite_drvdata *dd = fimc->dd; + struct v4l2_mbus_framefmt *mf = &format->format; + const struct fimc_fmt *fmt = NULL; + + if (format->pad == FLITE_SD_PAD_SINK) { + v4l_bound_align_image(&mf->width, 8, dd->max_width, + ffs(dd->out_width_align) - 1, + &mf->height, 0, dd->max_height, 0, 0); + + fmt = fimc_lite_find_format(NULL, &mf->code, 0, 0); + if (WARN_ON(!fmt)) + return NULL; + + mf->colorspace = fmt->colorspace; + mf->code = fmt->mbus_code; + } else { + struct flite_frame *sink = &fimc->inp_frame; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *rect; + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) { + sink_fmt = v4l2_subdev_get_try_format(&fimc->subdev, + sd_state, + FLITE_SD_PAD_SINK); + + mf->code = sink_fmt->code; + mf->colorspace = sink_fmt->colorspace; + + rect = v4l2_subdev_get_try_crop(&fimc->subdev, + sd_state, + FLITE_SD_PAD_SINK); + } else { + mf->code = sink->fmt->mbus_code; + mf->colorspace = sink->fmt->colorspace; + rect = &sink->rect; + } + + /* Allow changing format only on sink pad */ + mf->width = rect->width; + mf->height = rect->height; + } + + mf->field = V4L2_FIELD_NONE; + + v4l2_dbg(1, debug, &fimc->subdev, "code: %#x (%d), %dx%d\n", + mf->code, mf->colorspace, mf->width, mf->height); + + return fmt; +} + +static void fimc_lite_try_crop(struct fimc_lite *fimc, struct v4l2_rect *r) +{ + struct flite_frame *frame = &fimc->inp_frame; + + v4l_bound_align_image(&r->width, 0, frame->f_width, 0, + &r->height, 0, frame->f_height, 0, 0); + + /* Adjust left/top if cropping rectangle got out of bounds */ + r->left = clamp_t(u32, r->left, 0, frame->f_width - r->width); + r->left = round_down(r->left, fimc->dd->win_hor_offs_align); + r->top = clamp_t(u32, r->top, 0, frame->f_height - r->height); + + v4l2_dbg(1, debug, &fimc->subdev, "(%d,%d)/%dx%d, sink fmt: %dx%d\n", + r->left, r->top, r->width, r->height, + frame->f_width, frame->f_height); +} + +static void fimc_lite_try_compose(struct fimc_lite *fimc, struct v4l2_rect *r) +{ + struct flite_frame *frame = &fimc->out_frame; + struct v4l2_rect *crop_rect = &fimc->inp_frame.rect; + + /* Scaling is not supported so we enforce compose rectangle size + same as size of the sink crop rectangle. */ + r->width = crop_rect->width; + r->height = crop_rect->height; + + /* Adjust left/top if the composing rectangle got out of bounds */ + r->left = clamp_t(u32, r->left, 0, frame->f_width - r->width); + r->left = round_down(r->left, fimc->dd->out_hor_offs_align); + r->top = clamp_t(u32, r->top, 0, fimc->out_frame.f_height - r->height); + + v4l2_dbg(1, debug, &fimc->subdev, "(%d,%d)/%dx%d, source fmt: %dx%d\n", + r->left, r->top, r->width, r->height, + frame->f_width, frame->f_height); +} + +/* + * Video node ioctl operations + */ +static int fimc_lite_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, FIMC_LITE_DRV_NAME, sizeof(cap->driver)); + strscpy(cap->card, FIMC_LITE_DRV_NAME, sizeof(cap->card)); + return 0; +} + +static int fimc_lite_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + const struct fimc_fmt *fmt; + + if (f->index >= ARRAY_SIZE(fimc_lite_formats)) + return -EINVAL; + + fmt = &fimc_lite_formats[f->index]; + f->pixelformat = fmt->fourcc; + + return 0; +} + +static int fimc_lite_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_lite *fimc = video_drvdata(file); + struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp; + struct v4l2_plane_pix_format *plane_fmt = &pixm->plane_fmt[0]; + struct flite_frame *frame = &fimc->out_frame; + const struct fimc_fmt *fmt = frame->fmt; + + plane_fmt->bytesperline = (frame->f_width * fmt->depth[0]) / 8; + plane_fmt->sizeimage = plane_fmt->bytesperline * frame->f_height; + + pixm->num_planes = fmt->memplanes; + pixm->pixelformat = fmt->fourcc; + pixm->width = frame->f_width; + pixm->height = frame->f_height; + pixm->field = V4L2_FIELD_NONE; + pixm->colorspace = fmt->colorspace; + return 0; +} + +static int fimc_lite_try_fmt(struct fimc_lite *fimc, + struct v4l2_pix_format_mplane *pixm, + const struct fimc_fmt **ffmt) +{ + u32 bpl = pixm->plane_fmt[0].bytesperline; + struct flite_drvdata *dd = fimc->dd; + const struct fimc_fmt *inp_fmt = fimc->inp_frame.fmt; + const struct fimc_fmt *fmt; + + if (WARN_ON(inp_fmt == NULL)) + return -EINVAL; + /* + * We allow some flexibility only for YUV formats. In case of raw + * raw Bayer the FIMC-LITE's output format must match its camera + * interface input format. + */ + if (inp_fmt->flags & FMT_FLAGS_YUV) + fmt = fimc_lite_find_format(&pixm->pixelformat, NULL, + inp_fmt->flags, 0); + else + fmt = inp_fmt; + + if (WARN_ON(fmt == NULL)) + return -EINVAL; + if (ffmt) + *ffmt = fmt; + v4l_bound_align_image(&pixm->width, 8, dd->max_width, + ffs(dd->out_width_align) - 1, + &pixm->height, 0, dd->max_height, 0, 0); + + if ((bpl == 0 || ((bpl * 8) / fmt->depth[0]) < pixm->width)) + pixm->plane_fmt[0].bytesperline = (pixm->width * + fmt->depth[0]) / 8; + + if (pixm->plane_fmt[0].sizeimage == 0) + pixm->plane_fmt[0].sizeimage = (pixm->width * pixm->height * + fmt->depth[0]) / 8; + pixm->num_planes = fmt->memplanes; + pixm->pixelformat = fmt->fourcc; + pixm->colorspace = fmt->colorspace; + pixm->field = V4L2_FIELD_NONE; + return 0; +} + +static int fimc_lite_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_lite *fimc = video_drvdata(file); + return fimc_lite_try_fmt(fimc, &f->fmt.pix_mp, NULL); +} + +static int fimc_lite_s_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format_mplane *pixm = &f->fmt.pix_mp; + struct fimc_lite *fimc = video_drvdata(file); + struct flite_frame *frame = &fimc->out_frame; + const struct fimc_fmt *fmt = NULL; + int ret; + + if (vb2_is_busy(&fimc->vb_queue)) + return -EBUSY; + + ret = fimc_lite_try_fmt(fimc, &f->fmt.pix_mp, &fmt); + if (ret < 0) + return ret; + + frame->fmt = fmt; + fimc->payload[0] = max((pixm->width * pixm->height * fmt->depth[0]) / 8, + pixm->plane_fmt[0].sizeimage); + frame->f_width = pixm->width; + frame->f_height = pixm->height; + + return 0; +} + +static int fimc_pipeline_validate(struct fimc_lite *fimc) +{ + struct v4l2_subdev *sd = &fimc->subdev; + struct v4l2_subdev_format sink_fmt, src_fmt; + struct media_pad *pad; + int ret; + + while (1) { + /* Retrieve format at the sink pad */ + pad = &sd->entity.pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + /* Don't call FIMC subdev operation to avoid nested locking */ + if (sd == &fimc->subdev) { + struct flite_frame *ff = &fimc->out_frame; + sink_fmt.format.width = ff->f_width; + sink_fmt.format.height = ff->f_height; + sink_fmt.format.code = fimc->inp_frame.fmt->mbus_code; + } else { + sink_fmt.pad = pad->index; + sink_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, + &sink_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + } + /* Retrieve format at the source pad */ + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + sd = media_entity_to_v4l2_subdev(pad->entity); + src_fmt.pad = pad->index; + src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &src_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + if (src_fmt.format.width != sink_fmt.format.width || + src_fmt.format.height != sink_fmt.format.height || + src_fmt.format.code != sink_fmt.format.code) + return -EPIPE; + } + return 0; +} + +static int fimc_lite_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_lite *fimc = video_drvdata(file); + int ret; + + if (fimc_lite_active(fimc)) + return -EBUSY; + + ret = video_device_pipeline_start(&fimc->ve.vdev, &fimc->ve.pipe->mp); + if (ret < 0) + return ret; + + ret = fimc_pipeline_validate(fimc); + if (ret < 0) + goto err_p_stop; + + fimc->sensor = fimc_find_remote_sensor(&fimc->subdev.entity); + + ret = vb2_ioctl_streamon(file, priv, type); + if (!ret) { + fimc->streaming = true; + return ret; + } + +err_p_stop: + video_device_pipeline_stop(&fimc->ve.vdev); + return 0; +} + +static int fimc_lite_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct fimc_lite *fimc = video_drvdata(file); + int ret; + + ret = vb2_ioctl_streamoff(file, priv, type); + if (ret < 0) + return ret; + + video_device_pipeline_stop(&fimc->ve.vdev); + fimc->streaming = false; + return 0; +} + +static int fimc_lite_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct fimc_lite *fimc = video_drvdata(file); + int ret; + + reqbufs->count = max_t(u32, FLITE_REQ_BUFS_MIN, reqbufs->count); + ret = vb2_ioctl_reqbufs(file, priv, reqbufs); + if (!ret) + fimc->reqbufs_count = reqbufs->count; + + return ret; +} + +static int fimc_lite_g_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct fimc_lite *fimc = video_drvdata(file); + struct flite_frame *f = &fimc->out_frame; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = f->f_width; + sel->r.height = f->f_height; + return 0; + + case V4L2_SEL_TGT_COMPOSE: + sel->r = f->rect; + return 0; + } + + return -EINVAL; +} + +static int fimc_lite_s_selection(struct file *file, void *fh, + struct v4l2_selection *sel) +{ + struct fimc_lite *fimc = video_drvdata(file); + struct flite_frame *f = &fimc->out_frame; + struct v4l2_rect rect = sel->r; + unsigned long flags; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + sel->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + + fimc_lite_try_compose(fimc, &rect); + + if ((sel->flags & V4L2_SEL_FLAG_LE) && + !v4l2_rect_enclosed(&rect, &sel->r)) + return -ERANGE; + + if ((sel->flags & V4L2_SEL_FLAG_GE) && + !v4l2_rect_enclosed(&sel->r, &rect)) + return -ERANGE; + + sel->r = rect; + spin_lock_irqsave(&fimc->slock, flags); + f->rect = rect; + set_bit(ST_FLITE_CONFIG, &fimc->state); + spin_unlock_irqrestore(&fimc->slock, flags); + + return 0; +} + +static const struct v4l2_ioctl_ops fimc_lite_ioctl_ops = { + .vidioc_querycap = fimc_lite_querycap, + .vidioc_enum_fmt_vid_cap = fimc_lite_enum_fmt, + .vidioc_try_fmt_vid_cap_mplane = fimc_lite_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = fimc_lite_s_fmt_mplane, + .vidioc_g_fmt_vid_cap_mplane = fimc_lite_g_fmt_mplane, + .vidioc_g_selection = fimc_lite_g_selection, + .vidioc_s_selection = fimc_lite_s_selection, + .vidioc_reqbufs = fimc_lite_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = fimc_lite_streamon, + .vidioc_streamoff = fimc_lite_streamoff, +}; + +/* Capture subdev media entity operations */ +static int fimc_lite_link_setup(struct media_entity *entity, + const struct media_pad *local, + const struct media_pad *remote, u32 flags) +{ + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity); + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + int ret = 0; + + if (WARN_ON(fimc == NULL)) + return 0; + + v4l2_dbg(1, debug, sd, "%s: %s --> %s, flags: 0x%x. source_id: 0x%x\n", + __func__, remote->entity->name, local->entity->name, + flags, fimc->source_subdev_grp_id); + + switch (local->index) { + case FLITE_SD_PAD_SINK: + if (flags & MEDIA_LNK_FL_ENABLED) { + if (fimc->source_subdev_grp_id == 0) + fimc->source_subdev_grp_id = sd->grp_id; + else + ret = -EBUSY; + } else { + fimc->source_subdev_grp_id = 0; + fimc->sensor = NULL; + } + break; + + case FLITE_SD_PAD_SOURCE_DMA: + if (!(flags & MEDIA_LNK_FL_ENABLED)) + atomic_set(&fimc->out_path, FIMC_IO_NONE); + else + atomic_set(&fimc->out_path, FIMC_IO_DMA); + break; + + case FLITE_SD_PAD_SOURCE_ISP: + if (!(flags & MEDIA_LNK_FL_ENABLED)) + atomic_set(&fimc->out_path, FIMC_IO_NONE); + else + atomic_set(&fimc->out_path, FIMC_IO_ISP); + break; + + default: + v4l2_err(sd, "Invalid pad index\n"); + ret = -EINVAL; + } + mb(); + + return ret; +} + +static const struct media_entity_operations fimc_lite_subdev_media_ops = { + .link_setup = fimc_lite_link_setup, +}; + +static int fimc_lite_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct fimc_fmt *fmt; + + fmt = fimc_lite_find_format(NULL, NULL, 0, code->index); + if (!fmt) + return -EINVAL; + code->code = fmt->mbus_code; + return 0; +} + +static struct v4l2_mbus_framefmt *__fimc_lite_subdev_get_try_fmt( + struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, unsigned int pad) +{ + if (pad != FLITE_SD_PAD_SINK) + pad = FLITE_SD_PAD_SOURCE_DMA; + + return v4l2_subdev_get_try_format(sd, sd_state, pad); +} + +static int fimc_lite_subdev_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct flite_frame *f = &fimc->inp_frame; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = __fimc_lite_subdev_get_try_fmt(sd, sd_state, fmt->pad); + fmt->format = *mf; + return 0; + } + + mutex_lock(&fimc->lock); + mf->colorspace = f->fmt->colorspace; + mf->code = f->fmt->mbus_code; + + if (fmt->pad == FLITE_SD_PAD_SINK) { + /* full camera input frame size */ + mf->width = f->f_width; + mf->height = f->f_height; + } else { + /* crop size */ + mf->width = f->rect.width; + mf->height = f->rect.height; + } + mutex_unlock(&fimc->lock); + return 0; +} + +static int fimc_lite_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct flite_frame *sink = &fimc->inp_frame; + struct flite_frame *source = &fimc->out_frame; + const struct fimc_fmt *ffmt; + + v4l2_dbg(1, debug, sd, "pad%d: code: 0x%x, %dx%d\n", + fmt->pad, mf->code, mf->width, mf->height); + + mutex_lock(&fimc->lock); + + if ((atomic_read(&fimc->out_path) == FIMC_IO_ISP && + media_entity_is_streaming(&sd->entity)) || + (atomic_read(&fimc->out_path) == FIMC_IO_DMA && + vb2_is_busy(&fimc->vb_queue))) { + mutex_unlock(&fimc->lock); + return -EBUSY; + } + + ffmt = fimc_lite_subdev_try_fmt(fimc, sd_state, fmt); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + struct v4l2_mbus_framefmt *src_fmt; + + mf = __fimc_lite_subdev_get_try_fmt(sd, sd_state, fmt->pad); + *mf = fmt->format; + + if (fmt->pad == FLITE_SD_PAD_SINK) { + unsigned int pad = FLITE_SD_PAD_SOURCE_DMA; + src_fmt = __fimc_lite_subdev_get_try_fmt(sd, sd_state, + pad); + *src_fmt = *mf; + } + + mutex_unlock(&fimc->lock); + return 0; + } + + if (fmt->pad == FLITE_SD_PAD_SINK) { + sink->f_width = mf->width; + sink->f_height = mf->height; + sink->fmt = ffmt; + /* Set sink crop rectangle */ + sink->rect.width = mf->width; + sink->rect.height = mf->height; + sink->rect.left = 0; + sink->rect.top = 0; + /* Reset source format and crop rectangle */ + source->rect = sink->rect; + source->f_width = mf->width; + source->f_height = mf->height; + } + + mutex_unlock(&fimc->lock); + return 0; +} + +static int fimc_lite_subdev_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + struct flite_frame *f = &fimc->inp_frame; + + if ((sel->target != V4L2_SEL_TGT_CROP && + sel->target != V4L2_SEL_TGT_CROP_BOUNDS) || + sel->pad != FLITE_SD_PAD_SINK) + return -EINVAL; + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + sel->r = *v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); + return 0; + } + + mutex_lock(&fimc->lock); + if (sel->target == V4L2_SEL_TGT_CROP) { + sel->r = f->rect; + } else { + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = f->f_width; + sel->r.height = f->f_height; + } + mutex_unlock(&fimc->lock); + + v4l2_dbg(1, debug, sd, "%s: (%d,%d) %dx%d, f_w: %d, f_h: %d\n", + __func__, f->rect.left, f->rect.top, f->rect.width, + f->rect.height, f->f_width, f->f_height); + + return 0; +} + +static int fimc_lite_subdev_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + struct flite_frame *f = &fimc->inp_frame; + int ret = 0; + + if (sel->target != V4L2_SEL_TGT_CROP || sel->pad != FLITE_SD_PAD_SINK) + return -EINVAL; + + mutex_lock(&fimc->lock); + fimc_lite_try_crop(fimc, &sel->r); + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_crop(sd, sd_state, sel->pad) = sel->r; + } else { + unsigned long flags; + spin_lock_irqsave(&fimc->slock, flags); + f->rect = sel->r; + /* Same crop rectangle on the source pad */ + fimc->out_frame.rect = sel->r; + set_bit(ST_FLITE_CONFIG, &fimc->state); + spin_unlock_irqrestore(&fimc->slock, flags); + } + mutex_unlock(&fimc->lock); + + v4l2_dbg(1, debug, sd, "%s: (%d,%d) %dx%d, f_w: %d, f_h: %d\n", + __func__, f->rect.left, f->rect.top, f->rect.width, + f->rect.height, f->f_width, f->f_height); + + return ret; +} + +static int fimc_lite_subdev_s_stream(struct v4l2_subdev *sd, int on) +{ + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + unsigned long flags; + int ret; + + /* + * Find sensor subdev linked to FIMC-LITE directly or through + * MIPI-CSIS. This is required for configuration where FIMC-LITE + * is used as a subdev only and feeds data internally to FIMC-IS. + * The pipeline links are protected through entity.pipe so there is no + * need to take the media graph mutex here. + */ + fimc->sensor = fimc_find_remote_sensor(&sd->entity); + + if (atomic_read(&fimc->out_path) != FIMC_IO_ISP) + return -ENOIOCTLCMD; + + mutex_lock(&fimc->lock); + if (on) { + flite_hw_reset(fimc); + ret = fimc_lite_hw_init(fimc, true); + if (!ret) { + spin_lock_irqsave(&fimc->slock, flags); + flite_hw_capture_start(fimc); + spin_unlock_irqrestore(&fimc->slock, flags); + } + } else { + set_bit(ST_FLITE_OFF, &fimc->state); + + spin_lock_irqsave(&fimc->slock, flags); + flite_hw_capture_stop(fimc); + spin_unlock_irqrestore(&fimc->slock, flags); + + ret = wait_event_timeout(fimc->irq_queue, + !test_bit(ST_FLITE_OFF, &fimc->state), + msecs_to_jiffies(200)); + if (ret == 0) + v4l2_err(sd, "s_stream(0) timeout\n"); + clear_bit(ST_FLITE_RUN, &fimc->state); + } + + mutex_unlock(&fimc->lock); + return ret; +} + +static int fimc_lite_log_status(struct v4l2_subdev *sd) +{ + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + + flite_hw_dump_regs(fimc, __func__); + return 0; +} + +static int fimc_lite_subdev_registered(struct v4l2_subdev *sd) +{ + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + struct vb2_queue *q = &fimc->vb_queue; + struct video_device *vfd = &fimc->ve.vdev; + int ret; + + memset(vfd, 0, sizeof(*vfd)); + atomic_set(&fimc->out_path, FIMC_IO_DMA); + + snprintf(vfd->name, sizeof(vfd->name), "fimc-lite.%d.capture", + fimc->index); + + vfd->fops = &fimc_lite_fops; + vfd->ioctl_ops = &fimc_lite_ioctl_ops; + vfd->v4l2_dev = sd->v4l2_dev; + vfd->minor = -1; + vfd->release = video_device_release_empty; + vfd->queue = q; + vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING; + fimc->reqbufs_count = 0; + + INIT_LIST_HEAD(&fimc->pending_buf_q); + INIT_LIST_HEAD(&fimc->active_buf_q); + + memset(q, 0, sizeof(*q)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->ops = &fimc_lite_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct flite_buffer); + q->drv_priv = fimc; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &fimc->lock; + q->dev = &fimc->pdev->dev; + + ret = vb2_queue_init(q); + if (ret < 0) + return ret; + + fimc->vd_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vfd->entity, 1, &fimc->vd_pad); + if (ret < 0) + return ret; + + video_set_drvdata(vfd, fimc); + fimc->ve.pipe = v4l2_get_subdev_hostdata(sd); + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + media_entity_cleanup(&vfd->entity); + fimc->ve.pipe = NULL; + return ret; + } + + v4l2_info(sd->v4l2_dev, "Registered %s as /dev/%s\n", + vfd->name, video_device_node_name(vfd)); + return 0; +} + +static void fimc_lite_subdev_unregistered(struct v4l2_subdev *sd) +{ + struct fimc_lite *fimc = v4l2_get_subdevdata(sd); + + if (fimc == NULL) + return; + + mutex_lock(&fimc->lock); + + if (video_is_registered(&fimc->ve.vdev)) { + video_unregister_device(&fimc->ve.vdev); + media_entity_cleanup(&fimc->ve.vdev.entity); + fimc->ve.pipe = NULL; + } + + mutex_unlock(&fimc->lock); +} + +static const struct v4l2_subdev_internal_ops fimc_lite_subdev_internal_ops = { + .registered = fimc_lite_subdev_registered, + .unregistered = fimc_lite_subdev_unregistered, +}; + +static const struct v4l2_subdev_pad_ops fimc_lite_subdev_pad_ops = { + .enum_mbus_code = fimc_lite_subdev_enum_mbus_code, + .get_selection = fimc_lite_subdev_get_selection, + .set_selection = fimc_lite_subdev_set_selection, + .get_fmt = fimc_lite_subdev_get_fmt, + .set_fmt = fimc_lite_subdev_set_fmt, +}; + +static const struct v4l2_subdev_video_ops fimc_lite_subdev_video_ops = { + .s_stream = fimc_lite_subdev_s_stream, +}; + +static const struct v4l2_subdev_core_ops fimc_lite_core_ops = { + .log_status = fimc_lite_log_status, +}; + +static const struct v4l2_subdev_ops fimc_lite_subdev_ops = { + .core = &fimc_lite_core_ops, + .video = &fimc_lite_subdev_video_ops, + .pad = &fimc_lite_subdev_pad_ops, +}; + +static int fimc_lite_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct fimc_lite *fimc = container_of(ctrl->handler, struct fimc_lite, + ctrl_handler); + set_bit(ST_FLITE_CONFIG, &fimc->state); + return 0; +} + +static const struct v4l2_ctrl_ops fimc_lite_ctrl_ops = { + .s_ctrl = fimc_lite_s_ctrl, +}; + +static const struct v4l2_ctrl_config fimc_lite_ctrl = { + .ops = &fimc_lite_ctrl_ops, + .id = V4L2_CTRL_CLASS_USER | 0x1001, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Test Pattern 640x480", + .step = 1, +}; + +static void fimc_lite_set_default_config(struct fimc_lite *fimc) +{ + struct flite_frame *sink = &fimc->inp_frame; + struct flite_frame *source = &fimc->out_frame; + + sink->fmt = &fimc_lite_formats[0]; + sink->f_width = FLITE_DEFAULT_WIDTH; + sink->f_height = FLITE_DEFAULT_HEIGHT; + + sink->rect.width = FLITE_DEFAULT_WIDTH; + sink->rect.height = FLITE_DEFAULT_HEIGHT; + sink->rect.left = 0; + sink->rect.top = 0; + + *source = *sink; +} + +static int fimc_lite_create_capture_subdev(struct fimc_lite *fimc) +{ + struct v4l2_ctrl_handler *handler = &fimc->ctrl_handler; + struct v4l2_subdev *sd = &fimc->subdev; + int ret; + + v4l2_subdev_init(sd, &fimc_lite_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, sizeof(sd->name), "FIMC-LITE.%d", fimc->index); + + fimc->subdev_pads[FLITE_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + fimc->subdev_pads[FLITE_SD_PAD_SOURCE_DMA].flags = MEDIA_PAD_FL_SOURCE; + fimc->subdev_pads[FLITE_SD_PAD_SOURCE_ISP].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&sd->entity, FLITE_SD_PADS_NUM, + fimc->subdev_pads); + if (ret) + return ret; + + v4l2_ctrl_handler_init(handler, 1); + fimc->test_pattern = v4l2_ctrl_new_custom(handler, &fimc_lite_ctrl, + NULL); + if (handler->error) { + media_entity_cleanup(&sd->entity); + return handler->error; + } + + sd->ctrl_handler = handler; + sd->internal_ops = &fimc_lite_subdev_internal_ops; + sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER; + sd->entity.ops = &fimc_lite_subdev_media_ops; + sd->owner = THIS_MODULE; + v4l2_set_subdevdata(sd, fimc); + + return 0; +} + +static void fimc_lite_unregister_capture_subdev(struct fimc_lite *fimc) +{ + struct v4l2_subdev *sd = &fimc->subdev; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&fimc->ctrl_handler); + v4l2_set_subdevdata(sd, NULL); +} + +static void fimc_lite_clk_put(struct fimc_lite *fimc) +{ + if (IS_ERR(fimc->clock)) + return; + + clk_put(fimc->clock); + fimc->clock = ERR_PTR(-EINVAL); +} + +static int fimc_lite_clk_get(struct fimc_lite *fimc) +{ + fimc->clock = clk_get(&fimc->pdev->dev, FLITE_CLK_NAME); + return PTR_ERR_OR_ZERO(fimc->clock); +} + +static const struct of_device_id flite_of_match[]; + +static int fimc_lite_probe(struct platform_device *pdev) +{ + struct flite_drvdata *drv_data = NULL; + struct device *dev = &pdev->dev; + const struct of_device_id *of_id; + struct fimc_lite *fimc; + struct resource *res; + int ret; + int irq; + + if (!dev->of_node) + return -ENODEV; + + fimc = devm_kzalloc(dev, sizeof(*fimc), GFP_KERNEL); + if (!fimc) + return -ENOMEM; + + of_id = of_match_node(flite_of_match, dev->of_node); + if (of_id) + drv_data = (struct flite_drvdata *)of_id->data; + fimc->index = of_alias_get_id(dev->of_node, "fimc-lite"); + + if (!drv_data || fimc->index >= drv_data->num_instances || + fimc->index < 0) { + dev_err(dev, "Wrong %pOF node alias\n", dev->of_node); + return -EINVAL; + } + + fimc->dd = drv_data; + fimc->pdev = pdev; + + init_waitqueue_head(&fimc->irq_queue); + spin_lock_init(&fimc->slock); + mutex_init(&fimc->lock); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + fimc->regs = devm_ioremap_resource(dev, res); + if (IS_ERR(fimc->regs)) + return PTR_ERR(fimc->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = fimc_lite_clk_get(fimc); + if (ret) + return ret; + + ret = devm_request_irq(dev, irq, flite_irq_handler, + 0, dev_name(dev), fimc); + if (ret) { + dev_err(dev, "Failed to install irq (%d)\n", ret); + goto err_clk_put; + } + + /* The video node will be created within the subdev's registered() op */ + ret = fimc_lite_create_capture_subdev(fimc); + if (ret) + goto err_clk_put; + + platform_set_drvdata(pdev, fimc); + pm_runtime_enable(dev); + + if (!pm_runtime_enabled(dev)) { + ret = clk_prepare_enable(fimc->clock); + if (ret < 0) + goto err_sd; + } + + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); + + fimc_lite_set_default_config(fimc); + + dev_dbg(dev, "FIMC-LITE.%d registered successfully\n", + fimc->index); + return 0; + +err_sd: + fimc_lite_unregister_capture_subdev(fimc); +err_clk_put: + fimc_lite_clk_put(fimc); + return ret; +} + +#ifdef CONFIG_PM +static int fimc_lite_runtime_resume(struct device *dev) +{ + struct fimc_lite *fimc = dev_get_drvdata(dev); + + clk_prepare_enable(fimc->clock); + return 0; +} + +static int fimc_lite_runtime_suspend(struct device *dev) +{ + struct fimc_lite *fimc = dev_get_drvdata(dev); + + clk_disable_unprepare(fimc->clock); + return 0; +} +#endif + +#ifdef CONFIG_PM_SLEEP +static int fimc_lite_resume(struct device *dev) +{ + struct fimc_lite *fimc = dev_get_drvdata(dev); + struct flite_buffer *buf; + unsigned long flags; + int i; + + spin_lock_irqsave(&fimc->slock, flags); + if (!test_and_clear_bit(ST_LPM, &fimc->state) || + !test_bit(ST_FLITE_IN_USE, &fimc->state)) { + spin_unlock_irqrestore(&fimc->slock, flags); + return 0; + } + flite_hw_reset(fimc); + spin_unlock_irqrestore(&fimc->slock, flags); + + if (!test_and_clear_bit(ST_FLITE_SUSPENDED, &fimc->state)) + return 0; + + INIT_LIST_HEAD(&fimc->active_buf_q); + fimc_pipeline_call(&fimc->ve, open, + &fimc->ve.vdev.entity, false); + fimc_lite_hw_init(fimc, atomic_read(&fimc->out_path) == FIMC_IO_ISP); + clear_bit(ST_FLITE_SUSPENDED, &fimc->state); + + for (i = 0; i < fimc->reqbufs_count; i++) { + if (list_empty(&fimc->pending_buf_q)) + break; + buf = fimc_lite_pending_queue_pop(fimc); + buffer_queue(&buf->vb.vb2_buf); + } + return 0; +} + +static int fimc_lite_suspend(struct device *dev) +{ + struct fimc_lite *fimc = dev_get_drvdata(dev); + bool suspend = test_bit(ST_FLITE_IN_USE, &fimc->state); + int ret; + + if (test_and_set_bit(ST_LPM, &fimc->state)) + return 0; + + ret = fimc_lite_stop_capture(fimc, suspend); + if (ret < 0 || !fimc_lite_active(fimc)) + return ret; + + return fimc_pipeline_call(&fimc->ve, close); +} +#endif /* CONFIG_PM_SLEEP */ + +static int fimc_lite_remove(struct platform_device *pdev) +{ + struct fimc_lite *fimc = platform_get_drvdata(pdev); + struct device *dev = &pdev->dev; + + if (!pm_runtime_enabled(dev)) + clk_disable_unprepare(fimc->clock); + + pm_runtime_disable(dev); + pm_runtime_set_suspended(dev); + fimc_lite_unregister_capture_subdev(fimc); + vb2_dma_contig_clear_max_seg_size(dev); + fimc_lite_clk_put(fimc); + + dev_info(dev, "Driver unloaded\n"); + return 0; +} + +static const struct dev_pm_ops fimc_lite_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(fimc_lite_suspend, fimc_lite_resume) + SET_RUNTIME_PM_OPS(fimc_lite_runtime_suspend, fimc_lite_runtime_resume, + NULL) +}; + +/* EXYNOS4412 */ +static struct flite_drvdata fimc_lite_drvdata_exynos4 = { + .max_width = 8192, + .max_height = 8192, + .out_width_align = 8, + .win_hor_offs_align = 2, + .out_hor_offs_align = 8, + .max_dma_bufs = 1, + .num_instances = 2, +}; + +/* EXYNOS5250 */ +static struct flite_drvdata fimc_lite_drvdata_exynos5 = { + .max_width = 8192, + .max_height = 8192, + .out_width_align = 8, + .win_hor_offs_align = 2, + .out_hor_offs_align = 8, + .max_dma_bufs = 32, + .num_instances = 3, +}; + +static const struct of_device_id flite_of_match[] = { + { + .compatible = "samsung,exynos4212-fimc-lite", + .data = &fimc_lite_drvdata_exynos4, + }, + { + .compatible = "samsung,exynos5250-fimc-lite", + .data = &fimc_lite_drvdata_exynos5, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, flite_of_match); + +static struct platform_driver fimc_lite_driver = { + .probe = fimc_lite_probe, + .remove = fimc_lite_remove, + .driver = { + .of_match_table = flite_of_match, + .name = FIMC_LITE_DRV_NAME, + .pm = &fimc_lite_pm_ops, + } +}; +module_platform_driver(fimc_lite_driver); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" FIMC_LITE_DRV_NAME); diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-lite.h b/drivers/media/platform/samsung/exynos4-is/fimc-lite.h new file mode 100644 index 000000000..ddf29e0b5 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-lite.h @@ -0,0 +1,224 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + */ + +#ifndef FIMC_LITE_H_ +#define FIMC_LITE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#define FIMC_LITE_DRV_NAME "exynos-fimc-lite" +#define FLITE_CLK_NAME "flite" +#define FIMC_LITE_MAX_DEVS 3 +#define FLITE_REQ_BUFS_MIN 2 +#define FLITE_DEFAULT_WIDTH 640 +#define FLITE_DEFAULT_HEIGHT 480 + +/* Bit index definitions for struct fimc_lite::state */ +enum { + ST_FLITE_LPM, + ST_FLITE_PENDING, + ST_FLITE_RUN, + ST_FLITE_STREAM, + ST_FLITE_SUSPENDED, + ST_FLITE_OFF, + ST_FLITE_IN_USE, + ST_FLITE_CONFIG, + ST_SENSOR_STREAM, +}; + +#define FLITE_SD_PAD_SINK 0 +#define FLITE_SD_PAD_SOURCE_DMA 1 +#define FLITE_SD_PAD_SOURCE_ISP 2 +#define FLITE_SD_PADS_NUM 3 + +/** + * struct flite_drvdata - FIMC-LITE IP variant data structure + * @max_width: maximum camera interface input width in pixels + * @max_height: maximum camera interface input height in pixels + * @out_width_align: minimum output width alignment in pixels + * @win_hor_offs_align: minimum camera interface crop window horizontal + * offset alignment in pixels + * @out_hor_offs_align: minimum output DMA compose rectangle horizontal + * offset alignment in pixels + * @max_dma_bufs: number of output DMA buffer start address registers + * @num_instances: total number of FIMC-LITE IP instances available + */ +struct flite_drvdata { + unsigned short max_width; + unsigned short max_height; + unsigned short out_width_align; + unsigned short win_hor_offs_align; + unsigned short out_hor_offs_align; + unsigned short max_dma_bufs; + unsigned short num_instances; +}; + +struct fimc_lite_events { + unsigned int data_overflow; +}; + +#define FLITE_MAX_PLANES 1 + +/** + * struct flite_frame - source/target frame properties + * @f_width: full pixel width + * @f_height: full pixel height + * @rect: crop/composition rectangle + * @fmt: pointer to pixel format description data structure + */ +struct flite_frame { + u16 f_width; + u16 f_height; + struct v4l2_rect rect; + const struct fimc_fmt *fmt; +}; + +/** + * struct flite_buffer - video buffer structure + * @vb: vb2 buffer + * @list: list head for the buffers queue + * @addr: DMA buffer start address + * @index: DMA start address register's index + */ +struct flite_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; + dma_addr_t addr; + unsigned short index; +}; + +/** + * struct fimc_lite - fimc lite structure + * @pdev: pointer to FIMC-LITE platform device + * @dd: SoC specific driver data structure + * @ve: exynos video device entity structure + * @v4l2_dev: pointer to top the level v4l2_device + * @fh: v4l2 file handle + * @subdev: FIMC-LITE subdev + * @vd_pad: media (sink) pad for the capture video node + * @subdev_pads: the subdev media pads + * @sensor: sensor subdev attached to FIMC-LITE directly or through MIPI-CSIS + * @ctrl_handler: v4l2 control handler + * @test_pattern: test pattern controls + * @index: FIMC-LITE platform device index + * @pipeline: video capture pipeline data structure + * @pipeline_ops: media pipeline ops for the video node driver + * @slock: spinlock protecting this data structure and the hw registers + * @lock: mutex serializing video device and the subdev operations + * @clock: FIMC-LITE gate clock + * @regs: memory mapped io registers + * @irq_queue: interrupt handler waitqueue + * @payload: image size in bytes (w x h x bpp) + * @inp_frame: camera input frame structure + * @out_frame: DMA output frame structure + * @out_path: output data path (DMA or FIFO) + * @source_subdev_grp_id: source subdev group id + * @state: driver state flags + * @pending_buf_q: pending buffers queue head + * @active_buf_q: the queue head of buffers scheduled in hardware + * @vb_queue: vb2 buffers queue + * @buf_index: helps to keep track of the DMA start address register index + * @active_buf_count: number of video buffers scheduled in hardware + * @frame_count: the captured frames counter + * @reqbufs_count: the number of buffers requested with REQBUFS ioctl + * @events: event info + * @streaming: is streaming in progress? + */ +struct fimc_lite { + struct platform_device *pdev; + struct flite_drvdata *dd; + struct exynos_video_entity ve; + struct v4l2_device *v4l2_dev; + struct v4l2_fh fh; + struct v4l2_subdev subdev; + struct media_pad vd_pad; + struct media_pad subdev_pads[FLITE_SD_PADS_NUM]; + struct v4l2_subdev *sensor; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *test_pattern; + int index; + + struct mutex lock; + spinlock_t slock; + + struct clk *clock; + void __iomem *regs; + wait_queue_head_t irq_queue; + + unsigned long payload[FLITE_MAX_PLANES]; + struct flite_frame inp_frame; + struct flite_frame out_frame; + atomic_t out_path; + unsigned int source_subdev_grp_id; + + unsigned long state; + struct list_head pending_buf_q; + struct list_head active_buf_q; + struct vb2_queue vb_queue; + unsigned short buf_index; + unsigned int frame_count; + unsigned int reqbufs_count; + + struct fimc_lite_events events; + bool streaming; +}; + +static inline bool fimc_lite_active(struct fimc_lite *fimc) +{ + unsigned long flags; + bool ret; + + spin_lock_irqsave(&fimc->slock, flags); + ret = fimc->state & (1 << ST_FLITE_RUN) || + fimc->state & (1 << ST_FLITE_PENDING); + spin_unlock_irqrestore(&fimc->slock, flags); + return ret; +} + +static inline void fimc_lite_active_queue_add(struct fimc_lite *dev, + struct flite_buffer *buf) +{ + list_add_tail(&buf->list, &dev->active_buf_q); +} + +static inline struct flite_buffer *fimc_lite_active_queue_pop( + struct fimc_lite *dev) +{ + struct flite_buffer *buf = list_entry(dev->active_buf_q.next, + struct flite_buffer, list); + list_del(&buf->list); + return buf; +} + +static inline void fimc_lite_pending_queue_add(struct fimc_lite *dev, + struct flite_buffer *buf) +{ + list_add_tail(&buf->list, &dev->pending_buf_q); +} + +static inline struct flite_buffer *fimc_lite_pending_queue_pop( + struct fimc_lite *dev) +{ + struct flite_buffer *buf = list_entry(dev->pending_buf_q.next, + struct flite_buffer, list); + list_del(&buf->list); + return buf; +} + +#endif /* FIMC_LITE_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-m2m.c b/drivers/media/platform/samsung/exynos4-is/fimc-m2m.c new file mode 100644 index 000000000..df8e2aa45 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-m2m.c @@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Samsung S5P/EXYNOS4 SoC series FIMC (video postprocessor) driver + * + * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd. + * Sylwester Nawrocki + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "fimc-core.h" +#include "fimc-reg.h" +#include "media-dev.h" + +static unsigned int get_m2m_fmt_flags(unsigned int stream_type) +{ + if (stream_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return FMT_FLAGS_M2M_IN; + else + return FMT_FLAGS_M2M_OUT; +} + +void fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state) +{ + struct vb2_v4l2_buffer *src_vb, *dst_vb; + + if (!ctx || !ctx->fh.m2m_ctx) + return; + + src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (src_vb) + v4l2_m2m_buf_done(src_vb, vb_state); + if (dst_vb) + v4l2_m2m_buf_done(dst_vb, vb_state); + if (src_vb && dst_vb) + v4l2_m2m_job_finish(ctx->fimc_dev->m2m.m2m_dev, + ctx->fh.m2m_ctx); +} + +/* Complete the transaction which has been scheduled for execution. */ +static void fimc_m2m_shutdown(struct fimc_ctx *ctx) +{ + struct fimc_dev *fimc = ctx->fimc_dev; + + if (!fimc_m2m_pending(fimc)) + return; + + fimc_ctx_state_set(FIMC_CTX_SHUT, ctx); + + wait_event_timeout(fimc->irq_queue, + !fimc_ctx_state_is_set(FIMC_CTX_SHUT, ctx), + FIMC_SHUTDOWN_TIMEOUT); +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct fimc_ctx *ctx = q->drv_priv; + + return pm_runtime_resume_and_get(&ctx->fimc_dev->pdev->dev); +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct fimc_ctx *ctx = q->drv_priv; + + fimc_m2m_shutdown(ctx); + fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR); + pm_runtime_put(&ctx->fimc_dev->pdev->dev); +} + +static void fimc_device_run(void *priv) +{ + struct vb2_v4l2_buffer *src_vb, *dst_vb; + struct fimc_ctx *ctx = priv; + struct fimc_frame *sf, *df; + struct fimc_dev *fimc; + unsigned long flags; + int ret; + + if (WARN(!ctx, "Null context\n")) + return; + + fimc = ctx->fimc_dev; + spin_lock_irqsave(&fimc->slock, flags); + + set_bit(ST_M2M_PEND, &fimc->state); + sf = &ctx->s_frame; + df = &ctx->d_frame; + + if (ctx->state & FIMC_PARAMS) { + /* Prepare the DMA offsets for scaler */ + fimc_prepare_dma_offset(ctx, sf); + fimc_prepare_dma_offset(ctx, df); + } + + src_vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + ret = fimc_prepare_addr(ctx, &src_vb->vb2_buf, sf, &sf->addr); + if (ret) + goto dma_unlock; + + dst_vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + ret = fimc_prepare_addr(ctx, &dst_vb->vb2_buf, df, &df->addr); + if (ret) + goto dma_unlock; + + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + dst_vb->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->flags |= + src_vb->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + + /* Reconfigure hardware if the context has changed. */ + if (fimc->m2m.ctx != ctx) { + ctx->state |= FIMC_PARAMS; + fimc->m2m.ctx = ctx; + } + + if (ctx->state & FIMC_PARAMS) { + fimc_set_yuv_order(ctx); + fimc_hw_set_input_path(ctx); + fimc_hw_set_in_dma(ctx); + ret = fimc_set_scaler_info(ctx); + if (ret) + goto dma_unlock; + fimc_hw_set_prescaler(ctx); + fimc_hw_set_mainscaler(ctx); + fimc_hw_set_target_format(ctx); + fimc_hw_set_rotation(ctx); + fimc_hw_set_effect(ctx); + fimc_hw_set_out_dma(ctx); + if (fimc->drv_data->alpha_color) + fimc_hw_set_rgb_alpha(ctx); + fimc_hw_set_output_path(ctx); + } + fimc_hw_set_input_addr(fimc, &sf->addr); + fimc_hw_set_output_addr(fimc, &df->addr, -1); + + fimc_activate_capture(ctx); + ctx->state &= (FIMC_CTX_M2M | FIMC_CTX_CAP); + fimc_hw_activate_input_dma(fimc, true); + +dma_unlock: + spin_unlock_irqrestore(&fimc->slock, flags); +} + +static void fimc_job_abort(void *priv) +{ + fimc_m2m_shutdown(priv); +} + +static int fimc_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct fimc_ctx *ctx = vb2_get_drv_priv(vq); + struct fimc_frame *f; + int i; + + f = ctx_get_frame(ctx, vq->type); + if (IS_ERR(f)) + return PTR_ERR(f); + /* + * Return number of non-contiguous planes (plane buffers) + * depending on the configured color format. + */ + if (!f->fmt) + return -EINVAL; + + *num_planes = f->fmt->memplanes; + for (i = 0; i < f->fmt->memplanes; i++) + sizes[i] = f->payload[i]; + return 0; +} + +static int fimc_buf_prepare(struct vb2_buffer *vb) +{ + struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct fimc_frame *frame; + int i; + + frame = ctx_get_frame(ctx, vb->vb2_queue->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + for (i = 0; i < frame->fmt->memplanes; i++) + vb2_set_plane_payload(vb, i, frame->payload[i]); + + return 0; +} + +static void fimc_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static const struct vb2_ops fimc_qops = { + .queue_setup = fimc_queue_setup, + .buf_prepare = fimc_buf_prepare, + .buf_queue = fimc_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = stop_streaming, + .start_streaming = start_streaming, +}; + +/* + * V4L2 ioctl handlers + */ +static int fimc_m2m_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct fimc_dev *fimc = video_drvdata(file); + + __fimc_vidioc_querycap(&fimc->pdev->dev, cap); + return 0; +} + +static int fimc_m2m_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct fimc_fmt *fmt; + + fmt = fimc_find_format(NULL, NULL, get_m2m_fmt_flags(f->type), + f->index); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->fourcc; + return 0; +} + +static int fimc_m2m_g_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_ctx *ctx = fh_to_ctx(fh); + struct fimc_frame *frame = ctx_get_frame(ctx, f->type); + + if (IS_ERR(frame)) + return PTR_ERR(frame); + + __fimc_get_format(frame, f); + return 0; +} + +static int fimc_try_fmt_mplane(struct fimc_ctx *ctx, struct v4l2_format *f) +{ + struct fimc_dev *fimc = ctx->fimc_dev; + const struct fimc_variant *variant = fimc->variant; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; + struct fimc_fmt *fmt; + u32 max_w, mod_x, mod_y; + + if (!IS_M2M(f->type)) + return -EINVAL; + + fmt = fimc_find_format(&pix->pixelformat, NULL, + get_m2m_fmt_flags(f->type), 0); + if (WARN(fmt == NULL, "Pixel format lookup failed")) + return -EINVAL; + + if (pix->field == V4L2_FIELD_ANY) + pix->field = V4L2_FIELD_NONE; + else if (pix->field != V4L2_FIELD_NONE) + return -EINVAL; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + max_w = variant->pix_limit->scaler_dis_w; + mod_x = ffs(variant->min_inp_pixsize) - 1; + } else { + max_w = variant->pix_limit->out_rot_dis_w; + mod_x = ffs(variant->min_out_pixsize) - 1; + } + + if (tiled_fmt(fmt)) { + mod_x = 6; /* 64 x 32 pixels tile */ + mod_y = 5; + } else { + if (variant->min_vsize_align == 1) + mod_y = fimc_fmt_is_rgb(fmt->color) ? 0 : 1; + else + mod_y = ffs(variant->min_vsize_align) - 1; + } + + v4l_bound_align_image(&pix->width, 16, max_w, mod_x, + &pix->height, 8, variant->pix_limit->scaler_dis_w, mod_y, 0); + + fimc_adjust_mplane_format(fmt, pix->width, pix->height, &f->fmt.pix_mp); + return 0; +} + +static int fimc_m2m_try_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_ctx *ctx = fh_to_ctx(fh); + return fimc_try_fmt_mplane(ctx, f); +} + +static void __set_frame_format(struct fimc_frame *frame, struct fimc_fmt *fmt, + struct v4l2_pix_format_mplane *pixm) +{ + int i; + + for (i = 0; i < fmt->memplanes; i++) { + frame->bytesperline[i] = pixm->plane_fmt[i].bytesperline; + frame->payload[i] = pixm->plane_fmt[i].sizeimage; + } + + frame->f_width = pixm->width; + frame->f_height = pixm->height; + frame->o_width = pixm->width; + frame->o_height = pixm->height; + frame->width = pixm->width; + frame->height = pixm->height; + frame->offs_h = 0; + frame->offs_v = 0; + frame->fmt = fmt; +} + +static int fimc_m2m_s_fmt_mplane(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct fimc_ctx *ctx = fh_to_ctx(fh); + struct fimc_dev *fimc = ctx->fimc_dev; + struct fimc_fmt *fmt; + struct vb2_queue *vq; + struct fimc_frame *frame; + int ret; + + ret = fimc_try_fmt_mplane(ctx, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + + if (vb2_is_busy(vq)) { + v4l2_err(&fimc->m2m.vfd, "queue (%d) busy\n", f->type); + return -EBUSY; + } + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + frame = &ctx->s_frame; + else + frame = &ctx->d_frame; + + fmt = fimc_find_format(&f->fmt.pix_mp.pixelformat, NULL, + get_m2m_fmt_flags(f->type), 0); + if (!fmt) + return -EINVAL; + + __set_frame_format(frame, fmt, &f->fmt.pix_mp); + + /* Update RGB Alpha control state and value range */ + fimc_alpha_ctrl_update(ctx); + + return 0; +} + +static int fimc_m2m_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct fimc_ctx *ctx = fh_to_ctx(fh); + struct fimc_frame *frame; + + frame = ctx_get_frame(ctx, s->type); + if (IS_ERR(frame)) + return PTR_ERR(frame); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + break; + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + break; + default: + return -EINVAL; + } + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_COMPOSE: + s->r.left = frame->offs_h; + s->r.top = frame->offs_v; + s->r.width = frame->width; + s->r.height = frame->height; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->o_width; + s->r.height = frame->o_height; + break; + default: + return -EINVAL; + } + return 0; +} + +static int fimc_m2m_try_selection(struct fimc_ctx *ctx, + struct v4l2_selection *s) +{ + struct fimc_dev *fimc = ctx->fimc_dev; + struct fimc_frame *f; + u32 min_size, halign, depth = 0; + int i; + + if (s->r.top < 0 || s->r.left < 0) { + v4l2_err(&fimc->m2m.vfd, + "doesn't support negative values for top & left\n"); + return -EINVAL; + } + if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + f = &ctx->d_frame; + if (s->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + } else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + f = &ctx->s_frame; + if (s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + } else { + return -EINVAL; + } + + min_size = (f == &ctx->s_frame) ? + fimc->variant->min_inp_pixsize : fimc->variant->min_out_pixsize; + + /* Get pixel alignment constraints. */ + if (fimc->variant->min_vsize_align == 1) + halign = fimc_fmt_is_rgb(f->fmt->color) ? 0 : 1; + else + halign = ffs(fimc->variant->min_vsize_align) - 1; + + for (i = 0; i < f->fmt->memplanes; i++) + depth += f->fmt->depth[i]; + + v4l_bound_align_image(&s->r.width, min_size, f->o_width, + ffs(min_size) - 1, + &s->r.height, min_size, f->o_height, + halign, 64/(ALIGN(depth, 8))); + + /* adjust left/top if cropping rectangle is out of bounds */ + if (s->r.left + s->r.width > f->o_width) + s->r.left = f->o_width - s->r.width; + if (s->r.top + s->r.height > f->o_height) + s->r.top = f->o_height - s->r.height; + + s->r.left = round_down(s->r.left, min_size); + s->r.top = round_down(s->r.top, fimc->variant->hor_offs_align); + + dbg("l:%d, t:%d, w:%d, h:%d, f_w: %d, f_h: %d", + s->r.left, s->r.top, s->r.width, s->r.height, + f->f_width, f->f_height); + + return 0; +} + +static int fimc_m2m_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct fimc_ctx *ctx = fh_to_ctx(fh); + struct fimc_dev *fimc = ctx->fimc_dev; + struct fimc_frame *f; + int ret; + + ret = fimc_m2m_try_selection(ctx, s); + if (ret) + return ret; + + f = (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? + &ctx->s_frame : &ctx->d_frame; + + /* Check to see if scaling ratio is within supported range */ + if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ret = fimc_check_scaler_ratio(ctx, s->r.width, + s->r.height, ctx->d_frame.width, + ctx->d_frame.height, ctx->rotation); + } else { + ret = fimc_check_scaler_ratio(ctx, ctx->s_frame.width, + ctx->s_frame.height, s->r.width, + s->r.height, ctx->rotation); + } + if (ret) { + v4l2_err(&fimc->m2m.vfd, "Out of scaler range\n"); + return -EINVAL; + } + + f->offs_h = s->r.left; + f->offs_v = s->r.top; + f->width = s->r.width; + f->height = s->r.height; + + fimc_ctx_state_set(FIMC_PARAMS, ctx); + + return 0; +} + +static const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = { + .vidioc_querycap = fimc_m2m_querycap, + .vidioc_enum_fmt_vid_cap = fimc_m2m_enum_fmt, + .vidioc_enum_fmt_vid_out = fimc_m2m_enum_fmt, + .vidioc_g_fmt_vid_cap_mplane = fimc_m2m_g_fmt_mplane, + .vidioc_g_fmt_vid_out_mplane = fimc_m2m_g_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = fimc_m2m_try_fmt_mplane, + .vidioc_try_fmt_vid_out_mplane = fimc_m2m_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = fimc_m2m_s_fmt_mplane, + .vidioc_s_fmt_vid_out_mplane = fimc_m2m_s_fmt_mplane, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_g_selection = fimc_m2m_g_selection, + .vidioc_s_selection = fimc_m2m_s_selection, + +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct fimc_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->ops = &fimc_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->fimc_dev->lock; + src_vq->dev = &ctx->fimc_dev->pdev->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->ops = &fimc_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->fimc_dev->lock; + dst_vq->dev = &ctx->fimc_dev->pdev->dev; + + return vb2_queue_init(dst_vq); +} + +static int fimc_m2m_set_default_format(struct fimc_ctx *ctx) +{ + struct v4l2_pix_format_mplane pixm = { + .pixelformat = V4L2_PIX_FMT_RGB32, + .width = 800, + .height = 600, + .plane_fmt[0] = { + .bytesperline = 800 * 4, + .sizeimage = 800 * 4 * 600, + }, + }; + struct fimc_fmt *fmt; + + fmt = fimc_find_format(&pixm.pixelformat, NULL, FMT_FLAGS_M2M, 0); + if (!fmt) + return -EINVAL; + + __set_frame_format(&ctx->s_frame, fmt, &pixm); + __set_frame_format(&ctx->d_frame, fmt, &pixm); + + return 0; +} + +static int fimc_m2m_open(struct file *file) +{ + struct fimc_dev *fimc = video_drvdata(file); + struct fimc_ctx *ctx; + int ret = -EBUSY; + + pr_debug("pid: %d, state: %#lx\n", task_pid_nr(current), fimc->state); + + if (mutex_lock_interruptible(&fimc->lock)) + return -ERESTARTSYS; + /* + * Don't allow simultaneous open() of the mem-to-mem and the + * capture video node that belong to same FIMC IP instance. + */ + if (test_bit(ST_CAPT_BUSY, &fimc->state)) + goto unlock; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto unlock; + } + v4l2_fh_init(&ctx->fh, &fimc->m2m.vfd); + ctx->fimc_dev = fimc; + + /* Default color format */ + ctx->s_frame.fmt = fimc_get_format(0); + ctx->d_frame.fmt = fimc_get_format(0); + + ret = fimc_ctrls_create(ctx); + if (ret) + goto error_fh; + + /* Use separate control handler per file handle */ + ctx->fh.ctrl_handler = &ctx->ctrls.handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + /* Setup the device context for memory-to-memory mode */ + ctx->state = FIMC_CTX_M2M; + ctx->flags = 0; + ctx->in_path = FIMC_IO_DMA; + ctx->out_path = FIMC_IO_DMA; + ctx->scaler.enabled = 1; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(fimc->m2m.m2m_dev, ctx, queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto error_c; + } + + if (fimc->m2m.refcnt++ == 0) + set_bit(ST_M2M_RUN, &fimc->state); + + ret = fimc_m2m_set_default_format(ctx); + if (ret < 0) + goto error_m2m_ctx; + + mutex_unlock(&fimc->lock); + return 0; + +error_m2m_ctx: + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); +error_c: + fimc_ctrls_delete(ctx); + v4l2_fh_del(&ctx->fh); +error_fh: + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +unlock: + mutex_unlock(&fimc->lock); + return ret; +} + +static int fimc_m2m_release(struct file *file) +{ + struct fimc_ctx *ctx = fh_to_ctx(file->private_data); + struct fimc_dev *fimc = ctx->fimc_dev; + + dbg("pid: %d, state: 0x%lx, refcnt= %d", + task_pid_nr(current), fimc->state, fimc->m2m.refcnt); + + mutex_lock(&fimc->lock); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + fimc_ctrls_delete(ctx); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + if (--fimc->m2m.refcnt <= 0) + clear_bit(ST_M2M_RUN, &fimc->state); + kfree(ctx); + + mutex_unlock(&fimc->lock); + return 0; +} + +static const struct v4l2_file_operations fimc_m2m_fops = { + .owner = THIS_MODULE, + .open = fimc_m2m_open, + .release = fimc_m2m_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct v4l2_m2m_ops m2m_ops = { + .device_run = fimc_device_run, + .job_abort = fimc_job_abort, +}; + +int fimc_register_m2m_device(struct fimc_dev *fimc, + struct v4l2_device *v4l2_dev) +{ + struct video_device *vfd = &fimc->m2m.vfd; + int ret; + + fimc->v4l2_dev = v4l2_dev; + + memset(vfd, 0, sizeof(*vfd)); + vfd->fops = &fimc_m2m_fops; + vfd->ioctl_ops = &fimc_m2m_ioctl_ops; + vfd->v4l2_dev = v4l2_dev; + vfd->minor = -1; + vfd->release = video_device_release_empty; + vfd->lock = &fimc->lock; + vfd->vfl_dir = VFL_DIR_M2M; + vfd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE; + set_bit(V4L2_FL_QUIRK_INVERTED_CROP, &vfd->flags); + + snprintf(vfd->name, sizeof(vfd->name), "fimc.%d.m2m", fimc->id); + video_set_drvdata(vfd, fimc); + + fimc->m2m.m2m_dev = v4l2_m2m_init(&m2m_ops); + if (IS_ERR(fimc->m2m.m2m_dev)) { + v4l2_err(v4l2_dev, "failed to initialize v4l2-m2m device\n"); + return PTR_ERR(fimc->m2m.m2m_dev); + } + + ret = media_entity_pads_init(&vfd->entity, 0, NULL); + if (ret) + goto err_me; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) + goto err_vd; + + v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n", + vfd->name, video_device_node_name(vfd)); + return 0; + +err_vd: + media_entity_cleanup(&vfd->entity); +err_me: + v4l2_m2m_release(fimc->m2m.m2m_dev); + return ret; +} + +void fimc_unregister_m2m_device(struct fimc_dev *fimc) +{ + if (!fimc) + return; + + if (fimc->m2m.m2m_dev) + v4l2_m2m_release(fimc->m2m.m2m_dev); + + if (video_is_registered(&fimc->m2m.vfd)) { + video_unregister_device(&fimc->m2m.vfd); + media_entity_cleanup(&fimc->m2m.vfd.entity); + } +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-reg.c b/drivers/media/platform/samsung/exynos4-is/fimc-reg.c new file mode 100644 index 000000000..95165a2cc --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-reg.c @@ -0,0 +1,846 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Register interface file for Samsung Camera Interface (FIMC) driver + * + * Copyright (C) 2010 - 2013 Samsung Electronics Co., Ltd. + * Sylwester Nawrocki +*/ + +#include +#include +#include + +#include +#include "media-dev.h" + +#include "fimc-reg.h" +#include "fimc-core.h" + +void fimc_hw_reset(struct fimc_dev *dev) +{ + u32 cfg; + + cfg = readl(dev->regs + FIMC_REG_CISRCFMT); + cfg |= FIMC_REG_CISRCFMT_ITU601_8BIT; + writel(cfg, dev->regs + FIMC_REG_CISRCFMT); + + /* Software reset. */ + cfg = readl(dev->regs + FIMC_REG_CIGCTRL); + cfg |= (FIMC_REG_CIGCTRL_SWRST | FIMC_REG_CIGCTRL_IRQ_LEVEL); + writel(cfg, dev->regs + FIMC_REG_CIGCTRL); + udelay(10); + + cfg = readl(dev->regs + FIMC_REG_CIGCTRL); + cfg &= ~FIMC_REG_CIGCTRL_SWRST; + writel(cfg, dev->regs + FIMC_REG_CIGCTRL); + + if (dev->drv_data->out_buf_count > 4) + fimc_hw_set_dma_seq(dev, 0xF); +} + +static u32 fimc_hw_get_in_flip(struct fimc_ctx *ctx) +{ + u32 flip = FIMC_REG_MSCTRL_FLIP_NORMAL; + + if (ctx->hflip) + flip = FIMC_REG_MSCTRL_FLIP_Y_MIRROR; + if (ctx->vflip) + flip = FIMC_REG_MSCTRL_FLIP_X_MIRROR; + + if (ctx->rotation <= 90) + return flip; + + return (flip ^ FIMC_REG_MSCTRL_FLIP_180) & FIMC_REG_MSCTRL_FLIP_180; +} + +static u32 fimc_hw_get_target_flip(struct fimc_ctx *ctx) +{ + u32 flip = FIMC_REG_CITRGFMT_FLIP_NORMAL; + + if (ctx->hflip) + flip |= FIMC_REG_CITRGFMT_FLIP_Y_MIRROR; + if (ctx->vflip) + flip |= FIMC_REG_CITRGFMT_FLIP_X_MIRROR; + + if (ctx->rotation <= 90) + return flip; + + return (flip ^ FIMC_REG_CITRGFMT_FLIP_180) & FIMC_REG_CITRGFMT_FLIP_180; +} + +void fimc_hw_set_rotation(struct fimc_ctx *ctx) +{ + u32 cfg, flip; + struct fimc_dev *dev = ctx->fimc_dev; + + cfg = readl(dev->regs + FIMC_REG_CITRGFMT); + cfg &= ~(FIMC_REG_CITRGFMT_INROT90 | FIMC_REG_CITRGFMT_OUTROT90 | + FIMC_REG_CITRGFMT_FLIP_180); + + /* + * The input and output rotator cannot work simultaneously. + * Use the output rotator in output DMA mode or the input rotator + * in direct fifo output mode. + */ + if (ctx->rotation == 90 || ctx->rotation == 270) { + if (ctx->out_path == FIMC_IO_LCDFIFO) + cfg |= FIMC_REG_CITRGFMT_INROT90; + else + cfg |= FIMC_REG_CITRGFMT_OUTROT90; + } + + if (ctx->out_path == FIMC_IO_DMA) { + cfg |= fimc_hw_get_target_flip(ctx); + writel(cfg, dev->regs + FIMC_REG_CITRGFMT); + } else { + /* LCD FIFO path */ + flip = readl(dev->regs + FIMC_REG_MSCTRL); + flip &= ~FIMC_REG_MSCTRL_FLIP_MASK; + flip |= fimc_hw_get_in_flip(ctx); + writel(flip, dev->regs + FIMC_REG_MSCTRL); + } +} + +void fimc_hw_set_target_format(struct fimc_ctx *ctx) +{ + u32 cfg; + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->d_frame; + + dbg("w= %d, h= %d color: %d", frame->width, + frame->height, frame->fmt->color); + + cfg = readl(dev->regs + FIMC_REG_CITRGFMT); + cfg &= ~(FIMC_REG_CITRGFMT_FMT_MASK | FIMC_REG_CITRGFMT_HSIZE_MASK | + FIMC_REG_CITRGFMT_VSIZE_MASK); + + switch (frame->fmt->color) { + case FIMC_FMT_RGB444...FIMC_FMT_RGB888: + cfg |= FIMC_REG_CITRGFMT_RGB; + break; + case FIMC_FMT_YCBCR420: + cfg |= FIMC_REG_CITRGFMT_YCBCR420; + break; + case FIMC_FMT_YCBYCR422...FIMC_FMT_CRYCBY422: + if (frame->fmt->colplanes == 1) + cfg |= FIMC_REG_CITRGFMT_YCBCR422_1P; + else + cfg |= FIMC_REG_CITRGFMT_YCBCR422; + break; + default: + break; + } + + if (ctx->rotation == 90 || ctx->rotation == 270) + cfg |= (frame->height << 16) | frame->width; + else + cfg |= (frame->width << 16) | frame->height; + + writel(cfg, dev->regs + FIMC_REG_CITRGFMT); + + cfg = readl(dev->regs + FIMC_REG_CITAREA); + cfg &= ~FIMC_REG_CITAREA_MASK; + cfg |= (frame->width * frame->height); + writel(cfg, dev->regs + FIMC_REG_CITAREA); +} + +static void fimc_hw_set_out_dma_size(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->d_frame; + u32 cfg; + + cfg = (frame->f_height << 16) | frame->f_width; + writel(cfg, dev->regs + FIMC_REG_ORGOSIZE); + + /* Select color space conversion equation (HD/SD size).*/ + cfg = readl(dev->regs + FIMC_REG_CIGCTRL); + if (frame->f_width >= 1280) /* HD */ + cfg |= FIMC_REG_CIGCTRL_CSC_ITU601_709; + else /* SD */ + cfg &= ~FIMC_REG_CIGCTRL_CSC_ITU601_709; + writel(cfg, dev->regs + FIMC_REG_CIGCTRL); + +} + +void fimc_hw_set_out_dma(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->d_frame; + struct fimc_dma_offset *offset = &frame->dma_offset; + struct fimc_fmt *fmt = frame->fmt; + u32 cfg; + + /* Set the input dma offsets. */ + cfg = (offset->y_v << 16) | offset->y_h; + writel(cfg, dev->regs + FIMC_REG_CIOYOFF); + + cfg = (offset->cb_v << 16) | offset->cb_h; + writel(cfg, dev->regs + FIMC_REG_CIOCBOFF); + + cfg = (offset->cr_v << 16) | offset->cr_h; + writel(cfg, dev->regs + FIMC_REG_CIOCROFF); + + fimc_hw_set_out_dma_size(ctx); + + /* Configure chroma components order. */ + cfg = readl(dev->regs + FIMC_REG_CIOCTRL); + + cfg &= ~(FIMC_REG_CIOCTRL_ORDER2P_MASK | + FIMC_REG_CIOCTRL_ORDER422_MASK | + FIMC_REG_CIOCTRL_YCBCR_PLANE_MASK | + FIMC_REG_CIOCTRL_RGB16FMT_MASK); + + if (fmt->colplanes == 1) + cfg |= ctx->out_order_1p; + else if (fmt->colplanes == 2) + cfg |= ctx->out_order_2p | FIMC_REG_CIOCTRL_YCBCR_2PLANE; + else if (fmt->colplanes == 3) + cfg |= FIMC_REG_CIOCTRL_YCBCR_3PLANE; + + if (fmt->color == FIMC_FMT_RGB565) + cfg |= FIMC_REG_CIOCTRL_RGB565; + else if (fmt->color == FIMC_FMT_RGB555) + cfg |= FIMC_REG_CIOCTRL_ARGB1555; + else if (fmt->color == FIMC_FMT_RGB444) + cfg |= FIMC_REG_CIOCTRL_ARGB4444; + + writel(cfg, dev->regs + FIMC_REG_CIOCTRL); +} + +static void fimc_hw_en_autoload(struct fimc_dev *dev, int enable) +{ + u32 cfg = readl(dev->regs + FIMC_REG_ORGISIZE); + if (enable) + cfg |= FIMC_REG_CIREAL_ISIZE_AUTOLOAD_EN; + else + cfg &= ~FIMC_REG_CIREAL_ISIZE_AUTOLOAD_EN; + writel(cfg, dev->regs + FIMC_REG_ORGISIZE); +} + +void fimc_hw_en_lastirq(struct fimc_dev *dev, int enable) +{ + u32 cfg = readl(dev->regs + FIMC_REG_CIOCTRL); + if (enable) + cfg |= FIMC_REG_CIOCTRL_LASTIRQ_ENABLE; + else + cfg &= ~FIMC_REG_CIOCTRL_LASTIRQ_ENABLE; + writel(cfg, dev->regs + FIMC_REG_CIOCTRL); +} + +void fimc_hw_set_prescaler(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_scaler *sc = &ctx->scaler; + u32 cfg, shfactor; + + shfactor = 10 - (sc->hfactor + sc->vfactor); + cfg = shfactor << 28; + + cfg |= (sc->pre_hratio << 16) | sc->pre_vratio; + writel(cfg, dev->regs + FIMC_REG_CISCPRERATIO); + + cfg = (sc->pre_dst_width << 16) | sc->pre_dst_height; + writel(cfg, dev->regs + FIMC_REG_CISCPREDST); +} + +static void fimc_hw_set_scaler(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_scaler *sc = &ctx->scaler; + struct fimc_frame *src_frame = &ctx->s_frame; + struct fimc_frame *dst_frame = &ctx->d_frame; + + u32 cfg = readl(dev->regs + FIMC_REG_CISCCTRL); + + cfg &= ~(FIMC_REG_CISCCTRL_CSCR2Y_WIDE | FIMC_REG_CISCCTRL_CSCY2R_WIDE | + FIMC_REG_CISCCTRL_SCALEUP_H | FIMC_REG_CISCCTRL_SCALEUP_V | + FIMC_REG_CISCCTRL_SCALERBYPASS | FIMC_REG_CISCCTRL_ONE2ONE | + FIMC_REG_CISCCTRL_INRGB_FMT_MASK | FIMC_REG_CISCCTRL_OUTRGB_FMT_MASK | + FIMC_REG_CISCCTRL_INTERLACE | FIMC_REG_CISCCTRL_RGB_EXT); + + if (!(ctx->flags & FIMC_COLOR_RANGE_NARROW)) + cfg |= (FIMC_REG_CISCCTRL_CSCR2Y_WIDE | + FIMC_REG_CISCCTRL_CSCY2R_WIDE); + + if (!sc->enabled) + cfg |= FIMC_REG_CISCCTRL_SCALERBYPASS; + + if (sc->scaleup_h) + cfg |= FIMC_REG_CISCCTRL_SCALEUP_H; + + if (sc->scaleup_v) + cfg |= FIMC_REG_CISCCTRL_SCALEUP_V; + + if (sc->copy_mode) + cfg |= FIMC_REG_CISCCTRL_ONE2ONE; + + if (ctx->in_path == FIMC_IO_DMA) { + switch (src_frame->fmt->color) { + case FIMC_FMT_RGB565: + cfg |= FIMC_REG_CISCCTRL_INRGB_FMT_RGB565; + break; + case FIMC_FMT_RGB666: + cfg |= FIMC_REG_CISCCTRL_INRGB_FMT_RGB666; + break; + case FIMC_FMT_RGB888: + cfg |= FIMC_REG_CISCCTRL_INRGB_FMT_RGB888; + break; + } + } + + if (ctx->out_path == FIMC_IO_DMA) { + u32 color = dst_frame->fmt->color; + + if (color >= FIMC_FMT_RGB444 && color <= FIMC_FMT_RGB565) + cfg |= FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB565; + else if (color == FIMC_FMT_RGB666) + cfg |= FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB666; + else if (color == FIMC_FMT_RGB888) + cfg |= FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB888; + } else { + cfg |= FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB888; + + if (ctx->flags & FIMC_SCAN_MODE_INTERLACED) + cfg |= FIMC_REG_CISCCTRL_INTERLACE; + } + + writel(cfg, dev->regs + FIMC_REG_CISCCTRL); +} + +void fimc_hw_set_mainscaler(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + const struct fimc_variant *variant = dev->variant; + struct fimc_scaler *sc = &ctx->scaler; + u32 cfg; + + dbg("main_hratio= 0x%X main_vratio= 0x%X", + sc->main_hratio, sc->main_vratio); + + fimc_hw_set_scaler(ctx); + + cfg = readl(dev->regs + FIMC_REG_CISCCTRL); + cfg &= ~(FIMC_REG_CISCCTRL_MHRATIO_MASK | + FIMC_REG_CISCCTRL_MVRATIO_MASK); + + if (variant->has_mainscaler_ext) { + cfg |= FIMC_REG_CISCCTRL_MHRATIO_EXT(sc->main_hratio); + cfg |= FIMC_REG_CISCCTRL_MVRATIO_EXT(sc->main_vratio); + writel(cfg, dev->regs + FIMC_REG_CISCCTRL); + + cfg = readl(dev->regs + FIMC_REG_CIEXTEN); + + cfg &= ~(FIMC_REG_CIEXTEN_MVRATIO_EXT_MASK | + FIMC_REG_CIEXTEN_MHRATIO_EXT_MASK); + cfg |= FIMC_REG_CIEXTEN_MHRATIO_EXT(sc->main_hratio); + cfg |= FIMC_REG_CIEXTEN_MVRATIO_EXT(sc->main_vratio); + writel(cfg, dev->regs + FIMC_REG_CIEXTEN); + } else { + cfg |= FIMC_REG_CISCCTRL_MHRATIO(sc->main_hratio); + cfg |= FIMC_REG_CISCCTRL_MVRATIO(sc->main_vratio); + writel(cfg, dev->regs + FIMC_REG_CISCCTRL); + } +} + +void fimc_hw_enable_capture(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + u32 cfg; + + cfg = readl(dev->regs + FIMC_REG_CIIMGCPT); + cfg |= FIMC_REG_CIIMGCPT_CPT_FREN_ENABLE; + + if (ctx->scaler.enabled) + cfg |= FIMC_REG_CIIMGCPT_IMGCPTEN_SC; + else + cfg &= FIMC_REG_CIIMGCPT_IMGCPTEN_SC; + + cfg |= FIMC_REG_CIIMGCPT_IMGCPTEN; + writel(cfg, dev->regs + FIMC_REG_CIIMGCPT); +} + +void fimc_hw_disable_capture(struct fimc_dev *dev) +{ + u32 cfg = readl(dev->regs + FIMC_REG_CIIMGCPT); + cfg &= ~(FIMC_REG_CIIMGCPT_IMGCPTEN | + FIMC_REG_CIIMGCPT_IMGCPTEN_SC); + writel(cfg, dev->regs + FIMC_REG_CIIMGCPT); +} + +void fimc_hw_set_effect(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_effect *effect = &ctx->effect; + u32 cfg = 0; + + if (effect->type != FIMC_REG_CIIMGEFF_FIN_BYPASS) { + cfg |= FIMC_REG_CIIMGEFF_IE_SC_AFTER | + FIMC_REG_CIIMGEFF_IE_ENABLE; + cfg |= effect->type; + if (effect->type == FIMC_REG_CIIMGEFF_FIN_ARBITRARY) + cfg |= (effect->pat_cb << 13) | effect->pat_cr; + } + + writel(cfg, dev->regs + FIMC_REG_CIIMGEFF); +} + +void fimc_hw_set_rgb_alpha(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->d_frame; + u32 cfg; + + if (!(frame->fmt->flags & FMT_HAS_ALPHA)) + return; + + cfg = readl(dev->regs + FIMC_REG_CIOCTRL); + cfg &= ~FIMC_REG_CIOCTRL_ALPHA_OUT_MASK; + cfg |= (frame->alpha << 4); + writel(cfg, dev->regs + FIMC_REG_CIOCTRL); +} + +static void fimc_hw_set_in_dma_size(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->s_frame; + u32 cfg_o = 0; + u32 cfg_r = 0; + + if (FIMC_IO_LCDFIFO == ctx->out_path) + cfg_r |= FIMC_REG_CIREAL_ISIZE_AUTOLOAD_EN; + + cfg_o |= (frame->f_height << 16) | frame->f_width; + cfg_r |= (frame->height << 16) | frame->width; + + writel(cfg_o, dev->regs + FIMC_REG_ORGISIZE); + writel(cfg_r, dev->regs + FIMC_REG_CIREAL_ISIZE); +} + +void fimc_hw_set_in_dma(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct fimc_frame *frame = &ctx->s_frame; + struct fimc_dma_offset *offset = &frame->dma_offset; + u32 cfg; + + /* Set the pixel offsets. */ + cfg = (offset->y_v << 16) | offset->y_h; + writel(cfg, dev->regs + FIMC_REG_CIIYOFF); + + cfg = (offset->cb_v << 16) | offset->cb_h; + writel(cfg, dev->regs + FIMC_REG_CIICBOFF); + + cfg = (offset->cr_v << 16) | offset->cr_h; + writel(cfg, dev->regs + FIMC_REG_CIICROFF); + + /* Input original and real size. */ + fimc_hw_set_in_dma_size(ctx); + + /* Use DMA autoload only in FIFO mode. */ + fimc_hw_en_autoload(dev, ctx->out_path == FIMC_IO_LCDFIFO); + + /* Set the input DMA to process single frame only. */ + cfg = readl(dev->regs + FIMC_REG_MSCTRL); + cfg &= ~(FIMC_REG_MSCTRL_INFORMAT_MASK + | FIMC_REG_MSCTRL_IN_BURST_COUNT_MASK + | FIMC_REG_MSCTRL_INPUT_MASK + | FIMC_REG_MSCTRL_C_INT_IN_MASK + | FIMC_REG_MSCTRL_2P_IN_ORDER_MASK + | FIMC_REG_MSCTRL_ORDER422_MASK); + + cfg |= (FIMC_REG_MSCTRL_IN_BURST_COUNT(4) + | FIMC_REG_MSCTRL_INPUT_MEMORY + | FIMC_REG_MSCTRL_FIFO_CTRL_FULL); + + switch (frame->fmt->color) { + case FIMC_FMT_RGB565...FIMC_FMT_RGB888: + cfg |= FIMC_REG_MSCTRL_INFORMAT_RGB; + break; + case FIMC_FMT_YCBCR420: + cfg |= FIMC_REG_MSCTRL_INFORMAT_YCBCR420; + + if (frame->fmt->colplanes == 2) + cfg |= ctx->in_order_2p | FIMC_REG_MSCTRL_C_INT_IN_2PLANE; + else + cfg |= FIMC_REG_MSCTRL_C_INT_IN_3PLANE; + + break; + case FIMC_FMT_YCBYCR422...FIMC_FMT_CRYCBY422: + if (frame->fmt->colplanes == 1) { + cfg |= ctx->in_order_1p + | FIMC_REG_MSCTRL_INFORMAT_YCBCR422_1P; + } else { + cfg |= FIMC_REG_MSCTRL_INFORMAT_YCBCR422; + + if (frame->fmt->colplanes == 2) + cfg |= ctx->in_order_2p + | FIMC_REG_MSCTRL_C_INT_IN_2PLANE; + else + cfg |= FIMC_REG_MSCTRL_C_INT_IN_3PLANE; + } + break; + default: + break; + } + + writel(cfg, dev->regs + FIMC_REG_MSCTRL); + + /* Input/output DMA linear/tiled mode. */ + cfg = readl(dev->regs + FIMC_REG_CIDMAPARAM); + cfg &= ~FIMC_REG_CIDMAPARAM_TILE_MASK; + + if (tiled_fmt(ctx->s_frame.fmt)) + cfg |= FIMC_REG_CIDMAPARAM_R_64X32; + + if (tiled_fmt(ctx->d_frame.fmt)) + cfg |= FIMC_REG_CIDMAPARAM_W_64X32; + + writel(cfg, dev->regs + FIMC_REG_CIDMAPARAM); +} + + +void fimc_hw_set_input_path(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + + u32 cfg = readl(dev->regs + FIMC_REG_MSCTRL); + cfg &= ~FIMC_REG_MSCTRL_INPUT_MASK; + + if (ctx->in_path == FIMC_IO_DMA) + cfg |= FIMC_REG_MSCTRL_INPUT_MEMORY; + else + cfg |= FIMC_REG_MSCTRL_INPUT_EXTCAM; + + writel(cfg, dev->regs + FIMC_REG_MSCTRL); +} + +void fimc_hw_set_output_path(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + + u32 cfg = readl(dev->regs + FIMC_REG_CISCCTRL); + cfg &= ~FIMC_REG_CISCCTRL_LCDPATHEN_FIFO; + if (ctx->out_path == FIMC_IO_LCDFIFO) + cfg |= FIMC_REG_CISCCTRL_LCDPATHEN_FIFO; + writel(cfg, dev->regs + FIMC_REG_CISCCTRL); +} + +void fimc_hw_set_input_addr(struct fimc_dev *dev, struct fimc_addr *addr) +{ + u32 cfg = readl(dev->regs + FIMC_REG_CIREAL_ISIZE); + cfg |= FIMC_REG_CIREAL_ISIZE_ADDR_CH_DIS; + writel(cfg, dev->regs + FIMC_REG_CIREAL_ISIZE); + + writel(addr->y, dev->regs + FIMC_REG_CIIYSA(0)); + writel(addr->cb, dev->regs + FIMC_REG_CIICBSA(0)); + writel(addr->cr, dev->regs + FIMC_REG_CIICRSA(0)); + + cfg &= ~FIMC_REG_CIREAL_ISIZE_ADDR_CH_DIS; + writel(cfg, dev->regs + FIMC_REG_CIREAL_ISIZE); +} + +void fimc_hw_set_output_addr(struct fimc_dev *dev, + struct fimc_addr *addr, int index) +{ + int i = (index == -1) ? 0 : index; + do { + writel(addr->y, dev->regs + FIMC_REG_CIOYSA(i)); + writel(addr->cb, dev->regs + FIMC_REG_CIOCBSA(i)); + writel(addr->cr, dev->regs + FIMC_REG_CIOCRSA(i)); + dbg("dst_buf[%d]: 0x%X, cb: 0x%X, cr: 0x%X", + i, addr->y, addr->cb, addr->cr); + } while (index == -1 && ++i < FIMC_MAX_OUT_BUFS); +} + +int fimc_hw_set_camera_polarity(struct fimc_dev *fimc, + struct fimc_source_info *cam) +{ + u32 cfg = readl(fimc->regs + FIMC_REG_CIGCTRL); + + cfg &= ~(FIMC_REG_CIGCTRL_INVPOLPCLK | FIMC_REG_CIGCTRL_INVPOLVSYNC | + FIMC_REG_CIGCTRL_INVPOLHREF | FIMC_REG_CIGCTRL_INVPOLHSYNC | + FIMC_REG_CIGCTRL_INVPOLFIELD); + + if (cam->flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + cfg |= FIMC_REG_CIGCTRL_INVPOLPCLK; + + if (cam->flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + cfg |= FIMC_REG_CIGCTRL_INVPOLVSYNC; + + if (cam->flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + cfg |= FIMC_REG_CIGCTRL_INVPOLHREF; + + if (cam->flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + cfg |= FIMC_REG_CIGCTRL_INVPOLHSYNC; + + if (cam->flags & V4L2_MBUS_FIELD_EVEN_LOW) + cfg |= FIMC_REG_CIGCTRL_INVPOLFIELD; + + writel(cfg, fimc->regs + FIMC_REG_CIGCTRL); + + return 0; +} + +struct mbus_pixfmt_desc { + u32 pixelcode; + u32 cisrcfmt; + u16 bus_width; +}; + +static const struct mbus_pixfmt_desc pix_desc[] = { + { MEDIA_BUS_FMT_YUYV8_2X8, FIMC_REG_CISRCFMT_ORDER422_YCBYCR, 8 }, + { MEDIA_BUS_FMT_YVYU8_2X8, FIMC_REG_CISRCFMT_ORDER422_YCRYCB, 8 }, + { MEDIA_BUS_FMT_VYUY8_2X8, FIMC_REG_CISRCFMT_ORDER422_CRYCBY, 8 }, + { MEDIA_BUS_FMT_UYVY8_2X8, FIMC_REG_CISRCFMT_ORDER422_CBYCRY, 8 }, +}; + +int fimc_hw_set_camera_source(struct fimc_dev *fimc, + struct fimc_source_info *source) +{ + struct fimc_vid_cap *vc = &fimc->vid_cap; + struct fimc_frame *f = &vc->ctx->s_frame; + u32 bus_width, cfg = 0; + int i; + + switch (source->fimc_bus_type) { + case FIMC_BUS_TYPE_ITU_601: + case FIMC_BUS_TYPE_ITU_656: + if (fimc_fmt_is_user_defined(f->fmt->color)) { + cfg |= FIMC_REG_CISRCFMT_ITU601_8BIT; + break; + } + + for (i = 0; i < ARRAY_SIZE(pix_desc); i++) { + if (vc->ci_fmt.code == pix_desc[i].pixelcode) { + cfg = pix_desc[i].cisrcfmt; + bus_width = pix_desc[i].bus_width; + break; + } + } + + if (i == ARRAY_SIZE(pix_desc)) { + v4l2_err(&vc->ve.vdev, + "Camera color format not supported: %d\n", + vc->ci_fmt.code); + return -EINVAL; + } + + if (source->fimc_bus_type == FIMC_BUS_TYPE_ITU_601) { + if (bus_width == 8) + cfg |= FIMC_REG_CISRCFMT_ITU601_8BIT; + else if (bus_width == 16) + cfg |= FIMC_REG_CISRCFMT_ITU601_16BIT; + } /* else defaults to ITU-R BT.656 8-bit */ + break; + case FIMC_BUS_TYPE_MIPI_CSI2: + if (fimc_fmt_is_user_defined(f->fmt->color)) + cfg |= FIMC_REG_CISRCFMT_ITU601_8BIT; + break; + default: + case FIMC_BUS_TYPE_ISP_WRITEBACK: + /* Anything to do here ? */ + break; + } + + cfg |= (f->o_width << 16) | f->o_height; + writel(cfg, fimc->regs + FIMC_REG_CISRCFMT); + return 0; +} + +void fimc_hw_set_camera_offset(struct fimc_dev *fimc, struct fimc_frame *f) +{ + u32 hoff2, voff2; + + u32 cfg = readl(fimc->regs + FIMC_REG_CIWDOFST); + + cfg &= ~(FIMC_REG_CIWDOFST_HOROFF_MASK | FIMC_REG_CIWDOFST_VEROFF_MASK); + cfg |= FIMC_REG_CIWDOFST_OFF_EN | + (f->offs_h << 16) | f->offs_v; + + writel(cfg, fimc->regs + FIMC_REG_CIWDOFST); + + /* See CIWDOFSTn register description in the datasheet for details. */ + hoff2 = f->o_width - f->width - f->offs_h; + voff2 = f->o_height - f->height - f->offs_v; + cfg = (hoff2 << 16) | voff2; + writel(cfg, fimc->regs + FIMC_REG_CIWDOFST2); +} + +int fimc_hw_set_camera_type(struct fimc_dev *fimc, + struct fimc_source_info *source) +{ + struct fimc_vid_cap *vid_cap = &fimc->vid_cap; + u32 csis_data_alignment = 32; + u32 cfg, tmp; + + cfg = readl(fimc->regs + FIMC_REG_CIGCTRL); + + /* Select ITU B interface, disable Writeback path and test pattern. */ + cfg &= ~(FIMC_REG_CIGCTRL_TESTPAT_MASK | FIMC_REG_CIGCTRL_SELCAM_ITU_A | + FIMC_REG_CIGCTRL_SELCAM_MIPI | FIMC_REG_CIGCTRL_CAMIF_SELWB | + FIMC_REG_CIGCTRL_SELCAM_MIPI_A | FIMC_REG_CIGCTRL_CAM_JPEG | + FIMC_REG_CIGCTRL_SELWB_A); + + switch (source->fimc_bus_type) { + case FIMC_BUS_TYPE_MIPI_CSI2: + cfg |= FIMC_REG_CIGCTRL_SELCAM_MIPI; + + if (source->mux_id == 0) + cfg |= FIMC_REG_CIGCTRL_SELCAM_MIPI_A; + + /* TODO: add remaining supported formats. */ + switch (vid_cap->ci_fmt.code) { + case MEDIA_BUS_FMT_VYUY8_2X8: + tmp = FIMC_REG_CSIIMGFMT_YCBCR422_8BIT; + break; + case MEDIA_BUS_FMT_JPEG_1X8: + case MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8: + tmp = FIMC_REG_CSIIMGFMT_USER(1); + cfg |= FIMC_REG_CIGCTRL_CAM_JPEG; + break; + default: + v4l2_err(&vid_cap->ve.vdev, + "Not supported camera pixel format: %#x\n", + vid_cap->ci_fmt.code); + return -EINVAL; + } + tmp |= (csis_data_alignment == 32) << 8; + + writel(tmp, fimc->regs + FIMC_REG_CSIIMGFMT); + break; + case FIMC_BUS_TYPE_ITU_601...FIMC_BUS_TYPE_ITU_656: + if (source->mux_id == 0) /* ITU-A, ITU-B: 0, 1 */ + cfg |= FIMC_REG_CIGCTRL_SELCAM_ITU_A; + if (vid_cap->ci_fmt.code == MEDIA_BUS_FMT_JPEG_1X8) + cfg |= FIMC_REG_CIGCTRL_CAM_JPEG; + break; + case FIMC_BUS_TYPE_LCD_WRITEBACK_A: + cfg |= FIMC_REG_CIGCTRL_CAMIF_SELWB; + fallthrough; + case FIMC_BUS_TYPE_ISP_WRITEBACK: + if (fimc->variant->has_isp_wb) + cfg |= FIMC_REG_CIGCTRL_CAMIF_SELWB; + else + WARN_ONCE(1, "ISP Writeback input is not supported\n"); + break; + default: + v4l2_err(&vid_cap->ve.vdev, + "Invalid FIMC bus type selected: %d\n", + source->fimc_bus_type); + return -EINVAL; + } + writel(cfg, fimc->regs + FIMC_REG_CIGCTRL); + + return 0; +} + +void fimc_hw_clear_irq(struct fimc_dev *dev) +{ + u32 cfg = readl(dev->regs + FIMC_REG_CIGCTRL); + cfg |= FIMC_REG_CIGCTRL_IRQ_CLR; + writel(cfg, dev->regs + FIMC_REG_CIGCTRL); +} + +void fimc_hw_enable_scaler(struct fimc_dev *dev, bool on) +{ + u32 cfg = readl(dev->regs + FIMC_REG_CISCCTRL); + if (on) + cfg |= FIMC_REG_CISCCTRL_SCALERSTART; + else + cfg &= ~FIMC_REG_CISCCTRL_SCALERSTART; + writel(cfg, dev->regs + FIMC_REG_CISCCTRL); +} + +void fimc_hw_activate_input_dma(struct fimc_dev *dev, bool on) +{ + u32 cfg = readl(dev->regs + FIMC_REG_MSCTRL); + if (on) + cfg |= FIMC_REG_MSCTRL_ENVID; + else + cfg &= ~FIMC_REG_MSCTRL_ENVID; + writel(cfg, dev->regs + FIMC_REG_MSCTRL); +} + +/* Return an index to the buffer actually being written. */ +s32 fimc_hw_get_frame_index(struct fimc_dev *dev) +{ + s32 reg; + + if (dev->drv_data->cistatus2) { + reg = readl(dev->regs + FIMC_REG_CISTATUS2) & 0x3f; + return reg - 1; + } + + reg = readl(dev->regs + FIMC_REG_CISTATUS); + + return (reg & FIMC_REG_CISTATUS_FRAMECNT_MASK) >> + FIMC_REG_CISTATUS_FRAMECNT_SHIFT; +} + +/* Return an index to the buffer being written previously. */ +s32 fimc_hw_get_prev_frame_index(struct fimc_dev *dev) +{ + s32 reg; + + if (!dev->drv_data->cistatus2) + return -1; + + reg = readl(dev->regs + FIMC_REG_CISTATUS2); + return ((reg >> 7) & 0x3f) - 1; +} + +/* Locking: the caller holds fimc->slock */ +void fimc_activate_capture(struct fimc_ctx *ctx) +{ + fimc_hw_enable_scaler(ctx->fimc_dev, ctx->scaler.enabled); + fimc_hw_enable_capture(ctx); +} + +void fimc_deactivate_capture(struct fimc_dev *fimc) +{ + fimc_hw_en_lastirq(fimc, true); + fimc_hw_disable_capture(fimc); + fimc_hw_enable_scaler(fimc, false); + fimc_hw_en_lastirq(fimc, false); +} + +int fimc_hw_camblk_cfg_writeback(struct fimc_dev *fimc) +{ + struct regmap *map = fimc->sysreg; + unsigned int mask, val, camblk_cfg; + int ret; + + if (map == NULL) + return 0; + + ret = regmap_read(map, SYSREG_CAMBLK, &camblk_cfg); + if (ret < 0 || ((camblk_cfg & 0x00700000) >> 20 != 0x3)) + return ret; + + if (!WARN(fimc->id >= 3, "not supported id: %d\n", fimc->id)) + val = 0x1 << (fimc->id + 20); + else + val = 0; + + mask = SYSREG_CAMBLK_FIFORST_ISP | SYSREG_CAMBLK_ISPWB_FULL_EN; + ret = regmap_update_bits(map, SYSREG_CAMBLK, mask, val); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + + val |= SYSREG_CAMBLK_FIFORST_ISP; + ret = regmap_update_bits(map, SYSREG_CAMBLK, mask, val); + if (ret < 0) + return ret; + + mask = SYSREG_ISPBLK_FIFORST_CAM_BLK; + ret = regmap_update_bits(map, SYSREG_ISPBLK, mask, ~mask); + if (ret < 0) + return ret; + + usleep_range(1000, 2000); + + return regmap_update_bits(map, SYSREG_ISPBLK, mask, mask); +} diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-reg.h b/drivers/media/platform/samsung/exynos4-is/fimc-reg.h new file mode 100644 index 000000000..b9b33aa1f --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/fimc-reg.h @@ -0,0 +1,338 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung camera host interface (FIMC) registers definition + * + * Copyright (C) 2010 - 2012 Samsung Electronics Co., Ltd. + */ + +#ifndef FIMC_REG_H_ +#define FIMC_REG_H_ + +#include + +#include "fimc-core.h" + +/* Input source format */ +#define FIMC_REG_CISRCFMT 0x00 +#define FIMC_REG_CISRCFMT_ITU601_8BIT BIT(31) +#define FIMC_REG_CISRCFMT_ITU601_16BIT BIT(29) +#define FIMC_REG_CISRCFMT_ORDER422_YCBYCR (0 << 14) +#define FIMC_REG_CISRCFMT_ORDER422_YCRYCB (1 << 14) +#define FIMC_REG_CISRCFMT_ORDER422_CBYCRY (2 << 14) +#define FIMC_REG_CISRCFMT_ORDER422_CRYCBY (3 << 14) + +/* Window offset */ +#define FIMC_REG_CIWDOFST 0x04 +#define FIMC_REG_CIWDOFST_OFF_EN BIT(31) +#define FIMC_REG_CIWDOFST_CLROVFIY BIT(30) +#define FIMC_REG_CIWDOFST_CLROVRLB BIT(29) +#define FIMC_REG_CIWDOFST_HOROFF_MASK (0x7ff << 16) +#define FIMC_REG_CIWDOFST_CLROVFICB BIT(15) +#define FIMC_REG_CIWDOFST_CLROVFICR BIT(14) +#define FIMC_REG_CIWDOFST_VEROFF_MASK (0xfff << 0) + +/* Global control */ +#define FIMC_REG_CIGCTRL 0x08 +#define FIMC_REG_CIGCTRL_SWRST BIT(31) +#define FIMC_REG_CIGCTRL_CAMRST_A BIT(30) +#define FIMC_REG_CIGCTRL_SELCAM_ITU_A BIT(29) +#define FIMC_REG_CIGCTRL_TESTPAT_NORMAL (0 << 27) +#define FIMC_REG_CIGCTRL_TESTPAT_COLOR_BAR (1 << 27) +#define FIMC_REG_CIGCTRL_TESTPAT_HOR_INC (2 << 27) +#define FIMC_REG_CIGCTRL_TESTPAT_VER_INC (3 << 27) +#define FIMC_REG_CIGCTRL_TESTPAT_MASK (3 << 27) +#define FIMC_REG_CIGCTRL_TESTPAT_SHIFT 27 +#define FIMC_REG_CIGCTRL_INVPOLPCLK BIT(26) +#define FIMC_REG_CIGCTRL_INVPOLVSYNC BIT(25) +#define FIMC_REG_CIGCTRL_INVPOLHREF BIT(24) +#define FIMC_REG_CIGCTRL_IRQ_OVFEN BIT(22) +#define FIMC_REG_CIGCTRL_HREF_MASK BIT(21) +#define FIMC_REG_CIGCTRL_IRQ_LEVEL BIT(20) +#define FIMC_REG_CIGCTRL_IRQ_CLR BIT(19) +#define FIMC_REG_CIGCTRL_IRQ_ENABLE BIT(16) +#define FIMC_REG_CIGCTRL_SHDW_DISABLE BIT(12) +/* 0 - selects Writeback A (LCD), 1 - selects Writeback B (LCD/ISP) */ +#define FIMC_REG_CIGCTRL_SELWB_A BIT(10) +#define FIMC_REG_CIGCTRL_CAM_JPEG BIT(8) +#define FIMC_REG_CIGCTRL_SELCAM_MIPI_A BIT(7) +#define FIMC_REG_CIGCTRL_CAMIF_SELWB BIT(6) +/* 0 - ITU601; 1 - ITU709 */ +#define FIMC_REG_CIGCTRL_CSC_ITU601_709 BIT(5) +#define FIMC_REG_CIGCTRL_INVPOLHSYNC BIT(4) +#define FIMC_REG_CIGCTRL_SELCAM_MIPI BIT(3) +#define FIMC_REG_CIGCTRL_INVPOLFIELD BIT(1) +#define FIMC_REG_CIGCTRL_INTERLACE BIT(0) + +/* Window offset 2 */ +#define FIMC_REG_CIWDOFST2 0x14 +#define FIMC_REG_CIWDOFST2_HOROFF_MASK (0xfff << 16) +#define FIMC_REG_CIWDOFST2_VEROFF_MASK (0xfff << 0) + +/* Output DMA Y/Cb/Cr plane start addresses */ +#define FIMC_REG_CIOYSA(n) (0x18 + (n) * 4) +#define FIMC_REG_CIOCBSA(n) (0x28 + (n) * 4) +#define FIMC_REG_CIOCRSA(n) (0x38 + (n) * 4) + +/* Target image format */ +#define FIMC_REG_CITRGFMT 0x48 +#define FIMC_REG_CITRGFMT_INROT90 BIT(31) +#define FIMC_REG_CITRGFMT_YCBCR420 (0 << 29) +#define FIMC_REG_CITRGFMT_YCBCR422 (1 << 29) +#define FIMC_REG_CITRGFMT_YCBCR422_1P (2 << 29) +#define FIMC_REG_CITRGFMT_RGB (3 << 29) +#define FIMC_REG_CITRGFMT_FMT_MASK (3 << 29) +#define FIMC_REG_CITRGFMT_HSIZE_MASK (0xfff << 16) +#define FIMC_REG_CITRGFMT_FLIP_SHIFT 14 +#define FIMC_REG_CITRGFMT_FLIP_NORMAL (0 << 14) +#define FIMC_REG_CITRGFMT_FLIP_X_MIRROR (1 << 14) +#define FIMC_REG_CITRGFMT_FLIP_Y_MIRROR (2 << 14) +#define FIMC_REG_CITRGFMT_FLIP_180 (3 << 14) +#define FIMC_REG_CITRGFMT_FLIP_MASK (3 << 14) +#define FIMC_REG_CITRGFMT_OUTROT90 BIT(13) +#define FIMC_REG_CITRGFMT_VSIZE_MASK (0xfff << 0) + +/* Output DMA control */ +#define FIMC_REG_CIOCTRL 0x4c +#define FIMC_REG_CIOCTRL_ORDER422_MASK (3 << 0) +#define FIMC_REG_CIOCTRL_ORDER422_YCBYCR (0 << 0) +#define FIMC_REG_CIOCTRL_ORDER422_YCRYCB (1 << 0) +#define FIMC_REG_CIOCTRL_ORDER422_CBYCRY (2 << 0) +#define FIMC_REG_CIOCTRL_ORDER422_CRYCBY (3 << 0) +#define FIMC_REG_CIOCTRL_LASTIRQ_ENABLE BIT(2) +#define FIMC_REG_CIOCTRL_YCBCR_3PLANE (0 << 3) +#define FIMC_REG_CIOCTRL_YCBCR_2PLANE (1 << 3) +#define FIMC_REG_CIOCTRL_YCBCR_PLANE_MASK (1 << 3) +#define FIMC_REG_CIOCTRL_ALPHA_OUT_MASK (0xff << 4) +#define FIMC_REG_CIOCTRL_RGB16FMT_MASK (3 << 16) +#define FIMC_REG_CIOCTRL_RGB565 (0 << 16) +#define FIMC_REG_CIOCTRL_ARGB1555 (1 << 16) +#define FIMC_REG_CIOCTRL_ARGB4444 (2 << 16) +#define FIMC_REG_CIOCTRL_ORDER2P_SHIFT 24 +#define FIMC_REG_CIOCTRL_ORDER2P_MASK (3 << 24) +#define FIMC_REG_CIOCTRL_ORDER422_2P_LSB_CRCB (0 << 24) + +/* Pre-scaler control 1 */ +#define FIMC_REG_CISCPRERATIO 0x50 + +#define FIMC_REG_CISCPREDST 0x54 + +/* Main scaler control */ +#define FIMC_REG_CISCCTRL 0x58 +#define FIMC_REG_CISCCTRL_SCALERBYPASS BIT(31) +#define FIMC_REG_CISCCTRL_SCALEUP_H BIT(30) +#define FIMC_REG_CISCCTRL_SCALEUP_V BIT(29) +#define FIMC_REG_CISCCTRL_CSCR2Y_WIDE BIT(28) +#define FIMC_REG_CISCCTRL_CSCY2R_WIDE BIT(27) +#define FIMC_REG_CISCCTRL_LCDPATHEN_FIFO BIT(26) +#define FIMC_REG_CISCCTRL_INTERLACE BIT(25) +#define FIMC_REG_CISCCTRL_SCALERSTART BIT(15) +#define FIMC_REG_CISCCTRL_INRGB_FMT_RGB565 (0 << 13) +#define FIMC_REG_CISCCTRL_INRGB_FMT_RGB666 (1 << 13) +#define FIMC_REG_CISCCTRL_INRGB_FMT_RGB888 (2 << 13) +#define FIMC_REG_CISCCTRL_INRGB_FMT_MASK (3 << 13) +#define FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB565 (0 << 11) +#define FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB666 (1 << 11) +#define FIMC_REG_CISCCTRL_OUTRGB_FMT_RGB888 (2 << 11) +#define FIMC_REG_CISCCTRL_OUTRGB_FMT_MASK (3 << 11) +#define FIMC_REG_CISCCTRL_RGB_EXT BIT(10) +#define FIMC_REG_CISCCTRL_ONE2ONE BIT(9) +#define FIMC_REG_CISCCTRL_MHRATIO(x) ((x) << 16) +#define FIMC_REG_CISCCTRL_MVRATIO(x) ((x) << 0) +#define FIMC_REG_CISCCTRL_MHRATIO_MASK (0x1ff << 16) +#define FIMC_REG_CISCCTRL_MVRATIO_MASK (0x1ff << 0) +#define FIMC_REG_CISCCTRL_MHRATIO_EXT(x) (((x) >> 6) << 16) +#define FIMC_REG_CISCCTRL_MVRATIO_EXT(x) (((x) >> 6) << 0) + +/* Target area */ +#define FIMC_REG_CITAREA 0x5c +#define FIMC_REG_CITAREA_MASK 0x0fffffff + +/* General status */ +#define FIMC_REG_CISTATUS 0x64 +#define FIMC_REG_CISTATUS_OVFIY BIT(31) +#define FIMC_REG_CISTATUS_OVFICB BIT(30) +#define FIMC_REG_CISTATUS_OVFICR BIT(29) +#define FIMC_REG_CISTATUS_VSYNC BIT(28) +#define FIMC_REG_CISTATUS_FRAMECNT_MASK (3 << 26) +#define FIMC_REG_CISTATUS_FRAMECNT_SHIFT 26 +#define FIMC_REG_CISTATUS_WINOFF_EN BIT(25) +#define FIMC_REG_CISTATUS_IMGCPT_EN BIT(22) +#define FIMC_REG_CISTATUS_IMGCPT_SCEN BIT(21) +#define FIMC_REG_CISTATUS_VSYNC_A BIT(20) +#define FIMC_REG_CISTATUS_VSYNC_B BIT(19) +#define FIMC_REG_CISTATUS_OVRLB BIT(18) +#define FIMC_REG_CISTATUS_FRAME_END BIT(17) +#define FIMC_REG_CISTATUS_LASTCAPT_END BIT(16) +#define FIMC_REG_CISTATUS_VVALID_A BIT(15) +#define FIMC_REG_CISTATUS_VVALID_B BIT(14) + +/* Indexes to the last and the currently processed buffer. */ +#define FIMC_REG_CISTATUS2 0x68 + +/* Image capture control */ +#define FIMC_REG_CIIMGCPT 0xc0 +#define FIMC_REG_CIIMGCPT_IMGCPTEN BIT(31) +#define FIMC_REG_CIIMGCPT_IMGCPTEN_SC BIT(30) +#define FIMC_REG_CIIMGCPT_CPT_FREN_ENABLE BIT(25) +#define FIMC_REG_CIIMGCPT_CPT_FRMOD_CNT BIT(18) + +/* Frame capture sequence */ +#define FIMC_REG_CICPTSEQ 0xc4 + +/* Image effect */ +#define FIMC_REG_CIIMGEFF 0xd0 +#define FIMC_REG_CIIMGEFF_IE_ENABLE BIT(30) +#define FIMC_REG_CIIMGEFF_IE_SC_BEFORE (0 << 29) +#define FIMC_REG_CIIMGEFF_IE_SC_AFTER (1 << 29) +#define FIMC_REG_CIIMGEFF_FIN_BYPASS (0 << 26) +#define FIMC_REG_CIIMGEFF_FIN_ARBITRARY (1 << 26) +#define FIMC_REG_CIIMGEFF_FIN_NEGATIVE (2 << 26) +#define FIMC_REG_CIIMGEFF_FIN_ARTFREEZE (3 << 26) +#define FIMC_REG_CIIMGEFF_FIN_EMBOSSING (4 << 26) +#define FIMC_REG_CIIMGEFF_FIN_SILHOUETTE (5 << 26) +#define FIMC_REG_CIIMGEFF_FIN_MASK (7 << 26) +#define FIMC_REG_CIIMGEFF_PAT_CBCR_MASK ((0xff << 13) | 0xff) + +/* Input DMA Y/Cb/Cr plane start address 0/1 */ +#define FIMC_REG_CIIYSA(n) (0xd4 + (n) * 0x70) +#define FIMC_REG_CIICBSA(n) (0xd8 + (n) * 0x70) +#define FIMC_REG_CIICRSA(n) (0xdc + (n) * 0x70) + +/* Real input DMA image size */ +#define FIMC_REG_CIREAL_ISIZE 0xf8 +#define FIMC_REG_CIREAL_ISIZE_AUTOLOAD_EN BIT(31) +#define FIMC_REG_CIREAL_ISIZE_ADDR_CH_DIS BIT(30) + +/* Input DMA control */ +#define FIMC_REG_MSCTRL 0xfc +#define FIMC_REG_MSCTRL_IN_BURST_COUNT_MASK (0xf << 24) +#define FIMC_REG_MSCTRL_2P_IN_ORDER_MASK (3 << 16) +#define FIMC_REG_MSCTRL_2P_IN_ORDER_SHIFT 16 +#define FIMC_REG_MSCTRL_C_INT_IN_3PLANE (0 << 15) +#define FIMC_REG_MSCTRL_C_INT_IN_2PLANE (1 << 15) +#define FIMC_REG_MSCTRL_C_INT_IN_MASK (1 << 15) +#define FIMC_REG_MSCTRL_FLIP_SHIFT 13 +#define FIMC_REG_MSCTRL_FLIP_MASK (3 << 13) +#define FIMC_REG_MSCTRL_FLIP_NORMAL (0 << 13) +#define FIMC_REG_MSCTRL_FLIP_X_MIRROR (1 << 13) +#define FIMC_REG_MSCTRL_FLIP_Y_MIRROR (2 << 13) +#define FIMC_REG_MSCTRL_FLIP_180 (3 << 13) +#define FIMC_REG_MSCTRL_FIFO_CTRL_FULL BIT(12) +#define FIMC_REG_MSCTRL_ORDER422_SHIFT 4 +#define FIMC_REG_MSCTRL_ORDER422_CRYCBY (0 << 4) +#define FIMC_REG_MSCTRL_ORDER422_YCRYCB (1 << 4) +#define FIMC_REG_MSCTRL_ORDER422_CBYCRY (2 << 4) +#define FIMC_REG_MSCTRL_ORDER422_YCBYCR (3 << 4) +#define FIMC_REG_MSCTRL_ORDER422_MASK (3 << 4) +#define FIMC_REG_MSCTRL_INPUT_EXTCAM (0 << 3) +#define FIMC_REG_MSCTRL_INPUT_MEMORY BIT(3) +#define FIMC_REG_MSCTRL_INPUT_MASK BIT(3) +#define FIMC_REG_MSCTRL_INFORMAT_YCBCR420 (0 << 1) +#define FIMC_REG_MSCTRL_INFORMAT_YCBCR422 (1 << 1) +#define FIMC_REG_MSCTRL_INFORMAT_YCBCR422_1P (2 << 1) +#define FIMC_REG_MSCTRL_INFORMAT_RGB (3 << 1) +#define FIMC_REG_MSCTRL_INFORMAT_MASK (3 << 1) +#define FIMC_REG_MSCTRL_ENVID BIT(0) +#define FIMC_REG_MSCTRL_IN_BURST_COUNT(x) ((x) << 24) + +/* Output DMA Y/Cb/Cr offset */ +#define FIMC_REG_CIOYOFF 0x168 +#define FIMC_REG_CIOCBOFF 0x16c +#define FIMC_REG_CIOCROFF 0x170 + +/* Input DMA Y/Cb/Cr offset */ +#define FIMC_REG_CIIYOFF 0x174 +#define FIMC_REG_CIICBOFF 0x178 +#define FIMC_REG_CIICROFF 0x17c + +/* Input DMA original image size */ +#define FIMC_REG_ORGISIZE 0x180 + +/* Output DMA original image size */ +#define FIMC_REG_ORGOSIZE 0x184 + +/* Real output DMA image size (extension register) */ +#define FIMC_REG_CIEXTEN 0x188 +#define FIMC_REG_CIEXTEN_MHRATIO_EXT(x) (((x) & 0x3f) << 10) +#define FIMC_REG_CIEXTEN_MVRATIO_EXT(x) ((x) & 0x3f) +#define FIMC_REG_CIEXTEN_MHRATIO_EXT_MASK (0x3f << 10) +#define FIMC_REG_CIEXTEN_MVRATIO_EXT_MASK 0x3f + +#define FIMC_REG_CIDMAPARAM 0x18c +#define FIMC_REG_CIDMAPARAM_R_LINEAR (0 << 29) +#define FIMC_REG_CIDMAPARAM_R_64X32 (3 << 29) +#define FIMC_REG_CIDMAPARAM_W_LINEAR (0 << 13) +#define FIMC_REG_CIDMAPARAM_W_64X32 (3 << 13) +#define FIMC_REG_CIDMAPARAM_TILE_MASK ((3 << 29) | (3 << 13)) + +/* MIPI CSI image format */ +#define FIMC_REG_CSIIMGFMT 0x194 +#define FIMC_REG_CSIIMGFMT_YCBCR422_8BIT 0x1e +#define FIMC_REG_CSIIMGFMT_RAW8 0x2a +#define FIMC_REG_CSIIMGFMT_RAW10 0x2b +#define FIMC_REG_CSIIMGFMT_RAW12 0x2c +/* User defined formats. x = 0...16. */ +#define FIMC_REG_CSIIMGFMT_USER(x) (0x30 + x - 1) + +/* Output frame buffer sequence mask */ +#define FIMC_REG_CIFCNTSEQ 0x1fc + +/* SYSREG ISP Writeback register address offsets */ +#define SYSREG_ISPBLK 0x020c +#define SYSREG_ISPBLK_FIFORST_CAM_BLK BIT(7) + +#define SYSREG_CAMBLK 0x0218 +#define SYSREG_CAMBLK_FIFORST_ISP BIT(15) +#define SYSREG_CAMBLK_ISPWB_FULL_EN (7 << 20) + +/* + * Function declarations + */ +void fimc_hw_reset(struct fimc_dev *fimc); +void fimc_hw_set_rotation(struct fimc_ctx *ctx); +void fimc_hw_set_target_format(struct fimc_ctx *ctx); +void fimc_hw_set_out_dma(struct fimc_ctx *ctx); +void fimc_hw_en_lastirq(struct fimc_dev *fimc, int enable); +void fimc_hw_en_irq(struct fimc_dev *fimc, int enable); +void fimc_hw_set_prescaler(struct fimc_ctx *ctx); +void fimc_hw_set_mainscaler(struct fimc_ctx *ctx); +void fimc_hw_enable_capture(struct fimc_ctx *ctx); +void fimc_hw_set_effect(struct fimc_ctx *ctx); +void fimc_hw_set_rgb_alpha(struct fimc_ctx *ctx); +void fimc_hw_set_in_dma(struct fimc_ctx *ctx); +void fimc_hw_set_input_path(struct fimc_ctx *ctx); +void fimc_hw_set_output_path(struct fimc_ctx *ctx); +void fimc_hw_set_input_addr(struct fimc_dev *fimc, struct fimc_addr *addr); +void fimc_hw_set_output_addr(struct fimc_dev *fimc, struct fimc_addr *addr, + int index); +int fimc_hw_set_camera_source(struct fimc_dev *fimc, + struct fimc_source_info *cam); +void fimc_hw_set_camera_offset(struct fimc_dev *fimc, struct fimc_frame *f); +int fimc_hw_set_camera_polarity(struct fimc_dev *fimc, + struct fimc_source_info *cam); +int fimc_hw_set_camera_type(struct fimc_dev *fimc, + struct fimc_source_info *cam); +void fimc_hw_clear_irq(struct fimc_dev *dev); +void fimc_hw_enable_scaler(struct fimc_dev *dev, bool on); +void fimc_hw_activate_input_dma(struct fimc_dev *dev, bool on); +void fimc_hw_disable_capture(struct fimc_dev *dev); +s32 fimc_hw_get_frame_index(struct fimc_dev *dev); +s32 fimc_hw_get_prev_frame_index(struct fimc_dev *dev); +int fimc_hw_camblk_cfg_writeback(struct fimc_dev *fimc); +void fimc_activate_capture(struct fimc_ctx *ctx); +void fimc_deactivate_capture(struct fimc_dev *fimc); + +/** + * fimc_hw_set_dma_seq - configure output DMA buffer sequence + * @dev: fimc device + * @mask: bitmask for the DMA output buffer registers, set to 0 to skip buffer + * This function masks output DMA ring buffers, it allows to select which of + * the 32 available output buffer address registers will be used by the DMA + * engine. + */ +static inline void fimc_hw_set_dma_seq(struct fimc_dev *dev, u32 mask) +{ + writel(mask, dev->regs + FIMC_REG_CIFCNTSEQ); +} + +#endif /* FIMC_REG_H_ */ diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.c b/drivers/media/platform/samsung/exynos4-is/media-dev.c new file mode 100644 index 000000000..2f3071acb --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.c @@ -0,0 +1,1606 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * S5P/EXYNOS4 SoC series camera host interface media device driver + * + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "media-dev.h" +#include "fimc-core.h" +#include "fimc-is.h" +#include "fimc-lite.h" +#include "mipi-csis.h" + +/* Set up image sensor subdev -> FIMC capture node notifications. */ +static void __setup_sensor_notification(struct fimc_md *fmd, + struct v4l2_subdev *sensor, + struct v4l2_subdev *fimc_sd) +{ + struct fimc_source_info *src_inf; + struct fimc_sensor_info *md_si; + unsigned long flags; + + src_inf = v4l2_get_subdev_hostdata(sensor); + if (!src_inf || WARN_ON(fmd == NULL)) + return; + + md_si = source_to_sensor_info(src_inf); + spin_lock_irqsave(&fmd->slock, flags); + md_si->host = v4l2_get_subdevdata(fimc_sd); + spin_unlock_irqrestore(&fmd->slock, flags); +} + +/** + * fimc_pipeline_prepare - update pipeline information with subdevice pointers + * @p: fimc pipeline + * @me: media entity terminating the pipeline + * + * Caller holds the graph mutex. + */ +static void fimc_pipeline_prepare(struct fimc_pipeline *p, + struct media_entity *me) +{ + struct fimc_md *fmd = entity_to_fimc_mdev(me); + struct v4l2_subdev *sd; + struct v4l2_subdev *sensor = NULL; + int i; + + for (i = 0; i < IDX_MAX; i++) + p->subdevs[i] = NULL; + + while (1) { + struct media_pad *pad = NULL; + + /* Find remote source pad */ + for (i = 0; i < me->num_pads; i++) { + struct media_pad *spad = &me->pads[i]; + if (!(spad->flags & MEDIA_PAD_FL_SINK)) + continue; + pad = media_pad_remote_pad_first(spad); + if (pad) + break; + } + + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + sd = media_entity_to_v4l2_subdev(pad->entity); + + switch (sd->grp_id) { + case GRP_ID_SENSOR: + sensor = sd; + fallthrough; + case GRP_ID_FIMC_IS_SENSOR: + p->subdevs[IDX_SENSOR] = sd; + break; + case GRP_ID_CSIS: + p->subdevs[IDX_CSIS] = sd; + break; + case GRP_ID_FLITE: + p->subdevs[IDX_FLITE] = sd; + break; + case GRP_ID_FIMC: + p->subdevs[IDX_FIMC] = sd; + break; + case GRP_ID_FIMC_IS: + p->subdevs[IDX_IS_ISP] = sd; + break; + default: + break; + } + me = &sd->entity; + if (me->num_pads == 1) + break; + } + + if (sensor && p->subdevs[IDX_FIMC]) + __setup_sensor_notification(fmd, sensor, p->subdevs[IDX_FIMC]); +} + +/** + * __subdev_set_power - change power state of a single subdev + * @sd: subdevice to change power state for + * @on: 1 to enable power or 0 to disable + * + * Return result of s_power subdev operation or -ENXIO if sd argument + * is NULL. Return 0 if the subdevice does not implement s_power. + */ +static int __subdev_set_power(struct v4l2_subdev *sd, int on) +{ + int *use_count; + int ret; + + if (sd == NULL) + return -ENXIO; + + use_count = &sd->entity.use_count; + if (on && (*use_count)++ > 0) + return 0; + else if (!on && (*use_count == 0 || --(*use_count) > 0)) + return 0; + ret = v4l2_subdev_call(sd, core, s_power, on); + + return ret != -ENOIOCTLCMD ? ret : 0; +} + +/** + * fimc_pipeline_s_power - change power state of all pipeline subdevs + * @p: fimc device terminating the pipeline + * @on: true to power on, false to power off + * + * Needs to be called with the graph mutex held. + */ +static int fimc_pipeline_s_power(struct fimc_pipeline *p, bool on) +{ + static const u8 seq[2][IDX_MAX - 1] = { + { IDX_IS_ISP, IDX_SENSOR, IDX_CSIS, IDX_FLITE }, + { IDX_CSIS, IDX_FLITE, IDX_SENSOR, IDX_IS_ISP }, + }; + int i, ret = 0; + + if (p->subdevs[IDX_SENSOR] == NULL) + return -ENXIO; + + for (i = 0; i < IDX_MAX - 1; i++) { + unsigned int idx = seq[on][i]; + + ret = __subdev_set_power(p->subdevs[idx], on); + + + if (ret < 0 && ret != -ENXIO) + goto error; + } + return 0; +error: + for (; i >= 0; i--) { + unsigned int idx = seq[on][i]; + __subdev_set_power(p->subdevs[idx], !on); + } + return ret; +} + +/** + * __fimc_pipeline_enable - enable power of all pipeline subdevs + * and the sensor clock + * @ep: video pipeline structure + * @fmd: fimc media device + * + * Called with the graph mutex held. + */ +static int __fimc_pipeline_enable(struct exynos_media_pipeline *ep, + struct fimc_md *fmd) +{ + struct fimc_pipeline *p = to_fimc_pipeline(ep); + int ret; + + /* Enable PXLASYNC clock if this pipeline includes FIMC-IS */ + if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP]) { + ret = clk_prepare_enable(fmd->wbclk[CLK_IDX_WB_B]); + if (ret < 0) + return ret; + } + + ret = fimc_pipeline_s_power(p, 1); + if (!ret) + return 0; + + if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP]) + clk_disable_unprepare(fmd->wbclk[CLK_IDX_WB_B]); + + return ret; +} + +/** + * __fimc_pipeline_open - update the pipeline information, enable power + * of all pipeline subdevs and the sensor clock + * @ep: fimc device terminating the pipeline + * @me: media entity to start graph walk with + * @prepare: true to walk the current pipeline and acquire all subdevs + * + * Called with the graph mutex held. + */ +static int __fimc_pipeline_open(struct exynos_media_pipeline *ep, + struct media_entity *me, bool prepare) +{ + struct fimc_md *fmd = entity_to_fimc_mdev(me); + struct fimc_pipeline *p = to_fimc_pipeline(ep); + struct v4l2_subdev *sd; + + if (WARN_ON(p == NULL || me == NULL)) + return -EINVAL; + + if (prepare) + fimc_pipeline_prepare(p, me); + + sd = p->subdevs[IDX_SENSOR]; + if (sd == NULL) { + pr_warn("%s(): No sensor subdev\n", __func__); + /* + * Pipeline open cannot fail so as to make it possible + * for the user space to configure the pipeline. + */ + return 0; + } + + return __fimc_pipeline_enable(ep, fmd); +} + +/** + * __fimc_pipeline_close - disable the sensor clock and pipeline power + * @ep: fimc device terminating the pipeline + * + * Disable power of all subdevs and turn the external sensor clock off. + */ +static int __fimc_pipeline_close(struct exynos_media_pipeline *ep) +{ + struct fimc_pipeline *p = to_fimc_pipeline(ep); + struct v4l2_subdev *sd = p ? p->subdevs[IDX_SENSOR] : NULL; + struct fimc_md *fmd; + int ret; + + if (sd == NULL) { + pr_warn("%s(): No sensor subdev\n", __func__); + return 0; + } + + ret = fimc_pipeline_s_power(p, 0); + + fmd = entity_to_fimc_mdev(&sd->entity); + + /* Disable PXLASYNC clock if this pipeline includes FIMC-IS */ + if (!IS_ERR(fmd->wbclk[CLK_IDX_WB_B]) && p->subdevs[IDX_IS_ISP]) + clk_disable_unprepare(fmd->wbclk[CLK_IDX_WB_B]); + + return ret == -ENXIO ? 0 : ret; +} + +/** + * __fimc_pipeline_s_stream - call s_stream() on pipeline subdevs + * @ep: video pipeline structure + * @on: passed as the s_stream() callback argument + */ +static int __fimc_pipeline_s_stream(struct exynos_media_pipeline *ep, bool on) +{ + static const u8 seq[2][IDX_MAX] = { + { IDX_FIMC, IDX_SENSOR, IDX_IS_ISP, IDX_CSIS, IDX_FLITE }, + { IDX_CSIS, IDX_FLITE, IDX_FIMC, IDX_SENSOR, IDX_IS_ISP }, + }; + struct fimc_pipeline *p = to_fimc_pipeline(ep); + enum fimc_subdev_index sd_id; + int i, ret = 0; + + if (p->subdevs[IDX_SENSOR] == NULL) { + struct fimc_md *fmd; + struct v4l2_subdev *sd = p->subdevs[IDX_CSIS]; + + if (!sd) + sd = p->subdevs[IDX_FIMC]; + + if (!sd) { + /* + * If neither CSIS nor FIMC was set up, + * it's impossible to have any sensors + */ + return -ENODEV; + } + + fmd = entity_to_fimc_mdev(&sd->entity); + + if (!fmd->user_subdev_api) { + /* + * Sensor must be already discovered if we + * aren't in the user_subdev_api mode + */ + return -ENODEV; + } + + /* Get pipeline sink entity */ + if (p->subdevs[IDX_FIMC]) + sd_id = IDX_FIMC; + else if (p->subdevs[IDX_IS_ISP]) + sd_id = IDX_IS_ISP; + else if (p->subdevs[IDX_FLITE]) + sd_id = IDX_FLITE; + else + return -ENODEV; + + /* + * Sensor could have been linked between open and STREAMON - + * check if this is the case. + */ + fimc_pipeline_prepare(p, &p->subdevs[sd_id]->entity); + + if (p->subdevs[IDX_SENSOR] == NULL) + return -ENODEV; + + ret = __fimc_pipeline_enable(ep, fmd); + if (ret < 0) + return ret; + + } + + for (i = 0; i < IDX_MAX; i++) { + unsigned int idx = seq[on][i]; + + ret = v4l2_subdev_call(p->subdevs[idx], video, s_stream, on); + + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + goto error; + } + + return 0; +error: + fimc_pipeline_s_power(p, !on); + for (; i >= 0; i--) { + unsigned int idx = seq[on][i]; + v4l2_subdev_call(p->subdevs[idx], video, s_stream, !on); + } + return ret; +} + +/* Media pipeline operations for the FIMC/FIMC-LITE video device driver */ +static const struct exynos_media_pipeline_ops fimc_pipeline_ops = { + .open = __fimc_pipeline_open, + .close = __fimc_pipeline_close, + .set_stream = __fimc_pipeline_s_stream, +}; + +static struct exynos_media_pipeline *fimc_md_pipeline_create( + struct fimc_md *fmd) +{ + struct fimc_pipeline *p; + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) + return NULL; + + list_add_tail(&p->list, &fmd->pipelines); + + p->ep.ops = &fimc_pipeline_ops; + return &p->ep; +} + +static void fimc_md_pipelines_free(struct fimc_md *fmd) +{ + while (!list_empty(&fmd->pipelines)) { + struct fimc_pipeline *p; + + p = list_entry(fmd->pipelines.next, typeof(*p), list); + list_del(&p->list); + kfree(p); + } +} + +static int fimc_md_parse_one_endpoint(struct fimc_md *fmd, + struct device_node *ep) +{ + int index = fmd->num_sensors; + struct fimc_source_info *pd = &fmd->sensor[index].pdata; + struct device_node *rem, *np; + struct v4l2_async_subdev *asd; + struct v4l2_fwnode_endpoint endpoint = { .bus_type = 0 }; + int ret; + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &endpoint); + if (ret) { + of_node_put(ep); + return ret; + } + + if (WARN_ON(endpoint.base.port == 0) || index >= FIMC_MAX_SENSORS) { + of_node_put(ep); + return -EINVAL; + } + + pd->mux_id = (endpoint.base.port - 1) & 0x1; + + rem = of_graph_get_remote_port_parent(ep); + if (rem == NULL) { + v4l2_info(&fmd->v4l2_dev, "Remote device at %pOF not found\n", + ep); + of_node_put(ep); + return 0; + } + + if (fimc_input_is_parallel(endpoint.base.port)) { + if (endpoint.bus_type == V4L2_MBUS_PARALLEL) + pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_601; + else + pd->sensor_bus_type = FIMC_BUS_TYPE_ITU_656; + pd->flags = endpoint.bus.parallel.flags; + } else if (fimc_input_is_mipi_csi(endpoint.base.port)) { + /* + * MIPI CSI-2: only input mux selection and + * the sensor's clock frequency is needed. + */ + pd->sensor_bus_type = FIMC_BUS_TYPE_MIPI_CSI2; + } else { + v4l2_err(&fmd->v4l2_dev, "Wrong port id (%u) at node %pOF\n", + endpoint.base.port, rem); + } + /* + * For FIMC-IS handled sensors, that are placed under i2c-isp device + * node, FIMC is connected to the FIMC-IS through its ISP Writeback + * input. Sensors are attached to the FIMC-LITE hostdata interface + * directly or through MIPI-CSIS, depending on the external media bus + * used. This needs to be handled in a more reliable way, not by just + * checking parent's node name. + */ + np = of_get_parent(rem); + of_node_put(rem); + + if (of_node_name_eq(np, "i2c-isp")) + pd->fimc_bus_type = FIMC_BUS_TYPE_ISP_WRITEBACK; + else + pd->fimc_bus_type = pd->sensor_bus_type; + of_node_put(np); + + if (WARN_ON(index >= ARRAY_SIZE(fmd->sensor))) { + of_node_put(ep); + return -EINVAL; + } + + asd = v4l2_async_nf_add_fwnode_remote(&fmd->subdev_notifier, + of_fwnode_handle(ep), + struct v4l2_async_subdev); + + of_node_put(ep); + + if (IS_ERR(asd)) + return PTR_ERR(asd); + + fmd->sensor[index].asd = asd; + fmd->num_sensors++; + + return 0; +} + +/* Parse port node and register as a sub-device any sensor specified there. */ +static int fimc_md_parse_port_node(struct fimc_md *fmd, + struct device_node *port) +{ + struct device_node *ep; + int ret; + + for_each_child_of_node(port, ep) { + ret = fimc_md_parse_one_endpoint(fmd, ep); + if (ret < 0) { + of_node_put(ep); + return ret; + } + } + + return 0; +} + +/* Register all SoC external sub-devices */ +static int fimc_md_register_sensor_entities(struct fimc_md *fmd) +{ + struct device_node *parent = fmd->pdev->dev.of_node; + struct device_node *ports = NULL; + struct device_node *node; + int ret; + + /* + * Runtime resume one of the FIMC entities to make sure + * the sclk_cam clocks are not globally disabled. + */ + if (!fmd->pmf) + return -ENXIO; + + ret = pm_runtime_resume_and_get(fmd->pmf); + if (ret < 0) + return ret; + + fmd->num_sensors = 0; + + /* Attach sensors linked to MIPI CSI-2 receivers */ + for_each_available_child_of_node(parent, node) { + struct device_node *port; + + if (!of_node_name_eq(node, "csis")) + continue; + /* The csis node can have only port subnode. */ + port = of_get_next_child(node, NULL); + if (!port) + continue; + + ret = fimc_md_parse_port_node(fmd, port); + of_node_put(port); + if (ret < 0) { + of_node_put(node); + goto cleanup; + } + } + + /* Attach sensors listed in the parallel-ports node */ + ports = of_get_child_by_name(parent, "parallel-ports"); + if (!ports) + goto rpm_put; + + for_each_child_of_node(ports, node) { + ret = fimc_md_parse_port_node(fmd, node); + if (ret < 0) { + of_node_put(node); + goto cleanup; + } + } + of_node_put(ports); + +rpm_put: + pm_runtime_put(fmd->pmf); + return 0; + +cleanup: + of_node_put(ports); + v4l2_async_nf_cleanup(&fmd->subdev_notifier); + pm_runtime_put(fmd->pmf); + return ret; +} + +static int __of_get_csis_id(struct device_node *np) +{ + u32 reg = 0; + + np = of_get_child_by_name(np, "port"); + if (!np) + return -EINVAL; + of_property_read_u32(np, "reg", ®); + of_node_put(np); + return reg - FIMC_INPUT_MIPI_CSI2_0; +} + +/* + * MIPI-CSIS, FIMC and FIMC-LITE platform devices registration. + */ +static int register_fimc_lite_entity(struct fimc_md *fmd, + struct fimc_lite *fimc_lite) +{ + struct v4l2_subdev *sd; + struct exynos_media_pipeline *ep; + int ret; + + if (WARN_ON(fimc_lite->index >= FIMC_LITE_MAX_DEVS || + fmd->fimc_lite[fimc_lite->index])) + return -EBUSY; + + sd = &fimc_lite->subdev; + sd->grp_id = GRP_ID_FLITE; + + ep = fimc_md_pipeline_create(fmd); + if (!ep) + return -ENOMEM; + + v4l2_set_subdev_hostdata(sd, ep); + + ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd); + if (!ret) + fmd->fimc_lite[fimc_lite->index] = fimc_lite; + else + v4l2_err(&fmd->v4l2_dev, "Failed to register FIMC.LITE%d\n", + fimc_lite->index); + return ret; +} + +static int register_fimc_entity(struct fimc_md *fmd, struct fimc_dev *fimc) +{ + struct v4l2_subdev *sd; + struct exynos_media_pipeline *ep; + int ret; + + if (WARN_ON(fimc->id >= FIMC_MAX_DEVS || fmd->fimc[fimc->id])) + return -EBUSY; + + sd = &fimc->vid_cap.subdev; + sd->grp_id = GRP_ID_FIMC; + + ep = fimc_md_pipeline_create(fmd); + if (!ep) + return -ENOMEM; + + v4l2_set_subdev_hostdata(sd, ep); + + ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd); + if (!ret) { + if (!fmd->pmf && fimc->pdev) + fmd->pmf = &fimc->pdev->dev; + fmd->fimc[fimc->id] = fimc; + fimc->vid_cap.user_subdev_api = fmd->user_subdev_api; + } else { + v4l2_err(&fmd->v4l2_dev, "Failed to register FIMC.%d (%d)\n", + fimc->id, ret); + } + return ret; +} + +static int register_csis_entity(struct fimc_md *fmd, + struct platform_device *pdev, + struct v4l2_subdev *sd) +{ + struct device_node *node = pdev->dev.of_node; + int id, ret; + + id = node ? __of_get_csis_id(node) : max(0, pdev->id); + + if (WARN_ON(id < 0 || id >= CSIS_MAX_ENTITIES)) + return -ENOENT; + + if (WARN_ON(fmd->csis[id].sd)) + return -EBUSY; + + sd->grp_id = GRP_ID_CSIS; + ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd); + if (!ret) + fmd->csis[id].sd = sd; + else + v4l2_err(&fmd->v4l2_dev, + "Failed to register MIPI-CSIS.%d (%d)\n", id, ret); + return ret; +} + +static int register_fimc_is_entity(struct fimc_md *fmd, struct fimc_is *is) +{ + struct v4l2_subdev *sd = &is->isp.subdev; + struct exynos_media_pipeline *ep; + int ret; + + /* Allocate pipeline object for the ISP capture video node. */ + ep = fimc_md_pipeline_create(fmd); + if (!ep) + return -ENOMEM; + + v4l2_set_subdev_hostdata(sd, ep); + + ret = v4l2_device_register_subdev(&fmd->v4l2_dev, sd); + if (ret) { + v4l2_err(&fmd->v4l2_dev, + "Failed to register FIMC-ISP (%d)\n", ret); + return ret; + } + + fmd->fimc_is = is; + return 0; +} + +static int fimc_md_register_platform_entity(struct fimc_md *fmd, + struct platform_device *pdev, + int plat_entity) +{ + struct device *dev = &pdev->dev; + int ret = -EPROBE_DEFER; + void *drvdata; + + /* Lock to ensure dev->driver won't change. */ + device_lock(dev); + + if (!dev->driver || !try_module_get(dev->driver->owner)) + goto dev_unlock; + + drvdata = dev_get_drvdata(dev); + /* Some subdev didn't probe successfully id drvdata is NULL */ + if (drvdata) { + switch (plat_entity) { + case IDX_FIMC: + ret = register_fimc_entity(fmd, drvdata); + break; + case IDX_FLITE: + ret = register_fimc_lite_entity(fmd, drvdata); + break; + case IDX_CSIS: + ret = register_csis_entity(fmd, pdev, drvdata); + break; + case IDX_IS_ISP: + ret = register_fimc_is_entity(fmd, drvdata); + break; + default: + ret = -ENODEV; + } + } + + module_put(dev->driver->owner); +dev_unlock: + device_unlock(dev); + if (ret == -EPROBE_DEFER) + dev_info(&fmd->pdev->dev, "deferring %s device registration\n", + dev_name(dev)); + else if (ret < 0) + dev_err(&fmd->pdev->dev, "%s device registration failed (%d)\n", + dev_name(dev), ret); + return ret; +} + +/* Register FIMC, FIMC-LITE and CSIS media entities */ +static int fimc_md_register_platform_entities(struct fimc_md *fmd, + struct device_node *parent) +{ + struct device_node *node; + int ret = 0; + + for_each_available_child_of_node(parent, node) { + struct platform_device *pdev; + int plat_entity = -1; + + pdev = of_find_device_by_node(node); + if (!pdev) + continue; + + /* If driver of any entity isn't ready try all again later. */ + if (of_node_name_eq(node, CSIS_OF_NODE_NAME)) + plat_entity = IDX_CSIS; + else if (of_node_name_eq(node, FIMC_IS_OF_NODE_NAME)) + plat_entity = IDX_IS_ISP; + else if (of_node_name_eq(node, FIMC_LITE_OF_NODE_NAME)) + plat_entity = IDX_FLITE; + else if (of_node_name_eq(node, FIMC_OF_NODE_NAME) && + !of_property_read_bool(node, "samsung,lcd-wb")) + plat_entity = IDX_FIMC; + + if (plat_entity >= 0) + ret = fimc_md_register_platform_entity(fmd, pdev, + plat_entity); + put_device(&pdev->dev); + if (ret < 0) { + of_node_put(node); + break; + } + } + + return ret; +} + +static void fimc_md_unregister_entities(struct fimc_md *fmd) +{ + int i; + + for (i = 0; i < FIMC_MAX_DEVS; i++) { + struct fimc_dev *dev = fmd->fimc[i]; + if (dev == NULL) + continue; + v4l2_device_unregister_subdev(&dev->vid_cap.subdev); + dev->vid_cap.ve.pipe = NULL; + fmd->fimc[i] = NULL; + } + for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) { + struct fimc_lite *dev = fmd->fimc_lite[i]; + if (dev == NULL) + continue; + v4l2_device_unregister_subdev(&dev->subdev); + dev->ve.pipe = NULL; + fmd->fimc_lite[i] = NULL; + } + for (i = 0; i < CSIS_MAX_ENTITIES; i++) { + if (fmd->csis[i].sd == NULL) + continue; + v4l2_device_unregister_subdev(fmd->csis[i].sd); + fmd->csis[i].sd = NULL; + } + + if (fmd->fimc_is) + v4l2_device_unregister_subdev(&fmd->fimc_is->isp.subdev); + + v4l2_info(&fmd->v4l2_dev, "Unregistered all entities\n"); +} + +/** + * __fimc_md_create_fimc_sink_links - create links to all FIMC entities + * @fmd: fimc media device + * @source: the source entity to create links to all fimc entities from + * @sensor: sensor subdev linked to FIMC[fimc_id] entity, may be null + * @pad: the source entity pad index + * @link_mask: bitmask of the fimc devices for which link should be enabled + */ +static int __fimc_md_create_fimc_sink_links(struct fimc_md *fmd, + struct media_entity *source, + struct v4l2_subdev *sensor, + int pad, int link_mask) +{ + struct fimc_source_info *si = NULL; + struct media_entity *sink; + unsigned int flags = 0; + int i, ret = 0; + + if (sensor) { + si = v4l2_get_subdev_hostdata(sensor); + /* Skip direct FIMC links in the logical FIMC-IS sensor path */ + if (si && si->fimc_bus_type == FIMC_BUS_TYPE_ISP_WRITEBACK) + ret = 1; + } + + for (i = 0; !ret && i < FIMC_MAX_DEVS; i++) { + if (!fmd->fimc[i]) + continue; + /* + * Some FIMC variants are not fitted with camera capture + * interface. Skip creating a link from sensor for those. + */ + if (!fmd->fimc[i]->variant->has_cam_if) + continue; + + flags = ((1 << i) & link_mask) ? MEDIA_LNK_FL_ENABLED : 0; + + sink = &fmd->fimc[i]->vid_cap.subdev.entity; + ret = media_create_pad_link(source, pad, sink, + FIMC_SD_PAD_SINK_CAM, flags); + if (ret) + return ret; + + /* Notify FIMC capture subdev entity */ + ret = media_entity_call(sink, link_setup, &sink->pads[0], + &source->pads[pad], flags); + if (ret) + break; + + v4l2_info(&fmd->v4l2_dev, "created link [%s] %c> [%s]\n", + source->name, flags ? '=' : '-', sink->name); + } + + for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) { + if (!fmd->fimc_lite[i]) + continue; + + sink = &fmd->fimc_lite[i]->subdev.entity; + ret = media_create_pad_link(source, pad, sink, + FLITE_SD_PAD_SINK, 0); + if (ret) + return ret; + + /* Notify FIMC-LITE subdev entity */ + ret = media_entity_call(sink, link_setup, &sink->pads[0], + &source->pads[pad], 0); + if (ret) + break; + + v4l2_info(&fmd->v4l2_dev, "created link [%s] -> [%s]\n", + source->name, sink->name); + } + return 0; +} + +/* Create links from FIMC-LITE source pads to other entities */ +static int __fimc_md_create_flite_source_links(struct fimc_md *fmd) +{ + struct media_entity *source, *sink; + int i, ret = 0; + + for (i = 0; i < FIMC_LITE_MAX_DEVS; i++) { + struct fimc_lite *fimc = fmd->fimc_lite[i]; + + if (fimc == NULL) + continue; + + source = &fimc->subdev.entity; + sink = &fimc->ve.vdev.entity; + /* FIMC-LITE's subdev and video node */ + ret = media_create_pad_link(source, FLITE_SD_PAD_SOURCE_DMA, + sink, 0, 0); + if (ret) + break; + /* Link from FIMC-LITE to IS-ISP subdev */ + sink = &fmd->fimc_is->isp.subdev.entity; + ret = media_create_pad_link(source, FLITE_SD_PAD_SOURCE_ISP, + sink, 0, 0); + if (ret) + break; + } + + return ret; +} + +/* Create FIMC-IS links */ +static int __fimc_md_create_fimc_is_links(struct fimc_md *fmd) +{ + struct fimc_isp *isp = &fmd->fimc_is->isp; + struct media_entity *source, *sink; + int i, ret; + + source = &isp->subdev.entity; + + for (i = 0; i < FIMC_MAX_DEVS; i++) { + if (fmd->fimc[i] == NULL) + continue; + + /* Link from FIMC-IS-ISP subdev to FIMC */ + sink = &fmd->fimc[i]->vid_cap.subdev.entity; + ret = media_create_pad_link(source, FIMC_ISP_SD_PAD_SRC_FIFO, + sink, FIMC_SD_PAD_SINK_FIFO, 0); + if (ret) + return ret; + } + + /* Link from FIMC-IS-ISP subdev to fimc-is-isp.capture video node */ + sink = &isp->video_capture.ve.vdev.entity; + + /* Skip this link if the fimc-is-isp video node driver isn't built-in */ + if (sink->num_pads == 0) + return 0; + + return media_create_pad_link(source, FIMC_ISP_SD_PAD_SRC_DMA, + sink, 0, 0); +} + +/** + * fimc_md_create_links - create default links between registered entities + * @fmd: fimc media device + * + * Parallel interface sensor entities are connected directly to FIMC capture + * entities. The sensors using MIPI CSIS bus are connected through immutable + * link with CSI receiver entity specified by mux_id. Any registered CSIS + * entity has a link to each registered FIMC capture entity. Enabled links + * are created by default between each subsequent registered sensor and + * subsequent FIMC capture entity. The number of default active links is + * determined by the number of available sensors or FIMC entities, + * whichever is less. + */ +static int fimc_md_create_links(struct fimc_md *fmd) +{ + struct v4l2_subdev *csi_sensors[CSIS_MAX_ENTITIES] = { NULL }; + struct v4l2_subdev *sensor, *csis; + struct fimc_source_info *pdata; + struct media_entity *source, *sink; + int i, pad, fimc_id = 0, ret = 0; + u32 flags, link_mask = 0; + + for (i = 0; i < fmd->num_sensors; i++) { + if (fmd->sensor[i].subdev == NULL) + continue; + + sensor = fmd->sensor[i].subdev; + pdata = v4l2_get_subdev_hostdata(sensor); + if (!pdata) + continue; + + source = NULL; + + switch (pdata->sensor_bus_type) { + case FIMC_BUS_TYPE_MIPI_CSI2: + if (WARN(pdata->mux_id >= CSIS_MAX_ENTITIES, + "Wrong CSI channel id: %d\n", pdata->mux_id)) + return -EINVAL; + + csis = fmd->csis[pdata->mux_id].sd; + if (WARN(csis == NULL, + "MIPI-CSI interface specified but s5p-csis module is not loaded!\n")) + return -EINVAL; + + pad = sensor->entity.num_pads - 1; + ret = media_create_pad_link(&sensor->entity, pad, + &csis->entity, CSIS_PAD_SINK, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) + return ret; + + v4l2_info(&fmd->v4l2_dev, "created link [%s] => [%s]\n", + sensor->entity.name, csis->entity.name); + + source = NULL; + csi_sensors[pdata->mux_id] = sensor; + break; + + case FIMC_BUS_TYPE_ITU_601...FIMC_BUS_TYPE_ITU_656: + source = &sensor->entity; + pad = 0; + break; + + default: + v4l2_err(&fmd->v4l2_dev, "Wrong bus_type: %x\n", + pdata->sensor_bus_type); + return -EINVAL; + } + if (source == NULL) + continue; + + link_mask = 1 << fimc_id++; + ret = __fimc_md_create_fimc_sink_links(fmd, source, sensor, + pad, link_mask); + } + + for (i = 0; i < CSIS_MAX_ENTITIES; i++) { + if (fmd->csis[i].sd == NULL) + continue; + + source = &fmd->csis[i].sd->entity; + pad = CSIS_PAD_SOURCE; + sensor = csi_sensors[i]; + + link_mask = 1 << fimc_id++; + ret = __fimc_md_create_fimc_sink_links(fmd, source, sensor, + pad, link_mask); + } + + /* Create immutable links between each FIMC's subdev and video node */ + flags = MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED; + for (i = 0; i < FIMC_MAX_DEVS; i++) { + if (!fmd->fimc[i]) + continue; + + source = &fmd->fimc[i]->vid_cap.subdev.entity; + sink = &fmd->fimc[i]->vid_cap.ve.vdev.entity; + + ret = media_create_pad_link(source, FIMC_SD_PAD_SOURCE, + sink, 0, flags); + if (ret) + break; + } + + ret = __fimc_md_create_flite_source_links(fmd); + if (ret < 0) + return ret; + + if (fmd->use_isp) + ret = __fimc_md_create_fimc_is_links(fmd); + + return ret; +} + +/* + * The peripheral sensor and CAM_BLK (PIXELASYNCMx) clocks management. + */ +static void fimc_md_put_clocks(struct fimc_md *fmd) +{ + int i = FIMC_MAX_CAMCLKS; + + while (--i >= 0) { + if (IS_ERR(fmd->camclk[i].clock)) + continue; + clk_put(fmd->camclk[i].clock); + fmd->camclk[i].clock = ERR_PTR(-EINVAL); + } + + /* Writeback (PIXELASYNCMx) clocks */ + for (i = 0; i < FIMC_MAX_WBCLKS; i++) { + if (IS_ERR(fmd->wbclk[i])) + continue; + clk_put(fmd->wbclk[i]); + fmd->wbclk[i] = ERR_PTR(-EINVAL); + } +} + +static int fimc_md_get_clocks(struct fimc_md *fmd) +{ + struct device *dev = &fmd->pdev->dev; + char clk_name[32]; + struct clk *clock; + int i, ret = 0; + + for (i = 0; i < FIMC_MAX_CAMCLKS; i++) + fmd->camclk[i].clock = ERR_PTR(-EINVAL); + + for (i = 0; i < FIMC_MAX_CAMCLKS; i++) { + snprintf(clk_name, sizeof(clk_name), "sclk_cam%u", i); + clock = clk_get(dev, clk_name); + + if (IS_ERR(clock)) { + dev_err(dev, "Failed to get clock: %s\n", clk_name); + ret = PTR_ERR(clock); + break; + } + fmd->camclk[i].clock = clock; + } + if (ret) + fimc_md_put_clocks(fmd); + + if (!fmd->use_isp) + return 0; + /* + * For now get only PIXELASYNCM1 clock (Writeback B/ISP), + * leave PIXELASYNCM0 out for the LCD Writeback driver. + */ + fmd->wbclk[CLK_IDX_WB_A] = ERR_PTR(-EINVAL); + + for (i = CLK_IDX_WB_B; i < FIMC_MAX_WBCLKS; i++) { + snprintf(clk_name, sizeof(clk_name), "pxl_async%u", i); + clock = clk_get(dev, clk_name); + if (IS_ERR(clock)) { + v4l2_err(&fmd->v4l2_dev, "Failed to get clock: %s\n", + clk_name); + ret = PTR_ERR(clock); + break; + } + fmd->wbclk[i] = clock; + } + if (ret) + fimc_md_put_clocks(fmd); + + return ret; +} + +static int __fimc_md_modify_pipeline(struct media_entity *entity, bool enable) +{ + struct exynos_video_entity *ve; + struct fimc_pipeline *p; + struct video_device *vdev; + int ret; + + vdev = media_entity_to_video_device(entity); + if (vdev->entity.use_count == 0) + return 0; + + ve = vdev_to_exynos_video_entity(vdev); + p = to_fimc_pipeline(ve->pipe); + /* + * Nothing to do if we are disabling the pipeline, some link + * has been disconnected and p->subdevs array is cleared now. + */ + if (!enable && p->subdevs[IDX_SENSOR] == NULL) + return 0; + + if (enable) + ret = __fimc_pipeline_open(ve->pipe, entity, true); + else + ret = __fimc_pipeline_close(ve->pipe); + + if (ret == 0 && !enable) + memset(p->subdevs, 0, sizeof(p->subdevs)); + + return ret; +} + +/* Locking: called with entity->graph_obj.mdev->graph_mutex mutex held. */ +static int __fimc_md_modify_pipelines(struct media_entity *entity, bool enable, + struct media_graph *graph) +{ + struct media_entity *entity_err = entity; + int ret; + + /* + * Walk current graph and call the pipeline open/close routine for each + * opened video node that belongs to the graph of entities connected + * through active links. This is needed as we cannot power on/off the + * subdevs in random order. + */ + media_graph_walk_start(graph, entity); + + while ((entity = media_graph_walk_next(graph))) { + if (!is_media_entity_v4l2_video_device(entity)) + continue; + + ret = __fimc_md_modify_pipeline(entity, enable); + + if (ret < 0) + goto err; + } + + return 0; + +err: + media_graph_walk_start(graph, entity_err); + + while ((entity_err = media_graph_walk_next(graph))) { + if (!is_media_entity_v4l2_video_device(entity_err)) + continue; + + __fimc_md_modify_pipeline(entity_err, !enable); + + if (entity_err == entity) + break; + } + + return ret; +} + +static int fimc_md_link_notify(struct media_link *link, unsigned int flags, + unsigned int notification) +{ + struct media_graph *graph = + &container_of(link->graph_obj.mdev, struct fimc_md, + media_dev)->link_setup_graph; + struct media_entity *sink = link->sink->entity; + int ret = 0; + + /* Before link disconnection */ + if (notification == MEDIA_DEV_NOTIFY_PRE_LINK_CH) { + ret = media_graph_walk_init(graph, + link->graph_obj.mdev); + if (ret) + return ret; + if (!(flags & MEDIA_LNK_FL_ENABLED)) + ret = __fimc_md_modify_pipelines(sink, false, graph); +#if 0 + else + /* TODO: Link state change validation */ +#endif + /* After link activation */ + } else if (notification == MEDIA_DEV_NOTIFY_POST_LINK_CH) { + if (link->flags & MEDIA_LNK_FL_ENABLED) + ret = __fimc_md_modify_pipelines(sink, true, graph); + media_graph_walk_cleanup(graph); + } + + return ret ? -EPIPE : 0; +} + +static const struct media_device_ops fimc_md_ops = { + .link_notify = fimc_md_link_notify, +}; + +static ssize_t subdev_conf_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct fimc_md *fmd = dev_get_drvdata(dev); + + if (fmd->user_subdev_api) + return strscpy(buf, "Sub-device API (sub-dev)\n", PAGE_SIZE); + + return strscpy(buf, "V4L2 video node only API (vid-dev)\n", PAGE_SIZE); +} + +static ssize_t subdev_conf_mode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct fimc_md *fmd = dev_get_drvdata(dev); + bool subdev_api; + int i; + + if (!strcmp(buf, "vid-dev\n")) + subdev_api = false; + else if (!strcmp(buf, "sub-dev\n")) + subdev_api = true; + else + return count; + + fmd->user_subdev_api = subdev_api; + for (i = 0; i < FIMC_MAX_DEVS; i++) + if (fmd->fimc[i]) + fmd->fimc[i]->vid_cap.user_subdev_api = subdev_api; + return count; +} +/* + * This device attribute is to select video pipeline configuration method. + * There are following valid values: + * vid-dev - for V4L2 video node API only, subdevice will be configured + * by the host driver. + * sub-dev - for media controller API, subdevs must be configured in user + * space before starting streaming. + */ +static DEVICE_ATTR_RW(subdev_conf_mode); + +static int cam_clk_prepare(struct clk_hw *hw) +{ + struct cam_clk *camclk = to_cam_clk(hw); + + if (camclk->fmd->pmf == NULL) + return -ENODEV; + + return pm_runtime_resume_and_get(camclk->fmd->pmf); +} + +static void cam_clk_unprepare(struct clk_hw *hw) +{ + struct cam_clk *camclk = to_cam_clk(hw); + + if (camclk->fmd->pmf == NULL) + return; + + pm_runtime_put_sync(camclk->fmd->pmf); +} + +static const struct clk_ops cam_clk_ops = { + .prepare = cam_clk_prepare, + .unprepare = cam_clk_unprepare, +}; + +static void fimc_md_unregister_clk_provider(struct fimc_md *fmd) +{ + struct cam_clk_provider *cp = &fmd->clk_provider; + unsigned int i; + + if (cp->of_node) + of_clk_del_provider(cp->of_node); + + for (i = 0; i < cp->num_clocks; i++) + clk_unregister(cp->clks[i]); +} + +static int fimc_md_register_clk_provider(struct fimc_md *fmd) +{ + struct cam_clk_provider *cp = &fmd->clk_provider; + struct device *dev = &fmd->pdev->dev; + int i, ret; + + for (i = 0; i < FIMC_MAX_CAMCLKS; i++) { + struct cam_clk *camclk = &cp->camclk[i]; + struct clk_init_data init; + const char *p_name; + + ret = of_property_read_string_index(dev->of_node, + "clock-output-names", i, &init.name); + if (ret < 0) + break; + + p_name = __clk_get_name(fmd->camclk[i].clock); + + /* It's safe since clk_register() will duplicate the string. */ + init.parent_names = &p_name; + init.num_parents = 1; + init.ops = &cam_clk_ops; + init.flags = CLK_SET_RATE_PARENT; + camclk->hw.init = &init; + camclk->fmd = fmd; + + cp->clks[i] = clk_register(NULL, &camclk->hw); + if (IS_ERR(cp->clks[i])) { + dev_err(dev, "failed to register clock: %s (%ld)\n", + init.name, PTR_ERR(cp->clks[i])); + ret = PTR_ERR(cp->clks[i]); + goto err; + } + cp->num_clocks++; + } + + if (cp->num_clocks == 0) { + dev_warn(dev, "clk provider not registered\n"); + return 0; + } + + cp->clk_data.clks = cp->clks; + cp->clk_data.clk_num = cp->num_clocks; + cp->of_node = dev->of_node; + ret = of_clk_add_provider(dev->of_node, of_clk_src_onecell_get, + &cp->clk_data); + if (ret == 0) + return 0; +err: + fimc_md_unregister_clk_provider(fmd); + return ret; +} + +static int subdev_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct fimc_md *fmd = notifier_to_fimc_md(notifier); + struct fimc_sensor_info *si = NULL; + int i; + + /* Find platform data for this sensor subdev */ + for (i = 0; i < ARRAY_SIZE(fmd->sensor); i++) + if (fmd->sensor[i].asd == asd) + si = &fmd->sensor[i]; + + if (si == NULL) + return -EINVAL; + + v4l2_set_subdev_hostdata(subdev, &si->pdata); + + if (si->pdata.fimc_bus_type == FIMC_BUS_TYPE_ISP_WRITEBACK) + subdev->grp_id = GRP_ID_FIMC_IS_SENSOR; + else + subdev->grp_id = GRP_ID_SENSOR; + + si->subdev = subdev; + + v4l2_info(&fmd->v4l2_dev, "Registered sensor subdevice: %s (%d)\n", + subdev->name, fmd->num_sensors); + + fmd->num_sensors++; + + return 0; +} + +static int subdev_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct fimc_md *fmd = notifier_to_fimc_md(notifier); + int ret; + + mutex_lock(&fmd->media_dev.graph_mutex); + + ret = fimc_md_create_links(fmd); + if (ret < 0) + goto unlock; + + ret = v4l2_device_register_subdev_nodes(&fmd->v4l2_dev); +unlock: + mutex_unlock(&fmd->media_dev.graph_mutex); + if (ret < 0) + return ret; + + return media_device_register(&fmd->media_dev); +} + +static const struct v4l2_async_notifier_operations subdev_notifier_ops = { + .bound = subdev_notifier_bound, + .complete = subdev_notifier_complete, +}; + +static int fimc_md_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct v4l2_device *v4l2_dev; + struct pinctrl *pinctrl; + struct fimc_md *fmd; + int ret; + + fmd = devm_kzalloc(dev, sizeof(*fmd), GFP_KERNEL); + if (!fmd) + return -ENOMEM; + + spin_lock_init(&fmd->slock); + INIT_LIST_HEAD(&fmd->pipelines); + fmd->pdev = pdev; + + strscpy(fmd->media_dev.model, "Samsung S5P FIMC", + sizeof(fmd->media_dev.model)); + fmd->media_dev.ops = &fimc_md_ops; + fmd->media_dev.dev = dev; + + v4l2_dev = &fmd->v4l2_dev; + v4l2_dev->mdev = &fmd->media_dev; + v4l2_dev->notify = fimc_sensor_notify; + strscpy(v4l2_dev->name, "s5p-fimc-md", sizeof(v4l2_dev->name)); + + fmd->use_isp = fimc_md_is_isp_available(dev->of_node); + fmd->user_subdev_api = true; + + media_device_init(&fmd->media_dev); + + ret = v4l2_device_register(dev, &fmd->v4l2_dev); + if (ret < 0) { + v4l2_err(v4l2_dev, "Failed to register v4l2_device: %d\n", ret); + goto err_md; + } + + ret = fimc_md_get_clocks(fmd); + if (ret) + goto err_v4l2dev; + + pinctrl = devm_pinctrl_get(dev); + if (IS_ERR(pinctrl)) { + ret = PTR_ERR(pinctrl); + if (ret != -EPROBE_DEFER) + dev_err(dev, "Failed to get pinctrl: %d\n", ret); + goto err_clk; + } + + platform_set_drvdata(pdev, fmd); + + v4l2_async_nf_init(&fmd->subdev_notifier); + + ret = fimc_md_register_platform_entities(fmd, dev->of_node); + if (ret) + goto err_clk; + + ret = fimc_md_register_sensor_entities(fmd); + if (ret) + goto err_m_ent; + + ret = device_create_file(&pdev->dev, &dev_attr_subdev_conf_mode); + if (ret) + goto err_cleanup; + /* + * FIMC platform devices need to be registered before the sclk_cam + * clocks provider, as one of these devices needs to be activated + * to enable the clock. + */ + ret = fimc_md_register_clk_provider(fmd); + if (ret < 0) { + v4l2_err(v4l2_dev, "clock provider registration failed\n"); + goto err_attr; + } + + if (fmd->num_sensors > 0) { + fmd->subdev_notifier.ops = &subdev_notifier_ops; + fmd->num_sensors = 0; + + ret = v4l2_async_nf_register(&fmd->v4l2_dev, + &fmd->subdev_notifier); + if (ret) + goto err_clk_p; + } + + return 0; + +err_clk_p: + fimc_md_unregister_clk_provider(fmd); +err_attr: + device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode); +err_cleanup: + v4l2_async_nf_cleanup(&fmd->subdev_notifier); +err_m_ent: + fimc_md_unregister_entities(fmd); +err_clk: + fimc_md_put_clocks(fmd); +err_v4l2dev: + v4l2_device_unregister(&fmd->v4l2_dev); +err_md: + media_device_cleanup(&fmd->media_dev); + return ret; +} + +static int fimc_md_remove(struct platform_device *pdev) +{ + struct fimc_md *fmd = platform_get_drvdata(pdev); + + if (!fmd) + return 0; + + fimc_md_unregister_clk_provider(fmd); + v4l2_async_nf_unregister(&fmd->subdev_notifier); + v4l2_async_nf_cleanup(&fmd->subdev_notifier); + + v4l2_device_unregister(&fmd->v4l2_dev); + device_remove_file(&pdev->dev, &dev_attr_subdev_conf_mode); + fimc_md_unregister_entities(fmd); + fimc_md_pipelines_free(fmd); + media_device_unregister(&fmd->media_dev); + media_device_cleanup(&fmd->media_dev); + fimc_md_put_clocks(fmd); + + return 0; +} + +static const struct platform_device_id fimc_driver_ids[] __always_unused = { + { .name = "s5p-fimc-md" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, fimc_driver_ids); + +static const struct of_device_id fimc_md_of_match[] = { + { .compatible = "samsung,fimc" }, + { }, +}; +MODULE_DEVICE_TABLE(of, fimc_md_of_match); + +static struct platform_driver fimc_md_driver = { + .probe = fimc_md_probe, + .remove = fimc_md_remove, + .driver = { + .of_match_table = of_match_ptr(fimc_md_of_match), + .name = "s5p-fimc-md", + } +}; + +static int __init fimc_md_init(void) +{ + int ret; + + request_module("s5p-csis"); + ret = fimc_register_driver(); + if (ret) + return ret; + + ret = platform_driver_register(&fimc_md_driver); + if (ret) + fimc_unregister_driver(); + + return ret; +} + +static void __exit fimc_md_exit(void) +{ + platform_driver_unregister(&fimc_md_driver); + fimc_unregister_driver(); +} + +module_init(fimc_md_init); +module_exit(fimc_md_exit); + +MODULE_AUTHOR("Sylwester Nawrocki "); +MODULE_DESCRIPTION("S5P FIMC camera host interface/video postprocessor driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("2.0.1"); diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.h b/drivers/media/platform/samsung/exynos4-is/media-dev.h new file mode 100644 index 000000000..62ad5d7e0 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.h @@ -0,0 +1,201 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2011 - 2012 Samsung Electronics Co., Ltd. + */ + +#ifndef FIMC_MDEVICE_H_ +#define FIMC_MDEVICE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "fimc-core.h" +#include "fimc-lite.h" +#include "mipi-csis.h" + +#define FIMC_OF_NODE_NAME "fimc" +#define FIMC_LITE_OF_NODE_NAME "fimc-lite" +#define FIMC_IS_OF_NODE_NAME "fimc-is" +#define CSIS_OF_NODE_NAME "csis" + +#define FIMC_MAX_SENSORS 4 +#define FIMC_MAX_CAMCLKS 2 +#define DEFAULT_SENSOR_CLK_FREQ 24000000U + +/* LCD/ISP Writeback clocks (PIXELASYNCMx) */ +enum { + CLK_IDX_WB_A, + CLK_IDX_WB_B, + FIMC_MAX_WBCLKS +}; + +enum fimc_subdev_index { + IDX_SENSOR, + IDX_CSIS, + IDX_FLITE, + IDX_IS_ISP, + IDX_FIMC, + IDX_MAX, +}; + +/* + * This structure represents a chain of media entities, including a data + * source entity (e.g. an image sensor subdevice), a data capture entity + * - a video capture device node and any remaining entities. + */ +struct fimc_pipeline { + struct exynos_media_pipeline ep; + struct list_head list; + struct media_entity *vdev_entity; + struct v4l2_subdev *subdevs[IDX_MAX]; +}; + +#define to_fimc_pipeline(_ep) container_of(_ep, struct fimc_pipeline, ep) + +struct fimc_csis_info { + struct v4l2_subdev *sd; + int id; +}; + +struct fimc_camclk_info { + struct clk *clock; + int use_count; + unsigned long frequency; +}; + +/** + * struct fimc_sensor_info - image data source subdev information + * @pdata: sensor's attributes passed as media device's platform data + * @asd: asynchronous subdev registration data structure + * @subdev: image sensor v4l2 subdev + * @host: fimc device the sensor is currently linked to + * + * This data structure applies to image sensor and the writeback subdevs. + */ +struct fimc_sensor_info { + struct fimc_source_info pdata; + struct v4l2_async_subdev *asd; + struct v4l2_subdev *subdev; + struct fimc_dev *host; +}; + +struct cam_clk { + struct clk_hw hw; + struct fimc_md *fmd; +}; +#define to_cam_clk(_hw) container_of(_hw, struct cam_clk, hw) + +/** + * struct fimc_md - fimc media device information + * @csis: MIPI CSIS subdevs data + * @sensor: array of registered sensor subdevs + * @num_sensors: actual number of registered sensors + * @camclk: external sensor clock information + * @wbclk: external writeback clock information + * @fimc_lite: array of registered fimc-lite devices + * @fimc: array of registered fimc devices + * @fimc_is: fimc-is data structure + * @use_isp: set to true when FIMC-IS subsystem is used + * @pmf: handle to the CAMCLK clock control FIMC helper device + * @media_dev: top level media device + * @v4l2_dev: top level v4l2_device holding up the subdevs + * @pdev: platform device this media device is hooked up into + * @clk_provider: CAMCLK clock provider structure + * @subdev_notifier: notifier for the subdevs + * @user_subdev_api: true if subdevs are not configured by the host driver + * @slock: spinlock protecting @sensor array + * @pipelines: list of pipelines + * @link_setup_graph: graph iterator + */ +struct fimc_md { + struct fimc_csis_info csis[CSIS_MAX_ENTITIES]; + struct fimc_sensor_info sensor[FIMC_MAX_SENSORS]; + int num_sensors; + struct fimc_camclk_info camclk[FIMC_MAX_CAMCLKS]; + struct clk *wbclk[FIMC_MAX_WBCLKS]; + struct fimc_lite *fimc_lite[FIMC_LITE_MAX_DEVS]; + struct fimc_dev *fimc[FIMC_MAX_DEVS]; + struct fimc_is *fimc_is; + bool use_isp; + struct device *pmf; + struct media_device media_dev; + struct v4l2_device v4l2_dev; + struct platform_device *pdev; + + struct cam_clk_provider { + struct clk *clks[FIMC_MAX_CAMCLKS]; + struct clk_onecell_data clk_data; + struct device_node *of_node; + struct cam_clk camclk[FIMC_MAX_CAMCLKS]; + int num_clocks; + } clk_provider; + + struct v4l2_async_notifier subdev_notifier; + + bool user_subdev_api; + spinlock_t slock; + struct list_head pipelines; + struct media_graph link_setup_graph; +}; + +static inline +struct fimc_sensor_info *source_to_sensor_info(struct fimc_source_info *si) +{ + return container_of(si, struct fimc_sensor_info, pdata); +} + +static inline struct fimc_md *entity_to_fimc_mdev(struct media_entity *me) +{ + return me->graph_obj.mdev == NULL ? NULL : + container_of(me->graph_obj.mdev, struct fimc_md, media_dev); +} + +static inline struct fimc_md *notifier_to_fimc_md(struct v4l2_async_notifier *n) +{ + return container_of(n, struct fimc_md, subdev_notifier); +} + +static inline void fimc_md_graph_lock(struct exynos_video_entity *ve) +{ + mutex_lock(&ve->vdev.entity.graph_obj.mdev->graph_mutex); +} + +static inline void fimc_md_graph_unlock(struct exynos_video_entity *ve) +{ + mutex_unlock(&ve->vdev.entity.graph_obj.mdev->graph_mutex); +} + +int fimc_md_set_camclk(struct v4l2_subdev *sd, bool on); + +#ifdef CONFIG_OF +static inline bool fimc_md_is_isp_available(struct device_node *node) +{ + node = of_get_child_by_name(node, FIMC_IS_OF_NODE_NAME); + return node ? of_device_is_available(node) : false; +} +#else +#define fimc_md_is_isp_available(node) (false) +#endif /* CONFIG_OF */ + +static inline struct v4l2_subdev *__fimc_md_get_subdev( + struct exynos_media_pipeline *ep, + unsigned int index) +{ + struct fimc_pipeline *p = to_fimc_pipeline(ep); + + if (!p || index >= IDX_MAX) + return NULL; + else + return p->subdevs[index]; +} + +#endif diff --git a/drivers/media/platform/samsung/exynos4-is/mipi-csis.c b/drivers/media/platform/samsung/exynos4-is/mipi-csis.c new file mode 100644 index 000000000..6a0d35f33 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/mipi-csis.c @@ -0,0 +1,1037 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung S5P/EXYNOS SoC series MIPI-CSI receiver driver + * + * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd. + * Author: Sylwester Nawrocki + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mipi-csis.h" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level (0-2)"); + +/* Register map definition */ + +/* CSIS global control */ +#define S5PCSIS_CTRL 0x00 +#define S5PCSIS_CTRL_DPDN_DEFAULT (0 << 31) +#define S5PCSIS_CTRL_DPDN_SWAP (1UL << 31) +#define S5PCSIS_CTRL_ALIGN_32BIT (1 << 20) +#define S5PCSIS_CTRL_UPDATE_SHADOW (1 << 16) +#define S5PCSIS_CTRL_WCLK_EXTCLK (1 << 8) +#define S5PCSIS_CTRL_RESET (1 << 4) +#define S5PCSIS_CTRL_ENABLE (1 << 0) + +/* D-PHY control */ +#define S5PCSIS_DPHYCTRL 0x04 +#define S5PCSIS_DPHYCTRL_HSS_MASK (0x1f << 27) +#define S5PCSIS_DPHYCTRL_ENABLE (0x1f << 0) + +#define S5PCSIS_CONFIG 0x08 +#define S5PCSIS_CFG_FMT_YCBCR422_8BIT (0x1e << 2) +#define S5PCSIS_CFG_FMT_RAW8 (0x2a << 2) +#define S5PCSIS_CFG_FMT_RAW10 (0x2b << 2) +#define S5PCSIS_CFG_FMT_RAW12 (0x2c << 2) +/* User defined formats, x = 1...4 */ +#define S5PCSIS_CFG_FMT_USER(x) ((0x30 + x - 1) << 2) +#define S5PCSIS_CFG_FMT_MASK (0x3f << 2) +#define S5PCSIS_CFG_NR_LANE_MASK 3 + +/* Interrupt mask */ +#define S5PCSIS_INTMSK 0x10 +#define S5PCSIS_INTMSK_EVEN_BEFORE (1UL << 31) +#define S5PCSIS_INTMSK_EVEN_AFTER (1 << 30) +#define S5PCSIS_INTMSK_ODD_BEFORE (1 << 29) +#define S5PCSIS_INTMSK_ODD_AFTER (1 << 28) +#define S5PCSIS_INTMSK_FRAME_START (1 << 27) +#define S5PCSIS_INTMSK_FRAME_END (1 << 26) +#define S5PCSIS_INTMSK_ERR_SOT_HS (1 << 12) +#define S5PCSIS_INTMSK_ERR_LOST_FS (1 << 5) +#define S5PCSIS_INTMSK_ERR_LOST_FE (1 << 4) +#define S5PCSIS_INTMSK_ERR_OVER (1 << 3) +#define S5PCSIS_INTMSK_ERR_ECC (1 << 2) +#define S5PCSIS_INTMSK_ERR_CRC (1 << 1) +#define S5PCSIS_INTMSK_ERR_UNKNOWN (1 << 0) +#define S5PCSIS_INTMSK_EXYNOS4_EN_ALL 0xf000103f +#define S5PCSIS_INTMSK_EXYNOS5_EN_ALL 0xfc00103f + +/* Interrupt source */ +#define S5PCSIS_INTSRC 0x14 +#define S5PCSIS_INTSRC_EVEN_BEFORE (1UL << 31) +#define S5PCSIS_INTSRC_EVEN_AFTER (1 << 30) +#define S5PCSIS_INTSRC_EVEN (0x3 << 30) +#define S5PCSIS_INTSRC_ODD_BEFORE (1 << 29) +#define S5PCSIS_INTSRC_ODD_AFTER (1 << 28) +#define S5PCSIS_INTSRC_ODD (0x3 << 28) +#define S5PCSIS_INTSRC_NON_IMAGE_DATA (0xf << 28) +#define S5PCSIS_INTSRC_FRAME_START (1 << 27) +#define S5PCSIS_INTSRC_FRAME_END (1 << 26) +#define S5PCSIS_INTSRC_ERR_SOT_HS (0xf << 12) +#define S5PCSIS_INTSRC_ERR_LOST_FS (1 << 5) +#define S5PCSIS_INTSRC_ERR_LOST_FE (1 << 4) +#define S5PCSIS_INTSRC_ERR_OVER (1 << 3) +#define S5PCSIS_INTSRC_ERR_ECC (1 << 2) +#define S5PCSIS_INTSRC_ERR_CRC (1 << 1) +#define S5PCSIS_INTSRC_ERR_UNKNOWN (1 << 0) +#define S5PCSIS_INTSRC_ERRORS 0xf03f + +/* Pixel resolution */ +#define S5PCSIS_RESOL 0x2c +#define CSIS_MAX_PIX_WIDTH 0xffff +#define CSIS_MAX_PIX_HEIGHT 0xffff + +/* Non-image packet data buffers */ +#define S5PCSIS_PKTDATA_ODD 0x2000 +#define S5PCSIS_PKTDATA_EVEN 0x3000 +#define S5PCSIS_PKTDATA_SIZE SZ_4K + +enum { + CSIS_CLK_MUX, + CSIS_CLK_GATE, +}; + +static char *csi_clock_name[] = { + [CSIS_CLK_MUX] = "sclk_csis", + [CSIS_CLK_GATE] = "csis", +}; +#define NUM_CSIS_CLOCKS ARRAY_SIZE(csi_clock_name) +#define DEFAULT_SCLK_CSIS_FREQ 166000000UL + +static const char * const csis_supply_name[] = { + "vddcore", /* CSIS Core (1.0V, 1.1V or 1.2V) supply */ + "vddio", /* CSIS I/O and PLL (1.8V) supply */ +}; +#define CSIS_NUM_SUPPLIES ARRAY_SIZE(csis_supply_name) + +enum { + ST_POWERED = 1, + ST_STREAMING = 2, + ST_SUSPENDED = 4, +}; + +struct s5pcsis_event { + u32 mask; + const char * const name; + unsigned int counter; +}; + +static const struct s5pcsis_event s5pcsis_events[] = { + /* Errors */ + { S5PCSIS_INTSRC_ERR_SOT_HS, "SOT Error" }, + { S5PCSIS_INTSRC_ERR_LOST_FS, "Lost Frame Start Error" }, + { S5PCSIS_INTSRC_ERR_LOST_FE, "Lost Frame End Error" }, + { S5PCSIS_INTSRC_ERR_OVER, "FIFO Overflow Error" }, + { S5PCSIS_INTSRC_ERR_ECC, "ECC Error" }, + { S5PCSIS_INTSRC_ERR_CRC, "CRC Error" }, + { S5PCSIS_INTSRC_ERR_UNKNOWN, "Unknown Error" }, + /* Non-image data receive events */ + { S5PCSIS_INTSRC_EVEN_BEFORE, "Non-image data before even frame" }, + { S5PCSIS_INTSRC_EVEN_AFTER, "Non-image data after even frame" }, + { S5PCSIS_INTSRC_ODD_BEFORE, "Non-image data before odd frame" }, + { S5PCSIS_INTSRC_ODD_AFTER, "Non-image data after odd frame" }, + /* Frame start/end */ + { S5PCSIS_INTSRC_FRAME_START, "Frame Start" }, + { S5PCSIS_INTSRC_FRAME_END, "Frame End" }, +}; +#define S5PCSIS_NUM_EVENTS ARRAY_SIZE(s5pcsis_events) + +struct csis_pktbuf { + u32 *data; + unsigned int len; +}; + +struct csis_drvdata { + /* Mask of all used interrupts in S5PCSIS_INTMSK register */ + u32 interrupt_mask; +}; + +/** + * struct csis_state - the driver's internal state data structure + * @lock: mutex serializing the subdev and power management operations, + * protecting @format and @flags members + * @pads: CSIS pads array + * @sd: v4l2_subdev associated with CSIS device instance + * @index: the hardware instance index + * @pdev: CSIS platform device + * @phy: pointer to the CSIS generic PHY + * @regs: mmapped I/O registers memory + * @supplies: CSIS regulator supplies + * @clock: CSIS clocks + * @irq: requested s5p-mipi-csis irq number + * @interrupt_mask: interrupt mask of the all used interrupts + * @flags: the state variable for power and streaming control + * @clk_frequency: device bus clock frequency + * @hs_settle: HS-RX settle time + * @num_lanes: number of MIPI-CSI data lanes used + * @max_num_lanes: maximum number of MIPI-CSI data lanes supported + * @wclk_ext: CSI wrapper clock: 0 - bus clock, 1 - external SCLK_CAM + * @csis_fmt: current CSIS pixel format + * @format: common media bus format for the source and sink pad + * @slock: spinlock protecting structure members below + * @pkt_buf: the frame embedded (non-image) data buffer + * @events: MIPI-CSIS event (error) counters + */ +struct csis_state { + struct mutex lock; + struct media_pad pads[CSIS_PADS_NUM]; + struct v4l2_subdev sd; + u8 index; + struct platform_device *pdev; + struct phy *phy; + void __iomem *regs; + struct regulator_bulk_data supplies[CSIS_NUM_SUPPLIES]; + struct clk *clock[NUM_CSIS_CLOCKS]; + int irq; + u32 interrupt_mask; + u32 flags; + + u32 clk_frequency; + u32 hs_settle; + u32 num_lanes; + u32 max_num_lanes; + u8 wclk_ext; + + const struct csis_pix_format *csis_fmt; + struct v4l2_mbus_framefmt format; + + spinlock_t slock; + struct csis_pktbuf pkt_buf; + struct s5pcsis_event events[S5PCSIS_NUM_EVENTS]; +}; + +/** + * struct csis_pix_format - CSIS pixel format description + * @pix_width_alignment: horizontal pixel alignment, width will be + * multiple of 2^pix_width_alignment + * @code: corresponding media bus code + * @fmt_reg: S5PCSIS_CONFIG register value + * @data_alignment: MIPI-CSI data alignment in bits + */ +struct csis_pix_format { + unsigned int pix_width_alignment; + u32 code; + u32 fmt_reg; + u8 data_alignment; +}; + +static const struct csis_pix_format s5pcsis_formats[] = { + { + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .fmt_reg = S5PCSIS_CFG_FMT_YCBCR422_8BIT, + .data_alignment = 32, + }, { + .code = MEDIA_BUS_FMT_JPEG_1X8, + .fmt_reg = S5PCSIS_CFG_FMT_USER(1), + .data_alignment = 32, + }, { + .code = MEDIA_BUS_FMT_S5C_UYVY_JPEG_1X8, + .fmt_reg = S5PCSIS_CFG_FMT_USER(1), + .data_alignment = 32, + }, { + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .fmt_reg = S5PCSIS_CFG_FMT_RAW8, + .data_alignment = 24, + }, { + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .fmt_reg = S5PCSIS_CFG_FMT_RAW10, + .data_alignment = 24, + }, { + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .fmt_reg = S5PCSIS_CFG_FMT_RAW12, + .data_alignment = 24, + } +}; + +#define s5pcsis_write(__csis, __r, __v) writel(__v, __csis->regs + __r) +#define s5pcsis_read(__csis, __r) readl(__csis->regs + __r) + +static struct csis_state *sd_to_csis_state(struct v4l2_subdev *sdev) +{ + return container_of(sdev, struct csis_state, sd); +} + +static const struct csis_pix_format *find_csis_format( + struct v4l2_mbus_framefmt *mf) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(s5pcsis_formats); i++) + if (mf->code == s5pcsis_formats[i].code) + return &s5pcsis_formats[i]; + return NULL; +} + +static void s5pcsis_enable_interrupts(struct csis_state *state, bool on) +{ + u32 val = s5pcsis_read(state, S5PCSIS_INTMSK); + if (on) + val |= state->interrupt_mask; + else + val &= ~state->interrupt_mask; + s5pcsis_write(state, S5PCSIS_INTMSK, val); +} + +static void s5pcsis_reset(struct csis_state *state) +{ + u32 val = s5pcsis_read(state, S5PCSIS_CTRL); + + s5pcsis_write(state, S5PCSIS_CTRL, val | S5PCSIS_CTRL_RESET); + udelay(10); +} + +static void s5pcsis_system_enable(struct csis_state *state, int on) +{ + u32 val, mask; + + val = s5pcsis_read(state, S5PCSIS_CTRL); + if (on) + val |= S5PCSIS_CTRL_ENABLE; + else + val &= ~S5PCSIS_CTRL_ENABLE; + s5pcsis_write(state, S5PCSIS_CTRL, val); + + val = s5pcsis_read(state, S5PCSIS_DPHYCTRL); + val &= ~S5PCSIS_DPHYCTRL_ENABLE; + if (on) { + mask = (1 << (state->num_lanes + 1)) - 1; + val |= (mask & S5PCSIS_DPHYCTRL_ENABLE); + } + s5pcsis_write(state, S5PCSIS_DPHYCTRL, val); +} + +/* Called with the state.lock mutex held */ +static void __s5pcsis_set_format(struct csis_state *state) +{ + struct v4l2_mbus_framefmt *mf = &state->format; + u32 val; + + v4l2_dbg(1, debug, &state->sd, "fmt: %#x, %d x %d\n", + mf->code, mf->width, mf->height); + + /* Color format */ + val = s5pcsis_read(state, S5PCSIS_CONFIG); + val = (val & ~S5PCSIS_CFG_FMT_MASK) | state->csis_fmt->fmt_reg; + s5pcsis_write(state, S5PCSIS_CONFIG, val); + + /* Pixel resolution */ + val = (mf->width << 16) | mf->height; + s5pcsis_write(state, S5PCSIS_RESOL, val); +} + +static void s5pcsis_set_hsync_settle(struct csis_state *state, int settle) +{ + u32 val = s5pcsis_read(state, S5PCSIS_DPHYCTRL); + + val = (val & ~S5PCSIS_DPHYCTRL_HSS_MASK) | (settle << 27); + s5pcsis_write(state, S5PCSIS_DPHYCTRL, val); +} + +static void s5pcsis_set_params(struct csis_state *state) +{ + u32 val; + + val = s5pcsis_read(state, S5PCSIS_CONFIG); + val = (val & ~S5PCSIS_CFG_NR_LANE_MASK) | (state->num_lanes - 1); + s5pcsis_write(state, S5PCSIS_CONFIG, val); + + __s5pcsis_set_format(state); + s5pcsis_set_hsync_settle(state, state->hs_settle); + + val = s5pcsis_read(state, S5PCSIS_CTRL); + if (state->csis_fmt->data_alignment == 32) + val |= S5PCSIS_CTRL_ALIGN_32BIT; + else /* 24-bits */ + val &= ~S5PCSIS_CTRL_ALIGN_32BIT; + + val &= ~S5PCSIS_CTRL_WCLK_EXTCLK; + if (state->wclk_ext) + val |= S5PCSIS_CTRL_WCLK_EXTCLK; + s5pcsis_write(state, S5PCSIS_CTRL, val); + + /* Update the shadow register. */ + val = s5pcsis_read(state, S5PCSIS_CTRL); + s5pcsis_write(state, S5PCSIS_CTRL, val | S5PCSIS_CTRL_UPDATE_SHADOW); +} + +static void s5pcsis_clk_put(struct csis_state *state) +{ + int i; + + for (i = 0; i < NUM_CSIS_CLOCKS; i++) { + if (IS_ERR(state->clock[i])) + continue; + clk_unprepare(state->clock[i]); + clk_put(state->clock[i]); + state->clock[i] = ERR_PTR(-EINVAL); + } +} + +static int s5pcsis_clk_get(struct csis_state *state) +{ + struct device *dev = &state->pdev->dev; + int i, ret; + + for (i = 0; i < NUM_CSIS_CLOCKS; i++) + state->clock[i] = ERR_PTR(-EINVAL); + + for (i = 0; i < NUM_CSIS_CLOCKS; i++) { + state->clock[i] = clk_get(dev, csi_clock_name[i]); + if (IS_ERR(state->clock[i])) { + ret = PTR_ERR(state->clock[i]); + goto err; + } + ret = clk_prepare(state->clock[i]); + if (ret < 0) { + clk_put(state->clock[i]); + state->clock[i] = ERR_PTR(-EINVAL); + goto err; + } + } + return 0; +err: + s5pcsis_clk_put(state); + dev_err(dev, "failed to get clock: %s\n", csi_clock_name[i]); + return ret; +} + +static void dump_regs(struct csis_state *state, const char *label) +{ + struct { + u32 offset; + const char * const name; + } registers[] = { + { 0x00, "CTRL" }, + { 0x04, "DPHYCTRL" }, + { 0x08, "CONFIG" }, + { 0x0c, "DPHYSTS" }, + { 0x10, "INTMSK" }, + { 0x2c, "RESOL" }, + { 0x38, "SDW_CONFIG" }, + }; + u32 i; + + v4l2_info(&state->sd, "--- %s ---\n", label); + + for (i = 0; i < ARRAY_SIZE(registers); i++) { + u32 cfg = s5pcsis_read(state, registers[i].offset); + v4l2_info(&state->sd, "%10s: 0x%08x\n", registers[i].name, cfg); + } +} + +static void s5pcsis_start_stream(struct csis_state *state) +{ + s5pcsis_reset(state); + s5pcsis_set_params(state); + s5pcsis_system_enable(state, true); + s5pcsis_enable_interrupts(state, true); +} + +static void s5pcsis_stop_stream(struct csis_state *state) +{ + s5pcsis_enable_interrupts(state, false); + s5pcsis_system_enable(state, false); +} + +static void s5pcsis_clear_counters(struct csis_state *state) +{ + unsigned long flags; + int i; + + spin_lock_irqsave(&state->slock, flags); + for (i = 0; i < S5PCSIS_NUM_EVENTS; i++) + state->events[i].counter = 0; + spin_unlock_irqrestore(&state->slock, flags); +} + +static void s5pcsis_log_counters(struct csis_state *state, bool non_errors) +{ + int i = non_errors ? S5PCSIS_NUM_EVENTS : S5PCSIS_NUM_EVENTS - 4; + unsigned long flags; + + spin_lock_irqsave(&state->slock, flags); + + for (i--; i >= 0; i--) { + if (state->events[i].counter > 0 || debug) + v4l2_info(&state->sd, "%s events: %d\n", + state->events[i].name, + state->events[i].counter); + } + spin_unlock_irqrestore(&state->slock, flags); +} + +/* + * V4L2 subdev operations + */ +static int s5pcsis_s_power(struct v4l2_subdev *sd, int on) +{ + struct csis_state *state = sd_to_csis_state(sd); + struct device *dev = &state->pdev->dev; + + if (on) + return pm_runtime_resume_and_get(dev); + + return pm_runtime_put_sync(dev); +} + +static int s5pcsis_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct csis_state *state = sd_to_csis_state(sd); + int ret = 0; + + v4l2_dbg(1, debug, sd, "%s: %d, state: 0x%x\n", + __func__, enable, state->flags); + + if (enable) { + s5pcsis_clear_counters(state); + ret = pm_runtime_resume_and_get(&state->pdev->dev); + if (ret < 0) + return ret; + } + + mutex_lock(&state->lock); + if (enable) { + if (state->flags & ST_SUSPENDED) { + ret = -EBUSY; + goto unlock; + } + s5pcsis_start_stream(state); + state->flags |= ST_STREAMING; + } else { + s5pcsis_stop_stream(state); + state->flags &= ~ST_STREAMING; + if (debug > 0) + s5pcsis_log_counters(state, true); + } +unlock: + mutex_unlock(&state->lock); + if (!enable) + pm_runtime_put(&state->pdev->dev); + + return ret; +} + +static int s5pcsis_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(s5pcsis_formats)) + return -EINVAL; + + code->code = s5pcsis_formats[code->index].code; + return 0; +} + +static struct csis_pix_format const *s5pcsis_try_format( + struct v4l2_mbus_framefmt *mf) +{ + struct csis_pix_format const *csis_fmt; + + csis_fmt = find_csis_format(mf); + if (csis_fmt == NULL) + csis_fmt = &s5pcsis_formats[0]; + + mf->code = csis_fmt->code; + v4l_bound_align_image(&mf->width, 1, CSIS_MAX_PIX_WIDTH, + csis_fmt->pix_width_alignment, + &mf->height, 1, CSIS_MAX_PIX_HEIGHT, 1, + 0); + return csis_fmt; +} + +static struct v4l2_mbus_framefmt *__s5pcsis_get_format( + struct csis_state *state, struct v4l2_subdev_state *sd_state, + enum v4l2_subdev_format_whence which) +{ + if (which == V4L2_SUBDEV_FORMAT_TRY) + return sd_state ? v4l2_subdev_get_try_format(&state->sd, + sd_state, 0) : NULL; + + return &state->format; +} + +static int s5pcsis_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct csis_state *state = sd_to_csis_state(sd); + struct csis_pix_format const *csis_fmt; + struct v4l2_mbus_framefmt *mf; + + mf = __s5pcsis_get_format(state, sd_state, fmt->which); + + if (fmt->pad == CSIS_PAD_SOURCE) { + if (mf) { + mutex_lock(&state->lock); + fmt->format = *mf; + mutex_unlock(&state->lock); + } + return 0; + } + csis_fmt = s5pcsis_try_format(&fmt->format); + if (mf) { + mutex_lock(&state->lock); + *mf = fmt->format; + if (fmt->which == V4L2_SUBDEV_FORMAT_ACTIVE) + state->csis_fmt = csis_fmt; + mutex_unlock(&state->lock); + } + return 0; +} + +static int s5pcsis_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct csis_state *state = sd_to_csis_state(sd); + struct v4l2_mbus_framefmt *mf; + + mf = __s5pcsis_get_format(state, sd_state, fmt->which); + if (!mf) + return -EINVAL; + + mutex_lock(&state->lock); + fmt->format = *mf; + mutex_unlock(&state->lock); + return 0; +} + +static int s5pcsis_s_rx_buffer(struct v4l2_subdev *sd, void *buf, + unsigned int *size) +{ + struct csis_state *state = sd_to_csis_state(sd); + unsigned long flags; + + *size = min_t(unsigned int, *size, S5PCSIS_PKTDATA_SIZE); + + spin_lock_irqsave(&state->slock, flags); + state->pkt_buf.data = buf; + state->pkt_buf.len = *size; + spin_unlock_irqrestore(&state->slock, flags); + + return 0; +} + +static int s5pcsis_log_status(struct v4l2_subdev *sd) +{ + struct csis_state *state = sd_to_csis_state(sd); + + mutex_lock(&state->lock); + s5pcsis_log_counters(state, true); + if (debug && (state->flags & ST_POWERED)) + dump_regs(state, __func__); + mutex_unlock(&state->lock); + return 0; +} + +static const struct v4l2_subdev_core_ops s5pcsis_core_ops = { + .s_power = s5pcsis_s_power, + .log_status = s5pcsis_log_status, +}; + +static const struct v4l2_subdev_pad_ops s5pcsis_pad_ops = { + .enum_mbus_code = s5pcsis_enum_mbus_code, + .get_fmt = s5pcsis_get_fmt, + .set_fmt = s5pcsis_set_fmt, +}; + +static const struct v4l2_subdev_video_ops s5pcsis_video_ops = { + .s_rx_buffer = s5pcsis_s_rx_buffer, + .s_stream = s5pcsis_s_stream, +}; + +static const struct v4l2_subdev_ops s5pcsis_subdev_ops = { + .core = &s5pcsis_core_ops, + .pad = &s5pcsis_pad_ops, + .video = &s5pcsis_video_ops, +}; + +static irqreturn_t s5pcsis_irq_handler(int irq, void *dev_id) +{ + struct csis_state *state = dev_id; + struct csis_pktbuf *pktbuf = &state->pkt_buf; + unsigned long flags; + u32 status; + + status = s5pcsis_read(state, S5PCSIS_INTSRC); + spin_lock_irqsave(&state->slock, flags); + + if ((status & S5PCSIS_INTSRC_NON_IMAGE_DATA) && pktbuf->data) { + u32 offset; + + if (status & S5PCSIS_INTSRC_EVEN) + offset = S5PCSIS_PKTDATA_EVEN; + else + offset = S5PCSIS_PKTDATA_ODD; + + memcpy(pktbuf->data, (u8 __force *)state->regs + offset, + pktbuf->len); + pktbuf->data = NULL; + rmb(); + } + + /* Update the event/error counters */ + if ((status & S5PCSIS_INTSRC_ERRORS) || debug) { + int i; + for (i = 0; i < S5PCSIS_NUM_EVENTS; i++) { + if (!(status & state->events[i].mask)) + continue; + state->events[i].counter++; + v4l2_dbg(2, debug, &state->sd, "%s: %d\n", + state->events[i].name, + state->events[i].counter); + } + v4l2_dbg(2, debug, &state->sd, "status: %08x\n", status); + } + spin_unlock_irqrestore(&state->slock, flags); + + s5pcsis_write(state, S5PCSIS_INTSRC, status); + return IRQ_HANDLED; +} + +static int s5pcsis_parse_dt(struct platform_device *pdev, + struct csis_state *state) +{ + struct device_node *node = pdev->dev.of_node; + struct v4l2_fwnode_endpoint endpoint = { .bus_type = 0 }; + int ret; + + if (of_property_read_u32(node, "clock-frequency", + &state->clk_frequency)) + state->clk_frequency = DEFAULT_SCLK_CSIS_FREQ; + if (of_property_read_u32(node, "bus-width", + &state->max_num_lanes)) + return -EINVAL; + + node = of_graph_get_next_endpoint(node, NULL); + if (!node) { + dev_err(&pdev->dev, "No port node at %pOF\n", + pdev->dev.of_node); + return -EINVAL; + } + /* Get port node and validate MIPI-CSI channel id. */ + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &endpoint); + if (ret) + goto err; + + state->index = endpoint.base.port - FIMC_INPUT_MIPI_CSI2_0; + if (state->index >= CSIS_MAX_ENTITIES) { + ret = -ENXIO; + goto err; + } + + /* Get MIPI CSI-2 bus configuration from the endpoint node. */ + of_property_read_u32(node, "samsung,csis-hs-settle", + &state->hs_settle); + state->wclk_ext = of_property_read_bool(node, + "samsung,csis-wclk"); + + state->num_lanes = endpoint.bus.mipi_csi2.num_data_lanes; + +err: + of_node_put(node); + return ret; +} + +static int s5pcsis_pm_resume(struct device *dev, bool runtime); +static const struct of_device_id s5pcsis_of_match[]; + +static int s5pcsis_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + const struct csis_drvdata *drv_data; + struct device *dev = &pdev->dev; + struct csis_state *state; + int ret = -ENOMEM; + int i; + + state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + mutex_init(&state->lock); + spin_lock_init(&state->slock); + state->pdev = pdev; + + of_id = of_match_node(s5pcsis_of_match, dev->of_node); + if (WARN_ON(of_id == NULL)) + return -EINVAL; + + drv_data = of_id->data; + state->interrupt_mask = drv_data->interrupt_mask; + + ret = s5pcsis_parse_dt(pdev, state); + if (ret < 0) + return ret; + + if (state->num_lanes == 0 || state->num_lanes > state->max_num_lanes) { + dev_err(dev, "Unsupported number of data lanes: %d (max. %d)\n", + state->num_lanes, state->max_num_lanes); + return -EINVAL; + } + + state->phy = devm_phy_get(dev, "csis"); + if (IS_ERR(state->phy)) + return PTR_ERR(state->phy); + + state->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(state->regs)) + return PTR_ERR(state->regs); + + state->irq = platform_get_irq(pdev, 0); + if (state->irq < 0) + return state->irq; + + for (i = 0; i < CSIS_NUM_SUPPLIES; i++) + state->supplies[i].supply = csis_supply_name[i]; + + ret = devm_regulator_bulk_get(dev, CSIS_NUM_SUPPLIES, + state->supplies); + if (ret) + return ret; + + ret = s5pcsis_clk_get(state); + if (ret < 0) + return ret; + + if (state->clk_frequency) + ret = clk_set_rate(state->clock[CSIS_CLK_MUX], + state->clk_frequency); + else + dev_WARN(dev, "No clock frequency specified!\n"); + if (ret < 0) + goto e_clkput; + + ret = clk_enable(state->clock[CSIS_CLK_MUX]); + if (ret < 0) + goto e_clkput; + + ret = devm_request_irq(dev, state->irq, s5pcsis_irq_handler, + 0, dev_name(dev), state); + if (ret) { + dev_err(dev, "Interrupt request failed\n"); + goto e_clkdis; + } + + v4l2_subdev_init(&state->sd, &s5pcsis_subdev_ops); + state->sd.owner = THIS_MODULE; + snprintf(state->sd.name, sizeof(state->sd.name), "%s.%d", + CSIS_SUBDEV_NAME, state->index); + state->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + state->csis_fmt = &s5pcsis_formats[0]; + + state->format.code = s5pcsis_formats[0].code; + state->format.width = S5PCSIS_DEF_PIX_WIDTH; + state->format.height = S5PCSIS_DEF_PIX_HEIGHT; + + state->sd.entity.function = MEDIA_ENT_F_IO_V4L; + state->pads[CSIS_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + state->pads[CSIS_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&state->sd.entity, + CSIS_PADS_NUM, state->pads); + if (ret < 0) + goto e_clkdis; + + /* This allows to retrieve the platform device id by the host driver */ + v4l2_set_subdevdata(&state->sd, pdev); + + /* .. and a pointer to the subdev. */ + platform_set_drvdata(pdev, &state->sd); + memcpy(state->events, s5pcsis_events, sizeof(state->events)); + + pm_runtime_enable(dev); + if (!pm_runtime_enabled(dev)) { + ret = s5pcsis_pm_resume(dev, true); + if (ret < 0) + goto e_m_ent; + } + + dev_info(&pdev->dev, "lanes: %d, hs_settle: %d, wclk: %d, freq: %u\n", + state->num_lanes, state->hs_settle, state->wclk_ext, + state->clk_frequency); + return 0; + +e_m_ent: + media_entity_cleanup(&state->sd.entity); +e_clkdis: + clk_disable(state->clock[CSIS_CLK_MUX]); +e_clkput: + s5pcsis_clk_put(state); + return ret; +} + +static int s5pcsis_pm_suspend(struct device *dev, bool runtime) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csis_state *state = sd_to_csis_state(sd); + int ret = 0; + + v4l2_dbg(1, debug, sd, "%s: flags: 0x%x\n", + __func__, state->flags); + + mutex_lock(&state->lock); + if (state->flags & ST_POWERED) { + s5pcsis_stop_stream(state); + ret = phy_power_off(state->phy); + if (ret) + goto unlock; + ret = regulator_bulk_disable(CSIS_NUM_SUPPLIES, + state->supplies); + if (ret) + goto unlock; + clk_disable(state->clock[CSIS_CLK_GATE]); + state->flags &= ~ST_POWERED; + if (!runtime) + state->flags |= ST_SUSPENDED; + } + unlock: + mutex_unlock(&state->lock); + return ret ? -EAGAIN : 0; +} + +static int s5pcsis_pm_resume(struct device *dev, bool runtime) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct csis_state *state = sd_to_csis_state(sd); + int ret = 0; + + v4l2_dbg(1, debug, sd, "%s: flags: 0x%x\n", + __func__, state->flags); + + mutex_lock(&state->lock); + if (!runtime && !(state->flags & ST_SUSPENDED)) + goto unlock; + + if (!(state->flags & ST_POWERED)) { + ret = regulator_bulk_enable(CSIS_NUM_SUPPLIES, + state->supplies); + if (ret) + goto unlock; + ret = phy_power_on(state->phy); + if (!ret) { + state->flags |= ST_POWERED; + } else { + regulator_bulk_disable(CSIS_NUM_SUPPLIES, + state->supplies); + goto unlock; + } + clk_enable(state->clock[CSIS_CLK_GATE]); + } + if (state->flags & ST_STREAMING) + s5pcsis_start_stream(state); + + state->flags &= ~ST_SUSPENDED; + unlock: + mutex_unlock(&state->lock); + return ret ? -EAGAIN : 0; +} + +#ifdef CONFIG_PM_SLEEP +static int s5pcsis_suspend(struct device *dev) +{ + return s5pcsis_pm_suspend(dev, false); +} + +static int s5pcsis_resume(struct device *dev) +{ + return s5pcsis_pm_resume(dev, false); +} +#endif + +#ifdef CONFIG_PM +static int s5pcsis_runtime_suspend(struct device *dev) +{ + return s5pcsis_pm_suspend(dev, true); +} + +static int s5pcsis_runtime_resume(struct device *dev) +{ + return s5pcsis_pm_resume(dev, true); +} +#endif + +static int s5pcsis_remove(struct platform_device *pdev) +{ + struct v4l2_subdev *sd = platform_get_drvdata(pdev); + struct csis_state *state = sd_to_csis_state(sd); + + pm_runtime_disable(&pdev->dev); + s5pcsis_pm_suspend(&pdev->dev, true); + clk_disable(state->clock[CSIS_CLK_MUX]); + pm_runtime_set_suspended(&pdev->dev); + s5pcsis_clk_put(state); + + media_entity_cleanup(&state->sd.entity); + + return 0; +} + +static const struct dev_pm_ops s5pcsis_pm_ops = { + SET_RUNTIME_PM_OPS(s5pcsis_runtime_suspend, s5pcsis_runtime_resume, + NULL) + SET_SYSTEM_SLEEP_PM_OPS(s5pcsis_suspend, s5pcsis_resume) +}; + +static const struct csis_drvdata exynos4_csis_drvdata = { + .interrupt_mask = S5PCSIS_INTMSK_EXYNOS4_EN_ALL, +}; + +static const struct csis_drvdata exynos5_csis_drvdata = { + .interrupt_mask = S5PCSIS_INTMSK_EXYNOS5_EN_ALL, +}; + +static const struct of_device_id s5pcsis_of_match[] = { + { + .compatible = "samsung,s5pv210-csis", + .data = &exynos4_csis_drvdata, + }, { + .compatible = "samsung,exynos4210-csis", + .data = &exynos4_csis_drvdata, + }, { + .compatible = "samsung,exynos5250-csis", + .data = &exynos5_csis_drvdata, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, s5pcsis_of_match); + +static struct platform_driver s5pcsis_driver = { + .probe = s5pcsis_probe, + .remove = s5pcsis_remove, + .driver = { + .of_match_table = s5pcsis_of_match, + .name = CSIS_DRIVER_NAME, + .pm = &s5pcsis_pm_ops, + }, +}; + +module_platform_driver(s5pcsis_driver); + +MODULE_AUTHOR("Sylwester Nawrocki "); +MODULE_DESCRIPTION("Samsung S5P/EXYNOS SoC MIPI-CSI2 receiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/samsung/exynos4-is/mipi-csis.h b/drivers/media/platform/samsung/exynos4-is/mipi-csis.h new file mode 100644 index 000000000..193f253c7 --- /dev/null +++ b/drivers/media/platform/samsung/exynos4-is/mipi-csis.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Samsung S5P/EXYNOS4 SoC series MIPI-CSI receiver driver + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + */ +#ifndef S5P_MIPI_CSIS_H_ +#define S5P_MIPI_CSIS_H_ + +#define CSIS_DRIVER_NAME "s5p-mipi-csis" +#define CSIS_SUBDEV_NAME CSIS_DRIVER_NAME +#define CSIS_MAX_ENTITIES 2 +#define CSIS0_MAX_LANES 4 +#define CSIS1_MAX_LANES 2 + +#define CSIS_PAD_SINK 0 +#define CSIS_PAD_SOURCE 1 +#define CSIS_PADS_NUM 2 + +#define S5PCSIS_DEF_PIX_WIDTH 640 +#define S5PCSIS_DEF_PIX_HEIGHT 480 + +#endif diff --git a/drivers/media/platform/samsung/s3c-camif/Kconfig b/drivers/media/platform/samsung/s3c-camif/Kconfig new file mode 100644 index 000000000..8cb8d1ac3 --- /dev/null +++ b/drivers/media/platform/samsung/s3c-camif/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_S3C_CAMIF + tristate "Samsung S3C24XX/S3C64XX SoC Camera Interface driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && I2C && PM + depends on ARCH_S3C64XX || PLAT_S3C24XX || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + help + This is a v4l2 driver for s3c24xx and s3c64xx SoC series camera + host interface (CAMIF). + + To compile this driver as a module, choose M here: the module + will be called s3c-camif. diff --git a/drivers/media/platform/samsung/s3c-camif/Makefile b/drivers/media/platform/samsung/s3c-camif/Makefile new file mode 100644 index 000000000..70ee042a3 --- /dev/null +++ b/drivers/media/platform/samsung/s3c-camif/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for s3c244x/s3c64xx CAMIF driver + +s3c-camif-objs := camif-core.o camif-capture.o camif-regs.o + +obj-$(CONFIG_VIDEO_S3C_CAMIF) += s3c-camif.o diff --git a/drivers/media/platform/samsung/s3c-camif/camif-capture.c b/drivers/media/platform/samsung/s3c-camif/camif-capture.c new file mode 100644 index 000000000..bca3cae4d --- /dev/null +++ b/drivers/media/platform/samsung/s3c-camif/camif-capture.c @@ -0,0 +1,1650 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * s3c24xx/s3c64xx SoC series Camera Interface (CAMIF) driver + * + * Copyright (C) 2012 Sylwester Nawrocki + * Copyright (C) 2012 Tomasz Figa + * + * Based on drivers/media/platform/s5p-fimc, + * Copyright (C) 2010 - 2012 Samsung Electronics Co., Ltd. +*/ +#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "camif-core.h" +#include "camif-regs.h" + +static int debug; +module_param(debug, int, 0644); + +/* Locking: called with vp->camif->slock spinlock held */ +static void camif_cfg_video_path(struct camif_vp *vp) +{ + WARN_ON(s3c_camif_get_scaler_config(vp, &vp->scaler)); + camif_hw_set_scaler(vp); + camif_hw_set_flip(vp); + camif_hw_set_target_format(vp); + camif_hw_set_output_dma(vp); +} + +static void camif_prepare_dma_offset(struct camif_vp *vp) +{ + struct camif_frame *f = &vp->out_frame; + + f->dma_offset.initial = f->rect.top * f->f_width + f->rect.left; + f->dma_offset.line = f->f_width - (f->rect.left + f->rect.width); + + pr_debug("dma_offset: initial: %d, line: %d\n", + f->dma_offset.initial, f->dma_offset.line); +} + +/* Locking: called with camif->slock spinlock held */ +static int s3c_camif_hw_init(struct camif_dev *camif, struct camif_vp *vp) +{ + const struct s3c_camif_variant *variant = camif->variant; + + if (camif->sensor.sd == NULL || vp->out_fmt == NULL) + return -EINVAL; + + if (variant->ip_revision == S3C244X_CAMIF_IP_REV) + camif_hw_clear_fifo_overflow(vp); + camif_hw_set_camera_bus(camif); + camif_hw_set_source_format(camif); + camif_hw_set_camera_crop(camif); + camif_hw_set_test_pattern(camif, camif->test_pattern); + if (variant->has_img_effect) + camif_hw_set_effect(camif, camif->colorfx, + camif->colorfx_cr, camif->colorfx_cb); + if (variant->ip_revision == S3C6410_CAMIF_IP_REV) + camif_hw_set_input_path(vp); + camif_cfg_video_path(vp); + vp->state &= ~ST_VP_CONFIG; + + return 0; +} + +/* + * Initialize the video path, only up from the scaler stage. The camera + * input interface set up is skipped. This is useful to enable one of the + * video paths when the other is already running. + * Locking: called with camif->slock spinlock held. + */ +static int s3c_camif_hw_vp_init(struct camif_dev *camif, struct camif_vp *vp) +{ + unsigned int ip_rev = camif->variant->ip_revision; + + if (vp->out_fmt == NULL) + return -EINVAL; + + camif_prepare_dma_offset(vp); + if (ip_rev == S3C244X_CAMIF_IP_REV) + camif_hw_clear_fifo_overflow(vp); + camif_cfg_video_path(vp); + vp->state &= ~ST_VP_CONFIG; + return 0; +} + +static int sensor_set_power(struct camif_dev *camif, int on) +{ + struct cam_sensor *sensor = &camif->sensor; + int err = 0; + + if (camif->sensor.power_count == !on) + err = v4l2_subdev_call(sensor->sd, core, s_power, on); + if (err == -ENOIOCTLCMD) + err = 0; + if (!err) + sensor->power_count += on ? 1 : -1; + + pr_debug("on: %d, power_count: %d, err: %d\n", + on, sensor->power_count, err); + + return err; +} + +static int sensor_set_streaming(struct camif_dev *camif, int on) +{ + struct cam_sensor *sensor = &camif->sensor; + int err = 0; + + if (camif->sensor.stream_count == !on) + err = v4l2_subdev_call(sensor->sd, video, s_stream, on); + if (!err) + sensor->stream_count += on ? 1 : -1; + + pr_debug("on: %d, stream_count: %d, err: %d\n", + on, sensor->stream_count, err); + + return err; +} + +/* + * Reinitialize the driver so it is ready to start streaming again. + * Return any buffers to vb2, perform CAMIF software reset and + * turn off streaming at the data pipeline (sensor) if required. + */ +static int camif_reinitialize(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + struct camif_buffer *buf; + unsigned long flags; + bool streaming; + + spin_lock_irqsave(&camif->slock, flags); + streaming = vp->state & ST_VP_SENSOR_STREAMING; + + vp->state &= ~(ST_VP_PENDING | ST_VP_RUNNING | ST_VP_OFF | + ST_VP_ABORTING | ST_VP_STREAMING | + ST_VP_SENSOR_STREAMING | ST_VP_LASTIRQ); + + /* Release unused buffers */ + while (!list_empty(&vp->pending_buf_q)) { + buf = camif_pending_queue_pop(vp); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + while (!list_empty(&vp->active_buf_q)) { + buf = camif_active_queue_pop(vp); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + spin_unlock_irqrestore(&camif->slock, flags); + + if (!streaming) + return 0; + + return sensor_set_streaming(camif, 0); +} + +static bool s3c_vp_active(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + unsigned long flags; + bool ret; + + spin_lock_irqsave(&camif->slock, flags); + ret = (vp->state & ST_VP_RUNNING) || (vp->state & ST_VP_PENDING); + spin_unlock_irqrestore(&camif->slock, flags); + + return ret; +} + +static bool camif_is_streaming(struct camif_dev *camif) +{ + unsigned long flags; + bool status; + + spin_lock_irqsave(&camif->slock, flags); + status = camif->stream_count > 0; + spin_unlock_irqrestore(&camif->slock, flags); + + return status; +} + +static int camif_stop_capture(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + unsigned long flags; + int ret; + + if (!s3c_vp_active(vp)) + return 0; + + spin_lock_irqsave(&camif->slock, flags); + vp->state &= ~(ST_VP_OFF | ST_VP_LASTIRQ); + vp->state |= ST_VP_ABORTING; + spin_unlock_irqrestore(&camif->slock, flags); + + ret = wait_event_timeout(vp->irq_queue, + !(vp->state & ST_VP_ABORTING), + msecs_to_jiffies(CAMIF_STOP_TIMEOUT)); + + spin_lock_irqsave(&camif->slock, flags); + + if (ret == 0 && !(vp->state & ST_VP_OFF)) { + /* Timed out, forcibly stop capture */ + vp->state &= ~(ST_VP_OFF | ST_VP_ABORTING | + ST_VP_LASTIRQ); + + camif_hw_disable_capture(vp); + camif_hw_enable_scaler(vp, false); + } + + spin_unlock_irqrestore(&camif->slock, flags); + + return camif_reinitialize(vp); +} + +static int camif_prepare_addr(struct camif_vp *vp, struct vb2_buffer *vb, + struct camif_addr *paddr) +{ + struct camif_frame *frame = &vp->out_frame; + u32 pix_size; + + if (vb == NULL || frame == NULL) + return -EINVAL; + + pix_size = frame->rect.width * frame->rect.height; + + pr_debug("colplanes: %d, pix_size: %u\n", + vp->out_fmt->colplanes, pix_size); + + paddr->y = vb2_dma_contig_plane_dma_addr(vb, 0); + + switch (vp->out_fmt->colplanes) { + case 1: + paddr->cb = 0; + paddr->cr = 0; + break; + case 2: + /* decompose Y into Y/Cb */ + paddr->cb = (u32)(paddr->y + pix_size); + paddr->cr = 0; + break; + case 3: + paddr->cb = (u32)(paddr->y + pix_size); + /* decompose Y into Y/Cb/Cr */ + if (vp->out_fmt->color == IMG_FMT_YCBCR422P) + paddr->cr = (u32)(paddr->cb + (pix_size >> 1)); + else /* 420 */ + paddr->cr = (u32)(paddr->cb + (pix_size >> 2)); + + if (vp->out_fmt->color == IMG_FMT_YCRCB420) + swap(paddr->cb, paddr->cr); + break; + default: + return -EINVAL; + } + + pr_debug("DMA address: y: %pad cb: %pad cr: %pad\n", + &paddr->y, &paddr->cb, &paddr->cr); + + return 0; +} + +irqreturn_t s3c_camif_irq_handler(int irq, void *priv) +{ + struct camif_vp *vp = priv; + struct camif_dev *camif = vp->camif; + unsigned int ip_rev = camif->variant->ip_revision; + unsigned int status; + + spin_lock(&camif->slock); + + if (ip_rev == S3C6410_CAMIF_IP_REV) + camif_hw_clear_pending_irq(vp); + + status = camif_hw_get_status(vp); + + if (ip_rev == S3C244X_CAMIF_IP_REV && (status & CISTATUS_OVF_MASK)) { + camif_hw_clear_fifo_overflow(vp); + goto unlock; + } + + if (vp->state & ST_VP_ABORTING) { + if (vp->state & ST_VP_OFF) { + /* Last IRQ */ + vp->state &= ~(ST_VP_OFF | ST_VP_ABORTING | + ST_VP_LASTIRQ); + wake_up(&vp->irq_queue); + goto unlock; + } else if (vp->state & ST_VP_LASTIRQ) { + camif_hw_disable_capture(vp); + camif_hw_enable_scaler(vp, false); + camif_hw_set_lastirq(vp, false); + vp->state |= ST_VP_OFF; + } else { + /* Disable capture, enable last IRQ */ + camif_hw_set_lastirq(vp, true); + vp->state |= ST_VP_LASTIRQ; + } + } + + if (!list_empty(&vp->pending_buf_q) && (vp->state & ST_VP_RUNNING) && + !list_empty(&vp->active_buf_q)) { + unsigned int index; + struct camif_buffer *vbuf; + /* + * Get previous DMA write buffer index: + * 0 => DMA buffer 0, 2; + * 1 => DMA buffer 1, 3. + */ + index = (CISTATUS_FRAMECNT(status) + 2) & 1; + vbuf = camif_active_queue_peek(vp, index); + + if (!WARN_ON(vbuf == NULL)) { + /* Dequeue a filled buffer */ + vbuf->vb.vb2_buf.timestamp = ktime_get_ns(); + vbuf->vb.sequence = vp->frame_sequence++; + vb2_buffer_done(&vbuf->vb.vb2_buf, VB2_BUF_STATE_DONE); + + /* Set up an empty buffer at the DMA engine */ + vbuf = camif_pending_queue_pop(vp); + vbuf->index = index; + camif_hw_set_output_addr(vp, &vbuf->paddr, index); + camif_hw_set_output_addr(vp, &vbuf->paddr, index + 2); + + /* Scheduled in H/W, add to the queue */ + camif_active_queue_add(vp, vbuf); + } + } else if (!(vp->state & ST_VP_ABORTING) && + (vp->state & ST_VP_PENDING)) { + vp->state |= ST_VP_RUNNING; + } + + if (vp->state & ST_VP_CONFIG) { + camif_prepare_dma_offset(vp); + camif_hw_set_camera_crop(camif); + camif_hw_set_scaler(vp); + camif_hw_set_flip(vp); + camif_hw_set_test_pattern(camif, camif->test_pattern); + if (camif->variant->has_img_effect) + camif_hw_set_effect(camif, camif->colorfx, + camif->colorfx_cr, camif->colorfx_cb); + vp->state &= ~ST_VP_CONFIG; + } +unlock: + spin_unlock(&camif->slock); + return IRQ_HANDLED; +} + +static int start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct camif_vp *vp = vb2_get_drv_priv(vq); + struct camif_dev *camif = vp->camif; + unsigned long flags; + int ret; + + /* + * We assume the codec capture path is always activated + * first, before the preview path starts streaming. + * This is required to avoid internal FIFO overflow and + * a need for CAMIF software reset. + */ + spin_lock_irqsave(&camif->slock, flags); + + if (camif->stream_count == 0) { + camif_hw_reset(camif); + ret = s3c_camif_hw_init(camif, vp); + } else { + ret = s3c_camif_hw_vp_init(camif, vp); + } + spin_unlock_irqrestore(&camif->slock, flags); + + if (ret < 0) { + camif_reinitialize(vp); + return ret; + } + + spin_lock_irqsave(&camif->slock, flags); + vp->frame_sequence = 0; + vp->state |= ST_VP_PENDING; + + if (!list_empty(&vp->pending_buf_q) && + (!(vp->state & ST_VP_STREAMING) || + !(vp->state & ST_VP_SENSOR_STREAMING))) { + + camif_hw_enable_scaler(vp, vp->scaler.enable); + camif_hw_enable_capture(vp); + vp->state |= ST_VP_STREAMING; + + if (!(vp->state & ST_VP_SENSOR_STREAMING)) { + vp->state |= ST_VP_SENSOR_STREAMING; + spin_unlock_irqrestore(&camif->slock, flags); + ret = sensor_set_streaming(camif, 1); + if (ret) + v4l2_err(&vp->vdev, "Sensor s_stream failed\n"); + if (debug) + camif_hw_dump_regs(camif, __func__); + + return ret; + } + } + + spin_unlock_irqrestore(&camif->slock, flags); + return 0; +} + +static void stop_streaming(struct vb2_queue *vq) +{ + struct camif_vp *vp = vb2_get_drv_priv(vq); + camif_stop_capture(vp); +} + +static int queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct camif_vp *vp = vb2_get_drv_priv(vq); + struct camif_frame *frame = &vp->out_frame; + const struct camif_fmt *fmt = vp->out_fmt; + unsigned int size; + + if (fmt == NULL) + return -EINVAL; + + size = (frame->f_width * frame->f_height * fmt->depth) / 8; + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + + *num_planes = 1; + sizes[0] = size; + + pr_debug("size: %u\n", sizes[0]); + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct camif_vp *vp = vb2_get_drv_priv(vb->vb2_queue); + + if (vp->out_fmt == NULL) + return -EINVAL; + + if (vb2_plane_size(vb, 0) < vp->payload) { + v4l2_err(&vp->vdev, "buffer too small: %lu, required: %u\n", + vb2_plane_size(vb, 0), vp->payload); + return -EINVAL; + } + vb2_set_plane_payload(vb, 0, vp->payload); + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct camif_buffer *buf = container_of(vbuf, struct camif_buffer, vb); + struct camif_vp *vp = vb2_get_drv_priv(vb->vb2_queue); + struct camif_dev *camif = vp->camif; + unsigned long flags; + + spin_lock_irqsave(&camif->slock, flags); + WARN_ON(camif_prepare_addr(vp, &buf->vb.vb2_buf, &buf->paddr)); + + if (!(vp->state & ST_VP_STREAMING) && vp->active_buffers < 2) { + /* Schedule an empty buffer in H/W */ + buf->index = vp->buf_index; + + camif_hw_set_output_addr(vp, &buf->paddr, buf->index); + camif_hw_set_output_addr(vp, &buf->paddr, buf->index + 2); + + camif_active_queue_add(vp, buf); + vp->buf_index = !vp->buf_index; + } else { + camif_pending_queue_add(vp, buf); + } + + if (vb2_is_streaming(&vp->vb_queue) && !list_empty(&vp->pending_buf_q) + && !(vp->state & ST_VP_STREAMING)) { + + vp->state |= ST_VP_STREAMING; + camif_hw_enable_scaler(vp, vp->scaler.enable); + camif_hw_enable_capture(vp); + spin_unlock_irqrestore(&camif->slock, flags); + + if (!(vp->state & ST_VP_SENSOR_STREAMING)) { + if (sensor_set_streaming(camif, 1) == 0) + vp->state |= ST_VP_SENSOR_STREAMING; + else + v4l2_err(&vp->vdev, "Sensor s_stream failed\n"); + + if (debug) + camif_hw_dump_regs(camif, __func__); + } + return; + } + spin_unlock_irqrestore(&camif->slock, flags); +} + +static const struct vb2_ops s3c_camif_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + +static int s3c_camif_open(struct file *file) +{ + struct camif_vp *vp = video_drvdata(file); + struct camif_dev *camif = vp->camif; + int ret; + + pr_debug("[vp%d] state: %#x, owner: %p, pid: %d\n", vp->id, + vp->state, vp->owner, task_pid_nr(current)); + + if (mutex_lock_interruptible(&camif->lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + ret = pm_runtime_resume_and_get(camif->dev); + if (ret < 0) + goto err_pm; + + ret = sensor_set_power(camif, 1); + if (!ret) + goto unlock; + + pm_runtime_put(camif->dev); +err_pm: + v4l2_fh_release(file); +unlock: + mutex_unlock(&camif->lock); + return ret; +} + +static int s3c_camif_close(struct file *file) +{ + struct camif_vp *vp = video_drvdata(file); + struct camif_dev *camif = vp->camif; + int ret; + + pr_debug("[vp%d] state: %#x, owner: %p, pid: %d\n", vp->id, + vp->state, vp->owner, task_pid_nr(current)); + + mutex_lock(&camif->lock); + + if (vp->owner == file->private_data) { + camif_stop_capture(vp); + vb2_queue_release(&vp->vb_queue); + vp->owner = NULL; + } + + sensor_set_power(camif, 0); + + pm_runtime_put(camif->dev); + ret = v4l2_fh_release(file); + + mutex_unlock(&camif->lock); + return ret; +} + +static __poll_t s3c_camif_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct camif_vp *vp = video_drvdata(file); + struct camif_dev *camif = vp->camif; + __poll_t ret; + + mutex_lock(&camif->lock); + if (vp->owner && vp->owner != file->private_data) + ret = EPOLLERR; + else + ret = vb2_poll(&vp->vb_queue, file, wait); + + mutex_unlock(&camif->lock); + return ret; +} + +static int s3c_camif_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct camif_vp *vp = video_drvdata(file); + int ret; + + if (vp->owner && vp->owner != file->private_data) + ret = -EBUSY; + else + ret = vb2_mmap(&vp->vb_queue, vma); + + return ret; +} + +static const struct v4l2_file_operations s3c_camif_fops = { + .owner = THIS_MODULE, + .open = s3c_camif_open, + .release = s3c_camif_close, + .poll = s3c_camif_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = s3c_camif_mmap, +}; + +/* + * Video node IOCTLs + */ + +static int s3c_camif_vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct camif_vp *vp = video_drvdata(file); + + strscpy(cap->driver, S3C_CAMIF_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, S3C_CAMIF_DRIVER_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d", + dev_name(vp->camif->dev), vp->id); + return 0; +} + +static int s3c_camif_vidioc_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + struct camif_vp *vp = video_drvdata(file); + struct v4l2_subdev *sensor = vp->camif->sensor.sd; + + if (input->index || sensor == NULL) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, sensor->name, sizeof(input->name)); + return 0; +} + +static int s3c_camif_vidioc_s_input(struct file *file, void *priv, + unsigned int i) +{ + return i == 0 ? 0 : -EINVAL; +} + +static int s3c_camif_vidioc_g_input(struct file *file, void *priv, + unsigned int *i) +{ + *i = 0; + return 0; +} + +static int s3c_camif_vidioc_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct camif_vp *vp = video_drvdata(file); + const struct camif_fmt *fmt; + + fmt = s3c_camif_find_format(vp, NULL, f->index); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->fourcc; + return 0; +} + +static int s3c_camif_vidioc_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct camif_vp *vp = video_drvdata(file); + struct v4l2_pix_format *pix = &f->fmt.pix; + struct camif_frame *frame = &vp->out_frame; + const struct camif_fmt *fmt = vp->out_fmt; + + pix->bytesperline = frame->f_width * fmt->ybpp; + pix->sizeimage = vp->payload; + + pix->pixelformat = fmt->fourcc; + pix->width = frame->f_width; + pix->height = frame->f_height; + pix->field = V4L2_FIELD_NONE; + pix->colorspace = V4L2_COLORSPACE_JPEG; + + return 0; +} + +static int __camif_video_try_format(struct camif_vp *vp, + struct v4l2_pix_format *pix, + const struct camif_fmt **ffmt) +{ + struct camif_dev *camif = vp->camif; + struct v4l2_rect *crop = &camif->camif_crop; + unsigned int wmin, hmin, sc_hrmax, sc_vrmax; + const struct vp_pix_limits *pix_lim; + const struct camif_fmt *fmt; + + fmt = s3c_camif_find_format(vp, &pix->pixelformat, 0); + + if (WARN_ON(fmt == NULL)) + return -EINVAL; + + if (ffmt) + *ffmt = fmt; + + pix_lim = &camif->variant->vp_pix_limits[vp->id]; + + pr_debug("fmt: %ux%u, crop: %ux%u, bytesperline: %u\n", + pix->width, pix->height, crop->width, crop->height, + pix->bytesperline); + /* + * Calculate minimum width and height according to the configured + * camera input interface crop rectangle and the resizer's capabilities. + */ + sc_hrmax = min(SCALER_MAX_RATIO, 1 << (ffs(crop->width) - 3)); + sc_vrmax = min(SCALER_MAX_RATIO, 1 << (ffs(crop->height) - 1)); + + wmin = max_t(u32, pix_lim->min_out_width, crop->width / sc_hrmax); + wmin = round_up(wmin, pix_lim->out_width_align); + hmin = max_t(u32, 8, crop->height / sc_vrmax); + hmin = round_up(hmin, 8); + + v4l_bound_align_image(&pix->width, wmin, pix_lim->max_sc_out_width, + ffs(pix_lim->out_width_align) - 1, + &pix->height, hmin, pix_lim->max_height, 0, 0); + + pix->bytesperline = pix->width * fmt->ybpp; + pix->sizeimage = (pix->width * pix->height * fmt->depth) / 8; + pix->pixelformat = fmt->fourcc; + pix->colorspace = V4L2_COLORSPACE_JPEG; + pix->field = V4L2_FIELD_NONE; + + pr_debug("%ux%u, wmin: %d, hmin: %d, sc_hrmax: %d, sc_vrmax: %d\n", + pix->width, pix->height, wmin, hmin, sc_hrmax, sc_vrmax); + + return 0; +} + +static int s3c_camif_vidioc_try_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct camif_vp *vp = video_drvdata(file); + return __camif_video_try_format(vp, &f->fmt.pix, NULL); +} + +static int s3c_camif_vidioc_s_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct v4l2_pix_format *pix = &f->fmt.pix; + struct camif_vp *vp = video_drvdata(file); + struct camif_frame *out_frame = &vp->out_frame; + const struct camif_fmt *fmt = NULL; + int ret; + + pr_debug("[vp%d]\n", vp->id); + + if (vb2_is_busy(&vp->vb_queue)) + return -EBUSY; + + ret = __camif_video_try_format(vp, &f->fmt.pix, &fmt); + if (ret < 0) + return ret; + + vp->out_fmt = fmt; + vp->payload = pix->sizeimage; + out_frame->f_width = pix->width; + out_frame->f_height = pix->height; + + /* Reset composition rectangle */ + out_frame->rect.width = pix->width; + out_frame->rect.height = pix->height; + out_frame->rect.left = 0; + out_frame->rect.top = 0; + + if (vp->owner == NULL) + vp->owner = priv; + + pr_debug("%ux%u. payload: %u. fmt: 0x%08x. %d %d. sizeimage: %d. bpl: %d\n", + out_frame->f_width, out_frame->f_height, vp->payload, + fmt->fourcc, pix->width * pix->height * fmt->depth, + fmt->depth, pix->sizeimage, pix->bytesperline); + + return 0; +} + +/* Only check pixel formats at the sensor and the camif subdev pads */ +static int camif_pipeline_validate(struct camif_dev *camif) +{ + struct v4l2_subdev_format src_fmt; + struct media_pad *pad; + int ret; + + /* Retrieve format at the sensor subdev source pad */ + pad = media_pad_remote_pad_first(&camif->pads[0]); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + return -EPIPE; + + src_fmt.pad = pad->index; + src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(camif->sensor.sd, pad, get_fmt, NULL, &src_fmt); + if (ret < 0 && ret != -ENOIOCTLCMD) + return -EPIPE; + + if (src_fmt.format.width != camif->mbus_fmt.width || + src_fmt.format.height != camif->mbus_fmt.height || + src_fmt.format.code != camif->mbus_fmt.code) + return -EPIPE; + + return 0; +} + +static int s3c_camif_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct camif_vp *vp = video_drvdata(file); + struct camif_dev *camif = vp->camif; + struct media_entity *sensor = &camif->sensor.sd->entity; + int ret; + + pr_debug("[vp%d]\n", vp->id); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (vp->owner && vp->owner != priv) + return -EBUSY; + + if (s3c_vp_active(vp)) + return 0; + + ret = media_pipeline_start(sensor->pads, camif->m_pipeline); + if (ret < 0) + return ret; + + ret = camif_pipeline_validate(camif); + if (ret < 0) { + media_pipeline_stop(sensor->pads); + return ret; + } + + return vb2_streamon(&vp->vb_queue, type); +} + +static int s3c_camif_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct camif_vp *vp = video_drvdata(file); + struct camif_dev *camif = vp->camif; + int ret; + + pr_debug("[vp%d]\n", vp->id); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (vp->owner && vp->owner != priv) + return -EBUSY; + + ret = vb2_streamoff(&vp->vb_queue, type); + if (ret == 0) + media_pipeline_stop(camif->sensor.sd->entity.pads); + return ret; +} + +static int s3c_camif_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *rb) +{ + struct camif_vp *vp = video_drvdata(file); + int ret; + + pr_debug("[vp%d] rb count: %d, owner: %p, priv: %p\n", + vp->id, rb->count, vp->owner, priv); + + if (vp->owner && vp->owner != priv) + return -EBUSY; + + if (rb->count) + rb->count = max_t(u32, CAMIF_REQ_BUFS_MIN, rb->count); + else + vp->owner = NULL; + + ret = vb2_reqbufs(&vp->vb_queue, rb); + if (ret < 0) + return ret; + + if (rb->count && rb->count < CAMIF_REQ_BUFS_MIN) { + rb->count = 0; + vb2_reqbufs(&vp->vb_queue, rb); + ret = -ENOMEM; + } + + vp->reqbufs_count = rb->count; + if (vp->owner == NULL && rb->count > 0) + vp->owner = priv; + + return ret; +} + +static int s3c_camif_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct camif_vp *vp = video_drvdata(file); + return vb2_querybuf(&vp->vb_queue, buf); +} + +static int s3c_camif_qbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct camif_vp *vp = video_drvdata(file); + + pr_debug("[vp%d]\n", vp->id); + + if (vp->owner && vp->owner != priv) + return -EBUSY; + + return vb2_qbuf(&vp->vb_queue, vp->vdev.v4l2_dev->mdev, buf); +} + +static int s3c_camif_dqbuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct camif_vp *vp = video_drvdata(file); + + pr_debug("[vp%d] sequence: %d\n", vp->id, vp->frame_sequence); + + if (vp->owner && vp->owner != priv) + return -EBUSY; + + return vb2_dqbuf(&vp->vb_queue, buf, file->f_flags & O_NONBLOCK); +} + +static int s3c_camif_create_bufs(struct file *file, void *priv, + struct v4l2_create_buffers *create) +{ + struct camif_vp *vp = video_drvdata(file); + int ret; + + if (vp->owner && vp->owner != priv) + return -EBUSY; + + create->count = max_t(u32, 1, create->count); + ret = vb2_create_bufs(&vp->vb_queue, create); + + if (!ret && vp->owner == NULL) + vp->owner = priv; + + return ret; +} + +static int s3c_camif_prepare_buf(struct file *file, void *priv, + struct v4l2_buffer *b) +{ + struct camif_vp *vp = video_drvdata(file); + return vb2_prepare_buf(&vp->vb_queue, vp->vdev.v4l2_dev->mdev, b); +} + +static int s3c_camif_g_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct camif_vp *vp = video_drvdata(file); + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = vp->out_frame.f_width; + sel->r.height = vp->out_frame.f_height; + return 0; + + case V4L2_SEL_TGT_COMPOSE: + sel->r = vp->out_frame.rect; + return 0; + } + + return -EINVAL; +} + +static void __camif_try_compose(struct camif_dev *camif, struct camif_vp *vp, + struct v4l2_rect *r) +{ + /* s3c244x doesn't support composition */ + if (camif->variant->ip_revision == S3C244X_CAMIF_IP_REV) { + *r = vp->out_frame.rect; + return; + } + + /* TODO: s3c64xx */ +} + +static int s3c_camif_s_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct camif_vp *vp = video_drvdata(file); + struct camif_dev *camif = vp->camif; + struct v4l2_rect rect = sel->r; + unsigned long flags; + + if (sel->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + sel->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + + __camif_try_compose(camif, vp, &rect); + + sel->r = rect; + spin_lock_irqsave(&camif->slock, flags); + vp->out_frame.rect = rect; + vp->state |= ST_VP_CONFIG; + spin_unlock_irqrestore(&camif->slock, flags); + + pr_debug("type: %#x, target: %#x, flags: %#x, (%d,%d)/%dx%d\n", + sel->type, sel->target, sel->flags, + sel->r.left, sel->r.top, sel->r.width, sel->r.height); + + return 0; +} + +static const struct v4l2_ioctl_ops s3c_camif_ioctl_ops = { + .vidioc_querycap = s3c_camif_vidioc_querycap, + .vidioc_enum_input = s3c_camif_vidioc_enum_input, + .vidioc_g_input = s3c_camif_vidioc_g_input, + .vidioc_s_input = s3c_camif_vidioc_s_input, + .vidioc_enum_fmt_vid_cap = s3c_camif_vidioc_enum_fmt, + .vidioc_try_fmt_vid_cap = s3c_camif_vidioc_try_fmt, + .vidioc_s_fmt_vid_cap = s3c_camif_vidioc_s_fmt, + .vidioc_g_fmt_vid_cap = s3c_camif_vidioc_g_fmt, + .vidioc_g_selection = s3c_camif_g_selection, + .vidioc_s_selection = s3c_camif_s_selection, + .vidioc_reqbufs = s3c_camif_reqbufs, + .vidioc_querybuf = s3c_camif_querybuf, + .vidioc_prepare_buf = s3c_camif_prepare_buf, + .vidioc_create_bufs = s3c_camif_create_bufs, + .vidioc_qbuf = s3c_camif_qbuf, + .vidioc_dqbuf = s3c_camif_dqbuf, + .vidioc_streamon = s3c_camif_streamon, + .vidioc_streamoff = s3c_camif_streamoff, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +/* + * Video node controls + */ +static int s3c_camif_video_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct camif_vp *vp = ctrl->priv; + struct camif_dev *camif = vp->camif; + unsigned long flags; + + pr_debug("[vp%d] ctrl: %s, value: %d\n", vp->id, + ctrl->name, ctrl->val); + + spin_lock_irqsave(&camif->slock, flags); + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + vp->hflip = ctrl->val; + break; + + case V4L2_CID_VFLIP: + vp->vflip = ctrl->val; + break; + } + + vp->state |= ST_VP_CONFIG; + spin_unlock_irqrestore(&camif->slock, flags); + return 0; +} + +/* Codec and preview video node control ops */ +static const struct v4l2_ctrl_ops s3c_camif_video_ctrl_ops = { + .s_ctrl = s3c_camif_video_s_ctrl, +}; + +int s3c_camif_register_video_node(struct camif_dev *camif, int idx) +{ + struct camif_vp *vp = &camif->vp[idx]; + struct vb2_queue *q = &vp->vb_queue; + struct video_device *vfd = &vp->vdev; + struct v4l2_ctrl *ctrl; + int ret; + + memset(vfd, 0, sizeof(*vfd)); + snprintf(vfd->name, sizeof(vfd->name), "camif-%s", + vp->id == 0 ? "codec" : "preview"); + + vfd->fops = &s3c_camif_fops; + vfd->ioctl_ops = &s3c_camif_ioctl_ops; + vfd->v4l2_dev = &camif->v4l2_dev; + vfd->minor = -1; + vfd->release = video_device_release_empty; + vfd->lock = &camif->lock; + vp->reqbufs_count = 0; + + INIT_LIST_HEAD(&vp->pending_buf_q); + INIT_LIST_HEAD(&vp->active_buf_q); + + memset(q, 0, sizeof(*q)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->ops = &s3c_camif_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct camif_buffer); + q->drv_priv = vp; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &vp->camif->lock; + q->dev = camif->v4l2_dev.dev; + + ret = vb2_queue_init(q); + if (ret) + return ret; + + vp->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vfd->entity, 1, &vp->pad); + if (ret) + return ret; + + video_set_drvdata(vfd, vp); + + v4l2_ctrl_handler_init(&vp->ctrl_handler, 1); + ctrl = v4l2_ctrl_new_std(&vp->ctrl_handler, &s3c_camif_video_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + if (ctrl) + ctrl->priv = vp; + ctrl = v4l2_ctrl_new_std(&vp->ctrl_handler, &s3c_camif_video_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + if (ctrl) + ctrl->priv = vp; + + ret = vp->ctrl_handler.error; + if (ret < 0) + goto err_me_cleanup; + + vfd->ctrl_handler = &vp->ctrl_handler; + vfd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1); + if (ret) + goto err_ctrlh_free; + + v4l2_info(&camif->v4l2_dev, "registered %s as /dev/%s\n", + vfd->name, video_device_node_name(vfd)); + return 0; + +err_ctrlh_free: + v4l2_ctrl_handler_free(&vp->ctrl_handler); +err_me_cleanup: + media_entity_cleanup(&vfd->entity); + return ret; +} + +void s3c_camif_unregister_video_node(struct camif_dev *camif, int idx) +{ + struct video_device *vfd = &camif->vp[idx].vdev; + + if (video_is_registered(vfd)) { + video_unregister_device(vfd); + media_entity_cleanup(&vfd->entity); + v4l2_ctrl_handler_free(vfd->ctrl_handler); + } +} + +/* Media bus pixel formats supported at the camif input */ +static const u32 camif_mbus_formats[] = { + MEDIA_BUS_FMT_YUYV8_2X8, + MEDIA_BUS_FMT_YVYU8_2X8, + MEDIA_BUS_FMT_UYVY8_2X8, + MEDIA_BUS_FMT_VYUY8_2X8, +}; + +/* + * Camera input interface subdev operations + */ + +static int s3c_camif_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *code) +{ + if (code->index >= ARRAY_SIZE(camif_mbus_formats)) + return -EINVAL; + + code->code = camif_mbus_formats[code->index]; + return 0; +} + +static int s3c_camif_subdev_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct camif_dev *camif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf = &fmt->format; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + fmt->format = *mf; + return 0; + } + + mutex_lock(&camif->lock); + + switch (fmt->pad) { + case CAMIF_SD_PAD_SINK: + /* full camera input pixel size */ + *mf = camif->mbus_fmt; + break; + + case CAMIF_SD_PAD_SOURCE_C...CAMIF_SD_PAD_SOURCE_P: + /* crop rectangle at camera interface input */ + mf->width = camif->camif_crop.width; + mf->height = camif->camif_crop.height; + mf->code = camif->mbus_fmt.code; + break; + } + + mutex_unlock(&camif->lock); + mf->field = V4L2_FIELD_NONE; + mf->colorspace = V4L2_COLORSPACE_JPEG; + return 0; +} + +static void __camif_subdev_try_format(struct camif_dev *camif, + struct v4l2_mbus_framefmt *mf, int pad) +{ + const struct s3c_camif_variant *variant = camif->variant; + const struct vp_pix_limits *pix_lim; + unsigned int i; + + /* FIXME: constraints against codec or preview path ? */ + pix_lim = &variant->vp_pix_limits[VP_CODEC]; + + for (i = 0; i < ARRAY_SIZE(camif_mbus_formats); i++) + if (camif_mbus_formats[i] == mf->code) + break; + + if (i == ARRAY_SIZE(camif_mbus_formats)) + mf->code = camif_mbus_formats[0]; + + if (pad == CAMIF_SD_PAD_SINK) { + v4l_bound_align_image(&mf->width, 8, CAMIF_MAX_PIX_WIDTH, + ffs(pix_lim->out_width_align) - 1, + &mf->height, 8, CAMIF_MAX_PIX_HEIGHT, 0, + 0); + } else { + struct v4l2_rect *crop = &camif->camif_crop; + v4l_bound_align_image(&mf->width, 8, crop->width, + ffs(pix_lim->out_width_align) - 1, + &mf->height, 8, crop->height, + 0, 0); + } + + v4l2_dbg(1, debug, &camif->subdev, "%ux%u\n", mf->width, mf->height); +} + +static int s3c_camif_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct camif_dev *camif = v4l2_get_subdevdata(sd); + struct v4l2_mbus_framefmt *mf = &fmt->format; + struct v4l2_rect *crop = &camif->camif_crop; + int i; + + v4l2_dbg(1, debug, sd, "pad%d: code: 0x%x, %ux%u\n", + fmt->pad, mf->code, mf->width, mf->height); + + mf->field = V4L2_FIELD_NONE; + mf->colorspace = V4L2_COLORSPACE_JPEG; + mutex_lock(&camif->lock); + + /* + * No pixel format change at the camera input is allowed + * while streaming. + */ + if (vb2_is_busy(&camif->vp[VP_CODEC].vb_queue) || + vb2_is_busy(&camif->vp[VP_PREVIEW].vb_queue)) { + mutex_unlock(&camif->lock); + return -EBUSY; + } + + __camif_subdev_try_format(camif, mf, fmt->pad); + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { + mf = v4l2_subdev_get_try_format(sd, sd_state, fmt->pad); + *mf = fmt->format; + mutex_unlock(&camif->lock); + return 0; + } + + switch (fmt->pad) { + case CAMIF_SD_PAD_SINK: + camif->mbus_fmt = *mf; + /* Reset sink crop rectangle. */ + crop->width = mf->width; + crop->height = mf->height; + crop->left = 0; + crop->top = 0; + /* + * Reset source format (the camif's crop rectangle) + * and the video output resolution. + */ + for (i = 0; i < CAMIF_VP_NUM; i++) { + struct camif_frame *frame = &camif->vp[i].out_frame; + frame->rect = *crop; + frame->f_width = mf->width; + frame->f_height = mf->height; + } + break; + + case CAMIF_SD_PAD_SOURCE_C...CAMIF_SD_PAD_SOURCE_P: + /* Pixel format can be only changed on the sink pad. */ + mf->code = camif->mbus_fmt.code; + mf->width = crop->width; + mf->height = crop->height; + break; + } + + mutex_unlock(&camif->lock); + return 0; +} + +static int s3c_camif_subdev_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct camif_dev *camif = v4l2_get_subdevdata(sd); + struct v4l2_rect *crop = &camif->camif_crop; + struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt; + + if ((sel->target != V4L2_SEL_TGT_CROP && + sel->target != V4L2_SEL_TGT_CROP_BOUNDS) || + sel->pad != CAMIF_SD_PAD_SINK) + return -EINVAL; + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + sel->r = *v4l2_subdev_get_try_crop(sd, sd_state, sel->pad); + return 0; + } + + mutex_lock(&camif->lock); + + if (sel->target == V4L2_SEL_TGT_CROP) { + sel->r = *crop; + } else { /* crop bounds */ + sel->r.width = mf->width; + sel->r.height = mf->height; + sel->r.left = 0; + sel->r.top = 0; + } + + mutex_unlock(&camif->lock); + + v4l2_dbg(1, debug, sd, "%s: crop: (%d,%d) %dx%d, size: %ux%u\n", + __func__, crop->left, crop->top, crop->width, + crop->height, mf->width, mf->height); + + return 0; +} + +static void __camif_try_crop(struct camif_dev *camif, struct v4l2_rect *r) +{ + struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt; + const struct camif_pix_limits *pix_lim = &camif->variant->pix_limits; + unsigned int left = 2 * r->left; + unsigned int top = 2 * r->top; + + /* + * Following constraints must be met: + * - r->width + 2 * r->left = mf->width; + * - r->height + 2 * r->top = mf->height; + * - crop rectangle size and position must be aligned + * to 8 or 2 pixels, depending on SoC version. + */ + v4l_bound_align_image(&r->width, 0, mf->width, + ffs(pix_lim->win_hor_offset_align) - 1, + &r->height, 0, mf->height, 1, 0); + + v4l_bound_align_image(&left, 0, mf->width - r->width, + ffs(pix_lim->win_hor_offset_align), + &top, 0, mf->height - r->height, 2, 0); + + r->left = left / 2; + r->top = top / 2; + r->width = mf->width - left; + r->height = mf->height - top; + /* + * Make sure we either downscale or upscale both the pixel + * width and height. Just return current crop rectangle if + * this scaler constraint is not met. + */ + if (camif->variant->ip_revision == S3C244X_CAMIF_IP_REV && + camif_is_streaming(camif)) { + unsigned int i; + + for (i = 0; i < CAMIF_VP_NUM; i++) { + struct v4l2_rect *or = &camif->vp[i].out_frame.rect; + if ((or->width > r->width) == (or->height > r->height)) + continue; + *r = camif->camif_crop; + pr_debug("Width/height scaling direction limitation\n"); + break; + } + } + + v4l2_dbg(1, debug, &camif->v4l2_dev, "crop: (%d,%d)/%dx%d, fmt: %ux%u\n", + r->left, r->top, r->width, r->height, mf->width, mf->height); +} + +static int s3c_camif_subdev_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_selection *sel) +{ + struct camif_dev *camif = v4l2_get_subdevdata(sd); + struct v4l2_rect *crop = &camif->camif_crop; + struct camif_scaler scaler; + + if (sel->target != V4L2_SEL_TGT_CROP || sel->pad != CAMIF_SD_PAD_SINK) + return -EINVAL; + + mutex_lock(&camif->lock); + __camif_try_crop(camif, &sel->r); + + if (sel->which == V4L2_SUBDEV_FORMAT_TRY) { + *v4l2_subdev_get_try_crop(sd, sd_state, sel->pad) = sel->r; + } else { + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&camif->slock, flags); + *crop = sel->r; + + for (i = 0; i < CAMIF_VP_NUM; i++) { + struct camif_vp *vp = &camif->vp[i]; + scaler = vp->scaler; + if (s3c_camif_get_scaler_config(vp, &scaler)) + continue; + vp->scaler = scaler; + vp->state |= ST_VP_CONFIG; + } + + spin_unlock_irqrestore(&camif->slock, flags); + } + mutex_unlock(&camif->lock); + + v4l2_dbg(1, debug, sd, "%s: (%d,%d) %dx%d, f_w: %u, f_h: %u\n", + __func__, crop->left, crop->top, crop->width, crop->height, + camif->mbus_fmt.width, camif->mbus_fmt.height); + + return 0; +} + +static const struct v4l2_subdev_pad_ops s3c_camif_subdev_pad_ops = { + .enum_mbus_code = s3c_camif_subdev_enum_mbus_code, + .get_selection = s3c_camif_subdev_get_selection, + .set_selection = s3c_camif_subdev_set_selection, + .get_fmt = s3c_camif_subdev_get_fmt, + .set_fmt = s3c_camif_subdev_set_fmt, +}; + +static const struct v4l2_subdev_ops s3c_camif_subdev_ops = { + .pad = &s3c_camif_subdev_pad_ops, +}; + +static int s3c_camif_subdev_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct camif_dev *camif = container_of(ctrl->handler, struct camif_dev, + ctrl_handler); + unsigned long flags; + + spin_lock_irqsave(&camif->slock, flags); + + switch (ctrl->id) { + case V4L2_CID_COLORFX: + camif->colorfx = camif->ctrl_colorfx->val; + /* Set Cb, Cr */ + switch (ctrl->val) { + case V4L2_COLORFX_SEPIA: + camif->colorfx_cb = 115; + camif->colorfx_cr = 145; + break; + case V4L2_COLORFX_SET_CBCR: + camif->colorfx_cb = camif->ctrl_colorfx_cbcr->val >> 8; + camif->colorfx_cr = camif->ctrl_colorfx_cbcr->val & 0xff; + break; + default: + /* for V4L2_COLORFX_BW and others */ + camif->colorfx_cb = 128; + camif->colorfx_cr = 128; + } + break; + case V4L2_CID_TEST_PATTERN: + camif->test_pattern = camif->ctrl_test_pattern->val; + break; + default: + WARN_ON(1); + } + + camif->vp[VP_CODEC].state |= ST_VP_CONFIG; + camif->vp[VP_PREVIEW].state |= ST_VP_CONFIG; + spin_unlock_irqrestore(&camif->slock, flags); + + return 0; +} + +static const struct v4l2_ctrl_ops s3c_camif_subdev_ctrl_ops = { + .s_ctrl = s3c_camif_subdev_s_ctrl, +}; + +static const char * const s3c_camif_test_pattern_menu[] = { + "Disabled", + "Color bars", + "Horizontal increment", + "Vertical increment", +}; + +int s3c_camif_create_subdev(struct camif_dev *camif) +{ + struct v4l2_ctrl_handler *handler = &camif->ctrl_handler; + struct v4l2_subdev *sd = &camif->subdev; + int ret; + + v4l2_subdev_init(sd, &s3c_camif_subdev_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + strscpy(sd->name, "S3C-CAMIF", sizeof(sd->name)); + + camif->pads[CAMIF_SD_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + camif->pads[CAMIF_SD_PAD_SOURCE_C].flags = MEDIA_PAD_FL_SOURCE; + camif->pads[CAMIF_SD_PAD_SOURCE_P].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&sd->entity, CAMIF_SD_PADS_NUM, + camif->pads); + if (ret) + return ret; + + v4l2_ctrl_handler_init(handler, 3); + camif->ctrl_test_pattern = v4l2_ctrl_new_std_menu_items(handler, + &s3c_camif_subdev_ctrl_ops, V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(s3c_camif_test_pattern_menu) - 1, 0, 0, + s3c_camif_test_pattern_menu); + + if (camif->variant->has_img_effect) { + camif->ctrl_colorfx = v4l2_ctrl_new_std_menu(handler, + &s3c_camif_subdev_ctrl_ops, + V4L2_CID_COLORFX, V4L2_COLORFX_SET_CBCR, + ~0x981f, V4L2_COLORFX_NONE); + + camif->ctrl_colorfx_cbcr = v4l2_ctrl_new_std(handler, + &s3c_camif_subdev_ctrl_ops, + V4L2_CID_COLORFX_CBCR, 0, 0xffff, 1, 0); + } + + if (handler->error) { + v4l2_ctrl_handler_free(handler); + media_entity_cleanup(&sd->entity); + return handler->error; + } + + if (camif->variant->has_img_effect) + v4l2_ctrl_auto_cluster(2, &camif->ctrl_colorfx, + V4L2_COLORFX_SET_CBCR, false); + + sd->ctrl_handler = handler; + v4l2_set_subdevdata(sd, camif); + + return 0; +} + +void s3c_camif_unregister_subdev(struct camif_dev *camif) +{ + struct v4l2_subdev *sd = &camif->subdev; + + /* Return if not registered */ + if (v4l2_get_subdevdata(sd) == NULL) + return; + + v4l2_device_unregister_subdev(sd); + media_entity_cleanup(&sd->entity); + v4l2_ctrl_handler_free(&camif->ctrl_handler); + v4l2_set_subdevdata(sd, NULL); +} + +int s3c_camif_set_defaults(struct camif_dev *camif) +{ + unsigned int ip_rev = camif->variant->ip_revision; + int i; + + for (i = 0; i < CAMIF_VP_NUM; i++) { + struct camif_vp *vp = &camif->vp[i]; + struct camif_frame *f = &vp->out_frame; + + vp->camif = camif; + vp->id = i; + vp->offset = camif->variant->vp_offset; + + if (ip_rev == S3C244X_CAMIF_IP_REV) + vp->fmt_flags = i ? FMT_FL_S3C24XX_PREVIEW : + FMT_FL_S3C24XX_CODEC; + else + vp->fmt_flags = FMT_FL_S3C64XX; + + vp->out_fmt = s3c_camif_find_format(vp, NULL, 0); + BUG_ON(vp->out_fmt == NULL); + + memset(f, 0, sizeof(*f)); + f->f_width = CAMIF_DEF_WIDTH; + f->f_height = CAMIF_DEF_HEIGHT; + f->rect.width = CAMIF_DEF_WIDTH; + f->rect.height = CAMIF_DEF_HEIGHT; + + /* Scaler is always enabled */ + vp->scaler.enable = 1; + + vp->payload = (f->f_width * f->f_height * + vp->out_fmt->depth) / 8; + } + + memset(&camif->mbus_fmt, 0, sizeof(camif->mbus_fmt)); + camif->mbus_fmt.width = CAMIF_DEF_WIDTH; + camif->mbus_fmt.height = CAMIF_DEF_HEIGHT; + camif->mbus_fmt.code = camif_mbus_formats[0]; + + memset(&camif->camif_crop, 0, sizeof(camif->camif_crop)); + camif->camif_crop.width = CAMIF_DEF_WIDTH; + camif->camif_crop.height = CAMIF_DEF_HEIGHT; + + return 0; +} diff --git a/drivers/media/platform/samsung/s3c-camif/camif-core.c b/drivers/media/platform/samsung/s3c-camif/camif-core.c new file mode 100644 index 000000000..6e8ef8656 --- /dev/null +++ b/drivers/media/platform/samsung/s3c-camif/camif-core.c @@ -0,0 +1,639 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * s3c24xx/s3c64xx SoC series Camera Interface (CAMIF) driver + * + * Copyright (C) 2012 Sylwester Nawrocki + * Copyright (C) 2012 Tomasz Figa + */ +#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "camif-core.h" + +static char *camif_clocks[CLK_MAX_NUM] = { + /* HCLK CAMIF clock */ + [CLK_GATE] = "camif", + /* CAMIF / external camera sensor master clock */ + [CLK_CAM] = "camera", +}; + +static const struct camif_fmt camif_formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = 16, + .ybpp = 1, + .color = IMG_FMT_YCBCR422P, + .colplanes = 3, + .flags = FMT_FL_S3C24XX_CODEC | + FMT_FL_S3C64XX, + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .ybpp = 1, + .color = IMG_FMT_YCBCR420, + .colplanes = 3, + .flags = FMT_FL_S3C24XX_CODEC | + FMT_FL_S3C64XX, + }, { + .fourcc = V4L2_PIX_FMT_YVU420, + .depth = 12, + .ybpp = 1, + .color = IMG_FMT_YCRCB420, + .colplanes = 3, + .flags = FMT_FL_S3C24XX_CODEC | + FMT_FL_S3C64XX, + }, { + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .ybpp = 2, + .color = IMG_FMT_RGB565, + .colplanes = 1, + .flags = FMT_FL_S3C24XX_PREVIEW | + FMT_FL_S3C64XX, + }, { + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .ybpp = 4, + .color = IMG_FMT_XRGB8888, + .colplanes = 1, + .flags = FMT_FL_S3C24XX_PREVIEW | + FMT_FL_S3C64XX, + }, { + .fourcc = V4L2_PIX_FMT_BGR666, + .depth = 32, + .ybpp = 4, + .color = IMG_FMT_RGB666, + .colplanes = 1, + .flags = FMT_FL_S3C64XX, + } +}; + +/** + * s3c_camif_find_format() - lookup camif color format by fourcc or an index + * @vp: video path (DMA) description (codec/preview) + * @pixelformat: fourcc to match, ignored if null + * @index: index to the camif_formats array, ignored if negative + */ +const struct camif_fmt *s3c_camif_find_format(struct camif_vp *vp, + const u32 *pixelformat, + int index) +{ + const struct camif_fmt *fmt, *def_fmt = NULL; + unsigned int i; + int id = 0; + + if (index >= (int)ARRAY_SIZE(camif_formats)) + return NULL; + + for (i = 0; i < ARRAY_SIZE(camif_formats); ++i) { + fmt = &camif_formats[i]; + if (vp && !(vp->fmt_flags & fmt->flags)) + continue; + if (pixelformat && fmt->fourcc == *pixelformat) + return fmt; + if (index == id) + def_fmt = fmt; + id++; + } + return def_fmt; +} + +static int camif_get_scaler_factor(u32 src, u32 tar, u32 *ratio, u32 *shift) +{ + unsigned int sh = 6; + + if (src >= 64 * tar) + return -EINVAL; + + while (sh--) { + unsigned int tmp = 1 << sh; + if (src >= tar * tmp) { + *shift = sh; + *ratio = tmp; + return 0; + } + } + *shift = 0; + *ratio = 1; + return 0; +} + +int s3c_camif_get_scaler_config(struct camif_vp *vp, + struct camif_scaler *scaler) +{ + struct v4l2_rect *camif_crop = &vp->camif->camif_crop; + int source_x = camif_crop->width; + int source_y = camif_crop->height; + int target_x = vp->out_frame.rect.width; + int target_y = vp->out_frame.rect.height; + int ret; + + if (vp->rotation == 90 || vp->rotation == 270) + swap(target_x, target_y); + + ret = camif_get_scaler_factor(source_x, target_x, &scaler->pre_h_ratio, + &scaler->h_shift); + if (ret < 0) + return ret; + + ret = camif_get_scaler_factor(source_y, target_y, &scaler->pre_v_ratio, + &scaler->v_shift); + if (ret < 0) + return ret; + + scaler->pre_dst_width = source_x / scaler->pre_h_ratio; + scaler->pre_dst_height = source_y / scaler->pre_v_ratio; + + scaler->main_h_ratio = (source_x << 8) / (target_x << scaler->h_shift); + scaler->main_v_ratio = (source_y << 8) / (target_y << scaler->v_shift); + + scaler->scaleup_h = (target_x >= source_x); + scaler->scaleup_v = (target_y >= source_y); + + scaler->copy = 0; + + pr_debug("H: ratio: %u, shift: %u. V: ratio: %u, shift: %u.\n", + scaler->pre_h_ratio, scaler->h_shift, + scaler->pre_v_ratio, scaler->v_shift); + + pr_debug("Source: %dx%d, Target: %dx%d, scaleup_h/v: %d/%d\n", + source_x, source_y, target_x, target_y, + scaler->scaleup_h, scaler->scaleup_v); + + return 0; +} + +static int camif_register_sensor(struct camif_dev *camif) +{ + struct s3c_camif_sensor_info *sensor = &camif->pdata.sensor; + struct v4l2_device *v4l2_dev = &camif->v4l2_dev; + struct i2c_adapter *adapter; + struct v4l2_subdev_format format; + struct v4l2_subdev *sd; + int ret; + + camif->sensor.sd = NULL; + + if (sensor->i2c_board_info.addr == 0) + return -EINVAL; + + adapter = i2c_get_adapter(sensor->i2c_bus_num); + if (adapter == NULL) { + v4l2_warn(v4l2_dev, "failed to get I2C adapter %d\n", + sensor->i2c_bus_num); + return -EPROBE_DEFER; + } + + sd = v4l2_i2c_new_subdev_board(v4l2_dev, adapter, + &sensor->i2c_board_info, NULL); + if (sd == NULL) { + i2c_put_adapter(adapter); + v4l2_warn(v4l2_dev, "failed to acquire subdev %s\n", + sensor->i2c_board_info.type); + return -EPROBE_DEFER; + } + camif->sensor.sd = sd; + + v4l2_info(v4l2_dev, "registered sensor subdevice %s\n", sd->name); + + /* Get initial pixel format and set it at the camif sink pad */ + format.pad = 0; + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &format); + + if (ret < 0) + return 0; + + format.pad = CAMIF_SD_PAD_SINK; + v4l2_subdev_call(&camif->subdev, pad, set_fmt, NULL, &format); + + v4l2_info(sd, "Initial format from sensor: %dx%d, %#x\n", + format.format.width, format.format.height, + format.format.code); + return 0; +} + +static void camif_unregister_sensor(struct camif_dev *camif) +{ + struct v4l2_subdev *sd = camif->sensor.sd; + struct i2c_client *client = sd ? v4l2_get_subdevdata(sd) : NULL; + struct i2c_adapter *adapter; + + if (client == NULL) + return; + + adapter = client->adapter; + v4l2_device_unregister_subdev(sd); + camif->sensor.sd = NULL; + i2c_unregister_device(client); + i2c_put_adapter(adapter); +} + +static int camif_create_media_links(struct camif_dev *camif) +{ + int i, ret; + + ret = media_create_pad_link(&camif->sensor.sd->entity, 0, + &camif->subdev.entity, CAMIF_SD_PAD_SINK, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) + return ret; + + for (i = 1; i < CAMIF_SD_PADS_NUM && !ret; i++) { + ret = media_create_pad_link(&camif->subdev.entity, i, + &camif->vp[i - 1].vdev.entity, 0, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + } + + return ret; +} + +static int camif_register_video_nodes(struct camif_dev *camif) +{ + int ret = s3c_camif_register_video_node(camif, VP_CODEC); + if (ret < 0) + return ret; + + return s3c_camif_register_video_node(camif, VP_PREVIEW); +} + +static void camif_unregister_video_nodes(struct camif_dev *camif) +{ + s3c_camif_unregister_video_node(camif, VP_CODEC); + s3c_camif_unregister_video_node(camif, VP_PREVIEW); +} + +static void camif_unregister_media_entities(struct camif_dev *camif) +{ + camif_unregister_video_nodes(camif); + camif_unregister_sensor(camif); +} + +/* + * Media device + */ +static int camif_media_dev_init(struct camif_dev *camif) +{ + struct media_device *md = &camif->media_dev; + struct v4l2_device *v4l2_dev = &camif->v4l2_dev; + unsigned int ip_rev = camif->variant->ip_revision; + int ret; + + memset(md, 0, sizeof(*md)); + snprintf(md->model, sizeof(md->model), "Samsung S3C%s CAMIF", + ip_rev == S3C6410_CAMIF_IP_REV ? "6410" : "244X"); + strscpy(md->bus_info, "platform", sizeof(md->bus_info)); + md->hw_revision = ip_rev; + + md->dev = camif->dev; + + strscpy(v4l2_dev->name, "s3c-camif", sizeof(v4l2_dev->name)); + v4l2_dev->mdev = md; + + media_device_init(md); + + ret = v4l2_device_register(camif->dev, v4l2_dev); + if (ret < 0) + return ret; + + return ret; +} + +static void camif_clk_put(struct camif_dev *camif) +{ + int i; + + for (i = 0; i < CLK_MAX_NUM; i++) { + if (IS_ERR(camif->clock[i])) + continue; + clk_unprepare(camif->clock[i]); + clk_put(camif->clock[i]); + camif->clock[i] = ERR_PTR(-EINVAL); + } +} + +static int camif_clk_get(struct camif_dev *camif) +{ + int ret, i; + + for (i = 1; i < CLK_MAX_NUM; i++) + camif->clock[i] = ERR_PTR(-EINVAL); + + for (i = 0; i < CLK_MAX_NUM; i++) { + camif->clock[i] = clk_get(camif->dev, camif_clocks[i]); + if (IS_ERR(camif->clock[i])) { + ret = PTR_ERR(camif->clock[i]); + goto err; + } + ret = clk_prepare(camif->clock[i]); + if (ret < 0) { + clk_put(camif->clock[i]); + camif->clock[i] = NULL; + goto err; + } + } + return 0; +err: + camif_clk_put(camif); + dev_err(camif->dev, "failed to get clock: %s\n", + camif_clocks[i]); + return ret; +} + +/* + * The CAMIF device has two relatively independent data processing paths + * that can source data from memory or the common camera input frontend. + * Register interrupts for each data processing path (camif_vp). + */ +static int camif_request_irqs(struct platform_device *pdev, + struct camif_dev *camif) +{ + int irq, ret, i; + + for (i = 0; i < CAMIF_VP_NUM; i++) { + struct camif_vp *vp = &camif->vp[i]; + + init_waitqueue_head(&vp->irq_queue); + + irq = platform_get_irq(pdev, i); + if (irq <= 0) + return -ENXIO; + + ret = devm_request_irq(&pdev->dev, irq, s3c_camif_irq_handler, + 0, dev_name(&pdev->dev), vp); + if (ret < 0) { + dev_err(&pdev->dev, "failed to install IRQ: %d\n", ret); + break; + } + } + + return ret; +} + +static int s3c_camif_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct s3c_camif_plat_data *pdata = dev->platform_data; + struct s3c_camif_drvdata *drvdata; + struct camif_dev *camif; + int ret = 0; + + camif = devm_kzalloc(dev, sizeof(*camif), GFP_KERNEL); + if (!camif) + return -ENOMEM; + + spin_lock_init(&camif->slock); + mutex_init(&camif->lock); + + camif->dev = dev; + + if (!pdata || !pdata->gpio_get || !pdata->gpio_put) { + dev_err(dev, "wrong platform data\n"); + return -EINVAL; + } + + camif->pdata = *pdata; + drvdata = (void *)platform_get_device_id(pdev)->driver_data; + camif->variant = drvdata->variant; + + camif->io_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(camif->io_base)) + return PTR_ERR(camif->io_base); + + ret = camif_request_irqs(pdev, camif); + if (ret < 0) + return ret; + + ret = pdata->gpio_get(); + if (ret < 0) + return ret; + + ret = s3c_camif_create_subdev(camif); + if (ret < 0) + goto err_sd; + + ret = camif_clk_get(camif); + if (ret < 0) + goto err_clk; + + platform_set_drvdata(pdev, camif); + clk_set_rate(camif->clock[CLK_CAM], + camif->pdata.sensor.clock_frequency); + + dev_info(dev, "sensor clock frequency: %lu\n", + clk_get_rate(camif->clock[CLK_CAM])); + /* + * Set initial pixel format, resolution and crop rectangle. + * Must be done before a sensor subdev is registered as some + * settings are overrode with values from sensor subdev. + */ + s3c_camif_set_defaults(camif); + + pm_runtime_enable(dev); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + goto err_disable; + + ret = camif_media_dev_init(camif); + if (ret < 0) + goto err_pm; + + ret = camif_register_sensor(camif); + if (ret < 0) + goto err_sens; + + ret = v4l2_device_register_subdev(&camif->v4l2_dev, &camif->subdev); + if (ret < 0) + goto err_sens; + + ret = v4l2_device_register_subdev_nodes(&camif->v4l2_dev); + if (ret < 0) + goto err_sens; + + ret = camif_register_video_nodes(camif); + if (ret < 0) + goto err_sens; + + ret = camif_create_media_links(camif); + if (ret < 0) + goto err_sens; + + ret = media_device_register(&camif->media_dev); + if (ret < 0) + goto err_sens; + + pm_runtime_put(dev); + return 0; + +err_sens: + v4l2_device_unregister(&camif->v4l2_dev); + media_device_unregister(&camif->media_dev); + media_device_cleanup(&camif->media_dev); + camif_unregister_media_entities(camif); +err_pm: + pm_runtime_put(dev); +err_disable: + pm_runtime_disable(dev); + camif_clk_put(camif); +err_clk: + s3c_camif_unregister_subdev(camif); +err_sd: + pdata->gpio_put(); + return ret; +} + +static int s3c_camif_remove(struct platform_device *pdev) +{ + struct camif_dev *camif = platform_get_drvdata(pdev); + struct s3c_camif_plat_data *pdata = &camif->pdata; + + media_device_unregister(&camif->media_dev); + media_device_cleanup(&camif->media_dev); + camif_unregister_media_entities(camif); + v4l2_device_unregister(&camif->v4l2_dev); + + pm_runtime_disable(&pdev->dev); + camif_clk_put(camif); + s3c_camif_unregister_subdev(camif); + pdata->gpio_put(); + + return 0; +} + +static int s3c_camif_runtime_resume(struct device *dev) +{ + struct camif_dev *camif = dev_get_drvdata(dev); + + clk_enable(camif->clock[CLK_GATE]); + /* null op on s3c244x */ + clk_enable(camif->clock[CLK_CAM]); + return 0; +} + +static int s3c_camif_runtime_suspend(struct device *dev) +{ + struct camif_dev *camif = dev_get_drvdata(dev); + + /* null op on s3c244x */ + clk_disable(camif->clock[CLK_CAM]); + + clk_disable(camif->clock[CLK_GATE]); + return 0; +} + +static const struct s3c_camif_variant s3c244x_camif_variant = { + .vp_pix_limits = { + [VP_CODEC] = { + .max_out_width = 4096, + .max_sc_out_width = 2048, + .out_width_align = 16, + .min_out_width = 16, + .max_height = 4096, + }, + [VP_PREVIEW] = { + .max_out_width = 640, + .max_sc_out_width = 640, + .out_width_align = 16, + .min_out_width = 16, + .max_height = 480, + } + }, + .pix_limits = { + .win_hor_offset_align = 8, + }, + .ip_revision = S3C244X_CAMIF_IP_REV, +}; + +static struct s3c_camif_drvdata s3c244x_camif_drvdata = { + .variant = &s3c244x_camif_variant, + .bus_clk_freq = 24000000UL, +}; + +static const struct s3c_camif_variant s3c6410_camif_variant = { + .vp_pix_limits = { + [VP_CODEC] = { + .max_out_width = 4096, + .max_sc_out_width = 2048, + .out_width_align = 16, + .min_out_width = 16, + .max_height = 4096, + }, + [VP_PREVIEW] = { + .max_out_width = 4096, + .max_sc_out_width = 720, + .out_width_align = 16, + .min_out_width = 16, + .max_height = 4096, + } + }, + .pix_limits = { + .win_hor_offset_align = 8, + }, + .ip_revision = S3C6410_CAMIF_IP_REV, + .has_img_effect = 1, + .vp_offset = 0x20, +}; + +static struct s3c_camif_drvdata s3c6410_camif_drvdata = { + .variant = &s3c6410_camif_variant, + .bus_clk_freq = 133000000UL, +}; + +static const struct platform_device_id s3c_camif_driver_ids[] = { + { + .name = "s3c2440-camif", + .driver_data = (unsigned long)&s3c244x_camif_drvdata, + }, { + .name = "s3c6410-camif", + .driver_data = (unsigned long)&s3c6410_camif_drvdata, + }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(platform, s3c_camif_driver_ids); + +static const struct dev_pm_ops s3c_camif_pm_ops = { + .runtime_suspend = s3c_camif_runtime_suspend, + .runtime_resume = s3c_camif_runtime_resume, +}; + +static struct platform_driver s3c_camif_driver = { + .probe = s3c_camif_probe, + .remove = s3c_camif_remove, + .id_table = s3c_camif_driver_ids, + .driver = { + .name = S3C_CAMIF_DRIVER_NAME, + .pm = &s3c_camif_pm_ops, + } +}; + +module_platform_driver(s3c_camif_driver); + +MODULE_AUTHOR("Sylwester Nawrocki "); +MODULE_AUTHOR("Tomasz Figa "); +MODULE_DESCRIPTION("S3C24XX/S3C64XX SoC camera interface driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/samsung/s3c-camif/camif-core.h b/drivers/media/platform/samsung/s3c-camif/camif-core.h new file mode 100644 index 000000000..f3442e251 --- /dev/null +++ b/drivers/media/platform/samsung/s3c-camif/camif-core.h @@ -0,0 +1,397 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * s3c24xx/s3c64xx SoC series Camera Interface (CAMIF) driver + * + * Copyright (C) 2012 Sylwester Nawrocki + * Copyright (C) 2012 Tomasz Figa +*/ + +#ifndef CAMIF_CORE_H_ +#define CAMIF_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define S3C_CAMIF_DRIVER_NAME "s3c-camif" +#define CAMIF_REQ_BUFS_MIN 3 +#define CAMIF_MAX_OUT_BUFS 4 +#define CAMIF_MAX_PIX_WIDTH 4096 +#define CAMIF_MAX_PIX_HEIGHT 4096 +#define SCALER_MAX_RATIO 64 +#define CAMIF_DEF_WIDTH 640 +#define CAMIF_DEF_HEIGHT 480 +#define CAMIF_STOP_TIMEOUT 1500 /* ms */ + +#define S3C244X_CAMIF_IP_REV 0x20 /* 2.0 */ +#define S3C2450_CAMIF_IP_REV 0x30 /* 3.0 - not implemented, not tested */ +#define S3C6400_CAMIF_IP_REV 0x31 /* 3.1 - not implemented, not tested */ +#define S3C6410_CAMIF_IP_REV 0x32 /* 3.2 */ + +/* struct camif_vp::state */ + +#define ST_VP_PENDING (1 << 0) +#define ST_VP_RUNNING (1 << 1) +#define ST_VP_STREAMING (1 << 2) +#define ST_VP_SENSOR_STREAMING (1 << 3) + +#define ST_VP_ABORTING (1 << 4) +#define ST_VP_OFF (1 << 5) +#define ST_VP_LASTIRQ (1 << 6) + +#define ST_VP_CONFIG (1 << 8) + +#define CAMIF_SD_PAD_SINK 0 +#define CAMIF_SD_PAD_SOURCE_C 1 +#define CAMIF_SD_PAD_SOURCE_P 2 +#define CAMIF_SD_PADS_NUM 3 + +enum img_fmt { + IMG_FMT_RGB565 = 0x0010, + IMG_FMT_RGB666, + IMG_FMT_XRGB8888, + IMG_FMT_YCBCR420 = 0x0020, + IMG_FMT_YCRCB420, + IMG_FMT_YCBCR422P, + IMG_FMT_YCBYCR422 = 0x0040, + IMG_FMT_YCRYCB422, + IMG_FMT_CBYCRY422, + IMG_FMT_CRYCBY422, +}; + +#define img_fmt_is_rgb(x) ((x) & 0x10) +#define img_fmt_is_ycbcr(x) ((x) & 0x60) + +/* Possible values for struct camif_fmt::flags */ +#define FMT_FL_S3C24XX_CODEC (1 << 0) +#define FMT_FL_S3C24XX_PREVIEW (1 << 1) +#define FMT_FL_S3C64XX (1 << 2) + +/** + * struct camif_fmt - pixel format description + * @fourcc: fourcc code for this format, 0 if not applicable + * @color: a corresponding enum img_fmt + * @colplanes: number of physically contiguous data planes + * @flags: indicate for which SoCs revisions this format is valid + * @depth: bits per pixel (total) + * @ybpp: number of luminance bytes per pixel + */ +struct camif_fmt { + u32 fourcc; + u32 color; + u16 colplanes; + u16 flags; + u8 depth; + u8 ybpp; +}; + +/** + * struct camif_dma_offset - pixel offset information for DMA + * @initial: offset (in pixels) to first pixel + * @line: offset (in pixels) from end of line to start of next line + */ +struct camif_dma_offset { + int initial; + int line; +}; + +/** + * struct camif_frame - source/target frame properties + * @f_width: full pixel width + * @f_height: full pixel height + * @rect: crop/composition rectangle + * @dma_offset: DMA offset configuration + */ +struct camif_frame { + u16 f_width; + u16 f_height; + struct v4l2_rect rect; + struct camif_dma_offset dma_offset; +}; + +/* CAMIF clocks enumeration */ +enum { + CLK_GATE, + CLK_CAM, + CLK_MAX_NUM, +}; + +struct vp_pix_limits { + u16 max_out_width; + u16 max_sc_out_width; + u16 out_width_align; + u16 max_height; + u8 min_out_width; + u16 out_hor_offset_align; +}; + +struct camif_pix_limits { + u16 win_hor_offset_align; +}; + +/** + * struct s3c_camif_variant - CAMIF variant structure + * @vp_pix_limits: pixel limits for the codec and preview paths + * @pix_limits: pixel limits for the camera input interface + * @ip_revision: the CAMIF IP revision: 0x20 for s3c244x, 0x32 for s3c6410 + * @has_img_effect: supports image effects + * @vp_offset: register offset + */ +struct s3c_camif_variant { + struct vp_pix_limits vp_pix_limits[2]; + struct camif_pix_limits pix_limits; + u8 ip_revision; + u8 has_img_effect; + unsigned int vp_offset; +}; + +struct s3c_camif_drvdata { + const struct s3c_camif_variant *variant; + unsigned long bus_clk_freq; +}; + +struct camif_scaler { + u8 scaleup_h; + u8 scaleup_v; + u8 copy; + u8 enable; + u32 h_shift; + u32 v_shift; + u32 pre_h_ratio; + u32 pre_v_ratio; + u32 pre_dst_width; + u32 pre_dst_height; + u32 main_h_ratio; + u32 main_v_ratio; +}; + +struct camif_dev; + +/** + * struct camif_vp - CAMIF data processing path structure (codec/preview) + * @irq_queue: interrupt handling waitqueue + * @irq: interrupt number for this data path + * @camif: pointer to the camif structure + * @pad: media pad for the video node + * @vdev: video device + * @ctrl_handler: video node controls handler + * @owner: file handle that own the streaming + * @vb_queue: vb2 buffer queue + * @pending_buf_q: pending (empty) buffers queue head + * @active_buf_q: active (being written) buffers queue head + * @active_buffers: counter of buffer set up at the DMA engine + * @buf_index: identifier of a last empty buffer set up in H/W + * @frame_sequence: image frame sequence counter + * @reqbufs_count: the number of buffers requested + * @scaler: the scaler structure + * @out_fmt: pixel format at this video path output + * @payload: the output data frame payload size + * @out_frame: the output pixel resolution + * @state: the video path's state + * @fmt_flags: flags determining supported pixel formats + * @id: CAMIF id, 0 - codec, 1 - preview + * @rotation: current image rotation value + * @hflip: apply horizontal flip if set + * @vflip: apply vertical flip if set + * @offset: register offset + */ +struct camif_vp { + wait_queue_head_t irq_queue; + int irq; + struct camif_dev *camif; + struct media_pad pad; + struct video_device vdev; + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_fh *owner; + struct vb2_queue vb_queue; + struct list_head pending_buf_q; + struct list_head active_buf_q; + unsigned int active_buffers; + unsigned int buf_index; + unsigned int frame_sequence; + unsigned int reqbufs_count; + struct camif_scaler scaler; + const struct camif_fmt *out_fmt; + unsigned int payload; + struct camif_frame out_frame; + unsigned int state; + u16 fmt_flags; + u8 id; + u16 rotation; + u8 hflip; + u8 vflip; + unsigned int offset; +}; + +/* Video processing path enumeration */ +#define VP_CODEC 0 +#define VP_PREVIEW 1 +#define CAMIF_VP_NUM 2 + +/** + * struct camif_dev - the CAMIF driver private data structure + * @media_dev: top-level media device structure + * @v4l2_dev: root v4l2_device + * @subdev: camera interface ("catchcam") subdev + * @mbus_fmt: camera input media bus format + * @camif_crop: camera input interface crop rectangle + * @pads: the camif subdev's media pads + * @stream_count: the camera interface streaming reference counter + * @sensor: image sensor data structure + * @m_pipeline: video entity pipeline description + * @ctrl_handler: v4l2 control handler (owned by @subdev) + * @ctrl_test_pattern: V4L2_CID_TEST_PATTERN control + * @ctrl_colorfx: V4L2_CID_COLORFX control + * @ctrl_colorfx_cbcr: V4L2_CID_COLORFX_CBCR control + * @test_pattern: test pattern + * @colorfx: color effect + * @colorfx_cb: Cb value for V4L2_COLORFX_SET_CBCR + * @colorfx_cr: Cr value for V4L2_COLORFX_SET_CBCR + * @vp: video path (DMA) description (codec/preview) + * @variant: variant information for this device + * @dev: pointer to the CAMIF device struct + * @pdata: a copy of the driver's platform data + * @clock: clocks required for the CAMIF operation + * @lock: mutex protecting this data structure + * @slock: spinlock protecting CAMIF registers + * @io_base: start address of the mmapped CAMIF registers + */ +struct camif_dev { + struct media_device media_dev; + struct v4l2_device v4l2_dev; + struct v4l2_subdev subdev; + struct v4l2_mbus_framefmt mbus_fmt; + struct v4l2_rect camif_crop; + struct media_pad pads[CAMIF_SD_PADS_NUM]; + int stream_count; + + struct cam_sensor { + struct v4l2_subdev *sd; + short power_count; + short stream_count; + } sensor; + struct media_pipeline *m_pipeline; + + struct v4l2_ctrl_handler ctrl_handler; + struct v4l2_ctrl *ctrl_test_pattern; + struct { + struct v4l2_ctrl *ctrl_colorfx; + struct v4l2_ctrl *ctrl_colorfx_cbcr; + }; + u8 test_pattern; + u8 colorfx; + u8 colorfx_cb; + u8 colorfx_cr; + + struct camif_vp vp[CAMIF_VP_NUM]; + + const struct s3c_camif_variant *variant; + struct device *dev; + struct s3c_camif_plat_data pdata; + struct clk *clock[CLK_MAX_NUM]; + struct mutex lock; + spinlock_t slock; + void __iomem *io_base; +}; + +/** + * struct camif_addr - Y/Cb/Cr DMA start address structure + * @y: luminance plane dma address + * @cb: Cb plane dma address + * @cr: Cr plane dma address + */ +struct camif_addr { + dma_addr_t y; + dma_addr_t cb; + dma_addr_t cr; +}; + +/** + * struct camif_buffer - the camif video buffer structure + * @vb: vb2 buffer + * @list: list head for the buffers queue + * @paddr: DMA start addresses + * @index: an identifier of this buffer at the DMA engine + */ +struct camif_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; + struct camif_addr paddr; + unsigned int index; +}; + +const struct camif_fmt *s3c_camif_find_format(struct camif_vp *vp, + const u32 *pixelformat, int index); +int s3c_camif_register_video_node(struct camif_dev *camif, int idx); +void s3c_camif_unregister_video_node(struct camif_dev *camif, int idx); +irqreturn_t s3c_camif_irq_handler(int irq, void *priv); +int s3c_camif_create_subdev(struct camif_dev *camif); +void s3c_camif_unregister_subdev(struct camif_dev *camif); +int s3c_camif_set_defaults(struct camif_dev *camif); +int s3c_camif_get_scaler_config(struct camif_vp *vp, + struct camif_scaler *scaler); + +static inline void camif_active_queue_add(struct camif_vp *vp, + struct camif_buffer *buf) +{ + list_add_tail(&buf->list, &vp->active_buf_q); + vp->active_buffers++; +} + +static inline struct camif_buffer *camif_active_queue_pop( + struct camif_vp *vp) +{ + struct camif_buffer *buf = list_first_entry(&vp->active_buf_q, + struct camif_buffer, list); + list_del(&buf->list); + vp->active_buffers--; + return buf; +} + +static inline struct camif_buffer *camif_active_queue_peek( + struct camif_vp *vp, int index) +{ + struct camif_buffer *tmp, *buf; + + if (WARN_ON(list_empty(&vp->active_buf_q))) + return NULL; + + list_for_each_entry_safe(buf, tmp, &vp->active_buf_q, list) { + if (buf->index == index) { + list_del(&buf->list); + vp->active_buffers--; + return buf; + } + } + + return NULL; +} + +static inline void camif_pending_queue_add(struct camif_vp *vp, + struct camif_buffer *buf) +{ + list_add_tail(&buf->list, &vp->pending_buf_q); +} + +static inline struct camif_buffer *camif_pending_queue_pop( + struct camif_vp *vp) +{ + struct camif_buffer *buf = list_first_entry(&vp->pending_buf_q, + struct camif_buffer, list); + list_del(&buf->list); + return buf; +} + +#endif /* CAMIF_CORE_H_ */ diff --git a/drivers/media/platform/samsung/s3c-camif/camif-regs.c b/drivers/media/platform/samsung/s3c-camif/camif-regs.c new file mode 100644 index 000000000..e80204f57 --- /dev/null +++ b/drivers/media/platform/samsung/s3c-camif/camif-regs.c @@ -0,0 +1,603 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Samsung s3c24xx/s3c64xx SoC CAMIF driver + * + * Copyright (C) 2012 Sylwester Nawrocki + * Copyright (C) 2012 Tomasz Figa +*/ +#define pr_fmt(fmt) "%s:%d " fmt, __func__, __LINE__ + +#include +#include "camif-regs.h" + +#define camif_write(_camif, _off, _val) writel(_val, (_camif)->io_base + (_off)) +#define camif_read(_camif, _off) readl((_camif)->io_base + (_off)) + +void camif_hw_reset(struct camif_dev *camif) +{ + u32 cfg; + + cfg = camif_read(camif, S3C_CAMIF_REG_CISRCFMT); + cfg |= CISRCFMT_ITU601_8BIT; + camif_write(camif, S3C_CAMIF_REG_CISRCFMT, cfg); + + /* S/W reset */ + cfg = camif_read(camif, S3C_CAMIF_REG_CIGCTRL); + cfg |= CIGCTRL_SWRST; + if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV) + cfg |= CIGCTRL_IRQ_LEVEL; + camif_write(camif, S3C_CAMIF_REG_CIGCTRL, cfg); + udelay(10); + + cfg = camif_read(camif, S3C_CAMIF_REG_CIGCTRL); + cfg &= ~CIGCTRL_SWRST; + camif_write(camif, S3C_CAMIF_REG_CIGCTRL, cfg); + udelay(10); +} + +void camif_hw_clear_pending_irq(struct camif_vp *vp) +{ + u32 cfg = camif_read(vp->camif, S3C_CAMIF_REG_CIGCTRL); + cfg |= CIGCTRL_IRQ_CLR(vp->id); + camif_write(vp->camif, S3C_CAMIF_REG_CIGCTRL, cfg); +} + +/* + * Sets video test pattern (off, color bar, horizontal or vertical gradient). + * External sensor pixel clock must be active for the test pattern to work. + */ +void camif_hw_set_test_pattern(struct camif_dev *camif, unsigned int pattern) +{ + u32 cfg = camif_read(camif, S3C_CAMIF_REG_CIGCTRL); + cfg &= ~CIGCTRL_TESTPATTERN_MASK; + cfg |= (pattern << 27); + camif_write(camif, S3C_CAMIF_REG_CIGCTRL, cfg); +} + +void camif_hw_set_effect(struct camif_dev *camif, unsigned int effect, + unsigned int cr, unsigned int cb) +{ + static const struct v4l2_control colorfx[] = { + { V4L2_COLORFX_NONE, CIIMGEFF_FIN_BYPASS }, + { V4L2_COLORFX_BW, CIIMGEFF_FIN_ARBITRARY }, + { V4L2_COLORFX_SEPIA, CIIMGEFF_FIN_ARBITRARY }, + { V4L2_COLORFX_NEGATIVE, CIIMGEFF_FIN_NEGATIVE }, + { V4L2_COLORFX_ART_FREEZE, CIIMGEFF_FIN_ARTFREEZE }, + { V4L2_COLORFX_EMBOSS, CIIMGEFF_FIN_EMBOSSING }, + { V4L2_COLORFX_SILHOUETTE, CIIMGEFF_FIN_SILHOUETTE }, + { V4L2_COLORFX_SET_CBCR, CIIMGEFF_FIN_ARBITRARY }, + }; + unsigned int i, cfg; + + for (i = 0; i < ARRAY_SIZE(colorfx); i++) + if (colorfx[i].id == effect) + break; + + if (i == ARRAY_SIZE(colorfx)) + return; + + cfg = camif_read(camif, S3C_CAMIF_REG_CIIMGEFF(camif->vp->offset)); + /* Set effect */ + cfg &= ~CIIMGEFF_FIN_MASK; + cfg |= colorfx[i].value; + /* Set both paths */ + if (camif->variant->ip_revision >= S3C6400_CAMIF_IP_REV) { + if (effect == V4L2_COLORFX_NONE) + cfg &= ~CIIMGEFF_IE_ENABLE_MASK; + else + cfg |= CIIMGEFF_IE_ENABLE_MASK; + } + cfg &= ~CIIMGEFF_PAT_CBCR_MASK; + cfg |= cr | (cb << 13); + camif_write(camif, S3C_CAMIF_REG_CIIMGEFF(camif->vp->offset), cfg); +} + +static const u32 src_pixfmt_map[8][2] = { + { MEDIA_BUS_FMT_YUYV8_2X8, CISRCFMT_ORDER422_YCBYCR }, + { MEDIA_BUS_FMT_YVYU8_2X8, CISRCFMT_ORDER422_YCRYCB }, + { MEDIA_BUS_FMT_UYVY8_2X8, CISRCFMT_ORDER422_CBYCRY }, + { MEDIA_BUS_FMT_VYUY8_2X8, CISRCFMT_ORDER422_CRYCBY }, +}; + +/* Set camera input pixel format and resolution */ +void camif_hw_set_source_format(struct camif_dev *camif) +{ + struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt; + int i; + u32 cfg; + + for (i = ARRAY_SIZE(src_pixfmt_map) - 1; i >= 0; i--) { + if (src_pixfmt_map[i][0] == mf->code) + break; + } + if (i < 0) { + i = 0; + dev_err(camif->dev, + "Unsupported pixel code, falling back to %#08x\n", + src_pixfmt_map[i][0]); + } + + cfg = camif_read(camif, S3C_CAMIF_REG_CISRCFMT); + cfg &= ~(CISRCFMT_ORDER422_MASK | CISRCFMT_SIZE_CAM_MASK); + cfg |= (mf->width << 16) | mf->height; + cfg |= src_pixfmt_map[i][1]; + camif_write(camif, S3C_CAMIF_REG_CISRCFMT, cfg); +} + +/* Set the camera host input window offsets (cropping) */ +void camif_hw_set_camera_crop(struct camif_dev *camif) +{ + struct v4l2_mbus_framefmt *mf = &camif->mbus_fmt; + struct v4l2_rect *crop = &camif->camif_crop; + u32 hoff2, voff2; + u32 cfg; + + /* Note: s3c244x requirement: left = f_width - rect.width / 2 */ + cfg = camif_read(camif, S3C_CAMIF_REG_CIWDOFST); + cfg &= ~(CIWDOFST_OFST_MASK | CIWDOFST_WINOFSEN); + cfg |= (crop->left << 16) | crop->top; + if (crop->left != 0 || crop->top != 0) + cfg |= CIWDOFST_WINOFSEN; + camif_write(camif, S3C_CAMIF_REG_CIWDOFST, cfg); + + if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV) { + hoff2 = mf->width - crop->width - crop->left; + voff2 = mf->height - crop->height - crop->top; + cfg = (hoff2 << 16) | voff2; + camif_write(camif, S3C_CAMIF_REG_CIWDOFST2, cfg); + } +} + +void camif_hw_clear_fifo_overflow(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + u32 cfg; + + cfg = camif_read(camif, S3C_CAMIF_REG_CIWDOFST); + if (vp->id == 0) + cfg |= (CIWDOFST_CLROVCOFIY | CIWDOFST_CLROVCOFICB | + CIWDOFST_CLROVCOFICR); + else + cfg |= (/* CIWDOFST_CLROVPRFIY | */ CIWDOFST_CLROVPRFICB | + CIWDOFST_CLROVPRFICR); + camif_write(camif, S3C_CAMIF_REG_CIWDOFST, cfg); +} + +/* Set video bus signals polarity */ +void camif_hw_set_camera_bus(struct camif_dev *camif) +{ + unsigned int flags = camif->pdata.sensor.flags; + + u32 cfg = camif_read(camif, S3C_CAMIF_REG_CIGCTRL); + + cfg &= ~(CIGCTRL_INVPOLPCLK | CIGCTRL_INVPOLVSYNC | + CIGCTRL_INVPOLHREF | CIGCTRL_INVPOLFIELD); + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + cfg |= CIGCTRL_INVPOLPCLK; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + cfg |= CIGCTRL_INVPOLVSYNC; + /* + * HREF is normally high during frame active data + * transmission and low during horizontal synchronization + * period. Thus HREF active high means HSYNC active low. + */ + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + cfg |= CIGCTRL_INVPOLHREF; /* HREF active low */ + + if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV) { + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + cfg |= CIGCTRL_INVPOLFIELD; + cfg |= CIGCTRL_FIELDMODE; + } + + pr_debug("Setting CIGCTRL to: %#x\n", cfg); + + camif_write(camif, S3C_CAMIF_REG_CIGCTRL, cfg); +} + +void camif_hw_set_output_addr(struct camif_vp *vp, + struct camif_addr *paddr, int i) +{ + struct camif_dev *camif = vp->camif; + + camif_write(camif, S3C_CAMIF_REG_CIYSA(vp->id, i), paddr->y); + if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV + || vp->id == VP_CODEC) { + camif_write(camif, S3C_CAMIF_REG_CICBSA(vp->id, i), + paddr->cb); + camif_write(camif, S3C_CAMIF_REG_CICRSA(vp->id, i), + paddr->cr); + } + + pr_debug("dst_buf[%d]: %pad, cb: %pad, cr: %pad\n", + i, &paddr->y, &paddr->cb, &paddr->cr); +} + +static void camif_hw_set_out_dma_size(struct camif_vp *vp) +{ + struct camif_frame *frame = &vp->out_frame; + u32 cfg; + + cfg = camif_read(vp->camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset)); + cfg &= ~CITRGFMT_TARGETSIZE_MASK; + cfg |= (frame->f_width << 16) | frame->f_height; + camif_write(vp->camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset), cfg); +} + +static void camif_get_dma_burst(u32 width, u32 ybpp, u32 *mburst, u32 *rburst) +{ + unsigned int nwords = width * ybpp / 4; + unsigned int div, rem; + + if (WARN_ON(width < 8 || (width * ybpp) & 7)) + return; + + for (div = 16; div >= 2; div /= 2) { + if (nwords < div) + continue; + + rem = nwords & (div - 1); + if (rem == 0) { + *mburst = div; + *rburst = div; + break; + } + if (rem == div / 2 || rem == div / 4) { + *mburst = div; + *rburst = rem; + break; + } + } +} + +void camif_hw_set_output_dma(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + struct camif_frame *frame = &vp->out_frame; + const struct camif_fmt *fmt = vp->out_fmt; + unsigned int ymburst = 0, yrburst = 0; + u32 cfg; + + camif_hw_set_out_dma_size(vp); + + if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV) { + struct camif_dma_offset *offset = &frame->dma_offset; + /* Set the input dma offsets. */ + cfg = S3C_CISS_OFFS_INITIAL(offset->initial); + cfg |= S3C_CISS_OFFS_LINE(offset->line); + camif_write(camif, S3C_CAMIF_REG_CISSY(vp->id), cfg); + camif_write(camif, S3C_CAMIF_REG_CISSCB(vp->id), cfg); + camif_write(camif, S3C_CAMIF_REG_CISSCR(vp->id), cfg); + } + + /* Configure DMA burst values */ + camif_get_dma_burst(frame->rect.width, fmt->ybpp, &ymburst, &yrburst); + + cfg = camif_read(camif, S3C_CAMIF_REG_CICTRL(vp->id, vp->offset)); + cfg &= ~CICTRL_BURST_MASK; + + cfg |= CICTRL_YBURST1(ymburst) | CICTRL_YBURST2(yrburst); + cfg |= CICTRL_CBURST1(ymburst / 2) | CICTRL_CBURST2(yrburst / 2); + + camif_write(camif, S3C_CAMIF_REG_CICTRL(vp->id, vp->offset), cfg); + + pr_debug("ymburst: %u, yrburst: %u\n", ymburst, yrburst); +} + +void camif_hw_set_input_path(struct camif_vp *vp) +{ + u32 cfg = camif_read(vp->camif, S3C_CAMIF_REG_MSCTRL(vp->id)); + cfg &= ~MSCTRL_SEL_DMA_CAM; + camif_write(vp->camif, S3C_CAMIF_REG_MSCTRL(vp->id), cfg); +} + +void camif_hw_set_target_format(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + struct camif_frame *frame = &vp->out_frame; + u32 cfg; + + pr_debug("fw: %d, fh: %d color: %d\n", frame->f_width, + frame->f_height, vp->out_fmt->color); + + cfg = camif_read(camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset)); + cfg &= ~CITRGFMT_TARGETSIZE_MASK; + + if (camif->variant->ip_revision == S3C244X_CAMIF_IP_REV) { + /* We currently support only YCbCr 4:2:2 at the camera input */ + cfg |= CITRGFMT_IN422; + cfg &= ~CITRGFMT_OUT422; + if (vp->out_fmt->color == IMG_FMT_YCBCR422P) + cfg |= CITRGFMT_OUT422; + } else { + cfg &= ~CITRGFMT_OUTFORMAT_MASK; + switch (vp->out_fmt->color) { + case IMG_FMT_RGB565...IMG_FMT_XRGB8888: + cfg |= CITRGFMT_OUTFORMAT_RGB; + break; + case IMG_FMT_YCBCR420...IMG_FMT_YCRCB420: + cfg |= CITRGFMT_OUTFORMAT_YCBCR420; + break; + case IMG_FMT_YCBCR422P: + cfg |= CITRGFMT_OUTFORMAT_YCBCR422; + break; + case IMG_FMT_YCBYCR422...IMG_FMT_CRYCBY422: + cfg |= CITRGFMT_OUTFORMAT_YCBCR422I; + break; + } + } + + /* Rotation is only supported by s3c64xx */ + if (vp->rotation == 90 || vp->rotation == 270) + cfg |= (frame->f_height << 16) | frame->f_width; + else + cfg |= (frame->f_width << 16) | frame->f_height; + camif_write(camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset), cfg); + + /* Target area, output pixel width * height */ + cfg = camif_read(camif, S3C_CAMIF_REG_CITAREA(vp->id, vp->offset)); + cfg &= ~CITAREA_MASK; + cfg |= (frame->f_width * frame->f_height); + camif_write(camif, S3C_CAMIF_REG_CITAREA(vp->id, vp->offset), cfg); +} + +void camif_hw_set_flip(struct camif_vp *vp) +{ + u32 cfg = camif_read(vp->camif, + S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset)); + + cfg &= ~CITRGFMT_FLIP_MASK; + + if (vp->hflip) + cfg |= CITRGFMT_FLIP_Y_MIRROR; + if (vp->vflip) + cfg |= CITRGFMT_FLIP_X_MIRROR; + + camif_write(vp->camif, S3C_CAMIF_REG_CITRGFMT(vp->id, vp->offset), cfg); +} + +static void camif_hw_set_prescaler(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + struct camif_scaler *sc = &vp->scaler; + u32 cfg, shfactor, addr; + + addr = S3C_CAMIF_REG_CISCPRERATIO(vp->id, vp->offset); + + shfactor = 10 - (sc->h_shift + sc->v_shift); + cfg = shfactor << 28; + + cfg |= (sc->pre_h_ratio << 16) | sc->pre_v_ratio; + camif_write(camif, addr, cfg); + + cfg = (sc->pre_dst_width << 16) | sc->pre_dst_height; + camif_write(camif, S3C_CAMIF_REG_CISCPREDST(vp->id, vp->offset), cfg); +} + +static void camif_s3c244x_hw_set_scaler(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + struct camif_scaler *scaler = &vp->scaler; + unsigned int color = vp->out_fmt->color; + u32 cfg; + + camif_hw_set_prescaler(vp); + + cfg = camif_read(camif, S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset)); + + cfg &= ~(CISCCTRL_SCALEUP_MASK | CISCCTRL_SCALERBYPASS | + CISCCTRL_MAIN_RATIO_MASK | CIPRSCCTRL_RGB_FORMAT_24BIT); + + if (scaler->enable) { + if (scaler->scaleup_h) { + if (vp->id == VP_CODEC) + cfg |= CISCCTRL_SCALEUP_H; + else + cfg |= CIPRSCCTRL_SCALEUP_H; + } + if (scaler->scaleup_v) { + if (vp->id == VP_CODEC) + cfg |= CISCCTRL_SCALEUP_V; + else + cfg |= CIPRSCCTRL_SCALEUP_V; + } + } else { + if (vp->id == VP_CODEC) + cfg |= CISCCTRL_SCALERBYPASS; + } + + cfg |= ((scaler->main_h_ratio & 0x1ff) << 16); + cfg |= scaler->main_v_ratio & 0x1ff; + + if (vp->id == VP_PREVIEW) { + if (color == IMG_FMT_XRGB8888) + cfg |= CIPRSCCTRL_RGB_FORMAT_24BIT; + cfg |= CIPRSCCTRL_SAMPLE; + } + + camif_write(camif, S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset), cfg); + + pr_debug("main: h_ratio: %#x, v_ratio: %#x", + scaler->main_h_ratio, scaler->main_v_ratio); +} + +static void camif_s3c64xx_hw_set_scaler(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + struct camif_scaler *scaler = &vp->scaler; + unsigned int color = vp->out_fmt->color; + u32 cfg; + + camif_hw_set_prescaler(vp); + + cfg = camif_read(camif, S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset)); + + cfg &= ~(CISCCTRL_CSCR2Y_WIDE | CISCCTRL_CSCY2R_WIDE + | CISCCTRL_SCALEUP_H | CISCCTRL_SCALEUP_V + | CISCCTRL_SCALERBYPASS | CISCCTRL_ONE2ONE + | CISCCTRL_INRGB_FMT_MASK | CISCCTRL_OUTRGB_FMT_MASK + | CISCCTRL_INTERLACE | CISCCTRL_EXTRGB_EXTENSION + | CISCCTRL_MAIN_RATIO_MASK); + + cfg |= (CISCCTRL_CSCR2Y_WIDE | CISCCTRL_CSCY2R_WIDE); + + if (!scaler->enable) { + cfg |= CISCCTRL_SCALERBYPASS; + } else { + if (scaler->scaleup_h) + cfg |= CISCCTRL_SCALEUP_H; + if (scaler->scaleup_v) + cfg |= CISCCTRL_SCALEUP_V; + if (scaler->copy) + cfg |= CISCCTRL_ONE2ONE; + } + + switch (color) { + case IMG_FMT_RGB666: + cfg |= CISCCTRL_OUTRGB_FMT_RGB666; + break; + case IMG_FMT_XRGB8888: + cfg |= CISCCTRL_OUTRGB_FMT_RGB888; + break; + } + + cfg |= (scaler->main_h_ratio & 0x1ff) << 16; + cfg |= scaler->main_v_ratio & 0x1ff; + + camif_write(camif, S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset), cfg); + + pr_debug("main: h_ratio: %#x, v_ratio: %#x", + scaler->main_h_ratio, scaler->main_v_ratio); +} + +void camif_hw_set_scaler(struct camif_vp *vp) +{ + unsigned int ip_rev = vp->camif->variant->ip_revision; + + if (ip_rev == S3C244X_CAMIF_IP_REV) + camif_s3c244x_hw_set_scaler(vp); + else + camif_s3c64xx_hw_set_scaler(vp); +} + +void camif_hw_enable_scaler(struct camif_vp *vp, bool on) +{ + u32 addr = S3C_CAMIF_REG_CISCCTRL(vp->id, vp->offset); + u32 cfg; + + cfg = camif_read(vp->camif, addr); + if (on) + cfg |= CISCCTRL_SCALERSTART; + else + cfg &= ~CISCCTRL_SCALERSTART; + camif_write(vp->camif, addr, cfg); +} + +void camif_hw_set_lastirq(struct camif_vp *vp, int enable) +{ + u32 addr = S3C_CAMIF_REG_CICTRL(vp->id, vp->offset); + u32 cfg; + + cfg = camif_read(vp->camif, addr); + if (enable) + cfg |= CICTRL_LASTIRQ_ENABLE; + else + cfg &= ~CICTRL_LASTIRQ_ENABLE; + camif_write(vp->camif, addr, cfg); +} + +void camif_hw_enable_capture(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + u32 cfg; + + cfg = camif_read(camif, S3C_CAMIF_REG_CIIMGCPT(vp->offset)); + camif->stream_count++; + + if (camif->variant->ip_revision == S3C6410_CAMIF_IP_REV) + cfg |= CIIMGCPT_CPT_FREN_ENABLE(vp->id); + + if (vp->scaler.enable) + cfg |= CIIMGCPT_IMGCPTEN_SC(vp->id); + + if (camif->stream_count == 1) + cfg |= CIIMGCPT_IMGCPTEN; + + camif_write(camif, S3C_CAMIF_REG_CIIMGCPT(vp->offset), cfg); + + pr_debug("CIIMGCPT: %#x, camif->stream_count: %d\n", + cfg, camif->stream_count); +} + +void camif_hw_disable_capture(struct camif_vp *vp) +{ + struct camif_dev *camif = vp->camif; + u32 cfg; + + cfg = camif_read(camif, S3C_CAMIF_REG_CIIMGCPT(vp->offset)); + cfg &= ~CIIMGCPT_IMGCPTEN_SC(vp->id); + + if (WARN_ON(--(camif->stream_count) < 0)) + camif->stream_count = 0; + + if (camif->stream_count == 0) + cfg &= ~CIIMGCPT_IMGCPTEN; + + pr_debug("CIIMGCPT: %#x, camif->stream_count: %d\n", + cfg, camif->stream_count); + + camif_write(camif, S3C_CAMIF_REG_CIIMGCPT(vp->offset), cfg); +} + +void camif_hw_dump_regs(struct camif_dev *camif, const char *label) +{ + static const struct { + u32 offset; + const char * const name; + } registers[] = { + { S3C_CAMIF_REG_CISRCFMT, "CISRCFMT" }, + { S3C_CAMIF_REG_CIWDOFST, "CIWDOFST" }, + { S3C_CAMIF_REG_CIGCTRL, "CIGCTRL" }, + { S3C_CAMIF_REG_CIWDOFST2, "CIWDOFST2" }, + { S3C_CAMIF_REG_CIYSA(0, 0), "CICOYSA0" }, + { S3C_CAMIF_REG_CICBSA(0, 0), "CICOCBSA0" }, + { S3C_CAMIF_REG_CICRSA(0, 0), "CICOCRSA0" }, + { S3C_CAMIF_REG_CIYSA(0, 1), "CICOYSA1" }, + { S3C_CAMIF_REG_CICBSA(0, 1), "CICOCBSA1" }, + { S3C_CAMIF_REG_CICRSA(0, 1), "CICOCRSA1" }, + { S3C_CAMIF_REG_CIYSA(0, 2), "CICOYSA2" }, + { S3C_CAMIF_REG_CICBSA(0, 2), "CICOCBSA2" }, + { S3C_CAMIF_REG_CICRSA(0, 2), "CICOCRSA2" }, + { S3C_CAMIF_REG_CIYSA(0, 3), "CICOYSA3" }, + { S3C_CAMIF_REG_CICBSA(0, 3), "CICOCBSA3" }, + { S3C_CAMIF_REG_CICRSA(0, 3), "CICOCRSA3" }, + { S3C_CAMIF_REG_CIYSA(1, 0), "CIPRYSA0" }, + { S3C_CAMIF_REG_CIYSA(1, 1), "CIPRYSA1" }, + { S3C_CAMIF_REG_CIYSA(1, 2), "CIPRYSA2" }, + { S3C_CAMIF_REG_CIYSA(1, 3), "CIPRYSA3" }, + { S3C_CAMIF_REG_CITRGFMT(0, 0), "CICOTRGFMT" }, + { S3C_CAMIF_REG_CITRGFMT(1, 0), "CIPRTRGFMT" }, + { S3C_CAMIF_REG_CICTRL(0, 0), "CICOCTRL" }, + { S3C_CAMIF_REG_CICTRL(1, 0), "CIPRCTRL" }, + { S3C_CAMIF_REG_CISCPREDST(0, 0), "CICOSCPREDST" }, + { S3C_CAMIF_REG_CISCPREDST(1, 0), "CIPRSCPREDST" }, + { S3C_CAMIF_REG_CISCPRERATIO(0, 0), "CICOSCPRERATIO" }, + { S3C_CAMIF_REG_CISCPRERATIO(1, 0), "CIPRSCPRERATIO" }, + { S3C_CAMIF_REG_CISCCTRL(0, 0), "CICOSCCTRL" }, + { S3C_CAMIF_REG_CISCCTRL(1, 0), "CIPRSCCTRL" }, + { S3C_CAMIF_REG_CITAREA(0, 0), "CICOTAREA" }, + { S3C_CAMIF_REG_CITAREA(1, 0), "CIPRTAREA" }, + { S3C_CAMIF_REG_CISTATUS(0, 0), "CICOSTATUS" }, + { S3C_CAMIF_REG_CISTATUS(1, 0), "CIPRSTATUS" }, + { S3C_CAMIF_REG_CIIMGCPT(0), "CIIMGCPT" }, + }; + u32 i; + + pr_info("--- %s ---\n", label); + for (i = 0; i < ARRAY_SIZE(registers); i++) { + u32 cfg = readl(camif->io_base + registers[i].offset); + dev_info(camif->dev, "%s:\t0x%08x\n", registers[i].name, cfg); + } +} diff --git a/drivers/media/platform/samsung/s3c-camif/camif-regs.h b/drivers/media/platform/samsung/s3c-camif/camif-regs.h new file mode 100644 index 000000000..052948a7b --- /dev/null +++ b/drivers/media/platform/samsung/s3c-camif/camif-regs.h @@ -0,0 +1,268 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definition file for s3c24xx/s3c64xx SoC CAMIF driver + * + * Copyright (C) 2012 Sylwester Nawrocki + * Copyright (C) 2012 Tomasz Figa +*/ + +#ifndef CAMIF_REGS_H_ +#define CAMIF_REGS_H_ + +#include + +#include "camif-core.h" +#include + +/* + * The id argument indicates the processing path: + * id = 0 - codec (FIMC C), 1 - preview (FIMC P). + */ + +/* Camera input format */ +#define S3C_CAMIF_REG_CISRCFMT 0x00 +#define CISRCFMT_ITU601_8BIT BIT(31) +#define CISRCFMT_ITU656_8BIT (0 << 31) +#define CISRCFMT_ORDER422_YCBYCR (0 << 14) +#define CISRCFMT_ORDER422_YCRYCB (1 << 14) +#define CISRCFMT_ORDER422_CBYCRY (2 << 14) +#define CISRCFMT_ORDER422_CRYCBY (3 << 14) +#define CISRCFMT_ORDER422_MASK (3 << 14) +#define CISRCFMT_SIZE_CAM_MASK (0x1fff << 16 | 0x1fff) + +/* Window offset */ +#define S3C_CAMIF_REG_CIWDOFST 0x04 +#define CIWDOFST_WINOFSEN BIT(31) +#define CIWDOFST_CLROVCOFIY BIT(30) +#define CIWDOFST_CLROVRLB_PR BIT(28) +/* #define CIWDOFST_CLROVPRFIY BIT(27) */ +#define CIWDOFST_CLROVCOFICB BIT(15) +#define CIWDOFST_CLROVCOFICR BIT(14) +#define CIWDOFST_CLROVPRFICB BIT(13) +#define CIWDOFST_CLROVPRFICR BIT(12) +#define CIWDOFST_OFST_MASK (0x7ff << 16 | 0x7ff) + +/* Window offset 2 */ +#define S3C_CAMIF_REG_CIWDOFST2 0x14 +#define CIWDOFST2_OFST2_MASK (0xfff << 16 | 0xfff) + +/* Global control */ +#define S3C_CAMIF_REG_CIGCTRL 0x08 +#define CIGCTRL_SWRST BIT(31) +#define CIGCTRL_CAMRST BIT(30) +#define CIGCTRL_TESTPATTERN_NORMAL (0 << 27) +#define CIGCTRL_TESTPATTERN_COLOR_BAR (1 << 27) +#define CIGCTRL_TESTPATTERN_HOR_INC (2 << 27) +#define CIGCTRL_TESTPATTERN_VER_INC (3 << 27) +#define CIGCTRL_TESTPATTERN_MASK (3 << 27) +#define CIGCTRL_INVPOLPCLK BIT(26) +#define CIGCTRL_INVPOLVSYNC BIT(25) +#define CIGCTRL_INVPOLHREF BIT(24) +#define CIGCTRL_IRQ_OVFEN BIT(22) +#define CIGCTRL_HREF_MASK BIT(21) +#define CIGCTRL_IRQ_LEVEL BIT(20) +/* IRQ_CLR_C, IRQ_CLR_P */ +#define CIGCTRL_IRQ_CLR(id) BIT(19 - (id)) +#define CIGCTRL_FIELDMODE BIT(2) +#define CIGCTRL_INVPOLFIELD BIT(1) +#define CIGCTRL_CAM_INTERLACE BIT(0) + +/* Y DMA output frame start address. n = 0..3. */ +#define S3C_CAMIF_REG_CIYSA(id, n) (0x18 + (id) * 0x54 + (n) * 4) +/* Cb plane output DMA start address. n = 0..3. Only codec path. */ +#define S3C_CAMIF_REG_CICBSA(id, n) (0x28 + (id) * 0x54 + (n) * 4) +/* Cr plane output DMA start address. n = 0..3. Only codec path. */ +#define S3C_CAMIF_REG_CICRSA(id, n) (0x38 + (id) * 0x54 + (n) * 4) + +/* CICOTRGFMT, CIPRTRGFMT - Target format */ +#define S3C_CAMIF_REG_CITRGFMT(id, _offs) (0x48 + (id) * (0x34 + (_offs))) +#define CITRGFMT_IN422 BIT(31) /* only for s3c24xx */ +#define CITRGFMT_OUT422 BIT(30) /* only for s3c24xx */ +#define CITRGFMT_OUTFORMAT_YCBCR420 (0 << 29) /* only for s3c6410 */ +#define CITRGFMT_OUTFORMAT_YCBCR422 (1 << 29) /* only for s3c6410 */ +#define CITRGFMT_OUTFORMAT_YCBCR422I (2 << 29) /* only for s3c6410 */ +#define CITRGFMT_OUTFORMAT_RGB (3 << 29) /* only for s3c6410 */ +#define CITRGFMT_OUTFORMAT_MASK (3 << 29) /* only for s3c6410 */ +#define CITRGFMT_TARGETHSIZE(x) ((x) << 16) +#define CITRGFMT_FLIP_NORMAL (0 << 14) +#define CITRGFMT_FLIP_X_MIRROR (1 << 14) +#define CITRGFMT_FLIP_Y_MIRROR (2 << 14) +#define CITRGFMT_FLIP_180 (3 << 14) +#define CITRGFMT_FLIP_MASK (3 << 14) +/* Preview path only */ +#define CITRGFMT_ROT90_PR BIT(13) +#define CITRGFMT_TARGETVSIZE(x) ((x) << 0) +#define CITRGFMT_TARGETSIZE_MASK ((0x1fff << 16) | 0x1fff) + +/* CICOCTRL, CIPRCTRL. Output DMA control. */ +#define S3C_CAMIF_REG_CICTRL(id, _offs) (0x4c + (id) * (0x34 + (_offs))) +#define CICTRL_BURST_MASK (0xfffff << 4) +/* xBURSTn - 5-bits width */ +#define CICTRL_YBURST1(x) ((x) << 19) +#define CICTRL_YBURST2(x) ((x) << 14) +#define CICTRL_RGBBURST1(x) ((x) << 19) +#define CICTRL_RGBBURST2(x) ((x) << 14) +#define CICTRL_CBURST1(x) ((x) << 9) +#define CICTRL_CBURST2(x) ((x) << 4) +#define CICTRL_LASTIRQ_ENABLE BIT(2) +#define CICTRL_ORDER422_MASK (3 << 0) + +/* CICOSCPRERATIO, CIPRSCPRERATIO. Pre-scaler control 1. */ +#define S3C_CAMIF_REG_CISCPRERATIO(id, _offs) (0x50 + (id) * (0x34 + (_offs))) + +/* CICOSCPREDST, CIPRSCPREDST. Pre-scaler control 2. */ +#define S3C_CAMIF_REG_CISCPREDST(id, _offs) (0x54 + (id) * (0x34 + (_offs))) + +/* CICOSCCTRL, CIPRSCCTRL. Main scaler control. */ +#define S3C_CAMIF_REG_CISCCTRL(id, _offs) (0x58 + (id) * (0x34 + (_offs))) +#define CISCCTRL_SCALERBYPASS BIT(31) +/* s3c244x preview path only, s3c64xx both */ +#define CIPRSCCTRL_SAMPLE BIT(31) +/* 0 - 16-bit RGB, 1 - 24-bit RGB */ +#define CIPRSCCTRL_RGB_FORMAT_24BIT BIT(30) /* only for s3c244x */ +#define CIPRSCCTRL_SCALEUP_H BIT(29) /* only for s3c244x */ +#define CIPRSCCTRL_SCALEUP_V BIT(28) /* only for s3c244x */ +/* s3c64xx */ +#define CISCCTRL_SCALEUP_H BIT(30) +#define CISCCTRL_SCALEUP_V BIT(29) +#define CISCCTRL_SCALEUP_MASK (0x3 << 29) +#define CISCCTRL_CSCR2Y_WIDE BIT(28) +#define CISCCTRL_CSCY2R_WIDE BIT(27) +#define CISCCTRL_LCDPATHEN_FIFO BIT(26) +#define CISCCTRL_INTERLACE BIT(25) +#define CISCCTRL_SCALERSTART BIT(15) +#define CISCCTRL_INRGB_FMT_RGB565 (0 << 13) +#define CISCCTRL_INRGB_FMT_RGB666 (1 << 13) +#define CISCCTRL_INRGB_FMT_RGB888 (2 << 13) +#define CISCCTRL_INRGB_FMT_MASK (3 << 13) +#define CISCCTRL_OUTRGB_FMT_RGB565 (0 << 11) +#define CISCCTRL_OUTRGB_FMT_RGB666 (1 << 11) +#define CISCCTRL_OUTRGB_FMT_RGB888 (2 << 11) +#define CISCCTRL_OUTRGB_FMT_MASK (3 << 11) +#define CISCCTRL_EXTRGB_EXTENSION BIT(10) +#define CISCCTRL_ONE2ONE BIT(9) +#define CISCCTRL_MAIN_RATIO_MASK (0x1ff << 16 | 0x1ff) + +/* CICOTAREA, CIPRTAREA. Target area for DMA (Hsize x Vsize). */ +#define S3C_CAMIF_REG_CITAREA(id, _offs) (0x5c + (id) * (0x34 + (_offs))) +#define CITAREA_MASK 0xfffffff + +/* Codec (id = 0) or preview (id = 1) path status. */ +#define S3C_CAMIF_REG_CISTATUS(id, _offs) (0x64 + (id) * (0x34 + (_offs))) +#define CISTATUS_OVFIY_STATUS BIT(31) +#define CISTATUS_OVFICB_STATUS BIT(30) +#define CISTATUS_OVFICR_STATUS BIT(29) +#define CISTATUS_OVF_MASK (0x7 << 29) +#define CIPRSTATUS_OVF_MASK (0x3 << 30) +#define CISTATUS_VSYNC_STATUS BIT(28) +#define CISTATUS_FRAMECNT_MASK (3 << 26) +#define CISTATUS_FRAMECNT(__reg) (((__reg) >> 26) & 0x3) +#define CISTATUS_WINOFSTEN_STATUS BIT(25) +#define CISTATUS_IMGCPTEN_STATUS BIT(22) +#define CISTATUS_IMGCPTENSC_STATUS BIT(21) +#define CISTATUS_VSYNC_A_STATUS BIT(20) +#define CISTATUS_FRAMEEND_STATUS BIT(19) /* 17 on s3c64xx */ + +/* Image capture enable */ +#define S3C_CAMIF_REG_CIIMGCPT(_offs) (0xa0 + (_offs)) +#define CIIMGCPT_IMGCPTEN BIT(31) +#define CIIMGCPT_IMGCPTEN_SC(id) BIT(30 - (id)) +/* Frame control: 1 - one-shot, 0 - free run */ +#define CIIMGCPT_CPT_FREN_ENABLE(id) BIT(25 - (id)) +#define CIIMGCPT_CPT_FRMOD_ENABLE (0 << 18) +#define CIIMGCPT_CPT_FRMOD_CNT BIT(18) + +/* Capture sequence */ +#define S3C_CAMIF_REG_CICPTSEQ 0xc4 + +/* Image effects */ +#define S3C_CAMIF_REG_CIIMGEFF(_offs) (0xb0 + (_offs)) +#define CIIMGEFF_IE_ENABLE(id) BIT(30 + (id)) +#define CIIMGEFF_IE_ENABLE_MASK (3 << 30) +/* Image effect: 1 - after scaler, 0 - before scaler */ +#define CIIMGEFF_IE_AFTER_SC BIT(29) +#define CIIMGEFF_FIN_MASK (7 << 26) +#define CIIMGEFF_FIN_BYPASS (0 << 26) +#define CIIMGEFF_FIN_ARBITRARY (1 << 26) +#define CIIMGEFF_FIN_NEGATIVE (2 << 26) +#define CIIMGEFF_FIN_ARTFREEZE (3 << 26) +#define CIIMGEFF_FIN_EMBOSSING (4 << 26) +#define CIIMGEFF_FIN_SILHOUETTE (5 << 26) +#define CIIMGEFF_PAT_CBCR_MASK ((0xff << 13) | 0xff) +#define CIIMGEFF_PAT_CB(x) ((x) << 13) +#define CIIMGEFF_PAT_CR(x) (x) + +/* MSCOY0SA, MSPRY0SA. Y/Cb/Cr frame start address for input DMA. */ +#define S3C_CAMIF_REG_MSY0SA(id) (0xd4 + ((id) * 0x2c)) +#define S3C_CAMIF_REG_MSCB0SA(id) (0xd8 + ((id) * 0x2c)) +#define S3C_CAMIF_REG_MSCR0SA(id) (0xdc + ((id) * 0x2c)) + +/* MSCOY0END, MSCOY0END. Y/Cb/Cr frame end address for input DMA. */ +#define S3C_CAMIF_REG_MSY0END(id) (0xe0 + ((id) * 0x2c)) +#define S3C_CAMIF_REG_MSCB0END(id) (0xe4 + ((id) * 0x2c)) +#define S3C_CAMIF_REG_MSCR0END(id) (0xe8 + ((id) * 0x2c)) + +/* MSPRYOFF, MSPRYOFF. Y/Cb/Cr offset. n: 0 - codec, 1 - preview. */ +#define S3C_CAMIF_REG_MSYOFF(id) (0x118 + ((id) * 0x2c)) +#define S3C_CAMIF_REG_MSCBOFF(id) (0x11c + ((id) * 0x2c)) +#define S3C_CAMIF_REG_MSCROFF(id) (0x120 + ((id) * 0x2c)) + +/* Real input DMA data size. n = 0 - codec, 1 - preview. */ +#define S3C_CAMIF_REG_MSWIDTH(id) (0xf8 + (id) * 0x2c) +#define AUTOLOAD_ENABLE BIT(31) +#define ADDR_CH_DIS BIT(30) +#define MSHEIGHT(x) (((x) & 0x3ff) << 16) +#define MSWIDTH(x) ((x) & 0x3ff) + +/* Input DMA control. n = 0 - codec, 1 - preview */ +#define S3C_CAMIF_REG_MSCTRL(id) (0xfc + (id) * 0x2c) +#define MSCTRL_ORDER422_M_YCBYCR (0 << 4) +#define MSCTRL_ORDER422_M_YCRYCB (1 << 4) +#define MSCTRL_ORDER422_M_CBYCRY (2 << 4) +#define MSCTRL_ORDER422_M_CRYCBY (3 << 4) +/* 0 - camera, 1 - DMA */ +#define MSCTRL_SEL_DMA_CAM BIT(3) +#define MSCTRL_INFORMAT_M_YCBCR420 (0 << 1) +#define MSCTRL_INFORMAT_M_YCBCR422 (1 << 1) +#define MSCTRL_INFORMAT_M_YCBCR422I (2 << 1) +#define MSCTRL_INFORMAT_M_RGB (3 << 1) +#define MSCTRL_ENVID_M BIT(0) + +/* CICOSCOSY, CIPRSCOSY. Scan line Y/Cb/Cr offset. */ +#define S3C_CAMIF_REG_CISSY(id) (0x12c + (id) * 0x0c) +#define S3C_CAMIF_REG_CISSCB(id) (0x130 + (id) * 0x0c) +#define S3C_CAMIF_REG_CISSCR(id) (0x134 + (id) * 0x0c) +#define S3C_CISS_OFFS_INITIAL(x) ((x) << 16) +#define S3C_CISS_OFFS_LINE(x) ((x) << 0) + +/* ------------------------------------------------------------------ */ + +void camif_hw_reset(struct camif_dev *camif); +void camif_hw_clear_pending_irq(struct camif_vp *vp); +void camif_hw_clear_fifo_overflow(struct camif_vp *vp); +void camif_hw_set_lastirq(struct camif_vp *vp, int enable); +void camif_hw_set_input_path(struct camif_vp *vp); +void camif_hw_enable_scaler(struct camif_vp *vp, bool on); +void camif_hw_enable_capture(struct camif_vp *vp); +void camif_hw_disable_capture(struct camif_vp *vp); +void camif_hw_set_camera_bus(struct camif_dev *camif); +void camif_hw_set_source_format(struct camif_dev *camif); +void camif_hw_set_camera_crop(struct camif_dev *camif); +void camif_hw_set_scaler(struct camif_vp *vp); +void camif_hw_set_flip(struct camif_vp *vp); +void camif_hw_set_output_dma(struct camif_vp *vp); +void camif_hw_set_target_format(struct camif_vp *vp); +void camif_hw_set_test_pattern(struct camif_dev *camif, unsigned int pattern); +void camif_hw_set_effect(struct camif_dev *camif, unsigned int effect, + unsigned int cr, unsigned int cb); +void camif_hw_set_output_addr(struct camif_vp *vp, struct camif_addr *paddr, + int index); +void camif_hw_dump_regs(struct camif_dev *camif, const char *label); + +static inline u32 camif_hw_get_status(struct camif_vp *vp) +{ + return readl(vp->camif->io_base + S3C_CAMIF_REG_CISTATUS(vp->id, + vp->offset)); +} + +#endif /* CAMIF_REGS_H_ */ diff --git a/drivers/media/platform/samsung/s5p-g2d/Kconfig b/drivers/media/platform/samsung/s5p-g2d/Kconfig new file mode 100644 index 000000000..28ab88fc2 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-g2d/Kconfig @@ -0,0 +1,11 @@ +config VIDEO_SAMSUNG_S5P_G2D + tristate "Samsung S5P and EXYNOS4 G2D 2d graphics accelerator driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a v4l2 driver for Samsung S5P and EXYNOS4 G2D + 2d graphics accelerator. + diff --git a/drivers/media/platform/samsung/s5p-g2d/Makefile b/drivers/media/platform/samsung/s5p-g2d/Makefile new file mode 100644 index 000000000..ad2c5bf66 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-g2d/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +s5p-g2d-objs := g2d.o g2d-hw.o + +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_G2D) += s5p-g2d.o diff --git a/drivers/media/platform/samsung/s5p-g2d/g2d-hw.c b/drivers/media/platform/samsung/s5p-g2d/g2d-hw.c new file mode 100644 index 000000000..b69d3fb12 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-g2d/g2d-hw.c @@ -0,0 +1,113 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Samsung S5P G2D - 2D Graphics Accelerator Driver + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#include + +#include "g2d.h" +#include "g2d-regs.h" + +#define w(x, a) writel((x), d->regs + (a)) +#define r(a) readl(d->regs + (a)) + +/* g2d_reset clears all g2d registers */ +void g2d_reset(struct g2d_dev *d) +{ + w(1, SOFT_RESET_REG); +} + +void g2d_set_src_size(struct g2d_dev *d, struct g2d_frame *f) +{ + u32 n; + + w(0, SRC_SELECT_REG); + w(f->stride & 0xFFFF, SRC_STRIDE_REG); + + n = f->o_height & 0xFFF; + n <<= 16; + n |= f->o_width & 0xFFF; + w(n, SRC_LEFT_TOP_REG); + + n = f->bottom & 0xFFF; + n <<= 16; + n |= f->right & 0xFFF; + w(n, SRC_RIGHT_BOTTOM_REG); + + w(f->fmt->hw, SRC_COLOR_MODE_REG); +} + +void g2d_set_src_addr(struct g2d_dev *d, dma_addr_t a) +{ + w(a, SRC_BASE_ADDR_REG); +} + +void g2d_set_dst_size(struct g2d_dev *d, struct g2d_frame *f) +{ + u32 n; + + w(0, DST_SELECT_REG); + w(f->stride & 0xFFFF, DST_STRIDE_REG); + + n = f->o_height & 0xFFF; + n <<= 16; + n |= f->o_width & 0xFFF; + w(n, DST_LEFT_TOP_REG); + + n = f->bottom & 0xFFF; + n <<= 16; + n |= f->right & 0xFFF; + w(n, DST_RIGHT_BOTTOM_REG); + + w(f->fmt->hw, DST_COLOR_MODE_REG); +} + +void g2d_set_dst_addr(struct g2d_dev *d, dma_addr_t a) +{ + w(a, DST_BASE_ADDR_REG); +} + +void g2d_set_rop4(struct g2d_dev *d, u32 r) +{ + w(r, ROP4_REG); +} + +void g2d_set_flip(struct g2d_dev *d, u32 r) +{ + w(r, SRC_MSK_DIRECT_REG); +} + +void g2d_set_v41_stretch(struct g2d_dev *d, struct g2d_frame *src, + struct g2d_frame *dst) +{ + w(DEFAULT_SCALE_MODE, SRC_SCALE_CTRL_REG); + + /* inversed scaling factor: src is numerator */ + w((src->c_width << 16) / dst->c_width, SRC_XSCALE_REG); + w((src->c_height << 16) / dst->c_height, SRC_YSCALE_REG); +} + +void g2d_set_cmd(struct g2d_dev *d, u32 c) +{ + w(c, BITBLT_COMMAND_REG); +} + +void g2d_start(struct g2d_dev *d) +{ + /* Clear cache */ + if (d->variant->hw_rev == TYPE_G2D_3X) + w(0x7, CACHECTL_REG); + + /* Enable interrupt */ + w(1, INTEN_REG); + /* Start G2D engine */ + w(1, BITBLT_START_REG); +} + +void g2d_clear_int(struct g2d_dev *d) +{ + w(1, INTC_PEND_REG); +} diff --git a/drivers/media/platform/samsung/s5p-g2d/g2d-regs.h b/drivers/media/platform/samsung/s5p-g2d/g2d-regs.h new file mode 100644 index 000000000..b2630c613 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-g2d/g2d-regs.h @@ -0,0 +1,118 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Samsung S5P G2D - 2D Graphics Accelerator Driver + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +/* General Registers */ +#define SOFT_RESET_REG 0x0000 /* Software reset reg */ +#define INTEN_REG 0x0004 /* Interrupt Enable reg */ +#define INTC_PEND_REG 0x000C /* Interrupt Control Pending reg */ +#define FIFO_STAT_REG 0x0010 /* Command FIFO Status reg */ +#define AXI_ID_MODE_REG 0x0014 /* AXI Read ID Mode reg */ +#define CACHECTL_REG 0x0018 /* Cache & Buffer clear reg */ +#define AXI_MODE_REG 0x001C /* AXI Mode reg */ + +/* Command Registers */ +#define BITBLT_START_REG 0x0100 /* BitBLT Start reg */ +#define BITBLT_COMMAND_REG 0x0104 /* Command reg for BitBLT */ + +/* Parameter Setting Registers (Rotate & Direction) */ +#define ROTATE_REG 0x0200 /* Rotation reg */ +#define SRC_MSK_DIRECT_REG 0x0204 /* Src and Mask Direction reg */ +#define DST_PAT_DIRECT_REG 0x0208 /* Dest and Pattern Direction reg */ + +/* Parameter Setting Registers (Src) */ +#define SRC_SELECT_REG 0x0300 /* Src Image Selection reg */ +#define SRC_BASE_ADDR_REG 0x0304 /* Src Image Base Address reg */ +#define SRC_STRIDE_REG 0x0308 /* Src Stride reg */ +#define SRC_COLOR_MODE_REG 0x030C /* Src Image Color Mode reg */ +#define SRC_LEFT_TOP_REG 0x0310 /* Src Left Top Coordinate reg */ +#define SRC_RIGHT_BOTTOM_REG 0x0314 /* Src Right Bottom Coordinate reg */ +#define SRC_SCALE_CTRL_REG 0x0328 /* Src Scaling type select */ +#define SRC_XSCALE_REG 0x032c /* Src X Scaling ratio */ +#define SRC_YSCALE_REG 0x0330 /* Src Y Scaling ratio */ + +/* Parameter Setting Registers (Dest) */ +#define DST_SELECT_REG 0x0400 /* Dest Image Selection reg */ +#define DST_BASE_ADDR_REG 0x0404 /* Dest Image Base Address reg */ +#define DST_STRIDE_REG 0x0408 /* Dest Stride reg */ +#define DST_COLOR_MODE_REG 0x040C /* Dest Image Color Mode reg */ +#define DST_LEFT_TOP_REG 0x0410 /* Dest Left Top Coordinate reg */ +#define DST_RIGHT_BOTTOM_REG 0x0414 /* Dest Right Bottom Coordinate reg */ + +/* Parameter Setting Registers (Pattern) */ +#define PAT_BASE_ADDR_REG 0x0500 /* Pattern Image Base Address reg */ +#define PAT_SIZE_REG 0x0504 /* Pattern Image Size reg */ +#define PAT_COLOR_MODE_REG 0x0508 /* Pattern Image Color Mode reg */ +#define PAT_OFFSET_REG 0x050C /* Pattern Left Top Coordinate reg */ +#define PAT_STRIDE_REG 0x0510 /* Pattern Stride reg */ + +/* Parameter Setting Registers (Mask) */ +#define MASK_BASE_ADDR_REG 0x0520 /* Mask Base Address reg */ +#define MASK_STRIDE_REG 0x0524 /* Mask Stride reg */ + +/* Parameter Setting Registers (Clipping Window) */ +#define CW_LT_REG 0x0600 /* LeftTop coordinates of Clip Window */ +#define CW_RB_REG 0x0604 /* RightBottom coordinates of Clip + Window */ + +/* Parameter Setting Registers (ROP & Alpha Setting) */ +#define THIRD_OPERAND_REG 0x0610 /* Third Operand Selection reg */ +#define ROP4_REG 0x0614 /* Raster Operation reg */ +#define ALPHA_REG 0x0618 /* Alpha value, Fading offset value */ + +/* Parameter Setting Registers (Color) */ +#define FG_COLOR_REG 0x0700 /* Foreground Color reg */ +#define BG_COLOR_REG 0x0704 /* Background Color reg */ +#define BS_COLOR_REG 0x0708 /* Blue Screen Color reg */ + +/* Parameter Setting Registers (Color Key) */ +#define SRC_COLORKEY_CTRL_REG 0x0710 /* Src Colorkey control reg */ +#define SRC_COLORKEY_DR_MIN_REG 0x0714 /* Src Colorkey Decision Reference + Min reg */ +#define SRC_COLORKEY_DR_MAX_REG 0x0718 /* Src Colorkey Decision Reference + Max reg */ +#define DST_COLORKEY_CTRL_REG 0x071C /* Dest Colorkey control reg */ +#define DST_COLORKEY_DR_MIN_REG 0x0720 /* Dest Colorkey Decision Reference + Min reg */ +#define DST_COLORKEY_DR_MAX_REG 0x0724 /* Dest Colorkey Decision Reference + Max reg */ + +/* Color mode values */ + +#define ORDER_XRGB 0 +#define ORDER_RGBX 1 +#define ORDER_XBGR 2 +#define ORDER_BGRX 3 + +#define MODE_XRGB_8888 0 +#define MODE_ARGB_8888 1 +#define MODE_RGB_565 2 +#define MODE_XRGB_1555 3 +#define MODE_ARGB_1555 4 +#define MODE_XRGB_4444 5 +#define MODE_ARGB_4444 6 +#define MODE_PACKED_RGB_888 7 + +#define COLOR_MODE(o, m) (((o) << 4) | (m)) + +/* ROP4 operation values */ +#define ROP4_COPY 0xCCCC +#define ROP4_INVERT 0x3333 + +/* Hardware limits */ +#define MAX_WIDTH 8000 +#define MAX_HEIGHT 8000 + +#define G2D_TIMEOUT 500 + +#define DEFAULT_WIDTH 100 +#define DEFAULT_HEIGHT 100 + +#define DEFAULT_SCALE_MODE (2 << 0) + +/* Command mode register values */ +#define CMD_V3_ENABLE_STRETCH (1 << 4) diff --git a/drivers/media/platform/samsung/s5p-g2d/g2d.c b/drivers/media/platform/samsung/s5p-g2d/g2d.c new file mode 100644 index 000000000..dd8864779 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-g2d/g2d.c @@ -0,0 +1,792 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Samsung S5P G2D - 2D Graphics Accelerator Driver + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "g2d.h" +#include "g2d-regs.h" + +#define fh2ctx(__fh) container_of(__fh, struct g2d_ctx, fh) + +static struct g2d_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .hw = COLOR_MODE(ORDER_XRGB, MODE_XRGB_8888), + }, + { + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .hw = COLOR_MODE(ORDER_XRGB, MODE_RGB_565), + }, + { + .fourcc = V4L2_PIX_FMT_RGB555X, + .depth = 16, + .hw = COLOR_MODE(ORDER_XRGB, MODE_XRGB_1555), + }, + { + .fourcc = V4L2_PIX_FMT_RGB444, + .depth = 16, + .hw = COLOR_MODE(ORDER_XRGB, MODE_XRGB_4444), + }, + { + .fourcc = V4L2_PIX_FMT_RGB24, + .depth = 24, + .hw = COLOR_MODE(ORDER_XRGB, MODE_PACKED_RGB_888), + }, +}; +#define NUM_FORMATS ARRAY_SIZE(formats) + +static struct g2d_frame def_frame = { + .width = DEFAULT_WIDTH, + .height = DEFAULT_HEIGHT, + .c_width = DEFAULT_WIDTH, + .c_height = DEFAULT_HEIGHT, + .o_width = 0, + .o_height = 0, + .fmt = &formats[0], + .right = DEFAULT_WIDTH, + .bottom = DEFAULT_HEIGHT, +}; + +static struct g2d_fmt *find_fmt(struct v4l2_format *f) +{ + unsigned int i; + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].fourcc == f->fmt.pix.pixelformat) + return &formats[i]; + } + return NULL; +} + + +static struct g2d_frame *get_frame(struct g2d_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &ctx->in; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &ctx->out; + default: + return ERR_PTR(-EINVAL); + } +} + +static int g2d_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct g2d_ctx *ctx = vb2_get_drv_priv(vq); + struct g2d_frame *f = get_frame(ctx, vq->type); + + if (IS_ERR(f)) + return PTR_ERR(f); + + sizes[0] = f->size; + *nplanes = 1; + + if (*nbuffers == 0) + *nbuffers = 1; + + return 0; +} + +static int g2d_buf_prepare(struct vb2_buffer *vb) +{ + struct g2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct g2d_frame *f = get_frame(ctx, vb->vb2_queue->type); + + if (IS_ERR(f)) + return PTR_ERR(f); + vb2_set_plane_payload(vb, 0, f->size); + return 0; +} + +static void g2d_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct g2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static const struct vb2_ops g2d_qops = { + .queue_setup = g2d_queue_setup, + .buf_prepare = g2d_buf_prepare, + .buf_queue = g2d_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct g2d_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->ops = &g2d_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->mutex; + src_vq->dev = ctx->dev->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->ops = &g2d_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->mutex; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +static int g2d_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct g2d_ctx *ctx = container_of(ctrl->handler, struct g2d_ctx, + ctrl_handler); + unsigned long flags; + + spin_lock_irqsave(&ctx->dev->ctrl_lock, flags); + switch (ctrl->id) { + case V4L2_CID_COLORFX: + if (ctrl->val == V4L2_COLORFX_NEGATIVE) + ctx->rop = ROP4_INVERT; + else + ctx->rop = ROP4_COPY; + break; + + case V4L2_CID_HFLIP: + ctx->flip = ctx->ctrl_hflip->val | (ctx->ctrl_vflip->val << 1); + break; + + } + spin_unlock_irqrestore(&ctx->dev->ctrl_lock, flags); + return 0; +} + +static const struct v4l2_ctrl_ops g2d_ctrl_ops = { + .s_ctrl = g2d_s_ctrl, +}; + +static int g2d_setup_ctrls(struct g2d_ctx *ctx) +{ + struct g2d_dev *dev = ctx->dev; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3); + + ctx->ctrl_hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, &g2d_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + ctx->ctrl_vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, &g2d_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std_menu( + &ctx->ctrl_handler, + &g2d_ctrl_ops, + V4L2_CID_COLORFX, + V4L2_COLORFX_NEGATIVE, + ~((1 << V4L2_COLORFX_NONE) | (1 << V4L2_COLORFX_NEGATIVE)), + V4L2_COLORFX_NONE); + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + v4l2_err(&dev->v4l2_dev, "g2d_setup_ctrls failed\n"); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; + } + + v4l2_ctrl_cluster(2, &ctx->ctrl_hflip); + + return 0; +} + +static int g2d_open(struct file *file) +{ + struct g2d_dev *dev = video_drvdata(file); + struct g2d_ctx *ctx = NULL; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + ctx->dev = dev; + /* Set default formats */ + ctx->in = def_frame; + ctx->out = def_frame; + + if (mutex_lock_interruptible(&dev->mutex)) { + kfree(ctx); + return -ERESTARTSYS; + } + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + mutex_unlock(&dev->mutex); + kfree(ctx); + return ret; + } + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + g2d_setup_ctrls(ctx); + + /* Write the default values to the ctx struct */ + v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + mutex_unlock(&dev->mutex); + + v4l2_info(&dev->v4l2_dev, "instance opened\n"); + return 0; +} + +static int g2d_release(struct file *file) +{ + struct g2d_dev *dev = video_drvdata(file); + struct g2d_ctx *ctx = fh2ctx(file->private_data); + + mutex_lock(&dev->mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&dev->mutex); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + v4l2_info(&dev->v4l2_dev, "instance closed\n"); + return 0; +} + + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, G2D_NAME, sizeof(cap->driver)); + strscpy(cap->card, G2D_NAME, sizeof(cap->card)); + cap->bus_info[0] = 0; + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *prv, struct v4l2_fmtdesc *f) +{ + if (f->index >= NUM_FORMATS) + return -EINVAL; + f->pixelformat = formats[f->index].fourcc; + return 0; +} + +static int vidioc_g_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct g2d_ctx *ctx = prv; + struct vb2_queue *vq; + struct g2d_frame *frm; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + frm = get_frame(ctx, f->type); + if (IS_ERR(frm)) + return PTR_ERR(frm); + + f->fmt.pix.width = frm->width; + f->fmt.pix.height = frm->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = frm->fmt->fourcc; + f->fmt.pix.bytesperline = (frm->width * frm->fmt->depth) >> 3; + f->fmt.pix.sizeimage = frm->size; + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct g2d_fmt *fmt; + enum v4l2_field *field; + + fmt = find_fmt(f); + if (!fmt) + return -EINVAL; + + field = &f->fmt.pix.field; + if (*field == V4L2_FIELD_ANY) + *field = V4L2_FIELD_NONE; + else if (*field != V4L2_FIELD_NONE) + return -EINVAL; + + if (f->fmt.pix.width > MAX_WIDTH) + f->fmt.pix.width = MAX_WIDTH; + if (f->fmt.pix.height > MAX_HEIGHT) + f->fmt.pix.height = MAX_HEIGHT; + + if (f->fmt.pix.width < 1) + f->fmt.pix.width = 1; + if (f->fmt.pix.height < 1) + f->fmt.pix.height = 1; + + f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct g2d_ctx *ctx = prv; + struct g2d_dev *dev = ctx->dev; + struct vb2_queue *vq; + struct g2d_frame *frm; + struct g2d_fmt *fmt; + int ret = 0; + + /* Adjust all values accordingly to the hardware capabilities + * and chosen format. */ + ret = vidioc_try_fmt(file, prv, f); + if (ret) + return ret; + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) { + v4l2_err(&dev->v4l2_dev, "queue (%d) bust\n", f->type); + return -EBUSY; + } + frm = get_frame(ctx, f->type); + if (IS_ERR(frm)) + return PTR_ERR(frm); + fmt = find_fmt(f); + if (!fmt) + return -EINVAL; + frm->width = f->fmt.pix.width; + frm->height = f->fmt.pix.height; + frm->size = f->fmt.pix.sizeimage; + /* Reset crop settings */ + frm->o_width = 0; + frm->o_height = 0; + frm->c_width = frm->width; + frm->c_height = frm->height; + frm->right = frm->width; + frm->bottom = frm->height; + frm->fmt = fmt; + frm->stride = f->fmt.pix.bytesperline; + return 0; +} + +static int vidioc_g_selection(struct file *file, void *prv, + struct v4l2_selection *s) +{ + struct g2d_ctx *ctx = prv; + struct g2d_frame *f; + + f = get_frame(ctx, s->type); + if (IS_ERR(f)) + return PTR_ERR(f); + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + break; + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + break; + default: + return -EINVAL; + } + + switch (s->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_COMPOSE: + s->r.left = f->o_height; + s->r.top = f->o_width; + s->r.width = f->c_width; + s->r.height = f->c_height; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = 0; + s->r.top = 0; + s->r.width = f->width; + s->r.height = f->height; + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_try_selection(struct file *file, void *prv, + const struct v4l2_selection *s) +{ + struct g2d_ctx *ctx = prv; + struct g2d_dev *dev = ctx->dev; + struct g2d_frame *f; + + f = get_frame(ctx, s->type); + if (IS_ERR(f)) + return PTR_ERR(f); + + if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + if (s->target != V4L2_SEL_TGT_COMPOSE) + return -EINVAL; + } else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + } + + if (s->r.top < 0 || s->r.left < 0) { + v4l2_err(&dev->v4l2_dev, + "doesn't support negative values for top & left\n"); + return -EINVAL; + } + + return 0; +} + +static int vidioc_s_selection(struct file *file, void *prv, + struct v4l2_selection *s) +{ + struct g2d_ctx *ctx = prv; + struct g2d_frame *f; + int ret; + + ret = vidioc_try_selection(file, prv, s); + if (ret) + return ret; + f = get_frame(ctx, s->type); + if (IS_ERR(f)) + return PTR_ERR(f); + + f->c_width = s->r.width; + f->c_height = s->r.height; + f->o_width = s->r.left; + f->o_height = s->r.top; + f->bottom = f->o_height + f->c_height; + f->right = f->o_width + f->c_width; + return 0; +} + +static void device_run(void *prv) +{ + struct g2d_ctx *ctx = prv; + struct g2d_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *src, *dst; + unsigned long flags; + u32 cmd = 0; + + dev->curr = ctx; + + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + clk_enable(dev->gate); + g2d_reset(dev); + + spin_lock_irqsave(&dev->ctrl_lock, flags); + + g2d_set_src_size(dev, &ctx->in); + g2d_set_src_addr(dev, vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0)); + + g2d_set_dst_size(dev, &ctx->out); + g2d_set_dst_addr(dev, vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0)); + + g2d_set_rop4(dev, ctx->rop); + g2d_set_flip(dev, ctx->flip); + + if (ctx->in.c_width != ctx->out.c_width || + ctx->in.c_height != ctx->out.c_height) { + if (dev->variant->hw_rev == TYPE_G2D_3X) + cmd |= CMD_V3_ENABLE_STRETCH; + else + g2d_set_v41_stretch(dev, &ctx->in, &ctx->out); + } + + g2d_set_cmd(dev, cmd); + g2d_start(dev); + + spin_unlock_irqrestore(&dev->ctrl_lock, flags); +} + +static irqreturn_t g2d_isr(int irq, void *prv) +{ + struct g2d_dev *dev = prv; + struct g2d_ctx *ctx = dev->curr; + struct vb2_v4l2_buffer *src, *dst; + + g2d_clear_int(dev); + clk_disable(dev->gate); + + BUG_ON(ctx == NULL); + + src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + BUG_ON(src == NULL); + BUG_ON(dst == NULL); + + dst->timecode = src->timecode; + dst->vb2_buf.timestamp = src->vb2_buf.timestamp; + dst->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst->flags |= + src->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + + v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE); + v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx); + + dev->curr = NULL; + return IRQ_HANDLED; +} + +static const struct v4l2_file_operations g2d_fops = { + .owner = THIS_MODULE, + .open = g2d_open, + .release = g2d_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct v4l2_ioctl_ops g2d_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt, + .vidioc_g_fmt_vid_out = vidioc_g_fmt, + .vidioc_try_fmt_vid_out = vidioc_try_fmt, + .vidioc_s_fmt_vid_out = vidioc_s_fmt, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_g_selection = vidioc_g_selection, + .vidioc_s_selection = vidioc_s_selection, +}; + +static const struct video_device g2d_videodev = { + .name = G2D_NAME, + .fops = &g2d_fops, + .ioctl_ops = &g2d_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, +}; + +static const struct v4l2_m2m_ops g2d_m2m_ops = { + .device_run = device_run, +}; + +static const struct of_device_id exynos_g2d_match[]; + +static int g2d_probe(struct platform_device *pdev) +{ + struct g2d_dev *dev; + struct video_device *vfd; + const struct of_device_id *of_id; + int ret = 0; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->ctrl_lock); + mutex_init(&dev->mutex); + atomic_set(&dev->num_inst, 0); + + dev->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->regs)) + return PTR_ERR(dev->regs); + + dev->clk = clk_get(&pdev->dev, "sclk_fimg2d"); + if (IS_ERR(dev->clk)) { + dev_err(&pdev->dev, "failed to get g2d clock\n"); + return -ENXIO; + } + + ret = clk_prepare(dev->clk); + if (ret) { + dev_err(&pdev->dev, "failed to prepare g2d clock\n"); + goto put_clk; + } + + dev->gate = clk_get(&pdev->dev, "fimg2d"); + if (IS_ERR(dev->gate)) { + dev_err(&pdev->dev, "failed to get g2d clock gate\n"); + ret = -ENXIO; + goto unprep_clk; + } + + ret = clk_prepare(dev->gate); + if (ret) { + dev_err(&pdev->dev, "failed to prepare g2d clock gate\n"); + goto put_clk_gate; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto unprep_clk_gate; + + dev->irq = ret; + + ret = devm_request_irq(&pdev->dev, dev->irq, g2d_isr, + 0, pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "failed to install IRQ\n"); + goto unprep_clk_gate; + } + + vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto unprep_clk_gate; + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_v4l2_dev; + } + *vfd = g2d_videodev; + set_bit(V4L2_FL_QUIRK_INVERTED_CROP, &vfd->flags); + vfd->lock = &dev->mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; + + platform_set_drvdata(pdev, dev); + dev->m2m_dev = v4l2_m2m_init(&g2d_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + goto rel_vdev; + } + + def_frame.stride = (def_frame.width * def_frame.fmt->depth) >> 3; + + of_id = of_match_node(exynos_g2d_match, pdev->dev.of_node); + if (!of_id) { + ret = -ENODEV; + goto free_m2m; + } + dev->variant = (struct g2d_variant *)of_id->data; + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto free_m2m; + } + video_set_drvdata(vfd, dev); + dev->vfd = vfd; + v4l2_info(&dev->v4l2_dev, "device registered as /dev/video%d\n", + vfd->num); + + return 0; + +free_m2m: + v4l2_m2m_release(dev->m2m_dev); +rel_vdev: + video_device_release(vfd); +unreg_v4l2_dev: + v4l2_device_unregister(&dev->v4l2_dev); +unprep_clk_gate: + clk_unprepare(dev->gate); +put_clk_gate: + clk_put(dev->gate); +unprep_clk: + clk_unprepare(dev->clk); +put_clk: + clk_put(dev->clk); + + return ret; +} + +static int g2d_remove(struct platform_device *pdev) +{ + struct g2d_dev *dev = platform_get_drvdata(pdev); + + v4l2_info(&dev->v4l2_dev, "Removing " G2D_NAME); + v4l2_m2m_release(dev->m2m_dev); + video_unregister_device(dev->vfd); + v4l2_device_unregister(&dev->v4l2_dev); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); + clk_unprepare(dev->gate); + clk_put(dev->gate); + clk_unprepare(dev->clk); + clk_put(dev->clk); + return 0; +} + +static struct g2d_variant g2d_drvdata_v3x = { + .hw_rev = TYPE_G2D_3X, /* Revision 3.0 for S5PV210 and Exynos4210 */ +}; + +static struct g2d_variant g2d_drvdata_v4x = { + .hw_rev = TYPE_G2D_4X, /* Revision 4.1 for Exynos4X12 and Exynos5 */ +}; + +static const struct of_device_id exynos_g2d_match[] = { + { + .compatible = "samsung,s5pv210-g2d", + .data = &g2d_drvdata_v3x, + }, { + .compatible = "samsung,exynos4212-g2d", + .data = &g2d_drvdata_v4x, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_g2d_match); + +static struct platform_driver g2d_pdrv = { + .probe = g2d_probe, + .remove = g2d_remove, + .driver = { + .name = G2D_NAME, + .of_match_table = exynos_g2d_match, + }, +}; + +module_platform_driver(g2d_pdrv); + +MODULE_AUTHOR("Kamil Debski "); +MODULE_DESCRIPTION("S5P G2D 2d graphics accelerator driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/samsung/s5p-g2d/g2d.h b/drivers/media/platform/samsung/s5p-g2d/g2d.h new file mode 100644 index 000000000..c2309c137 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-g2d/g2d.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Samsung S5P G2D - 2D Graphics Accelerator Driver + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#include +#include +#include + +#define G2D_NAME "s5p-g2d" +#define TYPE_G2D_3X 3 +#define TYPE_G2D_4X 4 + +struct g2d_dev { + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct video_device *vfd; + struct mutex mutex; + spinlock_t ctrl_lock; + atomic_t num_inst; + void __iomem *regs; + struct clk *clk; + struct clk *gate; + struct g2d_ctx *curr; + struct g2d_variant *variant; + int irq; +}; + +struct g2d_frame { + /* Original dimensions */ + u32 width; + u32 height; + /* Crop size */ + u32 c_width; + u32 c_height; + /* Offset */ + u32 o_width; + u32 o_height; + /* Image format */ + struct g2d_fmt *fmt; + /* Variables that can calculated once and reused */ + u32 stride; + u32 bottom; + u32 right; + u32 size; +}; + +struct g2d_ctx { + struct v4l2_fh fh; + struct g2d_dev *dev; + struct g2d_frame in; + struct g2d_frame out; + struct v4l2_ctrl *ctrl_hflip; + struct v4l2_ctrl *ctrl_vflip; + struct v4l2_ctrl_handler ctrl_handler; + u32 rop; + u32 flip; +}; + +struct g2d_fmt { + u32 fourcc; + int depth; + u32 hw; +}; + +struct g2d_variant { + unsigned short hw_rev; +}; + +void g2d_reset(struct g2d_dev *d); +void g2d_set_src_size(struct g2d_dev *d, struct g2d_frame *f); +void g2d_set_src_addr(struct g2d_dev *d, dma_addr_t a); +void g2d_set_dst_size(struct g2d_dev *d, struct g2d_frame *f); +void g2d_set_dst_addr(struct g2d_dev *d, dma_addr_t a); +void g2d_start(struct g2d_dev *d); +void g2d_clear_int(struct g2d_dev *d); +void g2d_set_rop4(struct g2d_dev *d, u32 r); +void g2d_set_flip(struct g2d_dev *d, u32 r); +void g2d_set_v41_stretch(struct g2d_dev *d, + struct g2d_frame *src, struct g2d_frame *dst); +void g2d_set_cmd(struct g2d_dev *d, u32 c); diff --git a/drivers/media/platform/samsung/s5p-jpeg/Kconfig b/drivers/media/platform/samsung/s5p-jpeg/Kconfig new file mode 100644 index 000000000..11f6e99de --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/Kconfig @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_SAMSUNG_S5P_JPEG + tristate "Samsung S5P/Exynos3250/Exynos4 JPEG codec driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This is a v4l2 driver for Samsung S5P, EXYNOS3250 + and EXYNOS4 JPEG codec diff --git a/drivers/media/platform/samsung/s5p-jpeg/Makefile b/drivers/media/platform/samsung/s5p-jpeg/Makefile new file mode 100644 index 000000000..8b0f92e27 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +s5p-jpeg-objs := jpeg-core.o jpeg-hw-exynos3250.o jpeg-hw-exynos4.o jpeg-hw-s5p.o +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_JPEG) += s5p-jpeg.o diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c b/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c new file mode 100644 index 000000000..55814041b --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c @@ -0,0 +1,3180 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.c + * + * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + * Author: Jacek Anaszewski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "jpeg-core.h" +#include "jpeg-hw-s5p.h" +#include "jpeg-hw-exynos4.h" +#include "jpeg-hw-exynos3250.h" +#include "jpeg-regs.h" + +static struct s5p_jpeg_fmt sjpeg_formats[] = { + { + .fourcc = V4L2_PIX_FMT_JPEG, + .flags = SJPEG_FMT_FLAG_ENC_CAPTURE | + SJPEG_FMT_FLAG_DEC_OUTPUT | + SJPEG_FMT_FLAG_S5P | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_FLAG_EXYNOS4, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .colplanes = 1, + .h_align = 4, + .v_align = 3, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_S5P | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .colplanes = 1, + .h_align = 1, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + .colplanes = 1, + .h_align = 2, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .colplanes = 1, + .h_align = 1, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = 16, + .colplanes = 1, + .h_align = 2, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + .colplanes = 1, + .h_align = 2, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = 16, + .colplanes = 1, + .h_align = 2, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .colplanes = 1, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .colplanes = 1, + .h_align = 2, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = 16, + .colplanes = 1, + .h_align = 2, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .depth = 16, + .colplanes = 1, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_S5P | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .colplanes = 1, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = 32, + .colplanes = 1, + .h_align = 2, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .fourcc = V4L2_PIX_FMT_NV24, + .depth = 24, + .colplanes = 2, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .fourcc = V4L2_PIX_FMT_NV42, + .depth = 24, + .colplanes = 2, + .h_align = 0, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444, + }, + { + .fourcc = V4L2_PIX_FMT_NV61, + .depth = 16, + .colplanes = 2, + .h_align = 1, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_NV16, + .depth = 16, + .colplanes = 2, + .h_align = 1, + .v_align = 0, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422, + }, + { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .colplanes = 2, + .h_align = 1, + .v_align = 1, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .colplanes = 2, + .h_align = 3, + .v_align = 3, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .fourcc = V4L2_PIX_FMT_NV12, + .depth = 12, + .colplanes = 2, + .h_align = 4, + .v_align = 4, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_S5P | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 12, + .colplanes = 2, + .h_align = 3, + .v_align = 3, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .fourcc = V4L2_PIX_FMT_NV21, + .depth = 12, + .colplanes = 2, + .h_align = 1, + .v_align = 1, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .colplanes = 3, + .h_align = 1, + .v_align = 1, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = 12, + .colplanes = 3, + .h_align = 4, + .v_align = 4, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS3250 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420, + }, + { + .fourcc = V4L2_PIX_FMT_GREY, + .depth = 8, + .colplanes = 1, + .flags = SJPEG_FMT_FLAG_ENC_OUTPUT | + SJPEG_FMT_FLAG_DEC_CAPTURE | + SJPEG_FMT_FLAG_EXYNOS4 | + SJPEG_FMT_NON_RGB, + .subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, + }, +}; +#define SJPEG_NUM_FORMATS ARRAY_SIZE(sjpeg_formats) + +static const unsigned char qtbl_luminance[4][64] = { + {/*level 0 - high compression quality */ + 20, 16, 25, 39, 50, 46, 62, 68, + 16, 18, 23, 38, 38, 53, 65, 68, + 25, 23, 31, 38, 53, 65, 68, 68, + 39, 38, 38, 53, 65, 68, 68, 68, + 50, 38, 53, 65, 68, 68, 68, 68, + 46, 53, 65, 68, 68, 68, 68, 68, + 62, 65, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68 + }, + {/* level 1 */ + 16, 11, 11, 16, 23, 27, 31, 30, + 11, 12, 12, 15, 20, 23, 23, 30, + 11, 12, 13, 16, 23, 26, 35, 47, + 16, 15, 16, 23, 26, 37, 47, 64, + 23, 20, 23, 26, 39, 51, 64, 64, + 27, 23, 26, 37, 51, 64, 64, 64, + 31, 23, 35, 47, 64, 64, 64, 64, + 30, 30, 47, 64, 64, 64, 64, 64 + }, + {/* level 2 */ + 12, 8, 8, 12, 17, 21, 24, 23, + 8, 9, 9, 11, 15, 19, 18, 23, + 8, 9, 10, 12, 19, 20, 27, 36, + 12, 11, 12, 21, 20, 28, 36, 53, + 17, 15, 19, 20, 30, 39, 51, 59, + 21, 19, 20, 28, 39, 51, 59, 59, + 24, 18, 27, 36, 51, 59, 59, 59, + 23, 23, 36, 53, 59, 59, 59, 59 + }, + {/* level 3 - low compression quality */ + 8, 6, 6, 8, 12, 14, 16, 17, + 6, 6, 6, 8, 10, 13, 12, 15, + 6, 6, 7, 8, 13, 14, 18, 24, + 8, 8, 8, 14, 13, 19, 24, 35, + 12, 10, 13, 13, 20, 26, 34, 39, + 14, 13, 14, 19, 26, 34, 39, 39, + 16, 12, 18, 24, 34, 39, 39, 39, + 17, 15, 24, 35, 39, 39, 39, 39 + } +}; + +static const unsigned char qtbl_chrominance[4][64] = { + {/*level 0 - high compression quality */ + 21, 25, 32, 38, 54, 68, 68, 68, + 25, 28, 24, 38, 54, 68, 68, 68, + 32, 24, 32, 43, 66, 68, 68, 68, + 38, 38, 43, 53, 68, 68, 68, 68, + 54, 54, 66, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68, + 68, 68, 68, 68, 68, 68, 68, 68 + }, + {/* level 1 */ + 17, 15, 17, 21, 20, 26, 38, 48, + 15, 19, 18, 17, 20, 26, 35, 43, + 17, 18, 20, 22, 26, 30, 46, 53, + 21, 17, 22, 28, 30, 39, 53, 64, + 20, 20, 26, 30, 39, 48, 64, 64, + 26, 26, 30, 39, 48, 63, 64, 64, + 38, 35, 46, 53, 64, 64, 64, 64, + 48, 43, 53, 64, 64, 64, 64, 64 + }, + {/* level 2 */ + 13, 11, 13, 16, 20, 20, 29, 37, + 11, 14, 14, 14, 16, 20, 26, 32, + 13, 14, 15, 17, 20, 23, 35, 40, + 16, 14, 17, 21, 23, 30, 40, 50, + 20, 16, 20, 23, 30, 37, 50, 59, + 20, 20, 23, 30, 37, 48, 59, 59, + 29, 26, 35, 40, 50, 59, 59, 59, + 37, 32, 40, 50, 59, 59, 59, 59 + }, + {/* level 3 - low compression quality */ + 9, 8, 9, 11, 14, 17, 19, 24, + 8, 10, 9, 11, 14, 13, 17, 22, + 9, 9, 13, 14, 13, 15, 23, 26, + 11, 11, 14, 14, 15, 20, 26, 33, + 14, 14, 13, 15, 20, 24, 33, 39, + 17, 13, 15, 20, 24, 32, 39, 39, + 19, 17, 23, 26, 33, 39, 39, 39, + 24, 22, 26, 33, 39, 39, 39, 39 + } +}; + +static const unsigned char hdctbl0[16] = { + 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 +}; + +static const unsigned char hdctblg0[12] = { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0xa, 0xb +}; +static const unsigned char hactbl0[16] = { + 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d +}; +static const unsigned char hactblg0[162] = { + 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa +}; + +/* + * Fourcc downgrade schema lookup tables for 422 and 420 + * chroma subsampling - fourcc on each position maps on the + * fourcc from the table fourcc_to_dwngrd_schema_id which allows + * to get the most suitable fourcc counterpart for the given + * downgraded subsampling property. + */ +static const u32 subs422_fourcc_dwngrd_schema[] = { + V4L2_PIX_FMT_NV16, + V4L2_PIX_FMT_NV61, +}; + +static const u32 subs420_fourcc_dwngrd_schema[] = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_GREY, + V4L2_PIX_FMT_GREY, + V4L2_PIX_FMT_GREY, + V4L2_PIX_FMT_GREY, +}; + +/* + * Lookup table for translation of a fourcc to the position + * of its downgraded counterpart in the *fourcc_dwngrd_schema + * tables. + */ +static const u32 fourcc_to_dwngrd_schema_id[] = { + V4L2_PIX_FMT_NV24, + V4L2_PIX_FMT_NV42, + V4L2_PIX_FMT_NV16, + V4L2_PIX_FMT_NV61, + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_YVYU, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_GREY, +}; + +static int s5p_jpeg_get_dwngrd_sch_id_by_fourcc(u32 fourcc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fourcc_to_dwngrd_schema_id); ++i) { + if (fourcc_to_dwngrd_schema_id[i] == fourcc) + return i; + } + + return -EINVAL; +} + +static int s5p_jpeg_adjust_fourcc_to_subsampling( + enum v4l2_jpeg_chroma_subsampling subs, + u32 in_fourcc, + u32 *out_fourcc, + struct s5p_jpeg_ctx *ctx) +{ + int dwngrd_sch_id; + + if (ctx->subsampling != V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY) { + dwngrd_sch_id = + s5p_jpeg_get_dwngrd_sch_id_by_fourcc(in_fourcc); + if (dwngrd_sch_id < 0) + return -EINVAL; + } + + switch (ctx->subsampling) { + case V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY: + *out_fourcc = V4L2_PIX_FMT_GREY; + break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_420: + if (dwngrd_sch_id > + ARRAY_SIZE(subs420_fourcc_dwngrd_schema) - 1) + return -EINVAL; + *out_fourcc = subs420_fourcc_dwngrd_schema[dwngrd_sch_id]; + break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_422: + if (dwngrd_sch_id > + ARRAY_SIZE(subs422_fourcc_dwngrd_schema) - 1) + return -EINVAL; + *out_fourcc = subs422_fourcc_dwngrd_schema[dwngrd_sch_id]; + break; + default: + *out_fourcc = V4L2_PIX_FMT_GREY; + break; + } + + return 0; +} + +static int exynos4x12_decoded_subsampling[] = { + V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, + V4L2_JPEG_CHROMA_SUBSAMPLING_444, + V4L2_JPEG_CHROMA_SUBSAMPLING_422, + V4L2_JPEG_CHROMA_SUBSAMPLING_420, +}; + +static int exynos3250_decoded_subsampling[] = { + V4L2_JPEG_CHROMA_SUBSAMPLING_444, + V4L2_JPEG_CHROMA_SUBSAMPLING_422, + V4L2_JPEG_CHROMA_SUBSAMPLING_420, + V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, + -1, + -1, + V4L2_JPEG_CHROMA_SUBSAMPLING_411, +}; + +static inline struct s5p_jpeg_ctx *ctrl_to_ctx(struct v4l2_ctrl *c) +{ + return container_of(c->handler, struct s5p_jpeg_ctx, ctrl_handler); +} + +static inline struct s5p_jpeg_ctx *fh_to_ctx(struct v4l2_fh *fh) +{ + return container_of(fh, struct s5p_jpeg_ctx, fh); +} + +static int s5p_jpeg_to_user_subsampling(struct s5p_jpeg_ctx *ctx) +{ + switch (ctx->jpeg->variant->version) { + case SJPEG_S5P: + WARN_ON(ctx->subsampling > 3); + if (ctx->subsampling > 2) + return V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY; + return ctx->subsampling; + case SJPEG_EXYNOS3250: + case SJPEG_EXYNOS5420: + WARN_ON(ctx->subsampling > 6); + if (ctx->subsampling > 3) + return V4L2_JPEG_CHROMA_SUBSAMPLING_411; + return exynos3250_decoded_subsampling[ctx->subsampling]; + case SJPEG_EXYNOS4: + WARN_ON(ctx->subsampling > 3); + if (ctx->subsampling > 2) + return V4L2_JPEG_CHROMA_SUBSAMPLING_420; + return exynos4x12_decoded_subsampling[ctx->subsampling]; + case SJPEG_EXYNOS5433: + return ctx->subsampling; /* parsed from header */ + default: + WARN_ON(ctx->subsampling > 3); + return V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY; + } +} + +static inline void s5p_jpeg_set_qtbl(void __iomem *regs, + const unsigned char *qtbl, + unsigned long tab, int len) +{ + int i; + + for (i = 0; i < len; i++) + writel((unsigned int)qtbl[i], regs + tab + (i * 0x04)); +} + +static inline void s5p_jpeg_set_qtbl_lum(void __iomem *regs, int quality) +{ + /* this driver fills quantisation table 0 with data for luma */ + s5p_jpeg_set_qtbl(regs, qtbl_luminance[quality], + S5P_JPG_QTBL_CONTENT(0), + ARRAY_SIZE(qtbl_luminance[quality])); +} + +static inline void s5p_jpeg_set_qtbl_chr(void __iomem *regs, int quality) +{ + /* this driver fills quantisation table 1 with data for chroma */ + s5p_jpeg_set_qtbl(regs, qtbl_chrominance[quality], + S5P_JPG_QTBL_CONTENT(1), + ARRAY_SIZE(qtbl_chrominance[quality])); +} + +static inline void s5p_jpeg_set_htbl(void __iomem *regs, + const unsigned char *htbl, + unsigned long tab, int len) +{ + int i; + + for (i = 0; i < len; i++) + writel((unsigned int)htbl[i], regs + tab + (i * 0x04)); +} + +static inline void s5p_jpeg_set_hdctbl(void __iomem *regs) +{ + /* this driver fills table 0 for this component */ + s5p_jpeg_set_htbl(regs, hdctbl0, S5P_JPG_HDCTBL(0), + ARRAY_SIZE(hdctbl0)); +} + +static inline void s5p_jpeg_set_hdctblg(void __iomem *regs) +{ + /* this driver fills table 0 for this component */ + s5p_jpeg_set_htbl(regs, hdctblg0, S5P_JPG_HDCTBLG(0), + ARRAY_SIZE(hdctblg0)); +} + +static inline void s5p_jpeg_set_hactbl(void __iomem *regs) +{ + /* this driver fills table 0 for this component */ + s5p_jpeg_set_htbl(regs, hactbl0, S5P_JPG_HACTBL(0), + ARRAY_SIZE(hactbl0)); +} + +static inline void s5p_jpeg_set_hactblg(void __iomem *regs) +{ + /* this driver fills table 0 for this component */ + s5p_jpeg_set_htbl(regs, hactblg0, S5P_JPG_HACTBLG(0), + ARRAY_SIZE(hactblg0)); +} + +static inline void exynos4_jpeg_set_tbl(void __iomem *regs, + const unsigned char *tbl, + unsigned long tab, int len) +{ + int i; + unsigned int dword; + + for (i = 0; i < len; i += 4) { + dword = tbl[i] | + (tbl[i + 1] << 8) | + (tbl[i + 2] << 16) | + (tbl[i + 3] << 24); + writel(dword, regs + tab + i); + } +} + +static inline void exynos4_jpeg_set_qtbl_lum(void __iomem *regs, int quality) +{ + /* this driver fills quantisation table 0 with data for luma */ + exynos4_jpeg_set_tbl(regs, qtbl_luminance[quality], + EXYNOS4_QTBL_CONTENT(0), + ARRAY_SIZE(qtbl_luminance[quality])); +} + +static inline void exynos4_jpeg_set_qtbl_chr(void __iomem *regs, int quality) +{ + /* this driver fills quantisation table 1 with data for chroma */ + exynos4_jpeg_set_tbl(regs, qtbl_chrominance[quality], + EXYNOS4_QTBL_CONTENT(1), + ARRAY_SIZE(qtbl_chrominance[quality])); +} + +static void exynos4_jpeg_set_huff_tbl(void __iomem *base) +{ + exynos4_jpeg_set_tbl(base, hdctbl0, EXYNOS4_HUFF_TBL_HDCLL, + ARRAY_SIZE(hdctbl0)); + exynos4_jpeg_set_tbl(base, hdctbl0, EXYNOS4_HUFF_TBL_HDCCL, + ARRAY_SIZE(hdctbl0)); + exynos4_jpeg_set_tbl(base, hdctblg0, EXYNOS4_HUFF_TBL_HDCLV, + ARRAY_SIZE(hdctblg0)); + exynos4_jpeg_set_tbl(base, hdctblg0, EXYNOS4_HUFF_TBL_HDCCV, + ARRAY_SIZE(hdctblg0)); + exynos4_jpeg_set_tbl(base, hactbl0, EXYNOS4_HUFF_TBL_HACLL, + ARRAY_SIZE(hactbl0)); + exynos4_jpeg_set_tbl(base, hactbl0, EXYNOS4_HUFF_TBL_HACCL, + ARRAY_SIZE(hactbl0)); + exynos4_jpeg_set_tbl(base, hactblg0, EXYNOS4_HUFF_TBL_HACLV, + ARRAY_SIZE(hactblg0)); + exynos4_jpeg_set_tbl(base, hactblg0, EXYNOS4_HUFF_TBL_HACCV, + ARRAY_SIZE(hactblg0)); +} + +static inline int __exynos4_huff_tbl(int class, int id, bool lenval) +{ + /* + * class: 0 - DC, 1 - AC + * id: 0 - Y, 1 - Cb/Cr + */ + if (class) { + if (id) + return lenval ? EXYNOS4_HUFF_TBL_HACCL : + EXYNOS4_HUFF_TBL_HACCV; + return lenval ? EXYNOS4_HUFF_TBL_HACLL : EXYNOS4_HUFF_TBL_HACLV; + + } + /* class == 0 */ + if (id) + return lenval ? EXYNOS4_HUFF_TBL_HDCCL : EXYNOS4_HUFF_TBL_HDCCV; + + return lenval ? EXYNOS4_HUFF_TBL_HDCLL : EXYNOS4_HUFF_TBL_HDCLV; +} + +static inline int exynos4_huff_tbl_len(int class, int id) +{ + return __exynos4_huff_tbl(class, id, true); +} + +static inline int exynos4_huff_tbl_val(int class, int id) +{ + return __exynos4_huff_tbl(class, id, false); +} + +static int get_byte(struct s5p_jpeg_buffer *buf); +static int get_word_be(struct s5p_jpeg_buffer *buf, unsigned int *word); +static void skip(struct s5p_jpeg_buffer *buf, long len); + +static void exynos4_jpeg_parse_decode_h_tbl(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + struct s5p_jpeg_buffer jpeg_buffer; + unsigned int word; + int c, x, components; + + jpeg_buffer.size = 2; /* Ls */ + jpeg_buffer.data = + (unsigned long)vb2_plane_vaddr(&vb->vb2_buf, 0) + ctx->out_q.sos + 2; + jpeg_buffer.curr = 0; + + word = 0; + + if (get_word_be(&jpeg_buffer, &word)) + return; + jpeg_buffer.size = (long)word - 2; + jpeg_buffer.data += 2; + jpeg_buffer.curr = 0; + + components = get_byte(&jpeg_buffer); + if (components == -1) + return; + while (components--) { + c = get_byte(&jpeg_buffer); + if (c == -1) + return; + x = get_byte(&jpeg_buffer); + if (x == -1) + return; + exynos4_jpeg_select_dec_h_tbl(jpeg->regs, c, + (((x >> 4) & 0x1) << 1) | (x & 0x1)); + } + +} + +static void exynos4_jpeg_parse_huff_tbl(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + struct s5p_jpeg_buffer jpeg_buffer; + unsigned int word; + int c, i, n, j; + + for (j = 0; j < ctx->out_q.dht.n; ++j) { + jpeg_buffer.size = ctx->out_q.dht.len[j]; + jpeg_buffer.data = (unsigned long)vb2_plane_vaddr(&vb->vb2_buf, 0) + + ctx->out_q.dht.marker[j]; + jpeg_buffer.curr = 0; + + word = 0; + while (jpeg_buffer.curr < jpeg_buffer.size) { + char id, class; + + c = get_byte(&jpeg_buffer); + if (c == -1) + return; + id = c & 0xf; + class = (c >> 4) & 0xf; + n = 0; + for (i = 0; i < 16; ++i) { + c = get_byte(&jpeg_buffer); + if (c == -1) + return; + word |= c << ((i % 4) * 8); + if ((i + 1) % 4 == 0) { + writel(word, jpeg->regs + + exynos4_huff_tbl_len(class, id) + + (i / 4) * 4); + word = 0; + } + n += c; + } + word = 0; + for (i = 0; i < n; ++i) { + c = get_byte(&jpeg_buffer); + if (c == -1) + return; + word |= c << ((i % 4) * 8); + if ((i + 1) % 4 == 0) { + writel(word, jpeg->regs + + exynos4_huff_tbl_val(class, id) + + (i / 4) * 4); + word = 0; + } + } + if (i % 4) { + writel(word, jpeg->regs + + exynos4_huff_tbl_val(class, id) + (i / 4) * 4); + } + word = 0; + } + } +} + +static void exynos4_jpeg_parse_decode_q_tbl(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + struct s5p_jpeg_buffer jpeg_buffer; + int c, x, components; + + jpeg_buffer.size = ctx->out_q.sof_len; + jpeg_buffer.data = + (unsigned long)vb2_plane_vaddr(&vb->vb2_buf, 0) + ctx->out_q.sof; + jpeg_buffer.curr = 0; + + skip(&jpeg_buffer, 5); /* P, Y, X */ + components = get_byte(&jpeg_buffer); + if (components == -1) + return; + + exynos4_jpeg_set_dec_components(jpeg->regs, components); + + while (components--) { + c = get_byte(&jpeg_buffer); + if (c == -1) + return; + skip(&jpeg_buffer, 1); + x = get_byte(&jpeg_buffer); + if (x == -1) + return; + exynos4_jpeg_select_dec_q_tbl(jpeg->regs, c, x); + } +} + +static void exynos4_jpeg_parse_q_tbl(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + struct s5p_jpeg_buffer jpeg_buffer; + unsigned int word; + int c, i, j; + + for (j = 0; j < ctx->out_q.dqt.n; ++j) { + jpeg_buffer.size = ctx->out_q.dqt.len[j]; + jpeg_buffer.data = (unsigned long)vb2_plane_vaddr(&vb->vb2_buf, 0) + + ctx->out_q.dqt.marker[j]; + jpeg_buffer.curr = 0; + + word = 0; + while (jpeg_buffer.size - jpeg_buffer.curr >= 65) { + char id; + + c = get_byte(&jpeg_buffer); + if (c == -1) + return; + id = c & 0xf; + /* nonzero means extended mode - not supported */ + if ((c >> 4) & 0xf) + return; + for (i = 0; i < 64; ++i) { + c = get_byte(&jpeg_buffer); + if (c == -1) + return; + word |= c << ((i % 4) * 8); + if ((i + 1) % 4 == 0) { + writel(word, jpeg->regs + + EXYNOS4_QTBL_CONTENT(id) + (i / 4) * 4); + word = 0; + } + } + word = 0; + } + } +} + +/* + * ============================================================================ + * Device file operations + * ============================================================================ + */ + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq); +static struct s5p_jpeg_fmt *s5p_jpeg_find_format(struct s5p_jpeg_ctx *ctx, + __u32 pixelformat, unsigned int fmt_type); +static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx); + +static int s5p_jpeg_open(struct file *file) +{ + struct s5p_jpeg *jpeg = video_drvdata(file); + struct video_device *vfd = video_devdata(file); + struct s5p_jpeg_ctx *ctx; + struct s5p_jpeg_fmt *out_fmt, *cap_fmt; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (mutex_lock_interruptible(&jpeg->lock)) { + ret = -ERESTARTSYS; + goto free; + } + + v4l2_fh_init(&ctx->fh, vfd); + /* Use separate control handler per file handle */ + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ctx->jpeg = jpeg; + if (vfd == jpeg->vfd_encoder) { + ctx->mode = S5P_JPEG_ENCODE; + out_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_RGB565, + FMT_TYPE_OUTPUT); + cap_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG, + FMT_TYPE_CAPTURE); + } else { + ctx->mode = S5P_JPEG_DECODE; + out_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_JPEG, + FMT_TYPE_OUTPUT); + cap_fmt = s5p_jpeg_find_format(ctx, V4L2_PIX_FMT_YUYV, + FMT_TYPE_CAPTURE); + ctx->scale_factor = EXYNOS3250_DEC_SCALE_FACTOR_8_8; + } + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(jpeg->m2m_dev, ctx, queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto error; + } + + ctx->out_q.fmt = out_fmt; + ctx->cap_q.fmt = cap_fmt; + + ret = s5p_jpeg_controls_create(ctx); + if (ret < 0) + goto error; + + mutex_unlock(&jpeg->lock); + return 0; + +error: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + mutex_unlock(&jpeg->lock); +free: + kfree(ctx); + return ret; +} + +static int s5p_jpeg_release(struct file *file) +{ + struct s5p_jpeg *jpeg = video_drvdata(file); + struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data); + + mutex_lock(&jpeg->lock); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mutex_unlock(&jpeg->lock); + + return 0; +} + +static const struct v4l2_file_operations s5p_jpeg_fops = { + .owner = THIS_MODULE, + .open = s5p_jpeg_open, + .release = s5p_jpeg_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +/* + * ============================================================================ + * video ioctl operations + * ============================================================================ + */ + +static int get_byte(struct s5p_jpeg_buffer *buf) +{ + if (buf->curr >= buf->size) + return -1; + + return ((unsigned char *)buf->data)[buf->curr++]; +} + +static int get_word_be(struct s5p_jpeg_buffer *buf, unsigned int *word) +{ + unsigned int temp; + int byte; + + byte = get_byte(buf); + if (byte == -1) + return -1; + temp = byte << 8; + byte = get_byte(buf); + if (byte == -1) + return -1; + *word = (unsigned int)byte | temp; + return 0; +} + +static void skip(struct s5p_jpeg_buffer *buf, long len) +{ + if (len <= 0) + return; + + while (len--) + get_byte(buf); +} + +static bool s5p_jpeg_subsampling_decode(struct s5p_jpeg_ctx *ctx, + unsigned int subsampling) +{ + unsigned int version; + + switch (subsampling) { + case 0x11: + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_444; + break; + case 0x21: + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_422; + break; + case 0x22: + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_420; + break; + case 0x33: + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY; + break; + case 0x41: + /* + * 4:1:1 subsampling only supported by 3250, 5420, and 5433 + * variants + */ + version = ctx->jpeg->variant->version; + if (version != SJPEG_EXYNOS3250 && + version != SJPEG_EXYNOS5420 && + version != SJPEG_EXYNOS5433) + return false; + + ctx->subsampling = V4L2_JPEG_CHROMA_SUBSAMPLING_411; + break; + default: + return false; + } + + return true; +} + +static bool s5p_jpeg_parse_hdr(struct s5p_jpeg_q_data *result, + unsigned long buffer, unsigned long size, + struct s5p_jpeg_ctx *ctx) +{ + int c, components = 0, notfound, n_dht = 0, n_dqt = 0; + unsigned int height = 0, width = 0, word, subsampling = 0; + unsigned int sos = 0, sof = 0, sof_len = 0; + unsigned int dht[S5P_JPEG_MAX_MARKER], dht_len[S5P_JPEG_MAX_MARKER]; + unsigned int dqt[S5P_JPEG_MAX_MARKER], dqt_len[S5P_JPEG_MAX_MARKER]; + long length; + struct s5p_jpeg_buffer jpeg_buffer; + + jpeg_buffer.size = size; + jpeg_buffer.data = buffer; + jpeg_buffer.curr = 0; + + notfound = 1; + while (notfound || !sos) { + c = get_byte(&jpeg_buffer); + if (c == -1) + return false; + if (c != 0xff) + continue; + do + c = get_byte(&jpeg_buffer); + while (c == 0xff); + if (c == -1) + return false; + if (c == 0) + continue; + length = 0; + switch (c) { + /* JPEG_MARKER_SOF0: baseline JPEG */ + case JPEG_MARKER_SOF0: + if (get_word_be(&jpeg_buffer, &word)) + break; + length = (long)word - 2; + if (!length) + return false; + sof = jpeg_buffer.curr; /* after 0xffc0 */ + sof_len = length; + if (get_byte(&jpeg_buffer) == -1) + break; + if (get_word_be(&jpeg_buffer, &height)) + break; + if (get_word_be(&jpeg_buffer, &width)) + break; + components = get_byte(&jpeg_buffer); + if (components == -1) + break; + + if (components == 1) { + subsampling = 0x33; + } else { + skip(&jpeg_buffer, 1); + subsampling = get_byte(&jpeg_buffer); + skip(&jpeg_buffer, 1); + } + if (components > 3) + return false; + skip(&jpeg_buffer, components * 2); + notfound = 0; + break; + + case JPEG_MARKER_DQT: + if (get_word_be(&jpeg_buffer, &word)) + break; + length = (long)word - 2; + if (!length) + return false; + if (n_dqt >= S5P_JPEG_MAX_MARKER) + return false; + dqt[n_dqt] = jpeg_buffer.curr; /* after 0xffdb */ + dqt_len[n_dqt++] = length; + skip(&jpeg_buffer, length); + break; + + case JPEG_MARKER_DHT: + if (get_word_be(&jpeg_buffer, &word)) + break; + length = (long)word - 2; + if (!length) + return false; + if (n_dht >= S5P_JPEG_MAX_MARKER) + return false; + dht[n_dht] = jpeg_buffer.curr; /* after 0xffc4 */ + dht_len[n_dht++] = length; + skip(&jpeg_buffer, length); + break; + + case JPEG_MARKER_SOS: + sos = jpeg_buffer.curr - 2; /* 0xffda */ + break; + + /* skip payload-less markers */ + case JPEG_MARKER_RST ... JPEG_MARKER_RST + 7: + case JPEG_MARKER_SOI: + case JPEG_MARKER_EOI: + case JPEG_MARKER_TEM: + break; + + /* skip uninteresting payload markers */ + default: + if (get_word_be(&jpeg_buffer, &word)) + break; + length = (long)word - 2; + skip(&jpeg_buffer, length); + break; + } + } + + if (notfound || !sos || !s5p_jpeg_subsampling_decode(ctx, subsampling)) + return false; + + result->w = width; + result->h = height; + result->sos = sos; + result->dht.n = n_dht; + while (n_dht--) { + result->dht.marker[n_dht] = dht[n_dht]; + result->dht.len[n_dht] = dht_len[n_dht]; + } + result->dqt.n = n_dqt; + while (n_dqt--) { + result->dqt.marker[n_dqt] = dqt[n_dqt]; + result->dqt.len[n_dqt] = dqt_len[n_dqt]; + } + result->sof = sof; + result->sof_len = sof_len; + + return true; +} + +static int s5p_jpeg_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); + + if (ctx->mode == S5P_JPEG_ENCODE) { + strscpy(cap->driver, S5P_JPEG_M2M_NAME, + sizeof(cap->driver)); + strscpy(cap->card, S5P_JPEG_M2M_NAME " encoder", + sizeof(cap->card)); + } else { + strscpy(cap->driver, S5P_JPEG_M2M_NAME, + sizeof(cap->driver)); + strscpy(cap->card, S5P_JPEG_M2M_NAME " decoder", + sizeof(cap->card)); + } + return 0; +} + +static int enum_fmt(struct s5p_jpeg_ctx *ctx, + struct s5p_jpeg_fmt *sjpeg_formats, int n, + struct v4l2_fmtdesc *f, u32 type) +{ + int i, num = 0; + unsigned int fmt_ver_flag = ctx->jpeg->variant->fmt_ver_flag; + + for (i = 0; i < n; ++i) { + if (sjpeg_formats[i].flags & type && + sjpeg_formats[i].flags & fmt_ver_flag) { + /* index-th format of type type found ? */ + if (num == f->index) + break; + /* Correct type but haven't reached our index yet, + * just increment per-type index + */ + ++num; + } + } + + /* Format not found */ + if (i >= n) + return -EINVAL; + + f->pixelformat = sjpeg_formats[i].fourcc; + + return 0; +} + +static int s5p_jpeg_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); + + if (ctx->mode == S5P_JPEG_ENCODE) + return enum_fmt(ctx, sjpeg_formats, SJPEG_NUM_FORMATS, f, + SJPEG_FMT_FLAG_ENC_CAPTURE); + + return enum_fmt(ctx, sjpeg_formats, SJPEG_NUM_FORMATS, f, + SJPEG_FMT_FLAG_DEC_CAPTURE); +} + +static int s5p_jpeg_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); + + if (ctx->mode == S5P_JPEG_ENCODE) + return enum_fmt(ctx, sjpeg_formats, SJPEG_NUM_FORMATS, f, + SJPEG_FMT_FLAG_ENC_OUTPUT); + + return enum_fmt(ctx, sjpeg_formats, SJPEG_NUM_FORMATS, f, + SJPEG_FMT_FLAG_DEC_OUTPUT); +} + +static struct s5p_jpeg_q_data *get_q_data(struct s5p_jpeg_ctx *ctx, + enum v4l2_buf_type type) +{ + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + return &ctx->out_q; + if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + return &ctx->cap_q; + + return NULL; +} + +static int s5p_jpeg_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct s5p_jpeg_q_data *q_data = NULL; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct s5p_jpeg_ctx *ct = fh_to_ctx(priv); + + vq = v4l2_m2m_get_vq(ct->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE && + ct->mode == S5P_JPEG_DECODE && !ct->hdr_parsed) + return -EINVAL; + q_data = get_q_data(ct, f->type); + BUG_ON(q_data == NULL); + + pix->width = q_data->w; + pix->height = q_data->h; + pix->field = V4L2_FIELD_NONE; + pix->pixelformat = q_data->fmt->fourcc; + pix->bytesperline = 0; + if (q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG) { + u32 bpl = q_data->w; + + if (q_data->fmt->colplanes == 1) + bpl = (bpl * q_data->fmt->depth) >> 3; + pix->bytesperline = bpl; + } + pix->sizeimage = q_data->size; + + return 0; +} + +static struct s5p_jpeg_fmt *s5p_jpeg_find_format(struct s5p_jpeg_ctx *ctx, + u32 pixelformat, unsigned int fmt_type) +{ + unsigned int k, fmt_flag; + + if (ctx->mode == S5P_JPEG_ENCODE) + fmt_flag = (fmt_type == FMT_TYPE_OUTPUT) ? + SJPEG_FMT_FLAG_ENC_OUTPUT : + SJPEG_FMT_FLAG_ENC_CAPTURE; + else + fmt_flag = (fmt_type == FMT_TYPE_OUTPUT) ? + SJPEG_FMT_FLAG_DEC_OUTPUT : + SJPEG_FMT_FLAG_DEC_CAPTURE; + + for (k = 0; k < ARRAY_SIZE(sjpeg_formats); k++) { + struct s5p_jpeg_fmt *fmt = &sjpeg_formats[k]; + + if (fmt->fourcc == pixelformat && + fmt->flags & fmt_flag && + fmt->flags & ctx->jpeg->variant->fmt_ver_flag) { + return fmt; + } + } + + return NULL; +} + +static void jpeg_bound_align_image(struct s5p_jpeg_ctx *ctx, + u32 *w, unsigned int wmin, unsigned int wmax, + unsigned int walign, + u32 *h, unsigned int hmin, unsigned int hmax, + unsigned int halign) +{ + int width, height, w_step, h_step; + + width = *w; + height = *h; + + w_step = 1 << walign; + h_step = 1 << halign; + + if (ctx->jpeg->variant->hw3250_compat) { + /* + * Rightmost and bottommost pixels are cropped by the + * Exynos3250/compatible JPEG IP for RGB formats, for the + * specific width and height values respectively. This + * assignment will result in v4l_bound_align_image returning + * dimensions reduced by 1 for the aforementioned cases. + */ + if (w_step == 4 && ((width & 3) == 1)) { + wmax = width; + hmax = height; + } + } + + v4l_bound_align_image(w, wmin, wmax, walign, h, hmin, hmax, halign, 0); + + if (*w < width && (*w + w_step) < wmax) + *w += w_step; + if (*h < height && (*h + h_step) < hmax) + *h += h_step; +} + +static int vidioc_try_fmt(struct v4l2_format *f, struct s5p_jpeg_fmt *fmt, + struct s5p_jpeg_ctx *ctx, int q_type) +{ + struct v4l2_pix_format *pix = &f->fmt.pix; + + if (pix->field == V4L2_FIELD_ANY) + pix->field = V4L2_FIELD_NONE; + else if (pix->field != V4L2_FIELD_NONE) + return -EINVAL; + + /* V4L2 specification suggests the driver corrects the format struct + * if any of the dimensions is unsupported + */ + if (q_type == FMT_TYPE_OUTPUT) + jpeg_bound_align_image(ctx, &pix->width, S5P_JPEG_MIN_WIDTH, + S5P_JPEG_MAX_WIDTH, 0, + &pix->height, S5P_JPEG_MIN_HEIGHT, + S5P_JPEG_MAX_HEIGHT, 0); + else + jpeg_bound_align_image(ctx, &pix->width, S5P_JPEG_MIN_WIDTH, + S5P_JPEG_MAX_WIDTH, fmt->h_align, + &pix->height, S5P_JPEG_MIN_HEIGHT, + S5P_JPEG_MAX_HEIGHT, fmt->v_align); + + if (fmt->fourcc == V4L2_PIX_FMT_JPEG) { + if (pix->sizeimage <= 0) + pix->sizeimage = PAGE_SIZE; + pix->bytesperline = 0; + } else { + u32 bpl = pix->bytesperline; + + if (fmt->colplanes > 1 && bpl < pix->width) + bpl = pix->width; /* planar */ + + if (fmt->colplanes == 1 && /* packed */ + (bpl << 3) / fmt->depth < pix->width) + bpl = (pix->width * fmt->depth) >> 3; + + pix->bytesperline = bpl; + pix->sizeimage = (pix->width * pix->height * fmt->depth) >> 3; + } + + return 0; +} + +static int s5p_jpeg_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format *pix = &f->fmt.pix; + struct s5p_jpeg_fmt *fmt; + int ret; + + fmt = s5p_jpeg_find_format(ctx, f->fmt.pix.pixelformat, + FMT_TYPE_CAPTURE); + if (!fmt) { + v4l2_err(&ctx->jpeg->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + if (!ctx->jpeg->variant->hw_ex4_compat || ctx->mode != S5P_JPEG_DECODE) + goto exit; + + /* + * The exynos4x12 device requires resulting YUV image + * subsampling not to be lower than the input jpeg subsampling. + * If this requirement is not met then downgrade the requested + * capture format to the one with subsampling equal to the input jpeg. + */ + if ((fmt->flags & SJPEG_FMT_NON_RGB) && + (fmt->subsampling < ctx->subsampling)) { + ret = s5p_jpeg_adjust_fourcc_to_subsampling(ctx->subsampling, + fmt->fourcc, + &pix->pixelformat, + ctx); + if (ret < 0) + pix->pixelformat = V4L2_PIX_FMT_GREY; + + fmt = s5p_jpeg_find_format(ctx, pix->pixelformat, + FMT_TYPE_CAPTURE); + } + + /* + * Decompression of a JPEG file with 4:2:0 subsampling and odd + * width to the YUV 4:2:0 compliant formats produces a raw image + * with broken luma component. Adjust capture format to RGB565 + * in such a case. + */ + if (ctx->subsampling == V4L2_JPEG_CHROMA_SUBSAMPLING_420 && + (ctx->out_q.w & 1) && + (pix->pixelformat == V4L2_PIX_FMT_NV12 || + pix->pixelformat == V4L2_PIX_FMT_NV21 || + pix->pixelformat == V4L2_PIX_FMT_YUV420)) { + pix->pixelformat = V4L2_PIX_FMT_RGB565; + fmt = s5p_jpeg_find_format(ctx, pix->pixelformat, + FMT_TYPE_CAPTURE); + } + +exit: + return vidioc_try_fmt(f, fmt, ctx, FMT_TYPE_CAPTURE); +} + +static int s5p_jpeg_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); + struct s5p_jpeg_fmt *fmt; + + fmt = s5p_jpeg_find_format(ctx, f->fmt.pix.pixelformat, + FMT_TYPE_OUTPUT); + if (!fmt) { + v4l2_err(&ctx->jpeg->v4l2_dev, + "Fourcc format (0x%08x) invalid.\n", + f->fmt.pix.pixelformat); + return -EINVAL; + } + + return vidioc_try_fmt(f, fmt, ctx, FMT_TYPE_OUTPUT); +} + +static int exynos4_jpeg_get_output_buffer_size(struct s5p_jpeg_ctx *ctx, + struct v4l2_format *f, + int fmt_depth) +{ + struct v4l2_pix_format *pix = &f->fmt.pix; + u32 pix_fmt = f->fmt.pix.pixelformat; + int w = pix->width, h = pix->height, wh_align; + int padding = 0; + + if (pix_fmt == V4L2_PIX_FMT_RGB32 || + pix_fmt == V4L2_PIX_FMT_RGB565 || + pix_fmt == V4L2_PIX_FMT_NV24 || + pix_fmt == V4L2_PIX_FMT_NV42 || + pix_fmt == V4L2_PIX_FMT_NV12 || + pix_fmt == V4L2_PIX_FMT_NV21 || + pix_fmt == V4L2_PIX_FMT_YUV420) + wh_align = 4; + else + wh_align = 1; + + jpeg_bound_align_image(ctx, &w, S5P_JPEG_MIN_WIDTH, + S5P_JPEG_MAX_WIDTH, wh_align, + &h, S5P_JPEG_MIN_HEIGHT, + S5P_JPEG_MAX_HEIGHT, wh_align); + + if (ctx->jpeg->variant->version == SJPEG_EXYNOS4) + padding = PAGE_SIZE; + + return (w * h * fmt_depth >> 3) + padding; +} + +static int exynos3250_jpeg_try_downscale(struct s5p_jpeg_ctx *ctx, + struct v4l2_rect *r); + +static int s5p_jpeg_s_fmt(struct s5p_jpeg_ctx *ct, struct v4l2_format *f) +{ + struct vb2_queue *vq; + struct s5p_jpeg_q_data *q_data = NULL; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_ctrl *ctrl_subs; + struct v4l2_rect scale_rect; + unsigned int f_type; + + vq = v4l2_m2m_get_vq(ct->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + q_data = get_q_data(ct, f->type); + BUG_ON(q_data == NULL); + + if (vb2_is_busy(vq)) { + v4l2_err(&ct->jpeg->v4l2_dev, "%s queue busy\n", __func__); + return -EBUSY; + } + + f_type = V4L2_TYPE_IS_OUTPUT(f->type) ? + FMT_TYPE_OUTPUT : FMT_TYPE_CAPTURE; + + q_data->fmt = s5p_jpeg_find_format(ct, pix->pixelformat, f_type); + if (ct->mode == S5P_JPEG_ENCODE || + (ct->mode == S5P_JPEG_DECODE && + q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG)) { + q_data->w = pix->width; + q_data->h = pix->height; + } + if (q_data->fmt->fourcc != V4L2_PIX_FMT_JPEG) { + /* + * During encoding Exynos4x12 SoCs access wider memory area + * than it results from Image_x and Image_y values written to + * the JPEG_IMAGE_SIZE register. In order to avoid sysmmu + * page fault calculate proper buffer size in such a case. + */ + if (ct->jpeg->variant->hw_ex4_compat && + f_type == FMT_TYPE_OUTPUT && ct->mode == S5P_JPEG_ENCODE) + q_data->size = exynos4_jpeg_get_output_buffer_size(ct, + f, + q_data->fmt->depth); + else + q_data->size = q_data->w * q_data->h * + q_data->fmt->depth >> 3; + } else { + q_data->size = pix->sizeimage; + } + + if (f_type == FMT_TYPE_OUTPUT) { + ctrl_subs = v4l2_ctrl_find(&ct->ctrl_handler, + V4L2_CID_JPEG_CHROMA_SUBSAMPLING); + if (ctrl_subs) + v4l2_ctrl_s_ctrl(ctrl_subs, q_data->fmt->subsampling); + ct->crop_altered = false; + } + + /* + * For decoding init crop_rect with capture buffer dimmensions which + * contain aligned dimensions of the input JPEG image and do it only + * if crop rectangle hasn't been altered by the user space e.g. with + * S_SELECTION ioctl. For encoding assign output buffer dimensions. + */ + if (!ct->crop_altered && + ((ct->mode == S5P_JPEG_DECODE && f_type == FMT_TYPE_CAPTURE) || + (ct->mode == S5P_JPEG_ENCODE && f_type == FMT_TYPE_OUTPUT))) { + ct->crop_rect.width = pix->width; + ct->crop_rect.height = pix->height; + } + + /* + * Prevent downscaling to YUV420 format by more than 2 + * for Exynos3250/compatible SoC as it produces broken raw image + * in such cases. + */ + if (ct->mode == S5P_JPEG_DECODE && + f_type == FMT_TYPE_CAPTURE && + ct->jpeg->variant->hw3250_compat && + pix->pixelformat == V4L2_PIX_FMT_YUV420 && + ct->scale_factor > 2) { + scale_rect.width = ct->out_q.w / 2; + scale_rect.height = ct->out_q.h / 2; + exynos3250_jpeg_try_downscale(ct, &scale_rect); + } + + return 0; +} + +static int s5p_jpeg_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = s5p_jpeg_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + return s5p_jpeg_s_fmt(fh_to_ctx(priv), f); +} + +static int s5p_jpeg_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + int ret; + + ret = s5p_jpeg_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + return s5p_jpeg_s_fmt(fh_to_ctx(priv), f); +} + +static int s5p_jpeg_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + if (sub->type == V4L2_EVENT_SOURCE_CHANGE) + return v4l2_src_change_event_subscribe(fh, sub); + + return -EINVAL; +} + +static int exynos3250_jpeg_try_downscale(struct s5p_jpeg_ctx *ctx, + struct v4l2_rect *r) +{ + int w_ratio, h_ratio, scale_factor, cur_ratio, i; + + w_ratio = ctx->out_q.w / r->width; + h_ratio = ctx->out_q.h / r->height; + + scale_factor = max(w_ratio, h_ratio); + scale_factor = clamp_val(scale_factor, 1, 8); + + /* Align scale ratio to the nearest power of 2 */ + for (i = 0; i <= 3; ++i) { + cur_ratio = 1 << i; + if (scale_factor <= cur_ratio) { + ctx->scale_factor = cur_ratio; + break; + } + } + + r->width = round_down(ctx->out_q.w / ctx->scale_factor, 2); + r->height = round_down(ctx->out_q.h / ctx->scale_factor, 2); + + ctx->crop_rect.width = r->width; + ctx->crop_rect.height = r->height; + ctx->crop_rect.left = 0; + ctx->crop_rect.top = 0; + + ctx->crop_altered = true; + + return 0; +} + +static int exynos3250_jpeg_try_crop(struct s5p_jpeg_ctx *ctx, + struct v4l2_rect *r) +{ + struct v4l2_rect base_rect; + int w_step, h_step; + + switch (ctx->cap_q.fmt->fourcc) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + w_step = 1; + h_step = 2; + break; + case V4L2_PIX_FMT_YUV420: + w_step = 2; + h_step = 2; + break; + default: + w_step = 1; + h_step = 1; + break; + } + + base_rect.top = 0; + base_rect.left = 0; + base_rect.width = ctx->out_q.w; + base_rect.height = ctx->out_q.h; + + r->width = round_down(r->width, w_step); + r->height = round_down(r->height, h_step); + r->left = round_down(r->left, 2); + r->top = round_down(r->top, 2); + + if (!v4l2_rect_enclosed(r, &base_rect)) + return -EINVAL; + + ctx->crop_rect.left = r->left; + ctx->crop_rect.top = r->top; + ctx->crop_rect.width = r->width; + ctx->crop_rect.height = r->height; + + ctx->crop_altered = true; + + return 0; +} + +/* + * V4L2 controls + */ + +static int s5p_jpeg_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct s5p_jpeg_ctx *ctx = fh_to_ctx(priv); + + if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT && + s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + /* For JPEG blob active == default == bounds */ + switch (s->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + s->r.width = ctx->out_q.w; + s->r.height = ctx->out_q.h; + s->r.left = 0; + s->r.top = 0; + break; + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + case V4L2_SEL_TGT_COMPOSE_PADDED: + s->r.width = ctx->crop_rect.width; + s->r.height = ctx->crop_rect.height; + s->r.left = ctx->crop_rect.left; + s->r.top = ctx->crop_rect.top; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * V4L2 controls + */ +static int s5p_jpeg_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct s5p_jpeg_ctx *ctx = fh_to_ctx(file->private_data); + struct v4l2_rect *rect = &s->r; + int ret = -EINVAL; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (s->target == V4L2_SEL_TGT_COMPOSE) { + if (ctx->mode != S5P_JPEG_DECODE) + return -EINVAL; + if (ctx->jpeg->variant->hw3250_compat) + ret = exynos3250_jpeg_try_downscale(ctx, rect); + } else if (s->target == V4L2_SEL_TGT_CROP) { + if (ctx->mode != S5P_JPEG_ENCODE) + return -EINVAL; + if (ctx->jpeg->variant->hw3250_compat) + ret = exynos3250_jpeg_try_crop(ctx, rect); + } + + return ret; +} + +static int s5p_jpeg_g_volatile_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl); + struct s5p_jpeg *jpeg = ctx->jpeg; + unsigned long flags; + + switch (ctrl->id) { + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + spin_lock_irqsave(&jpeg->slock, flags); + ctrl->val = s5p_jpeg_to_user_subsampling(ctx); + spin_unlock_irqrestore(&jpeg->slock, flags); + break; + } + + return 0; +} + +static int s5p_jpeg_adjust_subs_ctrl(struct s5p_jpeg_ctx *ctx, int *ctrl_val) +{ + switch (ctx->jpeg->variant->version) { + case SJPEG_S5P: + return 0; + case SJPEG_EXYNOS3250: + case SJPEG_EXYNOS5420: + /* + * The exynos3250/compatible device can produce JPEG image only + * of 4:4:4 subsampling when given RGB32 source image. + */ + if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB32) + *ctrl_val = 0; + break; + case SJPEG_EXYNOS4: + /* + * The exynos4x12 device requires input raw image fourcc + * to be V4L2_PIX_FMT_GREY if gray jpeg format + * is to be set. + */ + if (ctx->out_q.fmt->fourcc != V4L2_PIX_FMT_GREY && + *ctrl_val == V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY) + return -EINVAL; + break; + } + + /* + * The exynos4x12 and exynos3250/compatible devices require resulting + * jpeg subsampling not to be lower than the input raw image + * subsampling. + */ + if (ctx->out_q.fmt->subsampling > *ctrl_val) + *ctrl_val = ctx->out_q.fmt->subsampling; + + return 0; +} + +static int s5p_jpeg_try_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; + int ret = 0; + + spin_lock_irqsave(&ctx->jpeg->slock, flags); + + if (ctrl->id == V4L2_CID_JPEG_CHROMA_SUBSAMPLING) + ret = s5p_jpeg_adjust_subs_ctrl(ctx, &ctrl->val); + + spin_unlock_irqrestore(&ctx->jpeg->slock, flags); + return ret; +} + +static int s5p_jpeg_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5p_jpeg_ctx *ctx = ctrl_to_ctx(ctrl); + unsigned long flags; + + spin_lock_irqsave(&ctx->jpeg->slock, flags); + + switch (ctrl->id) { + case V4L2_CID_JPEG_COMPRESSION_QUALITY: + ctx->compr_quality = ctrl->val; + break; + case V4L2_CID_JPEG_RESTART_INTERVAL: + ctx->restart_interval = ctrl->val; + break; + case V4L2_CID_JPEG_CHROMA_SUBSAMPLING: + ctx->subsampling = ctrl->val; + break; + } + + spin_unlock_irqrestore(&ctx->jpeg->slock, flags); + return 0; +} + +static const struct v4l2_ctrl_ops s5p_jpeg_ctrl_ops = { + .g_volatile_ctrl = s5p_jpeg_g_volatile_ctrl, + .try_ctrl = s5p_jpeg_try_ctrl, + .s_ctrl = s5p_jpeg_s_ctrl, +}; + +static int s5p_jpeg_controls_create(struct s5p_jpeg_ctx *ctx) +{ + unsigned int mask = ~0x27; /* 444, 422, 420, GRAY */ + struct v4l2_ctrl *ctrl; + int ret; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3); + + if (ctx->mode == S5P_JPEG_ENCODE) { + v4l2_ctrl_new_std(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, + V4L2_CID_JPEG_COMPRESSION_QUALITY, + 0, 3, 1, S5P_JPEG_COMPR_QUAL_WORST); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, + V4L2_CID_JPEG_RESTART_INTERVAL, + 0, 0xffff, 1, 0); + if (ctx->jpeg->variant->version == SJPEG_S5P) + mask = ~0x06; /* 422, 420 */ + } + + ctrl = v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &s5p_jpeg_ctrl_ops, + V4L2_CID_JPEG_CHROMA_SUBSAMPLING, + V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY, mask, + V4L2_JPEG_CHROMA_SUBSAMPLING_422); + + if (ctx->ctrl_handler.error) { + ret = ctx->ctrl_handler.error; + goto error_free; + } + + if (ctx->mode == S5P_JPEG_DECODE) + ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE | + V4L2_CTRL_FLAG_READ_ONLY; + + ret = v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + if (ret < 0) + goto error_free; + + return ret; + +error_free: + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return ret; +} + +static const struct v4l2_ioctl_ops s5p_jpeg_ioctl_ops = { + .vidioc_querycap = s5p_jpeg_querycap, + + .vidioc_enum_fmt_vid_cap = s5p_jpeg_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = s5p_jpeg_enum_fmt_vid_out, + + .vidioc_g_fmt_vid_cap = s5p_jpeg_g_fmt, + .vidioc_g_fmt_vid_out = s5p_jpeg_g_fmt, + + .vidioc_try_fmt_vid_cap = s5p_jpeg_try_fmt_vid_cap, + .vidioc_try_fmt_vid_out = s5p_jpeg_try_fmt_vid_out, + + .vidioc_s_fmt_vid_cap = s5p_jpeg_s_fmt_vid_cap, + .vidioc_s_fmt_vid_out = s5p_jpeg_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_g_selection = s5p_jpeg_g_selection, + .vidioc_s_selection = s5p_jpeg_s_selection, + + .vidioc_subscribe_event = s5p_jpeg_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * ============================================================================ + * mem2mem callbacks + * ============================================================================ + */ + +static void s5p_jpeg_device_run(void *priv) +{ + struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned long src_addr, dst_addr, flags; + + spin_lock_irqsave(&ctx->jpeg->slock, flags); + + src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + src_addr = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0); + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0); + + s5p_jpeg_reset(jpeg->regs); + s5p_jpeg_poweron(jpeg->regs); + s5p_jpeg_proc_mode(jpeg->regs, ctx->mode); + if (ctx->mode == S5P_JPEG_ENCODE) { + if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB565) + s5p_jpeg_input_raw_mode(jpeg->regs, + S5P_JPEG_RAW_IN_565); + else + s5p_jpeg_input_raw_mode(jpeg->regs, + S5P_JPEG_RAW_IN_422); + s5p_jpeg_subsampling_mode(jpeg->regs, ctx->subsampling); + s5p_jpeg_dri(jpeg->regs, ctx->restart_interval); + s5p_jpeg_x(jpeg->regs, ctx->out_q.w); + s5p_jpeg_y(jpeg->regs, ctx->out_q.h); + s5p_jpeg_imgadr(jpeg->regs, src_addr); + s5p_jpeg_jpgadr(jpeg->regs, dst_addr); + + /* ultimately comes from sizeimage from userspace */ + s5p_jpeg_enc_stream_int(jpeg->regs, ctx->cap_q.size); + + /* JPEG RGB to YCbCr conversion matrix */ + s5p_jpeg_coef(jpeg->regs, 1, 1, S5P_JPEG_COEF11); + s5p_jpeg_coef(jpeg->regs, 1, 2, S5P_JPEG_COEF12); + s5p_jpeg_coef(jpeg->regs, 1, 3, S5P_JPEG_COEF13); + s5p_jpeg_coef(jpeg->regs, 2, 1, S5P_JPEG_COEF21); + s5p_jpeg_coef(jpeg->regs, 2, 2, S5P_JPEG_COEF22); + s5p_jpeg_coef(jpeg->regs, 2, 3, S5P_JPEG_COEF23); + s5p_jpeg_coef(jpeg->regs, 3, 1, S5P_JPEG_COEF31); + s5p_jpeg_coef(jpeg->regs, 3, 2, S5P_JPEG_COEF32); + s5p_jpeg_coef(jpeg->regs, 3, 3, S5P_JPEG_COEF33); + + /* + * JPEG IP allows storing 4 quantization tables + * We fill table 0 for luma and table 1 for chroma + */ + s5p_jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality); + s5p_jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality); + /* use table 0 for Y */ + s5p_jpeg_qtbl(jpeg->regs, 1, 0); + /* use table 1 for Cb and Cr*/ + s5p_jpeg_qtbl(jpeg->regs, 2, 1); + s5p_jpeg_qtbl(jpeg->regs, 3, 1); + + /* Y, Cb, Cr use Huffman table 0 */ + s5p_jpeg_htbl_ac(jpeg->regs, 1); + s5p_jpeg_htbl_dc(jpeg->regs, 1); + s5p_jpeg_htbl_ac(jpeg->regs, 2); + s5p_jpeg_htbl_dc(jpeg->regs, 2); + s5p_jpeg_htbl_ac(jpeg->regs, 3); + s5p_jpeg_htbl_dc(jpeg->regs, 3); + } else { /* S5P_JPEG_DECODE */ + s5p_jpeg_rst_int_enable(jpeg->regs, true); + s5p_jpeg_data_num_int_enable(jpeg->regs, true); + s5p_jpeg_final_mcu_num_int_enable(jpeg->regs, true); + if (ctx->cap_q.fmt->fourcc == V4L2_PIX_FMT_YUYV) + s5p_jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_422); + else + s5p_jpeg_outform_raw(jpeg->regs, S5P_JPEG_RAW_OUT_420); + s5p_jpeg_jpgadr(jpeg->regs, src_addr); + s5p_jpeg_imgadr(jpeg->regs, dst_addr); + } + + s5p_jpeg_start(jpeg->regs); + + spin_unlock_irqrestore(&ctx->jpeg->slock, flags); +} + +static void exynos4_jpeg_set_img_addr(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct s5p_jpeg_fmt *fmt; + struct vb2_v4l2_buffer *vb; + struct s5p_jpeg_addr jpeg_addr = {}; + u32 pix_size, padding_bytes = 0; + + jpeg_addr.cb = 0; + jpeg_addr.cr = 0; + + pix_size = ctx->cap_q.w * ctx->cap_q.h; + + if (ctx->mode == S5P_JPEG_ENCODE) { + vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + fmt = ctx->out_q.fmt; + if (ctx->out_q.w % 2 && fmt->h_align > 0) + padding_bytes = ctx->out_q.h; + } else { + fmt = ctx->cap_q.fmt; + vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + } + + jpeg_addr.y = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0); + + if (fmt->colplanes == 2) { + jpeg_addr.cb = jpeg_addr.y + pix_size - padding_bytes; + } else if (fmt->colplanes == 3) { + jpeg_addr.cb = jpeg_addr.y + pix_size; + if (fmt->fourcc == V4L2_PIX_FMT_YUV420) + jpeg_addr.cr = jpeg_addr.cb + pix_size / 4; + else + jpeg_addr.cr = jpeg_addr.cb + pix_size / 2; + } + + exynos4_jpeg_set_frame_buf_address(jpeg->regs, &jpeg_addr); +} + +static void exynos4_jpeg_set_jpeg_addr(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *vb; + unsigned int jpeg_addr = 0; + + if (ctx->mode == S5P_JPEG_ENCODE) + vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + else + vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + + jpeg_addr = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0); + if (jpeg->variant->version == SJPEG_EXYNOS5433 && + ctx->mode == S5P_JPEG_DECODE) + jpeg_addr += ctx->out_q.sos; + exynos4_jpeg_set_stream_buf_address(jpeg->regs, jpeg_addr); +} + +static inline void exynos4_jpeg_set_img_fmt(void __iomem *base, + unsigned int img_fmt) +{ + __exynos4_jpeg_set_img_fmt(base, img_fmt, SJPEG_EXYNOS4); +} + +static inline void exynos5433_jpeg_set_img_fmt(void __iomem *base, + unsigned int img_fmt) +{ + __exynos4_jpeg_set_img_fmt(base, img_fmt, SJPEG_EXYNOS5433); +} + +static inline void exynos4_jpeg_set_enc_out_fmt(void __iomem *base, + unsigned int out_fmt) +{ + __exynos4_jpeg_set_enc_out_fmt(base, out_fmt, SJPEG_EXYNOS4); +} + +static inline void exynos5433_jpeg_set_enc_out_fmt(void __iomem *base, + unsigned int out_fmt) +{ + __exynos4_jpeg_set_enc_out_fmt(base, out_fmt, SJPEG_EXYNOS5433); +} + +static void exynos4_jpeg_device_run(void *priv) +{ + struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg *jpeg = ctx->jpeg; + unsigned int bitstream_size; + unsigned long flags; + + spin_lock_irqsave(&jpeg->slock, flags); + + if (ctx->mode == S5P_JPEG_ENCODE) { + exynos4_jpeg_sw_reset(jpeg->regs); + exynos4_jpeg_set_interrupt(jpeg->regs, jpeg->variant->version); + exynos4_jpeg_set_huf_table_enable(jpeg->regs, 1); + + exynos4_jpeg_set_huff_tbl(jpeg->regs); + + /* + * JPEG IP allows storing 4 quantization tables + * We fill table 0 for luma and table 1 for chroma + */ + exynos4_jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality); + exynos4_jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality); + + exynos4_jpeg_set_encode_tbl_select(jpeg->regs, + ctx->compr_quality); + exynos4_jpeg_set_stream_size(jpeg->regs, ctx->cap_q.w, + ctx->cap_q.h); + + if (ctx->jpeg->variant->version == SJPEG_EXYNOS4) { + exynos4_jpeg_set_enc_out_fmt(jpeg->regs, + ctx->subsampling); + exynos4_jpeg_set_img_fmt(jpeg->regs, + ctx->out_q.fmt->fourcc); + } else { + exynos5433_jpeg_set_enc_out_fmt(jpeg->regs, + ctx->subsampling); + exynos5433_jpeg_set_img_fmt(jpeg->regs, + ctx->out_q.fmt->fourcc); + } + exynos4_jpeg_set_img_addr(ctx); + exynos4_jpeg_set_jpeg_addr(ctx); + exynos4_jpeg_set_encode_hoff_cnt(jpeg->regs, + ctx->out_q.fmt->fourcc); + } else { + exynos4_jpeg_sw_reset(jpeg->regs); + exynos4_jpeg_set_interrupt(jpeg->regs, + jpeg->variant->version); + exynos4_jpeg_set_img_addr(ctx); + exynos4_jpeg_set_jpeg_addr(ctx); + + if (jpeg->variant->version == SJPEG_EXYNOS5433) { + exynos4_jpeg_parse_huff_tbl(ctx); + exynos4_jpeg_parse_decode_h_tbl(ctx); + + exynos4_jpeg_parse_q_tbl(ctx); + exynos4_jpeg_parse_decode_q_tbl(ctx); + + exynos4_jpeg_set_huf_table_enable(jpeg->regs, 1); + + exynos4_jpeg_set_stream_size(jpeg->regs, ctx->cap_q.w, + ctx->cap_q.h); + exynos5433_jpeg_set_enc_out_fmt(jpeg->regs, + ctx->subsampling); + exynos5433_jpeg_set_img_fmt(jpeg->regs, + ctx->cap_q.fmt->fourcc); + bitstream_size = DIV_ROUND_UP(ctx->out_q.size, 16); + } else { + exynos4_jpeg_set_img_fmt(jpeg->regs, + ctx->cap_q.fmt->fourcc); + bitstream_size = DIV_ROUND_UP(ctx->out_q.size, 32); + } + + exynos4_jpeg_set_dec_bitstream_size(jpeg->regs, bitstream_size); + } + + exynos4_jpeg_set_sys_int_enable(jpeg->regs, 1); + exynos4_jpeg_set_enc_dec_mode(jpeg->regs, ctx->mode); + + spin_unlock_irqrestore(&jpeg->slock, flags); +} + +static void exynos3250_jpeg_set_img_addr(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct s5p_jpeg_fmt *fmt; + struct vb2_v4l2_buffer *vb; + struct s5p_jpeg_addr jpeg_addr = {}; + u32 pix_size; + + pix_size = ctx->cap_q.w * ctx->cap_q.h; + + if (ctx->mode == S5P_JPEG_ENCODE) { + vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + fmt = ctx->out_q.fmt; + } else { + vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + fmt = ctx->cap_q.fmt; + } + + jpeg_addr.y = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0); + + if (fmt->colplanes == 2) { + jpeg_addr.cb = jpeg_addr.y + pix_size; + } else if (fmt->colplanes == 3) { + jpeg_addr.cb = jpeg_addr.y + pix_size; + if (fmt->fourcc == V4L2_PIX_FMT_YUV420) + jpeg_addr.cr = jpeg_addr.cb + pix_size / 4; + else + jpeg_addr.cr = jpeg_addr.cb + pix_size / 2; + } + + exynos3250_jpeg_imgadr(jpeg->regs, &jpeg_addr); +} + +static void exynos3250_jpeg_set_jpeg_addr(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg *jpeg = ctx->jpeg; + struct vb2_v4l2_buffer *vb; + unsigned int jpeg_addr = 0; + + if (ctx->mode == S5P_JPEG_ENCODE) + vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + else + vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + + jpeg_addr = vb2_dma_contig_plane_dma_addr(&vb->vb2_buf, 0); + exynos3250_jpeg_jpgadr(jpeg->regs, jpeg_addr); +} + +static void exynos3250_jpeg_device_run(void *priv) +{ + struct s5p_jpeg_ctx *ctx = priv; + struct s5p_jpeg *jpeg = ctx->jpeg; + unsigned long flags; + + spin_lock_irqsave(&ctx->jpeg->slock, flags); + + exynos3250_jpeg_reset(jpeg->regs); + exynos3250_jpeg_set_dma_num(jpeg->regs); + exynos3250_jpeg_poweron(jpeg->regs); + exynos3250_jpeg_clk_set(jpeg->regs); + exynos3250_jpeg_proc_mode(jpeg->regs, ctx->mode); + + if (ctx->mode == S5P_JPEG_ENCODE) { + exynos3250_jpeg_input_raw_fmt(jpeg->regs, + ctx->out_q.fmt->fourcc); + exynos3250_jpeg_dri(jpeg->regs, ctx->restart_interval); + + /* + * JPEG IP allows storing 4 quantization tables + * We fill table 0 for luma and table 1 for chroma + */ + s5p_jpeg_set_qtbl_lum(jpeg->regs, ctx->compr_quality); + s5p_jpeg_set_qtbl_chr(jpeg->regs, ctx->compr_quality); + /* use table 0 for Y */ + exynos3250_jpeg_qtbl(jpeg->regs, 1, 0); + /* use table 1 for Cb and Cr*/ + exynos3250_jpeg_qtbl(jpeg->regs, 2, 1); + exynos3250_jpeg_qtbl(jpeg->regs, 3, 1); + + /* + * Some SoCs require setting Huffman tables before each run + */ + if (jpeg->variant->htbl_reinit) { + s5p_jpeg_set_hdctbl(jpeg->regs); + s5p_jpeg_set_hdctblg(jpeg->regs); + s5p_jpeg_set_hactbl(jpeg->regs); + s5p_jpeg_set_hactblg(jpeg->regs); + } + + /* Y, Cb, Cr use Huffman table 0 */ + exynos3250_jpeg_htbl_ac(jpeg->regs, 1); + exynos3250_jpeg_htbl_dc(jpeg->regs, 1); + exynos3250_jpeg_htbl_ac(jpeg->regs, 2); + exynos3250_jpeg_htbl_dc(jpeg->regs, 2); + exynos3250_jpeg_htbl_ac(jpeg->regs, 3); + exynos3250_jpeg_htbl_dc(jpeg->regs, 3); + + exynos3250_jpeg_set_x(jpeg->regs, ctx->crop_rect.width); + exynos3250_jpeg_set_y(jpeg->regs, ctx->crop_rect.height); + exynos3250_jpeg_stride(jpeg->regs, ctx->out_q.fmt->fourcc, + ctx->out_q.w); + exynos3250_jpeg_offset(jpeg->regs, ctx->crop_rect.left, + ctx->crop_rect.top); + exynos3250_jpeg_set_img_addr(ctx); + exynos3250_jpeg_set_jpeg_addr(ctx); + exynos3250_jpeg_subsampling_mode(jpeg->regs, ctx->subsampling); + + /* ultimately comes from sizeimage from userspace */ + exynos3250_jpeg_enc_stream_bound(jpeg->regs, ctx->cap_q.size); + + if (ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB565 || + ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB565X || + ctx->out_q.fmt->fourcc == V4L2_PIX_FMT_RGB32) + exynos3250_jpeg_set_y16(jpeg->regs, true); + } else { + exynos3250_jpeg_set_img_addr(ctx); + exynos3250_jpeg_set_jpeg_addr(ctx); + exynos3250_jpeg_stride(jpeg->regs, ctx->cap_q.fmt->fourcc, + ctx->cap_q.w); + exynos3250_jpeg_offset(jpeg->regs, 0, 0); + exynos3250_jpeg_dec_scaling_ratio(jpeg->regs, + ctx->scale_factor); + exynos3250_jpeg_dec_stream_size(jpeg->regs, ctx->out_q.size); + exynos3250_jpeg_output_raw_fmt(jpeg->regs, + ctx->cap_q.fmt->fourcc); + } + + exynos3250_jpeg_interrupts_enable(jpeg->regs); + + /* JPEG RGB to YCbCr conversion matrix */ + exynos3250_jpeg_coef(jpeg->regs, ctx->mode); + + exynos3250_jpeg_set_timer(jpeg->regs, EXYNOS3250_IRQ_TIMEOUT); + jpeg->irq_status = 0; + exynos3250_jpeg_start(jpeg->regs); + + spin_unlock_irqrestore(&ctx->jpeg->slock, flags); +} + +static int s5p_jpeg_job_ready(void *priv) +{ + struct s5p_jpeg_ctx *ctx = priv; + + if (ctx->mode == S5P_JPEG_DECODE) { + /* + * We have only one input buffer and one output buffer. If there + * is a resolution change event, no need to continue decoding. + */ + if (ctx->state == JPEGCTX_RESOLUTION_CHANGE) + return 0; + + return ctx->hdr_parsed; + } + + return 1; +} + +static const struct v4l2_m2m_ops s5p_jpeg_m2m_ops = { + .device_run = s5p_jpeg_device_run, + .job_ready = s5p_jpeg_job_ready, +}; + +static const struct v4l2_m2m_ops exynos3250_jpeg_m2m_ops = { + .device_run = exynos3250_jpeg_device_run, + .job_ready = s5p_jpeg_job_ready, +}; + +static const struct v4l2_m2m_ops exynos4_jpeg_m2m_ops = { + .device_run = exynos4_jpeg_device_run, + .job_ready = s5p_jpeg_job_ready, +}; + +/* + * ============================================================================ + * Queue operations + * ============================================================================ + */ + +static int s5p_jpeg_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(vq); + struct s5p_jpeg_q_data *q_data = NULL; + unsigned int size, count = *nbuffers; + + q_data = get_q_data(ctx, vq->type); + BUG_ON(q_data == NULL); + + size = q_data->size; + + /* + * header is parsed during decoding and parsed information stored + * in the context so we do not allow another buffer to overwrite it + */ + if (ctx->mode == S5P_JPEG_DECODE) + count = 1; + + *nbuffers = count; + *nplanes = 1; + sizes[0] = size; + + return 0; +} + +static int s5p_jpeg_buf_prepare(struct vb2_buffer *vb) +{ + struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct s5p_jpeg_q_data *q_data = NULL; + + q_data = get_q_data(ctx, vb->vb2_queue->type); + BUG_ON(q_data == NULL); + + if (vb2_plane_size(vb, 0) < q_data->size) { + pr_err("%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), + (long)q_data->size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, q_data->size); + + return 0; +} + +static void s5p_jpeg_set_capture_queue_data(struct s5p_jpeg_ctx *ctx) +{ + struct s5p_jpeg_q_data *q_data = &ctx->cap_q; + + q_data->w = ctx->out_q.w; + q_data->h = ctx->out_q.h; + + /* + * This call to jpeg_bound_align_image() takes care of width and + * height values alignment when user space calls the QBUF of + * OUTPUT buffer after the S_FMT of CAPTURE buffer. + * Please note that on Exynos4x12 SoCs, resigning from executing + * S_FMT on capture buffer for each JPEG image can result in a + * hardware hangup if subsampling is lower than the one of input + * JPEG. + */ + jpeg_bound_align_image(ctx, &q_data->w, S5P_JPEG_MIN_WIDTH, + S5P_JPEG_MAX_WIDTH, q_data->fmt->h_align, + &q_data->h, S5P_JPEG_MIN_HEIGHT, + S5P_JPEG_MAX_HEIGHT, q_data->fmt->v_align); + + q_data->size = q_data->w * q_data->h * q_data->fmt->depth >> 3; +} + +static void s5p_jpeg_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + if (ctx->mode == S5P_JPEG_DECODE && + vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + static const struct v4l2_event ev_src_ch = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = V4L2_EVENT_SRC_CH_RESOLUTION, + }; + struct vb2_queue *dst_vq; + u32 ori_w; + u32 ori_h; + + dst_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + ori_w = ctx->out_q.w; + ori_h = ctx->out_q.h; + + ctx->hdr_parsed = s5p_jpeg_parse_hdr(&ctx->out_q, + (unsigned long)vb2_plane_vaddr(vb, 0), + min((unsigned long)ctx->out_q.size, + vb2_get_plane_payload(vb, 0)), ctx); + if (!ctx->hdr_parsed) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + return; + } + + /* + * If there is a resolution change event, only update capture + * queue when it is not streaming. Otherwise, update it in + * STREAMOFF. See s5p_jpeg_stop_streaming for detail. + */ + if (ctx->out_q.w != ori_w || ctx->out_q.h != ori_h) { + v4l2_event_queue_fh(&ctx->fh, &ev_src_ch); + if (vb2_is_streaming(dst_vq)) + ctx->state = JPEGCTX_RESOLUTION_CHANGE; + else + s5p_jpeg_set_capture_queue_data(ctx); + } + } + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int s5p_jpeg_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(q); + + return pm_runtime_resume_and_get(ctx->jpeg->dev); +} + +static void s5p_jpeg_stop_streaming(struct vb2_queue *q) +{ + struct s5p_jpeg_ctx *ctx = vb2_get_drv_priv(q); + + /* + * STREAMOFF is an acknowledgment for resolution change event. + * Before STREAMOFF, we still have to return the old resolution and + * subsampling. Update capture queue when the stream is off. + */ + if (ctx->state == JPEGCTX_RESOLUTION_CHANGE && + q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + s5p_jpeg_set_capture_queue_data(ctx); + ctx->state = JPEGCTX_RUNNING; + } + + pm_runtime_put(ctx->jpeg->dev); +} + +static const struct vb2_ops s5p_jpeg_qops = { + .queue_setup = s5p_jpeg_queue_setup, + .buf_prepare = s5p_jpeg_buf_prepare, + .buf_queue = s5p_jpeg_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = s5p_jpeg_start_streaming, + .stop_streaming = s5p_jpeg_stop_streaming, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct s5p_jpeg_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &s5p_jpeg_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->jpeg->lock; + src_vq->dev = ctx->jpeg->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &s5p_jpeg_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->jpeg->lock; + dst_vq->dev = ctx->jpeg->dev; + + return vb2_queue_init(dst_vq); +} + +/* + * ============================================================================ + * ISR + * ============================================================================ + */ + +static irqreturn_t s5p_jpeg_irq(int irq, void *dev_id) +{ + struct s5p_jpeg *jpeg = dev_id; + struct s5p_jpeg_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned long payload_size = 0; + enum vb2_buffer_state state = VB2_BUF_STATE_DONE; + bool enc_jpeg_too_large = false; + bool timer_elapsed = false; + bool op_completed = false; + + spin_lock(&jpeg->slock); + + curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + + src_buf = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + if (curr_ctx->mode == S5P_JPEG_ENCODE) + enc_jpeg_too_large = s5p_jpeg_enc_stream_stat(jpeg->regs); + timer_elapsed = s5p_jpeg_timer_stat(jpeg->regs); + op_completed = s5p_jpeg_result_stat_ok(jpeg->regs); + if (curr_ctx->mode == S5P_JPEG_DECODE) + op_completed = op_completed && + s5p_jpeg_stream_stat_ok(jpeg->regs); + + if (enc_jpeg_too_large) { + state = VB2_BUF_STATE_ERROR; + s5p_jpeg_clear_enc_stream_stat(jpeg->regs); + } else if (timer_elapsed) { + state = VB2_BUF_STATE_ERROR; + s5p_jpeg_clear_timer_stat(jpeg->regs); + } else if (!op_completed) { + state = VB2_BUF_STATE_ERROR; + } else { + payload_size = s5p_jpeg_compressed_size(jpeg->regs); + } + + dst_buf->timecode = src_buf->timecode; + dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp; + dst_buf->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_buf->flags |= + src_buf->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + + v4l2_m2m_buf_done(src_buf, state); + if (curr_ctx->mode == S5P_JPEG_ENCODE) + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload_size); + v4l2_m2m_buf_done(dst_buf, state); + + curr_ctx->subsampling = s5p_jpeg_get_subsampling_mode(jpeg->regs); + spin_unlock(&jpeg->slock); + + s5p_jpeg_clear_int(jpeg->regs); + + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); + return IRQ_HANDLED; +} + +static irqreturn_t exynos4_jpeg_irq(int irq, void *priv) +{ + unsigned int int_status; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + struct s5p_jpeg *jpeg = priv; + struct s5p_jpeg_ctx *curr_ctx; + unsigned long payload_size = 0; + + spin_lock(&jpeg->slock); + + exynos4_jpeg_set_sys_int_enable(jpeg->regs, 0); + + curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + int_status = exynos4_jpeg_get_int_status(jpeg->regs); + + if (int_status) { + switch (int_status & 0x1f) { + case 0x1: + jpeg->irq_ret = ERR_PROT; + break; + case 0x2: + jpeg->irq_ret = OK_ENC_OR_DEC; + break; + case 0x4: + jpeg->irq_ret = ERR_DEC_INVALID_FORMAT; + break; + case 0x8: + jpeg->irq_ret = ERR_MULTI_SCAN; + break; + case 0x10: + jpeg->irq_ret = ERR_FRAME; + break; + default: + jpeg->irq_ret = ERR_UNKNOWN; + break; + } + } else { + jpeg->irq_ret = ERR_UNKNOWN; + } + + if (jpeg->irq_ret == OK_ENC_OR_DEC) { + if (curr_ctx->mode == S5P_JPEG_ENCODE) { + payload_size = exynos4_jpeg_get_stream_size(jpeg->regs); + vb2_set_plane_payload(&dst_vb->vb2_buf, + 0, payload_size); + } + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + } else { + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_ERROR); + } + + if (jpeg->variant->version == SJPEG_EXYNOS4) + curr_ctx->subsampling = exynos4_jpeg_get_frame_fmt(jpeg->regs); + + exynos4_jpeg_set_enc_dec_mode(jpeg->regs, S5P_JPEG_DISABLE); + + spin_unlock(&jpeg->slock); + + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); + return IRQ_HANDLED; +} + +static irqreturn_t exynos3250_jpeg_irq(int irq, void *dev_id) +{ + struct s5p_jpeg *jpeg = dev_id; + struct s5p_jpeg_ctx *curr_ctx; + struct vb2_v4l2_buffer *src_buf, *dst_buf; + unsigned long payload_size = 0; + enum vb2_buffer_state state = VB2_BUF_STATE_DONE; + bool interrupt_timeout = false; + bool stream_error = false; + u32 irq_status; + + spin_lock(&jpeg->slock); + + irq_status = exynos3250_jpeg_get_timer_status(jpeg->regs); + if (irq_status & EXYNOS3250_TIMER_INT_STAT) { + exynos3250_jpeg_clear_timer_status(jpeg->regs); + interrupt_timeout = true; + dev_err(jpeg->dev, "Interrupt timeout occurred.\n"); + } + + irq_status = exynos3250_jpeg_get_int_status(jpeg->regs); + exynos3250_jpeg_clear_int_status(jpeg->regs, irq_status); + + jpeg->irq_status |= irq_status; + + if (jpeg->variant->version == SJPEG_EXYNOS5420 && + irq_status & EXYNOS3250_STREAM_STAT) { + stream_error = true; + dev_err(jpeg->dev, "Syntax error or unrecoverable error occurred.\n"); + } + + curr_ctx = v4l2_m2m_get_curr_priv(jpeg->m2m_dev); + + if (!curr_ctx) + goto exit_unlock; + + if ((irq_status & EXYNOS3250_HEADER_STAT) && + (curr_ctx->mode == S5P_JPEG_DECODE)) { + exynos3250_jpeg_rstart(jpeg->regs); + goto exit_unlock; + } + + if (jpeg->irq_status & (EXYNOS3250_JPEG_DONE | + EXYNOS3250_WDMA_DONE | + EXYNOS3250_RDMA_DONE | + EXYNOS3250_RESULT_STAT)) + payload_size = exynos3250_jpeg_compressed_size(jpeg->regs); + else if (interrupt_timeout || stream_error) + state = VB2_BUF_STATE_ERROR; + else + goto exit_unlock; + + src_buf = v4l2_m2m_src_buf_remove(curr_ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->fh.m2m_ctx); + + dst_buf->timecode = src_buf->timecode; + dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp; + + v4l2_m2m_buf_done(src_buf, state); + if (curr_ctx->mode == S5P_JPEG_ENCODE) + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, payload_size); + v4l2_m2m_buf_done(dst_buf, state); + + curr_ctx->subsampling = + exynos3250_jpeg_get_subsampling_mode(jpeg->regs); + + spin_unlock(&jpeg->slock); + + v4l2_m2m_job_finish(jpeg->m2m_dev, curr_ctx->fh.m2m_ctx); + return IRQ_HANDLED; + +exit_unlock: + spin_unlock(&jpeg->slock); + return IRQ_HANDLED; +} + +static void *jpeg_get_drv_data(struct device *dev); + +/* + * ============================================================================ + * Driver basic infrastructure + * ============================================================================ + */ + +static int s5p_jpeg_probe(struct platform_device *pdev) +{ + struct s5p_jpeg *jpeg; + int i, ret; + + /* JPEG IP abstraction struct */ + jpeg = devm_kzalloc(&pdev->dev, sizeof(struct s5p_jpeg), GFP_KERNEL); + if (!jpeg) + return -ENOMEM; + + jpeg->variant = jpeg_get_drv_data(&pdev->dev); + if (!jpeg->variant) + return -ENODEV; + + mutex_init(&jpeg->lock); + spin_lock_init(&jpeg->slock); + jpeg->dev = &pdev->dev; + + /* memory-mapped registers */ + jpeg->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(jpeg->regs)) + return PTR_ERR(jpeg->regs); + + /* interrupt service routine registration */ + jpeg->irq = ret = platform_get_irq(pdev, 0); + if (ret < 0) { + dev_err(&pdev->dev, "cannot find IRQ\n"); + return ret; + } + + ret = devm_request_irq(&pdev->dev, jpeg->irq, jpeg->variant->jpeg_irq, + 0, dev_name(&pdev->dev), jpeg); + if (ret) { + dev_err(&pdev->dev, "cannot claim IRQ %d\n", jpeg->irq); + return ret; + } + + /* clocks */ + for (i = 0; i < jpeg->variant->num_clocks; i++) { + jpeg->clocks[i] = devm_clk_get(&pdev->dev, + jpeg->variant->clk_names[i]); + if (IS_ERR(jpeg->clocks[i])) { + dev_err(&pdev->dev, "failed to get clock: %s\n", + jpeg->variant->clk_names[i]); + return PTR_ERR(jpeg->clocks[i]); + } + } + + /* v4l2 device */ + ret = v4l2_device_register(&pdev->dev, &jpeg->v4l2_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to register v4l2 device\n"); + return ret; + } + + /* mem2mem device */ + jpeg->m2m_dev = v4l2_m2m_init(jpeg->variant->m2m_ops); + if (IS_ERR(jpeg->m2m_dev)) { + v4l2_err(&jpeg->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(jpeg->m2m_dev); + goto device_register_rollback; + } + + vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); + + /* JPEG encoder /dev/videoX node */ + jpeg->vfd_encoder = video_device_alloc(); + if (!jpeg->vfd_encoder) { + v4l2_err(&jpeg->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto m2m_init_rollback; + } + snprintf(jpeg->vfd_encoder->name, sizeof(jpeg->vfd_encoder->name), + "%s-enc", S5P_JPEG_M2M_NAME); + jpeg->vfd_encoder->fops = &s5p_jpeg_fops; + jpeg->vfd_encoder->ioctl_ops = &s5p_jpeg_ioctl_ops; + jpeg->vfd_encoder->minor = -1; + jpeg->vfd_encoder->release = video_device_release; + jpeg->vfd_encoder->lock = &jpeg->lock; + jpeg->vfd_encoder->v4l2_dev = &jpeg->v4l2_dev; + jpeg->vfd_encoder->vfl_dir = VFL_DIR_M2M; + jpeg->vfd_encoder->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M; + + ret = video_register_device(jpeg->vfd_encoder, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&jpeg->v4l2_dev, "Failed to register video device\n"); + video_device_release(jpeg->vfd_encoder); + goto m2m_init_rollback; + } + + video_set_drvdata(jpeg->vfd_encoder, jpeg); + v4l2_info(&jpeg->v4l2_dev, + "encoder device registered as /dev/video%d\n", + jpeg->vfd_encoder->num); + + /* JPEG decoder /dev/videoX node */ + jpeg->vfd_decoder = video_device_alloc(); + if (!jpeg->vfd_decoder) { + v4l2_err(&jpeg->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto enc_vdev_register_rollback; + } + snprintf(jpeg->vfd_decoder->name, sizeof(jpeg->vfd_decoder->name), + "%s-dec", S5P_JPEG_M2M_NAME); + jpeg->vfd_decoder->fops = &s5p_jpeg_fops; + jpeg->vfd_decoder->ioctl_ops = &s5p_jpeg_ioctl_ops; + jpeg->vfd_decoder->minor = -1; + jpeg->vfd_decoder->release = video_device_release; + jpeg->vfd_decoder->lock = &jpeg->lock; + jpeg->vfd_decoder->v4l2_dev = &jpeg->v4l2_dev; + jpeg->vfd_decoder->vfl_dir = VFL_DIR_M2M; + jpeg->vfd_decoder->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M; + + ret = video_register_device(jpeg->vfd_decoder, VFL_TYPE_VIDEO, -1); + if (ret) { + v4l2_err(&jpeg->v4l2_dev, "Failed to register video device\n"); + video_device_release(jpeg->vfd_decoder); + goto enc_vdev_register_rollback; + } + + video_set_drvdata(jpeg->vfd_decoder, jpeg); + v4l2_info(&jpeg->v4l2_dev, + "decoder device registered as /dev/video%d\n", + jpeg->vfd_decoder->num); + + /* final statements & power management */ + platform_set_drvdata(pdev, jpeg); + + pm_runtime_enable(&pdev->dev); + + v4l2_info(&jpeg->v4l2_dev, "Samsung S5P JPEG codec\n"); + + return 0; + +enc_vdev_register_rollback: + video_unregister_device(jpeg->vfd_encoder); + +m2m_init_rollback: + v4l2_m2m_release(jpeg->m2m_dev); + +device_register_rollback: + v4l2_device_unregister(&jpeg->v4l2_dev); + + return ret; +} + +static int s5p_jpeg_remove(struct platform_device *pdev) +{ + struct s5p_jpeg *jpeg = platform_get_drvdata(pdev); + int i; + + pm_runtime_disable(jpeg->dev); + + video_unregister_device(jpeg->vfd_decoder); + video_unregister_device(jpeg->vfd_encoder); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); + v4l2_m2m_release(jpeg->m2m_dev); + v4l2_device_unregister(&jpeg->v4l2_dev); + + if (!pm_runtime_status_suspended(&pdev->dev)) { + for (i = jpeg->variant->num_clocks - 1; i >= 0; i--) + clk_disable_unprepare(jpeg->clocks[i]); + } + + return 0; +} + +#ifdef CONFIG_PM +static int s5p_jpeg_runtime_suspend(struct device *dev) +{ + struct s5p_jpeg *jpeg = dev_get_drvdata(dev); + int i; + + for (i = jpeg->variant->num_clocks - 1; i >= 0; i--) + clk_disable_unprepare(jpeg->clocks[i]); + + return 0; +} + +static int s5p_jpeg_runtime_resume(struct device *dev) +{ + struct s5p_jpeg *jpeg = dev_get_drvdata(dev); + unsigned long flags; + int i, ret; + + for (i = 0; i < jpeg->variant->num_clocks; i++) { + ret = clk_prepare_enable(jpeg->clocks[i]); + if (ret) { + while (--i >= 0) + clk_disable_unprepare(jpeg->clocks[i]); + return ret; + } + } + + spin_lock_irqsave(&jpeg->slock, flags); + + /* + * JPEG IP allows storing two Huffman tables for each component. + * We fill table 0 for each component and do this here only + * for S5PC210 and Exynos3250 SoCs. Exynos4x12 and Exynos542x SoC + * require programming their Huffman tables each time the encoding + * process is initialized, and thus it is accomplished in the + * device_run callback of m2m_ops. + */ + if (!jpeg->variant->htbl_reinit) { + s5p_jpeg_set_hdctbl(jpeg->regs); + s5p_jpeg_set_hdctblg(jpeg->regs); + s5p_jpeg_set_hactbl(jpeg->regs); + s5p_jpeg_set_hactblg(jpeg->regs); + } + + spin_unlock_irqrestore(&jpeg->slock, flags); + + return 0; +} +#endif /* CONFIG_PM */ + +static const struct dev_pm_ops s5p_jpeg_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) + SET_RUNTIME_PM_OPS(s5p_jpeg_runtime_suspend, s5p_jpeg_runtime_resume, + NULL) +}; + +static struct s5p_jpeg_variant s5p_jpeg_drvdata = { + .version = SJPEG_S5P, + .jpeg_irq = s5p_jpeg_irq, + .m2m_ops = &s5p_jpeg_m2m_ops, + .fmt_ver_flag = SJPEG_FMT_FLAG_S5P, + .clk_names = {"jpeg"}, + .num_clocks = 1, +}; + +static struct s5p_jpeg_variant exynos3250_jpeg_drvdata = { + .version = SJPEG_EXYNOS3250, + .jpeg_irq = exynos3250_jpeg_irq, + .m2m_ops = &exynos3250_jpeg_m2m_ops, + .fmt_ver_flag = SJPEG_FMT_FLAG_EXYNOS3250, + .hw3250_compat = 1, + .clk_names = {"jpeg", "sclk"}, + .num_clocks = 2, +}; + +static struct s5p_jpeg_variant exynos4_jpeg_drvdata = { + .version = SJPEG_EXYNOS4, + .jpeg_irq = exynos4_jpeg_irq, + .m2m_ops = &exynos4_jpeg_m2m_ops, + .fmt_ver_flag = SJPEG_FMT_FLAG_EXYNOS4, + .htbl_reinit = 1, + .clk_names = {"jpeg"}, + .num_clocks = 1, + .hw_ex4_compat = 1, +}; + +static struct s5p_jpeg_variant exynos5420_jpeg_drvdata = { + .version = SJPEG_EXYNOS5420, + .jpeg_irq = exynos3250_jpeg_irq, /* intentionally 3250 */ + .m2m_ops = &exynos3250_jpeg_m2m_ops, /* intentionally 3250 */ + .fmt_ver_flag = SJPEG_FMT_FLAG_EXYNOS3250, /* intentionally 3250 */ + .hw3250_compat = 1, + .htbl_reinit = 1, + .clk_names = {"jpeg"}, + .num_clocks = 1, +}; + +static struct s5p_jpeg_variant exynos5433_jpeg_drvdata = { + .version = SJPEG_EXYNOS5433, + .jpeg_irq = exynos4_jpeg_irq, + .m2m_ops = &exynos4_jpeg_m2m_ops, + .fmt_ver_flag = SJPEG_FMT_FLAG_EXYNOS4, + .htbl_reinit = 1, + .clk_names = {"pclk", "aclk", "aclk_xiu", "sclk"}, + .num_clocks = 4, + .hw_ex4_compat = 1, +}; + +static const struct of_device_id samsung_jpeg_match[] = { + { + .compatible = "samsung,s5pv210-jpeg", + .data = &s5p_jpeg_drvdata, + }, { + .compatible = "samsung,exynos3250-jpeg", + .data = &exynos3250_jpeg_drvdata, + }, { + .compatible = "samsung,exynos4210-jpeg", + .data = &exynos4_jpeg_drvdata, + }, { + .compatible = "samsung,exynos4212-jpeg", + .data = &exynos4_jpeg_drvdata, + }, { + .compatible = "samsung,exynos5420-jpeg", + .data = &exynos5420_jpeg_drvdata, + }, { + .compatible = "samsung,exynos5433-jpeg", + .data = &exynos5433_jpeg_drvdata, + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, samsung_jpeg_match); + +static void *jpeg_get_drv_data(struct device *dev) +{ + struct s5p_jpeg_variant *driver_data = NULL; + const struct of_device_id *match; + + if (!IS_ENABLED(CONFIG_OF) || !dev->of_node) + return &s5p_jpeg_drvdata; + + match = of_match_node(samsung_jpeg_match, dev->of_node); + + if (match) + driver_data = (struct s5p_jpeg_variant *)match->data; + + return driver_data; +} + +static struct platform_driver s5p_jpeg_driver = { + .probe = s5p_jpeg_probe, + .remove = s5p_jpeg_remove, + .driver = { + .of_match_table = of_match_ptr(samsung_jpeg_match), + .name = S5P_JPEG_M2M_NAME, + .pm = &s5p_jpeg_pm_ops, + }, +}; + +module_platform_driver(s5p_jpeg_driver); + +MODULE_AUTHOR("Andrzej Pietrasiewicz "); +MODULE_AUTHOR("Jacek Anaszewski "); +MODULE_DESCRIPTION("Samsung JPEG codec driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h new file mode 100644 index 000000000..5570c79f1 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h @@ -0,0 +1,267 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-core.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#ifndef JPEG_CORE_H_ +#define JPEG_CORE_H_ + +#include +#include +#include +#include + +#define S5P_JPEG_M2M_NAME "s5p-jpeg" + +#define JPEG_MAX_CLOCKS 4 + +/* JPEG compression quality setting */ +#define S5P_JPEG_COMPR_QUAL_BEST 0 +#define S5P_JPEG_COMPR_QUAL_WORST 3 + +/* JPEG RGB to YCbCr conversion matrix coefficients */ +#define S5P_JPEG_COEF11 0x4d +#define S5P_JPEG_COEF12 0x97 +#define S5P_JPEG_COEF13 0x1e +#define S5P_JPEG_COEF21 0x2c +#define S5P_JPEG_COEF22 0x57 +#define S5P_JPEG_COEF23 0x83 +#define S5P_JPEG_COEF31 0x83 +#define S5P_JPEG_COEF32 0x6e +#define S5P_JPEG_COEF33 0x13 + +#define EXYNOS3250_IRQ_TIMEOUT 0x10000000 + +/* a selection of JPEG markers */ +#define JPEG_MARKER_TEM 0x01 +#define JPEG_MARKER_SOF0 0xc0 +#define JPEG_MARKER_DHT 0xc4 +#define JPEG_MARKER_RST 0xd0 +#define JPEG_MARKER_SOI 0xd8 +#define JPEG_MARKER_EOI 0xd9 +#define JPEG_MARKER_SOS 0xda +#define JPEG_MARKER_DQT 0xdb +#define JPEG_MARKER_DHP 0xde + +/* Flags that indicate a format can be used for capture/output */ +#define SJPEG_FMT_FLAG_ENC_CAPTURE (1 << 0) +#define SJPEG_FMT_FLAG_ENC_OUTPUT (1 << 1) +#define SJPEG_FMT_FLAG_DEC_CAPTURE (1 << 2) +#define SJPEG_FMT_FLAG_DEC_OUTPUT (1 << 3) +#define SJPEG_FMT_FLAG_S5P (1 << 4) +#define SJPEG_FMT_FLAG_EXYNOS3250 (1 << 5) +#define SJPEG_FMT_FLAG_EXYNOS4 (1 << 6) +#define SJPEG_FMT_RGB (1 << 7) +#define SJPEG_FMT_NON_RGB (1 << 8) + +#define S5P_JPEG_ENCODE 0 +#define S5P_JPEG_DECODE 1 +#define S5P_JPEG_DISABLE -1 + +#define FMT_TYPE_OUTPUT 0 +#define FMT_TYPE_CAPTURE 1 + +#define SJPEG_SUBSAMPLING_444 0x11 +#define SJPEG_SUBSAMPLING_422 0x21 +#define SJPEG_SUBSAMPLING_420 0x22 + +#define S5P_JPEG_MAX_MARKER 4 + +/* Version numbers */ +enum sjpeg_version { + SJPEG_S5P, + SJPEG_EXYNOS3250, + SJPEG_EXYNOS4, + SJPEG_EXYNOS5420, + SJPEG_EXYNOS5433, +}; + +enum exynos4_jpeg_result { + OK_ENC_OR_DEC, + ERR_PROT, + ERR_DEC_INVALID_FORMAT, + ERR_MULTI_SCAN, + ERR_FRAME, + ERR_UNKNOWN, +}; + +enum exynos4_jpeg_img_quality_level { + QUALITY_LEVEL_1 = 0, /* high */ + QUALITY_LEVEL_2, + QUALITY_LEVEL_3, + QUALITY_LEVEL_4, /* low */ +}; + +enum s5p_jpeg_ctx_state { + JPEGCTX_RUNNING = 0, + JPEGCTX_RESOLUTION_CHANGE, +}; + +/** + * struct s5p_jpeg - JPEG IP abstraction + * @lock: the mutex protecting this structure + * @slock: spinlock protecting the device contexts + * @v4l2_dev: v4l2 device for mem2mem mode + * @vfd_encoder: video device node for encoder mem2mem mode + * @vfd_decoder: video device node for decoder mem2mem mode + * @m2m_dev: v4l2 mem2mem device data + * @regs: JPEG IP registers mapping + * @irq: JPEG IP irq + * @irq_ret: JPEG IP irq result value + * @clocks: JPEG IP clock(s) + * @dev: JPEG IP struct device + * @variant: driver variant to be used + * @irq_status: interrupt flags set during single encode/decode + * operation + */ +struct s5p_jpeg { + struct mutex lock; + spinlock_t slock; + + struct v4l2_device v4l2_dev; + struct video_device *vfd_encoder; + struct video_device *vfd_decoder; + struct v4l2_m2m_dev *m2m_dev; + + void __iomem *regs; + unsigned int irq; + enum exynos4_jpeg_result irq_ret; + struct clk *clocks[JPEG_MAX_CLOCKS]; + struct device *dev; + struct s5p_jpeg_variant *variant; + u32 irq_status; +}; + +struct s5p_jpeg_variant { + unsigned int version; + unsigned int fmt_ver_flag; + unsigned int hw3250_compat:1; + unsigned int htbl_reinit:1; + unsigned int hw_ex4_compat:1; + const struct v4l2_m2m_ops *m2m_ops; + irqreturn_t (*jpeg_irq)(int irq, void *priv); + const char *clk_names[JPEG_MAX_CLOCKS]; + int num_clocks; +}; + +/** + * struct s5p_jpeg_fmt - driver's internal color format data + * @fourcc: the fourcc code, 0 if not applicable + * @depth: number of bits per pixel + * @colplanes: number of color planes (1 for packed formats) + * @memplanes: number of memory planes (1 for packed formats) + * @h_align: horizontal alignment order (align to 2^h_align) + * @v_align: vertical alignment order (align to 2^v_align) + * @subsampling:subsampling of a raw format or a JPEG + * @flags: flags describing format applicability + */ +struct s5p_jpeg_fmt { + u32 fourcc; + int depth; + int colplanes; + int memplanes; + int h_align; + int v_align; + int subsampling; + u32 flags; +}; + +/** + * struct s5p_jpeg_marker - collection of markers from jpeg header + * @marker: markers' positions relative to the buffer beginning + * @len: markers' payload lengths (without length field) + * @n: number of markers in collection + */ +struct s5p_jpeg_marker { + u32 marker[S5P_JPEG_MAX_MARKER]; + u32 len[S5P_JPEG_MAX_MARKER]; + u32 n; +}; + +/** + * struct s5p_jpeg_q_data - parameters of one queue + * @fmt: driver-specific format of this queue + * @w: image width + * @h: image height + * @sos: JPEG_MARKER_SOS's position relative to the buffer beginning + * @dht: JPEG_MARKER_DHT' positions relative to the buffer beginning + * @dqt: JPEG_MARKER_DQT' positions relative to the buffer beginning + * @sof: JPEG_MARKER_SOF0's position relative to the buffer beginning + * @sof_len: JPEG_MARKER_SOF0's payload length (without length field itself) + * @size: image buffer size in bytes + */ +struct s5p_jpeg_q_data { + struct s5p_jpeg_fmt *fmt; + u32 w; + u32 h; + u32 sos; + struct s5p_jpeg_marker dht; + struct s5p_jpeg_marker dqt; + u32 sof; + u32 sof_len; + u32 size; +}; + +/** + * struct s5p_jpeg_ctx - the device context data + * @jpeg: JPEG IP device for this context + * @mode: compression (encode) operation or decompression (decode) + * @compr_quality: destination image quality in compression (encode) mode + * @restart_interval: JPEG restart interval for JPEG encoding + * @subsampling: subsampling of a raw format or a JPEG + * @out_q: source (output) queue information + * @cap_q: destination (capture) queue queue information + * @scale_factor: scale factor for JPEG decoding + * @crop_rect: a rectangle representing crop area of the output buffer + * @fh: V4L2 file handle + * @hdr_parsed: set if header has been parsed during decompression + * @crop_altered: set if crop rectangle has been altered by the user space + * @ctrl_handler: controls handler + * @state: state of the context + */ +struct s5p_jpeg_ctx { + struct s5p_jpeg *jpeg; + unsigned int mode; + unsigned short compr_quality; + unsigned short restart_interval; + unsigned short subsampling; + struct s5p_jpeg_q_data out_q; + struct s5p_jpeg_q_data cap_q; + unsigned int scale_factor; + struct v4l2_rect crop_rect; + struct v4l2_fh fh; + bool hdr_parsed; + bool crop_altered; + struct v4l2_ctrl_handler ctrl_handler; + enum s5p_jpeg_ctx_state state; +}; + +/** + * struct s5p_jpeg_buffer - description of memory containing input JPEG data + * @size: buffer size + * @curr: current position in the buffer + * @data: pointer to the data + */ +struct s5p_jpeg_buffer { + unsigned long size; + unsigned long curr; + unsigned long data; +}; + +/** + * struct s5p_jpeg_addr - JPEG converter physical address set for DMA + * @y: luminance plane physical address + * @cb: Cb plane physical address + * @cr: Cr plane physical address + */ +struct s5p_jpeg_addr { + u32 y; + u32 cb; + u32 cr; +}; + +#endif /* JPEG_CORE_H */ diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c new file mode 100644 index 000000000..637a5104d --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* linux/drivers/media/platform/exynos3250-jpeg/jpeg-hw.h + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Jacek Anaszewski + */ + +#include +#include +#include + +#include "jpeg-core.h" +#include "jpeg-regs.h" +#include "jpeg-hw-exynos3250.h" + +void exynos3250_jpeg_reset(void __iomem *regs) +{ + u32 reg = 1; + int count = 1000; + + writel(1, regs + EXYNOS3250_SW_RESET); + /* no other way but polling for when JPEG IP becomes operational */ + while (reg != 0 && --count > 0) { + udelay(1); + cpu_relax(); + reg = readl(regs + EXYNOS3250_SW_RESET); + } + + reg = 0; + count = 1000; + + while (reg != 1 && --count > 0) { + writel(1, regs + EXYNOS3250_JPGDRI); + udelay(1); + cpu_relax(); + reg = readl(regs + EXYNOS3250_JPGDRI); + } + + writel(0, regs + EXYNOS3250_JPGDRI); +} + +void exynos3250_jpeg_poweron(void __iomem *regs) +{ + writel(EXYNOS3250_POWER_ON, regs + EXYNOS3250_JPGCLKCON); +} + +void exynos3250_jpeg_set_dma_num(void __iomem *regs) +{ + writel(((EXYNOS3250_DMA_MO_COUNT << EXYNOS3250_WDMA_ISSUE_NUM_SHIFT) & + EXYNOS3250_WDMA_ISSUE_NUM_MASK) | + ((EXYNOS3250_DMA_MO_COUNT << EXYNOS3250_RDMA_ISSUE_NUM_SHIFT) & + EXYNOS3250_RDMA_ISSUE_NUM_MASK) | + ((EXYNOS3250_DMA_MO_COUNT << EXYNOS3250_ISSUE_GATHER_NUM_SHIFT) & + EXYNOS3250_ISSUE_GATHER_NUM_MASK), + regs + EXYNOS3250_DMA_ISSUE_NUM); +} + +void exynos3250_jpeg_clk_set(void __iomem *base) +{ + u32 reg; + + reg = readl(base + EXYNOS3250_JPGCMOD) & ~EXYNOS3250_HALF_EN_MASK; + + writel(reg | EXYNOS3250_HALF_EN, base + EXYNOS3250_JPGCMOD); +} + +void exynos3250_jpeg_input_raw_fmt(void __iomem *regs, unsigned int fmt) +{ + u32 reg; + + reg = readl(regs + EXYNOS3250_JPGCMOD) & + EXYNOS3250_MODE_Y16_MASK; + + switch (fmt) { + case V4L2_PIX_FMT_RGB32: + reg |= EXYNOS3250_MODE_SEL_ARGB8888; + break; + case V4L2_PIX_FMT_BGR32: + reg |= EXYNOS3250_MODE_SEL_ARGB8888 | EXYNOS3250_SRC_SWAP_RGB; + break; + case V4L2_PIX_FMT_RGB565: + reg |= EXYNOS3250_MODE_SEL_RGB565; + break; + case V4L2_PIX_FMT_RGB565X: + reg |= EXYNOS3250_MODE_SEL_RGB565 | EXYNOS3250_SRC_SWAP_RGB; + break; + case V4L2_PIX_FMT_YUYV: + reg |= EXYNOS3250_MODE_SEL_422_1P_LUM_CHR; + break; + case V4L2_PIX_FMT_YVYU: + reg |= EXYNOS3250_MODE_SEL_422_1P_LUM_CHR | + EXYNOS3250_SRC_SWAP_UV; + break; + case V4L2_PIX_FMT_UYVY: + reg |= EXYNOS3250_MODE_SEL_422_1P_CHR_LUM; + break; + case V4L2_PIX_FMT_VYUY: + reg |= EXYNOS3250_MODE_SEL_422_1P_CHR_LUM | + EXYNOS3250_SRC_SWAP_UV; + break; + case V4L2_PIX_FMT_NV12: + reg |= EXYNOS3250_MODE_SEL_420_2P | EXYNOS3250_SRC_NV12; + break; + case V4L2_PIX_FMT_NV21: + reg |= EXYNOS3250_MODE_SEL_420_2P | EXYNOS3250_SRC_NV21; + break; + case V4L2_PIX_FMT_YUV420: + reg |= EXYNOS3250_MODE_SEL_420_3P; + break; + default: + break; + + } + + writel(reg, regs + EXYNOS3250_JPGCMOD); +} + +void exynos3250_jpeg_set_y16(void __iomem *regs, bool y16) +{ + u32 reg; + + reg = readl(regs + EXYNOS3250_JPGCMOD); + if (y16) + reg |= EXYNOS3250_MODE_Y16; + else + reg &= ~EXYNOS3250_MODE_Y16_MASK; + writel(reg, regs + EXYNOS3250_JPGCMOD); +} + +void exynos3250_jpeg_proc_mode(void __iomem *regs, unsigned int mode) +{ + u32 reg, m; + + if (mode == S5P_JPEG_ENCODE) + m = EXYNOS3250_PROC_MODE_COMPR; + else + m = EXYNOS3250_PROC_MODE_DECOMPR; + reg = readl(regs + EXYNOS3250_JPGMOD); + reg &= ~EXYNOS3250_PROC_MODE_MASK; + reg |= m; + writel(reg, regs + EXYNOS3250_JPGMOD); +} + +void exynos3250_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode) +{ + u32 reg, m = 0; + + switch (mode) { + case V4L2_JPEG_CHROMA_SUBSAMPLING_444: + m = EXYNOS3250_SUBSAMPLING_MODE_444; + break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_422: + m = EXYNOS3250_SUBSAMPLING_MODE_422; + break; + case V4L2_JPEG_CHROMA_SUBSAMPLING_420: + m = EXYNOS3250_SUBSAMPLING_MODE_420; + break; + } + + reg = readl(regs + EXYNOS3250_JPGMOD); + reg &= ~EXYNOS3250_SUBSAMPLING_MODE_MASK; + reg |= m; + writel(reg, regs + EXYNOS3250_JPGMOD); +} + +unsigned int exynos3250_jpeg_get_subsampling_mode(void __iomem *regs) +{ + return readl(regs + EXYNOS3250_JPGMOD) & + EXYNOS3250_SUBSAMPLING_MODE_MASK; +} + +void exynos3250_jpeg_dri(void __iomem *regs, unsigned int dri) +{ + u32 reg; + + reg = dri & EXYNOS3250_JPGDRI_MASK; + writel(reg, regs + EXYNOS3250_JPGDRI); +} + +void exynos3250_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n) +{ + unsigned long reg; + + reg = readl(regs + EXYNOS3250_QHTBL); + reg &= ~EXYNOS3250_QT_NUM_MASK(t); + reg |= (n << EXYNOS3250_QT_NUM_SHIFT(t)) & + EXYNOS3250_QT_NUM_MASK(t); + writel(reg, regs + EXYNOS3250_QHTBL); +} + +void exynos3250_jpeg_htbl_ac(void __iomem *regs, unsigned int t) +{ + unsigned long reg; + + reg = readl(regs + EXYNOS3250_QHTBL); + reg &= ~EXYNOS3250_HT_NUM_AC_MASK(t); + /* this driver uses table 0 for all color components */ + reg |= (0 << EXYNOS3250_HT_NUM_AC_SHIFT(t)) & + EXYNOS3250_HT_NUM_AC_MASK(t); + writel(reg, regs + EXYNOS3250_QHTBL); +} + +void exynos3250_jpeg_htbl_dc(void __iomem *regs, unsigned int t) +{ + unsigned long reg; + + reg = readl(regs + EXYNOS3250_QHTBL); + reg &= ~EXYNOS3250_HT_NUM_DC_MASK(t); + /* this driver uses table 0 for all color components */ + reg |= (0 << EXYNOS3250_HT_NUM_DC_SHIFT(t)) & + EXYNOS3250_HT_NUM_DC_MASK(t); + writel(reg, regs + EXYNOS3250_QHTBL); +} + +void exynos3250_jpeg_set_y(void __iomem *regs, unsigned int y) +{ + u32 reg; + + reg = y & EXYNOS3250_JPGY_MASK; + writel(reg, regs + EXYNOS3250_JPGY); +} + +void exynos3250_jpeg_set_x(void __iomem *regs, unsigned int x) +{ + u32 reg; + + reg = x & EXYNOS3250_JPGX_MASK; + writel(reg, regs + EXYNOS3250_JPGX); +} + +#if 0 /* Currently unused */ +unsigned int exynos3250_jpeg_get_y(void __iomem *regs) +{ + return readl(regs + EXYNOS3250_JPGY); +} + +unsigned int exynos3250_jpeg_get_x(void __iomem *regs) +{ + return readl(regs + EXYNOS3250_JPGX); +} +#endif + +void exynos3250_jpeg_interrupts_enable(void __iomem *regs) +{ + u32 reg; + + reg = readl(regs + EXYNOS3250_JPGINTSE); + reg |= (EXYNOS3250_JPEG_DONE_EN | + EXYNOS3250_WDMA_DONE_EN | + EXYNOS3250_RDMA_DONE_EN | + EXYNOS3250_ENC_STREAM_INT_EN | + EXYNOS3250_CORE_DONE_EN | + EXYNOS3250_ERR_INT_EN | + EXYNOS3250_HEAD_INT_EN); + writel(reg, regs + EXYNOS3250_JPGINTSE); +} + +void exynos3250_jpeg_enc_stream_bound(void __iomem *regs, unsigned int size) +{ + u32 reg; + + reg = size & EXYNOS3250_ENC_STREAM_BOUND_MASK; + writel(reg, regs + EXYNOS3250_ENC_STREAM_BOUND); +} + +void exynos3250_jpeg_output_raw_fmt(void __iomem *regs, unsigned int fmt) +{ + u32 reg; + + switch (fmt) { + case V4L2_PIX_FMT_RGB32: + reg = EXYNOS3250_OUT_FMT_ARGB8888; + break; + case V4L2_PIX_FMT_BGR32: + reg = EXYNOS3250_OUT_FMT_ARGB8888 | EXYNOS3250_OUT_SWAP_RGB; + break; + case V4L2_PIX_FMT_RGB565: + reg = EXYNOS3250_OUT_FMT_RGB565; + break; + case V4L2_PIX_FMT_RGB565X: + reg = EXYNOS3250_OUT_FMT_RGB565 | EXYNOS3250_OUT_SWAP_RGB; + break; + case V4L2_PIX_FMT_YUYV: + reg = EXYNOS3250_OUT_FMT_422_1P_LUM_CHR; + break; + case V4L2_PIX_FMT_YVYU: + reg = EXYNOS3250_OUT_FMT_422_1P_LUM_CHR | + EXYNOS3250_OUT_SWAP_UV; + break; + case V4L2_PIX_FMT_UYVY: + reg = EXYNOS3250_OUT_FMT_422_1P_CHR_LUM; + break; + case V4L2_PIX_FMT_VYUY: + reg = EXYNOS3250_OUT_FMT_422_1P_CHR_LUM | + EXYNOS3250_OUT_SWAP_UV; + break; + case V4L2_PIX_FMT_NV12: + reg = EXYNOS3250_OUT_FMT_420_2P | EXYNOS3250_OUT_NV12; + break; + case V4L2_PIX_FMT_NV21: + reg = EXYNOS3250_OUT_FMT_420_2P | EXYNOS3250_OUT_NV21; + break; + case V4L2_PIX_FMT_YUV420: + reg = EXYNOS3250_OUT_FMT_420_3P; + break; + default: + reg = 0; + break; + } + + writel(reg, regs + EXYNOS3250_OUTFORM); +} + +void exynos3250_jpeg_jpgadr(void __iomem *regs, unsigned int addr) +{ + writel(addr, regs + EXYNOS3250_JPG_JPGADR); +} + +void exynos3250_jpeg_imgadr(void __iomem *regs, struct s5p_jpeg_addr *img_addr) +{ + writel(img_addr->y, regs + EXYNOS3250_LUMA_BASE); + writel(img_addr->cb, regs + EXYNOS3250_CHROMA_BASE); + writel(img_addr->cr, regs + EXYNOS3250_CHROMA_CR_BASE); +} + +void exynos3250_jpeg_stride(void __iomem *regs, unsigned int img_fmt, + unsigned int width) +{ + u32 reg_luma = 0, reg_cr = 0, reg_cb = 0; + + switch (img_fmt) { + case V4L2_PIX_FMT_RGB32: + reg_luma = 4 * width; + break; + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB565X: + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + reg_luma = 2 * width; + break; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + reg_luma = width; + reg_cb = reg_luma; + break; + case V4L2_PIX_FMT_YUV420: + reg_luma = width; + reg_cb = reg_cr = reg_luma / 2; + break; + default: + break; + } + + writel(reg_luma, regs + EXYNOS3250_LUMA_STRIDE); + writel(reg_cb, regs + EXYNOS3250_CHROMA_STRIDE); + writel(reg_cr, regs + EXYNOS3250_CHROMA_CR_STRIDE); +} + +void exynos3250_jpeg_offset(void __iomem *regs, unsigned int x_offset, + unsigned int y_offset) +{ + u32 reg; + + reg = (y_offset << EXYNOS3250_LUMA_YY_OFFSET_SHIFT) & + EXYNOS3250_LUMA_YY_OFFSET_MASK; + reg |= (x_offset << EXYNOS3250_LUMA_YX_OFFSET_SHIFT) & + EXYNOS3250_LUMA_YX_OFFSET_MASK; + + writel(reg, regs + EXYNOS3250_LUMA_XY_OFFSET); + + reg = (y_offset << EXYNOS3250_CHROMA_YY_OFFSET_SHIFT) & + EXYNOS3250_CHROMA_YY_OFFSET_MASK; + reg |= (x_offset << EXYNOS3250_CHROMA_YX_OFFSET_SHIFT) & + EXYNOS3250_CHROMA_YX_OFFSET_MASK; + + writel(reg, regs + EXYNOS3250_CHROMA_XY_OFFSET); + + reg = (y_offset << EXYNOS3250_CHROMA_CR_YY_OFFSET_SHIFT) & + EXYNOS3250_CHROMA_CR_YY_OFFSET_MASK; + reg |= (x_offset << EXYNOS3250_CHROMA_CR_YX_OFFSET_SHIFT) & + EXYNOS3250_CHROMA_CR_YX_OFFSET_MASK; + + writel(reg, regs + EXYNOS3250_CHROMA_CR_XY_OFFSET); +} + +void exynos3250_jpeg_coef(void __iomem *base, unsigned int mode) +{ + if (mode == S5P_JPEG_ENCODE) { + writel(EXYNOS3250_JPEG_ENC_COEF1, + base + EXYNOS3250_JPG_COEF(1)); + writel(EXYNOS3250_JPEG_ENC_COEF2, + base + EXYNOS3250_JPG_COEF(2)); + writel(EXYNOS3250_JPEG_ENC_COEF3, + base + EXYNOS3250_JPG_COEF(3)); + } else { + writel(EXYNOS3250_JPEG_DEC_COEF1, + base + EXYNOS3250_JPG_COEF(1)); + writel(EXYNOS3250_JPEG_DEC_COEF2, + base + EXYNOS3250_JPG_COEF(2)); + writel(EXYNOS3250_JPEG_DEC_COEF3, + base + EXYNOS3250_JPG_COEF(3)); + } +} + +void exynos3250_jpeg_start(void __iomem *regs) +{ + writel(1, regs + EXYNOS3250_JSTART); +} + +void exynos3250_jpeg_rstart(void __iomem *regs) +{ + writel(1, regs + EXYNOS3250_JRSTART); +} + +unsigned int exynos3250_jpeg_get_int_status(void __iomem *regs) +{ + return readl(regs + EXYNOS3250_JPGINTST); +} + +void exynos3250_jpeg_clear_int_status(void __iomem *regs, + unsigned int value) +{ + writel(value, regs + EXYNOS3250_JPGINTST); +} + +unsigned int exynos3250_jpeg_operating(void __iomem *regs) +{ + return readl(regs + S5P_JPGOPR) & EXYNOS3250_JPGOPR_MASK; +} + +unsigned int exynos3250_jpeg_compressed_size(void __iomem *regs) +{ + return readl(regs + EXYNOS3250_JPGCNT) & EXYNOS3250_JPGCNT_MASK; +} + +void exynos3250_jpeg_dec_stream_size(void __iomem *regs, + unsigned int size) +{ + writel(size & EXYNOS3250_DEC_STREAM_MASK, + regs + EXYNOS3250_DEC_STREAM_SIZE); +} + +void exynos3250_jpeg_dec_scaling_ratio(void __iomem *regs, + unsigned int sratio) +{ + switch (sratio) { + case 1: + default: + sratio = EXYNOS3250_DEC_SCALE_FACTOR_8_8; + break; + case 2: + sratio = EXYNOS3250_DEC_SCALE_FACTOR_4_8; + break; + case 4: + sratio = EXYNOS3250_DEC_SCALE_FACTOR_2_8; + break; + case 8: + sratio = EXYNOS3250_DEC_SCALE_FACTOR_1_8; + break; + } + + writel(sratio & EXYNOS3250_DEC_SCALE_FACTOR_MASK, + regs + EXYNOS3250_DEC_SCALING_RATIO); +} + +void exynos3250_jpeg_set_timer(void __iomem *regs, unsigned int time_value) +{ + time_value &= EXYNOS3250_TIMER_INIT_MASK; + + writel(EXYNOS3250_TIMER_INT_STAT | time_value, + regs + EXYNOS3250_TIMER_SE); +} + +unsigned int exynos3250_jpeg_get_timer_status(void __iomem *regs) +{ + return readl(regs + EXYNOS3250_TIMER_ST); +} + +void exynos3250_jpeg_clear_timer_status(void __iomem *regs) +{ + writel(EXYNOS3250_TIMER_INT_STAT, regs + EXYNOS3250_TIMER_ST); +} diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h new file mode 100644 index 000000000..15af928fa --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos3250.h + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Jacek Anaszewski + */ +#ifndef JPEG_HW_EXYNOS3250_H_ +#define JPEG_HW_EXYNOS3250_H_ + +#include +#include + +#include "jpeg-regs.h" + +void exynos3250_jpeg_reset(void __iomem *regs); +void exynos3250_jpeg_poweron(void __iomem *regs); +void exynos3250_jpeg_set_dma_num(void __iomem *regs); +void exynos3250_jpeg_clk_set(void __iomem *base); +void exynos3250_jpeg_input_raw_fmt(void __iomem *regs, unsigned int fmt); +void exynos3250_jpeg_output_raw_fmt(void __iomem *regs, unsigned int fmt); +void exynos3250_jpeg_set_y16(void __iomem *regs, bool y16); +void exynos3250_jpeg_proc_mode(void __iomem *regs, unsigned int mode); +void exynos3250_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode); +unsigned int exynos3250_jpeg_get_subsampling_mode(void __iomem *regs); +void exynos3250_jpeg_dri(void __iomem *regs, unsigned int dri); +void exynos3250_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n); +void exynos3250_jpeg_htbl_ac(void __iomem *regs, unsigned int t); +void exynos3250_jpeg_htbl_dc(void __iomem *regs, unsigned int t); +void exynos3250_jpeg_set_y(void __iomem *regs, unsigned int y); +void exynos3250_jpeg_set_x(void __iomem *regs, unsigned int x); +void exynos3250_jpeg_interrupts_enable(void __iomem *regs); +void exynos3250_jpeg_enc_stream_bound(void __iomem *regs, unsigned int size); +void exynos3250_jpeg_outform_raw(void __iomem *regs, unsigned long format); +void exynos3250_jpeg_jpgadr(void __iomem *regs, unsigned int addr); +void exynos3250_jpeg_imgadr(void __iomem *regs, struct s5p_jpeg_addr *img_addr); +void exynos3250_jpeg_stride(void __iomem *regs, unsigned int img_fmt, + unsigned int width); +void exynos3250_jpeg_offset(void __iomem *regs, unsigned int x_offset, + unsigned int y_offset); +void exynos3250_jpeg_coef(void __iomem *base, unsigned int mode); +void exynos3250_jpeg_start(void __iomem *regs); +void exynos3250_jpeg_rstart(void __iomem *regs); +unsigned int exynos3250_jpeg_get_int_status(void __iomem *regs); +void exynos3250_jpeg_clear_int_status(void __iomem *regs, + unsigned int value); +unsigned int exynos3250_jpeg_operating(void __iomem *regs); +unsigned int exynos3250_jpeg_compressed_size(void __iomem *regs); +void exynos3250_jpeg_dec_stream_size(void __iomem *regs, unsigned int size); +void exynos3250_jpeg_dec_scaling_ratio(void __iomem *regs, unsigned int sratio); +void exynos3250_jpeg_set_timer(void __iomem *regs, unsigned int time_value); +unsigned int exynos3250_jpeg_get_timer_status(void __iomem *regs); +void exynos3250_jpeg_set_timer_status(void __iomem *regs); +void exynos3250_jpeg_clear_timer_status(void __iomem *regs); + +#endif /* JPEG_HW_EXYNOS3250_H_ */ diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c new file mode 100644 index 000000000..0828cfa78 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.c @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Author: Jacek Anaszewski + * + * Register interface file for JPEG driver on Exynos4x12. + */ +#include +#include + +#include "jpeg-core.h" +#include "jpeg-hw-exynos4.h" +#include "jpeg-regs.h" + +void exynos4_jpeg_sw_reset(void __iomem *base) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG); + writel(reg & ~(EXYNOS4_DEC_MODE | EXYNOS4_ENC_MODE), + base + EXYNOS4_JPEG_CNTL_REG); + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG); + writel(reg & ~EXYNOS4_SOFT_RESET_HI, base + EXYNOS4_JPEG_CNTL_REG); + + udelay(100); + + writel(reg | EXYNOS4_SOFT_RESET_HI, base + EXYNOS4_JPEG_CNTL_REG); +} + +void exynos4_jpeg_set_enc_dec_mode(void __iomem *base, unsigned int mode) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG); + /* set exynos4_jpeg mod register */ + if (mode == S5P_JPEG_DECODE) { + writel((reg & EXYNOS4_ENC_DEC_MODE_MASK) | + EXYNOS4_DEC_MODE, + base + EXYNOS4_JPEG_CNTL_REG); + } else if (mode == S5P_JPEG_ENCODE) {/* encode */ + writel((reg & EXYNOS4_ENC_DEC_MODE_MASK) | + EXYNOS4_ENC_MODE, + base + EXYNOS4_JPEG_CNTL_REG); + } else { /* disable both */ + writel(reg & EXYNOS4_ENC_DEC_MODE_MASK, + base + EXYNOS4_JPEG_CNTL_REG); + } +} + +void __exynos4_jpeg_set_img_fmt(void __iomem *base, unsigned int img_fmt, + unsigned int version) +{ + unsigned int reg; + unsigned int exynos4_swap_chroma_cbcr; + unsigned int exynos4_swap_chroma_crcb; + + if (version == SJPEG_EXYNOS4) { + exynos4_swap_chroma_cbcr = EXYNOS4_SWAP_CHROMA_CBCR; + exynos4_swap_chroma_crcb = EXYNOS4_SWAP_CHROMA_CRCB; + } else { + exynos4_swap_chroma_cbcr = EXYNOS5433_SWAP_CHROMA_CBCR; + exynos4_swap_chroma_crcb = EXYNOS5433_SWAP_CHROMA_CRCB; + } + + reg = readl(base + EXYNOS4_IMG_FMT_REG) & + EXYNOS4_ENC_IN_FMT_MASK; /* clear except enc format */ + + switch (img_fmt) { + case V4L2_PIX_FMT_GREY: + reg = reg | EXYNOS4_ENC_GRAY_IMG | EXYNOS4_GRAY_IMG_IP; + break; + case V4L2_PIX_FMT_RGB32: + reg = reg | EXYNOS4_ENC_RGB_IMG | + EXYNOS4_RGB_IP_RGB_32BIT_IMG; + break; + case V4L2_PIX_FMT_RGB565: + reg = reg | EXYNOS4_ENC_RGB_IMG | + EXYNOS4_RGB_IP_RGB_16BIT_IMG; + break; + case V4L2_PIX_FMT_NV24: + reg = reg | EXYNOS4_ENC_YUV_444_IMG | + EXYNOS4_YUV_444_IP_YUV_444_2P_IMG | + exynos4_swap_chroma_cbcr; + break; + case V4L2_PIX_FMT_NV42: + reg = reg | EXYNOS4_ENC_YUV_444_IMG | + EXYNOS4_YUV_444_IP_YUV_444_2P_IMG | + exynos4_swap_chroma_crcb; + break; + case V4L2_PIX_FMT_YUYV: + reg = reg | EXYNOS4_DEC_YUV_422_IMG | + EXYNOS4_YUV_422_IP_YUV_422_1P_IMG | + exynos4_swap_chroma_cbcr; + break; + + case V4L2_PIX_FMT_YVYU: + reg = reg | EXYNOS4_DEC_YUV_422_IMG | + EXYNOS4_YUV_422_IP_YUV_422_1P_IMG | + exynos4_swap_chroma_crcb; + break; + case V4L2_PIX_FMT_NV16: + reg = reg | EXYNOS4_DEC_YUV_422_IMG | + EXYNOS4_YUV_422_IP_YUV_422_2P_IMG | + exynos4_swap_chroma_cbcr; + break; + case V4L2_PIX_FMT_NV61: + reg = reg | EXYNOS4_DEC_YUV_422_IMG | + EXYNOS4_YUV_422_IP_YUV_422_2P_IMG | + exynos4_swap_chroma_crcb; + break; + case V4L2_PIX_FMT_NV12: + reg = reg | EXYNOS4_DEC_YUV_420_IMG | + EXYNOS4_YUV_420_IP_YUV_420_2P_IMG | + exynos4_swap_chroma_cbcr; + break; + case V4L2_PIX_FMT_NV21: + reg = reg | EXYNOS4_DEC_YUV_420_IMG | + EXYNOS4_YUV_420_IP_YUV_420_2P_IMG | + exynos4_swap_chroma_crcb; + break; + case V4L2_PIX_FMT_YUV420: + reg = reg | EXYNOS4_DEC_YUV_420_IMG | + EXYNOS4_YUV_420_IP_YUV_420_3P_IMG | + exynos4_swap_chroma_cbcr; + break; + default: + break; + + } + + writel(reg, base + EXYNOS4_IMG_FMT_REG); +} + +void __exynos4_jpeg_set_enc_out_fmt(void __iomem *base, unsigned int out_fmt, + unsigned int version) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_IMG_FMT_REG) & + ~(version == SJPEG_EXYNOS4 ? EXYNOS4_ENC_FMT_MASK : + EXYNOS5433_ENC_FMT_MASK); /* clear enc format */ + + switch (out_fmt) { + case V4L2_JPEG_CHROMA_SUBSAMPLING_GRAY: + reg = reg | EXYNOS4_ENC_FMT_GRAY; + break; + + case V4L2_JPEG_CHROMA_SUBSAMPLING_444: + reg = reg | EXYNOS4_ENC_FMT_YUV_444; + break; + + case V4L2_JPEG_CHROMA_SUBSAMPLING_422: + reg = reg | EXYNOS4_ENC_FMT_YUV_422; + break; + + case V4L2_JPEG_CHROMA_SUBSAMPLING_420: + reg = reg | EXYNOS4_ENC_FMT_YUV_420; + break; + + default: + break; + } + + writel(reg, base + EXYNOS4_IMG_FMT_REG); +} + +void exynos4_jpeg_set_interrupt(void __iomem *base, unsigned int version) +{ + unsigned int reg; + + if (version == SJPEG_EXYNOS4) { + reg = readl(base + EXYNOS4_INT_EN_REG) & ~EXYNOS4_INT_EN_MASK; + writel(reg | EXYNOS4_INT_EN_ALL, base + EXYNOS4_INT_EN_REG); + } else { + reg = readl(base + EXYNOS4_INT_EN_REG) & + ~EXYNOS5433_INT_EN_MASK; + writel(reg | EXYNOS5433_INT_EN_ALL, base + EXYNOS4_INT_EN_REG); + } +} + +unsigned int exynos4_jpeg_get_int_status(void __iomem *base) +{ + return readl(base + EXYNOS4_INT_STATUS_REG); +} + +unsigned int exynos4_jpeg_get_fifo_status(void __iomem *base) +{ + return readl(base + EXYNOS4_FIFO_STATUS_REG); +} + +void exynos4_jpeg_set_huf_table_enable(void __iomem *base, int value) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG) & ~EXYNOS4_HUF_TBL_EN; + + if (value == 1) + writel(reg | EXYNOS4_HUF_TBL_EN, + base + EXYNOS4_JPEG_CNTL_REG); + else + writel(reg & ~EXYNOS4_HUF_TBL_EN, + base + EXYNOS4_JPEG_CNTL_REG); +} + +void exynos4_jpeg_set_sys_int_enable(void __iomem *base, int value) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_JPEG_CNTL_REG) & ~(EXYNOS4_SYS_INT_EN); + + if (value == 1) + writel(reg | EXYNOS4_SYS_INT_EN, base + EXYNOS4_JPEG_CNTL_REG); + else + writel(reg & ~EXYNOS4_SYS_INT_EN, base + EXYNOS4_JPEG_CNTL_REG); +} + +void exynos4_jpeg_set_stream_buf_address(void __iomem *base, + unsigned int address) +{ + writel(address, base + EXYNOS4_OUT_MEM_BASE_REG); +} + +void exynos4_jpeg_set_stream_size(void __iomem *base, + unsigned int x_value, unsigned int y_value) +{ + writel(0x0, base + EXYNOS4_JPEG_IMG_SIZE_REG); /* clear */ + writel(EXYNOS4_X_SIZE(x_value) | EXYNOS4_Y_SIZE(y_value), + base + EXYNOS4_JPEG_IMG_SIZE_REG); +} + +void exynos4_jpeg_set_frame_buf_address(void __iomem *base, + struct s5p_jpeg_addr *exynos4_jpeg_addr) +{ + writel(exynos4_jpeg_addr->y, base + EXYNOS4_IMG_BA_PLANE_1_REG); + writel(exynos4_jpeg_addr->cb, base + EXYNOS4_IMG_BA_PLANE_2_REG); + writel(exynos4_jpeg_addr->cr, base + EXYNOS4_IMG_BA_PLANE_3_REG); +} + +void exynos4_jpeg_set_encode_tbl_select(void __iomem *base, + enum exynos4_jpeg_img_quality_level level) +{ + unsigned int reg; + + reg = EXYNOS4_Q_TBL_COMP1_0 | EXYNOS4_Q_TBL_COMP2_1 | + EXYNOS4_Q_TBL_COMP3_1 | + EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_1 | + EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_0 | + EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_1; + + writel(reg, base + EXYNOS4_TBL_SEL_REG); +} + +void exynos4_jpeg_set_dec_components(void __iomem *base, int n) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_TBL_SEL_REG); + + reg |= EXYNOS4_NF(n); + writel(reg, base + EXYNOS4_TBL_SEL_REG); +} + +void exynos4_jpeg_select_dec_q_tbl(void __iomem *base, char c, char x) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_TBL_SEL_REG); + + reg |= EXYNOS4_Q_TBL_COMP(c, x); + writel(reg, base + EXYNOS4_TBL_SEL_REG); +} + +void exynos4_jpeg_select_dec_h_tbl(void __iomem *base, char c, char x) +{ + unsigned int reg; + + reg = readl(base + EXYNOS4_TBL_SEL_REG); + + reg |= EXYNOS4_HUFF_TBL_COMP(c, x); + writel(reg, base + EXYNOS4_TBL_SEL_REG); +} + +void exynos4_jpeg_set_encode_hoff_cnt(void __iomem *base, unsigned int fmt) +{ + if (fmt == V4L2_PIX_FMT_GREY) + writel(0xd2, base + EXYNOS4_HUFF_CNT_REG); + else + writel(0x1a2, base + EXYNOS4_HUFF_CNT_REG); +} + +unsigned int exynos4_jpeg_get_stream_size(void __iomem *base) +{ + return readl(base + EXYNOS4_BITSTREAM_SIZE_REG); +} + +void exynos4_jpeg_set_dec_bitstream_size(void __iomem *base, unsigned int size) +{ + writel(size, base + EXYNOS4_BITSTREAM_SIZE_REG); +} + +void exynos4_jpeg_get_frame_size(void __iomem *base, + unsigned int *width, unsigned int *height) +{ + *width = (readl(base + EXYNOS4_DECODE_XY_SIZE_REG) & + EXYNOS4_DECODED_SIZE_MASK); + *height = (readl(base + EXYNOS4_DECODE_XY_SIZE_REG) >> 16) & + EXYNOS4_DECODED_SIZE_MASK; +} + +unsigned int exynos4_jpeg_get_frame_fmt(void __iomem *base) +{ + return readl(base + EXYNOS4_DECODE_IMG_FMT_REG) & + EXYNOS4_JPEG_DECODED_IMG_FMT_MASK; +} + +void exynos4_jpeg_set_timer_count(void __iomem *base, unsigned int size) +{ + writel(size, base + EXYNOS4_INT_TIMER_COUNT_REG); +} diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h new file mode 100644 index 000000000..3e2887526 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-exynos4.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Author: Jacek Anaszewski + * + * Header file of the register interface for JPEG driver on Exynos4x12. +*/ + +#ifndef JPEG_HW_EXYNOS4_H_ +#define JPEG_HW_EXYNOS4_H_ + +void exynos4_jpeg_sw_reset(void __iomem *base); +void exynos4_jpeg_set_enc_dec_mode(void __iomem *base, unsigned int mode); +void __exynos4_jpeg_set_img_fmt(void __iomem *base, unsigned int img_fmt, + unsigned int version); +void __exynos4_jpeg_set_enc_out_fmt(void __iomem *base, unsigned int out_fmt, + unsigned int version); +void exynos4_jpeg_set_enc_tbl(void __iomem *base); +void exynos4_jpeg_set_interrupt(void __iomem *base, unsigned int version); +unsigned int exynos4_jpeg_get_int_status(void __iomem *base); +void exynos4_jpeg_set_huf_table_enable(void __iomem *base, int value); +void exynos4_jpeg_set_sys_int_enable(void __iomem *base, int value); +void exynos4_jpeg_set_stream_buf_address(void __iomem *base, + unsigned int address); +void exynos4_jpeg_set_stream_size(void __iomem *base, + unsigned int x_value, unsigned int y_value); +void exynos4_jpeg_set_frame_buf_address(void __iomem *base, + struct s5p_jpeg_addr *jpeg_addr); +void exynos4_jpeg_set_encode_tbl_select(void __iomem *base, + enum exynos4_jpeg_img_quality_level level); +void exynos4_jpeg_set_dec_components(void __iomem *base, int n); +void exynos4_jpeg_select_dec_q_tbl(void __iomem *base, char c, char x); +void exynos4_jpeg_select_dec_h_tbl(void __iomem *base, char c, char x); +void exynos4_jpeg_set_encode_hoff_cnt(void __iomem *base, unsigned int fmt); +void exynos4_jpeg_set_dec_bitstream_size(void __iomem *base, unsigned int size); +unsigned int exynos4_jpeg_get_stream_size(void __iomem *base); +void exynos4_jpeg_get_frame_size(void __iomem *base, + unsigned int *width, unsigned int *height); +unsigned int exynos4_jpeg_get_frame_fmt(void __iomem *base); +unsigned int exynos4_jpeg_get_fifo_status(void __iomem *base); +void exynos4_jpeg_set_timer_count(void __iomem *base, unsigned int size); + +#endif /* JPEG_HW_EXYNOS4_H_ */ diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c new file mode 100644 index 000000000..33e6e85df --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.c @@ -0,0 +1,305 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ + +#include +#include + +#include "jpeg-core.h" +#include "jpeg-regs.h" +#include "jpeg-hw-s5p.h" + +void s5p_jpeg_reset(void __iomem *regs) +{ + unsigned long reg; + + writel(1, regs + S5P_JPG_SW_RESET); + reg = readl(regs + S5P_JPG_SW_RESET); + /* no other way but polling for when JPEG IP becomes operational */ + while (reg != 0) { + cpu_relax(); + reg = readl(regs + S5P_JPG_SW_RESET); + } +} + +void s5p_jpeg_poweron(void __iomem *regs) +{ + writel(S5P_POWER_ON, regs + S5P_JPGCLKCON); +} + +void s5p_jpeg_input_raw_mode(void __iomem *regs, unsigned long mode) +{ + unsigned long reg, m; + + m = S5P_MOD_SEL_565; + if (mode == S5P_JPEG_RAW_IN_565) + m = S5P_MOD_SEL_565; + else if (mode == S5P_JPEG_RAW_IN_422) + m = S5P_MOD_SEL_422; + + reg = readl(regs + S5P_JPGCMOD); + reg &= ~S5P_MOD_SEL_MASK; + reg |= m; + writel(reg, regs + S5P_JPGCMOD); +} + +void s5p_jpeg_proc_mode(void __iomem *regs, unsigned long mode) +{ + unsigned long reg, m; + + if (mode == S5P_JPEG_ENCODE) + m = S5P_PROC_MODE_COMPR; + else + m = S5P_PROC_MODE_DECOMPR; + reg = readl(regs + S5P_JPGMOD); + reg &= ~S5P_PROC_MODE_MASK; + reg |= m; + writel(reg, regs + S5P_JPGMOD); +} + +void s5p_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode) +{ + unsigned long reg, m; + + if (mode == V4L2_JPEG_CHROMA_SUBSAMPLING_420) + m = S5P_SUBSAMPLING_MODE_420; + else + m = S5P_SUBSAMPLING_MODE_422; + + reg = readl(regs + S5P_JPGMOD); + reg &= ~S5P_SUBSAMPLING_MODE_MASK; + reg |= m; + writel(reg, regs + S5P_JPGMOD); +} + +unsigned int s5p_jpeg_get_subsampling_mode(void __iomem *regs) +{ + return readl(regs + S5P_JPGMOD) & S5P_SUBSAMPLING_MODE_MASK; +} + +void s5p_jpeg_dri(void __iomem *regs, unsigned int dri) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPGDRI_U); + reg &= ~0xff; + reg |= (dri >> 8) & 0xff; + writel(reg, regs + S5P_JPGDRI_U); + + reg = readl(regs + S5P_JPGDRI_L); + reg &= ~0xff; + reg |= dri & 0xff; + writel(reg, regs + S5P_JPGDRI_L); +} + +void s5p_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPG_QTBL); + reg &= ~S5P_QT_NUMt_MASK(t); + reg |= (n << S5P_QT_NUMt_SHIFT(t)) & S5P_QT_NUMt_MASK(t); + writel(reg, regs + S5P_JPG_QTBL); +} + +void s5p_jpeg_htbl_ac(void __iomem *regs, unsigned int t) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPG_HTBL); + reg &= ~S5P_HT_NUMt_AC_MASK(t); + /* this driver uses table 0 for all color components */ + reg |= (0 << S5P_HT_NUMt_AC_SHIFT(t)) & S5P_HT_NUMt_AC_MASK(t); + writel(reg, regs + S5P_JPG_HTBL); +} + +void s5p_jpeg_htbl_dc(void __iomem *regs, unsigned int t) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPG_HTBL); + reg &= ~S5P_HT_NUMt_DC_MASK(t); + /* this driver uses table 0 for all color components */ + reg |= (0 << S5P_HT_NUMt_DC_SHIFT(t)) & S5P_HT_NUMt_DC_MASK(t); + writel(reg, regs + S5P_JPG_HTBL); +} + +void s5p_jpeg_y(void __iomem *regs, unsigned int y) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPGY_U); + reg &= ~0xff; + reg |= (y >> 8) & 0xff; + writel(reg, regs + S5P_JPGY_U); + + reg = readl(regs + S5P_JPGY_L); + reg &= ~0xff; + reg |= y & 0xff; + writel(reg, regs + S5P_JPGY_L); +} + +void s5p_jpeg_x(void __iomem *regs, unsigned int x) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPGX_U); + reg &= ~0xff; + reg |= (x >> 8) & 0xff; + writel(reg, regs + S5P_JPGX_U); + + reg = readl(regs + S5P_JPGX_L); + reg &= ~0xff; + reg |= x & 0xff; + writel(reg, regs + S5P_JPGX_L); +} + +void s5p_jpeg_rst_int_enable(void __iomem *regs, bool enable) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPGINTSE); + reg &= ~S5P_RSTm_INT_EN_MASK; + if (enable) + reg |= S5P_RSTm_INT_EN; + writel(reg, regs + S5P_JPGINTSE); +} + +void s5p_jpeg_data_num_int_enable(void __iomem *regs, bool enable) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPGINTSE); + reg &= ~S5P_DATA_NUM_INT_EN_MASK; + if (enable) + reg |= S5P_DATA_NUM_INT_EN; + writel(reg, regs + S5P_JPGINTSE); +} + +void s5p_jpeg_final_mcu_num_int_enable(void __iomem *regs, bool enbl) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPGINTSE); + reg &= ~S5P_FINAL_MCU_NUM_INT_EN_MASK; + if (enbl) + reg |= S5P_FINAL_MCU_NUM_INT_EN; + writel(reg, regs + S5P_JPGINTSE); +} + +int s5p_jpeg_timer_stat(void __iomem *regs) +{ + return (int)((readl(regs + S5P_JPG_TIMER_ST) & S5P_TIMER_INT_STAT_MASK) + >> S5P_TIMER_INT_STAT_SHIFT); +} + +void s5p_jpeg_clear_timer_stat(void __iomem *regs) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPG_TIMER_SE); + reg &= ~S5P_TIMER_INT_STAT_MASK; + writel(reg, regs + S5P_JPG_TIMER_SE); +} + +void s5p_jpeg_enc_stream_int(void __iomem *regs, unsigned long size) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPG_ENC_STREAM_INTSE); + reg &= ~S5P_ENC_STREAM_BOUND_MASK; + reg |= S5P_ENC_STREAM_INT_EN; + reg |= size & S5P_ENC_STREAM_BOUND_MASK; + writel(reg, regs + S5P_JPG_ENC_STREAM_INTSE); +} + +int s5p_jpeg_enc_stream_stat(void __iomem *regs) +{ + return (int)(readl(regs + S5P_JPG_ENC_STREAM_INTST) & + S5P_ENC_STREAM_INT_STAT_MASK); +} + +void s5p_jpeg_clear_enc_stream_stat(void __iomem *regs) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPG_ENC_STREAM_INTSE); + reg &= ~S5P_ENC_STREAM_INT_MASK; + writel(reg, regs + S5P_JPG_ENC_STREAM_INTSE); +} + +void s5p_jpeg_outform_raw(void __iomem *regs, unsigned long format) +{ + unsigned long reg, f; + + f = S5P_DEC_OUT_FORMAT_422; + if (format == S5P_JPEG_RAW_OUT_422) + f = S5P_DEC_OUT_FORMAT_422; + else if (format == S5P_JPEG_RAW_OUT_420) + f = S5P_DEC_OUT_FORMAT_420; + reg = readl(regs + S5P_JPG_OUTFORM); + reg &= ~S5P_DEC_OUT_FORMAT_MASK; + reg |= f; + writel(reg, regs + S5P_JPG_OUTFORM); +} + +void s5p_jpeg_jpgadr(void __iomem *regs, unsigned long addr) +{ + writel(addr, regs + S5P_JPG_JPGADR); +} + +void s5p_jpeg_imgadr(void __iomem *regs, unsigned long addr) +{ + writel(addr, regs + S5P_JPG_IMGADR); +} + +void s5p_jpeg_coef(void __iomem *regs, unsigned int i, + unsigned int j, unsigned int coef) +{ + unsigned long reg; + + reg = readl(regs + S5P_JPG_COEF(i)); + reg &= ~S5P_COEFn_MASK(j); + reg |= (coef << S5P_COEFn_SHIFT(j)) & S5P_COEFn_MASK(j); + writel(reg, regs + S5P_JPG_COEF(i)); +} + +void s5p_jpeg_start(void __iomem *regs) +{ + writel(1, regs + S5P_JSTART); +} + +int s5p_jpeg_result_stat_ok(void __iomem *regs) +{ + return (int)((readl(regs + S5P_JPGINTST) & S5P_RESULT_STAT_MASK) + >> S5P_RESULT_STAT_SHIFT); +} + +int s5p_jpeg_stream_stat_ok(void __iomem *regs) +{ + return !(int)((readl(regs + S5P_JPGINTST) & S5P_STREAM_STAT_MASK) + >> S5P_STREAM_STAT_SHIFT); +} + +void s5p_jpeg_clear_int(void __iomem *regs) +{ + readl(regs + S5P_JPGINTST); + writel(S5P_INT_RELEASE, regs + S5P_JPGCOM); + readl(regs + S5P_JPGOPR); +} + +unsigned int s5p_jpeg_compressed_size(void __iomem *regs) +{ + unsigned long jpeg_size = 0; + + jpeg_size |= (readl(regs + S5P_JPGCNT_U) & 0xff) << 16; + jpeg_size |= (readl(regs + S5P_JPGCNT_M) & 0xff) << 8; + jpeg_size |= (readl(regs + S5P_JPGCNT_L) & 0xff); + + return (unsigned int)jpeg_size; +} diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h new file mode 100644 index 000000000..f068d52c6 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw-s5p.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-hw.h + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + */ +#ifndef JPEG_HW_S5P_H_ +#define JPEG_HW_S5P_H_ + +#include +#include + +#include "jpeg-regs.h" + +#define S5P_JPEG_MIN_WIDTH 32 +#define S5P_JPEG_MIN_HEIGHT 32 +#define S5P_JPEG_MAX_WIDTH 8192 +#define S5P_JPEG_MAX_HEIGHT 8192 +#define S5P_JPEG_RAW_IN_565 0 +#define S5P_JPEG_RAW_IN_422 1 +#define S5P_JPEG_RAW_OUT_422 0 +#define S5P_JPEG_RAW_OUT_420 1 + +void s5p_jpeg_reset(void __iomem *regs); +void s5p_jpeg_poweron(void __iomem *regs); +void s5p_jpeg_input_raw_mode(void __iomem *regs, unsigned long mode); +void s5p_jpeg_proc_mode(void __iomem *regs, unsigned long mode); +void s5p_jpeg_subsampling_mode(void __iomem *regs, unsigned int mode); +unsigned int s5p_jpeg_get_subsampling_mode(void __iomem *regs); +void s5p_jpeg_dri(void __iomem *regs, unsigned int dri); +void s5p_jpeg_qtbl(void __iomem *regs, unsigned int t, unsigned int n); +void s5p_jpeg_htbl_ac(void __iomem *regs, unsigned int t); +void s5p_jpeg_htbl_dc(void __iomem *regs, unsigned int t); +void s5p_jpeg_y(void __iomem *regs, unsigned int y); +void s5p_jpeg_x(void __iomem *regs, unsigned int x); +void s5p_jpeg_rst_int_enable(void __iomem *regs, bool enable); +void s5p_jpeg_data_num_int_enable(void __iomem *regs, bool enable); +void s5p_jpeg_final_mcu_num_int_enable(void __iomem *regs, bool enbl); +int s5p_jpeg_timer_stat(void __iomem *regs); +void s5p_jpeg_clear_timer_stat(void __iomem *regs); +void s5p_jpeg_enc_stream_int(void __iomem *regs, unsigned long size); +int s5p_jpeg_enc_stream_stat(void __iomem *regs); +void s5p_jpeg_clear_enc_stream_stat(void __iomem *regs); +void s5p_jpeg_outform_raw(void __iomem *regs, unsigned long format); +void s5p_jpeg_jpgadr(void __iomem *regs, unsigned long addr); +void s5p_jpeg_imgadr(void __iomem *regs, unsigned long addr); +void s5p_jpeg_coef(void __iomem *regs, unsigned int i, + unsigned int j, unsigned int coef); +void s5p_jpeg_start(void __iomem *regs); +int s5p_jpeg_result_stat_ok(void __iomem *regs); +int s5p_jpeg_stream_stat_ok(void __iomem *regs); +void s5p_jpeg_clear_int(void __iomem *regs); +unsigned int s5p_jpeg_compressed_size(void __iomem *regs); + +#endif /* JPEG_HW_S5P_H_ */ diff --git a/drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h b/drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h new file mode 100644 index 000000000..c2298b680 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h @@ -0,0 +1,646 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* linux/drivers/media/platform/samsung/s5p-jpeg/jpeg-regs.h + * + * Register definition file for Samsung JPEG codec driver + * + * Copyright (c) 2011-2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Author: Andrzej Pietrasiewicz + * Author: Jacek Anaszewski + */ + +#ifndef JPEG_REGS_H_ +#define JPEG_REGS_H_ + +/* Register and bit definitions for S5PC210 */ + +/* JPEG mode register */ +#define S5P_JPGMOD 0x00 +#define S5P_PROC_MODE_MASK (0x1 << 3) +#define S5P_PROC_MODE_DECOMPR (0x1 << 3) +#define S5P_PROC_MODE_COMPR (0x0 << 3) +#define S5P_SUBSAMPLING_MODE_MASK 0x7 +#define S5P_SUBSAMPLING_MODE_444 (0x0 << 0) +#define S5P_SUBSAMPLING_MODE_422 (0x1 << 0) +#define S5P_SUBSAMPLING_MODE_420 (0x2 << 0) +#define S5P_SUBSAMPLING_MODE_GRAY (0x3 << 0) + +/* JPEG operation status register */ +#define S5P_JPGOPR 0x04 + +/* Quantization tables*/ +#define S5P_JPG_QTBL 0x08 +#define S5P_QT_NUMt_SHIFT(t) (((t) - 1) << 1) +#define S5P_QT_NUMt_MASK(t) (0x3 << S5P_QT_NUMt_SHIFT(t)) + +/* Huffman tables */ +#define S5P_JPG_HTBL 0x0c +#define S5P_HT_NUMt_AC_SHIFT(t) (((t) << 1) - 1) +#define S5P_HT_NUMt_AC_MASK(t) (0x1 << S5P_HT_NUMt_AC_SHIFT(t)) + +#define S5P_HT_NUMt_DC_SHIFT(t) (((t) - 1) << 1) +#define S5P_HT_NUMt_DC_MASK(t) (0x1 << S5P_HT_NUMt_DC_SHIFT(t)) + +/* JPEG restart interval register upper byte */ +#define S5P_JPGDRI_U 0x10 + +/* JPEG restart interval register lower byte */ +#define S5P_JPGDRI_L 0x14 + +/* JPEG vertical resolution register upper byte */ +#define S5P_JPGY_U 0x18 + +/* JPEG vertical resolution register lower byte */ +#define S5P_JPGY_L 0x1c + +/* JPEG horizontal resolution register upper byte */ +#define S5P_JPGX_U 0x20 + +/* JPEG horizontal resolution register lower byte */ +#define S5P_JPGX_L 0x24 + +/* JPEG byte count register upper byte */ +#define S5P_JPGCNT_U 0x28 + +/* JPEG byte count register middle byte */ +#define S5P_JPGCNT_M 0x2c + +/* JPEG byte count register lower byte */ +#define S5P_JPGCNT_L 0x30 + +/* JPEG interrupt setting register */ +#define S5P_JPGINTSE 0x34 +#define S5P_RSTm_INT_EN_MASK (0x1 << 7) +#define S5P_RSTm_INT_EN (0x1 << 7) +#define S5P_DATA_NUM_INT_EN_MASK (0x1 << 6) +#define S5P_DATA_NUM_INT_EN (0x1 << 6) +#define S5P_FINAL_MCU_NUM_INT_EN_MASK (0x1 << 5) +#define S5P_FINAL_MCU_NUM_INT_EN (0x1 << 5) + +/* JPEG interrupt status register */ +#define S5P_JPGINTST 0x38 +#define S5P_RESULT_STAT_SHIFT 6 +#define S5P_RESULT_STAT_MASK (0x1 << S5P_RESULT_STAT_SHIFT) +#define S5P_STREAM_STAT_SHIFT 5 +#define S5P_STREAM_STAT_MASK (0x1 << S5P_STREAM_STAT_SHIFT) + +/* JPEG command register */ +#define S5P_JPGCOM 0x4c +#define S5P_INT_RELEASE (0x1 << 2) + +/* Raw image data r/w address register */ +#define S5P_JPG_IMGADR 0x50 + +/* JPEG file r/w address register */ +#define S5P_JPG_JPGADR 0x58 + +/* Coefficient for RGB-to-YCbCr converter register */ +#define S5P_JPG_COEF(n) (0x5c + (((n) - 1) << 2)) +#define S5P_COEFn_SHIFT(j) ((3 - (j)) << 3) +#define S5P_COEFn_MASK(j) (0xff << S5P_COEFn_SHIFT(j)) + +/* JPEG color mode register */ +#define S5P_JPGCMOD 0x68 +#define S5P_MOD_SEL_MASK (0x7 << 5) +#define S5P_MOD_SEL_422 (0x1 << 5) +#define S5P_MOD_SEL_565 (0x2 << 5) +#define S5P_MODE_Y16_MASK (0x1 << 1) +#define S5P_MODE_Y16 (0x1 << 1) + +/* JPEG clock control register */ +#define S5P_JPGCLKCON 0x6c +#define S5P_CLK_DOWN_READY (0x1 << 1) +#define S5P_POWER_ON (0x1 << 0) + +/* JPEG start register */ +#define S5P_JSTART 0x70 + +/* JPEG SW reset register */ +#define S5P_JPG_SW_RESET 0x78 + +/* JPEG timer setting register */ +#define S5P_JPG_TIMER_SE 0x7c +#define S5P_TIMER_INT_EN_MASK (0x1UL << 31) +#define S5P_TIMER_INT_EN (0x1UL << 31) +#define S5P_TIMER_INIT_MASK 0x7fffffff + +/* JPEG timer status register */ +#define S5P_JPG_TIMER_ST 0x80 +#define S5P_TIMER_INT_STAT_SHIFT 31 +#define S5P_TIMER_INT_STAT_MASK (0x1UL << S5P_TIMER_INT_STAT_SHIFT) +#define S5P_TIMER_CNT_SHIFT 0 +#define S5P_TIMER_CNT_MASK 0x7fffffff + +/* JPEG decompression output format register */ +#define S5P_JPG_OUTFORM 0x88 +#define S5P_DEC_OUT_FORMAT_MASK (0x1 << 0) +#define S5P_DEC_OUT_FORMAT_422 (0x0 << 0) +#define S5P_DEC_OUT_FORMAT_420 (0x1 << 0) + +/* JPEG version register */ +#define S5P_JPG_VERSION 0x8c + +/* JPEG compressed stream size interrupt setting register */ +#define S5P_JPG_ENC_STREAM_INTSE 0x98 +#define S5P_ENC_STREAM_INT_MASK (0x1 << 24) +#define S5P_ENC_STREAM_INT_EN (0x1 << 24) +#define S5P_ENC_STREAM_BOUND_MASK 0xffffff + +/* JPEG compressed stream size interrupt status register */ +#define S5P_JPG_ENC_STREAM_INTST 0x9c +#define S5P_ENC_STREAM_INT_STAT_MASK 0x1 + +/* JPEG quantizer table register */ +#define S5P_JPG_QTBL_CONTENT(n) (0x400 + (n) * 0x100) + +/* JPEG DC Huffman table register */ +#define S5P_JPG_HDCTBL(n) (0x800 + (n) * 0x400) + +/* JPEG DC Huffman table register */ +#define S5P_JPG_HDCTBLG(n) (0x840 + (n) * 0x400) + +/* JPEG AC Huffman table register */ +#define S5P_JPG_HACTBL(n) (0x880 + (n) * 0x400) + +/* JPEG AC Huffman table register */ +#define S5P_JPG_HACTBLG(n) (0x8c0 + (n) * 0x400) + + +/* Register and bit definitions for Exynos 4x12 */ + +/* JPEG Codec Control Registers */ +#define EXYNOS4_JPEG_CNTL_REG 0x00 +#define EXYNOS4_INT_EN_REG 0x04 +#define EXYNOS4_INT_TIMER_COUNT_REG 0x08 +#define EXYNOS4_INT_STATUS_REG 0x0c +#define EXYNOS4_OUT_MEM_BASE_REG 0x10 +#define EXYNOS4_JPEG_IMG_SIZE_REG 0x14 +#define EXYNOS4_IMG_BA_PLANE_1_REG 0x18 +#define EXYNOS4_IMG_SO_PLANE_1_REG 0x1c +#define EXYNOS4_IMG_PO_PLANE_1_REG 0x20 +#define EXYNOS4_IMG_BA_PLANE_2_REG 0x24 +#define EXYNOS4_IMG_SO_PLANE_2_REG 0x28 +#define EXYNOS4_IMG_PO_PLANE_2_REG 0x2c +#define EXYNOS4_IMG_BA_PLANE_3_REG 0x30 +#define EXYNOS4_IMG_SO_PLANE_3_REG 0x34 +#define EXYNOS4_IMG_PO_PLANE_3_REG 0x38 + +#define EXYNOS4_TBL_SEL_REG 0x3c + +#define EXYNOS4_IMG_FMT_REG 0x40 + +#define EXYNOS4_BITSTREAM_SIZE_REG 0x44 +#define EXYNOS4_PADDING_REG 0x48 +#define EXYNOS4_HUFF_CNT_REG 0x4c +#define EXYNOS4_FIFO_STATUS_REG 0x50 +#define EXYNOS4_DECODE_XY_SIZE_REG 0x54 +#define EXYNOS4_DECODE_IMG_FMT_REG 0x58 + +#define EXYNOS4_QUAN_TBL_ENTRY_REG 0x100 +#define EXYNOS4_HUFF_TBL_ENTRY_REG 0x200 + + +/****************************************************************/ +/* Bit definition part */ +/****************************************************************/ + +/* JPEG CNTL Register bit */ +#define EXYNOS4_ENC_DEC_MODE_MASK (0xfffffffc << 0) +#define EXYNOS4_DEC_MODE (1 << 0) +#define EXYNOS4_ENC_MODE (1 << 1) +#define EXYNOS4_AUTO_RST_MARKER (1 << 2) +#define EXYNOS4_RST_INTERVAL_SHIFT 3 +#define EXYNOS4_RST_INTERVAL(x) (((x) & 0xffff) \ + << EXYNOS4_RST_INTERVAL_SHIFT) +#define EXYNOS4_HUF_TBL_EN (1 << 19) +#define EXYNOS4_HOR_SCALING_SHIFT 20 +#define EXYNOS4_HOR_SCALING_MASK (3 << EXYNOS4_HOR_SCALING_SHIFT) +#define EXYNOS4_HOR_SCALING(x) (((x) & 0x3) \ + << EXYNOS4_HOR_SCALING_SHIFT) +#define EXYNOS4_VER_SCALING_SHIFT 22 +#define EXYNOS4_VER_SCALING_MASK (3 << EXYNOS4_VER_SCALING_SHIFT) +#define EXYNOS4_VER_SCALING(x) (((x) & 0x3) \ + << EXYNOS4_VER_SCALING_SHIFT) +#define EXYNOS4_PADDING (1 << 27) +#define EXYNOS4_SYS_INT_EN (1 << 28) +#define EXYNOS4_SOFT_RESET_HI (1 << 29) + +/* JPEG INT Register bit */ +#define EXYNOS4_INT_EN_MASK (0x1f << 0) +#define EXYNOS5433_INT_EN_MASK (0x1ff << 0) +#define EXYNOS4_PROT_ERR_INT_EN (1 << 0) +#define EXYNOS4_IMG_COMPLETION_INT_EN (1 << 1) +#define EXYNOS4_DEC_INVALID_FORMAT_EN (1 << 2) +#define EXYNOS4_MULTI_SCAN_ERROR_EN (1 << 3) +#define EXYNOS4_FRAME_ERR_EN (1 << 4) +#define EXYNOS4_INT_EN_ALL (0x1f << 0) +#define EXYNOS5433_INT_EN_ALL (0x1b6 << 0) + +#define EXYNOS4_MOD_REG_PROC_ENC (0 << 3) +#define EXYNOS4_MOD_REG_PROC_DEC (1 << 3) + +#define EXYNOS4_MOD_REG_SUBSAMPLE_444 (0 << 0) +#define EXYNOS4_MOD_REG_SUBSAMPLE_422 (1 << 0) +#define EXYNOS4_MOD_REG_SUBSAMPLE_420 (2 << 0) +#define EXYNOS4_MOD_REG_SUBSAMPLE_GRAY (3 << 0) + + +/* JPEG IMAGE SIZE Register bit */ +#define EXYNOS4_X_SIZE_SHIFT 0 +#define EXYNOS4_X_SIZE_MASK (0xffff << EXYNOS4_X_SIZE_SHIFT) +#define EXYNOS4_X_SIZE(x) (((x) & 0xffff) << EXYNOS4_X_SIZE_SHIFT) +#define EXYNOS4_Y_SIZE_SHIFT 16 +#define EXYNOS4_Y_SIZE_MASK (0xffff << EXYNOS4_Y_SIZE_SHIFT) +#define EXYNOS4_Y_SIZE(x) (((x) & 0xffff) << EXYNOS4_Y_SIZE_SHIFT) + +/* JPEG IMAGE FORMAT Register bit */ +#define EXYNOS4_ENC_IN_FMT_MASK 0xffff0000 +#define EXYNOS4_ENC_GRAY_IMG (0 << 0) +#define EXYNOS4_ENC_RGB_IMG (1 << 0) +#define EXYNOS4_ENC_YUV_444_IMG (2 << 0) +#define EXYNOS4_ENC_YUV_422_IMG (3 << 0) +#define EXYNOS4_ENC_YUV_440_IMG (4 << 0) + +#define EXYNOS4_DEC_GRAY_IMG (0 << 0) +#define EXYNOS4_DEC_RGB_IMG (1 << 0) +#define EXYNOS4_DEC_YUV_444_IMG (2 << 0) +#define EXYNOS4_DEC_YUV_422_IMG (3 << 0) +#define EXYNOS4_DEC_YUV_420_IMG (4 << 0) + +#define EXYNOS4_GRAY_IMG_IP_SHIFT 3 +#define EXYNOS4_GRAY_IMG_IP_MASK (7 << EXYNOS4_GRAY_IMG_IP_SHIFT) +#define EXYNOS4_GRAY_IMG_IP (4 << EXYNOS4_GRAY_IMG_IP_SHIFT) + +#define EXYNOS4_RGB_IP_SHIFT 6 +#define EXYNOS4_RGB_IP_MASK (7 << EXYNOS4_RGB_IP_SHIFT) +#define EXYNOS4_RGB_IP_RGB_16BIT_IMG (4 << EXYNOS4_RGB_IP_SHIFT) +#define EXYNOS4_RGB_IP_RGB_32BIT_IMG (5 << EXYNOS4_RGB_IP_SHIFT) + +#define EXYNOS4_YUV_444_IP_SHIFT 9 +#define EXYNOS4_YUV_444_IP_MASK (7 << EXYNOS4_YUV_444_IP_SHIFT) +#define EXYNOS4_YUV_444_IP_YUV_444_2P_IMG (4 << EXYNOS4_YUV_444_IP_SHIFT) +#define EXYNOS4_YUV_444_IP_YUV_444_3P_IMG (5 << EXYNOS4_YUV_444_IP_SHIFT) + +#define EXYNOS4_YUV_422_IP_SHIFT 12 +#define EXYNOS4_YUV_422_IP_MASK (7 << EXYNOS4_YUV_422_IP_SHIFT) +#define EXYNOS4_YUV_422_IP_YUV_422_1P_IMG (4 << EXYNOS4_YUV_422_IP_SHIFT) +#define EXYNOS4_YUV_422_IP_YUV_422_2P_IMG (5 << EXYNOS4_YUV_422_IP_SHIFT) +#define EXYNOS4_YUV_422_IP_YUV_422_3P_IMG (6 << EXYNOS4_YUV_422_IP_SHIFT) + +#define EXYNOS4_YUV_420_IP_SHIFT 15 +#define EXYNOS4_YUV_420_IP_MASK (7 << EXYNOS4_YUV_420_IP_SHIFT) +#define EXYNOS4_YUV_420_IP_YUV_420_2P_IMG (4 << EXYNOS4_YUV_420_IP_SHIFT) +#define EXYNOS4_YUV_420_IP_YUV_420_3P_IMG (5 << EXYNOS4_YUV_420_IP_SHIFT) + +#define EXYNOS4_ENC_FMT_SHIFT 24 +#define EXYNOS4_ENC_FMT_MASK (3 << EXYNOS4_ENC_FMT_SHIFT) +#define EXYNOS5433_ENC_FMT_MASK (7 << EXYNOS4_ENC_FMT_SHIFT) + +#define EXYNOS4_ENC_FMT_GRAY (0 << EXYNOS4_ENC_FMT_SHIFT) +#define EXYNOS4_ENC_FMT_YUV_444 (1 << EXYNOS4_ENC_FMT_SHIFT) +#define EXYNOS4_ENC_FMT_YUV_422 (2 << EXYNOS4_ENC_FMT_SHIFT) +#define EXYNOS4_ENC_FMT_YUV_420 (3 << EXYNOS4_ENC_FMT_SHIFT) + +#define EXYNOS4_JPEG_DECODED_IMG_FMT_MASK 0x03 + +#define EXYNOS4_SWAP_CHROMA_CRCB (1 << 26) +#define EXYNOS4_SWAP_CHROMA_CBCR (0 << 26) +#define EXYNOS5433_SWAP_CHROMA_CRCB (1 << 27) +#define EXYNOS5433_SWAP_CHROMA_CBCR (0 << 27) + +/* JPEG HUFF count Register bit */ +#define EXYNOS4_HUFF_COUNT_MASK 0xffff + +/* JPEG Decoded_img_x_y_size Register bit */ +#define EXYNOS4_DECODED_SIZE_MASK 0x0000ffff + +/* JPEG Decoded image format Register bit */ +#define EXYNOS4_DECODED_IMG_FMT_MASK 0x3 + +/* JPEG TBL SEL Register bit */ +#define EXYNOS4_Q_TBL_COMP(c, n) ((n) << (((c) - 1) << 1)) + +#define EXYNOS4_Q_TBL_COMP1_0 EXYNOS4_Q_TBL_COMP(1, 0) +#define EXYNOS4_Q_TBL_COMP1_1 EXYNOS4_Q_TBL_COMP(1, 1) +#define EXYNOS4_Q_TBL_COMP1_2 EXYNOS4_Q_TBL_COMP(1, 2) +#define EXYNOS4_Q_TBL_COMP1_3 EXYNOS4_Q_TBL_COMP(1, 3) + +#define EXYNOS4_Q_TBL_COMP2_0 EXYNOS4_Q_TBL_COMP(2, 0) +#define EXYNOS4_Q_TBL_COMP2_1 EXYNOS4_Q_TBL_COMP(2, 1) +#define EXYNOS4_Q_TBL_COMP2_2 EXYNOS4_Q_TBL_COMP(2, 2) +#define EXYNOS4_Q_TBL_COMP2_3 EXYNOS4_Q_TBL_COMP(2, 3) + +#define EXYNOS4_Q_TBL_COMP3_0 EXYNOS4_Q_TBL_COMP(3, 0) +#define EXYNOS4_Q_TBL_COMP3_1 EXYNOS4_Q_TBL_COMP(3, 1) +#define EXYNOS4_Q_TBL_COMP3_2 EXYNOS4_Q_TBL_COMP(3, 2) +#define EXYNOS4_Q_TBL_COMP3_3 EXYNOS4_Q_TBL_COMP(3, 3) + +#define EXYNOS4_HUFF_TBL_COMP(c, n) ((n) << ((((c) - 1) << 1) + 6)) + +#define EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_0 \ + EXYNOS4_HUFF_TBL_COMP(1, 0) +#define EXYNOS4_HUFF_TBL_COMP1_AC_0_DC_1 \ + EXYNOS4_HUFF_TBL_COMP(1, 1) +#define EXYNOS4_HUFF_TBL_COMP1_AC_1_DC_0 \ + EXYNOS4_HUFF_TBL_COMP(1, 2) +#define EXYNOS4_HUFF_TBL_COMP1_AC_1_DC_1 \ + EXYNOS4_HUFF_TBL_COMP(1, 3) + +#define EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_0 \ + EXYNOS4_HUFF_TBL_COMP(2, 0) +#define EXYNOS4_HUFF_TBL_COMP2_AC_0_DC_1 \ + EXYNOS4_HUFF_TBL_COMP(2, 1) +#define EXYNOS4_HUFF_TBL_COMP2_AC_1_DC_0 \ + EXYNOS4_HUFF_TBL_COMP(2, 2) +#define EXYNOS4_HUFF_TBL_COMP2_AC_1_DC_1 \ + EXYNOS4_HUFF_TBL_COMP(2, 3) + +#define EXYNOS4_HUFF_TBL_COMP3_AC_0_DC_0 \ + EXYNOS4_HUFF_TBL_COMP(3, 0) +#define EXYNOS4_HUFF_TBL_COMP3_AC_0_DC_1 \ + EXYNOS4_HUFF_TBL_COMP(3, 1) +#define EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_0 \ + EXYNOS4_HUFF_TBL_COMP(3, 2) +#define EXYNOS4_HUFF_TBL_COMP3_AC_1_DC_1 \ + EXYNOS4_HUFF_TBL_COMP(3, 3) + +#define EXYNOS4_NF_SHIFT 16 +#define EXYNOS4_NF_MASK 0xff +#define EXYNOS4_NF(x) \ + (((x) & EXYNOS4_NF_MASK) << EXYNOS4_NF_SHIFT) + +/* JPEG quantizer table register */ +#define EXYNOS4_QTBL_CONTENT(n) (0x100 + (n) * 0x40) + +/* JPEG DC luminance (code length) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HDCLL 0x200 + +/* JPEG DC luminance (values) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HDCLV 0x210 + +/* JPEG DC chrominance (code length) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HDCCL 0x220 + +/* JPEG DC chrominance (values) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HDCCV 0x230 + +/* JPEG AC luminance (code length) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HACLL 0x240 + +/* JPEG AC luminance (values) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HACLV 0x250 + +/* JPEG AC chrominance (code length) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HACCL 0x300 + +/* JPEG AC chrominance (values) Huffman table register */ +#define EXYNOS4_HUFF_TBL_HACCV 0x310 + +/* Register and bit definitions for Exynos 3250 */ + +/* JPEG mode register */ +#define EXYNOS3250_JPGMOD 0x00 +#define EXYNOS3250_PROC_MODE_MASK (0x1 << 3) +#define EXYNOS3250_PROC_MODE_DECOMPR (0x1 << 3) +#define EXYNOS3250_PROC_MODE_COMPR (0x0 << 3) +#define EXYNOS3250_SUBSAMPLING_MODE_MASK (0x7 << 0) +#define EXYNOS3250_SUBSAMPLING_MODE_444 (0x0 << 0) +#define EXYNOS3250_SUBSAMPLING_MODE_422 (0x1 << 0) +#define EXYNOS3250_SUBSAMPLING_MODE_420 (0x2 << 0) +#define EXYNOS3250_SUBSAMPLING_MODE_411 (0x6 << 0) +#define EXYNOS3250_SUBSAMPLING_MODE_GRAY (0x3 << 0) + +/* JPEG operation status register */ +#define EXYNOS3250_JPGOPR 0x04 +#define EXYNOS3250_JPGOPR_MASK 0x01 + +/* Quantization and Huffman tables register */ +#define EXYNOS3250_QHTBL 0x08 +#define EXYNOS3250_QT_NUM_SHIFT(t) ((((t) - 1) << 1) + 8) +#define EXYNOS3250_QT_NUM_MASK(t) (0x3 << EXYNOS3250_QT_NUM_SHIFT(t)) + +/* Huffman tables */ +#define EXYNOS3250_HT_NUM_AC_SHIFT(t) (((t) << 1) - 1) +#define EXYNOS3250_HT_NUM_AC_MASK(t) (0x1 << EXYNOS3250_HT_NUM_AC_SHIFT(t)) + +#define EXYNOS3250_HT_NUM_DC_SHIFT(t) (((t) - 1) << 1) +#define EXYNOS3250_HT_NUM_DC_MASK(t) (0x1 << EXYNOS3250_HT_NUM_DC_SHIFT(t)) + +/* JPEG restart interval register */ +#define EXYNOS3250_JPGDRI 0x0c +#define EXYNOS3250_JPGDRI_MASK 0xffff + +/* JPEG vertical resolution register */ +#define EXYNOS3250_JPGY 0x10 +#define EXYNOS3250_JPGY_MASK 0xffff + +/* JPEG horizontal resolution register */ +#define EXYNOS3250_JPGX 0x14 +#define EXYNOS3250_JPGX_MASK 0xffff + +/* JPEG byte count register */ +#define EXYNOS3250_JPGCNT 0x18 +#define EXYNOS3250_JPGCNT_MASK 0xffffff + +/* JPEG interrupt mask register */ +#define EXYNOS3250_JPGINTSE 0x1c +#define EXYNOS3250_JPEG_DONE_EN (1 << 11) +#define EXYNOS3250_WDMA_DONE_EN (1 << 10) +#define EXYNOS3250_RDMA_DONE_EN (1 << 9) +#define EXYNOS3250_ENC_STREAM_INT_EN (1 << 8) +#define EXYNOS3250_CORE_DONE_EN (1 << 5) +#define EXYNOS3250_ERR_INT_EN (1 << 4) +#define EXYNOS3250_HEAD_INT_EN (1 << 3) + +/* JPEG interrupt status register */ +#define EXYNOS3250_JPGINTST 0x20 +#define EXYNOS3250_JPEG_DONE (1 << 11) +#define EXYNOS3250_WDMA_DONE (1 << 10) +#define EXYNOS3250_RDMA_DONE (1 << 9) +#define EXYNOS3250_ENC_STREAM_STAT (1 << 8) +#define EXYNOS3250_RESULT_STAT (1 << 5) +#define EXYNOS3250_STREAM_STAT (1 << 4) +#define EXYNOS3250_HEADER_STAT (1 << 3) + +/* + * Base address of the luma component DMA buffer + * of the raw input or output image. + */ +#define EXYNOS3250_LUMA_BASE 0x100 +#define EXYNOS3250_SRC_TILE_EN_MASK 0x100 + +/* Stride of source or destination luma raw image buffer */ +#define EXYNOS3250_LUMA_STRIDE 0x104 + +/* Horizontal/vertical offset of active region in luma raw image buffer */ +#define EXYNOS3250_LUMA_XY_OFFSET 0x108 +#define EXYNOS3250_LUMA_YY_OFFSET_SHIFT 18 +#define EXYNOS3250_LUMA_YY_OFFSET_MASK (0x1fff << EXYNOS3250_LUMA_YY_OFFSET_SHIFT) +#define EXYNOS3250_LUMA_YX_OFFSET_SHIFT 2 +#define EXYNOS3250_LUMA_YX_OFFSET_MASK (0x1fff << EXYNOS3250_LUMA_YX_OFFSET_SHIFT) + +/* + * Base address of the chroma(Cb) component DMA buffer + * of the raw input or output image. + */ +#define EXYNOS3250_CHROMA_BASE 0x10c + +/* Stride of source or destination chroma(Cb) raw image buffer */ +#define EXYNOS3250_CHROMA_STRIDE 0x110 + +/* Horizontal/vertical offset of active region in chroma(Cb) raw image buffer */ +#define EXYNOS3250_CHROMA_XY_OFFSET 0x114 +#define EXYNOS3250_CHROMA_YY_OFFSET_SHIFT 18 +#define EXYNOS3250_CHROMA_YY_OFFSET_MASK (0x1fff << EXYNOS3250_CHROMA_YY_OFFSET_SHIFT) +#define EXYNOS3250_CHROMA_YX_OFFSET_SHIFT 2 +#define EXYNOS3250_CHROMA_YX_OFFSET_MASK (0x1fff << EXYNOS3250_CHROMA_YX_OFFSET_SHIFT) + +/* + * Base address of the chroma(Cr) component DMA buffer + * of the raw input or output image. + */ +#define EXYNOS3250_CHROMA_CR_BASE 0x118 + +/* Stride of source or destination chroma(Cr) raw image buffer */ +#define EXYNOS3250_CHROMA_CR_STRIDE 0x11c + +/* Horizontal/vertical offset of active region in chroma(Cb) raw image buffer */ +#define EXYNOS3250_CHROMA_CR_XY_OFFSET 0x120 +#define EXYNOS3250_CHROMA_CR_YY_OFFSET_SHIFT 18 +#define EXYNOS3250_CHROMA_CR_YY_OFFSET_MASK (0x1fff << EXYNOS3250_CHROMA_CR_YY_OFFSET_SHIFT) +#define EXYNOS3250_CHROMA_CR_YX_OFFSET_SHIFT 2 +#define EXYNOS3250_CHROMA_CR_YX_OFFSET_MASK (0x1fff << EXYNOS3250_CHROMA_CR_YX_OFFSET_SHIFT) + +/* Raw image data r/w address register */ +#define EXYNOS3250_JPG_IMGADR 0x50 + +/* Source or destination JPEG file DMA buffer address */ +#define EXYNOS3250_JPG_JPGADR 0x124 + +/* Coefficients for RGB-to-YCbCr converter register */ +#define EXYNOS3250_JPG_COEF(n) (0x128 + (((n) - 1) << 2)) +#define EXYNOS3250_COEF_SHIFT(j) ((3 - (j)) << 3) +#define EXYNOS3250_COEF_MASK(j) (0xff << EXYNOS3250_COEF_SHIFT(j)) + +/* Raw input format setting */ +#define EXYNOS3250_JPGCMOD 0x134 +#define EXYNOS3250_SRC_TILE_EN (0x1 << 10) +#define EXYNOS3250_SRC_NV_MASK (0x1 << 9) +#define EXYNOS3250_SRC_NV12 (0x0 << 9) +#define EXYNOS3250_SRC_NV21 (0x1 << 9) +#define EXYNOS3250_SRC_BIG_ENDIAN_MASK (0x1 << 8) +#define EXYNOS3250_SRC_BIG_ENDIAN (0x1 << 8) +#define EXYNOS3250_MODE_SEL_MASK (0x7 << 5) +#define EXYNOS3250_MODE_SEL_420_2P (0x0 << 5) +#define EXYNOS3250_MODE_SEL_422_1P_LUM_CHR (0x1 << 5) +#define EXYNOS3250_MODE_SEL_RGB565 (0x2 << 5) +#define EXYNOS3250_MODE_SEL_422_1P_CHR_LUM (0x3 << 5) +#define EXYNOS3250_MODE_SEL_ARGB8888 (0x4 << 5) +#define EXYNOS3250_MODE_SEL_420_3P (0x5 << 5) +#define EXYNOS3250_SRC_SWAP_RGB (0x1 << 3) +#define EXYNOS3250_SRC_SWAP_UV (0x1 << 2) +#define EXYNOS3250_MODE_Y16_MASK (0x1 << 1) +#define EXYNOS3250_MODE_Y16 (0x1 << 1) +#define EXYNOS3250_HALF_EN_MASK (0x1 << 0) +#define EXYNOS3250_HALF_EN (0x1 << 0) + +/* Power on/off and clock down control */ +#define EXYNOS3250_JPGCLKCON 0x138 +#define EXYNOS3250_CLK_DOWN_READY (0x1 << 1) +#define EXYNOS3250_POWER_ON (0x1 << 0) + +/* Start compression or decompression */ +#define EXYNOS3250_JSTART 0x13c + +/* Restart decompression after header analysis */ +#define EXYNOS3250_JRSTART 0x140 + +/* JPEG SW reset register */ +#define EXYNOS3250_SW_RESET 0x144 + +/* JPEG timer setting register */ +#define EXYNOS3250_TIMER_SE 0x148 +#define EXYNOS3250_TIMER_INT_EN_SHIFT 31 +#define EXYNOS3250_TIMER_INT_EN (1UL << EXYNOS3250_TIMER_INT_EN_SHIFT) +#define EXYNOS3250_TIMER_INIT_MASK 0x7fffffff + +/* JPEG timer status register */ +#define EXYNOS3250_TIMER_ST 0x14c +#define EXYNOS3250_TIMER_INT_STAT_SHIFT 31 +#define EXYNOS3250_TIMER_INT_STAT (1UL << EXYNOS3250_TIMER_INT_STAT_SHIFT) +#define EXYNOS3250_TIMER_CNT_SHIFT 0 +#define EXYNOS3250_TIMER_CNT_MASK 0x7fffffff + +/* Command status register */ +#define EXYNOS3250_COMSTAT 0x150 +#define EXYNOS3250_CUR_PROC_MODE (0x1 << 1) +#define EXYNOS3250_CUR_COM_MODE (0x1 << 0) + +/* JPEG decompression output format register */ +#define EXYNOS3250_OUTFORM 0x154 +#define EXYNOS3250_OUT_ALPHA_MASK (0xff << 24) +#define EXYNOS3250_OUT_TILE_EN (0x1 << 10) +#define EXYNOS3250_OUT_NV_MASK (0x1 << 9) +#define EXYNOS3250_OUT_NV12 (0x0 << 9) +#define EXYNOS3250_OUT_NV21 (0x1 << 9) +#define EXYNOS3250_OUT_BIG_ENDIAN_MASK (0x1 << 8) +#define EXYNOS3250_OUT_BIG_ENDIAN (0x1 << 8) +#define EXYNOS3250_OUT_SWAP_RGB (0x1 << 7) +#define EXYNOS3250_OUT_SWAP_UV (0x1 << 6) +#define EXYNOS3250_OUT_FMT_MASK (0x7 << 0) +#define EXYNOS3250_OUT_FMT_420_2P (0x0 << 0) +#define EXYNOS3250_OUT_FMT_422_1P_LUM_CHR (0x1 << 0) +#define EXYNOS3250_OUT_FMT_422_1P_CHR_LUM (0x3 << 0) +#define EXYNOS3250_OUT_FMT_420_3P (0x4 << 0) +#define EXYNOS3250_OUT_FMT_RGB565 (0x5 << 0) +#define EXYNOS3250_OUT_FMT_ARGB8888 (0x6 << 0) + +/* Input JPEG stream byte size for decompression */ +#define EXYNOS3250_DEC_STREAM_SIZE 0x158 +#define EXYNOS3250_DEC_STREAM_MASK 0x1fffffff + +/* The upper bound of the byte size of output compressed stream */ +#define EXYNOS3250_ENC_STREAM_BOUND 0x15c +#define EXYNOS3250_ENC_STREAM_BOUND_MASK 0xffffc0 + +/* Scale-down ratio when decoding */ +#define EXYNOS3250_DEC_SCALING_RATIO 0x160 +#define EXYNOS3250_DEC_SCALE_FACTOR_MASK 0x3 +#define EXYNOS3250_DEC_SCALE_FACTOR_8_8 0x0 +#define EXYNOS3250_DEC_SCALE_FACTOR_4_8 0x1 +#define EXYNOS3250_DEC_SCALE_FACTOR_2_8 0x2 +#define EXYNOS3250_DEC_SCALE_FACTOR_1_8 0x3 + +/* Error check */ +#define EXYNOS3250_CRC_RESULT 0x164 + +/* RDMA and WDMA operation status register */ +#define EXYNOS3250_DMA_OPER_STATUS 0x168 +#define EXYNOS3250_WDMA_OPER_STATUS (0x1 << 1) +#define EXYNOS3250_RDMA_OPER_STATUS (0x1 << 0) + +/* DMA issue gathering number and issue number settings */ +#define EXYNOS3250_DMA_ISSUE_NUM 0x16c +#define EXYNOS3250_WDMA_ISSUE_NUM_SHIFT 16 +#define EXYNOS3250_WDMA_ISSUE_NUM_MASK (0x7 << EXYNOS3250_WDMA_ISSUE_NUM_SHIFT) +#define EXYNOS3250_RDMA_ISSUE_NUM_SHIFT 8 +#define EXYNOS3250_RDMA_ISSUE_NUM_MASK (0x7 << EXYNOS3250_RDMA_ISSUE_NUM_SHIFT) +#define EXYNOS3250_ISSUE_GATHER_NUM_SHIFT 0 +#define EXYNOS3250_ISSUE_GATHER_NUM_MASK (0x7 << EXYNOS3250_ISSUE_GATHER_NUM_SHIFT) +#define EXYNOS3250_DMA_MO_COUNT 0x7 + +/* Version register */ +#define EXYNOS3250_VERSION 0x1fc + +/* RGB <-> YUV conversion coefficients */ +#define EXYNOS3250_JPEG_ENC_COEF1 0x01352e1e +#define EXYNOS3250_JPEG_ENC_COEF2 0x00b0ae83 +#define EXYNOS3250_JPEG_ENC_COEF3 0x020cdc13 + +#define EXYNOS3250_JPEG_DEC_COEF1 0x04a80199 +#define EXYNOS3250_JPEG_DEC_COEF2 0x04a9a064 +#define EXYNOS3250_JPEG_DEC_COEF3 0x04a80102 + +#endif /* JPEG_REGS_H_ */ + diff --git a/drivers/media/platform/samsung/s5p-mfc/Kconfig b/drivers/media/platform/samsung/s5p-mfc/Kconfig new file mode 100644 index 000000000..7ee3b0c8d --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/Kconfig @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SAMSUNG_S5P_MFC + tristate "Samsung S5P MFC Video Codec" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_S5PV210 || ARCH_EXYNOS || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + help + MFC 5.1 and 6.x driver for V4L2 diff --git a/drivers/media/platform/samsung/s5p-mfc/Makefile b/drivers/media/platform/samsung/s5p-mfc/Makefile new file mode 100644 index 000000000..0b324af2a --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_VIDEO_SAMSUNG_S5P_MFC) += s5p-mfc.o +s5p-mfc-y += s5p_mfc.o s5p_mfc_intr.o +s5p-mfc-y += s5p_mfc_dec.o s5p_mfc_enc.o +s5p-mfc-y += s5p_mfc_ctrl.o s5p_mfc_pm.o +s5p-mfc-y += s5p_mfc_opr.o s5p_mfc_opr_v5.o s5p_mfc_opr_v6.o +s5p-mfc-y += s5p_mfc_cmd.o s5p_mfc_cmd_v5.o s5p_mfc_cmd_v6.o diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v10.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v10.h new file mode 100644 index 000000000..fadd9139b --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v10.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * + * Copyright (c) 2017 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Register definition file for Samsung MFC V10.x Interface (FIMV) driver + * + */ + +#ifndef _REGS_MFC_V10_H +#define _REGS_MFC_V10_H + +#include +#include "regs-mfc-v8.h" + +/* MFCv10 register definitions*/ +#define S5P_FIMV_MFC_CLOCK_OFF_V10 0x7120 +#define S5P_FIMV_MFC_STATE_V10 0x7124 +#define S5P_FIMV_D_STATIC_BUFFER_ADDR_V10 0xF570 +#define S5P_FIMV_D_STATIC_BUFFER_SIZE_V10 0xF574 +#define S5P_FIMV_E_NUM_T_LAYER_V10 0xFBAC +#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER0_V10 0xFBB0 +#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER1_V10 0xFBB4 +#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER2_V10 0xFBB8 +#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER3_V10 0xFBBC +#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER4_V10 0xFBC0 +#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER5_V10 0xFBC4 +#define S5P_FIMV_E_HIERARCHICAL_QP_LAYER6_V10 0xFBC8 +#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER0_V10 0xFD18 +#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER1_V10 0xFD1C +#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER2_V10 0xFD20 +#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER3_V10 0xFD24 +#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER4_V10 0xFD28 +#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER5_V10 0xFD2C +#define S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER6_V10 0xFD30 +#define S5P_FIMV_E_HEVC_OPTIONS_V10 0xFDD4 +#define S5P_FIMV_E_HEVC_REFRESH_PERIOD_V10 0xFDD8 +#define S5P_FIMV_E_HEVC_CHROMA_QP_OFFSET_V10 0xFDDC +#define S5P_FIMV_E_HEVC_LF_BETA_OFFSET_DIV2_V10 0xFDE0 +#define S5P_FIMV_E_HEVC_LF_TC_OFFSET_DIV2_V10 0xFDE4 +#define S5P_FIMV_E_HEVC_NAL_CONTROL_V10 0xFDE8 + +/* MFCv10 Context buffer sizes */ +#define MFC_CTX_BUF_SIZE_V10 (30 * SZ_1K) +#define MFC_H264_DEC_CTX_BUF_SIZE_V10 (2 * SZ_1M) +#define MFC_OTHER_DEC_CTX_BUF_SIZE_V10 (20 * SZ_1K) +#define MFC_H264_ENC_CTX_BUF_SIZE_V10 (100 * SZ_1K) +#define MFC_HEVC_ENC_CTX_BUF_SIZE_V10 (30 * SZ_1K) +#define MFC_OTHER_ENC_CTX_BUF_SIZE_V10 (15 * SZ_1K) + +/* MFCv10 variant defines */ +#define MAX_FW_SIZE_V10 (SZ_1M) +#define MAX_CPB_SIZE_V10 (3 * SZ_1M) +#define MFC_VERSION_V10 0xA0 +#define MFC_NUM_PORTS_V10 1 + +/* MFCv10 codec defines*/ +#define S5P_FIMV_CODEC_HEVC_DEC 17 +#define S5P_FIMV_CODEC_VP9_DEC 18 +#define S5P_FIMV_CODEC_HEVC_ENC 26 + +/* Decoder buffer size for MFC v10 */ +#define DEC_VP9_STATIC_BUFFER_SIZE 20480 + +/* Encoder buffer size for MFC v10.0 */ +#define ENC_V100_BASE_SIZE(x, y) \ + (((x + 3) * (y + 3) * 8) \ + + ((y * 64) + 1280) * DIV_ROUND_UP(x, 8)) + +#define ENC_V100_H264_ME_SIZE(x, y) \ + (ENC_V100_BASE_SIZE(x, y) \ + + (DIV_ROUND_UP(x * y, 64) * 32)) + +#define ENC_V100_MPEG4_ME_SIZE(x, y) \ + (ENC_V100_BASE_SIZE(x, y) \ + + (DIV_ROUND_UP(x * y, 128) * 16)) + +#define ENC_V100_VP8_ME_SIZE(x, y) \ + ENC_V100_BASE_SIZE(x, y) + +#define ENC_V100_HEVC_ME_SIZE(x, y) \ + (((x + 3) * (y + 3) * 32) \ + + ((y * 128) + 1280) * DIV_ROUND_UP(x, 4)) + +#endif /*_REGS_MFC_V10_H*/ + diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v6.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v6.h new file mode 100644 index 000000000..fa49fe580 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v6.h @@ -0,0 +1,408 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definition file for Samsung MFC V6.x Interface (FIMV) driver + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef _REGS_FIMV_V6_H +#define _REGS_FIMV_V6_H + +#include +#include + +#define S5P_FIMV_REG_SIZE_V6 (S5P_FIMV_END_ADDR - S5P_FIMV_START_ADDR) +#define S5P_FIMV_REG_COUNT_V6 ((S5P_FIMV_END_ADDR - S5P_FIMV_START_ADDR) / 4) + +/* Number of bits that the buffer address should be shifted for particular + * MFC buffers. */ +#define S5P_FIMV_MEM_OFFSET_V6 0 + +#define S5P_FIMV_START_ADDR_V6 0x0000 +#define S5P_FIMV_END_ADDR_V6 0xfd80 + +#define S5P_FIMV_REG_CLEAR_BEGIN_V6 0xf000 +#define S5P_FIMV_REG_CLEAR_COUNT_V6 1024 + +/* Codec Common Registers */ +#define S5P_FIMV_RISC_ON_V6 0x0000 +#define S5P_FIMV_RISC2HOST_INT_V6 0x003C +#define S5P_FIMV_HOST2RISC_INT_V6 0x0044 +#define S5P_FIMV_RISC_BASE_ADDRESS_V6 0x0054 + +#define S5P_FIMV_MFC_RESET_V6 0x1070 + +#define S5P_FIMV_HOST2RISC_CMD_V6 0x1100 +#define S5P_FIMV_H2R_CMD_EMPTY_V6 0 +#define S5P_FIMV_H2R_CMD_SYS_INIT_V6 1 +#define S5P_FIMV_H2R_CMD_OPEN_INSTANCE_V6 2 +#define S5P_FIMV_CH_SEQ_HEADER_V6 3 +#define S5P_FIMV_CH_INIT_BUFS_V6 4 +#define S5P_FIMV_CH_FRAME_START_V6 5 +#define S5P_FIMV_H2R_CMD_CLOSE_INSTANCE_V6 6 +#define S5P_FIMV_H2R_CMD_SLEEP_V6 7 +#define S5P_FIMV_H2R_CMD_WAKEUP_V6 8 +#define S5P_FIMV_CH_LAST_FRAME_V6 9 +#define S5P_FIMV_H2R_CMD_FLUSH_V6 10 +/* RMVME: REALLOC used? */ +#define S5P_FIMV_CH_FRAME_START_REALLOC_V6 5 + +#define S5P_FIMV_RISC2HOST_CMD_V6 0x1104 +#define S5P_FIMV_R2H_CMD_EMPTY_V6 0 +#define S5P_FIMV_R2H_CMD_SYS_INIT_RET_V6 1 +#define S5P_FIMV_R2H_CMD_OPEN_INSTANCE_RET_V6 2 +#define S5P_FIMV_R2H_CMD_SEQ_DONE_RET_V6 3 +#define S5P_FIMV_R2H_CMD_INIT_BUFFERS_RET_V6 4 + +#define S5P_FIMV_R2H_CMD_CLOSE_INSTANCE_RET_V6 6 +#define S5P_FIMV_R2H_CMD_SLEEP_RET_V6 7 +#define S5P_FIMV_R2H_CMD_WAKEUP_RET_V6 8 +#define S5P_FIMV_R2H_CMD_COMPLETE_SEQ_RET_V6 9 +#define S5P_FIMV_R2H_CMD_DPB_FLUSH_RET_V6 10 +#define S5P_FIMV_R2H_CMD_NAL_ABORT_RET_V6 11 +#define S5P_FIMV_R2H_CMD_FW_STATUS_RET_V6 12 +#define S5P_FIMV_R2H_CMD_FRAME_DONE_RET_V6 13 +#define S5P_FIMV_R2H_CMD_FIELD_DONE_RET_V6 14 +#define S5P_FIMV_R2H_CMD_SLICE_DONE_RET_V6 15 +#define S5P_FIMV_R2H_CMD_ENC_BUFFER_FUL_RET_V6 16 +#define S5P_FIMV_R2H_CMD_ERR_RET_V6 32 + +#define S5P_FIMV_MFC_BUS_RESET_CTRL 0x7110 +#define S5P_FIMV_FW_VERSION_V6 0xf000 + +#define S5P_FIMV_INSTANCE_ID_V6 0xf008 +#define S5P_FIMV_CODEC_TYPE_V6 0xf00c +#define S5P_FIMV_CONTEXT_MEM_ADDR_V6 0xf014 +#define S5P_FIMV_CONTEXT_MEM_SIZE_V6 0xf018 +#define S5P_FIMV_PIXEL_FORMAT_V6 0xf020 + +#define S5P_FIMV_METADATA_ENABLE_V6 0xf024 +#define S5P_FIMV_DBG_BUFFER_ADDR_V6 0xf030 +#define S5P_FIMV_DBG_BUFFER_SIZE_V6 0xf034 +#define S5P_FIMV_RET_INSTANCE_ID_V6 0xf070 + +#define S5P_FIMV_ERROR_CODE_V6 0xf074 +#define S5P_FIMV_ERR_WARNINGS_START_V6 160 +#define S5P_FIMV_ERR_DEC_MASK_V6 0xffff +#define S5P_FIMV_ERR_DEC_SHIFT_V6 0 +#define S5P_FIMV_ERR_DSPL_MASK_V6 0xffff0000 +#define S5P_FIMV_ERR_DSPL_SHIFT_V6 16 + +#define S5P_FIMV_DBG_BUFFER_OUTPUT_SIZE_V6 0xf078 +#define S5P_FIMV_METADATA_STATUS_V6 0xf07C +#define S5P_FIMV_METADATA_ADDR_MB_INFO_V6 0xf080 +#define S5P_FIMV_METADATA_SIZE_MB_INFO_V6 0xf084 + +/* Decoder Registers */ +#define S5P_FIMV_D_CRC_CTRL_V6 0xf0b0 +#define S5P_FIMV_D_DEC_OPTIONS_V6 0xf0b4 +#define S5P_FIMV_D_OPT_FMO_ASO_CTRL_MASK_V6 4 +#define S5P_FIMV_D_OPT_DDELAY_EN_SHIFT_V6 3 +#define S5P_FIMV_D_OPT_LF_CTRL_SHIFT_V6 1 +#define S5P_FIMV_D_OPT_LF_CTRL_MASK_V6 0x3 +#define S5P_FIMV_D_OPT_TILE_MODE_SHIFT_V6 0 + +#define S5P_FIMV_D_DISPLAY_DELAY_V6 0xf0b8 + +#define S5P_FIMV_D_SET_FRAME_WIDTH_V6 0xf0bc +#define S5P_FIMV_D_SET_FRAME_HEIGHT_V6 0xf0c0 + +#define S5P_FIMV_D_SEI_ENABLE_V6 0xf0c4 + +/* Buffer setting registers */ +#define S5P_FIMV_D_MIN_NUM_DPB_V6 0xf0f0 +#define S5P_FIMV_D_MIN_LUMA_DPB_SIZE_V6 0xf0f4 +#define S5P_FIMV_D_MIN_CHROMA_DPB_SIZE_V6 0xf0f8 +#define S5P_FIMV_D_MVC_NUM_VIEWS_V6 0xf0fc +#define S5P_FIMV_D_MIN_NUM_MV_V6 0xf100 +#define S5P_FIMV_D_NUM_DPB_V6 0xf130 +#define S5P_FIMV_D_LUMA_DPB_SIZE_V6 0xf134 +#define S5P_FIMV_D_CHROMA_DPB_SIZE_V6 0xf138 +#define S5P_FIMV_D_MV_BUFFER_SIZE_V6 0xf13c + +#define S5P_FIMV_D_LUMA_DPB_V6 0xf140 +#define S5P_FIMV_D_CHROMA_DPB_V6 0xf240 +#define S5P_FIMV_D_MV_BUFFER_V6 0xf340 + +#define S5P_FIMV_D_SCRATCH_BUFFER_ADDR_V6 0xf440 +#define S5P_FIMV_D_SCRATCH_BUFFER_SIZE_V6 0xf444 +#define S5P_FIMV_D_METADATA_BUFFER_ADDR_V6 0xf448 +#define S5P_FIMV_D_METADATA_BUFFER_SIZE_V6 0xf44c +#define S5P_FIMV_D_NUM_MV_V6 0xf478 +#define S5P_FIMV_D_CPB_BUFFER_ADDR_V6 0xf4b0 +#define S5P_FIMV_D_CPB_BUFFER_SIZE_V6 0xf4b4 + +#define S5P_FIMV_D_AVAILABLE_DPB_FLAG_UPPER_V6 0xf4b8 +#define S5P_FIMV_D_AVAILABLE_DPB_FLAG_LOWER_V6 0xf4bc +#define S5P_FIMV_D_CPB_BUFFER_OFFSET_V6 0xf4c0 +#define S5P_FIMV_D_SLICE_IF_ENABLE_V6 0xf4c4 +#define S5P_FIMV_D_PICTURE_TAG_V6 0xf4c8 +#define S5P_FIMV_D_STREAM_DATA_SIZE_V6 0xf4d0 +#define S5P_FIMV_D_INIT_BUFFER_OPTIONS_V6 0xf47c + +/* Display information register */ +#define S5P_FIMV_D_DISPLAY_FRAME_WIDTH_V6 0xf500 +#define S5P_FIMV_D_DISPLAY_FRAME_HEIGHT_V6 0xf504 + +/* Display status */ +#define S5P_FIMV_D_DISPLAY_STATUS_V6 0xf508 + +#define S5P_FIMV_D_DISPLAY_LUMA_ADDR_V6 0xf50c +#define S5P_FIMV_D_DISPLAY_CHROMA_ADDR_V6 0xf510 + +#define S5P_FIMV_D_DISPLAY_FRAME_TYPE_V6 0xf514 + +#define S5P_FIMV_D_DISPLAY_CROP_INFO1_V6 0xf518 +#define S5P_FIMV_D_DISPLAY_CROP_INFO2_V6 0xf51c +#define S5P_FIMV_D_DISPLAY_PICTURE_PROFILE_V6 0xf520 +#define S5P_FIMV_D_DISPLAY_LUMA_CRC_TOP_V6 0xf524 +#define S5P_FIMV_D_DISPLAY_CHROMA_CRC_TOP_V6 0xf528 +#define S5P_FIMV_D_DISPLAY_LUMA_CRC_BOT_V6 0xf52c +#define S5P_FIMV_D_DISPLAY_CHROMA_CRC_BOT_V6 0xf530 +#define S5P_FIMV_D_DISPLAY_ASPECT_RATIO_V6 0xf534 +#define S5P_FIMV_D_DISPLAY_EXTENDED_AR_V6 0xf538 + +/* Decoded picture information register */ +#define S5P_FIMV_D_DECODED_FRAME_WIDTH_V6 0xf53c +#define S5P_FIMV_D_DECODED_FRAME_HEIGHT_V6 0xf540 +#define S5P_FIMV_D_DECODED_STATUS_V6 0xf544 +#define S5P_FIMV_DEC_CRC_GEN_MASK_V6 0x1 +#define S5P_FIMV_DEC_CRC_GEN_SHIFT_V6 6 + +#define S5P_FIMV_D_DECODED_LUMA_ADDR_V6 0xf548 +#define S5P_FIMV_D_DECODED_CHROMA_ADDR_V6 0xf54c + +#define S5P_FIMV_D_DECODED_FRAME_TYPE_V6 0xf550 +#define S5P_FIMV_DECODE_FRAME_MASK_V6 7 + +#define S5P_FIMV_D_DECODED_CROP_INFO1_V6 0xf554 +#define S5P_FIMV_D_DECODED_CROP_INFO2_V6 0xf558 +#define S5P_FIMV_D_DECODED_PICTURE_PROFILE_V6 0xf55c +#define S5P_FIMV_D_DECODED_NAL_SIZE_V6 0xf560 +#define S5P_FIMV_D_DECODED_LUMA_CRC_TOP_V6 0xf564 +#define S5P_FIMV_D_DECODED_CHROMA_CRC_TOP_V6 0xf568 +#define S5P_FIMV_D_DECODED_LUMA_CRC_BOT_V6 0xf56c +#define S5P_FIMV_D_DECODED_CHROMA_CRC_BOT_V6 0xf570 + +/* Returned value register for specific setting */ +#define S5P_FIMV_D_RET_PICTURE_TAG_TOP_V6 0xf574 +#define S5P_FIMV_D_RET_PICTURE_TAG_BOT_V6 0xf578 +#define S5P_FIMV_D_RET_PICTURE_TIME_TOP_V6 0xf57c +#define S5P_FIMV_D_RET_PICTURE_TIME_BOT_V6 0xf580 +#define S5P_FIMV_D_CHROMA_FORMAT_V6 0xf588 +#define S5P_FIMV_D_MPEG4_INFO_V6 0xf58c +#define S5P_FIMV_D_H264_INFO_V6 0xf590 + +#define S5P_FIMV_D_METADATA_ADDR_CONCEALED_MB_V6 0xf594 +#define S5P_FIMV_D_METADATA_SIZE_CONCEALED_MB_V6 0xf598 +#define S5P_FIMV_D_METADATA_ADDR_VC1_PARAM_V6 0xf59c +#define S5P_FIMV_D_METADATA_SIZE_VC1_PARAM_V6 0xf5a0 +#define S5P_FIMV_D_METADATA_ADDR_SEI_NAL_V6 0xf5a4 +#define S5P_FIMV_D_METADATA_SIZE_SEI_NAL_V6 0xf5a8 +#define S5P_FIMV_D_METADATA_ADDR_VUI_V6 0xf5ac +#define S5P_FIMV_D_METADATA_SIZE_VUI_V6 0xf5b0 + +#define S5P_FIMV_D_MVC_VIEW_ID_V6 0xf5b4 + +/* SEI related information */ +#define S5P_FIMV_D_FRAME_PACK_SEI_AVAIL_V6 0xf5f0 +#define S5P_FIMV_D_FRAME_PACK_ARRGMENT_ID_V6 0xf5f4 +#define S5P_FIMV_D_FRAME_PACK_SEI_INFO_V6 0xf5f8 +#define S5P_FIMV_D_FRAME_PACK_GRID_POS_V6 0xf5fc + +/* Encoder Registers */ +#define S5P_FIMV_E_FRAME_WIDTH_V6 0xf770 +#define S5P_FIMV_E_FRAME_HEIGHT_V6 0xf774 +#define S5P_FIMV_E_CROPPED_FRAME_WIDTH_V6 0xf778 +#define S5P_FIMV_E_CROPPED_FRAME_HEIGHT_V6 0xf77c +#define S5P_FIMV_E_FRAME_CROP_OFFSET_V6 0xf780 +#define S5P_FIMV_E_ENC_OPTIONS_V6 0xf784 +#define S5P_FIMV_E_PICTURE_PROFILE_V6 0xf788 +#define S5P_FIMV_E_FIXED_PICTURE_QP_V6 0xf790 + +#define S5P_FIMV_E_RC_CONFIG_V6 0xf794 +#define S5P_FIMV_E_RC_QP_BOUND_V6 0xf798 +#define S5P_FIMV_E_RC_RPARAM_V6 0xf79c +#define S5P_FIMV_E_MB_RC_CONFIG_V6 0xf7a0 +#define S5P_FIMV_E_PADDING_CTRL_V6 0xf7a4 +#define S5P_FIMV_E_MV_HOR_RANGE_V6 0xf7ac +#define S5P_FIMV_E_MV_VER_RANGE_V6 0xf7b0 +#define S5P_FIMV_E_MV_RANGE_V6_MASK 0x3fff + +#define S5P_FIMV_E_VBV_BUFFER_SIZE_V6 0xf84c +#define S5P_FIMV_E_VBV_INIT_DELAY_V6 0xf850 +#define S5P_FIMV_E_NUM_DPB_V6 0xf890 +#define S5P_FIMV_E_LUMA_DPB_V6 0xf8c0 +#define S5P_FIMV_E_CHROMA_DPB_V6 0xf904 +#define S5P_FIMV_E_ME_BUFFER_V6 0xf948 + +#define S5P_FIMV_E_SCRATCH_BUFFER_ADDR_V6 0xf98c +#define S5P_FIMV_E_SCRATCH_BUFFER_SIZE_V6 0xf990 +#define S5P_FIMV_E_TMV_BUFFER0_V6 0xf994 +#define S5P_FIMV_E_TMV_BUFFER1_V6 0xf998 +#define S5P_FIMV_E_SOURCE_LUMA_ADDR_V6 0xf9f0 +#define S5P_FIMV_E_SOURCE_CHROMA_ADDR_V6 0xf9f4 +#define S5P_FIMV_E_STREAM_BUFFER_ADDR_V6 0xf9f8 +#define S5P_FIMV_E_STREAM_BUFFER_SIZE_V6 0xf9fc +#define S5P_FIMV_E_ROI_BUFFER_ADDR_V6 0xfA00 + +#define S5P_FIMV_E_PARAM_CHANGE_V6 0xfa04 +#define S5P_FIMV_E_IR_SIZE_V6 0xfa08 +#define S5P_FIMV_E_GOP_CONFIG_V6 0xfa0c +#define S5P_FIMV_E_MSLICE_MODE_V6 0xfa10 +#define S5P_FIMV_E_MSLICE_SIZE_MB_V6 0xfa14 +#define S5P_FIMV_E_MSLICE_SIZE_BITS_V6 0xfa18 +#define S5P_FIMV_E_FRAME_INSERTION_V6 0xfa1c + +#define S5P_FIMV_E_RC_FRAME_RATE_V6 0xfa20 +#define S5P_FIMV_E_RC_BIT_RATE_V6 0xfa24 +#define S5P_FIMV_E_RC_QP_OFFSET_V6 0xfa28 +#define S5P_FIMV_E_RC_ROI_CTRL_V6 0xfa2c +#define S5P_FIMV_E_PICTURE_TAG_V6 0xfa30 +#define S5P_FIMV_E_BIT_COUNT_ENABLE_V6 0xfa34 +#define S5P_FIMV_E_MAX_BIT_COUNT_V6 0xfa38 +#define S5P_FIMV_E_MIN_BIT_COUNT_V6 0xfa3c + +#define S5P_FIMV_E_METADATA_BUFFER_ADDR_V6 0xfa40 +#define S5P_FIMV_E_METADATA_BUFFER_SIZE_V6 0xfa44 +#define S5P_FIMV_E_STREAM_SIZE_V6 0xfa80 +#define S5P_FIMV_E_SLICE_TYPE_V6 0xfa84 +#define S5P_FIMV_E_PICTURE_COUNT_V6 0xfa88 +#define S5P_FIMV_E_RET_PICTURE_TAG_V6 0xfa8c +#define S5P_FIMV_E_STREAM_BUFFER_WRITE_POINTER_V6 0xfa90 + +#define S5P_FIMV_E_ENCODED_SOURCE_LUMA_ADDR_V6 0xfa94 +#define S5P_FIMV_E_ENCODED_SOURCE_CHROMA_ADDR_V6 0xfa98 +#define S5P_FIMV_E_RECON_LUMA_DPB_ADDR_V6 0xfa9c +#define S5P_FIMV_E_RECON_CHROMA_DPB_ADDR_V6 0xfaa0 +#define S5P_FIMV_E_METADATA_ADDR_ENC_SLICE_V6 0xfaa4 +#define S5P_FIMV_E_METADATA_SIZE_ENC_SLICE_V6 0xfaa8 + +#define S5P_FIMV_E_MPEG4_OPTIONS_V6 0xfb10 +#define S5P_FIMV_E_MPEG4_HEC_PERIOD_V6 0xfb14 +#define S5P_FIMV_E_ASPECT_RATIO_V6 0xfb50 +#define S5P_FIMV_E_EXTENDED_SAR_V6 0xfb54 + +#define S5P_FIMV_E_H264_OPTIONS_V6 0xfb58 +#define S5P_FIMV_E_H264_LF_ALPHA_OFFSET_V6 0xfb5c +#define S5P_FIMV_E_H264_LF_BETA_OFFSET_V6 0xfb60 +#define S5P_FIMV_E_H264_I_PERIOD_V6 0xfb64 + +#define S5P_FIMV_E_H264_FMO_SLICE_GRP_MAP_TYPE_V6 0xfb68 +#define S5P_FIMV_E_H264_FMO_NUM_SLICE_GRP_MINUS1_V6 0xfb6c +#define S5P_FIMV_E_H264_FMO_SLICE_GRP_CHANGE_DIR_V6 0xfb70 +#define S5P_FIMV_E_H264_FMO_SLICE_GRP_CHANGE_RATE_MINUS1_V6 0xfb74 +#define S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_0_V6 0xfb78 +#define S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_1_V6 0xfb7c +#define S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_2_V6 0xfb80 +#define S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_3_V6 0xfb84 + +#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_0_V6 0xfb88 +#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_1_V6 0xfb8c +#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_2_V6 0xfb90 +#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_3_V6 0xfb94 +#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_4_V6 0xfb98 +#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_5_V6 0xfb9c +#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_6_V6 0xfba0 +#define S5P_FIMV_E_H264_ASO_SLICE_ORDER_7_V6 0xfba4 + +#define S5P_FIMV_E_H264_CHROMA_QP_OFFSET_V6 0xfba8 +#define S5P_FIMV_E_H264_NUM_T_LAYER_V6 0xfbac + +#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER0_V6 0xfbb0 +#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER1_V6 0xfbb4 +#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER2_V6 0xfbb8 +#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER3_V6 0xfbbc +#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER4_V6 0xfbc0 +#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER5_V6 0xfbc4 +#define S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER6_V6 0xfbc8 + +#define S5P_FIMV_E_H264_FRAME_PACKING_SEI_INFO_V6 0xfc4c +#define S5P_FIMV_ENC_FP_ARRANGEMENT_TYPE_SIDE_BY_SIDE_V6 0 +#define S5P_FIMV_ENC_FP_ARRANGEMENT_TYPE_TOP_BOTTOM_V6 1 +#define S5P_FIMV_ENC_FP_ARRANGEMENT_TYPE_TEMPORAL_V6 2 + +#define S5P_FIMV_E_MVC_FRAME_QP_VIEW1_V6 0xfd40 +#define S5P_FIMV_E_MVC_RC_FRAME_RATE_VIEW1_V6 0xfd44 +#define S5P_FIMV_E_MVC_RC_BIT_RATE_VIEW1_V6 0xfd48 +#define S5P_FIMV_E_MVC_RC_QBOUND_VIEW1_V6 0xfd4c +#define S5P_FIMV_E_MVC_RC_RPARA_VIEW1_V6 0xfd50 +#define S5P_FIMV_E_MVC_INTER_VIEW_PREDICTION_ON_V6 0xfd80 + +/* Codec numbers */ +#define S5P_FIMV_CODEC_NONE_V6 -1 + + +#define S5P_FIMV_CODEC_H264_DEC_V6 0 +#define S5P_FIMV_CODEC_H264_MVC_DEC_V6 1 + +#define S5P_FIMV_CODEC_MPEG4_DEC_V6 3 +#define S5P_FIMV_CODEC_FIMV1_DEC_V6 4 +#define S5P_FIMV_CODEC_FIMV2_DEC_V6 5 +#define S5P_FIMV_CODEC_FIMV3_DEC_V6 6 +#define S5P_FIMV_CODEC_FIMV4_DEC_V6 7 +#define S5P_FIMV_CODEC_H263_DEC_V6 8 +#define S5P_FIMV_CODEC_VC1RCV_DEC_V6 9 +#define S5P_FIMV_CODEC_VC1_DEC_V6 10 +/* FIXME: Add 11~12 */ +#define S5P_FIMV_CODEC_MPEG2_DEC_V6 13 +#define S5P_FIMV_CODEC_VP8_DEC_V6 14 +/* FIXME: Add 15~16 */ +#define S5P_FIMV_CODEC_H264_ENC_V6 20 +#define S5P_FIMV_CODEC_H264_MVC_ENC_V6 21 + +#define S5P_FIMV_CODEC_MPEG4_ENC_V6 23 +#define S5P_FIMV_CODEC_H263_ENC_V6 24 + +#define S5P_FIMV_NV12M_HALIGN_V6 16 +#define S5P_FIMV_NV12MT_HALIGN_V6 16 +#define S5P_FIMV_NV12MT_VALIGN_V6 16 + +#define S5P_FIMV_TMV_BUFFER_ALIGN_V6 16 +#define S5P_FIMV_LUMA_DPB_BUFFER_ALIGN_V6 256 +#define S5P_FIMV_CHROMA_DPB_BUFFER_ALIGN_V6 256 +#define S5P_FIMV_ME_BUFFER_ALIGN_V6 256 +#define S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6 256 + +#define S5P_FIMV_LUMA_MB_TO_PIXEL_V6 256 +#define S5P_FIMV_CHROMA_MB_TO_PIXEL_V6 128 +#define S5P_FIMV_NUM_TMV_BUFFERS_V6 2 + +#define S5P_FIMV_MAX_FRAME_SIZE_V6 (2 * SZ_1M) +#define S5P_FIMV_NUM_PIXELS_IN_MB_ROW_V6 16 +#define S5P_FIMV_NUM_PIXELS_IN_MB_COL_V6 16 + +/* Buffer size requirements defined by hardware */ +#define S5P_FIMV_TMV_BUFFER_SIZE_V6(w, h) (((w) + 1) * ((h) + 3) * 8) +#define S5P_FIMV_ME_BUFFER_SIZE_V6(imw, imh, mbw, mbh) \ + (((((imw + 127) / 64) * 16) * DIV_ROUND_UP(imh, 64) * 256) + \ + (DIV_ROUND_UP((mbw) * (mbh), 32) * 16)) +#define S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V6(w, h) (((w) * 192) + 64) +#define S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6(w, h) \ + ((w) * 144 + 8192 * (h) + 49216 + 1048576) +#define S5P_FIMV_SCRATCH_BUF_SIZE_VC1_DEC_V6(w, h) \ + (2096 * ((w) + (h) + 1)) +#define S5P_FIMV_SCRATCH_BUF_SIZE_H263_DEC_V6(w, h) \ + S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6(w, h) +#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_DEC_V6(w, h) \ + ((w) * 32 + (h) * 128 + (((w) + 1) / 2) * 64 + 2112) +#define S5P_FIMV_SCRATCH_BUF_SIZE_H264_ENC_V6(w, h) \ + (((w) * 64) + (((w) + 1) * 16) + (4096 * 16)) +#define S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_ENC_V6(w, h) \ + (((w) * 16) + (((w) + 1) * 16)) + +/* MFC Context buffer sizes */ +#define MFC_CTX_BUF_SIZE_V6 (28 * SZ_1K) /* 28KB */ +#define MFC_H264_DEC_CTX_BUF_SIZE_V6 (2 * SZ_1M) /* 2MB */ +#define MFC_OTHER_DEC_CTX_BUF_SIZE_V6 (20 * SZ_1K) /* 20KB */ +#define MFC_H264_ENC_CTX_BUF_SIZE_V6 (100 * SZ_1K) /* 100KB */ +#define MFC_OTHER_ENC_CTX_BUF_SIZE_V6 (12 * SZ_1K) /* 12KB */ + +/* MFCv6 variant defines */ +#define MAX_FW_SIZE_V6 (SZ_512K) /* 512KB */ +#define MAX_CPB_SIZE_V6 (3 * SZ_1M) /* 3MB */ +#define MFC_VERSION_V6 0x61 +#define MFC_NUM_PORTS_V6 1 + +#endif /* _REGS_FIMV_V6_H */ diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v7.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v7.h new file mode 100644 index 000000000..4a7adfdaa --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v7.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definition file for Samsung MFC V7.x Interface (FIMV) driver + * + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef _REGS_MFC_V7_H +#define _REGS_MFC_V7_H + +#include "regs-mfc-v6.h" + +/* Additional features of v7 */ +#define S5P_FIMV_CODEC_VP8_ENC_V7 25 + +/* Additional registers for v7 */ +#define S5P_FIMV_E_SOURCE_FIRST_ADDR_V7 0xf9e0 +#define S5P_FIMV_E_SOURCE_SECOND_ADDR_V7 0xf9e4 +#define S5P_FIMV_E_SOURCE_THIRD_ADDR_V7 0xf9e8 +#define S5P_FIMV_E_SOURCE_FIRST_STRIDE_V7 0xf9ec +#define S5P_FIMV_E_SOURCE_SECOND_STRIDE_V7 0xf9f0 +#define S5P_FIMV_E_SOURCE_THIRD_STRIDE_V7 0xf9f4 + +#define S5P_FIMV_E_ENCODED_SOURCE_FIRST_ADDR_V7 0xfa70 +#define S5P_FIMV_E_ENCODED_SOURCE_SECOND_ADDR_V7 0xfa74 + +#define S5P_FIMV_E_VP8_OPTIONS_V7 0xfdb0 +#define S5P_FIMV_E_VP8_FILTER_OPTIONS_V7 0xfdb4 +#define S5P_FIMV_E_VP8_GOLDEN_FRAME_OPTION_V7 0xfdb8 +#define S5P_FIMV_E_VP8_NUM_T_LAYER_V7 0xfdc4 + +/* MFCv7 variant defines */ +#define MAX_FW_SIZE_V7 (SZ_512K) /* 512KB */ +#define MAX_CPB_SIZE_V7 (3 * SZ_1M) /* 3MB */ +#define MFC_VERSION_V7 0x72 +#define MFC_NUM_PORTS_V7 1 + +#define MFC_LUMA_PAD_BYTES_V7 256 +#define MFC_CHROMA_PAD_BYTES_V7 128 + +/* MFCv7 Context buffer sizes */ +#define MFC_CTX_BUF_SIZE_V7 (30 * SZ_1K) /* 30KB */ +#define MFC_H264_DEC_CTX_BUF_SIZE_V7 (2 * SZ_1M) /* 2MB */ +#define MFC_OTHER_DEC_CTX_BUF_SIZE_V7 (20 * SZ_1K) /* 20KB */ +#define MFC_H264_ENC_CTX_BUF_SIZE_V7 (100 * SZ_1K) /* 100KB */ +#define MFC_OTHER_ENC_CTX_BUF_SIZE_V7 (10 * SZ_1K) /* 10KB */ + +/* Buffer size defines */ +#define S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V7(w, h) \ + (SZ_1M + ((w) * 144) + (8192 * (h)) + 49216) + +#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V7(w, h) \ + (((w) * 48) + 8192 + ((((w) + 1) / 2) * 128) + 144 + \ + ((((((w) * 16) * ((h) * 16)) * 3) / 2) * 4)) + +#endif /*_REGS_MFC_V7_H*/ diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v8.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v8.h new file mode 100644 index 000000000..162e3c7e9 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc-v8.h @@ -0,0 +1,123 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definition file for Samsung MFC V8.x Interface (FIMV) driver + * + * Copyright (c) 2014 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef _REGS_MFC_V8_H +#define _REGS_MFC_V8_H + +#include +#include "regs-mfc-v7.h" + +/* Additional registers for v8 */ +#define S5P_FIMV_D_MVC_NUM_VIEWS_V8 0xf104 +#define S5P_FIMV_D_MIN_SCRATCH_BUFFER_SIZE_V8 0xf108 +#define S5P_FIMV_D_FIRST_PLANE_DPB_SIZE_V8 0xf144 +#define S5P_FIMV_D_SECOND_PLANE_DPB_SIZE_V8 0xf148 +#define S5P_FIMV_D_MV_BUFFER_SIZE_V8 0xf150 + +#define S5P_FIMV_D_FIRST_PLANE_DPB_STRIDE_SIZE_V8 0xf138 +#define S5P_FIMV_D_SECOND_PLANE_DPB_STRIDE_SIZE_V8 0xf13c + +#define S5P_FIMV_D_FIRST_PLANE_DPB_V8 0xf160 +#define S5P_FIMV_D_SECOND_PLANE_DPB_V8 0xf260 +#define S5P_FIMV_D_MV_BUFFER_V8 0xf460 + +#define S5P_FIMV_D_NUM_MV_V8 0xf134 +#define S5P_FIMV_D_INIT_BUFFER_OPTIONS_V8 0xf154 + +#define S5P_FIMV_D_SCRATCH_BUFFER_ADDR_V8 0xf560 +#define S5P_FIMV_D_SCRATCH_BUFFER_SIZE_V8 0xf564 + +#define S5P_FIMV_D_CPB_BUFFER_ADDR_V8 0xf5b0 +#define S5P_FIMV_D_CPB_BUFFER_SIZE_V8 0xf5b4 +#define S5P_FIMV_D_AVAILABLE_DPB_FLAG_LOWER_V8 0xf5bc +#define S5P_FIMV_D_CPB_BUFFER_OFFSET_V8 0xf5c0 +#define S5P_FIMV_D_SLICE_IF_ENABLE_V8 0xf5c4 +#define S5P_FIMV_D_STREAM_DATA_SIZE_V8 0xf5d0 + +/* Display information register */ +#define S5P_FIMV_D_DISPLAY_FRAME_WIDTH_V8 0xf600 +#define S5P_FIMV_D_DISPLAY_FRAME_HEIGHT_V8 0xf604 + +/* Display status */ +#define S5P_FIMV_D_DISPLAY_STATUS_V8 0xf608 + +#define S5P_FIMV_D_DISPLAY_FIRST_PLANE_ADDR_V8 0xf60c +#define S5P_FIMV_D_DISPLAY_SECOND_PLANE_ADDR_V8 0xf610 + +#define S5P_FIMV_D_DISPLAY_FRAME_TYPE_V8 0xf618 +#define S5P_FIMV_D_DISPLAY_CROP_INFO1_V8 0xf61c +#define S5P_FIMV_D_DISPLAY_CROP_INFO2_V8 0xf620 +#define S5P_FIMV_D_DISPLAY_PICTURE_PROFILE_V8 0xf624 + +/* Decoded picture information register */ +#define S5P_FIMV_D_DECODED_STATUS_V8 0xf644 +#define S5P_FIMV_D_DECODED_FIRST_PLANE_ADDR_V8 0xf648 +#define S5P_FIMV_D_DECODED_SECOND_PLANE_ADDR_V8 0xf64c +#define S5P_FIMV_D_DECODED_THIRD_PLANE_ADDR_V8 0xf650 +#define S5P_FIMV_D_DECODED_FRAME_TYPE_V8 0xf654 +#define S5P_FIMV_D_DECODED_NAL_SIZE_V8 0xf664 + +/* Returned value register for specific setting */ +#define S5P_FIMV_D_RET_PICTURE_TAG_TOP_V8 0xf674 +#define S5P_FIMV_D_RET_PICTURE_TAG_BOT_V8 0xf678 +#define S5P_FIMV_D_MVC_VIEW_ID_V8 0xf6d8 + +/* SEI related information */ +#define S5P_FIMV_D_FRAME_PACK_SEI_AVAIL_V8 0xf6dc + +/* Encoder Registers */ +#define S5P_FIMV_E_FIXED_PICTURE_QP_V8 0xf794 +#define S5P_FIMV_E_RC_CONFIG_V8 0xf798 +#define S5P_FIMV_E_RC_QP_BOUND_V8 0xf79c +#define S5P_FIMV_E_RC_RPARAM_V8 0xf7a4 +#define S5P_FIMV_E_MB_RC_CONFIG_V8 0xf7a8 +#define S5P_FIMV_E_PADDING_CTRL_V8 0xf7ac +#define S5P_FIMV_E_MV_HOR_RANGE_V8 0xf7b4 +#define S5P_FIMV_E_MV_VER_RANGE_V8 0xf7b8 + +#define S5P_FIMV_E_VBV_BUFFER_SIZE_V8 0xf78c +#define S5P_FIMV_E_VBV_INIT_DELAY_V8 0xf790 +#define S5P_FIMV_E_MIN_SCRATCH_BUFFER_SIZE_V8 0xf894 + +#define S5P_FIMV_E_ASPECT_RATIO_V8 0xfb4c +#define S5P_FIMV_E_EXTENDED_SAR_V8 0xfb50 +#define S5P_FIMV_E_H264_OPTIONS_V8 0xfb54 + +/* MFCv8 Context buffer sizes */ +#define MFC_CTX_BUF_SIZE_V8 (36 * SZ_1K) /* 36KB */ +#define MFC_H264_DEC_CTX_BUF_SIZE_V8 (2 * SZ_1M) /* 2MB */ +#define MFC_OTHER_DEC_CTX_BUF_SIZE_V8 (20 * SZ_1K) /* 20KB */ +#define MFC_H264_ENC_CTX_BUF_SIZE_V8 (100 * SZ_1K) /* 100KB */ +#define MFC_OTHER_ENC_CTX_BUF_SIZE_V8 (10 * SZ_1K) /* 10KB */ + +/* Buffer size defines */ +#define S5P_FIMV_TMV_BUFFER_SIZE_V8(w, h) (((w) + 1) * ((h) + 1) * 8) + +#define S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V8(w, h) (((w) * 704) + 2176) +#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_DEC_V8(w, h) \ + (((w) * 576 + (h) * 128) + 4128) + +#define S5P_FIMV_SCRATCH_BUF_SIZE_H264_ENC_V8(w, h) \ + (((w) * 592) + 2336) +#define S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V8(w, h) \ + (((w) * 576) + 10512 + \ + ((((((w) * 16) * ((h) * 16)) * 3) / 2) * 4)) +#define S5P_FIMV_ME_BUFFER_SIZE_V8(imw, imh, mbw, mbh) \ + ((DIV_ROUND_UP((mbw * 16), 64) * DIV_ROUND_UP((mbh * 16), 64) * 256) \ + + (DIV_ROUND_UP((mbw) * (mbh), 32) * 16)) + +/* BUffer alignment defines */ +#define S5P_FIMV_D_ALIGN_PLANE_SIZE_V8 64 + +/* MFCv8 variant defines */ +#define MAX_FW_SIZE_V8 (SZ_512K) /* 512KB */ +#define MAX_CPB_SIZE_V8 (3 * SZ_1M) /* 3MB */ +#define MFC_VERSION_V8 0x80 +#define MFC_NUM_PORTS_V8 1 + +#endif /*_REGS_MFC_V8_H*/ diff --git a/drivers/media/platform/samsung/s5p-mfc/regs-mfc.h b/drivers/media/platform/samsung/s5p-mfc/regs-mfc.h new file mode 100644 index 000000000..9171e8181 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/regs-mfc.h @@ -0,0 +1,459 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Register definition file for Samsung MFC V5.1 Interface (FIMV) driver + * + * Kamil Debski, Copyright (c) 2010 Samsung Electronics + * http://www.samsung.com/ +*/ + +#ifndef _REGS_FIMV_H +#define _REGS_FIMV_H + +#include +#include + +#define S5P_FIMV_REG_SIZE (S5P_FIMV_END_ADDR - S5P_FIMV_START_ADDR) +#define S5P_FIMV_REG_COUNT ((S5P_FIMV_END_ADDR - S5P_FIMV_START_ADDR) / 4) + +/* Number of bits that the buffer address should be shifted for particular + * MFC buffers. */ +#define S5P_FIMV_START_ADDR 0x0000 +#define S5P_FIMV_END_ADDR 0xe008 + +#define S5P_FIMV_SW_RESET 0x0000 +#define S5P_FIMV_RISC_HOST_INT 0x0008 + +/* Command from HOST to RISC */ +#define S5P_FIMV_HOST2RISC_CMD 0x0030 +#define S5P_FIMV_HOST2RISC_ARG1 0x0034 +#define S5P_FIMV_HOST2RISC_ARG2 0x0038 +#define S5P_FIMV_HOST2RISC_ARG3 0x003c +#define S5P_FIMV_HOST2RISC_ARG4 0x0040 + +/* Command from RISC to HOST */ +#define S5P_FIMV_RISC2HOST_CMD 0x0044 +#define S5P_FIMV_RISC2HOST_CMD_MASK 0x1FFFF +#define S5P_FIMV_RISC2HOST_ARG1 0x0048 +#define S5P_FIMV_RISC2HOST_ARG2 0x004c +#define S5P_FIMV_RISC2HOST_ARG3 0x0050 +#define S5P_FIMV_RISC2HOST_ARG4 0x0054 + +#define S5P_FIMV_FW_VERSION 0x0058 +#define S5P_FIMV_SYS_MEM_SZ 0x005c +#define S5P_FIMV_FW_STATUS 0x0080 + +/* Memory controller register */ +#define S5P_FIMV_MC_DRAMBASE_ADR_A 0x0508 +#define S5P_FIMV_MC_DRAMBASE_ADR_B 0x050c +#define S5P_FIMV_MC_STATUS 0x0510 + +/* Common register */ +#define S5P_FIMV_COMMON_BASE_A 0x0600 +#define S5P_FIMV_COMMON_BASE_B 0x0700 + +/* Decoder */ +#define S5P_FIMV_DEC_CHROMA_ADR (S5P_FIMV_COMMON_BASE_A) +#define S5P_FIMV_DEC_LUMA_ADR (S5P_FIMV_COMMON_BASE_B) + +/* H.264 decoding */ +#define S5P_FIMV_H264_VERT_NB_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x8c) + /* vertical neighbor motion vector */ +#define S5P_FIMV_H264_NB_IP_ADR (S5P_FIMV_COMMON_BASE_A + 0x90) + /* neighbor pixels for intra pred */ +#define S5P_FIMV_H264_MV_ADR (S5P_FIMV_COMMON_BASE_B + 0x80) + /* H264 motion vector */ + +/* MPEG4 decoding */ +#define S5P_FIMV_MPEG4_NB_DCAC_ADR (S5P_FIMV_COMMON_BASE_A + 0x8c) + /* neighbor AC/DC coeff. */ +#define S5P_FIMV_MPEG4_UP_NB_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x90) + /* upper neighbor motion vector */ +#define S5P_FIMV_MPEG4_SA_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x94) + /* subseq. anchor motion vector */ +#define S5P_FIMV_MPEG4_OT_LINE_ADR (S5P_FIMV_COMMON_BASE_A + 0x98) + /* overlap transform line */ +#define S5P_FIMV_MPEG4_SP_ADR (S5P_FIMV_COMMON_BASE_A + 0xa8) + /* syntax parser */ + +/* H.263 decoding */ +#define S5P_FIMV_H263_NB_DCAC_ADR (S5P_FIMV_COMMON_BASE_A + 0x8c) +#define S5P_FIMV_H263_UP_NB_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x90) +#define S5P_FIMV_H263_SA_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x94) +#define S5P_FIMV_H263_OT_LINE_ADR (S5P_FIMV_COMMON_BASE_A + 0x98) + +/* VC-1 decoding */ +#define S5P_FIMV_VC1_NB_DCAC_ADR (S5P_FIMV_COMMON_BASE_A + 0x8c) +#define S5P_FIMV_VC1_UP_NB_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x90) +#define S5P_FIMV_VC1_SA_MV_ADR (S5P_FIMV_COMMON_BASE_A + 0x94) +#define S5P_FIMV_VC1_OT_LINE_ADR (S5P_FIMV_COMMON_BASE_A + 0x98) +#define S5P_FIMV_VC1_BITPLANE3_ADR (S5P_FIMV_COMMON_BASE_A + 0x9c) + /* bitplane3 */ +#define S5P_FIMV_VC1_BITPLANE2_ADR (S5P_FIMV_COMMON_BASE_A + 0xa0) + /* bitplane2 */ +#define S5P_FIMV_VC1_BITPLANE1_ADR (S5P_FIMV_COMMON_BASE_A + 0xa4) + /* bitplane1 */ + +/* Encoder */ +#define S5P_FIMV_ENC_REF0_LUMA_ADR (S5P_FIMV_COMMON_BASE_A + 0x1c) +#define S5P_FIMV_ENC_REF1_LUMA_ADR (S5P_FIMV_COMMON_BASE_A + 0x20) + /* reconstructed luma */ +#define S5P_FIMV_ENC_REF0_CHROMA_ADR (S5P_FIMV_COMMON_BASE_B) +#define S5P_FIMV_ENC_REF1_CHROMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x04) + /* reconstructed chroma */ +#define S5P_FIMV_ENC_REF2_LUMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x10) +#define S5P_FIMV_ENC_REF2_CHROMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x08) +#define S5P_FIMV_ENC_REF3_LUMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x14) +#define S5P_FIMV_ENC_REF3_CHROMA_ADR (S5P_FIMV_COMMON_BASE_B + 0x0c) + +/* H.264 encoding */ +#define S5P_FIMV_H264_UP_MV_ADR (S5P_FIMV_COMMON_BASE_A) + /* upper motion vector */ +#define S5P_FIMV_H264_NBOR_INFO_ADR (S5P_FIMV_COMMON_BASE_A + 0x04) + /* entropy engine's neighbor info. */ +#define S5P_FIMV_H264_UP_INTRA_MD_ADR (S5P_FIMV_COMMON_BASE_A + 0x08) + /* upper intra MD */ +#define S5P_FIMV_H264_COZERO_FLAG_ADR (S5P_FIMV_COMMON_BASE_A + 0x10) + /* direct cozero flag */ +#define S5P_FIMV_H264_UP_INTRA_PRED_ADR (S5P_FIMV_COMMON_BASE_B + 0x40) + /* upper intra PRED */ + +/* H.263 encoding */ +#define S5P_FIMV_H263_UP_MV_ADR (S5P_FIMV_COMMON_BASE_A) + /* upper motion vector */ +#define S5P_FIMV_H263_ACDC_COEF_ADR (S5P_FIMV_COMMON_BASE_A + 0x04) + /* upper Q coeff. */ + +/* MPEG4 encoding */ +#define S5P_FIMV_MPEG4_UP_MV_ADR (S5P_FIMV_COMMON_BASE_A) + /* upper motion vector */ +#define S5P_FIMV_MPEG4_ACDC_COEF_ADR (S5P_FIMV_COMMON_BASE_A + 0x04) + /* upper Q coeff. */ +#define S5P_FIMV_MPEG4_COZERO_FLAG_ADR (S5P_FIMV_COMMON_BASE_A + 0x10) + /* direct cozero flag */ + +#define S5P_FIMV_ENC_REF_B_LUMA_ADR 0x062c /* ref B Luma addr */ +#define S5P_FIMV_ENC_REF_B_CHROMA_ADR 0x0630 /* ref B Chroma addr */ + +#define S5P_FIMV_ENC_CUR_LUMA_ADR 0x0718 /* current Luma addr */ +#define S5P_FIMV_ENC_CUR_CHROMA_ADR 0x071C /* current Chroma addr */ + +/* Codec common register */ +#define S5P_FIMV_ENC_HSIZE_PX 0x0818 /* frame width at encoder */ +#define S5P_FIMV_ENC_VSIZE_PX 0x081c /* frame height at encoder */ +#define S5P_FIMV_ENC_PROFILE 0x0830 /* profile register */ +#define S5P_FIMV_ENC_PROFILE_H264_MAIN 0 +#define S5P_FIMV_ENC_PROFILE_H264_HIGH 1 +#define S5P_FIMV_ENC_PROFILE_H264_BASELINE 2 +#define S5P_FIMV_ENC_PROFILE_H264_CONSTRAINED_BASELINE 3 +#define S5P_FIMV_ENC_PROFILE_MPEG4_SIMPLE 0 +#define S5P_FIMV_ENC_PROFILE_MPEG4_ADVANCED_SIMPLE 1 +#define S5P_FIMV_ENC_PIC_STRUCT 0x083c /* picture field/frame flag */ +#define S5P_FIMV_ENC_LF_CTRL 0x0848 /* loop filter control */ +#define S5P_FIMV_ENC_ALPHA_OFF 0x084c /* loop filter alpha offset */ +#define S5P_FIMV_ENC_BETA_OFF 0x0850 /* loop filter beta offset */ +#define S5P_FIMV_MR_BUSIF_CTRL 0x0854 /* hidden, bus interface ctrl */ +#define S5P_FIMV_ENC_PXL_CACHE_CTRL 0x0a00 /* pixel cache control */ + +/* Channel & stream interface register */ +#define S5P_FIMV_SI_RTN_CHID 0x2000 /* Return CH inst ID register */ +#define S5P_FIMV_SI_CH0_INST_ID 0x2040 /* codec instance ID */ +#define S5P_FIMV_SI_CH1_INST_ID 0x2080 /* codec instance ID */ +/* Decoder */ +#define S5P_FIMV_SI_VRESOL 0x2004 /* vertical res of decoder */ +#define S5P_FIMV_SI_HRESOL 0x2008 /* horizontal res of decoder */ +#define S5P_FIMV_SI_BUF_NUMBER 0x200c /* number of frames in the + decoded pic */ +#define S5P_FIMV_SI_DISPLAY_Y_ADR 0x2010 /* luma addr of displayed pic */ +#define S5P_FIMV_SI_DISPLAY_C_ADR 0x2014 /* chroma addrof displayed pic */ + +#define S5P_FIMV_SI_CONSUMED_BYTES 0x2018 /* Consumed number of bytes to + decode a frame */ +#define S5P_FIMV_SI_DISPLAY_STATUS 0x201c /* status of decoded picture */ + +#define S5P_FIMV_SI_DECODE_Y_ADR 0x2024 /* luma addr of decoded pic */ +#define S5P_FIMV_SI_DECODE_C_ADR 0x2028 /* chroma addrof decoded pic */ +#define S5P_FIMV_SI_DECODE_STATUS 0x202c /* status of decoded picture */ + +#define S5P_FIMV_SI_CH0_SB_ST_ADR 0x2044 /* start addr of stream buf */ +#define S5P_FIMV_SI_CH0_SB_FRM_SIZE 0x2048 /* size of stream buf */ +#define S5P_FIMV_SI_CH0_DESC_ADR 0x204c /* addr of descriptor buf */ +#define S5P_FIMV_SI_CH0_CPB_SIZE 0x2058 /* max size of coded pic. buf */ +#define S5P_FIMV_SI_CH0_DESC_SIZE 0x205c /* max size of descriptor buf */ + +#define S5P_FIMV_SI_CH1_SB_ST_ADR 0x2084 /* start addr of stream buf */ +#define S5P_FIMV_SI_CH1_SB_FRM_SIZE 0x2088 /* size of stream buf */ +#define S5P_FIMV_SI_CH1_DESC_ADR 0x208c /* addr of descriptor buf */ +#define S5P_FIMV_SI_CH1_CPB_SIZE 0x2098 /* max size of coded pic. buf */ +#define S5P_FIMV_SI_CH1_DESC_SIZE 0x209c /* max size of descriptor buf */ + +#define S5P_FIMV_CRC_LUMA0 0x2030 /* luma crc data per frame + (top field) */ +#define S5P_FIMV_CRC_CHROMA0 0x2034 /* chroma crc data per frame + (top field) */ +#define S5P_FIMV_CRC_LUMA1 0x2038 /* luma crc data per bottom + field */ +#define S5P_FIMV_CRC_CHROMA1 0x203c /* chroma crc data per bottom + field */ + +/* Display status */ +#define S5P_FIMV_DEC_STATUS_DECODING_ONLY 0 +#define S5P_FIMV_DEC_STATUS_DECODING_DISPLAY 1 +#define S5P_FIMV_DEC_STATUS_DISPLAY_ONLY 2 +#define S5P_FIMV_DEC_STATUS_DECODING_EMPTY 3 +#define S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK 7 +#define S5P_FIMV_DEC_STATUS_PROGRESSIVE (0<<3) +#define S5P_FIMV_DEC_STATUS_INTERLACE (1<<3) +#define S5P_FIMV_DEC_STATUS_INTERLACE_MASK (1<<3) +#define S5P_FIMV_DEC_STATUS_CRC_NUMBER_TWO (0<<4) +#define S5P_FIMV_DEC_STATUS_CRC_NUMBER_FOUR (1<<4) +#define S5P_FIMV_DEC_STATUS_CRC_NUMBER_MASK (1<<4) +#define S5P_FIMV_DEC_STATUS_CRC_GENERATED (1<<5) +#define S5P_FIMV_DEC_STATUS_CRC_NOT_GENERATED (0<<5) +#define S5P_FIMV_DEC_STATUS_CRC_MASK (1<<5) + +#define S5P_FIMV_DEC_STATUS_RESOLUTION_MASK (3<<4) +#define S5P_FIMV_DEC_STATUS_RESOLUTION_INC (1<<4) +#define S5P_FIMV_DEC_STATUS_RESOLUTION_DEC (2<<4) +#define S5P_FIMV_DEC_STATUS_RESOLUTION_SHIFT 4 + +/* Decode frame address */ +#define S5P_FIMV_DECODE_Y_ADR 0x2024 +#define S5P_FIMV_DECODE_C_ADR 0x2028 + +/* Decoded frame tpe */ +#define S5P_FIMV_DECODE_FRAME_TYPE 0x2020 +#define S5P_FIMV_DECODE_FRAME_MASK 7 + +#define S5P_FIMV_DECODE_FRAME_SKIPPED 0 +#define S5P_FIMV_DECODE_FRAME_I_FRAME 1 +#define S5P_FIMV_DECODE_FRAME_P_FRAME 2 +#define S5P_FIMV_DECODE_FRAME_B_FRAME 3 +#define S5P_FIMV_DECODE_FRAME_OTHER_FRAME 4 + +/* Sizes of buffers required for decoding */ +#define S5P_FIMV_DEC_NB_IP_SIZE (32 * 1024) +#define S5P_FIMV_DEC_VERT_NB_MV_SIZE (16 * 1024) +#define S5P_FIMV_DEC_NB_DCAC_SIZE (16 * 1024) +#define S5P_FIMV_DEC_UPNB_MV_SIZE (68 * 1024) +#define S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE (136 * 1024) +#define S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE (32 * 1024) +#define S5P_FIMV_DEC_VC1_BITPLANE_SIZE (2 * 1024) +#define S5P_FIMV_DEC_STX_PARSER_SIZE (68 * 1024) + +#define S5P_FIMV_DEC_BUF_ALIGN (8 * 1024) +#define S5P_FIMV_ENC_BUF_ALIGN (8 * 1024) +#define S5P_FIMV_NV12M_HALIGN 16 +#define S5P_FIMV_NV12M_LVALIGN 16 +#define S5P_FIMV_NV12M_CVALIGN 8 +#define S5P_FIMV_NV12MT_HALIGN 128 +#define S5P_FIMV_NV12MT_VALIGN 32 +#define S5P_FIMV_NV12M_SALIGN 2048 +#define S5P_FIMV_NV12MT_SALIGN 8192 + +/* Sizes of buffers required for encoding */ +#define S5P_FIMV_ENC_UPMV_SIZE 0x10000 +#define S5P_FIMV_ENC_COLFLG_SIZE 0x10000 +#define S5P_FIMV_ENC_INTRAMD_SIZE 0x10000 +#define S5P_FIMV_ENC_INTRAPRED_SIZE 0x4000 +#define S5P_FIMV_ENC_NBORINFO_SIZE 0x10000 +#define S5P_FIMV_ENC_ACDCCOEF_SIZE 0x10000 + +/* Encoder */ +#define S5P_FIMV_ENC_SI_STRM_SIZE 0x2004 /* stream size */ +#define S5P_FIMV_ENC_SI_PIC_CNT 0x2008 /* picture count */ +#define S5P_FIMV_ENC_SI_WRITE_PTR 0x200c /* write pointer */ +#define S5P_FIMV_ENC_SI_SLICE_TYPE 0x2010 /* slice type(I/P/B/IDR) */ +#define S5P_FIMV_ENC_SI_SLICE_TYPE_NON_CODED 0 +#define S5P_FIMV_ENC_SI_SLICE_TYPE_I 1 +#define S5P_FIMV_ENC_SI_SLICE_TYPE_P 2 +#define S5P_FIMV_ENC_SI_SLICE_TYPE_B 3 +#define S5P_FIMV_ENC_SI_SLICE_TYPE_SKIPPED 4 +#define S5P_FIMV_ENC_SI_SLICE_TYPE_OTHERS 5 +#define S5P_FIMV_ENCODED_Y_ADDR 0x2014 /* the addr of the encoded + luma pic */ +#define S5P_FIMV_ENCODED_C_ADDR 0x2018 /* the addr of the encoded + chroma pic */ + +#define S5P_FIMV_ENC_SI_CH0_SB_ADR 0x2044 /* addr of stream buf */ +#define S5P_FIMV_ENC_SI_CH0_SB_SIZE 0x204c /* size of stream buf */ +#define S5P_FIMV_ENC_SI_CH0_CUR_Y_ADR 0x2050 /* current Luma addr */ +#define S5P_FIMV_ENC_SI_CH0_CUR_C_ADR 0x2054 /* current Chroma addr */ +#define S5P_FIMV_ENC_SI_CH0_FRAME_INS 0x2058 /* frame insertion */ + +#define S5P_FIMV_ENC_SI_CH1_SB_ADR 0x2084 /* addr of stream buf */ +#define S5P_FIMV_ENC_SI_CH1_SB_SIZE 0x208c /* size of stream buf */ +#define S5P_FIMV_ENC_SI_CH1_CUR_Y_ADR 0x2090 /* current Luma addr */ +#define S5P_FIMV_ENC_SI_CH1_CUR_C_ADR 0x2094 /* current Chroma addr */ +#define S5P_FIMV_ENC_SI_CH1_FRAME_INS 0x2098 /* frame insertion */ + +#define S5P_FIMV_ENC_PIC_TYPE_CTRL 0xc504 /* pic type level control */ +#define S5P_FIMV_ENC_B_RECON_WRITE_ON 0xc508 /* B frame recon write ctrl */ +#define S5P_FIMV_ENC_MSLICE_CTRL 0xc50c /* multi slice control */ +#define S5P_FIMV_ENC_MSLICE_MB 0xc510 /* MB number in the one slice */ +#define S5P_FIMV_ENC_MSLICE_BIT 0xc514 /* bit count for one slice */ +#define S5P_FIMV_ENC_CIR_CTRL 0xc518 /* number of intra refresh MB */ +#define S5P_FIMV_ENC_MAP_FOR_CUR 0xc51c /* linear or tiled mode */ +#define S5P_FIMV_ENC_PADDING_CTRL 0xc520 /* padding control */ + +#define S5P_FIMV_ENC_RC_CONFIG 0xc5a0 /* RC config */ +#define S5P_FIMV_ENC_RC_BIT_RATE 0xc5a8 /* bit rate */ +#define S5P_FIMV_ENC_RC_QBOUND 0xc5ac /* max/min QP */ +#define S5P_FIMV_ENC_RC_RPARA 0xc5b0 /* rate control reaction coeff */ +#define S5P_FIMV_ENC_RC_MB_CTRL 0xc5b4 /* MB adaptive scaling */ + +/* Encoder for H264 only */ +#define S5P_FIMV_ENC_H264_ENTROPY_MODE 0xd004 /* CAVLC or CABAC */ +#define S5P_FIMV_ENC_H264_ALPHA_OFF 0xd008 /* loop filter alpha offset */ +#define S5P_FIMV_ENC_H264_BETA_OFF 0xd00c /* loop filter beta offset */ +#define S5P_FIMV_ENC_H264_NUM_OF_REF 0xd010 /* number of reference for P/B */ +#define S5P_FIMV_ENC_H264_TRANS_FLAG 0xd034 /* 8x8 transform flag in PPS & + high profile */ + +#define S5P_FIMV_ENC_RC_FRAME_RATE 0xd0d0 /* frame rate */ + +/* Encoder for MPEG4 only */ +#define S5P_FIMV_ENC_MPEG4_QUART_PXL 0xe008 /* qpel interpolation ctrl */ + +/* Additional */ +#define S5P_FIMV_SI_CH0_DPB_CONF_CTRL 0x2068 /* DPB Config Control Register */ +#define S5P_FIMV_SLICE_INT_MASK 1 +#define S5P_FIMV_SLICE_INT_SHIFT 31 +#define S5P_FIMV_DDELAY_ENA_SHIFT 30 +#define S5P_FIMV_DDELAY_VAL_MASK 0xff +#define S5P_FIMV_DDELAY_VAL_SHIFT 16 +#define S5P_FIMV_DPB_COUNT_MASK 0xffff +#define S5P_FIMV_DPB_FLUSH_MASK 1 +#define S5P_FIMV_DPB_FLUSH_SHIFT 14 + + +#define S5P_FIMV_SI_CH0_RELEASE_BUF 0x2060 /* DPB release buffer register */ +#define S5P_FIMV_SI_CH0_HOST_WR_ADR 0x2064 /* address of shared memory */ + +/* Codec numbers */ +#define S5P_FIMV_CODEC_NONE -1 + +#define S5P_FIMV_CODEC_H264_DEC 0 +#define S5P_FIMV_CODEC_VC1_DEC 1 +#define S5P_FIMV_CODEC_MPEG4_DEC 2 +#define S5P_FIMV_CODEC_MPEG2_DEC 3 +#define S5P_FIMV_CODEC_H263_DEC 4 +#define S5P_FIMV_CODEC_VC1RCV_DEC 5 + +#define S5P_FIMV_CODEC_H264_ENC 16 +#define S5P_FIMV_CODEC_MPEG4_ENC 17 +#define S5P_FIMV_CODEC_H263_ENC 18 + +/* Channel Control Register */ +#define S5P_FIMV_CH_SEQ_HEADER 1 +#define S5P_FIMV_CH_FRAME_START 2 +#define S5P_FIMV_CH_LAST_FRAME 3 +#define S5P_FIMV_CH_INIT_BUFS 4 +#define S5P_FIMV_CH_FRAME_START_REALLOC 5 +#define S5P_FIMV_CH_MASK 7 +#define S5P_FIMV_CH_SHIFT 16 + + +/* Host to RISC command */ +#define S5P_FIMV_H2R_CMD_EMPTY 0 +#define S5P_FIMV_H2R_CMD_OPEN_INSTANCE 1 +#define S5P_FIMV_H2R_CMD_CLOSE_INSTANCE 2 +#define S5P_FIMV_H2R_CMD_SYS_INIT 3 +#define S5P_FIMV_H2R_CMD_FLUSH 4 +#define S5P_FIMV_H2R_CMD_SLEEP 5 +#define S5P_FIMV_H2R_CMD_WAKEUP 6 + +#define S5P_FIMV_R2H_CMD_EMPTY 0 +#define S5P_FIMV_R2H_CMD_OPEN_INSTANCE_RET 1 +#define S5P_FIMV_R2H_CMD_CLOSE_INSTANCE_RET 2 +#define S5P_FIMV_R2H_CMD_RSV_RET 3 +#define S5P_FIMV_R2H_CMD_SEQ_DONE_RET 4 +#define S5P_FIMV_R2H_CMD_FRAME_DONE_RET 5 +#define S5P_FIMV_R2H_CMD_SLICE_DONE_RET 6 +#define S5P_FIMV_R2H_CMD_ENC_COMPLETE_RET 7 +#define S5P_FIMV_R2H_CMD_SYS_INIT_RET 8 +#define S5P_FIMV_R2H_CMD_FW_STATUS_RET 9 +#define S5P_FIMV_R2H_CMD_SLEEP_RET 10 +#define S5P_FIMV_R2H_CMD_WAKEUP_RET 11 +#define S5P_FIMV_R2H_CMD_FLUSH_RET 12 +#define S5P_FIMV_R2H_CMD_INIT_BUFFERS_RET 15 +#define S5P_FIMV_R2H_CMD_EDFU_INIT_RET 16 +#define S5P_FIMV_R2H_CMD_ERR_RET 32 + +/* Dummy definition for MFCv6 compatibility */ +#define S5P_FIMV_CODEC_H264_MVC_DEC -1 +#define S5P_FIMV_R2H_CMD_FIELD_DONE_RET -1 +#define S5P_FIMV_MFC_RESET -1 +#define S5P_FIMV_RISC_ON -1 +#define S5P_FIMV_RISC_BASE_ADDRESS -1 +#define S5P_FIMV_CODEC_VP8_DEC -1 +#define S5P_FIMV_REG_CLEAR_BEGIN 0 +#define S5P_FIMV_REG_CLEAR_COUNT 0 + +/* Error handling defines */ +#define S5P_FIMV_ERR_NO_VALID_SEQ_HDR 67 +#define S5P_FIMV_ERR_INCOMPLETE_FRAME 124 +#define S5P_FIMV_ERR_TIMEOUT 140 +#define S5P_FIMV_ERR_WARNINGS_START 145 +#define S5P_FIMV_ERR_DEC_MASK 0xFFFF +#define S5P_FIMV_ERR_DEC_SHIFT 0 +#define S5P_FIMV_ERR_DSPL_MASK 0xFFFF0000 +#define S5P_FIMV_ERR_DSPL_SHIFT 16 + +/* Shared memory registers' offsets */ + +/* An offset of the start position in the stream when + * the start position is not aligned */ +#define S5P_FIMV_SHARED_CROP_INFO_H 0x0020 +#define S5P_FIMV_SHARED_CROP_LEFT_MASK 0xFFFF +#define S5P_FIMV_SHARED_CROP_LEFT_SHIFT 0 +#define S5P_FIMV_SHARED_CROP_RIGHT_MASK 0xFFFF0000 +#define S5P_FIMV_SHARED_CROP_RIGHT_SHIFT 16 +#define S5P_FIMV_SHARED_CROP_INFO_V 0x0024 +#define S5P_FIMV_SHARED_CROP_TOP_MASK 0xFFFF +#define S5P_FIMV_SHARED_CROP_TOP_SHIFT 0 +#define S5P_FIMV_SHARED_CROP_BOTTOM_MASK 0xFFFF0000 +#define S5P_FIMV_SHARED_CROP_BOTTOM_SHIFT 16 +#define S5P_FIMV_SHARED_SET_FRAME_TAG 0x0004 +#define S5P_FIMV_SHARED_GET_FRAME_TAG_TOP 0x0008 +#define S5P_FIMV_SHARED_GET_FRAME_TAG_BOT 0x000C +#define S5P_FIMV_SHARED_START_BYTE_NUM 0x0018 +#define S5P_FIMV_SHARED_RC_VOP_TIMING 0x0030 +#define S5P_FIMV_SHARED_LUMA_DPB_SIZE 0x0064 +#define S5P_FIMV_SHARED_CHROMA_DPB_SIZE 0x0068 +#define S5P_FIMV_SHARED_MV_SIZE 0x006C +#define S5P_FIMV_SHARED_PIC_TIME_TOP 0x0010 +#define S5P_FIMV_SHARED_PIC_TIME_BOTTOM 0x0014 +#define S5P_FIMV_SHARED_EXT_ENC_CONTROL 0x0028 +#define S5P_FIMV_SHARED_P_B_FRAME_QP 0x0070 +#define S5P_FIMV_SHARED_ASPECT_RATIO_IDC 0x0074 +#define S5P_FIMV_SHARED_EXTENDED_SAR 0x0078 +#define S5P_FIMV_SHARED_H264_I_PERIOD 0x009C +#define S5P_FIMV_SHARED_RC_CONTROL_CONFIG 0x00A0 +#define S5P_FIMV_SHARED_DISP_FRAME_TYPE_SHIFT 2 + +/* Offset used by the hardware to store addresses */ +#define MFC_OFFSET_SHIFT 11 + +#define FIRMWARE_ALIGN (128 * SZ_1K) /* 128KB */ +#define MFC_H264_CTX_BUF_SIZE (600 * SZ_1K) /* 600KB per H264 instance */ +#define MFC_CTX_BUF_SIZE (10 * SZ_1K) /* 10KB per instance */ +#define DESC_BUF_SIZE (128 * SZ_1K) /* 128KB for DESC buffer */ +#define SHARED_BUF_SIZE (8 * SZ_1K) /* 8KB for shared buffer */ + +#define DEF_CPB_SIZE (256 * SZ_1K) /* 256KB */ +#define MAX_CPB_SIZE (4 * SZ_1M) /* 4MB */ +#define MAX_FW_SIZE (384 * SZ_1K) + +#define MFC_VERSION 0x51 +#define MFC_NUM_PORTS 2 + +#define S5P_FIMV_SHARED_FRAME_PACK_SEI_AVAIL 0x16C +#define S5P_FIMV_SHARED_FRAME_PACK_ARRGMENT_ID 0x170 +#define S5P_FIMV_SHARED_FRAME_PACK_SEI_INFO 0x174 +#define S5P_FIMV_SHARED_FRAME_PACK_GRID_POS 0x178 + +/* Values for resolution change in display status */ +#define S5P_FIMV_RES_INCREASE 1 +#define S5P_FIMV_RES_DECREASE 2 + +#endif /* _REGS_FIMV_H */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c new file mode 100644 index 000000000..007c7dbee --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc.c @@ -0,0 +1,1692 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Samsung S5P Multi Format Codec v 5.1 + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "s5p_mfc_common.h" +#include "s5p_mfc_ctrl.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_dec.h" +#include "s5p_mfc_enc.h" +#include "s5p_mfc_intr.h" +#include "s5p_mfc_iommu.h" +#include "s5p_mfc_opr.h" +#include "s5p_mfc_cmd.h" +#include "s5p_mfc_pm.h" + +#define S5P_MFC_DEC_NAME "s5p-mfc-dec" +#define S5P_MFC_ENC_NAME "s5p-mfc-enc" + +int mfc_debug_level; +module_param_named(debug, mfc_debug_level, int, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug level - higher value produces more verbose messages"); + +static char *mfc_mem_size; +module_param_named(mem, mfc_mem_size, charp, 0644); +MODULE_PARM_DESC(mem, "Preallocated memory size for the firmware and context buffers"); + +/* Helper functions for interrupt processing */ + +/* Remove from hw execution round robin */ +void clear_work_bit(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + spin_lock(&dev->condlock); + __clear_bit(ctx->num, &dev->ctx_work_bits); + spin_unlock(&dev->condlock); +} + +/* Add to hw execution round robin */ +void set_work_bit(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + spin_lock(&dev->condlock); + __set_bit(ctx->num, &dev->ctx_work_bits); + spin_unlock(&dev->condlock); +} + +/* Remove from hw execution round robin */ +void clear_work_bit_irqsave(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->condlock, flags); + __clear_bit(ctx->num, &dev->ctx_work_bits); + spin_unlock_irqrestore(&dev->condlock, flags); +} + +/* Add to hw execution round robin */ +void set_work_bit_irqsave(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->condlock, flags); + __set_bit(ctx->num, &dev->ctx_work_bits); + spin_unlock_irqrestore(&dev->condlock, flags); +} + +int s5p_mfc_get_new_ctx(struct s5p_mfc_dev *dev) +{ + unsigned long flags; + int ctx; + + spin_lock_irqsave(&dev->condlock, flags); + ctx = dev->curr_ctx; + do { + ctx = (ctx + 1) % MFC_NUM_CONTEXTS; + if (ctx == dev->curr_ctx) { + if (!test_bit(ctx, &dev->ctx_work_bits)) + ctx = -EAGAIN; + break; + } + } while (!test_bit(ctx, &dev->ctx_work_bits)); + spin_unlock_irqrestore(&dev->condlock, flags); + + return ctx; +} + +/* Wake up context wait_queue */ +static void wake_up_ctx(struct s5p_mfc_ctx *ctx, unsigned int reason, + unsigned int err) +{ + ctx->int_cond = 1; + ctx->int_type = reason; + ctx->int_err = err; + wake_up(&ctx->queue); +} + +/* Wake up device wait_queue */ +static void wake_up_dev(struct s5p_mfc_dev *dev, unsigned int reason, + unsigned int err) +{ + dev->int_cond = 1; + dev->int_type = reason; + dev->int_err = err; + wake_up(&dev->queue); +} + +void s5p_mfc_cleanup_queue(struct list_head *lh, struct vb2_queue *vq) +{ + struct s5p_mfc_buf *b; + int i; + + while (!list_empty(lh)) { + b = list_entry(lh->next, struct s5p_mfc_buf, list); + for (i = 0; i < b->b->vb2_buf.num_planes; i++) + vb2_set_plane_payload(&b->b->vb2_buf, i, 0); + vb2_buffer_done(&b->b->vb2_buf, VB2_BUF_STATE_ERROR); + list_del(&b->list); + } +} + +static void s5p_mfc_watchdog(struct timer_list *t) +{ + struct s5p_mfc_dev *dev = from_timer(dev, t, watchdog_timer); + + if (test_bit(0, &dev->hw_lock)) + atomic_inc(&dev->watchdog_cnt); + if (atomic_read(&dev->watchdog_cnt) >= MFC_WATCHDOG_CNT) { + /* This means that hw is busy and no interrupts were + * generated by hw for the Nth time of running this + * watchdog timer. This usually means a serious hw + * error. Now it is time to kill all instances and + * reset the MFC. */ + mfc_err("Time out during waiting for HW\n"); + schedule_work(&dev->watchdog_work); + } + dev->watchdog_timer.expires = jiffies + + msecs_to_jiffies(MFC_WATCHDOG_INTERVAL); + add_timer(&dev->watchdog_timer); +} + +static void s5p_mfc_watchdog_worker(struct work_struct *work) +{ + struct s5p_mfc_dev *dev; + struct s5p_mfc_ctx *ctx; + unsigned long flags; + int mutex_locked; + int i, ret; + + dev = container_of(work, struct s5p_mfc_dev, watchdog_work); + + mfc_err("Driver timeout error handling\n"); + /* Lock the mutex that protects open and release. + * This is necessary as they may load and unload firmware. */ + mutex_locked = mutex_trylock(&dev->mfc_mutex); + if (!mutex_locked) + mfc_err("Error: some instance may be closing/opening\n"); + spin_lock_irqsave(&dev->irqlock, flags); + + s5p_mfc_clock_off(); + + for (i = 0; i < MFC_NUM_CONTEXTS; i++) { + ctx = dev->ctx[i]; + if (!ctx) + continue; + ctx->state = MFCINST_ERROR; + s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst); + s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src); + clear_work_bit(ctx); + wake_up_ctx(ctx, S5P_MFC_R2H_CMD_ERR_RET, 0); + } + clear_bit(0, &dev->hw_lock); + spin_unlock_irqrestore(&dev->irqlock, flags); + + /* De-init MFC */ + s5p_mfc_deinit_hw(dev); + + /* Double check if there is at least one instance running. + * If no instance is in memory than no firmware should be present */ + if (dev->num_inst > 0) { + ret = s5p_mfc_load_firmware(dev); + if (ret) { + mfc_err("Failed to reload FW\n"); + goto unlock; + } + s5p_mfc_clock_on(); + ret = s5p_mfc_init_hw(dev); + s5p_mfc_clock_off(); + if (ret) + mfc_err("Failed to reinit FW\n"); + } +unlock: + if (mutex_locked) + mutex_unlock(&dev->mfc_mutex); +} + +static void s5p_mfc_handle_frame_all_extracted(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_buf *dst_buf; + struct s5p_mfc_dev *dev = ctx->dev; + + ctx->state = MFCINST_FINISHED; + ctx->sequence++; + while (!list_empty(&ctx->dst_queue)) { + dst_buf = list_entry(ctx->dst_queue.next, + struct s5p_mfc_buf, list); + mfc_debug(2, "Cleaning up buffer: %d\n", + dst_buf->b->vb2_buf.index); + vb2_set_plane_payload(&dst_buf->b->vb2_buf, 0, 0); + vb2_set_plane_payload(&dst_buf->b->vb2_buf, 1, 0); + list_del(&dst_buf->list); + dst_buf->flags |= MFC_BUF_FLAG_EOS; + ctx->dst_queue_cnt--; + dst_buf->b->sequence = (ctx->sequence++); + + if (s5p_mfc_hw_call(dev->mfc_ops, get_pic_type_top, ctx) == + s5p_mfc_hw_call(dev->mfc_ops, get_pic_type_bot, ctx)) + dst_buf->b->field = V4L2_FIELD_NONE; + else + dst_buf->b->field = V4L2_FIELD_INTERLACED; + dst_buf->b->flags |= V4L2_BUF_FLAG_LAST; + + ctx->dec_dst_flag &= ~(1 << dst_buf->b->vb2_buf.index); + vb2_buffer_done(&dst_buf->b->vb2_buf, VB2_BUF_STATE_DONE); + } +} + +static void s5p_mfc_handle_frame_copy_time(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *dst_buf, *src_buf; + u32 dec_y_addr; + unsigned int frame_type; + + /* Make sure we actually have a new frame before continuing. */ + frame_type = s5p_mfc_hw_call(dev->mfc_ops, get_dec_frame_type, dev); + if (frame_type == S5P_FIMV_DECODE_FRAME_SKIPPED) + return; + dec_y_addr = (u32)s5p_mfc_hw_call(dev->mfc_ops, get_dec_y_adr, dev); + + /* Copy timestamp / timecode from decoded src to dst and set + appropriate flags. */ + src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); + list_for_each_entry(dst_buf, &ctx->dst_queue, list) { + u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0); + + if (addr == dec_y_addr) { + dst_buf->b->timecode = src_buf->b->timecode; + dst_buf->b->vb2_buf.timestamp = + src_buf->b->vb2_buf.timestamp; + dst_buf->b->flags &= + ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_buf->b->flags |= + src_buf->b->flags + & V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + switch (frame_type) { + case S5P_FIMV_DECODE_FRAME_I_FRAME: + dst_buf->b->flags |= + V4L2_BUF_FLAG_KEYFRAME; + break; + case S5P_FIMV_DECODE_FRAME_P_FRAME: + dst_buf->b->flags |= + V4L2_BUF_FLAG_PFRAME; + break; + case S5P_FIMV_DECODE_FRAME_B_FRAME: + dst_buf->b->flags |= + V4L2_BUF_FLAG_BFRAME; + break; + default: + /* Don't know how to handle + S5P_FIMV_DECODE_FRAME_OTHER_FRAME. */ + mfc_debug(2, "Unexpected frame type: %d\n", + frame_type); + } + break; + } + } +} + +static void s5p_mfc_handle_frame_new(struct s5p_mfc_ctx *ctx, unsigned int err) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *dst_buf; + u32 dspl_y_addr; + unsigned int frame_type; + + dspl_y_addr = (u32)s5p_mfc_hw_call(dev->mfc_ops, get_dspl_y_adr, dev); + if (IS_MFCV6_PLUS(dev)) + frame_type = s5p_mfc_hw_call(dev->mfc_ops, + get_disp_frame_type, ctx); + else + frame_type = s5p_mfc_hw_call(dev->mfc_ops, + get_dec_frame_type, dev); + + /* If frame is same as previous then skip and do not dequeue */ + if (frame_type == S5P_FIMV_DECODE_FRAME_SKIPPED) { + if (!ctx->after_packed_pb) + ctx->sequence++; + ctx->after_packed_pb = 0; + return; + } + ctx->sequence++; + /* The MFC returns address of the buffer, now we have to + * check which vb2_buffer does it correspond to */ + list_for_each_entry(dst_buf, &ctx->dst_queue, list) { + u32 addr = (u32)vb2_dma_contig_plane_dma_addr(&dst_buf->b->vb2_buf, 0); + + /* Check if this is the buffer we're looking for */ + if (addr == dspl_y_addr) { + list_del(&dst_buf->list); + ctx->dst_queue_cnt--; + dst_buf->b->sequence = ctx->sequence; + if (s5p_mfc_hw_call(dev->mfc_ops, + get_pic_type_top, ctx) == + s5p_mfc_hw_call(dev->mfc_ops, + get_pic_type_bot, ctx)) + dst_buf->b->field = V4L2_FIELD_NONE; + else + dst_buf->b->field = + V4L2_FIELD_INTERLACED; + vb2_set_plane_payload(&dst_buf->b->vb2_buf, 0, + ctx->luma_size); + vb2_set_plane_payload(&dst_buf->b->vb2_buf, 1, + ctx->chroma_size); + clear_bit(dst_buf->b->vb2_buf.index, + &ctx->dec_dst_flag); + + vb2_buffer_done(&dst_buf->b->vb2_buf, err ? + VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + + break; + } + } +} + +/* Handle frame decoding interrupt */ +static void s5p_mfc_handle_frame(struct s5p_mfc_ctx *ctx, + unsigned int reason, unsigned int err) +{ + struct s5p_mfc_dev *dev = ctx->dev; + unsigned int dst_frame_status; + unsigned int dec_frame_status; + struct s5p_mfc_buf *src_buf; + unsigned int res_change; + + dst_frame_status = s5p_mfc_hw_call(dev->mfc_ops, get_dspl_status, dev) + & S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK; + dec_frame_status = s5p_mfc_hw_call(dev->mfc_ops, get_dec_status, dev) + & S5P_FIMV_DEC_STATUS_DECODING_STATUS_MASK; + res_change = (s5p_mfc_hw_call(dev->mfc_ops, get_dspl_status, dev) + & S5P_FIMV_DEC_STATUS_RESOLUTION_MASK) + >> S5P_FIMV_DEC_STATUS_RESOLUTION_SHIFT; + mfc_debug(2, "Frame Status: %x\n", dst_frame_status); + if (ctx->state == MFCINST_RES_CHANGE_INIT) + ctx->state = MFCINST_RES_CHANGE_FLUSH; + if (res_change == S5P_FIMV_RES_INCREASE || + res_change == S5P_FIMV_RES_DECREASE) { + ctx->state = MFCINST_RES_CHANGE_INIT; + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + wake_up_ctx(ctx, reason, err); + WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0); + s5p_mfc_clock_off(); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + return; + } + if (ctx->dpb_flush_flag) + ctx->dpb_flush_flag = 0; + + /* All frames remaining in the buffer have been extracted */ + if (dst_frame_status == S5P_FIMV_DEC_STATUS_DECODING_EMPTY) { + if (ctx->state == MFCINST_RES_CHANGE_FLUSH) { + static const struct v4l2_event ev_src_ch = { + .type = V4L2_EVENT_SOURCE_CHANGE, + .u.src_change.changes = + V4L2_EVENT_SRC_CH_RESOLUTION, + }; + + s5p_mfc_handle_frame_all_extracted(ctx); + ctx->state = MFCINST_RES_CHANGE_END; + v4l2_event_queue_fh(&ctx->fh, &ev_src_ch); + + goto leave_handle_frame; + } else { + s5p_mfc_handle_frame_all_extracted(ctx); + } + } + + if (dec_frame_status == S5P_FIMV_DEC_STATUS_DECODING_DISPLAY) + s5p_mfc_handle_frame_copy_time(ctx); + + /* A frame has been decoded and is in the buffer */ + if (dst_frame_status == S5P_FIMV_DEC_STATUS_DISPLAY_ONLY || + dst_frame_status == S5P_FIMV_DEC_STATUS_DECODING_DISPLAY) { + s5p_mfc_handle_frame_new(ctx, err); + } else { + mfc_debug(2, "No frame decode\n"); + } + /* Mark source buffer as complete */ + if (dst_frame_status != S5P_FIMV_DEC_STATUS_DISPLAY_ONLY + && !list_empty(&ctx->src_queue)) { + src_buf = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, + list); + ctx->consumed_stream += s5p_mfc_hw_call(dev->mfc_ops, + get_consumed_stream, dev); + if (ctx->codec_mode != S5P_MFC_CODEC_H264_DEC && + ctx->codec_mode != S5P_MFC_CODEC_VP8_DEC && + ctx->consumed_stream + STUFF_BYTE < + src_buf->b->vb2_buf.planes[0].bytesused) { + /* Run MFC again on the same buffer */ + mfc_debug(2, "Running again the same buffer\n"); + ctx->after_packed_pb = 1; + } else { + mfc_debug(2, "MFC needs next buffer\n"); + ctx->consumed_stream = 0; + if (src_buf->flags & MFC_BUF_FLAG_EOS) + ctx->state = MFCINST_FINISHING; + list_del(&src_buf->list); + ctx->src_queue_cnt--; + if (s5p_mfc_hw_call(dev->mfc_ops, err_dec, err) > 0) + vb2_buffer_done(&src_buf->b->vb2_buf, + VB2_BUF_STATE_ERROR); + else + vb2_buffer_done(&src_buf->b->vb2_buf, + VB2_BUF_STATE_DONE); + } + } +leave_handle_frame: + if ((ctx->src_queue_cnt == 0 && ctx->state != MFCINST_FINISHING) + || ctx->dst_queue_cnt < ctx->pb_count) + clear_work_bit(ctx); + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + wake_up_ctx(ctx, reason, err); + WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0); + s5p_mfc_clock_off(); + /* if suspending, wake up device and do not try_run again*/ + if (test_bit(0, &dev->enter_suspend)) + wake_up_dev(dev, reason, err); + else + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); +} + +/* Error handling for interrupt */ +static void s5p_mfc_handle_error(struct s5p_mfc_dev *dev, + struct s5p_mfc_ctx *ctx, unsigned int reason, unsigned int err) +{ + mfc_err("Interrupt Error: %08x\n", err); + + if (ctx) { + /* Error recovery is dependent on the state of context */ + switch (ctx->state) { + case MFCINST_RES_CHANGE_INIT: + case MFCINST_RES_CHANGE_FLUSH: + case MFCINST_RES_CHANGE_END: + case MFCINST_FINISHING: + case MFCINST_FINISHED: + case MFCINST_RUNNING: + /* It is highly probable that an error occurred + * while decoding a frame */ + clear_work_bit(ctx); + ctx->state = MFCINST_ERROR; + /* Mark all dst buffers as having an error */ + s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst); + /* Mark all src buffers as having an error */ + s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src); + wake_up_ctx(ctx, reason, err); + break; + default: + clear_work_bit(ctx); + ctx->state = MFCINST_ERROR; + wake_up_ctx(ctx, reason, err); + break; + } + } + WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0); + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + s5p_mfc_clock_off(); + wake_up_dev(dev, reason, err); +} + +/* Header parsing interrupt handling */ +static void s5p_mfc_handle_seq_done(struct s5p_mfc_ctx *ctx, + unsigned int reason, unsigned int err) +{ + struct s5p_mfc_dev *dev; + + if (!ctx) + return; + dev = ctx->dev; + if (ctx->c_ops->post_seq_start) { + if (ctx->c_ops->post_seq_start(ctx)) + mfc_err("post_seq_start() failed\n"); + } else { + ctx->img_width = s5p_mfc_hw_call(dev->mfc_ops, get_img_width, + dev); + ctx->img_height = s5p_mfc_hw_call(dev->mfc_ops, get_img_height, + dev); + + s5p_mfc_hw_call(dev->mfc_ops, dec_calc_dpb_size, ctx); + + ctx->pb_count = s5p_mfc_hw_call(dev->mfc_ops, get_dpb_count, + dev); + ctx->mv_count = s5p_mfc_hw_call(dev->mfc_ops, get_mv_count, + dev); + if (FW_HAS_E_MIN_SCRATCH_BUF(dev)) + ctx->scratch_buf_size = s5p_mfc_hw_call(dev->mfc_ops, + get_min_scratch_buf_size, dev); + if (ctx->img_width == 0 || ctx->img_height == 0) + ctx->state = MFCINST_ERROR; + else + ctx->state = MFCINST_HEAD_PARSED; + + if ((ctx->codec_mode == S5P_MFC_CODEC_H264_DEC || + ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC) && + !list_empty(&ctx->src_queue)) { + struct s5p_mfc_buf *src_buf; + src_buf = list_entry(ctx->src_queue.next, + struct s5p_mfc_buf, list); + if (s5p_mfc_hw_call(dev->mfc_ops, get_consumed_stream, + dev) < + src_buf->b->vb2_buf.planes[0].bytesused) + ctx->head_processed = 0; + else + ctx->head_processed = 1; + } else { + ctx->head_processed = 1; + } + } + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + clear_work_bit(ctx); + WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0); + s5p_mfc_clock_off(); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + wake_up_ctx(ctx, reason, err); +} + +/* Header parsing interrupt handling */ +static void s5p_mfc_handle_init_buffers(struct s5p_mfc_ctx *ctx, + unsigned int reason, unsigned int err) +{ + struct s5p_mfc_buf *src_buf; + struct s5p_mfc_dev *dev; + + if (!ctx) + return; + dev = ctx->dev; + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + ctx->int_type = reason; + ctx->int_err = err; + ctx->int_cond = 1; + clear_work_bit(ctx); + if (err == 0) { + ctx->state = MFCINST_RUNNING; + if (!ctx->dpb_flush_flag && ctx->head_processed) { + if (!list_empty(&ctx->src_queue)) { + src_buf = list_entry(ctx->src_queue.next, + struct s5p_mfc_buf, list); + list_del(&src_buf->list); + ctx->src_queue_cnt--; + vb2_buffer_done(&src_buf->b->vb2_buf, + VB2_BUF_STATE_DONE); + } + } else { + ctx->dpb_flush_flag = 0; + } + WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0); + + s5p_mfc_clock_off(); + + wake_up(&ctx->queue); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + } else { + WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0); + + s5p_mfc_clock_off(); + + wake_up(&ctx->queue); + } +} + +static void s5p_mfc_handle_stream_complete(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *mb_entry; + + mfc_debug(2, "Stream completed\n"); + + ctx->state = MFCINST_FINISHED; + + if (!list_empty(&ctx->dst_queue)) { + mb_entry = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, + list); + list_del(&mb_entry->list); + ctx->dst_queue_cnt--; + vb2_set_plane_payload(&mb_entry->b->vb2_buf, 0, 0); + vb2_buffer_done(&mb_entry->b->vb2_buf, VB2_BUF_STATE_DONE); + } + + clear_work_bit(ctx); + + WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0); + + s5p_mfc_clock_off(); + wake_up(&ctx->queue); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); +} + +/* Interrupt processing */ +static irqreturn_t s5p_mfc_irq(int irq, void *priv) +{ + struct s5p_mfc_dev *dev = priv; + struct s5p_mfc_ctx *ctx; + unsigned int reason; + unsigned int err; + + mfc_debug_enter(); + /* Reset the timeout watchdog */ + atomic_set(&dev->watchdog_cnt, 0); + spin_lock(&dev->irqlock); + ctx = dev->ctx[dev->curr_ctx]; + /* Get the reason of interrupt and the error code */ + reason = s5p_mfc_hw_call(dev->mfc_ops, get_int_reason, dev); + err = s5p_mfc_hw_call(dev->mfc_ops, get_int_err, dev); + mfc_debug(1, "Int reason: %d (err: %08x)\n", reason, err); + switch (reason) { + case S5P_MFC_R2H_CMD_ERR_RET: + /* An error has occurred */ + if (ctx->state == MFCINST_RUNNING && + (s5p_mfc_hw_call(dev->mfc_ops, err_dec, err) >= + dev->warn_start || + err == S5P_FIMV_ERR_NO_VALID_SEQ_HDR || + err == S5P_FIMV_ERR_INCOMPLETE_FRAME || + err == S5P_FIMV_ERR_TIMEOUT)) + s5p_mfc_handle_frame(ctx, reason, err); + else + s5p_mfc_handle_error(dev, ctx, reason, err); + clear_bit(0, &dev->enter_suspend); + break; + + case S5P_MFC_R2H_CMD_SLICE_DONE_RET: + case S5P_MFC_R2H_CMD_FIELD_DONE_RET: + case S5P_MFC_R2H_CMD_FRAME_DONE_RET: + if (ctx->c_ops->post_frame_start) { + if (ctx->c_ops->post_frame_start(ctx)) + mfc_err("post_frame_start() failed\n"); + + if (ctx->state == MFCINST_FINISHING && + list_empty(&ctx->ref_queue)) { + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + s5p_mfc_handle_stream_complete(ctx); + break; + } + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + WARN_ON(test_and_clear_bit(0, &dev->hw_lock) == 0); + s5p_mfc_clock_off(); + wake_up_ctx(ctx, reason, err); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + } else { + s5p_mfc_handle_frame(ctx, reason, err); + } + break; + + case S5P_MFC_R2H_CMD_SEQ_DONE_RET: + s5p_mfc_handle_seq_done(ctx, reason, err); + break; + + case S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET: + ctx->inst_no = s5p_mfc_hw_call(dev->mfc_ops, get_inst_no, dev); + ctx->state = MFCINST_GOT_INST; + goto irq_cleanup_hw; + + case S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET: + ctx->inst_no = MFC_NO_INSTANCE_SET; + ctx->state = MFCINST_FREE; + goto irq_cleanup_hw; + + case S5P_MFC_R2H_CMD_SYS_INIT_RET: + case S5P_MFC_R2H_CMD_FW_STATUS_RET: + case S5P_MFC_R2H_CMD_SLEEP_RET: + case S5P_MFC_R2H_CMD_WAKEUP_RET: + if (ctx) + clear_work_bit(ctx); + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + clear_bit(0, &dev->hw_lock); + clear_bit(0, &dev->enter_suspend); + wake_up_dev(dev, reason, err); + break; + + case S5P_MFC_R2H_CMD_INIT_BUFFERS_RET: + s5p_mfc_handle_init_buffers(ctx, reason, err); + break; + + case S5P_MFC_R2H_CMD_COMPLETE_SEQ_RET: + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + ctx->int_type = reason; + ctx->int_err = err; + s5p_mfc_handle_stream_complete(ctx); + break; + + case S5P_MFC_R2H_CMD_DPB_FLUSH_RET: + ctx->state = MFCINST_RUNNING; + goto irq_cleanup_hw; + + default: + mfc_debug(2, "Unknown int reason\n"); + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + } + spin_unlock(&dev->irqlock); + mfc_debug_leave(); + return IRQ_HANDLED; +irq_cleanup_hw: + s5p_mfc_hw_call(dev->mfc_ops, clear_int_flags, dev); + ctx->int_type = reason; + ctx->int_err = err; + ctx->int_cond = 1; + if (test_and_clear_bit(0, &dev->hw_lock) == 0) + mfc_err("Failed to unlock hw\n"); + + s5p_mfc_clock_off(); + clear_work_bit(ctx); + wake_up(&ctx->queue); + + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + spin_unlock(&dev->irqlock); + mfc_debug(2, "Exit via irq_cleanup_hw\n"); + return IRQ_HANDLED; +} + +/* Open an MFC node */ +static int s5p_mfc_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct s5p_mfc_dev *dev = video_drvdata(file); + struct s5p_mfc_ctx *ctx = NULL; + struct vb2_queue *q; + int ret = 0; + + mfc_debug_enter(); + if (mutex_lock_interruptible(&dev->mfc_mutex)) + return -ERESTARTSYS; + dev->num_inst++; /* It is guarded by mfc_mutex in vfd */ + /* Allocate memory for context */ + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto err_alloc; + } + init_waitqueue_head(&ctx->queue); + v4l2_fh_init(&ctx->fh, vdev); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + ctx->dev = dev; + INIT_LIST_HEAD(&ctx->src_queue); + INIT_LIST_HEAD(&ctx->dst_queue); + ctx->src_queue_cnt = 0; + ctx->dst_queue_cnt = 0; + /* Get context number */ + ctx->num = 0; + while (dev->ctx[ctx->num]) { + ctx->num++; + if (ctx->num >= MFC_NUM_CONTEXTS) { + mfc_debug(2, "Too many open contexts\n"); + ret = -EBUSY; + goto err_no_ctx; + } + } + /* Mark context as idle */ + clear_work_bit_irqsave(ctx); + dev->ctx[ctx->num] = ctx; + if (vdev == dev->vfd_dec) { + ctx->type = MFCINST_DECODER; + ctx->c_ops = get_dec_codec_ops(); + s5p_mfc_dec_init(ctx); + /* Setup ctrl handler */ + ret = s5p_mfc_dec_ctrls_setup(ctx); + if (ret) { + mfc_err("Failed to setup mfc controls\n"); + goto err_ctrls_setup; + } + } else if (vdev == dev->vfd_enc) { + ctx->type = MFCINST_ENCODER; + ctx->c_ops = get_enc_codec_ops(); + /* only for encoder */ + INIT_LIST_HEAD(&ctx->ref_queue); + ctx->ref_queue_cnt = 0; + s5p_mfc_enc_init(ctx); + /* Setup ctrl handler */ + ret = s5p_mfc_enc_ctrls_setup(ctx); + if (ret) { + mfc_err("Failed to setup mfc controls\n"); + goto err_ctrls_setup; + } + } else { + ret = -ENOENT; + goto err_bad_node; + } + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + ctx->inst_no = MFC_NO_INSTANCE_SET; + /* Load firmware if this is the first instance */ + if (dev->num_inst == 1) { + dev->watchdog_timer.expires = jiffies + + msecs_to_jiffies(MFC_WATCHDOG_INTERVAL); + add_timer(&dev->watchdog_timer); + ret = s5p_mfc_power_on(); + if (ret < 0) { + mfc_err("power on failed\n"); + goto err_pwr_enable; + } + s5p_mfc_clock_on(); + ret = s5p_mfc_load_firmware(dev); + if (ret) { + s5p_mfc_clock_off(); + goto err_load_fw; + } + /* Init the FW */ + ret = s5p_mfc_init_hw(dev); + s5p_mfc_clock_off(); + if (ret) + goto err_init_hw; + } + /* Init videobuf2 queue for CAPTURE */ + q = &ctx->vq_dst; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->drv_priv = &ctx->fh; + q->lock = &dev->mfc_mutex; + if (vdev == dev->vfd_dec) { + q->io_modes = VB2_MMAP; + q->ops = get_dec_queue_ops(); + } else if (vdev == dev->vfd_enc) { + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->ops = get_enc_queue_ops(); + } else { + ret = -ENOENT; + goto err_queue_init; + } + /* + * We'll do mostly sequential access, so sacrifice TLB efficiency for + * faster allocation. + */ + q->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + ret = vb2_queue_init(q); + if (ret) { + mfc_err("Failed to initialize videobuf2 queue(capture)\n"); + goto err_queue_init; + } + /* Init videobuf2 queue for OUTPUT */ + q = &ctx->vq_src; + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + q->drv_priv = &ctx->fh; + q->lock = &dev->mfc_mutex; + if (vdev == dev->vfd_dec) { + q->io_modes = VB2_MMAP; + q->ops = get_dec_queue_ops(); + } else if (vdev == dev->vfd_enc) { + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->ops = get_enc_queue_ops(); + } else { + ret = -ENOENT; + goto err_queue_init; + } + /* One way to indicate end-of-stream for MFC is to set the + * bytesused == 0. However by default videobuf2 handles bytesused + * equal to 0 as a special case and changes its value to the size + * of the buffer. Set the allow_zero_bytesused flag so that videobuf2 + * will keep the value of bytesused intact. + */ + q->allow_zero_bytesused = 1; + + /* + * We'll do mostly sequential access, so sacrifice TLB efficiency for + * faster allocation. + */ + q->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + ret = vb2_queue_init(q); + if (ret) { + mfc_err("Failed to initialize videobuf2 queue(output)\n"); + goto err_queue_init; + } + mutex_unlock(&dev->mfc_mutex); + mfc_debug_leave(); + return ret; + /* Deinit when failure occurred */ +err_queue_init: + if (dev->num_inst == 1) + s5p_mfc_deinit_hw(dev); +err_init_hw: +err_load_fw: +err_pwr_enable: + if (dev->num_inst == 1) { + if (s5p_mfc_power_off() < 0) + mfc_err("power off failed\n"); + del_timer_sync(&dev->watchdog_timer); + } +err_ctrls_setup: + s5p_mfc_dec_ctrls_delete(ctx); +err_bad_node: + dev->ctx[ctx->num] = NULL; +err_no_ctx: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +err_alloc: + dev->num_inst--; + mutex_unlock(&dev->mfc_mutex); + mfc_debug_leave(); + return ret; +} + +/* Release MFC context */ +static int s5p_mfc_release(struct file *file) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data); + struct s5p_mfc_dev *dev = ctx->dev; + + /* if dev is null, do cleanup that doesn't need dev */ + mfc_debug_enter(); + if (dev) + mutex_lock(&dev->mfc_mutex); + vb2_queue_release(&ctx->vq_src); + vb2_queue_release(&ctx->vq_dst); + if (dev) { + s5p_mfc_clock_on(); + + /* Mark context as idle */ + clear_work_bit_irqsave(ctx); + /* + * If instance was initialised and not yet freed, + * return instance and free resources + */ + if (ctx->state != MFCINST_FREE && ctx->state != MFCINST_INIT) { + mfc_debug(2, "Has to free instance\n"); + s5p_mfc_close_mfc_inst(dev, ctx); + } + /* hardware locking scheme */ + if (dev->curr_ctx == ctx->num) + clear_bit(0, &dev->hw_lock); + dev->num_inst--; + if (dev->num_inst == 0) { + mfc_debug(2, "Last instance\n"); + s5p_mfc_deinit_hw(dev); + del_timer_sync(&dev->watchdog_timer); + s5p_mfc_clock_off(); + if (s5p_mfc_power_off() < 0) + mfc_err("Power off failed\n"); + } else { + mfc_debug(2, "Shutting down clock\n"); + s5p_mfc_clock_off(); + } + } + if (dev) + dev->ctx[ctx->num] = NULL; + s5p_mfc_dec_ctrls_delete(ctx); + v4l2_fh_del(&ctx->fh); + /* vdev is gone if dev is null */ + if (dev) + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + mfc_debug_leave(); + if (dev) + mutex_unlock(&dev->mfc_mutex); + + return 0; +} + +/* Poll */ +static __poll_t s5p_mfc_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data); + struct s5p_mfc_dev *dev = ctx->dev; + struct vb2_queue *src_q, *dst_q; + struct vb2_buffer *src_vb = NULL, *dst_vb = NULL; + __poll_t rc = 0; + unsigned long flags; + + mutex_lock(&dev->mfc_mutex); + src_q = &ctx->vq_src; + dst_q = &ctx->vq_dst; + /* + * There has to be at least one buffer queued on each queued_list, which + * means either in driver already or waiting for driver to claim it + * and start processing. + */ + if ((!src_q->streaming || list_empty(&src_q->queued_list)) + && (!dst_q->streaming || list_empty(&dst_q->queued_list))) { + rc = EPOLLERR; + goto end; + } + mutex_unlock(&dev->mfc_mutex); + poll_wait(file, &ctx->fh.wait, wait); + poll_wait(file, &src_q->done_wq, wait); + poll_wait(file, &dst_q->done_wq, wait); + mutex_lock(&dev->mfc_mutex); + if (v4l2_event_pending(&ctx->fh)) + rc |= EPOLLPRI; + spin_lock_irqsave(&src_q->done_lock, flags); + if (!list_empty(&src_q->done_list)) + src_vb = list_first_entry(&src_q->done_list, struct vb2_buffer, + done_entry); + if (src_vb && (src_vb->state == VB2_BUF_STATE_DONE + || src_vb->state == VB2_BUF_STATE_ERROR)) + rc |= EPOLLOUT | EPOLLWRNORM; + spin_unlock_irqrestore(&src_q->done_lock, flags); + spin_lock_irqsave(&dst_q->done_lock, flags); + if (!list_empty(&dst_q->done_list)) + dst_vb = list_first_entry(&dst_q->done_list, struct vb2_buffer, + done_entry); + if (dst_vb && (dst_vb->state == VB2_BUF_STATE_DONE + || dst_vb->state == VB2_BUF_STATE_ERROR)) + rc |= EPOLLIN | EPOLLRDNORM; + spin_unlock_irqrestore(&dst_q->done_lock, flags); +end: + mutex_unlock(&dev->mfc_mutex); + return rc; +} + +/* Mmap */ +static int s5p_mfc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(file->private_data); + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + int ret; + + if (offset < DST_QUEUE_OFF_BASE) { + mfc_debug(2, "mmaping source\n"); + ret = vb2_mmap(&ctx->vq_src, vma); + } else { /* capture */ + mfc_debug(2, "mmaping destination\n"); + vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT); + ret = vb2_mmap(&ctx->vq_dst, vma); + } + return ret; +} + +/* v4l2 ops */ +static const struct v4l2_file_operations s5p_mfc_fops = { + .owner = THIS_MODULE, + .open = s5p_mfc_open, + .release = s5p_mfc_release, + .poll = s5p_mfc_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = s5p_mfc_mmap, +}; + +/* DMA memory related helper functions */ +static void s5p_mfc_memdev_release(struct device *dev) +{ + of_reserved_mem_device_release(dev); +} + +static struct device *s5p_mfc_alloc_memdev(struct device *dev, + const char *name, unsigned int idx) +{ + struct device *child; + int ret; + + child = devm_kzalloc(dev, sizeof(*child), GFP_KERNEL); + if (!child) + return NULL; + + device_initialize(child); + dev_set_name(child, "%s:%s", dev_name(dev), name); + child->parent = dev; + child->coherent_dma_mask = dev->coherent_dma_mask; + child->dma_mask = dev->dma_mask; + child->release = s5p_mfc_memdev_release; + child->dma_parms = devm_kzalloc(dev, sizeof(*child->dma_parms), + GFP_KERNEL); + if (!child->dma_parms) + goto err; + + /* + * The memdevs are not proper OF platform devices, so in order for them + * to be treated as valid DMA masters we need a bit of a hack to force + * them to inherit the MFC node's DMA configuration. + */ + of_dma_configure(child, dev->of_node, true); + + if (device_add(child) == 0) { + ret = of_reserved_mem_device_init_by_idx(child, dev->of_node, + idx); + if (ret == 0) + return child; + device_del(child); + } +err: + put_device(child); + return NULL; +} + +static int s5p_mfc_configure_2port_memory(struct s5p_mfc_dev *mfc_dev) +{ + struct device *dev = &mfc_dev->plat_dev->dev; + void *bank2_virt; + dma_addr_t bank2_dma_addr; + unsigned long align_size = 1 << MFC_BASE_ALIGN_ORDER; + int ret; + + /* + * Create and initialize virtual devices for accessing + * reserved memory regions. + */ + mfc_dev->mem_dev[BANK_L_CTX] = s5p_mfc_alloc_memdev(dev, "left", + BANK_L_CTX); + if (!mfc_dev->mem_dev[BANK_L_CTX]) + return -ENODEV; + mfc_dev->mem_dev[BANK_R_CTX] = s5p_mfc_alloc_memdev(dev, "right", + BANK_R_CTX); + if (!mfc_dev->mem_dev[BANK_R_CTX]) { + device_unregister(mfc_dev->mem_dev[BANK_L_CTX]); + return -ENODEV; + } + + /* Allocate memory for firmware and initialize both banks addresses */ + ret = s5p_mfc_alloc_firmware(mfc_dev); + if (ret) { + device_unregister(mfc_dev->mem_dev[BANK_R_CTX]); + device_unregister(mfc_dev->mem_dev[BANK_L_CTX]); + return ret; + } + + mfc_dev->dma_base[BANK_L_CTX] = mfc_dev->fw_buf.dma; + + bank2_virt = dma_alloc_coherent(mfc_dev->mem_dev[BANK_R_CTX], + align_size, &bank2_dma_addr, GFP_KERNEL); + if (!bank2_virt) { + mfc_err("Allocating bank2 base failed\n"); + s5p_mfc_release_firmware(mfc_dev); + device_unregister(mfc_dev->mem_dev[BANK_R_CTX]); + device_unregister(mfc_dev->mem_dev[BANK_L_CTX]); + return -ENOMEM; + } + + /* Valid buffers passed to MFC encoder with LAST_FRAME command + * should not have address of bank2 - MFC will treat it as a null frame. + * To avoid such situation we set bank2 address below the pool address. + */ + mfc_dev->dma_base[BANK_R_CTX] = bank2_dma_addr - align_size; + + dma_free_coherent(mfc_dev->mem_dev[BANK_R_CTX], align_size, bank2_virt, + bank2_dma_addr); + + vb2_dma_contig_set_max_seg_size(mfc_dev->mem_dev[BANK_L_CTX], + DMA_BIT_MASK(32)); + vb2_dma_contig_set_max_seg_size(mfc_dev->mem_dev[BANK_R_CTX], + DMA_BIT_MASK(32)); + + return 0; +} + +static void s5p_mfc_unconfigure_2port_memory(struct s5p_mfc_dev *mfc_dev) +{ + device_unregister(mfc_dev->mem_dev[BANK_L_CTX]); + device_unregister(mfc_dev->mem_dev[BANK_R_CTX]); + vb2_dma_contig_clear_max_seg_size(mfc_dev->mem_dev[BANK_L_CTX]); + vb2_dma_contig_clear_max_seg_size(mfc_dev->mem_dev[BANK_R_CTX]); +} + +static int s5p_mfc_configure_common_memory(struct s5p_mfc_dev *mfc_dev) +{ + struct device *dev = &mfc_dev->plat_dev->dev; + unsigned long mem_size = SZ_4M; + + if (IS_ENABLED(CONFIG_DMA_CMA) || exynos_is_iommu_available(dev)) + mem_size = SZ_8M; + + if (mfc_mem_size) + mem_size = memparse(mfc_mem_size, NULL); + + mfc_dev->mem_bitmap = bitmap_zalloc(mem_size >> PAGE_SHIFT, GFP_KERNEL); + if (!mfc_dev->mem_bitmap) + return -ENOMEM; + + mfc_dev->mem_virt = dma_alloc_coherent(dev, mem_size, + &mfc_dev->mem_base, GFP_KERNEL); + if (!mfc_dev->mem_virt) { + bitmap_free(mfc_dev->mem_bitmap); + dev_err(dev, "failed to preallocate %ld MiB for the firmware and context buffers\n", + (mem_size / SZ_1M)); + return -ENOMEM; + } + mfc_dev->mem_size = mem_size; + mfc_dev->dma_base[BANK_L_CTX] = mfc_dev->mem_base; + mfc_dev->dma_base[BANK_R_CTX] = mfc_dev->mem_base; + + /* + * MFC hardware cannot handle 0 as a base address, so mark first 128K + * as used (to keep required base alignment) and adjust base address + */ + if (mfc_dev->mem_base == (dma_addr_t)0) { + unsigned int offset = 1 << MFC_BASE_ALIGN_ORDER; + + bitmap_set(mfc_dev->mem_bitmap, 0, offset >> PAGE_SHIFT); + mfc_dev->dma_base[BANK_L_CTX] += offset; + mfc_dev->dma_base[BANK_R_CTX] += offset; + } + + /* Firmware allocation cannot fail in this case */ + s5p_mfc_alloc_firmware(mfc_dev); + + mfc_dev->mem_dev[BANK_L_CTX] = mfc_dev->mem_dev[BANK_R_CTX] = dev; + vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32)); + + dev_info(dev, "preallocated %ld MiB buffer for the firmware and context buffers\n", + (mem_size / SZ_1M)); + + return 0; +} + +static void s5p_mfc_unconfigure_common_memory(struct s5p_mfc_dev *mfc_dev) +{ + struct device *dev = &mfc_dev->plat_dev->dev; + + dma_free_coherent(dev, mfc_dev->mem_size, mfc_dev->mem_virt, + mfc_dev->mem_base); + bitmap_free(mfc_dev->mem_bitmap); + vb2_dma_contig_clear_max_seg_size(dev); +} + +static int s5p_mfc_configure_dma_memory(struct s5p_mfc_dev *mfc_dev) +{ + struct device *dev = &mfc_dev->plat_dev->dev; + + if (exynos_is_iommu_available(dev) || !IS_TWOPORT(mfc_dev)) + return s5p_mfc_configure_common_memory(mfc_dev); + else + return s5p_mfc_configure_2port_memory(mfc_dev); +} + +static void s5p_mfc_unconfigure_dma_memory(struct s5p_mfc_dev *mfc_dev) +{ + struct device *dev = &mfc_dev->plat_dev->dev; + + s5p_mfc_release_firmware(mfc_dev); + if (exynos_is_iommu_available(dev) || !IS_TWOPORT(mfc_dev)) + s5p_mfc_unconfigure_common_memory(mfc_dev); + else + s5p_mfc_unconfigure_2port_memory(mfc_dev); +} + +/* MFC probe function */ +static int s5p_mfc_probe(struct platform_device *pdev) +{ + struct s5p_mfc_dev *dev; + struct video_device *vfd; + int ret; + + pr_debug("%s++\n", __func__); + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->irqlock); + spin_lock_init(&dev->condlock); + dev->plat_dev = pdev; + if (!dev->plat_dev) { + mfc_err("No platform data specified\n"); + return -ENODEV; + } + + dev->variant = of_device_get_match_data(&pdev->dev); + if (!dev->variant) { + dev_err(&pdev->dev, "Failed to get device MFC hardware variant information\n"); + return -ENOENT; + } + + dev->regs_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->regs_base)) + return PTR_ERR(dev->regs_base); + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + return ret; + dev->irq = ret; + ret = devm_request_irq(&pdev->dev, dev->irq, s5p_mfc_irq, + 0, pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "Failed to install irq (%d)\n", ret); + return ret; + } + + ret = s5p_mfc_configure_dma_memory(dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to configure DMA memory\n"); + return ret; + } + + ret = s5p_mfc_init_pm(dev); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get mfc clock source\n"); + goto err_dma; + } + + /* + * Load fails if fs isn't mounted. Try loading anyway. + * _open() will load it, it it fails now. Ignore failure. + */ + s5p_mfc_load_firmware(dev); + + mutex_init(&dev->mfc_mutex); + init_waitqueue_head(&dev->queue); + dev->hw_lock = 0; + INIT_WORK(&dev->watchdog_work, s5p_mfc_watchdog_worker); + atomic_set(&dev->watchdog_cnt, 0); + timer_setup(&dev->watchdog_timer, s5p_mfc_watchdog, 0); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto err_v4l2_dev_reg; + + /* decoder */ + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto err_dec_alloc; + } + vfd->fops = &s5p_mfc_fops; + vfd->ioctl_ops = get_dec_v4l2_ioctl_ops(); + vfd->release = video_device_release; + vfd->lock = &dev->mfc_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->vfl_dir = VFL_DIR_M2M; + vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + set_bit(V4L2_FL_QUIRK_INVERTED_CROP, &vfd->flags); + snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_DEC_NAME); + dev->vfd_dec = vfd; + video_set_drvdata(vfd, dev); + + /* encoder */ + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto err_enc_alloc; + } + vfd->fops = &s5p_mfc_fops; + vfd->ioctl_ops = get_enc_v4l2_ioctl_ops(); + vfd->release = video_device_release; + vfd->lock = &dev->mfc_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->vfl_dir = VFL_DIR_M2M; + vfd->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING; + snprintf(vfd->name, sizeof(vfd->name), "%s", S5P_MFC_ENC_NAME); + dev->vfd_enc = vfd; + video_set_drvdata(vfd, dev); + platform_set_drvdata(pdev, dev); + + /* Initialize HW ops and commands based on MFC version */ + s5p_mfc_init_hw_ops(dev); + s5p_mfc_init_hw_cmds(dev); + s5p_mfc_init_regs(dev); + + /* Register decoder and encoder */ + ret = video_register_device(dev->vfd_dec, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto err_dec_reg; + } + v4l2_info(&dev->v4l2_dev, + "decoder registered as /dev/video%d\n", dev->vfd_dec->num); + + ret = video_register_device(dev->vfd_enc, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto err_enc_reg; + } + v4l2_info(&dev->v4l2_dev, + "encoder registered as /dev/video%d\n", dev->vfd_enc->num); + + pr_debug("%s--\n", __func__); + return 0; + +/* Deinit MFC if probe had failed */ +err_enc_reg: + video_unregister_device(dev->vfd_dec); + dev->vfd_dec = NULL; +err_dec_reg: + video_device_release(dev->vfd_enc); +err_enc_alloc: + video_device_release(dev->vfd_dec); +err_dec_alloc: + v4l2_device_unregister(&dev->v4l2_dev); +err_v4l2_dev_reg: + s5p_mfc_final_pm(dev); +err_dma: + s5p_mfc_unconfigure_dma_memory(dev); + + pr_debug("%s-- with error\n", __func__); + return ret; + +} + +/* Remove the driver */ +static int s5p_mfc_remove(struct platform_device *pdev) +{ + struct s5p_mfc_dev *dev = platform_get_drvdata(pdev); + struct s5p_mfc_ctx *ctx; + int i; + + v4l2_info(&dev->v4l2_dev, "Removing %s\n", pdev->name); + + /* + * Clear ctx dev pointer to avoid races between s5p_mfc_remove() + * and s5p_mfc_release() and s5p_mfc_release() accessing ctx->dev + * after s5p_mfc_remove() is run during unbind. + */ + mutex_lock(&dev->mfc_mutex); + for (i = 0; i < MFC_NUM_CONTEXTS; i++) { + ctx = dev->ctx[i]; + if (!ctx) + continue; + /* clear ctx->dev */ + ctx->dev = NULL; + } + mutex_unlock(&dev->mfc_mutex); + + del_timer_sync(&dev->watchdog_timer); + flush_work(&dev->watchdog_work); + + video_unregister_device(dev->vfd_enc); + video_unregister_device(dev->vfd_dec); + v4l2_device_unregister(&dev->v4l2_dev); + s5p_mfc_unconfigure_dma_memory(dev); + + s5p_mfc_final_pm(dev); + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static int s5p_mfc_suspend(struct device *dev) +{ + struct s5p_mfc_dev *m_dev = dev_get_drvdata(dev); + int ret; + + if (m_dev->num_inst == 0) + return 0; + + if (test_and_set_bit(0, &m_dev->enter_suspend) != 0) { + mfc_err("Error: going to suspend for a second time\n"); + return -EIO; + } + + /* Check if we're processing then wait if it necessary. */ + while (test_and_set_bit(0, &m_dev->hw_lock) != 0) { + /* Try and lock the HW */ + /* Wait on the interrupt waitqueue */ + ret = wait_event_interruptible_timeout(m_dev->queue, + m_dev->int_cond, msecs_to_jiffies(MFC_INT_TIMEOUT)); + if (ret == 0) { + mfc_err("Waiting for hardware to finish timed out\n"); + clear_bit(0, &m_dev->enter_suspend); + return -EIO; + } + } + + ret = s5p_mfc_sleep(m_dev); + if (ret) { + clear_bit(0, &m_dev->enter_suspend); + clear_bit(0, &m_dev->hw_lock); + } + return ret; +} + +static int s5p_mfc_resume(struct device *dev) +{ + struct s5p_mfc_dev *m_dev = dev_get_drvdata(dev); + + if (m_dev->num_inst == 0) + return 0; + return s5p_mfc_wakeup(m_dev); +} +#endif + +/* Power management */ +static const struct dev_pm_ops s5p_mfc_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(s5p_mfc_suspend, s5p_mfc_resume) +}; + +static struct s5p_mfc_buf_size_v5 mfc_buf_size_v5 = { + .h264_ctx = MFC_H264_CTX_BUF_SIZE, + .non_h264_ctx = MFC_CTX_BUF_SIZE, + .dsc = DESC_BUF_SIZE, + .shm = SHARED_BUF_SIZE, +}; + +static struct s5p_mfc_buf_size buf_size_v5 = { + .fw = MAX_FW_SIZE, + .cpb = MAX_CPB_SIZE, + .priv = &mfc_buf_size_v5, +}; + +static struct s5p_mfc_variant mfc_drvdata_v5 = { + .version = MFC_VERSION, + .version_bit = MFC_V5_BIT, + .port_num = MFC_NUM_PORTS, + .buf_size = &buf_size_v5, + .fw_name[0] = "s5p-mfc.fw", + .clk_names = {"mfc", "sclk_mfc"}, + .num_clocks = 2, + .use_clock_gating = true, +}; + +static struct s5p_mfc_buf_size_v6 mfc_buf_size_v6 = { + .dev_ctx = MFC_CTX_BUF_SIZE_V6, + .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V6, + .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V6, + .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V6, + .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V6, +}; + +static struct s5p_mfc_buf_size buf_size_v6 = { + .fw = MAX_FW_SIZE_V6, + .cpb = MAX_CPB_SIZE_V6, + .priv = &mfc_buf_size_v6, +}; + +static struct s5p_mfc_variant mfc_drvdata_v6 = { + .version = MFC_VERSION_V6, + .version_bit = MFC_V6_BIT, + .port_num = MFC_NUM_PORTS_V6, + .buf_size = &buf_size_v6, + .fw_name[0] = "s5p-mfc-v6.fw", + /* + * v6-v2 firmware contains bug fixes and interface change + * for init buffer command + */ + .fw_name[1] = "s5p-mfc-v6-v2.fw", + .clk_names = {"mfc"}, + .num_clocks = 1, +}; + +static struct s5p_mfc_buf_size_v6 mfc_buf_size_v7 = { + .dev_ctx = MFC_CTX_BUF_SIZE_V7, + .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V7, + .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V7, + .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V7, + .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V7, +}; + +static struct s5p_mfc_buf_size buf_size_v7 = { + .fw = MAX_FW_SIZE_V7, + .cpb = MAX_CPB_SIZE_V7, + .priv = &mfc_buf_size_v7, +}; + +static struct s5p_mfc_variant mfc_drvdata_v7 = { + .version = MFC_VERSION_V7, + .version_bit = MFC_V7_BIT, + .port_num = MFC_NUM_PORTS_V7, + .buf_size = &buf_size_v7, + .fw_name[0] = "s5p-mfc-v7.fw", + .clk_names = {"mfc"}, + .num_clocks = 1, +}; + +static struct s5p_mfc_variant mfc_drvdata_v7_3250 = { + .version = MFC_VERSION_V7, + .version_bit = MFC_V7_BIT, + .port_num = MFC_NUM_PORTS_V7, + .buf_size = &buf_size_v7, + .fw_name[0] = "s5p-mfc-v7.fw", + .clk_names = {"mfc", "sclk_mfc"}, + .num_clocks = 2, +}; + +static struct s5p_mfc_buf_size_v6 mfc_buf_size_v8 = { + .dev_ctx = MFC_CTX_BUF_SIZE_V8, + .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V8, + .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V8, + .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V8, + .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V8, +}; + +static struct s5p_mfc_buf_size buf_size_v8 = { + .fw = MAX_FW_SIZE_V8, + .cpb = MAX_CPB_SIZE_V8, + .priv = &mfc_buf_size_v8, +}; + +static struct s5p_mfc_variant mfc_drvdata_v8 = { + .version = MFC_VERSION_V8, + .version_bit = MFC_V8_BIT, + .port_num = MFC_NUM_PORTS_V8, + .buf_size = &buf_size_v8, + .fw_name[0] = "s5p-mfc-v8.fw", + .clk_names = {"mfc"}, + .num_clocks = 1, +}; + +static struct s5p_mfc_variant mfc_drvdata_v8_5433 = { + .version = MFC_VERSION_V8, + .version_bit = MFC_V8_BIT, + .port_num = MFC_NUM_PORTS_V8, + .buf_size = &buf_size_v8, + .fw_name[0] = "s5p-mfc-v8.fw", + .clk_names = {"pclk", "aclk", "aclk_xiu"}, + .num_clocks = 3, +}; + +static struct s5p_mfc_buf_size_v6 mfc_buf_size_v10 = { + .dev_ctx = MFC_CTX_BUF_SIZE_V10, + .h264_dec_ctx = MFC_H264_DEC_CTX_BUF_SIZE_V10, + .other_dec_ctx = MFC_OTHER_DEC_CTX_BUF_SIZE_V10, + .h264_enc_ctx = MFC_H264_ENC_CTX_BUF_SIZE_V10, + .hevc_enc_ctx = MFC_HEVC_ENC_CTX_BUF_SIZE_V10, + .other_enc_ctx = MFC_OTHER_ENC_CTX_BUF_SIZE_V10, +}; + +static struct s5p_mfc_buf_size buf_size_v10 = { + .fw = MAX_FW_SIZE_V10, + .cpb = MAX_CPB_SIZE_V10, + .priv = &mfc_buf_size_v10, +}; + +static struct s5p_mfc_variant mfc_drvdata_v10 = { + .version = MFC_VERSION_V10, + .version_bit = MFC_V10_BIT, + .port_num = MFC_NUM_PORTS_V10, + .buf_size = &buf_size_v10, + .fw_name[0] = "s5p-mfc-v10.fw", +}; + +static const struct of_device_id exynos_mfc_match[] = { + { + .compatible = "samsung,mfc-v5", + .data = &mfc_drvdata_v5, + }, { + .compatible = "samsung,mfc-v6", + .data = &mfc_drvdata_v6, + }, { + .compatible = "samsung,mfc-v7", + .data = &mfc_drvdata_v7, + }, { + .compatible = "samsung,exynos3250-mfc", + .data = &mfc_drvdata_v7_3250, + }, { + .compatible = "samsung,mfc-v8", + .data = &mfc_drvdata_v8, + }, { + .compatible = "samsung,exynos5433-mfc", + .data = &mfc_drvdata_v8_5433, + }, { + .compatible = "samsung,mfc-v10", + .data = &mfc_drvdata_v10, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_mfc_match); + +static struct platform_driver s5p_mfc_driver = { + .probe = s5p_mfc_probe, + .remove = s5p_mfc_remove, + .driver = { + .name = S5P_MFC_NAME, + .pm = &s5p_mfc_pm_ops, + .of_match_table = exynos_mfc_match, + }, +}; + +module_platform_driver(s5p_mfc_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Kamil Debski "); +MODULE_DESCRIPTION("Samsung S5P Multi Format Codec V4L2 driver"); + diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c new file mode 100644 index 000000000..774c573dc --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.c + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#include "s5p_mfc_cmd.h" +#include "s5p_mfc_common.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_cmd_v5.h" +#include "s5p_mfc_cmd_v6.h" + +static struct s5p_mfc_hw_cmds *s5p_mfc_cmds; + +void s5p_mfc_init_hw_cmds(struct s5p_mfc_dev *dev) +{ + if (IS_MFCV6_PLUS(dev)) + s5p_mfc_cmds = s5p_mfc_init_hw_cmds_v6(); + else + s5p_mfc_cmds = s5p_mfc_init_hw_cmds_v5(); + + dev->mfc_cmds = s5p_mfc_cmds; +} diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h new file mode 100644 index 000000000..945d12fdc --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd.h + * + * Copyright (C) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_CMD_H_ +#define S5P_MFC_CMD_H_ + +#include "s5p_mfc_common.h" + +#define MAX_H2R_ARG 4 + +struct s5p_mfc_cmd_args { + unsigned int arg[MAX_H2R_ARG]; +}; + +struct s5p_mfc_hw_cmds { + int (*cmd_host2risc)(struct s5p_mfc_dev *dev, int cmd, + struct s5p_mfc_cmd_args *args); + int (*sys_init_cmd)(struct s5p_mfc_dev *dev); + int (*sleep_cmd)(struct s5p_mfc_dev *dev); + int (*wakeup_cmd)(struct s5p_mfc_dev *dev); + int (*open_inst_cmd)(struct s5p_mfc_ctx *ctx); + int (*close_inst_cmd)(struct s5p_mfc_ctx *ctx); +}; + +void s5p_mfc_init_hw_cmds(struct s5p_mfc_dev *dev); +#endif /* S5P_MFC_CMD_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c new file mode 100644 index 000000000..327e54e70 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c @@ -0,0 +1,163 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.c + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#include "regs-mfc.h" +#include "s5p_mfc_cmd.h" +#include "s5p_mfc_common.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_cmd_v5.h" + +/* This function is used to send a command to the MFC */ +static int s5p_mfc_cmd_host2risc_v5(struct s5p_mfc_dev *dev, int cmd, + struct s5p_mfc_cmd_args *args) +{ + int cur_cmd; + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(MFC_BW_TIMEOUT); + /* wait until host to risc command register becomes 'H2R_CMD_EMPTY' */ + do { + if (time_after(jiffies, timeout)) { + mfc_err("Timeout while waiting for hardware\n"); + return -EIO; + } + cur_cmd = mfc_read(dev, S5P_FIMV_HOST2RISC_CMD); + } while (cur_cmd != S5P_FIMV_H2R_CMD_EMPTY); + mfc_write(dev, args->arg[0], S5P_FIMV_HOST2RISC_ARG1); + mfc_write(dev, args->arg[1], S5P_FIMV_HOST2RISC_ARG2); + mfc_write(dev, args->arg[2], S5P_FIMV_HOST2RISC_ARG3); + mfc_write(dev, args->arg[3], S5P_FIMV_HOST2RISC_ARG4); + /* Issue the command */ + mfc_write(dev, cmd, S5P_FIMV_HOST2RISC_CMD); + return 0; +} + +/* Initialize the MFC */ +static int s5p_mfc_sys_init_cmd_v5(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_cmd_args h2r_args; + + memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args)); + h2r_args.arg[0] = dev->fw_buf.size; + return s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_SYS_INIT, + &h2r_args); +} + +/* Suspend the MFC hardware */ +static int s5p_mfc_sleep_cmd_v5(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_cmd_args h2r_args; + + memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args)); + return s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_SLEEP, &h2r_args); +} + +/* Wake up the MFC hardware */ +static int s5p_mfc_wakeup_cmd_v5(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_cmd_args h2r_args; + + memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args)); + return s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_WAKEUP, + &h2r_args); +} + + +static int s5p_mfc_open_inst_cmd_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_cmd_args h2r_args; + int ret; + + /* Preparing decoding - getting instance number */ + mfc_debug(2, "Getting instance number (codec: %d)\n", ctx->codec_mode); + dev->curr_ctx = ctx->num; + memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args)); + switch (ctx->codec_mode) { + case S5P_MFC_CODEC_H264_DEC: + h2r_args.arg[0] = S5P_FIMV_CODEC_H264_DEC; + break; + case S5P_MFC_CODEC_VC1_DEC: + h2r_args.arg[0] = S5P_FIMV_CODEC_VC1_DEC; + break; + case S5P_MFC_CODEC_MPEG4_DEC: + h2r_args.arg[0] = S5P_FIMV_CODEC_MPEG4_DEC; + break; + case S5P_MFC_CODEC_MPEG2_DEC: + h2r_args.arg[0] = S5P_FIMV_CODEC_MPEG2_DEC; + break; + case S5P_MFC_CODEC_H263_DEC: + h2r_args.arg[0] = S5P_FIMV_CODEC_H263_DEC; + break; + case S5P_MFC_CODEC_VC1RCV_DEC: + h2r_args.arg[0] = S5P_FIMV_CODEC_VC1RCV_DEC; + break; + case S5P_MFC_CODEC_H264_ENC: + h2r_args.arg[0] = S5P_FIMV_CODEC_H264_ENC; + break; + case S5P_MFC_CODEC_MPEG4_ENC: + h2r_args.arg[0] = S5P_FIMV_CODEC_MPEG4_ENC; + break; + case S5P_MFC_CODEC_H263_ENC: + h2r_args.arg[0] = S5P_FIMV_CODEC_H263_ENC; + break; + default: + h2r_args.arg[0] = S5P_FIMV_CODEC_NONE; + } + h2r_args.arg[1] = 0; /* no crc & no pixelcache */ + h2r_args.arg[2] = ctx->ctx.ofs; + h2r_args.arg[3] = ctx->ctx.size; + ret = s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_OPEN_INSTANCE, + &h2r_args); + if (ret) { + mfc_err("Failed to create a new instance\n"); + ctx->state = MFCINST_ERROR; + } + return ret; +} + +static int s5p_mfc_close_inst_cmd_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_cmd_args h2r_args; + int ret; + + if (ctx->state == MFCINST_FREE) { + mfc_err("Instance already returned\n"); + ctx->state = MFCINST_ERROR; + return -EINVAL; + } + /* Closing decoding instance */ + mfc_debug(2, "Returning instance number %d\n", ctx->inst_no); + dev->curr_ctx = ctx->num; + memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args)); + h2r_args.arg[0] = ctx->inst_no; + ret = s5p_mfc_cmd_host2risc_v5(dev, S5P_FIMV_H2R_CMD_CLOSE_INSTANCE, + &h2r_args); + if (ret) { + mfc_err("Failed to return an instance\n"); + ctx->state = MFCINST_ERROR; + return -EINVAL; + } + return 0; +} + +/* Initialize cmd function pointers for MFC v5 */ +static struct s5p_mfc_hw_cmds s5p_mfc_cmds_v5 = { + .cmd_host2risc = s5p_mfc_cmd_host2risc_v5, + .sys_init_cmd = s5p_mfc_sys_init_cmd_v5, + .sleep_cmd = s5p_mfc_sleep_cmd_v5, + .wakeup_cmd = s5p_mfc_wakeup_cmd_v5, + .open_inst_cmd = s5p_mfc_open_inst_cmd_v5, + .close_inst_cmd = s5p_mfc_close_inst_cmd_v5, +}; + +struct s5p_mfc_hw_cmds *s5p_mfc_init_hw_cmds_v5(void) +{ + return &s5p_mfc_cmds_v5; +} diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h new file mode 100644 index 000000000..6eafa514a --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v5.h + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_CMD_V5_H_ +#define S5P_MFC_CMD_V5_H_ + +#include "s5p_mfc_common.h" + +struct s5p_mfc_hw_cmds *s5p_mfc_init_hw_cmds_v5(void); + +#endif /* S5P_MFC_CMD_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c new file mode 100644 index 000000000..f8588e52d --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.c + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#include "s5p_mfc_common.h" + +#include "s5p_mfc_cmd.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_intr.h" +#include "s5p_mfc_opr.h" +#include "s5p_mfc_cmd_v6.h" + +static int s5p_mfc_cmd_host2risc_v6(struct s5p_mfc_dev *dev, int cmd, + struct s5p_mfc_cmd_args *args) +{ + mfc_debug(2, "Issue the command: %d\n", cmd); + + /* Reset RISC2HOST command */ + mfc_write(dev, 0x0, S5P_FIMV_RISC2HOST_CMD_V6); + + /* Issue the command */ + mfc_write(dev, cmd, S5P_FIMV_HOST2RISC_CMD_V6); + mfc_write(dev, 0x1, S5P_FIMV_HOST2RISC_INT_V6); + + return 0; +} + +static int s5p_mfc_sys_init_cmd_v6(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_cmd_args h2r_args; + struct s5p_mfc_buf_size_v6 *buf_size = dev->variant->buf_size->priv; + int ret; + + ret = s5p_mfc_hw_call(dev->mfc_ops, alloc_dev_context_buffer, dev); + if (ret) + return ret; + + mfc_write(dev, dev->ctx_buf.dma, S5P_FIMV_CONTEXT_MEM_ADDR_V6); + mfc_write(dev, buf_size->dev_ctx, S5P_FIMV_CONTEXT_MEM_SIZE_V6); + return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_SYS_INIT_V6, + &h2r_args); +} + +static int s5p_mfc_sleep_cmd_v6(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_cmd_args h2r_args; + + memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args)); + return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_SLEEP_V6, + &h2r_args); +} + +static int s5p_mfc_wakeup_cmd_v6(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_cmd_args h2r_args; + + memset(&h2r_args, 0, sizeof(struct s5p_mfc_cmd_args)); + return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_WAKEUP_V6, + &h2r_args); +} + +/* Open a new instance and get its number */ +static int s5p_mfc_open_inst_cmd_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_cmd_args h2r_args; + int codec_type; + + mfc_debug(2, "Requested codec mode: %d\n", ctx->codec_mode); + dev->curr_ctx = ctx->num; + switch (ctx->codec_mode) { + case S5P_MFC_CODEC_H264_DEC: + codec_type = S5P_FIMV_CODEC_H264_DEC_V6; + break; + case S5P_MFC_CODEC_H264_MVC_DEC: + codec_type = S5P_FIMV_CODEC_H264_MVC_DEC_V6; + break; + case S5P_MFC_CODEC_VC1_DEC: + codec_type = S5P_FIMV_CODEC_VC1_DEC_V6; + break; + case S5P_MFC_CODEC_MPEG4_DEC: + codec_type = S5P_FIMV_CODEC_MPEG4_DEC_V6; + break; + case S5P_MFC_CODEC_MPEG2_DEC: + codec_type = S5P_FIMV_CODEC_MPEG2_DEC_V6; + break; + case S5P_MFC_CODEC_H263_DEC: + codec_type = S5P_FIMV_CODEC_H263_DEC_V6; + break; + case S5P_MFC_CODEC_VC1RCV_DEC: + codec_type = S5P_FIMV_CODEC_VC1RCV_DEC_V6; + break; + case S5P_MFC_CODEC_VP8_DEC: + codec_type = S5P_FIMV_CODEC_VP8_DEC_V6; + break; + case S5P_MFC_CODEC_HEVC_DEC: + codec_type = S5P_FIMV_CODEC_HEVC_DEC; + break; + case S5P_MFC_CODEC_VP9_DEC: + codec_type = S5P_FIMV_CODEC_VP9_DEC; + break; + case S5P_MFC_CODEC_H264_ENC: + codec_type = S5P_FIMV_CODEC_H264_ENC_V6; + break; + case S5P_MFC_CODEC_H264_MVC_ENC: + codec_type = S5P_FIMV_CODEC_H264_MVC_ENC_V6; + break; + case S5P_MFC_CODEC_MPEG4_ENC: + codec_type = S5P_FIMV_CODEC_MPEG4_ENC_V6; + break; + case S5P_MFC_CODEC_H263_ENC: + codec_type = S5P_FIMV_CODEC_H263_ENC_V6; + break; + case S5P_MFC_CODEC_VP8_ENC: + codec_type = S5P_FIMV_CODEC_VP8_ENC_V7; + break; + case S5P_MFC_CODEC_HEVC_ENC: + codec_type = S5P_FIMV_CODEC_HEVC_ENC; + break; + default: + codec_type = S5P_FIMV_CODEC_NONE_V6; + } + mfc_write(dev, codec_type, S5P_FIMV_CODEC_TYPE_V6); + mfc_write(dev, ctx->ctx.dma, S5P_FIMV_CONTEXT_MEM_ADDR_V6); + mfc_write(dev, ctx->ctx.size, S5P_FIMV_CONTEXT_MEM_SIZE_V6); + mfc_write(dev, 0, S5P_FIMV_D_CRC_CTRL_V6); /* no crc */ + + return s5p_mfc_cmd_host2risc_v6(dev, S5P_FIMV_H2R_CMD_OPEN_INSTANCE_V6, + &h2r_args); +} + +/* Close instance */ +static int s5p_mfc_close_inst_cmd_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_cmd_args h2r_args; + int ret = 0; + + dev->curr_ctx = ctx->num; + if (ctx->state != MFCINST_FREE) { + mfc_write(dev, ctx->inst_no, S5P_FIMV_INSTANCE_ID_V6); + ret = s5p_mfc_cmd_host2risc_v6(dev, + S5P_FIMV_H2R_CMD_CLOSE_INSTANCE_V6, + &h2r_args); + } else { + ret = -EINVAL; + } + + return ret; +} + +/* Initialize cmd function pointers for MFC v6 */ +static struct s5p_mfc_hw_cmds s5p_mfc_cmds_v6 = { + .cmd_host2risc = s5p_mfc_cmd_host2risc_v6, + .sys_init_cmd = s5p_mfc_sys_init_cmd_v6, + .sleep_cmd = s5p_mfc_sleep_cmd_v6, + .wakeup_cmd = s5p_mfc_wakeup_cmd_v6, + .open_inst_cmd = s5p_mfc_open_inst_cmd_v6, + .close_inst_cmd = s5p_mfc_close_inst_cmd_v6, +}; + +struct s5p_mfc_hw_cmds *s5p_mfc_init_hw_cmds_v6(void) +{ + return &s5p_mfc_cmds_v6; +} diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h new file mode 100644 index 000000000..9dc44460c --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_cmd_v6.h + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_CMD_V6_H_ +#define S5P_MFC_CMD_V6_H_ + +#include "s5p_mfc_common.h" + +struct s5p_mfc_hw_cmds *s5p_mfc_init_hw_cmds_v6(void); + +#endif /* S5P_MFC_CMD_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_common.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_common.h new file mode 100644 index 000000000..5304f42c8 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_common.h @@ -0,0 +1,792 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Samsung S5P Multi Format Codec v 5.0 + * + * This file contains definitions of enums and structs used by the codec + * driver. + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#ifndef S5P_MFC_COMMON_H_ +#define S5P_MFC_COMMON_H_ + +#include +#include +#include +#include +#include +#include +#include "regs-mfc.h" +#include "regs-mfc-v10.h" + +#define S5P_MFC_NAME "s5p-mfc" + +/* Definitions related to MFC memory */ + +/* Offset base used to differentiate between CAPTURE and OUTPUT +* while mmaping */ +#define DST_QUEUE_OFF_BASE (1 << 30) + +#define BANK_L_CTX 0 +#define BANK_R_CTX 1 +#define BANK_CTX_NUM 2 + +#define MFC_BANK1_ALIGN_ORDER 13 +#define MFC_BANK2_ALIGN_ORDER 13 +#define MFC_BASE_ALIGN_ORDER 17 + +#define MFC_FW_MAX_VERSIONS 2 + +#include + +/* MFC definitions */ +#define MFC_MAX_EXTRA_DPB 5 +#define MFC_MAX_BUFFERS 32 +#define MFC_NUM_CONTEXTS 4 +/* Interrupt timeout */ +#define MFC_INT_TIMEOUT 2000 +/* Busy wait timeout */ +#define MFC_BW_TIMEOUT 500 +/* Watchdog interval */ +#define MFC_WATCHDOG_INTERVAL 1000 +/* After how many executions watchdog should assume lock up */ +#define MFC_WATCHDOG_CNT 10 +#define MFC_NO_INSTANCE_SET -1 +#define MFC_ENC_CAP_PLANE_COUNT 1 +#define MFC_ENC_OUT_PLANE_COUNT 2 +#define STUFF_BYTE 4 +#define MFC_MAX_CTRLS 128 + +#define S5P_MFC_CODEC_NONE -1 +#define S5P_MFC_CODEC_H264_DEC 0 +#define S5P_MFC_CODEC_H264_MVC_DEC 1 +#define S5P_MFC_CODEC_VC1_DEC 2 +#define S5P_MFC_CODEC_MPEG4_DEC 3 +#define S5P_MFC_CODEC_MPEG2_DEC 4 +#define S5P_MFC_CODEC_H263_DEC 5 +#define S5P_MFC_CODEC_VC1RCV_DEC 6 +#define S5P_MFC_CODEC_VP8_DEC 7 +#define S5P_MFC_CODEC_HEVC_DEC 17 +#define S5P_MFC_CODEC_VP9_DEC 18 + +#define S5P_MFC_CODEC_H264_ENC 20 +#define S5P_MFC_CODEC_H264_MVC_ENC 21 +#define S5P_MFC_CODEC_MPEG4_ENC 22 +#define S5P_MFC_CODEC_H263_ENC 23 +#define S5P_MFC_CODEC_VP8_ENC 24 +#define S5P_MFC_CODEC_HEVC_ENC 26 + +#define S5P_MFC_R2H_CMD_EMPTY 0 +#define S5P_MFC_R2H_CMD_SYS_INIT_RET 1 +#define S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET 2 +#define S5P_MFC_R2H_CMD_SEQ_DONE_RET 3 +#define S5P_MFC_R2H_CMD_INIT_BUFFERS_RET 4 +#define S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET 6 +#define S5P_MFC_R2H_CMD_SLEEP_RET 7 +#define S5P_MFC_R2H_CMD_WAKEUP_RET 8 +#define S5P_MFC_R2H_CMD_COMPLETE_SEQ_RET 9 +#define S5P_MFC_R2H_CMD_DPB_FLUSH_RET 10 +#define S5P_MFC_R2H_CMD_NAL_ABORT_RET 11 +#define S5P_MFC_R2H_CMD_FW_STATUS_RET 12 +#define S5P_MFC_R2H_CMD_FRAME_DONE_RET 13 +#define S5P_MFC_R2H_CMD_FIELD_DONE_RET 14 +#define S5P_MFC_R2H_CMD_SLICE_DONE_RET 15 +#define S5P_MFC_R2H_CMD_ENC_BUFFER_FUL_RET 16 +#define S5P_MFC_R2H_CMD_ERR_RET 32 + +#define MFC_MAX_CLOCKS 4 + +#define mfc_read(dev, offset) readl(dev->regs_base + (offset)) +#define mfc_write(dev, data, offset) writel((data), dev->regs_base + \ + (offset)) + +/* + * enum s5p_mfc_fmt_type - type of the pixelformat + */ +enum s5p_mfc_fmt_type { + MFC_FMT_DEC, + MFC_FMT_ENC, + MFC_FMT_RAW, +}; + +/* + * enum s5p_mfc_inst_type - The type of an MFC instance. + */ +enum s5p_mfc_inst_type { + MFCINST_INVALID, + MFCINST_DECODER, + MFCINST_ENCODER, +}; + +/* + * enum s5p_mfc_inst_state - The state of an MFC instance. + */ +enum s5p_mfc_inst_state { + MFCINST_FREE = 0, + MFCINST_INIT = 100, + MFCINST_GOT_INST, + MFCINST_HEAD_PARSED, + MFCINST_HEAD_PRODUCED, + MFCINST_BUFS_SET, + MFCINST_RUNNING, + MFCINST_FINISHING, + MFCINST_FINISHED, + MFCINST_RETURN_INST, + MFCINST_ERROR, + MFCINST_ABORT, + MFCINST_FLUSH, + MFCINST_RES_CHANGE_INIT, + MFCINST_RES_CHANGE_FLUSH, + MFCINST_RES_CHANGE_END, +}; + +/* + * enum s5p_mfc_queue_state - The state of buffer queue. + */ +enum s5p_mfc_queue_state { + QUEUE_FREE, + QUEUE_BUFS_REQUESTED, + QUEUE_BUFS_QUERIED, + QUEUE_BUFS_MMAPED, +}; + +/* + * enum s5p_mfc_decode_arg - type of frame decoding + */ +enum s5p_mfc_decode_arg { + MFC_DEC_FRAME, + MFC_DEC_LAST_FRAME, + MFC_DEC_RES_CHANGE, +}; + +enum s5p_mfc_fw_ver { + MFC_FW_V1, + MFC_FW_V2, +}; + +#define MFC_BUF_FLAG_USED (1 << 0) +#define MFC_BUF_FLAG_EOS (1 << 1) + +struct s5p_mfc_ctx; + +/* + * struct s5p_mfc_buf - MFC buffer + */ +struct s5p_mfc_buf { + struct vb2_v4l2_buffer *b; + struct list_head list; + union { + struct { + size_t luma; + size_t chroma; + } raw; + size_t stream; + } cookie; + int flags; +}; + +/* + * struct s5p_mfc_pm - power management data structure + */ +struct s5p_mfc_pm { + struct clk *clock_gate; + const char * const *clk_names; + struct clk *clocks[MFC_MAX_CLOCKS]; + int num_clocks; + bool use_clock_gating; + + struct device *device; +}; + +struct s5p_mfc_buf_size_v5 { + unsigned int h264_ctx; + unsigned int non_h264_ctx; + unsigned int dsc; + unsigned int shm; +}; + +struct s5p_mfc_buf_size_v6 { + unsigned int dev_ctx; + unsigned int h264_dec_ctx; + unsigned int other_dec_ctx; + unsigned int h264_enc_ctx; + unsigned int hevc_enc_ctx; + unsigned int other_enc_ctx; +}; + +struct s5p_mfc_buf_size { + unsigned int fw; + unsigned int cpb; + void *priv; +}; + +struct s5p_mfc_variant { + unsigned int version; + unsigned int port_num; + u32 version_bit; + struct s5p_mfc_buf_size *buf_size; + char *fw_name[MFC_FW_MAX_VERSIONS]; + const char *clk_names[MFC_MAX_CLOCKS]; + int num_clocks; + bool use_clock_gating; +}; + +/** + * struct s5p_mfc_priv_buf - represents internal used buffer + * @ofs: offset of each buffer, will be used for MFC + * @virt: kernel virtual address, only valid when the + * buffer accessed by driver + * @dma: DMA address, only valid when kernel DMA API used + * @size: size of the buffer + * @ctx: memory context (bank) used for this allocation + */ +struct s5p_mfc_priv_buf { + unsigned long ofs; + void *virt; + dma_addr_t dma; + size_t size; + unsigned int ctx; +}; + +/** + * struct s5p_mfc_dev - The struct containing driver internal parameters. + * + * @v4l2_dev: v4l2_device + * @vfd_dec: video device for decoding + * @vfd_enc: video device for encoding + * @plat_dev: platform device + * @mem_dev: child devices of the memory banks + * @regs_base: base address of the MFC hw registers + * @irq: irq resource + * @dec_ctrl_handler: control framework handler for decoding + * @enc_ctrl_handler: control framework handler for encoding + * @pm: power management control + * @variant: MFC hardware variant information + * @num_inst: counter of active MFC instances + * @irqlock: lock for operations on videobuf2 queues + * @condlock: lock for changing/checking if a context is ready to be + * processed + * @mfc_mutex: lock for video_device + * @int_cond: variable used by the waitqueue + * @int_type: type of last interrupt + * @int_err: error number for last interrupt + * @queue: waitqueue for waiting for completion of device commands + * @fw_buf: the firmware buffer data structure + * @mem_size: size of the firmware operation memory + * @mem_base: base DMA address of the firmware operation memory + * @mem_bitmap: bitmap for managing MFC internal buffer allocations + * @mem_virt: virtual address of the firmware operation memory + * @dma_base: address of the beginning of memory banks + * @hw_lock: used for hardware locking + * @ctx: array of driver contexts + * @curr_ctx: number of the currently running context + * @ctx_work_bits: used to mark which contexts are waiting for hardware + * @watchdog_cnt: counter for the watchdog + * @watchdog_timer: timer for the watchdog + * @watchdog_workqueue: workqueue for the watchdog + * @watchdog_work: worker for the watchdog + * @enter_suspend: flag set when entering suspend + * @ctx_buf: common context memory (MFCv6) + * @warn_start: hardware error code from which warnings start + * @mfc_ops: ops structure holding HW operation function pointers + * @mfc_cmds: cmd structure holding HW commands function pointers + * @mfc_regs: structure holding MFC registers + * @fw_ver: loaded firmware sub-version + * @fw_get_done: flag set when request_firmware() is complete and + * copied into fw_buf + * @risc_on: flag indicates RISC is on or off + * + */ +struct s5p_mfc_dev { + struct v4l2_device v4l2_dev; + struct video_device *vfd_dec; + struct video_device *vfd_enc; + struct platform_device *plat_dev; + struct device *mem_dev[BANK_CTX_NUM]; + void __iomem *regs_base; + int irq; + struct v4l2_ctrl_handler dec_ctrl_handler; + struct v4l2_ctrl_handler enc_ctrl_handler; + struct s5p_mfc_pm pm; + const struct s5p_mfc_variant *variant; + int num_inst; + spinlock_t irqlock; /* lock when operating on context */ + spinlock_t condlock; /* lock when changing/checking if a context is + ready to be processed */ + struct mutex mfc_mutex; /* video_device lock */ + int int_cond; + int int_type; + unsigned int int_err; + wait_queue_head_t queue; + struct s5p_mfc_priv_buf fw_buf; + size_t mem_size; + dma_addr_t mem_base; + unsigned long *mem_bitmap; + void *mem_virt; + dma_addr_t dma_base[BANK_CTX_NUM]; + unsigned long hw_lock; + struct s5p_mfc_ctx *ctx[MFC_NUM_CONTEXTS]; + int curr_ctx; + unsigned long ctx_work_bits; + atomic_t watchdog_cnt; + struct timer_list watchdog_timer; + struct workqueue_struct *watchdog_workqueue; + struct work_struct watchdog_work; + unsigned long enter_suspend; + + struct s5p_mfc_priv_buf ctx_buf; + int warn_start; + struct s5p_mfc_hw_ops *mfc_ops; + struct s5p_mfc_hw_cmds *mfc_cmds; + const struct s5p_mfc_regs *mfc_regs; + enum s5p_mfc_fw_ver fw_ver; + bool fw_get_done; + bool risc_on; /* indicates if RISC is on or off */ +}; + +/* + * struct s5p_mfc_h264_enc_params - encoding parameters for h264 + */ +struct s5p_mfc_h264_enc_params { + enum v4l2_mpeg_video_h264_profile profile; + enum v4l2_mpeg_video_h264_loop_filter_mode loop_filter_mode; + s8 loop_filter_alpha; + s8 loop_filter_beta; + enum v4l2_mpeg_video_h264_entropy_mode entropy_mode; + u8 max_ref_pic; + u8 num_ref_pic_4p; + int _8x8_transform; + int rc_mb_dark; + int rc_mb_smooth; + int rc_mb_static; + int rc_mb_activity; + int vui_sar; + u8 vui_sar_idc; + u16 vui_ext_sar_width; + u16 vui_ext_sar_height; + int open_gop; + u16 open_gop_size; + u8 rc_frame_qp; + u8 rc_min_qp; + u8 rc_max_qp; + u8 rc_p_frame_qp; + u8 rc_b_frame_qp; + enum v4l2_mpeg_video_h264_level level_v4l2; + int level; + u16 cpb_size; + int interlace; + u8 hier_qp; + u8 hier_qp_type; + u8 hier_qp_layer; + u8 hier_qp_layer_qp[7]; + u8 sei_frame_packing; + u8 sei_fp_curr_frame_0; + u8 sei_fp_arrangement_type; + + u8 fmo; + u8 fmo_map_type; + u8 fmo_slice_grp; + u8 fmo_chg_dir; + u32 fmo_chg_rate; + u32 fmo_run_len[4]; + u8 aso; + u32 aso_slice_order[8]; +}; + +/* + * struct s5p_mfc_mpeg4_enc_params - encoding parameters for h263 and mpeg4 + */ +struct s5p_mfc_mpeg4_enc_params { + /* MPEG4 Only */ + enum v4l2_mpeg_video_mpeg4_profile profile; + int quarter_pixel; + /* Common for MPEG4, H263 */ + u16 vop_time_res; + u16 vop_frm_delta; + u8 rc_frame_qp; + u8 rc_min_qp; + u8 rc_max_qp; + u8 rc_p_frame_qp; + u8 rc_b_frame_qp; + enum v4l2_mpeg_video_mpeg4_level level_v4l2; + int level; +}; + +/* + * struct s5p_mfc_vp8_enc_params - encoding parameters for vp8 + */ +struct s5p_mfc_vp8_enc_params { + u8 imd_4x4; + enum v4l2_vp8_num_partitions num_partitions; + enum v4l2_vp8_num_ref_frames num_ref; + u8 filter_level; + u8 filter_sharpness; + u32 golden_frame_ref_period; + enum v4l2_vp8_golden_frame_sel golden_frame_sel; + u8 hier_layer; + u8 hier_layer_qp[3]; + u8 rc_min_qp; + u8 rc_max_qp; + u8 rc_frame_qp; + u8 rc_p_frame_qp; + u8 profile; +}; + +struct s5p_mfc_hevc_enc_params { + enum v4l2_mpeg_video_hevc_profile profile; + int level; + enum v4l2_mpeg_video_h264_level level_v4l2; + u8 tier; + u32 rc_framerate; + u8 rc_min_qp; + u8 rc_max_qp; + u8 rc_lcu_dark; + u8 rc_lcu_smooth; + u8 rc_lcu_static; + u8 rc_lcu_activity; + u8 rc_frame_qp; + u8 rc_p_frame_qp; + u8 rc_b_frame_qp; + u8 max_partition_depth; + u8 num_refs_for_p; + u8 refreshtype; + u16 refreshperiod; + s32 lf_beta_offset_div2; + s32 lf_tc_offset_div2; + u8 loopfilter; + u8 loopfilter_disable; + u8 loopfilter_across; + u8 nal_control_length_filed; + u8 nal_control_user_ref; + u8 nal_control_store_ref; + u8 const_intra_period_enable; + u8 lossless_cu_enable; + u8 wavefront_enable; + u8 enable_ltr; + u8 hier_qp_enable; + enum v4l2_mpeg_video_hevc_hier_coding_type hier_qp_type; + u8 num_hier_layer; + u8 hier_qp_layer[7]; + u32 hier_bit_layer[7]; + u8 sign_data_hiding; + u8 general_pb_enable; + u8 temporal_id_enable; + u8 strong_intra_smooth; + u8 intra_pu_split_disable; + u8 tmv_prediction_disable; + u8 max_num_merge_mv; + u8 eco_mode_enable; + u8 encoding_nostartcode_enable; + u8 size_of_length_field; + u8 prepend_sps_pps_to_idr; +}; + +/* + * struct s5p_mfc_enc_params - general encoding parameters + */ +struct s5p_mfc_enc_params { + u16 width; + u16 height; + u32 mv_h_range; + u32 mv_v_range; + + u16 gop_size; + enum v4l2_mpeg_video_multi_slice_mode slice_mode; + u16 slice_mb; + u32 slice_bit; + u16 intra_refresh_mb; + int pad; + u8 pad_luma; + u8 pad_cb; + u8 pad_cr; + int rc_frame; + int rc_mb; + u32 rc_bitrate; + u16 rc_reaction_coeff; + u16 vbv_size; + u32 vbv_delay; + + enum v4l2_mpeg_video_header_mode seq_hdr_mode; + enum v4l2_mpeg_mfc51_video_frame_skip_mode frame_skip_mode; + int fixed_target_bit; + + u8 num_b_frame; + u32 rc_framerate_num; + u32 rc_framerate_denom; + + struct { + struct s5p_mfc_h264_enc_params h264; + struct s5p_mfc_mpeg4_enc_params mpeg4; + struct s5p_mfc_vp8_enc_params vp8; + struct s5p_mfc_hevc_enc_params hevc; + } codec; + +}; + +/* + * struct s5p_mfc_codec_ops - codec ops, used by encoding + */ +struct s5p_mfc_codec_ops { + /* initialization routines */ + int (*pre_seq_start) (struct s5p_mfc_ctx *ctx); + int (*post_seq_start) (struct s5p_mfc_ctx *ctx); + /* execution routines */ + int (*pre_frame_start) (struct s5p_mfc_ctx *ctx); + int (*post_frame_start) (struct s5p_mfc_ctx *ctx); +}; + +#define call_cop(c, op, args...) \ + (((c)->c_ops->op) ? \ + ((c)->c_ops->op(args)) : 0) + +/** + * struct s5p_mfc_ctx - This struct contains the instance context + * + * @dev: pointer to the s5p_mfc_dev of the device + * @fh: struct v4l2_fh + * @num: number of the context that this structure describes + * @int_cond: variable used by the waitqueue + * @int_type: type of the last interrupt + * @int_err: error number received from MFC hw in the interrupt + * @queue: waitqueue that can be used to wait for this context to + * finish + * @src_fmt: source pixelformat information + * @dst_fmt: destination pixelformat information + * @vq_src: vb2 queue for source buffers + * @vq_dst: vb2 queue for destination buffers + * @src_queue: driver internal queue for source buffers + * @dst_queue: driver internal queue for destination buffers + * @src_queue_cnt: number of buffers queued on the source internal queue + * @dst_queue_cnt: number of buffers queued on the dest internal queue + * @type: type of the instance - decoder or encoder + * @state: state of the context + * @inst_no: number of hw instance associated with the context + * @img_width: width of the image that is decoded or encoded + * @img_height: height of the image that is decoded or encoded + * @buf_width: width of the buffer for processed image + * @buf_height: height of the buffer for processed image + * @luma_size: size of a luma plane + * @chroma_size: size of a chroma plane + * @mv_size: size of a motion vectors buffer + * @consumed_stream: number of bytes that have been used so far from the + * decoding buffer + * @dpb_flush_flag: flag used to indicate that a DPB buffers are being + * flushed + * @head_processed: flag mentioning whether the header data is processed + * completely or not + * @bank1: handle to memory allocated for temporary buffers from + * memory bank 1 + * @bank2: handle to memory allocated for temporary buffers from + * memory bank 2 + * @capture_state: state of the capture buffers queue + * @output_state: state of the output buffers queue + * @src_bufs: information on allocated source buffers + * @src_bufs_cnt: number of allocated source buffers + * @dst_bufs: information on allocated destination buffers + * @dst_bufs_cnt: number of allocated destination buffers + * @sequence: counter for the sequence number for v4l2 + * @dec_dst_flag: flags for buffers queued in the hardware + * @dec_src_buf_size: size of the buffer for source buffers in decoding + * @codec_mode: number of codec mode used by MFC hw + * @slice_interface: slice interface flag + * @loop_filter_mpeg4: loop filter for MPEG4 flag + * @display_delay: value of the display delay for H264 + * @display_delay_enable: display delay for H264 enable flag + * @after_packed_pb: flag used to track buffer when stream is in + * Packed PB format + * @sei_fp_parse: enable/disable parsing of frame packing SEI information + * @pb_count: count of the DPB buffers required by MFC hw + * @total_dpb_count: count of DPB buffers with additional buffers + * requested by the application + * @ctx: context buffer information + * @dsc: descriptor buffer information + * @shm: shared memory buffer information + * @mv_count: number of MV buffers allocated for decoding + * @enc_params: encoding parameters for MFC + * @enc_dst_buf_size: size of the buffers for encoder output + * @luma_dpb_size: dpb buffer size for luma + * @chroma_dpb_size: dpb buffer size for chroma + * @me_buffer_size: size of the motion estimation buffer + * @tmv_buffer_size: size of temporal predictor motion vector buffer + * @frame_type: used to force the type of the next encoded frame + * @ref_queue: list of the reference buffers for encoding + * @force_frame_type: encoder's frame type forcing control + * @ref_queue_cnt: number of the buffers in the reference list + * @slice_size: slice size + * @slice_mode: mode of dividing frames into slices + * @c_ops: ops for encoding + * @ctrls: array of controls, used when adding controls to the + * v4l2 control framework + * @ctrl_handler: handler for v4l2 framework + * @scratch_buf_size: scratch buffer size + */ +struct s5p_mfc_ctx { + struct s5p_mfc_dev *dev; + struct v4l2_fh fh; + + int num; + + int int_cond; + int int_type; + unsigned int int_err; + wait_queue_head_t queue; + + struct s5p_mfc_fmt *src_fmt; + struct s5p_mfc_fmt *dst_fmt; + + struct vb2_queue vq_src; + struct vb2_queue vq_dst; + + struct list_head src_queue; + struct list_head dst_queue; + + unsigned int src_queue_cnt; + unsigned int dst_queue_cnt; + + enum s5p_mfc_inst_type type; + enum s5p_mfc_inst_state state; + int inst_no; + + /* Image parameters */ + int img_width; + int img_height; + int buf_width; + int buf_height; + + int luma_size; + int chroma_size; + int mv_size; + + unsigned long consumed_stream; + + unsigned int dpb_flush_flag; + unsigned int head_processed; + + struct s5p_mfc_priv_buf bank1; + struct s5p_mfc_priv_buf bank2; + + enum s5p_mfc_queue_state capture_state; + enum s5p_mfc_queue_state output_state; + + struct s5p_mfc_buf src_bufs[MFC_MAX_BUFFERS]; + int src_bufs_cnt; + struct s5p_mfc_buf dst_bufs[MFC_MAX_BUFFERS]; + int dst_bufs_cnt; + + unsigned int sequence; + unsigned long dec_dst_flag; + size_t dec_src_buf_size; + + /* Control values */ + int codec_mode; + int slice_interface; + int loop_filter_mpeg4; + int display_delay; + int display_delay_enable; + int after_packed_pb; + int sei_fp_parse; + + int pb_count; + int total_dpb_count; + int mv_count; + /* Buffers */ + struct s5p_mfc_priv_buf ctx; + struct s5p_mfc_priv_buf dsc; + struct s5p_mfc_priv_buf shm; + + struct s5p_mfc_enc_params enc_params; + + size_t enc_dst_buf_size; + size_t luma_dpb_size; + size_t chroma_dpb_size; + size_t me_buffer_size; + size_t tmv_buffer_size; + + enum v4l2_mpeg_mfc51_video_force_frame_type force_frame_type; + + struct list_head ref_queue; + unsigned int ref_queue_cnt; + + enum v4l2_mpeg_video_multi_slice_mode slice_mode; + union { + unsigned int mb; + unsigned int bits; + } slice_size; + + const struct s5p_mfc_codec_ops *c_ops; + + struct v4l2_ctrl *ctrls[MFC_MAX_CTRLS]; + struct v4l2_ctrl_handler ctrl_handler; + size_t scratch_buf_size; +}; + +/* + * struct s5p_mfc_fmt - structure used to store information about pixelformats + * used by the MFC + */ +struct s5p_mfc_fmt { + u32 fourcc; + u32 codec_mode; + enum s5p_mfc_fmt_type type; + u32 num_planes; + u32 versions; + u32 flags; +}; + +/* + * struct mfc_control - structure used to store information about MFC controls + * it is used to initialize the control framework. + */ +struct mfc_control { + __u32 id; + enum v4l2_ctrl_type type; + __u8 name[32]; /* Whatever */ + __s32 minimum; /* Note signedness */ + __s32 maximum; + __s32 step; + __u32 menu_skip_mask; + __s32 default_value; + __u32 flags; + __u32 reserved[2]; + __u8 is_volatile; +}; + +/* Macro for making hardware specific calls */ +#define s5p_mfc_hw_call(f, op, args...) \ + ((f && f->op) ? f->op(args) : (typeof(f->op(args)))(-ENODEV)) + +#define fh_to_ctx(__fh) container_of(__fh, struct s5p_mfc_ctx, fh) +#define ctrl_to_ctx(__ctrl) \ + container_of((__ctrl)->handler, struct s5p_mfc_ctx, ctrl_handler) + +void clear_work_bit(struct s5p_mfc_ctx *ctx); +void set_work_bit(struct s5p_mfc_ctx *ctx); +void clear_work_bit_irqsave(struct s5p_mfc_ctx *ctx); +void set_work_bit_irqsave(struct s5p_mfc_ctx *ctx); +int s5p_mfc_get_new_ctx(struct s5p_mfc_dev *dev); +void s5p_mfc_cleanup_queue(struct list_head *lh, struct vb2_queue *vq); + +#define HAS_PORTNUM(dev) (dev ? (dev->variant ? \ + (dev->variant->port_num ? 1 : 0) : 0) : 0) +#define IS_TWOPORT(dev) (dev->variant->port_num == 2 ? 1 : 0) +#define IS_MFCV6_PLUS(dev) (dev->variant->version >= 0x60 ? 1 : 0) +#define IS_MFCV7_PLUS(dev) (dev->variant->version >= 0x70 ? 1 : 0) +#define IS_MFCV8_PLUS(dev) (dev->variant->version >= 0x80 ? 1 : 0) +#define IS_MFCV10(dev) (dev->variant->version >= 0xA0 ? 1 : 0) +#define FW_HAS_E_MIN_SCRATCH_BUF(dev) (IS_MFCV10(dev)) + +#define MFC_V5_BIT BIT(0) +#define MFC_V6_BIT BIT(1) +#define MFC_V7_BIT BIT(2) +#define MFC_V8_BIT BIT(3) +#define MFC_V10_BIT BIT(5) + +#define MFC_V5PLUS_BITS (MFC_V5_BIT | MFC_V6_BIT | MFC_V7_BIT | \ + MFC_V8_BIT | MFC_V10_BIT) +#define MFC_V6PLUS_BITS (MFC_V6_BIT | MFC_V7_BIT | MFC_V8_BIT | \ + MFC_V10_BIT) +#define MFC_V7PLUS_BITS (MFC_V7_BIT | MFC_V8_BIT | MFC_V10_BIT) + +#endif /* S5P_MFC_COMMON_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c new file mode 100644 index 000000000..6d3c92045 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c @@ -0,0 +1,484 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#include +#include +#include +#include +#include +#include "s5p_mfc_cmd.h" +#include "s5p_mfc_common.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_intr.h" +#include "s5p_mfc_opr.h" +#include "s5p_mfc_pm.h" +#include "s5p_mfc_ctrl.h" + +/* Allocate memory for firmware */ +int s5p_mfc_alloc_firmware(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_priv_buf *fw_buf = &dev->fw_buf; + int err; + + fw_buf->size = dev->variant->buf_size->fw; + + if (fw_buf->virt) { + mfc_err("Attempting to allocate firmware when it seems that it is already loaded\n"); + return -ENOMEM; + } + + err = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &dev->fw_buf); + if (err) { + mfc_err("Allocating bitprocessor buffer failed\n"); + return err; + } + + return 0; +} + +/* Load firmware */ +int s5p_mfc_load_firmware(struct s5p_mfc_dev *dev) +{ + struct firmware *fw_blob; + int i, err = -EINVAL; + + /* Firmware has to be present as a separate file or compiled + * into kernel. */ + mfc_debug_enter(); + + if (dev->fw_get_done) + return 0; + + for (i = MFC_FW_MAX_VERSIONS - 1; i >= 0; i--) { + if (!dev->variant->fw_name[i]) + continue; + err = request_firmware((const struct firmware **)&fw_blob, + dev->variant->fw_name[i], &dev->plat_dev->dev); + if (!err) { + dev->fw_ver = (enum s5p_mfc_fw_ver) i; + break; + } + } + + if (err != 0) { + mfc_err("Firmware is not present in the /lib/firmware directory nor compiled in kernel\n"); + return -EINVAL; + } + if (fw_blob->size > dev->fw_buf.size) { + mfc_err("MFC firmware is too big to be loaded\n"); + release_firmware(fw_blob); + return -ENOMEM; + } + memcpy(dev->fw_buf.virt, fw_blob->data, fw_blob->size); + wmb(); + dev->fw_get_done = true; + release_firmware(fw_blob); + mfc_debug_leave(); + return 0; +} + +/* Release firmware memory */ +int s5p_mfc_release_firmware(struct s5p_mfc_dev *dev) +{ + /* Before calling this function one has to make sure + * that MFC is no longer processing */ + s5p_mfc_release_priv_buf(dev, &dev->fw_buf); + dev->fw_get_done = false; + return 0; +} + +static int s5p_mfc_bus_reset(struct s5p_mfc_dev *dev) +{ + unsigned int status; + unsigned long timeout; + + /* Reset */ + mfc_write(dev, 0x1, S5P_FIMV_MFC_BUS_RESET_CTRL); + timeout = jiffies + msecs_to_jiffies(MFC_BW_TIMEOUT); + /* Check bus status */ + do { + if (time_after(jiffies, timeout)) { + mfc_err("Timeout while resetting MFC.\n"); + return -EIO; + } + status = mfc_read(dev, S5P_FIMV_MFC_BUS_RESET_CTRL); + } while ((status & 0x2) == 0); + return 0; +} + +/* Reset the device */ +int s5p_mfc_reset(struct s5p_mfc_dev *dev) +{ + unsigned int mc_status; + unsigned long timeout; + int i; + + mfc_debug_enter(); + + if (IS_MFCV6_PLUS(dev)) { + /* Zero Initialization of MFC registers */ + mfc_write(dev, 0, S5P_FIMV_RISC2HOST_CMD_V6); + mfc_write(dev, 0, S5P_FIMV_HOST2RISC_CMD_V6); + mfc_write(dev, 0, S5P_FIMV_FW_VERSION_V6); + + for (i = 0; i < S5P_FIMV_REG_CLEAR_COUNT_V6; i++) + mfc_write(dev, 0, S5P_FIMV_REG_CLEAR_BEGIN_V6 + (i*4)); + + /* check bus reset control before reset */ + if (dev->risc_on) + if (s5p_mfc_bus_reset(dev)) + return -EIO; + /* Reset + * set RISC_ON to 0 during power_on & wake_up. + * V6 needs RISC_ON set to 0 during reset also. + */ + if ((!dev->risc_on) || (!IS_MFCV7_PLUS(dev))) + mfc_write(dev, 0, S5P_FIMV_RISC_ON_V6); + + mfc_write(dev, 0x1FFF, S5P_FIMV_MFC_RESET_V6); + mfc_write(dev, 0, S5P_FIMV_MFC_RESET_V6); + } else { + /* Stop procedure */ + /* reset RISC */ + mfc_write(dev, 0x3f6, S5P_FIMV_SW_RESET); + /* All reset except for MC */ + mfc_write(dev, 0x3e2, S5P_FIMV_SW_RESET); + mdelay(10); + + timeout = jiffies + msecs_to_jiffies(MFC_BW_TIMEOUT); + /* Check MC status */ + do { + if (time_after(jiffies, timeout)) { + mfc_err("Timeout while resetting MFC\n"); + return -EIO; + } + + mc_status = mfc_read(dev, S5P_FIMV_MC_STATUS); + + } while (mc_status & 0x3); + + mfc_write(dev, 0x0, S5P_FIMV_SW_RESET); + mfc_write(dev, 0x3fe, S5P_FIMV_SW_RESET); + } + + mfc_debug_leave(); + return 0; +} + +static inline void s5p_mfc_init_memctrl(struct s5p_mfc_dev *dev) +{ + if (IS_MFCV6_PLUS(dev)) { + mfc_write(dev, dev->dma_base[BANK_L_CTX], + S5P_FIMV_RISC_BASE_ADDRESS_V6); + mfc_debug(2, "Base Address : %pad\n", + &dev->dma_base[BANK_L_CTX]); + } else { + mfc_write(dev, dev->dma_base[BANK_L_CTX], + S5P_FIMV_MC_DRAMBASE_ADR_A); + mfc_write(dev, dev->dma_base[BANK_R_CTX], + S5P_FIMV_MC_DRAMBASE_ADR_B); + mfc_debug(2, "Bank1: %pad, Bank2: %pad\n", + &dev->dma_base[BANK_L_CTX], + &dev->dma_base[BANK_R_CTX]); + } +} + +static inline void s5p_mfc_clear_cmds(struct s5p_mfc_dev *dev) +{ + if (IS_MFCV6_PLUS(dev)) { + /* Zero initialization should be done before RESET. + * Nothing to do here. */ + } else { + mfc_write(dev, 0xffffffff, S5P_FIMV_SI_CH0_INST_ID); + mfc_write(dev, 0xffffffff, S5P_FIMV_SI_CH1_INST_ID); + mfc_write(dev, 0, S5P_FIMV_RISC2HOST_CMD); + mfc_write(dev, 0, S5P_FIMV_HOST2RISC_CMD); + } +} + +/* Initialize hardware */ +int s5p_mfc_init_hw(struct s5p_mfc_dev *dev) +{ + unsigned int ver; + int ret; + + mfc_debug_enter(); + if (!dev->fw_buf.virt) { + mfc_err("Firmware memory is not allocated.\n"); + return -EINVAL; + } + + /* 0. MFC reset */ + mfc_debug(2, "MFC reset..\n"); + s5p_mfc_clock_on(); + dev->risc_on = 0; + ret = s5p_mfc_reset(dev); + if (ret) { + mfc_err("Failed to reset MFC - timeout\n"); + return ret; + } + mfc_debug(2, "Done MFC reset..\n"); + /* 1. Set DRAM base Addr */ + s5p_mfc_init_memctrl(dev); + /* 2. Initialize registers of channel I/F */ + s5p_mfc_clear_cmds(dev); + /* 3. Release reset signal to the RISC */ + s5p_mfc_clean_dev_int_flags(dev); + if (IS_MFCV6_PLUS(dev)) { + dev->risc_on = 1; + mfc_write(dev, 0x1, S5P_FIMV_RISC_ON_V6); + } + else + mfc_write(dev, 0x3ff, S5P_FIMV_SW_RESET); + + if (IS_MFCV10(dev)) + mfc_write(dev, 0x0, S5P_FIMV_MFC_CLOCK_OFF_V10); + + mfc_debug(2, "Will now wait for completion of firmware transfer\n"); + if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_FW_STATUS_RET)) { + mfc_err("Failed to load firmware\n"); + s5p_mfc_reset(dev); + s5p_mfc_clock_off(); + return -EIO; + } + s5p_mfc_clean_dev_int_flags(dev); + /* 4. Initialize firmware */ + ret = s5p_mfc_hw_call(dev->mfc_cmds, sys_init_cmd, dev); + if (ret) { + mfc_err("Failed to send command to MFC - timeout\n"); + s5p_mfc_reset(dev); + s5p_mfc_clock_off(); + return ret; + } + mfc_debug(2, "Ok, now will wait for completion of hardware init\n"); + if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_SYS_INIT_RET)) { + mfc_err("Failed to init hardware\n"); + s5p_mfc_reset(dev); + s5p_mfc_clock_off(); + return -EIO; + } + dev->int_cond = 0; + if (dev->int_err != 0 || dev->int_type != + S5P_MFC_R2H_CMD_SYS_INIT_RET) { + /* Failure. */ + mfc_err("Failed to init firmware - error: %d int: %d\n", + dev->int_err, dev->int_type); + s5p_mfc_reset(dev); + s5p_mfc_clock_off(); + return -EIO; + } + if (IS_MFCV6_PLUS(dev)) + ver = mfc_read(dev, S5P_FIMV_FW_VERSION_V6); + else + ver = mfc_read(dev, S5P_FIMV_FW_VERSION); + + mfc_debug(2, "MFC F/W version : %02xyy, %02xmm, %02xdd\n", + (ver >> 16) & 0xFF, (ver >> 8) & 0xFF, ver & 0xFF); + s5p_mfc_clock_off(); + mfc_debug_leave(); + return 0; +} + + +/* Deinitialize hardware */ +void s5p_mfc_deinit_hw(struct s5p_mfc_dev *dev) +{ + s5p_mfc_clock_on(); + + s5p_mfc_reset(dev); + s5p_mfc_hw_call(dev->mfc_ops, release_dev_context_buffer, dev); + + s5p_mfc_clock_off(); +} + +int s5p_mfc_sleep(struct s5p_mfc_dev *dev) +{ + int ret; + + mfc_debug_enter(); + s5p_mfc_clock_on(); + s5p_mfc_clean_dev_int_flags(dev); + ret = s5p_mfc_hw_call(dev->mfc_cmds, sleep_cmd, dev); + if (ret) { + mfc_err("Failed to send command to MFC - timeout\n"); + return ret; + } + if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_SLEEP_RET)) { + mfc_err("Failed to sleep\n"); + return -EIO; + } + s5p_mfc_clock_off(); + dev->int_cond = 0; + if (dev->int_err != 0 || dev->int_type != + S5P_MFC_R2H_CMD_SLEEP_RET) { + /* Failure. */ + mfc_err("Failed to sleep - error: %d int: %d\n", dev->int_err, + dev->int_type); + return -EIO; + } + mfc_debug_leave(); + return ret; +} + +static int s5p_mfc_v8_wait_wakeup(struct s5p_mfc_dev *dev) +{ + int ret; + + /* Release reset signal to the RISC */ + dev->risc_on = 1; + mfc_write(dev, 0x1, S5P_FIMV_RISC_ON_V6); + + if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_FW_STATUS_RET)) { + mfc_err("Failed to reset MFCV8\n"); + return -EIO; + } + mfc_debug(2, "Write command to wakeup MFCV8\n"); + ret = s5p_mfc_hw_call(dev->mfc_cmds, wakeup_cmd, dev); + if (ret) { + mfc_err("Failed to send command to MFCV8 - timeout\n"); + return ret; + } + + if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_WAKEUP_RET)) { + mfc_err("Failed to wakeup MFC\n"); + return -EIO; + } + return ret; +} + +static int s5p_mfc_wait_wakeup(struct s5p_mfc_dev *dev) +{ + int ret; + + /* Send MFC wakeup command */ + ret = s5p_mfc_hw_call(dev->mfc_cmds, wakeup_cmd, dev); + if (ret) { + mfc_err("Failed to send command to MFC - timeout\n"); + return ret; + } + + /* Release reset signal to the RISC */ + if (IS_MFCV6_PLUS(dev)) { + dev->risc_on = 1; + mfc_write(dev, 0x1, S5P_FIMV_RISC_ON_V6); + } else { + mfc_write(dev, 0x3ff, S5P_FIMV_SW_RESET); + } + + if (s5p_mfc_wait_for_done_dev(dev, S5P_MFC_R2H_CMD_WAKEUP_RET)) { + mfc_err("Failed to wakeup MFC\n"); + return -EIO; + } + return ret; +} + +int s5p_mfc_wakeup(struct s5p_mfc_dev *dev) +{ + int ret; + + mfc_debug_enter(); + /* 0. MFC reset */ + mfc_debug(2, "MFC reset..\n"); + s5p_mfc_clock_on(); + dev->risc_on = 0; + ret = s5p_mfc_reset(dev); + if (ret) { + mfc_err("Failed to reset MFC - timeout\n"); + s5p_mfc_clock_off(); + return ret; + } + mfc_debug(2, "Done MFC reset..\n"); + /* 1. Set DRAM base Addr */ + s5p_mfc_init_memctrl(dev); + /* 2. Initialize registers of channel I/F */ + s5p_mfc_clear_cmds(dev); + s5p_mfc_clean_dev_int_flags(dev); + /* 3. Send MFC wakeup command and wait for completion*/ + if (IS_MFCV8_PLUS(dev)) + ret = s5p_mfc_v8_wait_wakeup(dev); + else + ret = s5p_mfc_wait_wakeup(dev); + + s5p_mfc_clock_off(); + if (ret) + return ret; + + dev->int_cond = 0; + if (dev->int_err != 0 || dev->int_type != + S5P_MFC_R2H_CMD_WAKEUP_RET) { + /* Failure. */ + mfc_err("Failed to wakeup - error: %d int: %d\n", dev->int_err, + dev->int_type); + return -EIO; + } + mfc_debug_leave(); + return 0; +} + +int s5p_mfc_open_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx) +{ + int ret = 0; + + ret = s5p_mfc_hw_call(dev->mfc_ops, alloc_instance_buffer, ctx); + if (ret) { + mfc_err("Failed allocating instance buffer\n"); + goto err; + } + + if (ctx->type == MFCINST_DECODER) { + ret = s5p_mfc_hw_call(dev->mfc_ops, + alloc_dec_temp_buffers, ctx); + if (ret) { + mfc_err("Failed allocating temporary buffers\n"); + goto err_free_inst_buf; + } + } + + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + if (s5p_mfc_wait_for_done_ctx(ctx, + S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET, 0)) { + /* Error or timeout */ + mfc_err("Error getting instance from hardware\n"); + ret = -EIO; + goto err_free_desc_buf; + } + + mfc_debug(2, "Got instance number: %d\n", ctx->inst_no); + return ret; + +err_free_desc_buf: + if (ctx->type == MFCINST_DECODER) + s5p_mfc_hw_call(dev->mfc_ops, release_dec_desc_buffer, ctx); +err_free_inst_buf: + s5p_mfc_hw_call(dev->mfc_ops, release_instance_buffer, ctx); +err: + return ret; +} + +void s5p_mfc_close_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx) +{ + ctx->state = MFCINST_RETURN_INST; + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + /* Wait until instance is returned or timeout occurred */ + if (s5p_mfc_wait_for_done_ctx(ctx, + S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET, 0)){ + clear_work_bit_irqsave(ctx); + mfc_err("Err returning instance\n"); + } + + /* Free resources */ + s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, ctx); + s5p_mfc_hw_call(dev->mfc_ops, release_instance_buffer, ctx); + if (ctx->type == MFCINST_DECODER) + s5p_mfc_hw_call(dev->mfc_ops, release_dec_desc_buffer, ctx); + + ctx->inst_no = MFC_NO_INSTANCE_SET; + ctx->state = MFCINST_FREE; +} diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h new file mode 100644 index 000000000..653ba5f3d --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h @@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_ctrl.h + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_CTRL_H +#define S5P_MFC_CTRL_H + +#include "s5p_mfc_common.h" + +int s5p_mfc_release_firmware(struct s5p_mfc_dev *dev); +int s5p_mfc_alloc_firmware(struct s5p_mfc_dev *dev); +int s5p_mfc_load_firmware(struct s5p_mfc_dev *dev); + +int s5p_mfc_init_hw(struct s5p_mfc_dev *dev); +void s5p_mfc_deinit_hw(struct s5p_mfc_dev *dev); + +int s5p_mfc_sleep(struct s5p_mfc_dev *dev); +int s5p_mfc_wakeup(struct s5p_mfc_dev *dev); + +int s5p_mfc_reset(struct s5p_mfc_dev *dev); + +int s5p_mfc_open_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx); +void s5p_mfc_close_mfc_inst(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx); + +#endif /* S5P_MFC_CTRL_H */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h new file mode 100644 index 000000000..bba5dad6d --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h @@ -0,0 +1,51 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_debug.h + * + * Header file for Samsung MFC (Multi Function Codec - FIMV) driver + * This file contains debug macros + * + * Kamil Debski, Copyright (c) 2011 Samsung Electronics + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_DEBUG_H_ +#define S5P_MFC_DEBUG_H_ + +#define DEBUG + +#ifdef DEBUG +extern int mfc_debug_level; + +#define mfc_debug(level, fmt, args...) \ + do { \ + if (mfc_debug_level >= level) \ + printk(KERN_DEBUG "%s:%d: " fmt, \ + __func__, __LINE__, ##args); \ + } while (0) +#else +#define mfc_debug(level, fmt, args...) +#endif + +#define mfc_debug_enter() mfc_debug(5, "enter\n") +#define mfc_debug_leave() mfc_debug(5, "leave\n") + +#define mfc_err(fmt, args...) \ + do { \ + printk(KERN_ERR "%s:%d: " fmt, \ + __func__, __LINE__, ##args); \ + } while (0) + +#define mfc_err_limited(fmt, args...) \ + do { \ + printk_ratelimited(KERN_ERR "%s:%d: " fmt, \ + __func__, __LINE__, ##args); \ + } while (0) + +#define mfc_info(fmt, args...) \ + do { \ + printk(KERN_INFO "%s:%d: " fmt, \ + __func__, __LINE__, ##args); \ + } while (0) + +#endif /* S5P_MFC_DEBUG_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c new file mode 100644 index 000000000..268ffe4da --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c @@ -0,0 +1,1216 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.c + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * Kamil Debski, + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "s5p_mfc_common.h" +#include "s5p_mfc_ctrl.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_dec.h" +#include "s5p_mfc_intr.h" +#include "s5p_mfc_opr.h" +#include "s5p_mfc_pm.h" + +static struct s5p_mfc_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_NV12MT_16X16, + .codec_mode = S5P_MFC_CODEC_NONE, + .type = MFC_FMT_RAW, + .num_planes = 2, + .versions = MFC_V6_BIT | MFC_V7_BIT, + }, + { + .fourcc = V4L2_PIX_FMT_NV12MT, + .codec_mode = S5P_MFC_CODEC_NONE, + .type = MFC_FMT_RAW, + .num_planes = 2, + .versions = MFC_V5_BIT, + }, + { + .fourcc = V4L2_PIX_FMT_NV12M, + .codec_mode = S5P_MFC_CODEC_NONE, + .type = MFC_FMT_RAW, + .num_planes = 2, + .versions = MFC_V6PLUS_BITS, + }, + { + .fourcc = V4L2_PIX_FMT_NV21M, + .codec_mode = S5P_MFC_CODEC_NONE, + .type = MFC_FMT_RAW, + .num_planes = 2, + .versions = MFC_V6PLUS_BITS, + }, + { + .fourcc = V4L2_PIX_FMT_H264, + .codec_mode = S5P_MFC_CODEC_H264_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | + V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM, + }, + { + .fourcc = V4L2_PIX_FMT_H264_MVC, + .codec_mode = S5P_MFC_CODEC_H264_MVC_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V6PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | + V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM, + }, + { + .fourcc = V4L2_PIX_FMT_H263, + .codec_mode = S5P_MFC_CODEC_H263_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, + { + .fourcc = V4L2_PIX_FMT_MPEG1, + .codec_mode = S5P_MFC_CODEC_MPEG2_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | + V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM, + }, + { + .fourcc = V4L2_PIX_FMT_MPEG2, + .codec_mode = S5P_MFC_CODEC_MPEG2_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | + V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM, + }, + { + .fourcc = V4L2_PIX_FMT_MPEG4, + .codec_mode = S5P_MFC_CODEC_MPEG4_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | + V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM, + }, + { + .fourcc = V4L2_PIX_FMT_XVID, + .codec_mode = S5P_MFC_CODEC_MPEG4_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, + { + .fourcc = V4L2_PIX_FMT_VC1_ANNEX_G, + .codec_mode = S5P_MFC_CODEC_VC1_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, + { + .fourcc = V4L2_PIX_FMT_VC1_ANNEX_L, + .codec_mode = S5P_MFC_CODEC_VC1RCV_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, + { + .fourcc = V4L2_PIX_FMT_VP8, + .codec_mode = S5P_MFC_CODEC_VP8_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V6PLUS_BITS, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, + { + .fourcc = V4L2_PIX_FMT_HEVC, + .codec_mode = S5P_FIMV_CODEC_HEVC_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V10_BIT, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION | + V4L2_FMT_FLAG_CONTINUOUS_BYTESTREAM, + }, + { + .fourcc = V4L2_PIX_FMT_VP9, + .codec_mode = S5P_FIMV_CODEC_VP9_DEC, + .type = MFC_FMT_DEC, + .num_planes = 1, + .versions = MFC_V10_BIT, + .flags = V4L2_FMT_FLAG_DYN_RESOLUTION, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +/* Find selected format description */ +static struct s5p_mfc_fmt *find_format(struct v4l2_format *f, unsigned int t) +{ + unsigned int i; + + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].fourcc == f->fmt.pix_mp.pixelformat && + formats[i].type == t) + return &formats[i]; + } + return NULL; +} + +static struct mfc_control controls[] = { + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "H264 Display Delay", + .minimum = 0, + .maximum = 16383, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 16383, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "H264 Display Delay Enable", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mpeg4 Loop Filter Enable", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Slice Interface Enable", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Minimum number of cap bufs", + .minimum = 1, + .maximum = 32, + .step = 1, + .default_value = 1, + .is_volatile = 1, + }, +}; + +#define NUM_CTRLS ARRAY_SIZE(controls) + +/* Check whether a context should be run on hardware */ +static int s5p_mfc_ctx_ready(struct s5p_mfc_ctx *ctx) +{ + /* Context is to parse header */ + if (ctx->src_queue_cnt >= 1 && ctx->state == MFCINST_GOT_INST) + return 1; + /* Context is to decode a frame */ + if (ctx->src_queue_cnt >= 1 && + ctx->state == MFCINST_RUNNING && + ctx->dst_queue_cnt >= ctx->pb_count) + return 1; + /* Context is to return last frame */ + if (ctx->state == MFCINST_FINISHING && + ctx->dst_queue_cnt >= ctx->pb_count) + return 1; + /* Context is to set buffers */ + if (ctx->src_queue_cnt >= 1 && + ctx->state == MFCINST_HEAD_PARSED && + ctx->capture_state == QUEUE_BUFS_MMAPED) + return 1; + /* Resolution change */ + if ((ctx->state == MFCINST_RES_CHANGE_INIT || + ctx->state == MFCINST_RES_CHANGE_FLUSH) && + ctx->dst_queue_cnt >= ctx->pb_count) + return 1; + if (ctx->state == MFCINST_RES_CHANGE_END && + ctx->src_queue_cnt >= 1) + return 1; + mfc_debug(2, "ctx is not ready\n"); + return 0; +} + +static const struct s5p_mfc_codec_ops decoder_codec_ops = { + .pre_seq_start = NULL, + .post_seq_start = NULL, + .pre_frame_start = NULL, + .post_frame_start = NULL, +}; + +/* Query capabilities of the device */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + + strscpy(cap->driver, S5P_MFC_NAME, sizeof(cap->driver)); + strscpy(cap->card, dev->vfd_dec->name, sizeof(cap->card)); + return 0; +} + +/* Enumerate format */ +static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *f, + bool out) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + int i, j = 0; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (out && formats[i].type != MFC_FMT_DEC) + continue; + else if (!out && formats[i].type != MFC_FMT_RAW) + continue; + else if ((dev->variant->version_bit & formats[i].versions) == 0) + continue; + + if (j == f->index) + break; + ++j; + } + if (i == ARRAY_SIZE(formats)) + return -EINVAL; + f->pixelformat = formats[i].fourcc; + return 0; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *pirv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt(file, f, false); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt(file, f, true); +} + +/* Get format */ +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format_mplane *pix_mp; + + mfc_debug_enter(); + pix_mp = &f->fmt.pix_mp; + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + (ctx->state == MFCINST_GOT_INST || ctx->state == + MFCINST_RES_CHANGE_END)) { + /* If the MFC is parsing the header, + * so wait until it is finished */ + s5p_mfc_wait_for_done_ctx(ctx, S5P_MFC_R2H_CMD_SEQ_DONE_RET, + 0); + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + ctx->state >= MFCINST_HEAD_PARSED && + ctx->state < MFCINST_ABORT) { + /* This is run on CAPTURE (decode output) */ + /* Width and height are set to the dimensions + of the movie, the buffer is bigger and + further processing stages should crop to this + rectangle. */ + pix_mp->width = ctx->buf_width; + pix_mp->height = ctx->buf_height; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->num_planes = 2; + /* Set pixelformat to the format in which MFC + outputs the decoded frame */ + pix_mp->pixelformat = ctx->dst_fmt->fourcc; + pix_mp->plane_fmt[0].bytesperline = ctx->buf_width; + pix_mp->plane_fmt[0].sizeimage = ctx->luma_size; + pix_mp->plane_fmt[1].bytesperline = ctx->buf_width; + pix_mp->plane_fmt[1].sizeimage = ctx->chroma_size; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + /* This is run on OUTPUT + The buffer contains compressed image + so width and height have no meaning */ + pix_mp->width = 0; + pix_mp->height = 0; + pix_mp->field = V4L2_FIELD_NONE; + pix_mp->plane_fmt[0].bytesperline = ctx->dec_src_buf_size; + pix_mp->plane_fmt[0].sizeimage = ctx->dec_src_buf_size; + pix_mp->pixelformat = ctx->src_fmt->fourcc; + pix_mp->num_planes = ctx->src_fmt->num_planes; + } else { + mfc_err("Format could not be read\n"); + mfc_debug(2, "%s-- with error\n", __func__); + return -EINVAL; + } + mfc_debug_leave(); + return 0; +} + +/* Try format */ +static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + struct s5p_mfc_fmt *fmt; + + mfc_debug(2, "Type is %d\n", f->type); + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + fmt = find_format(f, MFC_FMT_DEC); + if (!fmt) { + mfc_err("Unsupported format for source.\n"); + return -EINVAL; + } + if (fmt->codec_mode == S5P_FIMV_CODEC_NONE) { + mfc_err("Unknown codec\n"); + return -EINVAL; + } + if ((dev->variant->version_bit & fmt->versions) == 0) { + mfc_err("Unsupported format by this MFC version.\n"); + return -EINVAL; + } + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmt = find_format(f, MFC_FMT_RAW); + if (!fmt) { + mfc_err("Unsupported format for destination.\n"); + return -EINVAL; + } + if ((dev->variant->version_bit & fmt->versions) == 0) { + mfc_err("Unsupported format by this MFC version.\n"); + return -EINVAL; + } + } + + return 0; +} + +/* Set format */ +static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + int ret = 0; + struct v4l2_pix_format_mplane *pix_mp; + struct s5p_mfc_buf_size *buf_size = dev->variant->buf_size; + + mfc_debug_enter(); + ret = vidioc_try_fmt(file, priv, f); + pix_mp = &f->fmt.pix_mp; + if (ret) + return ret; + if (vb2_is_streaming(&ctx->vq_src) || vb2_is_streaming(&ctx->vq_dst)) { + v4l2_err(&dev->v4l2_dev, "%s queue busy\n", __func__); + ret = -EBUSY; + goto out; + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* dst_fmt is validated by call to vidioc_try_fmt */ + ctx->dst_fmt = find_format(f, MFC_FMT_RAW); + ret = 0; + goto out; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + /* src_fmt is validated by call to vidioc_try_fmt */ + ctx->src_fmt = find_format(f, MFC_FMT_DEC); + ctx->codec_mode = ctx->src_fmt->codec_mode; + mfc_debug(2, "The codec number is: %d\n", ctx->codec_mode); + pix_mp->height = 0; + pix_mp->width = 0; + if (pix_mp->plane_fmt[0].sizeimage == 0) + pix_mp->plane_fmt[0].sizeimage = ctx->dec_src_buf_size = + DEF_CPB_SIZE; + else if (pix_mp->plane_fmt[0].sizeimage > buf_size->cpb) + ctx->dec_src_buf_size = buf_size->cpb; + else + ctx->dec_src_buf_size = pix_mp->plane_fmt[0].sizeimage; + pix_mp->plane_fmt[0].bytesperline = 0; + ctx->state = MFCINST_INIT; + ret = 0; + goto out; + } else { + mfc_err("Wrong type error for S_FMT : %d", f->type); + ret = -EINVAL; + goto out; + } + +out: + mfc_debug_leave(); + return ret; +} + +static int reqbufs_output(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx, + struct v4l2_requestbuffers *reqbufs) +{ + int ret = 0; + + s5p_mfc_clock_on(); + + if (reqbufs->count == 0) { + mfc_debug(2, "Freeing buffers\n"); + ret = vb2_reqbufs(&ctx->vq_src, reqbufs); + if (ret) + goto out; + ctx->src_bufs_cnt = 0; + ctx->output_state = QUEUE_FREE; + } else if (ctx->output_state == QUEUE_FREE) { + /* Can only request buffers when we have a valid format set. */ + WARN_ON(ctx->src_bufs_cnt != 0); + if (ctx->state != MFCINST_INIT) { + mfc_err("Reqbufs called in an invalid state\n"); + ret = -EINVAL; + goto out; + } + + mfc_debug(2, "Allocating %d buffers for OUTPUT queue\n", + reqbufs->count); + ret = vb2_reqbufs(&ctx->vq_src, reqbufs); + if (ret) + goto out; + + ret = s5p_mfc_open_mfc_inst(dev, ctx); + if (ret) { + reqbufs->count = 0; + vb2_reqbufs(&ctx->vq_src, reqbufs); + goto out; + } + + ctx->output_state = QUEUE_BUFS_REQUESTED; + } else { + mfc_err("Buffers have already been requested\n"); + ret = -EINVAL; + } +out: + s5p_mfc_clock_off(); + if (ret) + mfc_err("Failed allocating buffers for OUTPUT queue\n"); + return ret; +} + +static int reqbufs_capture(struct s5p_mfc_dev *dev, struct s5p_mfc_ctx *ctx, + struct v4l2_requestbuffers *reqbufs) +{ + int ret = 0; + + s5p_mfc_clock_on(); + + if (reqbufs->count == 0) { + mfc_debug(2, "Freeing buffers\n"); + ret = vb2_reqbufs(&ctx->vq_dst, reqbufs); + if (ret) + goto out; + s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, ctx); + ctx->dst_bufs_cnt = 0; + } else if (ctx->capture_state == QUEUE_FREE) { + WARN_ON(ctx->dst_bufs_cnt != 0); + mfc_debug(2, "Allocating %d buffers for CAPTURE queue\n", + reqbufs->count); + ret = vb2_reqbufs(&ctx->vq_dst, reqbufs); + if (ret) + goto out; + + ctx->capture_state = QUEUE_BUFS_REQUESTED; + ctx->total_dpb_count = reqbufs->count; + + ret = s5p_mfc_hw_call(dev->mfc_ops, alloc_codec_buffers, ctx); + if (ret) { + mfc_err("Failed to allocate decoding buffers\n"); + reqbufs->count = 0; + vb2_reqbufs(&ctx->vq_dst, reqbufs); + ret = -ENOMEM; + ctx->capture_state = QUEUE_FREE; + goto out; + } + + WARN_ON(ctx->dst_bufs_cnt != ctx->total_dpb_count); + ctx->capture_state = QUEUE_BUFS_MMAPED; + + if (s5p_mfc_ctx_ready(ctx)) + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + s5p_mfc_wait_for_done_ctx(ctx, S5P_MFC_R2H_CMD_INIT_BUFFERS_RET, + 0); + } else { + mfc_err("Buffers have already been requested\n"); + ret = -EINVAL; + } +out: + s5p_mfc_clock_off(); + if (ret) + mfc_err("Failed allocating buffers for CAPTURE queue\n"); + return ret; +} + +/* Request buffers */ +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (reqbufs->memory != V4L2_MEMORY_MMAP) { + mfc_debug(2, "Only V4L2_MEMORY_MMAP is supported\n"); + return -EINVAL; + } + + if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + return reqbufs_output(dev, ctx, reqbufs); + } else if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + return reqbufs_capture(dev, ctx, reqbufs); + } else { + mfc_err("Invalid type requested\n"); + return -EINVAL; + } +} + +/* Query buffer */ +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + int ret; + int i; + + if (buf->memory != V4L2_MEMORY_MMAP) { + mfc_err("Only mmapped buffers can be used\n"); + return -EINVAL; + } + mfc_debug(2, "State: %d, buf->type: %d\n", ctx->state, buf->type); + if (ctx->state == MFCINST_GOT_INST && + buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = vb2_querybuf(&ctx->vq_src, buf); + } else if (ctx->state == MFCINST_RUNNING && + buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = vb2_querybuf(&ctx->vq_dst, buf); + for (i = 0; i < buf->length; i++) + buf->m.planes[i].m.mem_offset += DST_QUEUE_OFF_BASE; + } else { + mfc_err("vidioc_querybuf called in an inappropriate state\n"); + ret = -EINVAL; + } + mfc_debug_leave(); + return ret; +} + +/* Queue a buffer */ +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (ctx->state == MFCINST_ERROR) { + mfc_err("Call on QBUF after unrecoverable error\n"); + return -EIO; + } + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return vb2_qbuf(&ctx->vq_src, NULL, buf); + else if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return vb2_qbuf(&ctx->vq_dst, NULL, buf); + return -EINVAL; +} + +/* Dequeue a buffer */ +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + const struct v4l2_event ev = { + .type = V4L2_EVENT_EOS + }; + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + int ret; + + if (ctx->state == MFCINST_ERROR) { + mfc_err_limited("Call on DQBUF after unrecoverable error\n"); + return -EIO; + } + + switch (buf->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return vb2_dqbuf(&ctx->vq_src, buf, file->f_flags & O_NONBLOCK); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + ret = vb2_dqbuf(&ctx->vq_dst, buf, file->f_flags & O_NONBLOCK); + if (ret) + return ret; + + if (ctx->state == MFCINST_FINISHED && + (ctx->dst_bufs[buf->index].flags & MFC_BUF_FLAG_EOS)) + v4l2_event_queue_fh(&ctx->fh, &ev); + return 0; + default: + return -EINVAL; + } +} + +/* Export DMA buffer */ +static int vidioc_expbuf(struct file *file, void *priv, + struct v4l2_exportbuffer *eb) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (eb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return vb2_expbuf(&ctx->vq_src, eb); + if (eb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return vb2_expbuf(&ctx->vq_dst, eb); + return -EINVAL; +} + +/* Stream on */ +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + int ret = -EINVAL; + + mfc_debug_enter(); + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + ret = vb2_streamon(&ctx->vq_src, type); + else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + ret = vb2_streamon(&ctx->vq_dst, type); + mfc_debug_leave(); + return ret; +} + +/* Stream off, which equals to a pause */ +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return vb2_streamoff(&ctx->vq_src, type); + else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return vb2_streamoff(&ctx->vq_dst, type); + return -EINVAL; +} + +/* Set controls - v4l2 control framework */ +static int s5p_mfc_dec_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5p_mfc_ctx *ctx = ctrl_to_ctx(ctrl); + + switch (ctrl->id) { + case V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY: + case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY: + ctx->display_delay = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_DECODER_H264_DISPLAY_DELAY_ENABLE: + case V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE: + ctx->display_delay_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: + ctx->loop_filter_mpeg4 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_DECODER_SLICE_INTERFACE: + ctx->slice_interface = ctrl->val; + break; + default: + mfc_err("Invalid control 0x%08x\n", ctrl->id); + return -EINVAL; + } + return 0; +} + +static int s5p_mfc_dec_g_v_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5p_mfc_ctx *ctx = ctrl_to_ctx(ctrl); + struct s5p_mfc_dev *dev = ctx->dev; + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE: + if (ctx->state >= MFCINST_HEAD_PARSED && + ctx->state < MFCINST_ABORT) { + ctrl->val = ctx->pb_count; + break; + } else if (ctx->state != MFCINST_INIT && + ctx->state != MFCINST_RES_CHANGE_END) { + v4l2_err(&dev->v4l2_dev, "Decoding not initialised\n"); + return -EINVAL; + } + /* Should wait for the header to be parsed */ + s5p_mfc_wait_for_done_ctx(ctx, + S5P_MFC_R2H_CMD_SEQ_DONE_RET, 0); + if (ctx->state >= MFCINST_HEAD_PARSED && + ctx->state < MFCINST_ABORT) { + ctrl->val = ctx->pb_count; + } else { + v4l2_err(&dev->v4l2_dev, "Decoding not initialised\n"); + return -EINVAL; + } + break; + } + return 0; +} + + +static const struct v4l2_ctrl_ops s5p_mfc_dec_ctrl_ops = { + .s_ctrl = s5p_mfc_dec_s_ctrl, + .g_volatile_ctrl = s5p_mfc_dec_g_v_ctrl, +}; + +/* Get compose information */ +static int vidioc_g_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + struct s5p_mfc_dev *dev = ctx->dev; + u32 left, right, top, bottom; + u32 width, height; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if (ctx->state != MFCINST_HEAD_PARSED && + ctx->state != MFCINST_RUNNING && + ctx->state != MFCINST_FINISHING && + ctx->state != MFCINST_FINISHED) { + mfc_err("Can not get compose information\n"); + return -EINVAL; + } + if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_H264) { + left = s5p_mfc_hw_call(dev->mfc_ops, get_crop_info_h, ctx); + right = left >> S5P_FIMV_SHARED_CROP_RIGHT_SHIFT; + left = left & S5P_FIMV_SHARED_CROP_LEFT_MASK; + top = s5p_mfc_hw_call(dev->mfc_ops, get_crop_info_v, ctx); + bottom = top >> S5P_FIMV_SHARED_CROP_BOTTOM_SHIFT; + top = top & S5P_FIMV_SHARED_CROP_TOP_MASK; + width = ctx->img_width - left - right; + height = ctx->img_height - top - bottom; + mfc_debug(2, "Composing info [h264]: l=%d t=%d w=%d h=%d (r=%d b=%d fw=%d fh=%d\n", + left, top, s->r.width, s->r.height, right, bottom, + ctx->buf_width, ctx->buf_height); + } else { + left = 0; + top = 0; + width = ctx->img_width; + height = ctx->img_height; + mfc_debug(2, "Composing info: w=%d h=%d fw=%d fh=%d\n", + s->r.width, s->r.height, ctx->buf_width, + ctx->buf_height); + } + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + s->r.left = left; + s->r.top = top; + s->r.width = width; + s->r.height = height; + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_decoder_cmd(struct file *file, void *priv, + struct v4l2_decoder_cmd *cmd) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *buf; + unsigned long flags; + + switch (cmd->cmd) { + case V4L2_DEC_CMD_STOP: + if (cmd->flags != 0) + return -EINVAL; + + if (!vb2_is_streaming(&ctx->vq_src)) + return -EINVAL; + + spin_lock_irqsave(&dev->irqlock, flags); + if (list_empty(&ctx->src_queue)) { + mfc_err("EOS: empty src queue, entering finishing state"); + ctx->state = MFCINST_FINISHING; + if (s5p_mfc_ctx_ready(ctx)) + set_work_bit_irqsave(ctx); + spin_unlock_irqrestore(&dev->irqlock, flags); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + } else { + mfc_err("EOS: marking last buffer of stream"); + buf = list_entry(ctx->src_queue.prev, + struct s5p_mfc_buf, list); + if (buf->flags & MFC_BUF_FLAG_USED) + ctx->state = MFCINST_FINISHING; + else + buf->flags |= MFC_BUF_FLAG_EOS; + spin_unlock_irqrestore(&dev->irqlock, flags); + } + break; + default: + return -EINVAL; + } + return 0; +} + +static int vidioc_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + case V4L2_EVENT_SOURCE_CHANGE: + return v4l2_src_change_event_subscribe(fh, sub); + default: + return -EINVAL; + } +} + + +/* v4l2_ioctl_ops */ +static const struct v4l2_ioctl_ops s5p_mfc_dec_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt, + .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_expbuf = vidioc_expbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_g_selection = vidioc_g_selection, + .vidioc_decoder_cmd = vidioc_decoder_cmd, + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int s5p_mfc_queue_setup(struct vb2_queue *vq, + unsigned int *buf_count, + unsigned int *plane_count, unsigned int psize[], + struct device *alloc_devs[]) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv); + struct s5p_mfc_dev *dev = ctx->dev; + + /* Video output for decoding (source) + * this can be set after getting an instance */ + if (ctx->state == MFCINST_INIT && + vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + /* A single plane is required for input */ + *plane_count = 1; + if (*buf_count < 1) + *buf_count = 1; + if (*buf_count > MFC_MAX_BUFFERS) + *buf_count = MFC_MAX_BUFFERS; + /* Video capture for decoding (destination) + * this can be set after the header was parsed */ + } else if (ctx->state == MFCINST_HEAD_PARSED && + vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* Output plane count is 2 - one for Y and one for CbCr */ + *plane_count = 2; + /* Setup buffer count */ + if (*buf_count < ctx->pb_count) + *buf_count = ctx->pb_count; + if (*buf_count > ctx->pb_count + MFC_MAX_EXTRA_DPB) + *buf_count = ctx->pb_count + MFC_MAX_EXTRA_DPB; + if (*buf_count > MFC_MAX_BUFFERS) + *buf_count = MFC_MAX_BUFFERS; + } else { + mfc_err("State seems invalid. State = %d, vq->type = %d\n", + ctx->state, vq->type); + return -EINVAL; + } + mfc_debug(2, "Buffer count=%d, plane count=%d\n", + *buf_count, *plane_count); + if (ctx->state == MFCINST_HEAD_PARSED && + vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + psize[0] = ctx->luma_size; + psize[1] = ctx->chroma_size; + + if (IS_MFCV6_PLUS(dev)) + alloc_devs[0] = ctx->dev->mem_dev[BANK_L_CTX]; + else + alloc_devs[0] = ctx->dev->mem_dev[BANK_R_CTX]; + alloc_devs[1] = ctx->dev->mem_dev[BANK_L_CTX]; + } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + ctx->state == MFCINST_INIT) { + psize[0] = ctx->dec_src_buf_size; + alloc_devs[0] = ctx->dev->mem_dev[BANK_L_CTX]; + } else { + mfc_err("This video node is dedicated to decoding. Decoding not initialized\n"); + return -EINVAL; + } + return 0; +} + +static int s5p_mfc_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv); + unsigned int i; + + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (ctx->capture_state == QUEUE_BUFS_MMAPED) + return 0; + for (i = 0; i < ctx->dst_fmt->num_planes; i++) { + if (IS_ERR_OR_NULL(ERR_PTR( + vb2_dma_contig_plane_dma_addr(vb, i)))) { + mfc_err("Plane mem not allocated\n"); + return -EINVAL; + } + } + if (vb2_plane_size(vb, 0) < ctx->luma_size || + vb2_plane_size(vb, 1) < ctx->chroma_size) { + mfc_err("Plane buffer (CAPTURE) is too small\n"); + return -EINVAL; + } + i = vb->index; + ctx->dst_bufs[i].b = vbuf; + ctx->dst_bufs[i].cookie.raw.luma = + vb2_dma_contig_plane_dma_addr(vb, 0); + ctx->dst_bufs[i].cookie.raw.chroma = + vb2_dma_contig_plane_dma_addr(vb, 1); + ctx->dst_bufs_cnt++; + } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (IS_ERR_OR_NULL(ERR_PTR( + vb2_dma_contig_plane_dma_addr(vb, 0)))) { + mfc_err("Plane memory not allocated\n"); + return -EINVAL; + } + if (vb2_plane_size(vb, 0) < ctx->dec_src_buf_size) { + mfc_err("Plane buffer (OUTPUT) is too small\n"); + return -EINVAL; + } + + i = vb->index; + ctx->src_bufs[i].b = vbuf; + ctx->src_bufs[i].cookie.stream = + vb2_dma_contig_plane_dma_addr(vb, 0); + ctx->src_bufs_cnt++; + } else { + mfc_err("s5p_mfc_buf_init: unknown queue type\n"); + return -EINVAL; + } + return 0; +} + +static int s5p_mfc_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv); + struct s5p_mfc_dev *dev = ctx->dev; + + v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + if (ctx->state == MFCINST_FINISHING || + ctx->state == MFCINST_FINISHED) + ctx->state = MFCINST_RUNNING; + /* If context is ready then dev = work->data;schedule it to run */ + if (s5p_mfc_ctx_ready(ctx)) + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + return 0; +} + +static void s5p_mfc_stop_streaming(struct vb2_queue *q) +{ + unsigned long flags; + struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv); + struct s5p_mfc_dev *dev = ctx->dev; + int aborted = 0; + + spin_lock_irqsave(&dev->irqlock, flags); + if ((ctx->state == MFCINST_FINISHING || + ctx->state == MFCINST_RUNNING) && + dev->curr_ctx == ctx->num && dev->hw_lock) { + ctx->state = MFCINST_ABORT; + spin_unlock_irqrestore(&dev->irqlock, flags); + s5p_mfc_wait_for_done_ctx(ctx, + S5P_MFC_R2H_CMD_FRAME_DONE_RET, 0); + aborted = 1; + spin_lock_irqsave(&dev->irqlock, flags); + } + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst); + INIT_LIST_HEAD(&ctx->dst_queue); + ctx->dst_queue_cnt = 0; + ctx->dpb_flush_flag = 1; + ctx->dec_dst_flag = 0; + if (IS_MFCV6_PLUS(dev) && (ctx->state == MFCINST_RUNNING)) { + ctx->state = MFCINST_FLUSH; + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + spin_unlock_irqrestore(&dev->irqlock, flags); + if (s5p_mfc_wait_for_done_ctx(ctx, + S5P_MFC_R2H_CMD_DPB_FLUSH_RET, 0)) + mfc_err("Err flushing buffers\n"); + spin_lock_irqsave(&dev->irqlock, flags); + } + } else if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src); + INIT_LIST_HEAD(&ctx->src_queue); + ctx->src_queue_cnt = 0; + } + if (aborted) + ctx->state = MFCINST_RUNNING; + spin_unlock_irqrestore(&dev->irqlock, flags); +} + + +static void s5p_mfc_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv); + struct s5p_mfc_dev *dev = ctx->dev; + unsigned long flags; + struct s5p_mfc_buf *mfc_buf; + + if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + mfc_buf = &ctx->src_bufs[vb->index]; + mfc_buf->flags &= ~MFC_BUF_FLAG_USED; + spin_lock_irqsave(&dev->irqlock, flags); + list_add_tail(&mfc_buf->list, &ctx->src_queue); + ctx->src_queue_cnt++; + spin_unlock_irqrestore(&dev->irqlock, flags); + } else if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + mfc_buf = &ctx->dst_bufs[vb->index]; + mfc_buf->flags &= ~MFC_BUF_FLAG_USED; + /* Mark destination as available for use by MFC */ + spin_lock_irqsave(&dev->irqlock, flags); + set_bit(vb->index, &ctx->dec_dst_flag); + list_add_tail(&mfc_buf->list, &ctx->dst_queue); + ctx->dst_queue_cnt++; + spin_unlock_irqrestore(&dev->irqlock, flags); + } else { + mfc_err("Unsupported buffer type (%d)\n", vq->type); + } + if (s5p_mfc_ctx_ready(ctx)) + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); +} + +static struct vb2_ops s5p_mfc_dec_qops = { + .queue_setup = s5p_mfc_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_init = s5p_mfc_buf_init, + .start_streaming = s5p_mfc_start_streaming, + .stop_streaming = s5p_mfc_stop_streaming, + .buf_queue = s5p_mfc_buf_queue, +}; + +const struct s5p_mfc_codec_ops *get_dec_codec_ops(void) +{ + return &decoder_codec_ops; +} + +struct vb2_ops *get_dec_queue_ops(void) +{ + return &s5p_mfc_dec_qops; +} + +const struct v4l2_ioctl_ops *get_dec_v4l2_ioctl_ops(void) +{ + return &s5p_mfc_dec_ioctl_ops; +} + +#define IS_MFC51_PRIV(x) ((V4L2_CTRL_ID2WHICH(x) == V4L2_CTRL_CLASS_CODEC) \ + && V4L2_CTRL_DRIVER_PRIV(x)) + +int s5p_mfc_dec_ctrls_setup(struct s5p_mfc_ctx *ctx) +{ + struct v4l2_ctrl_config cfg; + int i; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, NUM_CTRLS); + if (ctx->ctrl_handler.error) { + mfc_err("v4l2_ctrl_handler_init failed\n"); + return ctx->ctrl_handler.error; + } + + for (i = 0; i < NUM_CTRLS; i++) { + if (IS_MFC51_PRIV(controls[i].id)) { + memset(&cfg, 0, sizeof(struct v4l2_ctrl_config)); + cfg.ops = &s5p_mfc_dec_ctrl_ops; + cfg.id = controls[i].id; + cfg.min = controls[i].minimum; + cfg.max = controls[i].maximum; + cfg.def = controls[i].default_value; + cfg.name = controls[i].name; + cfg.type = controls[i].type; + + cfg.step = controls[i].step; + cfg.menu_skip_mask = 0; + + ctx->ctrls[i] = v4l2_ctrl_new_custom(&ctx->ctrl_handler, + &cfg, NULL); + } else { + ctx->ctrls[i] = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &s5p_mfc_dec_ctrl_ops, + controls[i].id, controls[i].minimum, + controls[i].maximum, controls[i].step, + controls[i].default_value); + } + if (ctx->ctrl_handler.error) { + mfc_err("Adding control (%d) failed\n", i); + return ctx->ctrl_handler.error; + } + if (controls[i].is_volatile && ctx->ctrls[i]) + ctx->ctrls[i]->flags |= V4L2_CTRL_FLAG_VOLATILE; + } + return 0; +} + +void s5p_mfc_dec_ctrls_delete(struct s5p_mfc_ctx *ctx) +{ + int i; + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + for (i = 0; i < NUM_CTRLS; i++) + ctx->ctrls[i] = NULL; +} + +void s5p_mfc_dec_init(struct s5p_mfc_ctx *ctx) +{ + struct v4l2_format f; + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + ctx->src_fmt = find_format(&f, MFC_FMT_DEC); + if (IS_MFCV8_PLUS(ctx->dev)) + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M; + else if (IS_MFCV6_PLUS(ctx->dev)) + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12MT_16X16; + else + f.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12MT; + ctx->dst_fmt = find_format(&f, MFC_FMT_RAW); + mfc_debug(2, "Default src_fmt is %p, dest_fmt is %p\n", + ctx->src_fmt, ctx->dst_fmt); +} + diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h new file mode 100644 index 000000000..0c52ab46c --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_dec.h + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_DEC_H_ +#define S5P_MFC_DEC_H_ + +const struct s5p_mfc_codec_ops *get_dec_codec_ops(void); +struct vb2_ops *get_dec_queue_ops(void); +const struct v4l2_ioctl_ops *get_dec_v4l2_ioctl_ops(void); +struct s5p_mfc_fmt *get_dec_def_fmt(bool src); +int s5p_mfc_dec_ctrls_setup(struct s5p_mfc_ctx *ctx); +void s5p_mfc_dec_ctrls_delete(struct s5p_mfc_ctx *ctx); +void s5p_mfc_dec_init(struct s5p_mfc_ctx *ctx); + +#endif /* S5P_MFC_DEC_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c new file mode 100644 index 000000000..f62703ceb --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c @@ -0,0 +1,2703 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.c + * + * Copyright (c) 2010-2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + * + * Jeongtae Park + * Kamil Debski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "s5p_mfc_common.h" +#include "s5p_mfc_ctrl.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_enc.h" +#include "s5p_mfc_intr.h" +#include "s5p_mfc_opr.h" + +#define DEF_SRC_FMT_ENC V4L2_PIX_FMT_NV12M +#define DEF_DST_FMT_ENC V4L2_PIX_FMT_H264 + +static struct s5p_mfc_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_NV12MT_16X16, + .codec_mode = S5P_MFC_CODEC_NONE, + .type = MFC_FMT_RAW, + .num_planes = 2, + .versions = MFC_V6_BIT | MFC_V7_BIT, + }, + { + .fourcc = V4L2_PIX_FMT_NV12MT, + .codec_mode = S5P_MFC_CODEC_NONE, + .type = MFC_FMT_RAW, + .num_planes = 2, + .versions = MFC_V5_BIT, + }, + { + .fourcc = V4L2_PIX_FMT_NV12M, + .codec_mode = S5P_MFC_CODEC_NONE, + .type = MFC_FMT_RAW, + .num_planes = 2, + .versions = MFC_V5PLUS_BITS, + }, + { + .fourcc = V4L2_PIX_FMT_NV21M, + .codec_mode = S5P_MFC_CODEC_NONE, + .type = MFC_FMT_RAW, + .num_planes = 2, + .versions = MFC_V6PLUS_BITS, + }, + { + .fourcc = V4L2_PIX_FMT_H264, + .codec_mode = S5P_MFC_CODEC_H264_ENC, + .type = MFC_FMT_ENC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + }, + { + .fourcc = V4L2_PIX_FMT_MPEG4, + .codec_mode = S5P_MFC_CODEC_MPEG4_ENC, + .type = MFC_FMT_ENC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + }, + { + .fourcc = V4L2_PIX_FMT_H263, + .codec_mode = S5P_MFC_CODEC_H263_ENC, + .type = MFC_FMT_ENC, + .num_planes = 1, + .versions = MFC_V5PLUS_BITS, + }, + { + .fourcc = V4L2_PIX_FMT_VP8, + .codec_mode = S5P_MFC_CODEC_VP8_ENC, + .type = MFC_FMT_ENC, + .num_planes = 1, + .versions = MFC_V7PLUS_BITS, + }, + { + .fourcc = V4L2_PIX_FMT_HEVC, + .codec_mode = S5P_FIMV_CODEC_HEVC_ENC, + .type = MFC_FMT_ENC, + .num_planes = 1, + .versions = MFC_V10_BIT, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) +static struct s5p_mfc_fmt *find_format(struct v4l2_format *f, unsigned int t) +{ + unsigned int i; + + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].fourcc == f->fmt.pix_mp.pixelformat && + formats[i].type == t) + return &formats[i]; + } + return NULL; +} + +static struct mfc_control controls[] = { + { + .id = V4L2_CID_MPEG_VIDEO_GOP_SIZE, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 12, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE, + .maximum = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES, + .default_value = V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_SINGLE, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1900, + .maximum = (1 << 30) - 1, + .step = 1, + .default_value = 1900, + }, + { + .id = V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_PADDING, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Padding Control Enable", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_PADDING_YUV, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Padding Color YUV Value", + .minimum = 0, + .maximum = (1 << 25) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_BITRATE, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = (1 << 30) - 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_RC_REACTION_COEFF, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Rate Control Reaction Coeff.", + .minimum = 1, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Force frame type", + .minimum = V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_DISABLED, + .maximum = V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_NOT_CODED, + .default_value = V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_DISABLED, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, + .type = V4L2_CTRL_TYPE_BUTTON, + .minimum = 0, + .maximum = 0, + .step = 0, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VBV_SIZE, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MV_H_SEARCH_RANGE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Horizontal MV Search Range", + .minimum = 16, + .maximum = 128, + .step = 16, + .default_value = 32, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MV_V_SEARCH_RANGE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Vertical MV Search Range", + .minimum = 16, + .maximum = 128, + .step = 16, + .default_value = 32, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEADER_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE, + .maximum = V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME, + .default_value = V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Frame Skip Enable", + .minimum = V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_DISABLED, + .maximum = V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT, + .menu_skip_mask = 0, + .default_value = V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_DISABLED, + }, + { + .id = V4L2_CID_MPEG_VIDEO_FRAME_SKIP_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .maximum = V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT, + .default_value = V4L2_MPEG_VIDEO_FRAME_SKIP_MODE_DISABLED, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_RC_FIXED_TARGET_BIT, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Fixed Target Bit Enable", + .minimum = 0, + .maximum = 1, + .default_value = 0, + .step = 1, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_B_FRAMES, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 2, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_PROFILE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .maximum = V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH, + .default_value = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, + .menu_skip_mask = ~( + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) + ), + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LEVEL, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, + .maximum = V4L2_MPEG_VIDEO_H264_LEVEL_4_0, + .default_value = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0, + .maximum = V4L2_MPEG_VIDEO_MPEG4_LEVEL_5, + .default_value = V4L2_MPEG_VIDEO_MPEG4_LEVEL_0, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED, + .maximum = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY, + .default_value = V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = -6, + .maximum = 6, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = -6, + .maximum = 6, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC, + .maximum = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC, + .default_value = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_NUM_REF_PIC_FOR_P, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "The Number of Ref. Pic for P", + .minimum = 1, + .maximum = 2, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_MIN_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_MAX_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 51, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "H263 I-Frame QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H263_MIN_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "H263 Minimum QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H263_MAX_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "H263 Maximum QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 31, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "H263 P frame QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "H263 B frame QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "MPEG4 I-Frame QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "MPEG4 Minimum QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "MPEG4 Maximum QP value", + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 51, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "MPEG4 P frame QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "MPEG4 B frame QP value", + .minimum = 1, + .maximum = 31, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_DARK, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "H264 Dark Reg Adaptive RC", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_SMOOTH, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "H264 Smooth Reg Adaptive RC", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_STATIC, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "H264 Static Reg Adaptive RC", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_ACTIVITY, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "H264 Activity Reg Adaptive RC", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED, + .maximum = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED, + .default_value = V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_GOP_CLOSURE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE, + .maximum = V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE, + .default_value = V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_MPEG4_QPEL, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS, + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .maximum = V4L2_CID_MPEG_VIDEO_VPX_8_PARTITIONS, + .default_value = V4L2_CID_MPEG_VIDEO_VPX_1_PARTITION, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_IMD_DISABLE_4X4, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES, + .type = V4L2_CTRL_TYPE_INTEGER_MENU, + .maximum = V4L2_CID_MPEG_VIDEO_VPX_2_REF_FRAME, + .default_value = V4L2_CID_MPEG_VIDEO_VPX_1_REF_FRAME, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_FILTER_LEVEL, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 63, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_FILTER_SHARPNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 7, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_REF_PERIOD, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_PREV, + .maximum = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_REF_PERIOD, + .default_value = V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_USE_PREV, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_MAX_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 127, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_MIN_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 11, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_I_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 10, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VPX_P_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 127, + .step = 1, + .default_value = 10, + }, + { + .id = V4L2_CID_MPEG_VIDEO_VP8_PROFILE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_VP8_PROFILE_0, + .maximum = V4L2_MPEG_VIDEO_VP8_PROFILE_3, + .default_value = V4L2_MPEG_VIDEO_VP8_PROFILE_0, + .menu_skip_mask = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "HEVC I Frame QP Value", + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "HEVC P Frame QP Value", + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_PROFILE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + .maximum = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE, + .step = 1, + .default_value = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_LEVEL, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEVC_LEVEL_1, + .maximum = V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2, + .step = 1, + .default_value = V4L2_MPEG_VIDEO_HEVC_LEVEL_1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_TIER, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEVC_TIER_MAIN, + .maximum = V4L2_MPEG_VIDEO_HEVC_TIER_HIGH, + .step = 1, + .default_value = V4L2_MPEG_VIDEO_HEVC_TIER_MAIN, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_FRAME_RATE_RESOLUTION, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_MAX_PARTITION_DEPTH, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_REF_NUMBER_FOR_PFRAMES, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 1, + .maximum = 2, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_TYPE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEVC_REFRESH_NONE, + .maximum = V4L2_MPEG_VIDEO_HEVC_REFRESH_IDR, + .step = 1, + .default_value = V4L2_MPEG_VIDEO_HEVC_REFRESH_NONE, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_CONST_INTRA_PRED, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_LOSSLESS_CU, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_WAVEFRONT, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED, + .maximum = V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY, + .step = 1, + .default_value = V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_QP, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_TYPE, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEVC_HIERARCHICAL_CODING_B, + .maximum = V4L2_MPEG_VIDEO_HEVC_HIERARCHICAL_CODING_P, + .step = 1, + .default_value = V4L2_MPEG_VIDEO_HEVC_HIERARCHICAL_CODING_B, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_LAYER, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 6, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_QP, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 51, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_BR, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = INT_MIN, + .maximum = INT_MAX, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_BR, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = INT_MIN, + .maximum = INT_MAX, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_BR, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = INT_MIN, + .maximum = INT_MAX, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_BR, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = INT_MIN, + .maximum = INT_MAX, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_BR, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = INT_MIN, + .maximum = INT_MAX, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_BR, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = INT_MIN, + .maximum = INT_MAX, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_BR, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = INT_MIN, + .maximum = INT_MAX, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_GENERAL_PB, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_TEMPORAL_ID, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_STRONG_SMOOTHING, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_INTRA_PU_SPLIT, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_MAX_NUM_MERGE_MV_MINUS1, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 4, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_WITHOUT_STARTCODE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = (1 << 16) - 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_LF_BETA_OFFSET_DIV2, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = -6, + .maximum = 6, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_LF_TC_OFFSET_DIV2, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = -6, + .maximum = 6, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_HEVC_SIZE_OF_LENGTH_FIELD, + .type = V4L2_CTRL_TYPE_MENU, + .minimum = V4L2_MPEG_VIDEO_HEVC_SIZE_0, + .maximum = V4L2_MPEG_VIDEO_HEVC_SIZE_4, + .step = 1, + .default_value = V4L2_MPEG_VIDEO_HEVC_SIZE_0, + }, + { + .id = V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR, + .type = V4L2_CTRL_TYPE_INTEGER, + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 0, + }, + { + .id = V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Minimum number of output bufs", + .minimum = 1, + .maximum = 32, + .step = 1, + .default_value = 1, + .is_volatile = 1, + }, +}; + +#define NUM_CTRLS ARRAY_SIZE(controls) +static const char * const *mfc51_get_menu(u32 id) +{ + static const char * const mfc51_video_frame_skip[] = { + "Disabled", + "Level Limit", + "VBV/CPB Limit", + NULL, + }; + static const char * const mfc51_video_force_frame[] = { + "Disabled", + "I Frame", + "Not Coded", + NULL, + }; + switch (id) { + case V4L2_CID_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE: + return mfc51_video_frame_skip; + case V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE: + return mfc51_video_force_frame; + } + return NULL; +} + +static int s5p_mfc_ctx_ready(struct s5p_mfc_ctx *ctx) +{ + mfc_debug(2, "src=%d, dst=%d, state=%d\n", + ctx->src_queue_cnt, ctx->dst_queue_cnt, ctx->state); + /* context is ready to make header */ + if (ctx->state == MFCINST_GOT_INST && ctx->dst_queue_cnt >= 1) + return 1; + /* context is ready to encode a frame */ + if ((ctx->state == MFCINST_RUNNING || + ctx->state == MFCINST_HEAD_PRODUCED) && + ctx->src_queue_cnt >= 1 && ctx->dst_queue_cnt >= 1) + return 1; + /* context is ready to encode remaining frames */ + if (ctx->state == MFCINST_FINISHING && + ctx->dst_queue_cnt >= 1) + return 1; + mfc_debug(2, "ctx is not ready\n"); + return 0; +} + +static void cleanup_ref_queue(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_buf *mb_entry; + + /* move buffers in ref queue to src queue */ + while (!list_empty(&ctx->ref_queue)) { + mb_entry = list_entry((&ctx->ref_queue)->next, + struct s5p_mfc_buf, list); + list_del(&mb_entry->list); + ctx->ref_queue_cnt--; + list_add_tail(&mb_entry->list, &ctx->src_queue); + ctx->src_queue_cnt++; + } + mfc_debug(2, "enc src count: %d, enc ref count: %d\n", + ctx->src_queue_cnt, ctx->ref_queue_cnt); + INIT_LIST_HEAD(&ctx->ref_queue); + ctx->ref_queue_cnt = 0; +} + +static int enc_pre_seq_start(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *dst_mb; + unsigned long dst_addr; + unsigned int dst_size; + + dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list); + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0); + dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0); + s5p_mfc_hw_call(dev->mfc_ops, set_enc_stream_buffer, ctx, dst_addr, + dst_size); + return 0; +} + +static int enc_post_seq_start(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_buf *dst_mb; + unsigned int enc_pb_count; + + if (p->seq_hdr_mode == V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE) { + if (!list_empty(&ctx->dst_queue)) { + dst_mb = list_entry(ctx->dst_queue.next, + struct s5p_mfc_buf, list); + list_del(&dst_mb->list); + ctx->dst_queue_cnt--; + vb2_set_plane_payload(&dst_mb->b->vb2_buf, 0, + s5p_mfc_hw_call(dev->mfc_ops, get_enc_strm_size, + dev)); + vb2_buffer_done(&dst_mb->b->vb2_buf, + VB2_BUF_STATE_DONE); + } + } + + if (!IS_MFCV6_PLUS(dev)) { + ctx->state = MFCINST_RUNNING; + if (s5p_mfc_ctx_ready(ctx)) + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + } else { + enc_pb_count = s5p_mfc_hw_call(dev->mfc_ops, + get_enc_dpb_count, dev); + if (ctx->pb_count < enc_pb_count) + ctx->pb_count = enc_pb_count; + if (FW_HAS_E_MIN_SCRATCH_BUF(dev)) { + ctx->scratch_buf_size = s5p_mfc_hw_call(dev->mfc_ops, + get_e_min_scratch_buf_size, dev); + ctx->bank1.size += ctx->scratch_buf_size; + } + ctx->state = MFCINST_HEAD_PRODUCED; + } + + return 0; +} + +static int enc_pre_frame_start(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *dst_mb; + struct s5p_mfc_buf *src_mb; + unsigned long src_y_addr, src_c_addr, dst_addr; + unsigned int dst_size; + + src_mb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); + src_y_addr = vb2_dma_contig_plane_dma_addr(&src_mb->b->vb2_buf, 0); + src_c_addr = vb2_dma_contig_plane_dma_addr(&src_mb->b->vb2_buf, 1); + s5p_mfc_hw_call(dev->mfc_ops, set_enc_frame_buffer, ctx, + src_y_addr, src_c_addr); + + dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list); + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0); + dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0); + s5p_mfc_hw_call(dev->mfc_ops, set_enc_stream_buffer, ctx, dst_addr, + dst_size); + + return 0; +} + +static int enc_post_frame_start(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *mb_entry; + unsigned long enc_y_addr = 0, enc_c_addr = 0; + unsigned long mb_y_addr, mb_c_addr; + int slice_type; + unsigned int strm_size; + bool src_ready; + + slice_type = s5p_mfc_hw_call(dev->mfc_ops, get_enc_slice_type, dev); + strm_size = s5p_mfc_hw_call(dev->mfc_ops, get_enc_strm_size, dev); + mfc_debug(2, "Encoded slice type: %d\n", slice_type); + mfc_debug(2, "Encoded stream size: %d\n", strm_size); + mfc_debug(2, "Display order: %d\n", + mfc_read(dev, S5P_FIMV_ENC_SI_PIC_CNT)); + if (slice_type >= 0) { + s5p_mfc_hw_call(dev->mfc_ops, get_enc_frame_buffer, ctx, + &enc_y_addr, &enc_c_addr); + list_for_each_entry(mb_entry, &ctx->src_queue, list) { + mb_y_addr = vb2_dma_contig_plane_dma_addr( + &mb_entry->b->vb2_buf, 0); + mb_c_addr = vb2_dma_contig_plane_dma_addr( + &mb_entry->b->vb2_buf, 1); + if ((enc_y_addr == mb_y_addr) && + (enc_c_addr == mb_c_addr)) { + list_del(&mb_entry->list); + ctx->src_queue_cnt--; + vb2_buffer_done(&mb_entry->b->vb2_buf, + VB2_BUF_STATE_DONE); + break; + } + } + list_for_each_entry(mb_entry, &ctx->ref_queue, list) { + mb_y_addr = vb2_dma_contig_plane_dma_addr( + &mb_entry->b->vb2_buf, 0); + mb_c_addr = vb2_dma_contig_plane_dma_addr( + &mb_entry->b->vb2_buf, 1); + if ((enc_y_addr == mb_y_addr) && + (enc_c_addr == mb_c_addr)) { + list_del(&mb_entry->list); + ctx->ref_queue_cnt--; + vb2_buffer_done(&mb_entry->b->vb2_buf, + VB2_BUF_STATE_DONE); + break; + } + } + } + if (ctx->src_queue_cnt > 0 && (ctx->state == MFCINST_RUNNING || + ctx->state == MFCINST_FINISHING)) { + mb_entry = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, + list); + if (mb_entry->flags & MFC_BUF_FLAG_USED) { + list_del(&mb_entry->list); + ctx->src_queue_cnt--; + list_add_tail(&mb_entry->list, &ctx->ref_queue); + ctx->ref_queue_cnt++; + } + } + mfc_debug(2, "enc src count: %d, enc ref count: %d\n", + ctx->src_queue_cnt, ctx->ref_queue_cnt); + if ((ctx->dst_queue_cnt > 0) && (strm_size > 0)) { + mb_entry = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, + list); + list_del(&mb_entry->list); + ctx->dst_queue_cnt--; + switch (slice_type) { + case S5P_FIMV_ENC_SI_SLICE_TYPE_I: + mb_entry->b->flags |= V4L2_BUF_FLAG_KEYFRAME; + break; + case S5P_FIMV_ENC_SI_SLICE_TYPE_P: + mb_entry->b->flags |= V4L2_BUF_FLAG_PFRAME; + break; + case S5P_FIMV_ENC_SI_SLICE_TYPE_B: + mb_entry->b->flags |= V4L2_BUF_FLAG_BFRAME; + break; + } + vb2_set_plane_payload(&mb_entry->b->vb2_buf, 0, strm_size); + vb2_buffer_done(&mb_entry->b->vb2_buf, VB2_BUF_STATE_DONE); + } + + src_ready = true; + if (ctx->state == MFCINST_RUNNING && ctx->src_queue_cnt == 0) + src_ready = false; + if (ctx->state == MFCINST_FINISHING && ctx->ref_queue_cnt == 0) + src_ready = false; + if (!src_ready || ctx->dst_queue_cnt == 0) + clear_work_bit(ctx); + + return 0; +} + +static const struct s5p_mfc_codec_ops encoder_codec_ops = { + .pre_seq_start = enc_pre_seq_start, + .post_seq_start = enc_post_seq_start, + .pre_frame_start = enc_pre_frame_start, + .post_frame_start = enc_post_frame_start, +}; + +/* Query capabilities of the device */ +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + + strscpy(cap->driver, S5P_MFC_NAME, sizeof(cap->driver)); + strscpy(cap->card, dev->vfd_enc->name, sizeof(cap->card)); + return 0; +} + +static int vidioc_enum_fmt(struct file *file, struct v4l2_fmtdesc *f, + bool out) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + int i, j = 0; + + for (i = 0; i < ARRAY_SIZE(formats); ++i) { + if (out && formats[i].type != MFC_FMT_RAW) + continue; + else if (!out && formats[i].type != MFC_FMT_ENC) + continue; + else if ((dev->variant->version_bit & formats[i].versions) == 0) + continue; + + if (j == f->index) { + f->pixelformat = formats[i].fourcc; + return 0; + } + ++j; + } + return -EINVAL; +} + +static int vidioc_enum_fmt_vid_cap(struct file *file, void *pirv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt(file, f, false); +} + +static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return vidioc_enum_fmt(file, f, true); +} + +static int vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp; + + mfc_debug(2, "f->type = %d ctx->state = %d\n", f->type, ctx->state); + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* This is run on output (encoder dest) */ + pix_fmt_mp->width = 0; + pix_fmt_mp->height = 0; + pix_fmt_mp->field = V4L2_FIELD_NONE; + pix_fmt_mp->pixelformat = ctx->dst_fmt->fourcc; + pix_fmt_mp->num_planes = ctx->dst_fmt->num_planes; + + pix_fmt_mp->plane_fmt[0].bytesperline = ctx->enc_dst_buf_size; + pix_fmt_mp->plane_fmt[0].sizeimage = ctx->enc_dst_buf_size; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + /* This is run on capture (encoder src) */ + pix_fmt_mp->width = ctx->img_width; + pix_fmt_mp->height = ctx->img_height; + + pix_fmt_mp->field = V4L2_FIELD_NONE; + pix_fmt_mp->pixelformat = ctx->src_fmt->fourcc; + pix_fmt_mp->num_planes = ctx->src_fmt->num_planes; + + pix_fmt_mp->plane_fmt[0].bytesperline = ctx->buf_width; + pix_fmt_mp->plane_fmt[0].sizeimage = ctx->luma_size; + pix_fmt_mp->plane_fmt[1].bytesperline = ctx->buf_width; + pix_fmt_mp->plane_fmt[1].sizeimage = ctx->chroma_size; + } else { + mfc_err("invalid buf type\n"); + return -EINVAL; + } + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + struct s5p_mfc_fmt *fmt; + struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + fmt = find_format(f, MFC_FMT_ENC); + if (!fmt) { + mfc_err("failed to try output format\n"); + return -EINVAL; + } + if ((dev->variant->version_bit & fmt->versions) == 0) { + mfc_err("Unsupported format by this MFC version.\n"); + return -EINVAL; + } + + pix_fmt_mp->plane_fmt[0].bytesperline = + pix_fmt_mp->plane_fmt[0].sizeimage; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + fmt = find_format(f, MFC_FMT_RAW); + if (!fmt) { + mfc_err("failed to try output format\n"); + return -EINVAL; + } + if ((dev->variant->version_bit & fmt->versions) == 0) { + mfc_err("Unsupported format by this MFC version.\n"); + return -EINVAL; + } + + v4l_bound_align_image(&pix_fmt_mp->width, 8, 1920, 1, + &pix_fmt_mp->height, 4, 1080, 1, 0); + } else { + mfc_err("invalid buf type\n"); + return -EINVAL; + } + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + struct v4l2_pix_format_mplane *pix_fmt_mp = &f->fmt.pix_mp; + int ret = 0; + + ret = vidioc_try_fmt(file, priv, f); + if (ret) + return ret; + if (ctx->vq_src.streaming || ctx->vq_dst.streaming) { + v4l2_err(&dev->v4l2_dev, "%s queue busy\n", __func__); + ret = -EBUSY; + goto out; + } + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + /* dst_fmt is validated by call to vidioc_try_fmt */ + ctx->dst_fmt = find_format(f, MFC_FMT_ENC); + ctx->state = MFCINST_INIT; + ctx->codec_mode = ctx->dst_fmt->codec_mode; + ctx->enc_dst_buf_size = pix_fmt_mp->plane_fmt[0].sizeimage; + pix_fmt_mp->plane_fmt[0].bytesperline = 0; + ctx->dst_bufs_cnt = 0; + ctx->capture_state = QUEUE_FREE; + ret = s5p_mfc_open_mfc_inst(dev, ctx); + } else if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + /* src_fmt is validated by call to vidioc_try_fmt */ + ctx->src_fmt = find_format(f, MFC_FMT_RAW); + ctx->img_width = pix_fmt_mp->width; + ctx->img_height = pix_fmt_mp->height; + mfc_debug(2, "codec number: %d\n", ctx->src_fmt->codec_mode); + mfc_debug(2, "fmt - w: %d, h: %d, ctx - w: %d, h: %d\n", + pix_fmt_mp->width, pix_fmt_mp->height, + ctx->img_width, ctx->img_height); + + s5p_mfc_hw_call(dev->mfc_ops, enc_calc_src_size, ctx); + pix_fmt_mp->plane_fmt[0].sizeimage = ctx->luma_size; + pix_fmt_mp->plane_fmt[0].bytesperline = ctx->buf_width; + pix_fmt_mp->plane_fmt[1].sizeimage = ctx->chroma_size; + pix_fmt_mp->plane_fmt[1].bytesperline = ctx->buf_width; + + ctx->src_bufs_cnt = 0; + ctx->output_state = QUEUE_FREE; + } else { + mfc_err("invalid buf type\n"); + ret = -EINVAL; + } +out: + mfc_debug_leave(); + return ret; +} + +static int vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *reqbufs) +{ + struct s5p_mfc_dev *dev = video_drvdata(file); + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + int ret = 0; + + /* if memory is not mmp or userptr return error */ + if ((reqbufs->memory != V4L2_MEMORY_MMAP) && + (reqbufs->memory != V4L2_MEMORY_USERPTR)) + return -EINVAL; + if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (reqbufs->count == 0) { + mfc_debug(2, "Freeing buffers\n"); + ret = vb2_reqbufs(&ctx->vq_dst, reqbufs); + s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, + ctx); + ctx->capture_state = QUEUE_FREE; + return ret; + } + if (ctx->capture_state != QUEUE_FREE) { + mfc_err("invalid capture state: %d\n", + ctx->capture_state); + return -EINVAL; + } + ret = vb2_reqbufs(&ctx->vq_dst, reqbufs); + if (ret != 0) { + mfc_err("error in vb2_reqbufs() for E(D)\n"); + return ret; + } + ctx->capture_state = QUEUE_BUFS_REQUESTED; + + ret = s5p_mfc_hw_call(ctx->dev->mfc_ops, + alloc_codec_buffers, ctx); + if (ret) { + mfc_err("Failed to allocate encoding buffers\n"); + reqbufs->count = 0; + ret = vb2_reqbufs(&ctx->vq_dst, reqbufs); + return -ENOMEM; + } + } else if (reqbufs->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (reqbufs->count == 0) { + mfc_debug(2, "Freeing buffers\n"); + ret = vb2_reqbufs(&ctx->vq_src, reqbufs); + s5p_mfc_hw_call(dev->mfc_ops, release_codec_buffers, + ctx); + ctx->output_state = QUEUE_FREE; + return ret; + } + if (ctx->output_state != QUEUE_FREE) { + mfc_err("invalid output state: %d\n", + ctx->output_state); + return -EINVAL; + } + + if (IS_MFCV6_PLUS(dev)) { + /* Check for min encoder buffers */ + if (ctx->pb_count && + (reqbufs->count < ctx->pb_count)) { + reqbufs->count = ctx->pb_count; + mfc_debug(2, "Minimum %d output buffers needed\n", + ctx->pb_count); + } else { + ctx->pb_count = reqbufs->count; + } + } + + ret = vb2_reqbufs(&ctx->vq_src, reqbufs); + if (ret != 0) { + mfc_err("error in vb2_reqbufs() for E(S)\n"); + return ret; + } + ctx->output_state = QUEUE_BUFS_REQUESTED; + } else { + mfc_err("invalid buf type\n"); + return -EINVAL; + } + return ret; +} + +static int vidioc_querybuf(struct file *file, void *priv, + struct v4l2_buffer *buf) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + int ret = 0; + + /* if memory is not mmp or userptr return error */ + if ((buf->memory != V4L2_MEMORY_MMAP) && + (buf->memory != V4L2_MEMORY_USERPTR)) + return -EINVAL; + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (ctx->state != MFCINST_GOT_INST) { + mfc_err("invalid context state: %d\n", ctx->state); + return -EINVAL; + } + ret = vb2_querybuf(&ctx->vq_dst, buf); + if (ret != 0) { + mfc_err("error in vb2_querybuf() for E(D)\n"); + return ret; + } + buf->m.planes[0].m.mem_offset += DST_QUEUE_OFF_BASE; + } else if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = vb2_querybuf(&ctx->vq_src, buf); + if (ret != 0) { + mfc_err("error in vb2_querybuf() for E(S)\n"); + return ret; + } + } else { + mfc_err("invalid buf type\n"); + return -EINVAL; + } + return ret; +} + +/* Queue a buffer */ +static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (ctx->state == MFCINST_ERROR) { + mfc_err("Call on QBUF after unrecoverable error\n"); + return -EIO; + } + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (ctx->state == MFCINST_FINISHING) { + mfc_err("Call on QBUF after EOS command\n"); + return -EIO; + } + return vb2_qbuf(&ctx->vq_src, NULL, buf); + } else if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + return vb2_qbuf(&ctx->vq_dst, NULL, buf); + } + return -EINVAL; +} + +/* Dequeue a buffer */ +static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + const struct v4l2_event ev = { + .type = V4L2_EVENT_EOS + }; + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + int ret; + + if (ctx->state == MFCINST_ERROR) { + mfc_err_limited("Call on DQBUF after unrecoverable error\n"); + return -EIO; + } + if (buf->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = vb2_dqbuf(&ctx->vq_src, buf, file->f_flags & O_NONBLOCK); + } else if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = vb2_dqbuf(&ctx->vq_dst, buf, file->f_flags & O_NONBLOCK); + if (ret == 0 && ctx->state == MFCINST_FINISHED + && list_empty(&ctx->vq_dst.done_list)) + v4l2_event_queue_fh(&ctx->fh, &ev); + } else { + ret = -EINVAL; + } + + return ret; +} + +/* Export DMA buffer */ +static int vidioc_expbuf(struct file *file, void *priv, + struct v4l2_exportbuffer *eb) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (eb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return vb2_expbuf(&ctx->vq_src, eb); + if (eb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return vb2_expbuf(&ctx->vq_dst, eb); + return -EINVAL; +} + +/* Stream on */ +static int vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return vb2_streamon(&ctx->vq_src, type); + else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return vb2_streamon(&ctx->vq_dst, type); + return -EINVAL; +} + +/* Stream off, which equals to a pause */ +static int vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return vb2_streamoff(&ctx->vq_src, type); + else if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return vb2_streamoff(&ctx->vq_dst, type); + return -EINVAL; +} + +static inline int h264_level(enum v4l2_mpeg_video_h264_level lvl) +{ + static unsigned int t[V4L2_MPEG_VIDEO_H264_LEVEL_4_0 + 1] = { + /* V4L2_MPEG_VIDEO_H264_LEVEL_1_0 */ 10, + /* V4L2_MPEG_VIDEO_H264_LEVEL_1B */ 9, + /* V4L2_MPEG_VIDEO_H264_LEVEL_1_1 */ 11, + /* V4L2_MPEG_VIDEO_H264_LEVEL_1_2 */ 12, + /* V4L2_MPEG_VIDEO_H264_LEVEL_1_3 */ 13, + /* V4L2_MPEG_VIDEO_H264_LEVEL_2_0 */ 20, + /* V4L2_MPEG_VIDEO_H264_LEVEL_2_1 */ 21, + /* V4L2_MPEG_VIDEO_H264_LEVEL_2_2 */ 22, + /* V4L2_MPEG_VIDEO_H264_LEVEL_3_0 */ 30, + /* V4L2_MPEG_VIDEO_H264_LEVEL_3_1 */ 31, + /* V4L2_MPEG_VIDEO_H264_LEVEL_3_2 */ 32, + /* V4L2_MPEG_VIDEO_H264_LEVEL_4_0 */ 40, + }; + return t[lvl]; +} + +static inline int mpeg4_level(enum v4l2_mpeg_video_mpeg4_level lvl) +{ + static unsigned int t[V4L2_MPEG_VIDEO_MPEG4_LEVEL_5 + 1] = { + /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_0 */ 0, + /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_0B */ 9, + /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_1 */ 1, + /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_2 */ 2, + /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_3 */ 3, + /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_3B */ 7, + /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_4 */ 4, + /* V4L2_MPEG_VIDEO_MPEG4_LEVEL_5 */ 5, + }; + return t[lvl]; +} + +static inline int hevc_level(enum v4l2_mpeg_video_hevc_level lvl) +{ + static unsigned int t[] = { + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_1 */ 10, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_2 */ 20, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1 */ 21, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_3 */ 30, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1 */ 31, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_4 */ 40, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1 */ 41, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_5 */ 50, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1 */ 51, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_5_2 */ 52, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_6 */ 60, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1 */ 61, + /* V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2 */ 62, + }; + return t[lvl]; +} + +static inline int vui_sar_idc(enum v4l2_mpeg_video_h264_vui_sar_idc sar) +{ + static unsigned int t[V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED + 1] = { + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_UNSPECIFIED */ 0, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_1x1 */ 1, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_12x11 */ 2, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_10x11 */ 3, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_16x11 */ 4, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_40x33 */ 5, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_24x11 */ 6, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_20x11 */ 7, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_32x11 */ 8, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_80x33 */ 9, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_18x11 */ 10, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_15x11 */ 11, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_64x33 */ 12, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_160x99 */ 13, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_4x3 */ 14, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_3x2 */ 15, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_2x1 */ 16, + /* V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_EXTENDED */ 255, + }; + return t[sar]; +} + +/* + * Update range of all HEVC quantization parameter controls that depend on the + * V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP, V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP controls. + */ +static void __enc_update_hevc_qp_ctrls_range(struct s5p_mfc_ctx *ctx, + int min, int max) +{ + static const int __hevc_qp_ctrls[] = { + V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP, + V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP, + V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP, + V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_QP, + V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_QP, + V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_QP, + V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_QP, + V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_QP, + V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_QP, + V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_QP, + }; + struct v4l2_ctrl *ctrl = NULL; + int i, j; + + for (i = 0; i < ARRAY_SIZE(__hevc_qp_ctrls); i++) { + for (j = 0; j < ARRAY_SIZE(ctx->ctrls); j++) { + if (ctx->ctrls[j]->id == __hevc_qp_ctrls[i]) { + ctrl = ctx->ctrls[j]; + break; + } + } + if (WARN_ON(!ctrl)) + break; + + __v4l2_ctrl_modify_range(ctrl, min, max, ctrl->step, min); + } +} + +static int s5p_mfc_enc_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5p_mfc_ctx *ctx = ctrl_to_ctx(ctrl); + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + int ret = 0; + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + p->gop_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MODE: + p->slice_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB: + p->slice_mb = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES: + p->slice_bit = ctrl->val * 8; + break; + case V4L2_CID_MPEG_VIDEO_CYCLIC_INTRA_REFRESH_MB: + p->intra_refresh_mb = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_PADDING: + p->pad = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_PADDING_YUV: + p->pad_luma = (ctrl->val >> 16) & 0xff; + p->pad_cb = (ctrl->val >> 8) & 0xff; + p->pad_cr = (ctrl->val >> 0) & 0xff; + break; + case V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE: + p->rc_frame = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + p->rc_bitrate = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_RC_REACTION_COEFF: + p->rc_reaction_coeff = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE: + ctx->force_frame_type = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME: + ctx->force_frame_type = + V4L2_MPEG_MFC51_VIDEO_FORCE_FRAME_TYPE_I_FRAME; + break; + case V4L2_CID_MPEG_VIDEO_VBV_SIZE: + p->vbv_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MV_H_SEARCH_RANGE: + p->mv_h_range = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MV_V_SEARCH_RANGE: + p->mv_v_range = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE: + p->codec.h264.cpb_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEADER_MODE: + p->seq_hdr_mode = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE: + case V4L2_CID_MPEG_VIDEO_FRAME_SKIP_MODE: + p->frame_skip_mode = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_RC_FIXED_TARGET_BIT: + p->fixed_target_bit = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_B_FRAMES: + p->num_b_frame = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + switch (ctrl->val) { + case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN: + p->codec.h264.profile = + S5P_FIMV_ENC_PROFILE_H264_MAIN; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH: + p->codec.h264.profile = + S5P_FIMV_ENC_PROFILE_H264_HIGH; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE: + p->codec.h264.profile = + S5P_FIMV_ENC_PROFILE_H264_BASELINE; + break; + case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE: + if (IS_MFCV6_PLUS(dev)) + p->codec.h264.profile = + S5P_FIMV_ENC_PROFILE_H264_CONSTRAINED_BASELINE; + else + ret = -EINVAL; + break; + default: + ret = -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + p->codec.h264.level_v4l2 = ctrl->val; + p->codec.h264.level = h264_level(ctrl->val); + if (p->codec.h264.level < 0) { + mfc_err("Level number is wrong\n"); + ret = p->codec.h264.level; + } + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_LEVEL: + p->codec.mpeg4.level_v4l2 = ctrl->val; + p->codec.mpeg4.level = mpeg4_level(ctrl->val); + if (p->codec.mpeg4.level < 0) { + mfc_err("Level number is wrong\n"); + ret = p->codec.mpeg4.level; + } + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE: + p->codec.h264.loop_filter_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA: + p->codec.h264.loop_filter_alpha = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA: + p->codec.h264.loop_filter_beta = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + p->codec.h264.entropy_mode = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_H264_NUM_REF_PIC_FOR_P: + p->codec.h264.num_ref_pic_4p = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM: + p->codec.h264._8x8_transform = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MB_RC_ENABLE: + p->rc_mb = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP: + p->codec.h264.rc_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + p->codec.h264.rc_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + p->codec.h264.rc_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP: + p->codec.h264.rc_p_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_B_FRAME_QP: + p->codec.h264.rc_b_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_I_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_I_FRAME_QP: + p->codec.mpeg4.rc_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_MIN_QP: + case V4L2_CID_MPEG_VIDEO_H263_MIN_QP: + p->codec.mpeg4.rc_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_MAX_QP: + case V4L2_CID_MPEG_VIDEO_H263_MAX_QP: + p->codec.mpeg4.rc_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_P_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_P_FRAME_QP: + p->codec.mpeg4.rc_p_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_B_FRAME_QP: + case V4L2_CID_MPEG_VIDEO_H263_B_FRAME_QP: + p->codec.mpeg4.rc_b_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_DARK: + p->codec.h264.rc_mb_dark = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_SMOOTH: + p->codec.h264.rc_mb_smooth = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_STATIC: + p->codec.h264.rc_mb_static = ctrl->val; + break; + case V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_ACTIVITY: + p->codec.h264.rc_mb_activity = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE: + p->codec.h264.vui_sar = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC: + p->codec.h264.vui_sar_idc = vui_sar_idc(ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_WIDTH: + p->codec.h264.vui_ext_sar_width = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_EXT_SAR_HEIGHT: + p->codec.h264.vui_ext_sar_height = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_GOP_CLOSURE: + p->codec.h264.open_gop = !ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_I_PERIOD: + p->codec.h264.open_gop_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_PROFILE: + switch (ctrl->val) { + case V4L2_MPEG_VIDEO_MPEG4_PROFILE_SIMPLE: + p->codec.mpeg4.profile = + S5P_FIMV_ENC_PROFILE_MPEG4_SIMPLE; + break; + case V4L2_MPEG_VIDEO_MPEG4_PROFILE_ADVANCED_SIMPLE: + p->codec.mpeg4.profile = + S5P_FIMV_ENC_PROFILE_MPEG4_ADVANCED_SIMPLE; + break; + default: + ret = -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_MPEG4_QPEL: + p->codec.mpeg4.quarter_pixel = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_NUM_PARTITIONS: + p->codec.vp8.num_partitions = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_IMD_DISABLE_4X4: + p->codec.vp8.imd_4x4 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_NUM_REF_FRAMES: + p->codec.vp8.num_ref = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_FILTER_LEVEL: + p->codec.vp8.filter_level = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_FILTER_SHARPNESS: + p->codec.vp8.filter_sharpness = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_REF_PERIOD: + p->codec.vp8.golden_frame_ref_period = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_GOLDEN_FRAME_SEL: + p->codec.vp8.golden_frame_sel = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_MIN_QP: + p->codec.vp8.rc_min_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_MAX_QP: + p->codec.vp8.rc_max_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_I_FRAME_QP: + p->codec.vp8.rc_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VPX_P_FRAME_QP: + p->codec.vp8.rc_p_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_VP8_PROFILE: + p->codec.vp8.profile = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP: + p->codec.hevc.rc_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP: + p->codec.hevc.rc_p_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP: + p->codec.hevc.rc_b_frame_qp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_FRAME_RATE_RESOLUTION: + p->codec.hevc.rc_framerate = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP: + p->codec.hevc.rc_min_qp = ctrl->val; + __enc_update_hevc_qp_ctrls_range(ctx, ctrl->val, + p->codec.hevc.rc_max_qp); + break; + case V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP: + p->codec.hevc.rc_max_qp = ctrl->val; + __enc_update_hevc_qp_ctrls_range(ctx, p->codec.hevc.rc_min_qp, + ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_HEVC_LEVEL: + p->codec.hevc.level_v4l2 = ctrl->val; + p->codec.hevc.level = hevc_level(ctrl->val); + break; + case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE: + switch (ctrl->val) { + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN: + p->codec.hevc.profile = + V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN; + break; + case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE: + p->codec.hevc.profile = + V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE; + break; + default: + ret = -EINVAL; + } + break; + case V4L2_CID_MPEG_VIDEO_HEVC_TIER: + p->codec.hevc.tier = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_MAX_PARTITION_DEPTH: + p->codec.hevc.max_partition_depth = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_REF_NUMBER_FOR_PFRAMES: + p->codec.hevc.num_refs_for_p = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_TYPE: + p->codec.hevc.refreshtype = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_CONST_INTRA_PRED: + p->codec.hevc.const_intra_period_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_LOSSLESS_CU: + p->codec.hevc.lossless_cu_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_WAVEFRONT: + p->codec.hevc.wavefront_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE: + p->codec.hevc.loopfilter = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_QP: + p->codec.hevc.hier_qp_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_TYPE: + p->codec.hevc.hier_qp_type = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_LAYER: + p->codec.hevc.num_hier_layer = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_QP: + p->codec.hevc.hier_qp_layer[0] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_QP: + p->codec.hevc.hier_qp_layer[1] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_QP: + p->codec.hevc.hier_qp_layer[2] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_QP: + p->codec.hevc.hier_qp_layer[3] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_QP: + p->codec.hevc.hier_qp_layer[4] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_QP: + p->codec.hevc.hier_qp_layer[5] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_QP: + p->codec.hevc.hier_qp_layer[6] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L0_BR: + p->codec.hevc.hier_bit_layer[0] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L1_BR: + p->codec.hevc.hier_bit_layer[1] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L2_BR: + p->codec.hevc.hier_bit_layer[2] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L3_BR: + p->codec.hevc.hier_bit_layer[3] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L4_BR: + p->codec.hevc.hier_bit_layer[4] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L5_BR: + p->codec.hevc.hier_bit_layer[5] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_HIER_CODING_L6_BR: + p->codec.hevc.hier_bit_layer[6] = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_GENERAL_PB: + p->codec.hevc.general_pb_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_TEMPORAL_ID: + p->codec.hevc.temporal_id_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_STRONG_SMOOTHING: + p->codec.hevc.strong_intra_smooth = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_INTRA_PU_SPLIT: + p->codec.hevc.intra_pu_split_disable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION: + p->codec.hevc.tmv_prediction_disable = !ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_MAX_NUM_MERGE_MV_MINUS1: + p->codec.hevc.max_num_merge_mv = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_WITHOUT_STARTCODE: + p->codec.hevc.encoding_nostartcode_enable = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD: + p->codec.hevc.refreshperiod = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_LF_BETA_OFFSET_DIV2: + p->codec.hevc.lf_beta_offset_div2 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_LF_TC_OFFSET_DIV2: + p->codec.hevc.lf_tc_offset_div2 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_HEVC_SIZE_OF_LENGTH_FIELD: + p->codec.hevc.size_of_length_field = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR: + p->codec.hevc.prepend_sps_pps_to_idr = ctrl->val; + break; + default: + v4l2_err(&dev->v4l2_dev, "Invalid control, id=%d, val=%d\n", + ctrl->id, ctrl->val); + ret = -EINVAL; + } + return ret; +} + +static int s5p_mfc_enc_g_v_ctrl(struct v4l2_ctrl *ctrl) +{ + struct s5p_mfc_ctx *ctx = ctrl_to_ctx(ctrl); + struct s5p_mfc_dev *dev = ctx->dev; + + switch (ctrl->id) { + case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT: + if (ctx->state >= MFCINST_HEAD_PARSED && + ctx->state < MFCINST_ABORT) { + ctrl->val = ctx->pb_count; + break; + } else if (ctx->state != MFCINST_INIT) { + v4l2_err(&dev->v4l2_dev, "Encoding not initialised\n"); + return -EINVAL; + } + /* Should wait for the header to be produced */ + s5p_mfc_wait_for_done_ctx(ctx, + S5P_MFC_R2H_CMD_SEQ_DONE_RET, 0); + if (ctx->state >= MFCINST_HEAD_PARSED && + ctx->state < MFCINST_ABORT) { + ctrl->val = ctx->pb_count; + } else { + v4l2_err(&dev->v4l2_dev, "Encoding not initialised\n"); + return -EINVAL; + } + break; + } + return 0; +} + +static const struct v4l2_ctrl_ops s5p_mfc_enc_ctrl_ops = { + .s_ctrl = s5p_mfc_enc_s_ctrl, + .g_volatile_ctrl = s5p_mfc_enc_g_v_ctrl, +}; + +static int vidioc_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *a) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (a->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ctx->enc_params.rc_framerate_num = + a->parm.output.timeperframe.denominator; + ctx->enc_params.rc_framerate_denom = + a->parm.output.timeperframe.numerator; + } else { + mfc_err("Setting FPS is only possible for the output queue\n"); + return -EINVAL; + } + return 0; +} + +static int vidioc_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *a) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + + if (a->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + a->parm.output.timeperframe.denominator = + ctx->enc_params.rc_framerate_num; + a->parm.output.timeperframe.numerator = + ctx->enc_params.rc_framerate_denom; + } else { + mfc_err("Setting FPS is only possible for the output queue\n"); + return -EINVAL; + } + return 0; +} + +static int vidioc_encoder_cmd(struct file *file, void *priv, + struct v4l2_encoder_cmd *cmd) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(priv); + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *buf; + unsigned long flags; + + switch (cmd->cmd) { + case V4L2_ENC_CMD_STOP: + if (cmd->flags != 0) + return -EINVAL; + + if (!ctx->vq_src.streaming) + return -EINVAL; + + spin_lock_irqsave(&dev->irqlock, flags); + if (list_empty(&ctx->src_queue)) { + mfc_debug(2, "EOS: empty src queue, entering finishing state\n"); + ctx->state = MFCINST_FINISHING; + if (s5p_mfc_ctx_ready(ctx)) + set_work_bit_irqsave(ctx); + spin_unlock_irqrestore(&dev->irqlock, flags); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + } else { + mfc_debug(2, "EOS: marking last buffer of stream\n"); + buf = list_entry(ctx->src_queue.prev, + struct s5p_mfc_buf, list); + if (buf->flags & MFC_BUF_FLAG_USED) + ctx->state = MFCINST_FINISHING; + else + buf->flags |= MFC_BUF_FLAG_EOS; + spin_unlock_irqrestore(&dev->irqlock, flags); + } + break; + default: + return -EINVAL; + + } + return 0; +} + +static int vidioc_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + default: + return -EINVAL; + } +} + +static const struct v4l2_ioctl_ops s5p_mfc_enc_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid_out, + .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt, + .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt, + .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt, + .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt, + .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt, + .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + .vidioc_expbuf = vidioc_expbuf, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_s_parm = vidioc_s_parm, + .vidioc_g_parm = vidioc_g_parm, + .vidioc_encoder_cmd = vidioc_encoder_cmd, + .vidioc_subscribe_event = vidioc_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int check_vb_with_fmt(struct s5p_mfc_fmt *fmt, struct vb2_buffer *vb) +{ + int i; + + if (!fmt) + return -EINVAL; + if (fmt->num_planes != vb->num_planes) { + mfc_err("invalid plane number for the format\n"); + return -EINVAL; + } + for (i = 0; i < fmt->num_planes; i++) { + dma_addr_t dma = vb2_dma_contig_plane_dma_addr(vb, i); + if (!dma) { + mfc_err("failed to get plane cookie\n"); + return -EINVAL; + } + mfc_debug(2, "index: %d, plane[%d] cookie: %pad\n", + vb->index, i, &dma); + } + return 0; +} + +static int s5p_mfc_queue_setup(struct vb2_queue *vq, + unsigned int *buf_count, unsigned int *plane_count, + unsigned int psize[], struct device *alloc_devs[]) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv); + struct s5p_mfc_dev *dev = ctx->dev; + + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + if (ctx->state != MFCINST_GOT_INST) { + mfc_err("invalid state: %d\n", ctx->state); + return -EINVAL; + } + + if (ctx->dst_fmt) + *plane_count = ctx->dst_fmt->num_planes; + else + *plane_count = MFC_ENC_CAP_PLANE_COUNT; + if (*buf_count < 1) + *buf_count = 1; + if (*buf_count > MFC_MAX_BUFFERS) + *buf_count = MFC_MAX_BUFFERS; + psize[0] = ctx->enc_dst_buf_size; + alloc_devs[0] = ctx->dev->mem_dev[BANK_L_CTX]; + } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + if (ctx->src_fmt) + *plane_count = ctx->src_fmt->num_planes; + else + *plane_count = MFC_ENC_OUT_PLANE_COUNT; + + if (*buf_count < 1) + *buf_count = 1; + if (*buf_count > MFC_MAX_BUFFERS) + *buf_count = MFC_MAX_BUFFERS; + + psize[0] = ctx->luma_size; + psize[1] = ctx->chroma_size; + + if (IS_MFCV6_PLUS(dev)) { + alloc_devs[0] = ctx->dev->mem_dev[BANK_L_CTX]; + alloc_devs[1] = ctx->dev->mem_dev[BANK_L_CTX]; + } else { + alloc_devs[0] = ctx->dev->mem_dev[BANK_R_CTX]; + alloc_devs[1] = ctx->dev->mem_dev[BANK_R_CTX]; + } + } else { + mfc_err("invalid queue type: %d\n", vq->type); + return -EINVAL; + } + return 0; +} + +static int s5p_mfc_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *vq = vb->vb2_queue; + struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv); + unsigned int i; + int ret; + + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = check_vb_with_fmt(ctx->dst_fmt, vb); + if (ret < 0) + return ret; + i = vb->index; + ctx->dst_bufs[i].b = vbuf; + ctx->dst_bufs[i].cookie.stream = + vb2_dma_contig_plane_dma_addr(vb, 0); + ctx->dst_bufs_cnt++; + } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = check_vb_with_fmt(ctx->src_fmt, vb); + if (ret < 0) + return ret; + i = vb->index; + ctx->src_bufs[i].b = vbuf; + ctx->src_bufs[i].cookie.raw.luma = + vb2_dma_contig_plane_dma_addr(vb, 0); + ctx->src_bufs[i].cookie.raw.chroma = + vb2_dma_contig_plane_dma_addr(vb, 1); + ctx->src_bufs_cnt++; + } else { + mfc_err("invalid queue type: %d\n", vq->type); + return -EINVAL; + } + return 0; +} + +static int s5p_mfc_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv); + int ret; + + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + ret = check_vb_with_fmt(ctx->dst_fmt, vb); + if (ret < 0) + return ret; + mfc_debug(2, "plane size: %ld, dst size: %zu\n", + vb2_plane_size(vb, 0), ctx->enc_dst_buf_size); + if (vb2_plane_size(vb, 0) < ctx->enc_dst_buf_size) { + mfc_err("plane size is too small for capture\n"); + return -EINVAL; + } + } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = check_vb_with_fmt(ctx->src_fmt, vb); + if (ret < 0) + return ret; + mfc_debug(2, "plane size: %ld, luma size: %d\n", + vb2_plane_size(vb, 0), ctx->luma_size); + mfc_debug(2, "plane size: %ld, chroma size: %d\n", + vb2_plane_size(vb, 1), ctx->chroma_size); + if (vb2_plane_size(vb, 0) < ctx->luma_size || + vb2_plane_size(vb, 1) < ctx->chroma_size) { + mfc_err("plane size is too small for output\n"); + return -EINVAL; + } + } else { + mfc_err("invalid queue type: %d\n", vq->type); + return -EINVAL; + } + return 0; +} + +static int s5p_mfc_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv); + struct s5p_mfc_dev *dev = ctx->dev; + + if (IS_MFCV6_PLUS(dev) && + (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)) { + + if ((ctx->state == MFCINST_GOT_INST) && + (dev->curr_ctx == ctx->num) && dev->hw_lock) { + s5p_mfc_wait_for_done_ctx(ctx, + S5P_MFC_R2H_CMD_SEQ_DONE_RET, + 0); + } + + if (ctx->src_bufs_cnt < ctx->pb_count) { + mfc_err("Need minimum %d OUTPUT buffers\n", + ctx->pb_count); + return -ENOBUFS; + } + } + + /* If context is ready then dev = work->data;schedule it to run */ + if (s5p_mfc_ctx_ready(ctx)) + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); + + return 0; +} + +static void s5p_mfc_stop_streaming(struct vb2_queue *q) +{ + unsigned long flags; + struct s5p_mfc_ctx *ctx = fh_to_ctx(q->drv_priv); + struct s5p_mfc_dev *dev = ctx->dev; + + if ((ctx->state == MFCINST_FINISHING || + ctx->state == MFCINST_RUNNING) && + dev->curr_ctx == ctx->num && dev->hw_lock) { + ctx->state = MFCINST_ABORT; + s5p_mfc_wait_for_done_ctx(ctx, S5P_MFC_R2H_CMD_FRAME_DONE_RET, + 0); + } + ctx->state = MFCINST_FINISHED; + spin_lock_irqsave(&dev->irqlock, flags); + if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + s5p_mfc_cleanup_queue(&ctx->dst_queue, &ctx->vq_dst); + INIT_LIST_HEAD(&ctx->dst_queue); + ctx->dst_queue_cnt = 0; + } + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + cleanup_ref_queue(ctx); + s5p_mfc_cleanup_queue(&ctx->src_queue, &ctx->vq_src); + INIT_LIST_HEAD(&ctx->src_queue); + ctx->src_queue_cnt = 0; + } + spin_unlock_irqrestore(&dev->irqlock, flags); +} + +static void s5p_mfc_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct s5p_mfc_ctx *ctx = fh_to_ctx(vq->drv_priv); + struct s5p_mfc_dev *dev = ctx->dev; + unsigned long flags; + struct s5p_mfc_buf *mfc_buf; + + if (ctx->state == MFCINST_ERROR) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + cleanup_ref_queue(ctx); + return; + } + if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { + mfc_buf = &ctx->dst_bufs[vb->index]; + mfc_buf->flags &= ~MFC_BUF_FLAG_USED; + /* Mark destination as available for use by MFC */ + spin_lock_irqsave(&dev->irqlock, flags); + list_add_tail(&mfc_buf->list, &ctx->dst_queue); + ctx->dst_queue_cnt++; + spin_unlock_irqrestore(&dev->irqlock, flags); + } else if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + mfc_buf = &ctx->src_bufs[vb->index]; + mfc_buf->flags &= ~MFC_BUF_FLAG_USED; + spin_lock_irqsave(&dev->irqlock, flags); + list_add_tail(&mfc_buf->list, &ctx->src_queue); + ctx->src_queue_cnt++; + spin_unlock_irqrestore(&dev->irqlock, flags); + } else { + mfc_err("unsupported buffer type (%d)\n", vq->type); + } + if (s5p_mfc_ctx_ready(ctx)) + set_work_bit_irqsave(ctx); + s5p_mfc_hw_call(dev->mfc_ops, try_run, dev); +} + +static struct vb2_ops s5p_mfc_enc_qops = { + .queue_setup = s5p_mfc_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_init = s5p_mfc_buf_init, + .buf_prepare = s5p_mfc_buf_prepare, + .start_streaming = s5p_mfc_start_streaming, + .stop_streaming = s5p_mfc_stop_streaming, + .buf_queue = s5p_mfc_buf_queue, +}; + +const struct s5p_mfc_codec_ops *get_enc_codec_ops(void) +{ + return &encoder_codec_ops; +} + +struct vb2_ops *get_enc_queue_ops(void) +{ + return &s5p_mfc_enc_qops; +} + +const struct v4l2_ioctl_ops *get_enc_v4l2_ioctl_ops(void) +{ + return &s5p_mfc_enc_ioctl_ops; +} + +#define IS_MFC51_PRIV(x) ((V4L2_CTRL_ID2WHICH(x) == V4L2_CTRL_CLASS_CODEC) \ + && V4L2_CTRL_DRIVER_PRIV(x)) + +int s5p_mfc_enc_ctrls_setup(struct s5p_mfc_ctx *ctx) +{ + struct v4l2_ctrl_config cfg; + int i; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, NUM_CTRLS); + if (ctx->ctrl_handler.error) { + mfc_err("v4l2_ctrl_handler_init failed\n"); + return ctx->ctrl_handler.error; + } + for (i = 0; i < NUM_CTRLS; i++) { + if (IS_MFC51_PRIV(controls[i].id)) { + memset(&cfg, 0, sizeof(struct v4l2_ctrl_config)); + cfg.ops = &s5p_mfc_enc_ctrl_ops; + cfg.id = controls[i].id; + cfg.min = controls[i].minimum; + cfg.max = controls[i].maximum; + cfg.def = controls[i].default_value; + cfg.name = controls[i].name; + cfg.type = controls[i].type; + cfg.flags = 0; + + if (cfg.type == V4L2_CTRL_TYPE_MENU) { + cfg.step = 0; + cfg.menu_skip_mask = controls[i].menu_skip_mask; + cfg.qmenu = mfc51_get_menu(cfg.id); + } else { + cfg.step = controls[i].step; + cfg.menu_skip_mask = 0; + } + ctx->ctrls[i] = v4l2_ctrl_new_custom(&ctx->ctrl_handler, + &cfg, NULL); + } else { + if ((controls[i].type == V4L2_CTRL_TYPE_MENU) || + (controls[i].type == + V4L2_CTRL_TYPE_INTEGER_MENU)) { + ctx->ctrls[i] = v4l2_ctrl_new_std_menu( + &ctx->ctrl_handler, + &s5p_mfc_enc_ctrl_ops, controls[i].id, + controls[i].maximum, 0, + controls[i].default_value); + } else { + ctx->ctrls[i] = v4l2_ctrl_new_std( + &ctx->ctrl_handler, + &s5p_mfc_enc_ctrl_ops, controls[i].id, + controls[i].minimum, + controls[i].maximum, controls[i].step, + controls[i].default_value); + } + } + if (ctx->ctrl_handler.error) { + mfc_err("Adding control (%d) failed\n", i); + return ctx->ctrl_handler.error; + } + if (controls[i].is_volatile && ctx->ctrls[i]) + ctx->ctrls[i]->flags |= V4L2_CTRL_FLAG_VOLATILE; + } + v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + return 0; +} + +void s5p_mfc_enc_ctrls_delete(struct s5p_mfc_ctx *ctx) +{ + int i; + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + for (i = 0; i < NUM_CTRLS; i++) + ctx->ctrls[i] = NULL; +} + +void s5p_mfc_enc_init(struct s5p_mfc_ctx *ctx) +{ + struct v4l2_format f; + f.fmt.pix_mp.pixelformat = DEF_SRC_FMT_ENC; + ctx->src_fmt = find_format(&f, MFC_FMT_RAW); + f.fmt.pix_mp.pixelformat = DEF_DST_FMT_ENC; + ctx->dst_fmt = find_format(&f, MFC_FMT_ENC); +} diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h new file mode 100644 index 000000000..3f1b1a037 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_enc.h + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_ENC_H_ +#define S5P_MFC_ENC_H_ + +const struct s5p_mfc_codec_ops *get_enc_codec_ops(void); +struct vb2_ops *get_enc_queue_ops(void); +const struct v4l2_ioctl_ops *get_enc_v4l2_ioctl_ops(void); +struct s5p_mfc_fmt *get_enc_def_fmt(bool src); +int s5p_mfc_enc_ctrls_setup(struct s5p_mfc_ctx *ctx); +void s5p_mfc_enc_ctrls_delete(struct s5p_mfc_ctx *ctx); +void s5p_mfc_enc_init(struct s5p_mfc_ctx *ctx); + +#endif /* S5P_MFC_ENC_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.c new file mode 100644 index 000000000..0a38f6d70 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.c @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/platform/samsung/mfc5/s5p_mfc_intr.c + * + * C file for Samsung MFC (Multi Function Codec - FIMV) driver + * This file contains functions used to wait for command completion. + * + * Kamil Debski, Copyright (C) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#include +#include +#include +#include +#include +#include "s5p_mfc_common.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_intr.h" + +int s5p_mfc_wait_for_done_dev(struct s5p_mfc_dev *dev, int command) +{ + int ret; + + ret = wait_event_interruptible_timeout(dev->queue, + (dev->int_cond && (dev->int_type == command + || dev->int_type == S5P_MFC_R2H_CMD_ERR_RET)), + msecs_to_jiffies(MFC_INT_TIMEOUT)); + if (ret == 0) { + mfc_err("Interrupt (dev->int_type:%d, command:%d) timed out\n", + dev->int_type, command); + return 1; + } else if (ret == -ERESTARTSYS) { + mfc_err("Interrupted by a signal\n"); + return 1; + } + mfc_debug(1, "Finished waiting (dev->int_type:%d, command: %d)\n", + dev->int_type, command); + if (dev->int_type == S5P_MFC_R2H_CMD_ERR_RET) + return 1; + return 0; +} + +void s5p_mfc_clean_dev_int_flags(struct s5p_mfc_dev *dev) +{ + dev->int_cond = 0; + dev->int_type = 0; + dev->int_err = 0; +} + +int s5p_mfc_wait_for_done_ctx(struct s5p_mfc_ctx *ctx, + int command, int interrupt) +{ + int ret; + + if (interrupt) { + ret = wait_event_interruptible_timeout(ctx->queue, + (ctx->int_cond && (ctx->int_type == command + || ctx->int_type == S5P_MFC_R2H_CMD_ERR_RET)), + msecs_to_jiffies(MFC_INT_TIMEOUT)); + } else { + ret = wait_event_timeout(ctx->queue, + (ctx->int_cond && (ctx->int_type == command + || ctx->int_type == S5P_MFC_R2H_CMD_ERR_RET)), + msecs_to_jiffies(MFC_INT_TIMEOUT)); + } + if (ret == 0) { + mfc_err("Interrupt (ctx->int_type:%d, command:%d) timed out\n", + ctx->int_type, command); + return 1; + } else if (ret == -ERESTARTSYS) { + mfc_err("Interrupted by a signal\n"); + return 1; + } + mfc_debug(1, "Finished waiting (ctx->int_type:%d, command: %d)\n", + ctx->int_type, command); + if (ctx->int_type == S5P_MFC_R2H_CMD_ERR_RET) + return 1; + return 0; +} + +void s5p_mfc_clean_ctx_int_flags(struct s5p_mfc_ctx *ctx) +{ + ctx->int_cond = 0; + ctx->int_type = 0; + ctx->int_err = 0; +} + diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.h new file mode 100644 index 000000000..d32860db1 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_intr.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/platform/samsung/mfc5/s5p_mfc_intr.h + * + * Header file for Samsung MFC (Multi Function Codec - FIMV) driver + * It contains waiting functions declarations. + * + * Kamil Debski, Copyright (C) 2011 Samsung Electronics + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_INTR_H_ +#define S5P_MFC_INTR_H_ + +#include "s5p_mfc_common.h" + +int s5p_mfc_wait_for_done_ctx(struct s5p_mfc_ctx *ctx, + int command, int interrupt); +int s5p_mfc_wait_for_done_dev(struct s5p_mfc_dev *dev, int command); +void s5p_mfc_clean_ctx_int_flags(struct s5p_mfc_ctx *ctx); +void s5p_mfc_clean_dev_int_flags(struct s5p_mfc_dev *dev); + +#endif /* S5P_MFC_INTR_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h new file mode 100644 index 000000000..1a32266b7 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_iommu.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Samsung Electronics Co.Ltd + * Authors: Marek Szyprowski + */ + +#ifndef S5P_MFC_IOMMU_H_ +#define S5P_MFC_IOMMU_H_ + +#if defined(CONFIG_EXYNOS_IOMMU) + +#include + +static inline bool exynos_is_iommu_available(struct device *dev) +{ + return dev_iommu_priv_get(dev) != NULL; +} + +#else + +static inline bool exynos_is_iommu_available(struct device *dev) +{ + return false; +} + +#endif + +#endif /* S5P_MFC_IOMMU_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c new file mode 100644 index 000000000..673962301 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.c + * + * Samsung MFC (Multi Function Codec - FIMV) driver + * This file contains hw related functions. + * + * Kamil Debski, Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#include "s5p_mfc_debug.h" +#include "s5p_mfc_opr.h" +#include "s5p_mfc_opr_v5.h" +#include "s5p_mfc_opr_v6.h" + +static struct s5p_mfc_hw_ops *s5p_mfc_ops; + +void s5p_mfc_init_hw_ops(struct s5p_mfc_dev *dev) +{ + if (IS_MFCV6_PLUS(dev)) { + s5p_mfc_ops = s5p_mfc_init_hw_ops_v6(); + dev->warn_start = S5P_FIMV_ERR_WARNINGS_START_V6; + } else { + s5p_mfc_ops = s5p_mfc_init_hw_ops_v5(); + dev->warn_start = S5P_FIMV_ERR_WARNINGS_START; + } + dev->mfc_ops = s5p_mfc_ops; +} + +void s5p_mfc_init_regs(struct s5p_mfc_dev *dev) +{ + if (IS_MFCV6_PLUS(dev)) + dev->mfc_regs = s5p_mfc_init_regs_v6_plus(dev); +} + +int s5p_mfc_alloc_priv_buf(struct s5p_mfc_dev *dev, unsigned int mem_ctx, + struct s5p_mfc_priv_buf *b) +{ + unsigned int bits = dev->mem_size >> PAGE_SHIFT; + unsigned int count = b->size >> PAGE_SHIFT; + unsigned int align = (SZ_64K >> PAGE_SHIFT) - 1; + unsigned int start, offset; + + mfc_debug(3, "Allocating priv: %zu\n", b->size); + + if (dev->mem_virt) { + start = bitmap_find_next_zero_area(dev->mem_bitmap, bits, 0, count, align); + if (start > bits) + goto no_mem; + + bitmap_set(dev->mem_bitmap, start, count); + offset = start << PAGE_SHIFT; + b->virt = dev->mem_virt + offset; + b->dma = dev->mem_base + offset; + } else { + struct device *mem_dev = dev->mem_dev[mem_ctx]; + dma_addr_t base = dev->dma_base[mem_ctx]; + + b->ctx = mem_ctx; + b->virt = dma_alloc_coherent(mem_dev, b->size, &b->dma, GFP_KERNEL); + if (!b->virt) + goto no_mem; + if (b->dma < base) { + mfc_err("Invalid memory configuration - buffer (%pad) is below base memory address(%pad)\n", + &b->dma, &base); + dma_free_coherent(mem_dev, b->size, b->virt, b->dma); + return -ENOMEM; + } + } + + mfc_debug(3, "Allocated addr %p %pad\n", b->virt, &b->dma); + return 0; +no_mem: + mfc_err("Allocating private buffer of size %zu failed\n", b->size); + return -ENOMEM; +} + +int s5p_mfc_alloc_generic_buf(struct s5p_mfc_dev *dev, unsigned int mem_ctx, + struct s5p_mfc_priv_buf *b) +{ + struct device *mem_dev = dev->mem_dev[mem_ctx]; + + mfc_debug(3, "Allocating generic buf: %zu\n", b->size); + + b->ctx = mem_ctx; + b->virt = dma_alloc_coherent(mem_dev, b->size, &b->dma, GFP_KERNEL); + if (!b->virt) + goto no_mem; + + mfc_debug(3, "Allocated addr %p %pad\n", b->virt, &b->dma); + return 0; +no_mem: + mfc_err("Allocating generic buffer of size %zu failed\n", b->size); + return -ENOMEM; +} + +void s5p_mfc_release_priv_buf(struct s5p_mfc_dev *dev, + struct s5p_mfc_priv_buf *b) +{ + if (dev->mem_virt) { + unsigned int start = (b->dma - dev->mem_base) >> PAGE_SHIFT; + unsigned int count = b->size >> PAGE_SHIFT; + + bitmap_clear(dev->mem_bitmap, start, count); + } else { + struct device *mem_dev = dev->mem_dev[b->ctx]; + + dma_free_coherent(mem_dev, b->size, b->virt, b->dma); + } + b->virt = NULL; + b->dma = 0; + b->size = 0; +} + +void s5p_mfc_release_generic_buf(struct s5p_mfc_dev *dev, + struct s5p_mfc_priv_buf *b) +{ + struct device *mem_dev = dev->mem_dev[b->ctx]; + dma_free_coherent(mem_dev, b->size, b->virt, b->dma); + b->virt = NULL; + b->dma = 0; + b->size = 0; +} diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h new file mode 100644 index 000000000..b9831275f --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr.h + * + * Header file for Samsung MFC (Multi Function Codec - FIMV) driver + * Contains declarations of hw related functions. + * + * Kamil Debski, Copyright (C) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_OPR_H_ +#define S5P_MFC_OPR_H_ + +#include "s5p_mfc_common.h" + +struct s5p_mfc_regs { + + /* codec common registers */ + void __iomem *risc_on; + void __iomem *risc2host_int; + void __iomem *host2risc_int; + void __iomem *risc_base_address; + void __iomem *mfc_reset; + void __iomem *host2risc_command; + void __iomem *risc2host_command; + void __iomem *mfc_bus_reset_ctrl; + void __iomem *firmware_version; + void __iomem *instance_id; + void __iomem *codec_type; + void __iomem *context_mem_addr; + void __iomem *context_mem_size; + void __iomem *pixel_format; + void __iomem *metadata_enable; + void __iomem *mfc_version; + void __iomem *dbg_info_enable; + void __iomem *dbg_buffer_addr; + void __iomem *dbg_buffer_size; + void __iomem *hed_control; + void __iomem *mfc_timeout_value; + void __iomem *hed_shared_mem_addr; + void __iomem *dis_shared_mem_addr;/* only v7 */ + void __iomem *ret_instance_id; + void __iomem *error_code; + void __iomem *dbg_buffer_output_size; + void __iomem *metadata_status; + void __iomem *metadata_addr_mb_info; + void __iomem *metadata_size_mb_info; + void __iomem *dbg_info_stage_counter; + + /* decoder registers */ + void __iomem *d_crc_ctrl; + void __iomem *d_dec_options; + void __iomem *d_display_delay; + void __iomem *d_set_frame_width; + void __iomem *d_set_frame_height; + void __iomem *d_sei_enable; + void __iomem *d_min_num_dpb; + void __iomem *d_min_first_plane_dpb_size; + void __iomem *d_min_second_plane_dpb_size; + void __iomem *d_min_third_plane_dpb_size;/* only v8 */ + void __iomem *d_min_num_mv; + void __iomem *d_mvc_num_views; + void __iomem *d_min_num_dis;/* only v7 */ + void __iomem *d_min_first_dis_size;/* only v7 */ + void __iomem *d_min_second_dis_size;/* only v7 */ + void __iomem *d_min_third_dis_size;/* only v7 */ + void __iomem *d_post_filter_luma_dpb0;/* v7 and v8 */ + void __iomem *d_post_filter_luma_dpb1;/* v7 and v8 */ + void __iomem *d_post_filter_luma_dpb2;/* only v7 */ + void __iomem *d_post_filter_chroma_dpb0;/* v7 and v8 */ + void __iomem *d_post_filter_chroma_dpb1;/* v7 and v8 */ + void __iomem *d_post_filter_chroma_dpb2;/* only v7 */ + void __iomem *d_num_dpb; + void __iomem *d_num_mv; + void __iomem *d_init_buffer_options; + void __iomem *d_first_plane_dpb_stride_size;/* only v8 */ + void __iomem *d_second_plane_dpb_stride_size;/* only v8 */ + void __iomem *d_third_plane_dpb_stride_size;/* only v8 */ + void __iomem *d_first_plane_dpb_size; + void __iomem *d_second_plane_dpb_size; + void __iomem *d_third_plane_dpb_size;/* only v8 */ + void __iomem *d_mv_buffer_size; + void __iomem *d_first_plane_dpb; + void __iomem *d_second_plane_dpb; + void __iomem *d_third_plane_dpb; + void __iomem *d_mv_buffer; + void __iomem *d_scratch_buffer_addr; + void __iomem *d_scratch_buffer_size; + void __iomem *d_metadata_buffer_addr; + void __iomem *d_metadata_buffer_size; + void __iomem *d_nal_start_options;/* v7 and v8 */ + void __iomem *d_cpb_buffer_addr; + void __iomem *d_cpb_buffer_size; + void __iomem *d_available_dpb_flag_upper; + void __iomem *d_available_dpb_flag_lower; + void __iomem *d_cpb_buffer_offset; + void __iomem *d_slice_if_enable; + void __iomem *d_picture_tag; + void __iomem *d_stream_data_size; + void __iomem *d_dynamic_dpb_flag_upper;/* v7 and v8 */ + void __iomem *d_dynamic_dpb_flag_lower;/* v7 and v8 */ + void __iomem *d_display_frame_width; + void __iomem *d_display_frame_height; + void __iomem *d_display_status; + void __iomem *d_display_first_plane_addr; + void __iomem *d_display_second_plane_addr; + void __iomem *d_display_third_plane_addr;/* only v8 */ + void __iomem *d_display_frame_type; + void __iomem *d_display_crop_info1; + void __iomem *d_display_crop_info2; + void __iomem *d_display_picture_profile; + void __iomem *d_display_luma_crc;/* v7 and v8 */ + void __iomem *d_display_chroma0_crc;/* v7 and v8 */ + void __iomem *d_display_chroma1_crc;/* only v8 */ + void __iomem *d_display_luma_crc_top;/* only v6 */ + void __iomem *d_display_chroma_crc_top;/* only v6 */ + void __iomem *d_display_luma_crc_bot;/* only v6 */ + void __iomem *d_display_chroma_crc_bot;/* only v6 */ + void __iomem *d_display_aspect_ratio; + void __iomem *d_display_extended_ar; + void __iomem *d_decoded_frame_width; + void __iomem *d_decoded_frame_height; + void __iomem *d_decoded_status; + void __iomem *d_decoded_first_plane_addr; + void __iomem *d_decoded_second_plane_addr; + void __iomem *d_decoded_third_plane_addr;/* only v8 */ + void __iomem *d_decoded_frame_type; + void __iomem *d_decoded_crop_info1; + void __iomem *d_decoded_crop_info2; + void __iomem *d_decoded_picture_profile; + void __iomem *d_decoded_nal_size; + void __iomem *d_decoded_luma_crc; + void __iomem *d_decoded_chroma0_crc; + void __iomem *d_decoded_chroma1_crc;/* only v8 */ + void __iomem *d_ret_picture_tag_top; + void __iomem *d_ret_picture_tag_bot; + void __iomem *d_ret_picture_time_top; + void __iomem *d_ret_picture_time_bot; + void __iomem *d_chroma_format; + void __iomem *d_vc1_info;/* v7 and v8 */ + void __iomem *d_mpeg4_info; + void __iomem *d_h264_info; + void __iomem *d_metadata_addr_concealed_mb; + void __iomem *d_metadata_size_concealed_mb; + void __iomem *d_metadata_addr_vc1_param; + void __iomem *d_metadata_size_vc1_param; + void __iomem *d_metadata_addr_sei_nal; + void __iomem *d_metadata_size_sei_nal; + void __iomem *d_metadata_addr_vui; + void __iomem *d_metadata_size_vui; + void __iomem *d_metadata_addr_mvcvui;/* v7 and v8 */ + void __iomem *d_metadata_size_mvcvui;/* v7 and v8 */ + void __iomem *d_mvc_view_id; + void __iomem *d_frame_pack_sei_avail; + void __iomem *d_frame_pack_arrgment_id; + void __iomem *d_frame_pack_sei_info; + void __iomem *d_frame_pack_grid_pos; + void __iomem *d_display_recovery_sei_info;/* v7 and v8 */ + void __iomem *d_decoded_recovery_sei_info;/* v7 and v8 */ + void __iomem *d_display_first_addr;/* only v7 */ + void __iomem *d_display_second_addr;/* only v7 */ + void __iomem *d_display_third_addr;/* only v7 */ + void __iomem *d_decoded_first_addr;/* only v7 */ + void __iomem *d_decoded_second_addr;/* only v7 */ + void __iomem *d_decoded_third_addr;/* only v7 */ + void __iomem *d_used_dpb_flag_upper;/* v7 and v8 */ + void __iomem *d_used_dpb_flag_lower;/* v7 and v8 */ + void __iomem *d_min_scratch_buffer_size; /* v10 */ + void __iomem *d_static_buffer_addr; /* v10 */ + void __iomem *d_static_buffer_size; /* v10 */ + + /* encoder registers */ + void __iomem *e_frame_width; + void __iomem *e_frame_height; + void __iomem *e_cropped_frame_width; + void __iomem *e_cropped_frame_height; + void __iomem *e_frame_crop_offset; + void __iomem *e_enc_options; + void __iomem *e_picture_profile; + void __iomem *e_vbv_buffer_size; + void __iomem *e_vbv_init_delay; + void __iomem *e_fixed_picture_qp; + void __iomem *e_rc_config; + void __iomem *e_rc_qp_bound; + void __iomem *e_rc_qp_bound_pb;/* v7 and v8 */ + void __iomem *e_rc_mode; + void __iomem *e_mb_rc_config; + void __iomem *e_padding_ctrl; + void __iomem *e_air_threshold; + void __iomem *e_mv_hor_range; + void __iomem *e_mv_ver_range; + void __iomem *e_num_dpb; + void __iomem *e_luma_dpb; + void __iomem *e_chroma_dpb; + void __iomem *e_me_buffer; + void __iomem *e_scratch_buffer_addr; + void __iomem *e_scratch_buffer_size; + void __iomem *e_tmv_buffer0; + void __iomem *e_tmv_buffer1; + void __iomem *e_ir_buffer_addr;/* v7 and v8 */ + void __iomem *e_source_first_plane_addr; + void __iomem *e_source_second_plane_addr; + void __iomem *e_source_third_plane_addr;/* v7 and v8 */ + void __iomem *e_source_first_plane_stride;/* v7 and v8 */ + void __iomem *e_source_second_plane_stride;/* v7 and v8 */ + void __iomem *e_source_third_plane_stride;/* v7 and v8 */ + void __iomem *e_stream_buffer_addr; + void __iomem *e_stream_buffer_size; + void __iomem *e_roi_buffer_addr; + void __iomem *e_param_change; + void __iomem *e_ir_size; + void __iomem *e_gop_config; + void __iomem *e_mslice_mode; + void __iomem *e_mslice_size_mb; + void __iomem *e_mslice_size_bits; + void __iomem *e_frame_insertion; + void __iomem *e_rc_frame_rate; + void __iomem *e_rc_bit_rate; + void __iomem *e_rc_roi_ctrl; + void __iomem *e_picture_tag; + void __iomem *e_bit_count_enable; + void __iomem *e_max_bit_count; + void __iomem *e_min_bit_count; + void __iomem *e_metadata_buffer_addr; + void __iomem *e_metadata_buffer_size; + void __iomem *e_encoded_source_first_plane_addr; + void __iomem *e_encoded_source_second_plane_addr; + void __iomem *e_encoded_source_third_plane_addr;/* v7 and v8 */ + void __iomem *e_stream_size; + void __iomem *e_slice_type; + void __iomem *e_picture_count; + void __iomem *e_ret_picture_tag; + void __iomem *e_stream_buffer_write_pointer; /* only v6 */ + void __iomem *e_recon_luma_dpb_addr; + void __iomem *e_recon_chroma_dpb_addr; + void __iomem *e_metadata_addr_enc_slice; + void __iomem *e_metadata_size_enc_slice; + void __iomem *e_mpeg4_options; + void __iomem *e_mpeg4_hec_period; + void __iomem *e_aspect_ratio; + void __iomem *e_extended_sar; + void __iomem *e_h264_options; + void __iomem *e_h264_options_2;/* v7 and v8 */ + void __iomem *e_h264_lf_alpha_offset; + void __iomem *e_h264_lf_beta_offset; + void __iomem *e_h264_i_period; + void __iomem *e_h264_fmo_slice_grp_map_type; + void __iomem *e_h264_fmo_num_slice_grp_minus1; + void __iomem *e_h264_fmo_slice_grp_change_dir; + void __iomem *e_h264_fmo_slice_grp_change_rate_minus1; + void __iomem *e_h264_fmo_run_length_minus1_0; + void __iomem *e_h264_aso_slice_order_0; + void __iomem *e_h264_chroma_qp_offset; + void __iomem *e_h264_num_t_layer; + void __iomem *e_h264_hierarchical_qp_layer0; + void __iomem *e_h264_frame_packing_sei_info; + void __iomem *e_h264_nal_control;/* v7 and v8 */ + void __iomem *e_mvc_frame_qp_view1; + void __iomem *e_mvc_rc_bit_rate_view1; + void __iomem *e_mvc_rc_qbound_view1; + void __iomem *e_mvc_rc_mode_view1; + void __iomem *e_mvc_inter_view_prediction_on; + void __iomem *e_vp8_options;/* v7 and v8 */ + void __iomem *e_vp8_filter_options;/* v7 and v8 */ + void __iomem *e_vp8_golden_frame_option;/* v7 and v8 */ + void __iomem *e_vp8_num_t_layer;/* v7 and v8 */ + void __iomem *e_vp8_hierarchical_qp_layer0;/* v7 and v8 */ + void __iomem *e_vp8_hierarchical_qp_layer1;/* v7 and v8 */ + void __iomem *e_vp8_hierarchical_qp_layer2;/* v7 and v8 */ + void __iomem *e_min_scratch_buffer_size; /* v10 */ + void __iomem *e_num_t_layer; /* v10 */ + void __iomem *e_hier_qp_layer0; /* v10 */ + void __iomem *e_hier_bit_rate_layer0; /* v10 */ + void __iomem *e_hevc_options; /* v10 */ + void __iomem *e_hevc_refresh_period; /* v10 */ + void __iomem *e_hevc_lf_beta_offset_div2; /* v10 */ + void __iomem *e_hevc_lf_tc_offset_div2; /* v10 */ + void __iomem *e_hevc_nal_control; /* v10 */ +}; + +struct s5p_mfc_hw_ops { + int (*alloc_dec_temp_buffers)(struct s5p_mfc_ctx *ctx); + void (*release_dec_desc_buffer)(struct s5p_mfc_ctx *ctx); + int (*alloc_codec_buffers)(struct s5p_mfc_ctx *ctx); + void (*release_codec_buffers)(struct s5p_mfc_ctx *ctx); + int (*alloc_instance_buffer)(struct s5p_mfc_ctx *ctx); + void (*release_instance_buffer)(struct s5p_mfc_ctx *ctx); + int (*alloc_dev_context_buffer)(struct s5p_mfc_dev *dev); + void (*release_dev_context_buffer)(struct s5p_mfc_dev *dev); + void (*dec_calc_dpb_size)(struct s5p_mfc_ctx *ctx); + void (*enc_calc_src_size)(struct s5p_mfc_ctx *ctx); + int (*set_enc_stream_buffer)(struct s5p_mfc_ctx *ctx, + unsigned long addr, unsigned int size); + void (*set_enc_frame_buffer)(struct s5p_mfc_ctx *ctx, + unsigned long y_addr, unsigned long c_addr); + void (*get_enc_frame_buffer)(struct s5p_mfc_ctx *ctx, + unsigned long *y_addr, unsigned long *c_addr); + void (*try_run)(struct s5p_mfc_dev *dev); + void (*clear_int_flags)(struct s5p_mfc_dev *dev); + int (*get_dspl_y_adr)(struct s5p_mfc_dev *dev); + int (*get_dec_y_adr)(struct s5p_mfc_dev *dev); + int (*get_dspl_status)(struct s5p_mfc_dev *dev); + int (*get_dec_status)(struct s5p_mfc_dev *dev); + int (*get_dec_frame_type)(struct s5p_mfc_dev *dev); + int (*get_disp_frame_type)(struct s5p_mfc_ctx *ctx); + int (*get_consumed_stream)(struct s5p_mfc_dev *dev); + int (*get_int_reason)(struct s5p_mfc_dev *dev); + int (*get_int_err)(struct s5p_mfc_dev *dev); + int (*err_dec)(unsigned int err); + int (*get_img_width)(struct s5p_mfc_dev *dev); + int (*get_img_height)(struct s5p_mfc_dev *dev); + int (*get_dpb_count)(struct s5p_mfc_dev *dev); + int (*get_mv_count)(struct s5p_mfc_dev *dev); + int (*get_inst_no)(struct s5p_mfc_dev *dev); + int (*get_enc_strm_size)(struct s5p_mfc_dev *dev); + int (*get_enc_slice_type)(struct s5p_mfc_dev *dev); + int (*get_enc_dpb_count)(struct s5p_mfc_dev *dev); + unsigned int (*get_pic_type_top)(struct s5p_mfc_ctx *ctx); + unsigned int (*get_pic_type_bot)(struct s5p_mfc_ctx *ctx); + unsigned int (*get_crop_info_h)(struct s5p_mfc_ctx *ctx); + unsigned int (*get_crop_info_v)(struct s5p_mfc_ctx *ctx); + int (*get_min_scratch_buf_size)(struct s5p_mfc_dev *dev); + int (*get_e_min_scratch_buf_size)(struct s5p_mfc_dev *dev); +}; + +void s5p_mfc_init_hw_ops(struct s5p_mfc_dev *dev); +void s5p_mfc_init_regs(struct s5p_mfc_dev *dev); +int s5p_mfc_alloc_priv_buf(struct s5p_mfc_dev *dev, unsigned int mem_ctx, + struct s5p_mfc_priv_buf *b); +void s5p_mfc_release_priv_buf(struct s5p_mfc_dev *dev, + struct s5p_mfc_priv_buf *b); +int s5p_mfc_alloc_generic_buf(struct s5p_mfc_dev *dev, unsigned int mem_ctx, + struct s5p_mfc_priv_buf *b); +void s5p_mfc_release_generic_buf(struct s5p_mfc_dev *dev, + struct s5p_mfc_priv_buf *b); + + +#endif /* S5P_MFC_OPR_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c new file mode 100644 index 000000000..28a06dc34 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.c @@ -0,0 +1,1637 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/platform/samsung/mfc5/s5p_mfc_opr_v5.c + * + * Samsung MFC (Multi Function Codec - FIMV) driver + * This file contains hw related functions. + * + * Kamil Debski, Copyright (c) 2011 Samsung Electronics + * http://www.samsung.com/ + */ + +#include "s5p_mfc_common.h" +#include "s5p_mfc_cmd.h" +#include "s5p_mfc_ctrl.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_intr.h" +#include "s5p_mfc_pm.h" +#include "s5p_mfc_opr.h" +#include "s5p_mfc_opr_v5.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define OFFSETA(x) (((x) - dev->dma_base[BANK_L_CTX]) >> MFC_OFFSET_SHIFT) +#define OFFSETB(x) (((x) - dev->dma_base[BANK_R_CTX]) >> MFC_OFFSET_SHIFT) + +/* Allocate temporary buffers for decoding */ +static int s5p_mfc_alloc_dec_temp_buffers_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf_size_v5 *buf_size = dev->variant->buf_size->priv; + int ret; + + ctx->dsc.size = buf_size->dsc; + ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->dsc); + if (ret) { + mfc_err("Failed to allocate temporary buffer\n"); + return ret; + } + + BUG_ON(ctx->dsc.dma & ((1 << MFC_BANK1_ALIGN_ORDER) - 1)); + memset(ctx->dsc.virt, 0, ctx->dsc.size); + wmb(); + return 0; +} + + +/* Release temporary buffers for decoding */ +static void s5p_mfc_release_dec_desc_buffer_v5(struct s5p_mfc_ctx *ctx) +{ + s5p_mfc_release_priv_buf(ctx->dev, &ctx->dsc); +} + +/* Allocate codec buffers */ +static int s5p_mfc_alloc_codec_buffers_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + unsigned int enc_ref_y_size = 0; + unsigned int enc_ref_c_size = 0; + unsigned int guard_width, guard_height; + int ret; + + if (ctx->type == MFCINST_DECODER) { + mfc_debug(2, "Luma size:%d Chroma size:%d MV size:%d\n", + ctx->luma_size, ctx->chroma_size, ctx->mv_size); + mfc_debug(2, "Totals bufs: %d\n", ctx->total_dpb_count); + } else if (ctx->type == MFCINST_ENCODER) { + enc_ref_y_size = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN) + * ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN); + enc_ref_y_size = ALIGN(enc_ref_y_size, S5P_FIMV_NV12MT_SALIGN); + + if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC) { + enc_ref_c_size = ALIGN(ctx->img_width, + S5P_FIMV_NV12MT_HALIGN) + * ALIGN(ctx->img_height >> 1, + S5P_FIMV_NV12MT_VALIGN); + enc_ref_c_size = ALIGN(enc_ref_c_size, + S5P_FIMV_NV12MT_SALIGN); + } else { + guard_width = ALIGN(ctx->img_width + 16, + S5P_FIMV_NV12MT_HALIGN); + guard_height = ALIGN((ctx->img_height >> 1) + 4, + S5P_FIMV_NV12MT_VALIGN); + enc_ref_c_size = ALIGN(guard_width * guard_height, + S5P_FIMV_NV12MT_SALIGN); + } + mfc_debug(2, "recon luma size: %d chroma size: %d\n", + enc_ref_y_size, enc_ref_c_size); + } else { + return -EINVAL; + } + /* Codecs have different memory requirements */ + switch (ctx->codec_mode) { + case S5P_MFC_CODEC_H264_DEC: + ctx->bank1.size = + ALIGN(S5P_FIMV_DEC_NB_IP_SIZE + + S5P_FIMV_DEC_VERT_NB_MV_SIZE, + S5P_FIMV_DEC_BUF_ALIGN); + ctx->bank2.size = ctx->total_dpb_count * ctx->mv_size; + break; + case S5P_MFC_CODEC_MPEG4_DEC: + ctx->bank1.size = + ALIGN(S5P_FIMV_DEC_NB_DCAC_SIZE + + S5P_FIMV_DEC_UPNB_MV_SIZE + + S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE + + S5P_FIMV_DEC_STX_PARSER_SIZE + + S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE, + S5P_FIMV_DEC_BUF_ALIGN); + ctx->bank2.size = 0; + break; + case S5P_MFC_CODEC_VC1RCV_DEC: + case S5P_MFC_CODEC_VC1_DEC: + ctx->bank1.size = + ALIGN(S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE + + S5P_FIMV_DEC_UPNB_MV_SIZE + + S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE + + S5P_FIMV_DEC_NB_DCAC_SIZE + + 3 * S5P_FIMV_DEC_VC1_BITPLANE_SIZE, + S5P_FIMV_DEC_BUF_ALIGN); + ctx->bank2.size = 0; + break; + case S5P_MFC_CODEC_MPEG2_DEC: + ctx->bank1.size = 0; + ctx->bank2.size = 0; + break; + case S5P_MFC_CODEC_H263_DEC: + ctx->bank1.size = + ALIGN(S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE + + S5P_FIMV_DEC_UPNB_MV_SIZE + + S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE + + S5P_FIMV_DEC_NB_DCAC_SIZE, + S5P_FIMV_DEC_BUF_ALIGN); + ctx->bank2.size = 0; + break; + case S5P_MFC_CODEC_H264_ENC: + ctx->bank1.size = (enc_ref_y_size * 2) + + S5P_FIMV_ENC_UPMV_SIZE + + S5P_FIMV_ENC_COLFLG_SIZE + + S5P_FIMV_ENC_INTRAMD_SIZE + + S5P_FIMV_ENC_NBORINFO_SIZE; + ctx->bank2.size = (enc_ref_y_size * 2) + + (enc_ref_c_size * 4) + + S5P_FIMV_ENC_INTRAPRED_SIZE; + break; + case S5P_MFC_CODEC_MPEG4_ENC: + ctx->bank1.size = (enc_ref_y_size * 2) + + S5P_FIMV_ENC_UPMV_SIZE + + S5P_FIMV_ENC_COLFLG_SIZE + + S5P_FIMV_ENC_ACDCCOEF_SIZE; + ctx->bank2.size = (enc_ref_y_size * 2) + + (enc_ref_c_size * 4); + break; + case S5P_MFC_CODEC_H263_ENC: + ctx->bank1.size = (enc_ref_y_size * 2) + + S5P_FIMV_ENC_UPMV_SIZE + + S5P_FIMV_ENC_ACDCCOEF_SIZE; + ctx->bank2.size = (enc_ref_y_size * 2) + + (enc_ref_c_size * 4); + break; + default: + break; + } + /* Allocate only if memory from bank 1 is necessary */ + if (ctx->bank1.size > 0) { + + ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->bank1); + if (ret) { + mfc_err("Failed to allocate Bank1 temporary buffer\n"); + return ret; + } + BUG_ON(ctx->bank1.dma & ((1 << MFC_BANK1_ALIGN_ORDER) - 1)); + } + /* Allocate only if memory from bank 2 is necessary */ + if (ctx->bank2.size > 0) { + ret = s5p_mfc_alloc_priv_buf(dev, BANK_R_CTX, &ctx->bank2); + if (ret) { + mfc_err("Failed to allocate Bank2 temporary buffer\n"); + s5p_mfc_release_priv_buf(ctx->dev, &ctx->bank1); + return ret; + } + BUG_ON(ctx->bank2.dma & ((1 << MFC_BANK2_ALIGN_ORDER) - 1)); + } + return 0; +} + +/* Release buffers allocated for codec */ +static void s5p_mfc_release_codec_buffers_v5(struct s5p_mfc_ctx *ctx) +{ + s5p_mfc_release_priv_buf(ctx->dev, &ctx->bank1); + s5p_mfc_release_priv_buf(ctx->dev, &ctx->bank2); +} + +/* Allocate memory for instance data buffer */ +static int s5p_mfc_alloc_instance_buffer_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf_size_v5 *buf_size = dev->variant->buf_size->priv; + int ret; + + if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC || + ctx->codec_mode == S5P_MFC_CODEC_H264_ENC) + ctx->ctx.size = buf_size->h264_ctx; + else + ctx->ctx.size = buf_size->non_h264_ctx; + + ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->ctx); + if (ret) { + mfc_err("Failed to allocate instance buffer\n"); + return ret; + } + ctx->ctx.ofs = OFFSETA(ctx->ctx.dma); + + /* Zero content of the allocated memory */ + memset(ctx->ctx.virt, 0, ctx->ctx.size); + wmb(); + + /* Initialize shared memory */ + ctx->shm.size = buf_size->shm; + ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->shm); + if (ret) { + mfc_err("Failed to allocate shared memory buffer\n"); + s5p_mfc_release_priv_buf(dev, &ctx->ctx); + return ret; + } + + /* shared memory offset only keeps the offset from base (port a) */ + ctx->shm.ofs = ctx->shm.dma - dev->dma_base[BANK_L_CTX]; + BUG_ON(ctx->shm.ofs & ((1 << MFC_BANK1_ALIGN_ORDER) - 1)); + + memset(ctx->shm.virt, 0, buf_size->shm); + wmb(); + return 0; +} + +/* Release instance buffer */ +static void s5p_mfc_release_instance_buffer_v5(struct s5p_mfc_ctx *ctx) +{ + s5p_mfc_release_priv_buf(ctx->dev, &ctx->ctx); + s5p_mfc_release_priv_buf(ctx->dev, &ctx->shm); +} + +static int s5p_mfc_alloc_dev_context_buffer_v5(struct s5p_mfc_dev *dev) +{ + /* NOP */ + + return 0; +} + +static void s5p_mfc_release_dev_context_buffer_v5(struct s5p_mfc_dev *dev) +{ + /* NOP */ +} + +static void s5p_mfc_write_info_v5(struct s5p_mfc_ctx *ctx, unsigned int data, + unsigned int ofs) +{ + *(u32 *)(ctx->shm.virt + ofs) = data; + wmb(); +} + +static unsigned int s5p_mfc_read_info_v5(struct s5p_mfc_ctx *ctx, + unsigned long ofs) +{ + rmb(); + return *(u32 *)(ctx->shm.virt + ofs); +} + +static void s5p_mfc_dec_calc_dpb_size_v5(struct s5p_mfc_ctx *ctx) +{ + unsigned int guard_width, guard_height; + + ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN); + ctx->buf_height = ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN); + mfc_debug(2, + "SEQ Done: Movie dimensions %dx%d, buffer dimensions: %dx%d\n", + ctx->img_width, ctx->img_height, ctx->buf_width, + ctx->buf_height); + + if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC) { + ctx->luma_size = ALIGN(ctx->buf_width * ctx->buf_height, + S5P_FIMV_DEC_BUF_ALIGN); + ctx->chroma_size = ALIGN(ctx->buf_width * + ALIGN((ctx->img_height >> 1), + S5P_FIMV_NV12MT_VALIGN), + S5P_FIMV_DEC_BUF_ALIGN); + ctx->mv_size = ALIGN(ctx->buf_width * + ALIGN((ctx->buf_height >> 2), + S5P_FIMV_NV12MT_VALIGN), + S5P_FIMV_DEC_BUF_ALIGN); + } else { + guard_width = + ALIGN(ctx->img_width + 24, S5P_FIMV_NV12MT_HALIGN); + guard_height = + ALIGN(ctx->img_height + 16, S5P_FIMV_NV12MT_VALIGN); + ctx->luma_size = ALIGN(guard_width * guard_height, + S5P_FIMV_DEC_BUF_ALIGN); + + guard_width = + ALIGN(ctx->img_width + 16, S5P_FIMV_NV12MT_HALIGN); + guard_height = + ALIGN((ctx->img_height >> 1) + 4, + S5P_FIMV_NV12MT_VALIGN); + ctx->chroma_size = ALIGN(guard_width * guard_height, + S5P_FIMV_DEC_BUF_ALIGN); + + ctx->mv_size = 0; + } +} + +static void s5p_mfc_enc_calc_src_size_v5(struct s5p_mfc_ctx *ctx) +{ + if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12M) { + ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN); + + ctx->luma_size = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN) + * ALIGN(ctx->img_height, S5P_FIMV_NV12M_LVALIGN); + ctx->chroma_size = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN) + * ALIGN((ctx->img_height >> 1), S5P_FIMV_NV12M_CVALIGN); + + ctx->luma_size = ALIGN(ctx->luma_size, S5P_FIMV_NV12M_SALIGN); + ctx->chroma_size = + ALIGN(ctx->chroma_size, S5P_FIMV_NV12M_SALIGN); + } else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12MT) { + ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN); + + ctx->luma_size = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN) + * ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN); + ctx->chroma_size = + ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN) + * ALIGN((ctx->img_height >> 1), S5P_FIMV_NV12MT_VALIGN); + + ctx->luma_size = ALIGN(ctx->luma_size, S5P_FIMV_NV12MT_SALIGN); + ctx->chroma_size = + ALIGN(ctx->chroma_size, S5P_FIMV_NV12MT_SALIGN); + } +} + +/* Set registers for decoding temporary buffers */ +static void s5p_mfc_set_dec_desc_buffer(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf_size_v5 *buf_size = dev->variant->buf_size->priv; + + mfc_write(dev, OFFSETA(ctx->dsc.dma), S5P_FIMV_SI_CH0_DESC_ADR); + mfc_write(dev, buf_size->dsc, S5P_FIMV_SI_CH0_DESC_SIZE); +} + +/* Set registers for shared buffer */ +static void s5p_mfc_set_shared_buffer(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + mfc_write(dev, ctx->shm.ofs, S5P_FIMV_SI_CH0_HOST_WR_ADR); +} + +/* Set registers for decoding stream buffer */ +static int s5p_mfc_set_dec_stream_buffer_v5(struct s5p_mfc_ctx *ctx, + int buf_addr, unsigned int start_num_byte, + unsigned int buf_size) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + mfc_write(dev, OFFSETA(buf_addr), S5P_FIMV_SI_CH0_SB_ST_ADR); + mfc_write(dev, ctx->dec_src_buf_size, S5P_FIMV_SI_CH0_CPB_SIZE); + mfc_write(dev, buf_size, S5P_FIMV_SI_CH0_SB_FRM_SIZE); + s5p_mfc_write_info_v5(ctx, start_num_byte, START_BYTE_NUM); + return 0; +} + +/* Set decoding frame buffer */ +static int s5p_mfc_set_dec_frame_buffer_v5(struct s5p_mfc_ctx *ctx) +{ + unsigned int frame_size_lu, i; + unsigned int frame_size_ch, frame_size_mv; + struct s5p_mfc_dev *dev = ctx->dev; + unsigned int dpb; + size_t buf_addr1, buf_addr2; + int buf_size1, buf_size2; + + buf_addr1 = ctx->bank1.dma; + buf_size1 = ctx->bank1.size; + buf_addr2 = ctx->bank2.dma; + buf_size2 = ctx->bank2.size; + dpb = mfc_read(dev, S5P_FIMV_SI_CH0_DPB_CONF_CTRL) & + ~S5P_FIMV_DPB_COUNT_MASK; + mfc_write(dev, ctx->total_dpb_count | dpb, + S5P_FIMV_SI_CH0_DPB_CONF_CTRL); + s5p_mfc_set_shared_buffer(ctx); + switch (ctx->codec_mode) { + case S5P_MFC_CODEC_H264_DEC: + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_H264_VERT_NB_MV_ADR); + buf_addr1 += S5P_FIMV_DEC_VERT_NB_MV_SIZE; + buf_size1 -= S5P_FIMV_DEC_VERT_NB_MV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H264_NB_IP_ADR); + buf_addr1 += S5P_FIMV_DEC_NB_IP_SIZE; + buf_size1 -= S5P_FIMV_DEC_NB_IP_SIZE; + break; + case S5P_MFC_CODEC_MPEG4_DEC: + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_NB_DCAC_ADR); + buf_addr1 += S5P_FIMV_DEC_NB_DCAC_SIZE; + buf_size1 -= S5P_FIMV_DEC_NB_DCAC_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_UP_NB_MV_ADR); + buf_addr1 += S5P_FIMV_DEC_UPNB_MV_SIZE; + buf_size1 -= S5P_FIMV_DEC_UPNB_MV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_SA_MV_ADR); + buf_addr1 += S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE; + buf_size1 -= S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_SP_ADR); + buf_addr1 += S5P_FIMV_DEC_STX_PARSER_SIZE; + buf_size1 -= S5P_FIMV_DEC_STX_PARSER_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_OT_LINE_ADR); + buf_addr1 += S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE; + buf_size1 -= S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE; + break; + case S5P_MFC_CODEC_H263_DEC: + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_OT_LINE_ADR); + buf_addr1 += S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE; + buf_size1 -= S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_UP_NB_MV_ADR); + buf_addr1 += S5P_FIMV_DEC_UPNB_MV_SIZE; + buf_size1 -= S5P_FIMV_DEC_UPNB_MV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_SA_MV_ADR); + buf_addr1 += S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE; + buf_size1 -= S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_NB_DCAC_ADR); + buf_addr1 += S5P_FIMV_DEC_NB_DCAC_SIZE; + buf_size1 -= S5P_FIMV_DEC_NB_DCAC_SIZE; + break; + case S5P_MFC_CODEC_VC1_DEC: + case S5P_MFC_CODEC_VC1RCV_DEC: + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_NB_DCAC_ADR); + buf_addr1 += S5P_FIMV_DEC_NB_DCAC_SIZE; + buf_size1 -= S5P_FIMV_DEC_NB_DCAC_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_OT_LINE_ADR); + buf_addr1 += S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE; + buf_size1 -= S5P_FIMV_DEC_OVERLAP_TRANSFORM_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_UP_NB_MV_ADR); + buf_addr1 += S5P_FIMV_DEC_UPNB_MV_SIZE; + buf_size1 -= S5P_FIMV_DEC_UPNB_MV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_SA_MV_ADR); + buf_addr1 += S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE; + buf_size1 -= S5P_FIMV_DEC_SUB_ANCHOR_MV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_BITPLANE3_ADR); + buf_addr1 += S5P_FIMV_DEC_VC1_BITPLANE_SIZE; + buf_size1 -= S5P_FIMV_DEC_VC1_BITPLANE_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_BITPLANE2_ADR); + buf_addr1 += S5P_FIMV_DEC_VC1_BITPLANE_SIZE; + buf_size1 -= S5P_FIMV_DEC_VC1_BITPLANE_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_VC1_BITPLANE1_ADR); + buf_addr1 += S5P_FIMV_DEC_VC1_BITPLANE_SIZE; + buf_size1 -= S5P_FIMV_DEC_VC1_BITPLANE_SIZE; + break; + case S5P_MFC_CODEC_MPEG2_DEC: + break; + default: + mfc_err("Unknown codec for decoding (%x)\n", + ctx->codec_mode); + return -EINVAL; + } + frame_size_lu = ctx->luma_size; + frame_size_ch = ctx->chroma_size; + frame_size_mv = ctx->mv_size; + mfc_debug(2, "Frm size: %d ch: %d mv: %d\n", frame_size_lu, frame_size_ch, + frame_size_mv); + for (i = 0; i < ctx->total_dpb_count; i++) { + /* Bank2 */ + mfc_debug(2, "Luma %d: %zx\n", i, + ctx->dst_bufs[i].cookie.raw.luma); + mfc_write(dev, OFFSETB(ctx->dst_bufs[i].cookie.raw.luma), + S5P_FIMV_DEC_LUMA_ADR + i * 4); + mfc_debug(2, "\tChroma %d: %zx\n", i, + ctx->dst_bufs[i].cookie.raw.chroma); + mfc_write(dev, OFFSETA(ctx->dst_bufs[i].cookie.raw.chroma), + S5P_FIMV_DEC_CHROMA_ADR + i * 4); + if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC) { + mfc_debug(2, "\tBuf2: %zx, size: %d\n", + buf_addr2, buf_size2); + mfc_write(dev, OFFSETB(buf_addr2), + S5P_FIMV_H264_MV_ADR + i * 4); + buf_addr2 += frame_size_mv; + buf_size2 -= frame_size_mv; + } + } + mfc_debug(2, "Buf1: %zu, buf_size1: %d\n", buf_addr1, buf_size1); + mfc_debug(2, "Buf 1/2 size after: %d/%d (frames %d)\n", + buf_size1, buf_size2, ctx->total_dpb_count); + if (buf_size1 < 0 || buf_size2 < 0) { + mfc_debug(2, "Not enough memory has been allocated\n"); + return -ENOMEM; + } + s5p_mfc_write_info_v5(ctx, frame_size_lu, ALLOC_LUMA_DPB_SIZE); + s5p_mfc_write_info_v5(ctx, frame_size_ch, ALLOC_CHROMA_DPB_SIZE); + if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC) + s5p_mfc_write_info_v5(ctx, frame_size_mv, ALLOC_MV_SIZE); + mfc_write(dev, ((S5P_FIMV_CH_INIT_BUFS & S5P_FIMV_CH_MASK) + << S5P_FIMV_CH_SHIFT) | (ctx->inst_no), + S5P_FIMV_SI_CH0_INST_ID); + return 0; +} + +/* Set registers for encoding stream buffer */ +static int s5p_mfc_set_enc_stream_buffer_v5(struct s5p_mfc_ctx *ctx, + unsigned long addr, unsigned int size) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + mfc_write(dev, OFFSETA(addr), S5P_FIMV_ENC_SI_CH0_SB_ADR); + mfc_write(dev, size, S5P_FIMV_ENC_SI_CH0_SB_SIZE); + return 0; +} + +static void s5p_mfc_set_enc_frame_buffer_v5(struct s5p_mfc_ctx *ctx, + unsigned long y_addr, unsigned long c_addr) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + mfc_write(dev, OFFSETB(y_addr), S5P_FIMV_ENC_SI_CH0_CUR_Y_ADR); + mfc_write(dev, OFFSETB(c_addr), S5P_FIMV_ENC_SI_CH0_CUR_C_ADR); +} + +static void s5p_mfc_get_enc_frame_buffer_v5(struct s5p_mfc_ctx *ctx, + unsigned long *y_addr, unsigned long *c_addr) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + *y_addr = dev->dma_base[BANK_R_CTX] + + (mfc_read(dev, S5P_FIMV_ENCODED_Y_ADDR) << MFC_OFFSET_SHIFT); + *c_addr = dev->dma_base[BANK_R_CTX] + + (mfc_read(dev, S5P_FIMV_ENCODED_C_ADDR) << MFC_OFFSET_SHIFT); +} + +/* Set encoding ref & codec buffer */ +static int s5p_mfc_set_enc_ref_buffer_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + size_t buf_addr1, buf_addr2; + size_t buf_size1, buf_size2; + unsigned int enc_ref_y_size, enc_ref_c_size; + unsigned int guard_width, guard_height; + int i; + + buf_addr1 = ctx->bank1.dma; + buf_size1 = ctx->bank1.size; + buf_addr2 = ctx->bank2.dma; + buf_size2 = ctx->bank2.size; + enc_ref_y_size = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN) + * ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN); + enc_ref_y_size = ALIGN(enc_ref_y_size, S5P_FIMV_NV12MT_SALIGN); + if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC) { + enc_ref_c_size = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN) + * ALIGN((ctx->img_height >> 1), S5P_FIMV_NV12MT_VALIGN); + enc_ref_c_size = ALIGN(enc_ref_c_size, S5P_FIMV_NV12MT_SALIGN); + } else { + guard_width = ALIGN(ctx->img_width + 16, + S5P_FIMV_NV12MT_HALIGN); + guard_height = ALIGN((ctx->img_height >> 1) + 4, + S5P_FIMV_NV12MT_VALIGN); + enc_ref_c_size = ALIGN(guard_width * guard_height, + S5P_FIMV_NV12MT_SALIGN); + } + mfc_debug(2, "buf_size1: %zu, buf_size2: %zu\n", buf_size1, buf_size2); + switch (ctx->codec_mode) { + case S5P_MFC_CODEC_H264_ENC: + for (i = 0; i < 2; i++) { + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_ENC_REF0_LUMA_ADR + (4 * i)); + buf_addr1 += enc_ref_y_size; + buf_size1 -= enc_ref_y_size; + + mfc_write(dev, OFFSETB(buf_addr2), + S5P_FIMV_ENC_REF2_LUMA_ADR + (4 * i)); + buf_addr2 += enc_ref_y_size; + buf_size2 -= enc_ref_y_size; + } + for (i = 0; i < 4; i++) { + mfc_write(dev, OFFSETB(buf_addr2), + S5P_FIMV_ENC_REF0_CHROMA_ADR + (4 * i)); + buf_addr2 += enc_ref_c_size; + buf_size2 -= enc_ref_c_size; + } + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H264_UP_MV_ADR); + buf_addr1 += S5P_FIMV_ENC_UPMV_SIZE; + buf_size1 -= S5P_FIMV_ENC_UPMV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_H264_COZERO_FLAG_ADR); + buf_addr1 += S5P_FIMV_ENC_COLFLG_SIZE; + buf_size1 -= S5P_FIMV_ENC_COLFLG_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_H264_UP_INTRA_MD_ADR); + buf_addr1 += S5P_FIMV_ENC_INTRAMD_SIZE; + buf_size1 -= S5P_FIMV_ENC_INTRAMD_SIZE; + mfc_write(dev, OFFSETB(buf_addr2), + S5P_FIMV_H264_UP_INTRA_PRED_ADR); + buf_addr2 += S5P_FIMV_ENC_INTRAPRED_SIZE; + buf_size2 -= S5P_FIMV_ENC_INTRAPRED_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_H264_NBOR_INFO_ADR); + buf_addr1 += S5P_FIMV_ENC_NBORINFO_SIZE; + buf_size1 -= S5P_FIMV_ENC_NBORINFO_SIZE; + mfc_debug(2, "buf_size1: %zu, buf_size2: %zu\n", + buf_size1, buf_size2); + break; + case S5P_MFC_CODEC_MPEG4_ENC: + for (i = 0; i < 2; i++) { + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_ENC_REF0_LUMA_ADR + (4 * i)); + buf_addr1 += enc_ref_y_size; + buf_size1 -= enc_ref_y_size; + mfc_write(dev, OFFSETB(buf_addr2), + S5P_FIMV_ENC_REF2_LUMA_ADR + (4 * i)); + buf_addr2 += enc_ref_y_size; + buf_size2 -= enc_ref_y_size; + } + for (i = 0; i < 4; i++) { + mfc_write(dev, OFFSETB(buf_addr2), + S5P_FIMV_ENC_REF0_CHROMA_ADR + (4 * i)); + buf_addr2 += enc_ref_c_size; + buf_size2 -= enc_ref_c_size; + } + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_MPEG4_UP_MV_ADR); + buf_addr1 += S5P_FIMV_ENC_UPMV_SIZE; + buf_size1 -= S5P_FIMV_ENC_UPMV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_MPEG4_COZERO_FLAG_ADR); + buf_addr1 += S5P_FIMV_ENC_COLFLG_SIZE; + buf_size1 -= S5P_FIMV_ENC_COLFLG_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_MPEG4_ACDC_COEF_ADR); + buf_addr1 += S5P_FIMV_ENC_ACDCCOEF_SIZE; + buf_size1 -= S5P_FIMV_ENC_ACDCCOEF_SIZE; + mfc_debug(2, "buf_size1: %zu, buf_size2: %zu\n", + buf_size1, buf_size2); + break; + case S5P_MFC_CODEC_H263_ENC: + for (i = 0; i < 2; i++) { + mfc_write(dev, OFFSETA(buf_addr1), + S5P_FIMV_ENC_REF0_LUMA_ADR + (4 * i)); + buf_addr1 += enc_ref_y_size; + buf_size1 -= enc_ref_y_size; + mfc_write(dev, OFFSETB(buf_addr2), + S5P_FIMV_ENC_REF2_LUMA_ADR + (4 * i)); + buf_addr2 += enc_ref_y_size; + buf_size2 -= enc_ref_y_size; + } + for (i = 0; i < 4; i++) { + mfc_write(dev, OFFSETB(buf_addr2), + S5P_FIMV_ENC_REF0_CHROMA_ADR + (4 * i)); + buf_addr2 += enc_ref_c_size; + buf_size2 -= enc_ref_c_size; + } + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_UP_MV_ADR); + buf_addr1 += S5P_FIMV_ENC_UPMV_SIZE; + buf_size1 -= S5P_FIMV_ENC_UPMV_SIZE; + mfc_write(dev, OFFSETA(buf_addr1), S5P_FIMV_H263_ACDC_COEF_ADR); + buf_addr1 += S5P_FIMV_ENC_ACDCCOEF_SIZE; + buf_size1 -= S5P_FIMV_ENC_ACDCCOEF_SIZE; + mfc_debug(2, "buf_size1: %zu, buf_size2: %zu\n", + buf_size1, buf_size2); + break; + default: + mfc_err("Unknown codec set for encoding: %d\n", + ctx->codec_mode); + return -EINVAL; + } + return 0; +} + +static int s5p_mfc_set_enc_params(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + unsigned int reg; + unsigned int shm; + + /* width */ + mfc_write(dev, ctx->img_width, S5P_FIMV_ENC_HSIZE_PX); + /* height */ + mfc_write(dev, ctx->img_height, S5P_FIMV_ENC_VSIZE_PX); + /* pictype : enable, IDR period */ + reg = mfc_read(dev, S5P_FIMV_ENC_PIC_TYPE_CTRL); + reg |= (1 << 18); + reg &= ~(0xFFFF); + reg |= p->gop_size; + mfc_write(dev, reg, S5P_FIMV_ENC_PIC_TYPE_CTRL); + mfc_write(dev, 0, S5P_FIMV_ENC_B_RECON_WRITE_ON); + /* multi-slice control */ + /* multi-slice MB number or bit size */ + mfc_write(dev, p->slice_mode, S5P_FIMV_ENC_MSLICE_CTRL); + if (p->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB) { + mfc_write(dev, p->slice_mb, S5P_FIMV_ENC_MSLICE_MB); + } else if (p->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES) { + mfc_write(dev, p->slice_bit, S5P_FIMV_ENC_MSLICE_BIT); + } else { + mfc_write(dev, 0, S5P_FIMV_ENC_MSLICE_MB); + mfc_write(dev, 0, S5P_FIMV_ENC_MSLICE_BIT); + } + /* cyclic intra refresh */ + mfc_write(dev, p->intra_refresh_mb, S5P_FIMV_ENC_CIR_CTRL); + /* memory structure cur. frame */ + if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12M) + mfc_write(dev, 0, S5P_FIMV_ENC_MAP_FOR_CUR); + else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12MT) + mfc_write(dev, 3, S5P_FIMV_ENC_MAP_FOR_CUR); + /* padding control & value */ + reg = mfc_read(dev, S5P_FIMV_ENC_PADDING_CTRL); + if (p->pad) { + /** enable */ + reg |= (1UL << 31); + /** cr value */ + reg &= ~(0xFF << 16); + reg |= (p->pad_cr << 16); + /** cb value */ + reg &= ~(0xFF << 8); + reg |= (p->pad_cb << 8); + /** y value */ + reg &= ~(0xFF); + reg |= (p->pad_luma); + } else { + /** disable & all value clear */ + reg = 0; + } + mfc_write(dev, reg, S5P_FIMV_ENC_PADDING_CTRL); + /* rate control config. */ + reg = mfc_read(dev, S5P_FIMV_ENC_RC_CONFIG); + /** frame-level rate control */ + reg &= ~(0x1 << 9); + reg |= (p->rc_frame << 9); + mfc_write(dev, reg, S5P_FIMV_ENC_RC_CONFIG); + /* bit rate */ + if (p->rc_frame) + mfc_write(dev, p->rc_bitrate, + S5P_FIMV_ENC_RC_BIT_RATE); + else + mfc_write(dev, 0, S5P_FIMV_ENC_RC_BIT_RATE); + /* reaction coefficient */ + if (p->rc_frame) + mfc_write(dev, p->rc_reaction_coeff, S5P_FIMV_ENC_RC_RPARA); + shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL); + /* seq header ctrl */ + shm &= ~(0x1 << 3); + shm |= (p->seq_hdr_mode << 3); + /* frame skip mode */ + shm &= ~(0x3 << 1); + shm |= (p->frame_skip_mode << 1); + s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL); + /* fixed target bit */ + s5p_mfc_write_info_v5(ctx, p->fixed_target_bit, RC_CONTROL_CONFIG); + return 0; +} + +static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_h264_enc_params *p_264 = &p->codec.h264; + unsigned int reg; + unsigned int shm; + + s5p_mfc_set_enc_params(ctx); + /* pictype : number of B */ + reg = mfc_read(dev, S5P_FIMV_ENC_PIC_TYPE_CTRL); + /* num_b_frame - 0 ~ 2 */ + reg &= ~(0x3 << 16); + reg |= (p->num_b_frame << 16); + mfc_write(dev, reg, S5P_FIMV_ENC_PIC_TYPE_CTRL); + /* profile & level */ + reg = mfc_read(dev, S5P_FIMV_ENC_PROFILE); + /* level */ + reg &= ~(0xFF << 8); + reg |= (p_264->level << 8); + /* profile - 0 ~ 2 */ + reg &= ~(0x3F); + reg |= p_264->profile; + mfc_write(dev, reg, S5P_FIMV_ENC_PROFILE); + /* interlace */ + mfc_write(dev, p_264->interlace, S5P_FIMV_ENC_PIC_STRUCT); + /* height */ + if (p_264->interlace) + mfc_write(dev, ctx->img_height >> 1, S5P_FIMV_ENC_VSIZE_PX); + /* loopfilter ctrl */ + mfc_write(dev, p_264->loop_filter_mode, S5P_FIMV_ENC_LF_CTRL); + /* loopfilter alpha offset */ + if (p_264->loop_filter_alpha < 0) { + reg = 0x10; + reg |= (0xFF - p_264->loop_filter_alpha) + 1; + } else { + reg = 0x00; + reg |= (p_264->loop_filter_alpha & 0xF); + } + mfc_write(dev, reg, S5P_FIMV_ENC_ALPHA_OFF); + /* loopfilter beta offset */ + if (p_264->loop_filter_beta < 0) { + reg = 0x10; + reg |= (0xFF - p_264->loop_filter_beta) + 1; + } else { + reg = 0x00; + reg |= (p_264->loop_filter_beta & 0xF); + } + mfc_write(dev, reg, S5P_FIMV_ENC_BETA_OFF); + /* entropy coding mode */ + if (p_264->entropy_mode == V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC) + mfc_write(dev, 1, S5P_FIMV_ENC_H264_ENTROPY_MODE); + else + mfc_write(dev, 0, S5P_FIMV_ENC_H264_ENTROPY_MODE); + /* number of ref. picture */ + reg = mfc_read(dev, S5P_FIMV_ENC_H264_NUM_OF_REF); + /* num of ref. pictures of P */ + reg &= ~(0x3 << 5); + reg |= (p_264->num_ref_pic_4p << 5); + /* max number of ref. pictures */ + reg &= ~(0x1F); + reg |= p_264->max_ref_pic; + mfc_write(dev, reg, S5P_FIMV_ENC_H264_NUM_OF_REF); + /* 8x8 transform enable */ + mfc_write(dev, p_264->_8x8_transform, S5P_FIMV_ENC_H264_TRANS_FLAG); + /* rate control config. */ + reg = mfc_read(dev, S5P_FIMV_ENC_RC_CONFIG); + /* macroblock level rate control */ + reg &= ~(0x1 << 8); + reg |= (p->rc_mb << 8); + /* frame QP */ + reg &= ~(0x3F); + reg |= p_264->rc_frame_qp; + mfc_write(dev, reg, S5P_FIMV_ENC_RC_CONFIG); + /* frame rate */ + if (p->rc_frame && p->rc_framerate_denom) + mfc_write(dev, p->rc_framerate_num * 1000 + / p->rc_framerate_denom, S5P_FIMV_ENC_RC_FRAME_RATE); + else + mfc_write(dev, 0, S5P_FIMV_ENC_RC_FRAME_RATE); + /* max & min value of QP */ + reg = mfc_read(dev, S5P_FIMV_ENC_RC_QBOUND); + /* max QP */ + reg &= ~(0x3F << 8); + reg |= (p_264->rc_max_qp << 8); + /* min QP */ + reg &= ~(0x3F); + reg |= p_264->rc_min_qp; + mfc_write(dev, reg, S5P_FIMV_ENC_RC_QBOUND); + /* macroblock adaptive scaling features */ + if (p->rc_mb) { + reg = mfc_read(dev, S5P_FIMV_ENC_RC_MB_CTRL); + /* dark region */ + reg &= ~(0x1 << 3); + reg |= (p_264->rc_mb_dark << 3); + /* smooth region */ + reg &= ~(0x1 << 2); + reg |= (p_264->rc_mb_smooth << 2); + /* static region */ + reg &= ~(0x1 << 1); + reg |= (p_264->rc_mb_static << 1); + /* high activity region */ + reg &= ~(0x1); + reg |= p_264->rc_mb_activity; + mfc_write(dev, reg, S5P_FIMV_ENC_RC_MB_CTRL); + } + if (!p->rc_frame && !p->rc_mb) { + shm = s5p_mfc_read_info_v5(ctx, P_B_FRAME_QP); + shm &= ~(0xFFF); + shm |= ((p_264->rc_b_frame_qp & 0x3F) << 6); + shm |= (p_264->rc_p_frame_qp & 0x3F); + s5p_mfc_write_info_v5(ctx, shm, P_B_FRAME_QP); + } + /* extended encoder ctrl */ + shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL); + /* AR VUI control */ + shm &= ~(0x1 << 15); + shm |= (p_264->vui_sar << 1); + s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL); + if (p_264->vui_sar) { + /* aspect ration IDC */ + shm = s5p_mfc_read_info_v5(ctx, SAMPLE_ASPECT_RATIO_IDC); + shm &= ~(0xFF); + shm |= p_264->vui_sar_idc; + s5p_mfc_write_info_v5(ctx, shm, SAMPLE_ASPECT_RATIO_IDC); + if (p_264->vui_sar_idc == 0xFF) { + /* sample AR info */ + shm = s5p_mfc_read_info_v5(ctx, EXTENDED_SAR); + shm &= ~(0xFFFFFFFF); + shm |= p_264->vui_ext_sar_width << 16; + shm |= p_264->vui_ext_sar_height; + s5p_mfc_write_info_v5(ctx, shm, EXTENDED_SAR); + } + } + /* intra picture period for H.264 */ + shm = s5p_mfc_read_info_v5(ctx, H264_I_PERIOD); + /* control */ + shm &= ~(0x1 << 16); + shm |= (p_264->open_gop << 16); + /* value */ + if (p_264->open_gop) { + shm &= ~(0xFFFF); + shm |= p_264->open_gop_size; + } + s5p_mfc_write_info_v5(ctx, shm, H264_I_PERIOD); + /* extended encoder ctrl */ + shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL); + /* vbv buffer size */ + if (p->frame_skip_mode == + V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { + shm &= ~(0xFFFF << 16); + shm |= (p_264->cpb_size << 16); + } + s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL); + return 0; +} + +static int s5p_mfc_set_enc_params_mpeg4(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_mpeg4_enc_params *p_mpeg4 = &p->codec.mpeg4; + unsigned int reg; + unsigned int shm; + unsigned int framerate; + + s5p_mfc_set_enc_params(ctx); + /* pictype : number of B */ + reg = mfc_read(dev, S5P_FIMV_ENC_PIC_TYPE_CTRL); + /* num_b_frame - 0 ~ 2 */ + reg &= ~(0x3 << 16); + reg |= (p->num_b_frame << 16); + mfc_write(dev, reg, S5P_FIMV_ENC_PIC_TYPE_CTRL); + /* profile & level */ + reg = mfc_read(dev, S5P_FIMV_ENC_PROFILE); + /* level */ + reg &= ~(0xFF << 8); + reg |= (p_mpeg4->level << 8); + /* profile - 0 ~ 2 */ + reg &= ~(0x3F); + reg |= p_mpeg4->profile; + mfc_write(dev, reg, S5P_FIMV_ENC_PROFILE); + /* quarter_pixel */ + mfc_write(dev, p_mpeg4->quarter_pixel, S5P_FIMV_ENC_MPEG4_QUART_PXL); + /* qp */ + if (!p->rc_frame) { + shm = s5p_mfc_read_info_v5(ctx, P_B_FRAME_QP); + shm &= ~(0xFFF); + shm |= ((p_mpeg4->rc_b_frame_qp & 0x3F) << 6); + shm |= (p_mpeg4->rc_p_frame_qp & 0x3F); + s5p_mfc_write_info_v5(ctx, shm, P_B_FRAME_QP); + } + /* frame rate */ + if (p->rc_frame) { + if (p->rc_framerate_denom > 0) { + framerate = p->rc_framerate_num * 1000 / + p->rc_framerate_denom; + mfc_write(dev, framerate, + S5P_FIMV_ENC_RC_FRAME_RATE); + shm = s5p_mfc_read_info_v5(ctx, RC_VOP_TIMING); + shm &= ~(0xFFFFFFFF); + shm |= (1UL << 31); + shm |= ((p->rc_framerate_num & 0x7FFF) << 16); + shm |= (p->rc_framerate_denom & 0xFFFF); + s5p_mfc_write_info_v5(ctx, shm, RC_VOP_TIMING); + } + } else { + mfc_write(dev, 0, S5P_FIMV_ENC_RC_FRAME_RATE); + } + /* rate control config. */ + reg = mfc_read(dev, S5P_FIMV_ENC_RC_CONFIG); + /* frame QP */ + reg &= ~(0x3F); + reg |= p_mpeg4->rc_frame_qp; + mfc_write(dev, reg, S5P_FIMV_ENC_RC_CONFIG); + /* max & min value of QP */ + reg = mfc_read(dev, S5P_FIMV_ENC_RC_QBOUND); + /* max QP */ + reg &= ~(0x3F << 8); + reg |= (p_mpeg4->rc_max_qp << 8); + /* min QP */ + reg &= ~(0x3F); + reg |= p_mpeg4->rc_min_qp; + mfc_write(dev, reg, S5P_FIMV_ENC_RC_QBOUND); + /* extended encoder ctrl */ + shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL); + /* vbv buffer size */ + if (p->frame_skip_mode == + V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { + shm &= ~(0xFFFF << 16); + shm |= (p->vbv_size << 16); + } + s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL); + return 0; +} + +static int s5p_mfc_set_enc_params_h263(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_mpeg4_enc_params *p_h263 = &p->codec.mpeg4; + unsigned int reg; + unsigned int shm; + + s5p_mfc_set_enc_params(ctx); + /* qp */ + if (!p->rc_frame) { + shm = s5p_mfc_read_info_v5(ctx, P_B_FRAME_QP); + shm &= ~(0xFFF); + shm |= (p_h263->rc_p_frame_qp & 0x3F); + s5p_mfc_write_info_v5(ctx, shm, P_B_FRAME_QP); + } + /* frame rate */ + if (p->rc_frame && p->rc_framerate_denom) + mfc_write(dev, p->rc_framerate_num * 1000 + / p->rc_framerate_denom, S5P_FIMV_ENC_RC_FRAME_RATE); + else + mfc_write(dev, 0, S5P_FIMV_ENC_RC_FRAME_RATE); + /* rate control config. */ + reg = mfc_read(dev, S5P_FIMV_ENC_RC_CONFIG); + /* frame QP */ + reg &= ~(0x3F); + reg |= p_h263->rc_frame_qp; + mfc_write(dev, reg, S5P_FIMV_ENC_RC_CONFIG); + /* max & min value of QP */ + reg = mfc_read(dev, S5P_FIMV_ENC_RC_QBOUND); + /* max QP */ + reg &= ~(0x3F << 8); + reg |= (p_h263->rc_max_qp << 8); + /* min QP */ + reg &= ~(0x3F); + reg |= p_h263->rc_min_qp; + mfc_write(dev, reg, S5P_FIMV_ENC_RC_QBOUND); + /* extended encoder ctrl */ + shm = s5p_mfc_read_info_v5(ctx, EXT_ENC_CONTROL); + /* vbv buffer size */ + if (p->frame_skip_mode == + V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { + shm &= ~(0xFFFF << 16); + shm |= (p->vbv_size << 16); + } + s5p_mfc_write_info_v5(ctx, shm, EXT_ENC_CONTROL); + return 0; +} + +/* Initialize decoding */ +static int s5p_mfc_init_decode_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + s5p_mfc_set_shared_buffer(ctx); + /* Setup loop filter, for decoding this is only valid for MPEG4 */ + if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_DEC) + mfc_write(dev, ctx->loop_filter_mpeg4, S5P_FIMV_ENC_LF_CTRL); + else + mfc_write(dev, 0, S5P_FIMV_ENC_LF_CTRL); + mfc_write(dev, ((ctx->slice_interface & S5P_FIMV_SLICE_INT_MASK) << + S5P_FIMV_SLICE_INT_SHIFT) | (ctx->display_delay_enable << + S5P_FIMV_DDELAY_ENA_SHIFT) | ((ctx->display_delay & + S5P_FIMV_DDELAY_VAL_MASK) << S5P_FIMV_DDELAY_VAL_SHIFT), + S5P_FIMV_SI_CH0_DPB_CONF_CTRL); + mfc_write(dev, + ((S5P_FIMV_CH_SEQ_HEADER & S5P_FIMV_CH_MASK) << S5P_FIMV_CH_SHIFT) + | (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID); + return 0; +} + +static void s5p_mfc_set_flush(struct s5p_mfc_ctx *ctx, int flush) +{ + struct s5p_mfc_dev *dev = ctx->dev; + unsigned int dpb; + + if (flush) + dpb = mfc_read(dev, S5P_FIMV_SI_CH0_DPB_CONF_CTRL) | ( + S5P_FIMV_DPB_FLUSH_MASK << S5P_FIMV_DPB_FLUSH_SHIFT); + else + dpb = mfc_read(dev, S5P_FIMV_SI_CH0_DPB_CONF_CTRL) & + ~(S5P_FIMV_DPB_FLUSH_MASK << S5P_FIMV_DPB_FLUSH_SHIFT); + mfc_write(dev, dpb, S5P_FIMV_SI_CH0_DPB_CONF_CTRL); +} + +/* Decode a single frame */ +static int s5p_mfc_decode_one_frame_v5(struct s5p_mfc_ctx *ctx, + enum s5p_mfc_decode_arg last_frame) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + mfc_write(dev, ctx->dec_dst_flag, S5P_FIMV_SI_CH0_RELEASE_BUF); + s5p_mfc_set_shared_buffer(ctx); + s5p_mfc_set_flush(ctx, ctx->dpb_flush_flag); + /* Issue different commands to instance basing on whether it + * is the last frame or not. */ + switch (last_frame) { + case MFC_DEC_FRAME: + mfc_write(dev, ((S5P_FIMV_CH_FRAME_START & S5P_FIMV_CH_MASK) << + S5P_FIMV_CH_SHIFT) | (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID); + break; + case MFC_DEC_LAST_FRAME: + mfc_write(dev, ((S5P_FIMV_CH_LAST_FRAME & S5P_FIMV_CH_MASK) << + S5P_FIMV_CH_SHIFT) | (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID); + break; + case MFC_DEC_RES_CHANGE: + mfc_write(dev, ((S5P_FIMV_CH_FRAME_START_REALLOC & + S5P_FIMV_CH_MASK) << S5P_FIMV_CH_SHIFT) | (ctx->inst_no), + S5P_FIMV_SI_CH0_INST_ID); + break; + } + mfc_debug(2, "Decoding a usual frame\n"); + return 0; +} + +static int s5p_mfc_init_encode_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC) + s5p_mfc_set_enc_params_h264(ctx); + else if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_ENC) + s5p_mfc_set_enc_params_mpeg4(ctx); + else if (ctx->codec_mode == S5P_MFC_CODEC_H263_ENC) + s5p_mfc_set_enc_params_h263(ctx); + else { + mfc_err("Unknown codec for encoding (%x)\n", + ctx->codec_mode); + return -EINVAL; + } + s5p_mfc_set_shared_buffer(ctx); + mfc_write(dev, ((S5P_FIMV_CH_SEQ_HEADER << 16) & 0x70000) | + (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID); + return 0; +} + +/* Encode a single frame */ +static int s5p_mfc_encode_one_frame_v5(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + int cmd; + /* memory structure cur. frame */ + if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12M) + mfc_write(dev, 0, S5P_FIMV_ENC_MAP_FOR_CUR); + else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12MT) + mfc_write(dev, 3, S5P_FIMV_ENC_MAP_FOR_CUR); + s5p_mfc_set_shared_buffer(ctx); + + if (ctx->state == MFCINST_FINISHING) + cmd = S5P_FIMV_CH_LAST_FRAME; + else + cmd = S5P_FIMV_CH_FRAME_START; + mfc_write(dev, ((cmd & S5P_FIMV_CH_MASK) << S5P_FIMV_CH_SHIFT) + | (ctx->inst_no), S5P_FIMV_SI_CH0_INST_ID); + + return 0; +} + +static void s5p_mfc_run_res_change(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + s5p_mfc_set_dec_stream_buffer_v5(ctx, 0, 0, 0); + dev->curr_ctx = ctx->num; + s5p_mfc_decode_one_frame_v5(ctx, MFC_DEC_RES_CHANGE); +} + +static int s5p_mfc_run_dec_frame(struct s5p_mfc_ctx *ctx, int last_frame) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *temp_vb; + + if (ctx->state == MFCINST_FINISHING) { + last_frame = MFC_DEC_LAST_FRAME; + s5p_mfc_set_dec_stream_buffer_v5(ctx, 0, 0, 0); + dev->curr_ctx = ctx->num; + s5p_mfc_decode_one_frame_v5(ctx, last_frame); + return 0; + } + + /* Frames are being decoded */ + if (list_empty(&ctx->src_queue)) { + mfc_debug(2, "No src buffers\n"); + return -EAGAIN; + } + /* Get the next source buffer */ + temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); + temp_vb->flags |= MFC_BUF_FLAG_USED; + s5p_mfc_set_dec_stream_buffer_v5(ctx, + vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0), + ctx->consumed_stream, temp_vb->b->vb2_buf.planes[0].bytesused); + dev->curr_ctx = ctx->num; + if (temp_vb->b->vb2_buf.planes[0].bytesused == 0) { + last_frame = MFC_DEC_LAST_FRAME; + mfc_debug(2, "Setting ctx->state to FINISHING\n"); + ctx->state = MFCINST_FINISHING; + } + s5p_mfc_decode_one_frame_v5(ctx, last_frame); + return 0; +} + +static int s5p_mfc_run_enc_frame(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *dst_mb; + struct s5p_mfc_buf *src_mb; + unsigned long src_y_addr, src_c_addr, dst_addr; + unsigned int dst_size; + + if (list_empty(&ctx->src_queue) && ctx->state != MFCINST_FINISHING) { + mfc_debug(2, "no src buffers\n"); + return -EAGAIN; + } + if (list_empty(&ctx->dst_queue)) { + mfc_debug(2, "no dst buffers\n"); + return -EAGAIN; + } + if (list_empty(&ctx->src_queue)) { + /* send null frame */ + s5p_mfc_set_enc_frame_buffer_v5(ctx, dev->dma_base[BANK_R_CTX], + dev->dma_base[BANK_R_CTX]); + src_mb = NULL; + } else { + src_mb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, + list); + src_mb->flags |= MFC_BUF_FLAG_USED; + if (src_mb->b->vb2_buf.planes[0].bytesused == 0) { + /* send null frame */ + s5p_mfc_set_enc_frame_buffer_v5(ctx, + dev->dma_base[BANK_R_CTX], + dev->dma_base[BANK_R_CTX]); + ctx->state = MFCINST_FINISHING; + } else { + src_y_addr = vb2_dma_contig_plane_dma_addr( + &src_mb->b->vb2_buf, 0); + src_c_addr = vb2_dma_contig_plane_dma_addr( + &src_mb->b->vb2_buf, 1); + s5p_mfc_set_enc_frame_buffer_v5(ctx, src_y_addr, + src_c_addr); + if (src_mb->flags & MFC_BUF_FLAG_EOS) + ctx->state = MFCINST_FINISHING; + } + } + dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list); + dst_mb->flags |= MFC_BUF_FLAG_USED; + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0); + dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0); + s5p_mfc_set_enc_stream_buffer_v5(ctx, dst_addr, dst_size); + dev->curr_ctx = ctx->num; + mfc_debug(2, "encoding buffer with index=%d state=%d\n", + src_mb ? src_mb->b->vb2_buf.index : -1, ctx->state); + s5p_mfc_encode_one_frame_v5(ctx); + return 0; +} + +static void s5p_mfc_run_init_dec(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *temp_vb; + + /* Initializing decoding - parsing header */ + mfc_debug(2, "Preparing to init decoding\n"); + temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); + s5p_mfc_set_dec_desc_buffer(ctx); + mfc_debug(2, "Header size: %d\n", + temp_vb->b->vb2_buf.planes[0].bytesused); + s5p_mfc_set_dec_stream_buffer_v5(ctx, + vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0), + 0, temp_vb->b->vb2_buf.planes[0].bytesused); + dev->curr_ctx = ctx->num; + s5p_mfc_init_decode_v5(ctx); +} + +static void s5p_mfc_run_init_enc(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *dst_mb; + unsigned long dst_addr; + unsigned int dst_size; + + s5p_mfc_set_enc_ref_buffer_v5(ctx); + dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list); + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0); + dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0); + s5p_mfc_set_enc_stream_buffer_v5(ctx, dst_addr, dst_size); + dev->curr_ctx = ctx->num; + s5p_mfc_init_encode_v5(ctx); +} + +static int s5p_mfc_run_init_dec_buffers(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *temp_vb; + int ret; + + /* + * Header was parsed now starting processing + * First set the output frame buffers + */ + if (ctx->capture_state != QUEUE_BUFS_MMAPED) { + mfc_err("It seems that not all destination buffers were mmapped\nMFC requires that all destination are mmapped before starting processing\n"); + return -EAGAIN; + } + if (list_empty(&ctx->src_queue)) { + mfc_err("Header has been deallocated in the middle of initialization\n"); + return -EIO; + } + temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); + mfc_debug(2, "Header size: %d\n", + temp_vb->b->vb2_buf.planes[0].bytesused); + s5p_mfc_set_dec_stream_buffer_v5(ctx, + vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0), + 0, temp_vb->b->vb2_buf.planes[0].bytesused); + dev->curr_ctx = ctx->num; + ret = s5p_mfc_set_dec_frame_buffer_v5(ctx); + if (ret) { + mfc_err("Failed to alloc frame mem\n"); + ctx->state = MFCINST_ERROR; + } + return ret; +} + +/* Try running an operation on hardware */ +static void s5p_mfc_try_run_v5(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_ctx *ctx; + int new_ctx; + unsigned int ret = 0; + + if (test_bit(0, &dev->enter_suspend)) { + mfc_debug(1, "Entering suspend so do not schedule any jobs\n"); + return; + } + /* Check whether hardware is not running */ + if (test_and_set_bit(0, &dev->hw_lock) != 0) { + /* This is perfectly ok, the scheduled ctx should wait */ + mfc_debug(1, "Couldn't lock HW\n"); + return; + } + /* Choose the context to run */ + new_ctx = s5p_mfc_get_new_ctx(dev); + if (new_ctx < 0) { + /* No contexts to run */ + if (test_and_clear_bit(0, &dev->hw_lock) == 0) { + mfc_err("Failed to unlock hardware\n"); + return; + } + mfc_debug(1, "No ctx is scheduled to be run\n"); + return; + } + ctx = dev->ctx[new_ctx]; + /* Got context to run in ctx */ + /* + * Last frame has already been sent to MFC. + * Now obtaining frames from MFC buffer + */ + s5p_mfc_clock_on(); + s5p_mfc_clean_ctx_int_flags(ctx); + + if (ctx->type == MFCINST_DECODER) { + s5p_mfc_set_dec_desc_buffer(ctx); + switch (ctx->state) { + case MFCINST_FINISHING: + s5p_mfc_run_dec_frame(ctx, MFC_DEC_LAST_FRAME); + break; + case MFCINST_RUNNING: + ret = s5p_mfc_run_dec_frame(ctx, MFC_DEC_FRAME); + break; + case MFCINST_INIT: + ret = s5p_mfc_hw_call(dev->mfc_cmds, open_inst_cmd, + ctx); + break; + case MFCINST_RETURN_INST: + ret = s5p_mfc_hw_call(dev->mfc_cmds, close_inst_cmd, + ctx); + break; + case MFCINST_GOT_INST: + s5p_mfc_run_init_dec(ctx); + break; + case MFCINST_HEAD_PARSED: + ret = s5p_mfc_run_init_dec_buffers(ctx); + mfc_debug(1, "head parsed\n"); + break; + case MFCINST_RES_CHANGE_INIT: + s5p_mfc_run_res_change(ctx); + break; + case MFCINST_RES_CHANGE_FLUSH: + s5p_mfc_run_dec_frame(ctx, MFC_DEC_FRAME); + break; + case MFCINST_RES_CHANGE_END: + mfc_debug(2, "Finished remaining frames after resolution change\n"); + ctx->capture_state = QUEUE_FREE; + mfc_debug(2, "Will re-init the codec\n"); + s5p_mfc_run_init_dec(ctx); + break; + default: + ret = -EAGAIN; + } + } else if (ctx->type == MFCINST_ENCODER) { + switch (ctx->state) { + case MFCINST_FINISHING: + case MFCINST_RUNNING: + ret = s5p_mfc_run_enc_frame(ctx); + break; + case MFCINST_INIT: + ret = s5p_mfc_hw_call(dev->mfc_cmds, open_inst_cmd, + ctx); + break; + case MFCINST_RETURN_INST: + ret = s5p_mfc_hw_call(dev->mfc_cmds, close_inst_cmd, + ctx); + break; + case MFCINST_GOT_INST: + s5p_mfc_run_init_enc(ctx); + break; + default: + ret = -EAGAIN; + } + } else { + mfc_err("Invalid context type: %d\n", ctx->type); + ret = -EAGAIN; + } + + if (ret) { + /* Free hardware lock */ + if (test_and_clear_bit(0, &dev->hw_lock) == 0) + mfc_err("Failed to unlock hardware\n"); + + /* This is indeed important, as no operation has been + * scheduled, reduce the clock count as no one will + * ever do this, because no interrupt related to this try_run + * will ever come from hardware. */ + s5p_mfc_clock_off(); + } +} + +static void s5p_mfc_clear_int_flags_v5(struct s5p_mfc_dev *dev) +{ + mfc_write(dev, 0, S5P_FIMV_RISC_HOST_INT); + mfc_write(dev, 0, S5P_FIMV_RISC2HOST_CMD); + mfc_write(dev, 0xffff, S5P_FIMV_SI_RTN_CHID); +} + +static int s5p_mfc_get_dspl_y_adr_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_SI_DISPLAY_Y_ADR) << MFC_OFFSET_SHIFT; +} + +static int s5p_mfc_get_dec_y_adr_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_SI_DECODE_Y_ADR) << MFC_OFFSET_SHIFT; +} + +static int s5p_mfc_get_dspl_status_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_SI_DISPLAY_STATUS); +} + +static int s5p_mfc_get_dec_status_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_SI_DECODE_STATUS); +} + +static int s5p_mfc_get_dec_frame_type_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_DECODE_FRAME_TYPE) & + S5P_FIMV_DECODE_FRAME_MASK; +} + +static int s5p_mfc_get_disp_frame_type_v5(struct s5p_mfc_ctx *ctx) +{ + return (s5p_mfc_read_info_v5(ctx, DISP_PIC_FRAME_TYPE) >> + S5P_FIMV_SHARED_DISP_FRAME_TYPE_SHIFT) & + S5P_FIMV_DECODE_FRAME_MASK; +} + +static int s5p_mfc_get_consumed_stream_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_SI_CONSUMED_BYTES); +} + +static int s5p_mfc_get_int_reason_v5(struct s5p_mfc_dev *dev) +{ + int reason; + reason = mfc_read(dev, S5P_FIMV_RISC2HOST_CMD) & + S5P_FIMV_RISC2HOST_CMD_MASK; + switch (reason) { + case S5P_FIMV_R2H_CMD_OPEN_INSTANCE_RET: + reason = S5P_MFC_R2H_CMD_OPEN_INSTANCE_RET; + break; + case S5P_FIMV_R2H_CMD_CLOSE_INSTANCE_RET: + reason = S5P_MFC_R2H_CMD_CLOSE_INSTANCE_RET; + break; + case S5P_FIMV_R2H_CMD_SEQ_DONE_RET: + reason = S5P_MFC_R2H_CMD_SEQ_DONE_RET; + break; + case S5P_FIMV_R2H_CMD_FRAME_DONE_RET: + reason = S5P_MFC_R2H_CMD_FRAME_DONE_RET; + break; + case S5P_FIMV_R2H_CMD_SLICE_DONE_RET: + reason = S5P_MFC_R2H_CMD_SLICE_DONE_RET; + break; + case S5P_FIMV_R2H_CMD_SYS_INIT_RET: + reason = S5P_MFC_R2H_CMD_SYS_INIT_RET; + break; + case S5P_FIMV_R2H_CMD_FW_STATUS_RET: + reason = S5P_MFC_R2H_CMD_FW_STATUS_RET; + break; + case S5P_FIMV_R2H_CMD_SLEEP_RET: + reason = S5P_MFC_R2H_CMD_SLEEP_RET; + break; + case S5P_FIMV_R2H_CMD_WAKEUP_RET: + reason = S5P_MFC_R2H_CMD_WAKEUP_RET; + break; + case S5P_FIMV_R2H_CMD_INIT_BUFFERS_RET: + reason = S5P_MFC_R2H_CMD_INIT_BUFFERS_RET; + break; + case S5P_FIMV_R2H_CMD_ENC_COMPLETE_RET: + reason = S5P_MFC_R2H_CMD_COMPLETE_SEQ_RET; + break; + case S5P_FIMV_R2H_CMD_ERR_RET: + reason = S5P_MFC_R2H_CMD_ERR_RET; + break; + default: + reason = S5P_MFC_R2H_CMD_EMPTY; + } + return reason; +} + +static int s5p_mfc_get_int_err_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_RISC2HOST_ARG2); +} + +static int s5p_mfc_err_dec_v5(unsigned int err) +{ + return (err & S5P_FIMV_ERR_DEC_MASK) >> S5P_FIMV_ERR_DEC_SHIFT; +} + +static int s5p_mfc_get_img_width_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_SI_HRESOL); +} + +static int s5p_mfc_get_img_height_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_SI_VRESOL); +} + +static int s5p_mfc_get_dpb_count_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_SI_BUF_NUMBER); +} + +static int s5p_mfc_get_mv_count_v5(struct s5p_mfc_dev *dev) +{ + /* NOP */ + return -1; +} + +static int s5p_mfc_get_inst_no_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_RISC2HOST_ARG1); +} + +static int s5p_mfc_get_enc_strm_size_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_ENC_SI_STRM_SIZE); +} + +static int s5p_mfc_get_enc_slice_type_v5(struct s5p_mfc_dev *dev) +{ + return mfc_read(dev, S5P_FIMV_ENC_SI_SLICE_TYPE); +} + +static int s5p_mfc_get_enc_dpb_count_v5(struct s5p_mfc_dev *dev) +{ + return -1; +} + +static unsigned int s5p_mfc_get_pic_type_top_v5(struct s5p_mfc_ctx *ctx) +{ + return s5p_mfc_read_info_v5(ctx, PIC_TIME_TOP); +} + +static unsigned int s5p_mfc_get_pic_type_bot_v5(struct s5p_mfc_ctx *ctx) +{ + return s5p_mfc_read_info_v5(ctx, PIC_TIME_BOT); +} + +static unsigned int s5p_mfc_get_crop_info_h_v5(struct s5p_mfc_ctx *ctx) +{ + return s5p_mfc_read_info_v5(ctx, CROP_INFO_H); +} + +static unsigned int s5p_mfc_get_crop_info_v_v5(struct s5p_mfc_ctx *ctx) +{ + return s5p_mfc_read_info_v5(ctx, CROP_INFO_V); +} + +/* Initialize opr function pointers for MFC v5 */ +static struct s5p_mfc_hw_ops s5p_mfc_ops_v5 = { + .alloc_dec_temp_buffers = s5p_mfc_alloc_dec_temp_buffers_v5, + .release_dec_desc_buffer = s5p_mfc_release_dec_desc_buffer_v5, + .alloc_codec_buffers = s5p_mfc_alloc_codec_buffers_v5, + .release_codec_buffers = s5p_mfc_release_codec_buffers_v5, + .alloc_instance_buffer = s5p_mfc_alloc_instance_buffer_v5, + .release_instance_buffer = s5p_mfc_release_instance_buffer_v5, + .alloc_dev_context_buffer = s5p_mfc_alloc_dev_context_buffer_v5, + .release_dev_context_buffer = s5p_mfc_release_dev_context_buffer_v5, + .dec_calc_dpb_size = s5p_mfc_dec_calc_dpb_size_v5, + .enc_calc_src_size = s5p_mfc_enc_calc_src_size_v5, + .set_enc_stream_buffer = s5p_mfc_set_enc_stream_buffer_v5, + .set_enc_frame_buffer = s5p_mfc_set_enc_frame_buffer_v5, + .get_enc_frame_buffer = s5p_mfc_get_enc_frame_buffer_v5, + .try_run = s5p_mfc_try_run_v5, + .clear_int_flags = s5p_mfc_clear_int_flags_v5, + .get_dspl_y_adr = s5p_mfc_get_dspl_y_adr_v5, + .get_dec_y_adr = s5p_mfc_get_dec_y_adr_v5, + .get_dspl_status = s5p_mfc_get_dspl_status_v5, + .get_dec_status = s5p_mfc_get_dec_status_v5, + .get_dec_frame_type = s5p_mfc_get_dec_frame_type_v5, + .get_disp_frame_type = s5p_mfc_get_disp_frame_type_v5, + .get_consumed_stream = s5p_mfc_get_consumed_stream_v5, + .get_int_reason = s5p_mfc_get_int_reason_v5, + .get_int_err = s5p_mfc_get_int_err_v5, + .err_dec = s5p_mfc_err_dec_v5, + .get_img_width = s5p_mfc_get_img_width_v5, + .get_img_height = s5p_mfc_get_img_height_v5, + .get_dpb_count = s5p_mfc_get_dpb_count_v5, + .get_mv_count = s5p_mfc_get_mv_count_v5, + .get_inst_no = s5p_mfc_get_inst_no_v5, + .get_enc_strm_size = s5p_mfc_get_enc_strm_size_v5, + .get_enc_slice_type = s5p_mfc_get_enc_slice_type_v5, + .get_enc_dpb_count = s5p_mfc_get_enc_dpb_count_v5, + .get_pic_type_top = s5p_mfc_get_pic_type_top_v5, + .get_pic_type_bot = s5p_mfc_get_pic_type_bot_v5, + .get_crop_info_h = s5p_mfc_get_crop_info_h_v5, + .get_crop_info_v = s5p_mfc_get_crop_info_v_v5, +}; + +struct s5p_mfc_hw_ops *s5p_mfc_init_hw_ops_v5(void) +{ + return &s5p_mfc_ops_v5; +} diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h new file mode 100644 index 000000000..b53d376ea --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v5.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/platform/samsung/mfc5/s5p_mfc_opr_v5.h + * + * Header file for Samsung MFC (Multi Function Codec - FIMV) driver + * Contains declarations of hw related functions. + * + * Kamil Debski, Copyright (C) 2011 Samsung Electronics + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_OPR_V5_H_ +#define S5P_MFC_OPR_V5_H_ + +#include "s5p_mfc_common.h" +#include "s5p_mfc_opr.h" + +enum MFC_SHM_OFS { + EXTENEDED_DECODE_STATUS = 0x00, /* D */ + SET_FRAME_TAG = 0x04, /* D */ + GET_FRAME_TAG_TOP = 0x08, /* D */ + GET_FRAME_TAG_BOT = 0x0C, /* D */ + PIC_TIME_TOP = 0x10, /* D */ + PIC_TIME_BOT = 0x14, /* D */ + START_BYTE_NUM = 0x18, /* D */ + + CROP_INFO_H = 0x20, /* D */ + CROP_INFO_V = 0x24, /* D */ + EXT_ENC_CONTROL = 0x28, /* E */ + ENC_PARAM_CHANGE = 0x2C, /* E */ + RC_VOP_TIMING = 0x30, /* E, MPEG4 */ + HEC_PERIOD = 0x34, /* E, MPEG4 */ + METADATA_ENABLE = 0x38, /* C */ + METADATA_STATUS = 0x3C, /* C */ + METADATA_DISPLAY_INDEX = 0x40, /* C */ + EXT_METADATA_START_ADDR = 0x44, /* C */ + PUT_EXTRADATA = 0x48, /* C */ + EXTRADATA_ADDR = 0x4C, /* C */ + + ALLOC_LUMA_DPB_SIZE = 0x64, /* D */ + ALLOC_CHROMA_DPB_SIZE = 0x68, /* D */ + ALLOC_MV_SIZE = 0x6C, /* D */ + P_B_FRAME_QP = 0x70, /* E */ + SAMPLE_ASPECT_RATIO_IDC = 0x74, /* E, H.264, depend on + ASPECT_RATIO_VUI_ENABLE in EXT_ENC_CONTROL */ + EXTENDED_SAR = 0x78, /* E, H.264, depned on + ASPECT_RATIO_VUI_ENABLE in EXT_ENC_CONTROL */ + DISP_PIC_PROFILE = 0x7C, /* D */ + FLUSH_CMD_TYPE = 0x80, /* C */ + FLUSH_CMD_INBUF1 = 0x84, /* C */ + FLUSH_CMD_INBUF2 = 0x88, /* C */ + FLUSH_CMD_OUTBUF = 0x8C, /* E */ + NEW_RC_BIT_RATE = 0x90, /* E, format as RC_BIT_RATE(0xC5A8) + depend on RC_BIT_RATE_CHANGE in ENC_PARAM_CHANGE */ + NEW_RC_FRAME_RATE = 0x94, /* E, format as RC_FRAME_RATE(0xD0D0) + depend on RC_FRAME_RATE_CHANGE in ENC_PARAM_CHANGE */ + NEW_I_PERIOD = 0x98, /* E, format as I_FRM_CTRL(0xC504) + depend on I_PERIOD_CHANGE in ENC_PARAM_CHANGE */ + H264_I_PERIOD = 0x9C, /* E, H.264, open GOP */ + RC_CONTROL_CONFIG = 0xA0, /* E */ + BATCH_INPUT_ADDR = 0xA4, /* E */ + BATCH_OUTPUT_ADDR = 0xA8, /* E */ + BATCH_OUTPUT_SIZE = 0xAC, /* E */ + MIN_LUMA_DPB_SIZE = 0xB0, /* D */ + DEVICE_FORMAT_ID = 0xB4, /* C */ + H264_POC_TYPE = 0xB8, /* D */ + MIN_CHROMA_DPB_SIZE = 0xBC, /* D */ + DISP_PIC_FRAME_TYPE = 0xC0, /* D */ + FREE_LUMA_DPB = 0xC4, /* D, VC1 MPEG4 */ + ASPECT_RATIO_INFO = 0xC8, /* D, MPEG4 */ + EXTENDED_PAR = 0xCC, /* D, MPEG4 */ + DBG_HISTORY_INPUT0 = 0xD0, /* C */ + DBG_HISTORY_INPUT1 = 0xD4, /* C */ + DBG_HISTORY_OUTPUT = 0xD8, /* C */ + HIERARCHICAL_P_QP = 0xE0, /* E, H.264 */ + FRAME_PACK_SEI_ENABLE = 0x168, /* C */ + FRAME_PACK_SEI_AVAIL = 0x16c, /* D */ + FRAME_PACK_SEI_INFO = 0x17c, /* E */ +}; + +struct s5p_mfc_hw_ops *s5p_mfc_init_hw_ops_v5(void); +#endif /* S5P_MFC_OPR_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c new file mode 100644 index 000000000..c0df5ac9f --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c @@ -0,0 +1,2534 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.c + * + * Samsung MFC (Multi Function Codec - FIMV) driver + * This file contains hw related functions. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#undef DEBUG + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "s5p_mfc_common.h" +#include "s5p_mfc_cmd.h" +#include "s5p_mfc_intr.h" +#include "s5p_mfc_pm.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_opr.h" +#include "s5p_mfc_opr_v6.h" + +/* #define S5P_MFC_DEBUG_REGWRITE */ +#ifdef S5P_MFC_DEBUG_REGWRITE +#undef writel +#define writel(v, r) \ + do { \ + pr_err("MFCWRITE(%p): %08x\n", r, (unsigned int)v); \ + __raw_writel(v, r); \ + } while (0) +#endif /* S5P_MFC_DEBUG_REGWRITE */ + +#define IS_MFCV6_V2(dev) (!IS_MFCV7_PLUS(dev) && dev->fw_ver == MFC_FW_V2) + +/* Allocate temporary buffers for decoding */ +static int s5p_mfc_alloc_dec_temp_buffers_v6(struct s5p_mfc_ctx *ctx) +{ + /* NOP */ + + return 0; +} + +/* Release temporary buffers for decoding */ +static void s5p_mfc_release_dec_desc_buffer_v6(struct s5p_mfc_ctx *ctx) +{ + /* NOP */ +} + +/* Allocate codec buffers */ +static int s5p_mfc_alloc_codec_buffers_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + unsigned int mb_width, mb_height; + unsigned int lcu_width = 0, lcu_height = 0; + int ret; + + mb_width = MB_WIDTH(ctx->img_width); + mb_height = MB_HEIGHT(ctx->img_height); + + if (ctx->type == MFCINST_DECODER) { + mfc_debug(2, "Luma size:%d Chroma size:%d MV size:%d\n", + ctx->luma_size, ctx->chroma_size, ctx->mv_size); + mfc_debug(2, "Totals bufs: %d\n", ctx->total_dpb_count); + } else if (ctx->type == MFCINST_ENCODER) { + if (IS_MFCV10(dev)) { + ctx->tmv_buffer_size = 0; + } else if (IS_MFCV8_PLUS(dev)) + ctx->tmv_buffer_size = S5P_FIMV_NUM_TMV_BUFFERS_V6 * + ALIGN(S5P_FIMV_TMV_BUFFER_SIZE_V8(mb_width, mb_height), + S5P_FIMV_TMV_BUFFER_ALIGN_V6); + else + ctx->tmv_buffer_size = S5P_FIMV_NUM_TMV_BUFFERS_V6 * + ALIGN(S5P_FIMV_TMV_BUFFER_SIZE_V6(mb_width, mb_height), + S5P_FIMV_TMV_BUFFER_ALIGN_V6); + if (IS_MFCV10(dev)) { + lcu_width = S5P_MFC_LCU_WIDTH(ctx->img_width); + lcu_height = S5P_MFC_LCU_HEIGHT(ctx->img_height); + if (ctx->codec_mode != S5P_FIMV_CODEC_HEVC_ENC) { + ctx->luma_dpb_size = + ALIGN((mb_width * 16), 64) + * ALIGN((mb_height * 16), 32) + + 64; + ctx->chroma_dpb_size = + ALIGN((mb_width * 16), 64) + * (mb_height * 8) + + 64; + } else { + ctx->luma_dpb_size = + ALIGN((lcu_width * 32), 64) + * ALIGN((lcu_height * 32), 32) + + 64; + ctx->chroma_dpb_size = + ALIGN((lcu_width * 32), 64) + * (lcu_height * 16) + + 64; + } + } else { + ctx->luma_dpb_size = ALIGN((mb_width * mb_height) * + S5P_FIMV_LUMA_MB_TO_PIXEL_V6, + S5P_FIMV_LUMA_DPB_BUFFER_ALIGN_V6); + ctx->chroma_dpb_size = ALIGN((mb_width * mb_height) * + S5P_FIMV_CHROMA_MB_TO_PIXEL_V6, + S5P_FIMV_CHROMA_DPB_BUFFER_ALIGN_V6); + } + if (IS_MFCV8_PLUS(dev)) + ctx->me_buffer_size = ALIGN(S5P_FIMV_ME_BUFFER_SIZE_V8( + ctx->img_width, ctx->img_height, + mb_width, mb_height), + S5P_FIMV_ME_BUFFER_ALIGN_V6); + else + ctx->me_buffer_size = ALIGN(S5P_FIMV_ME_BUFFER_SIZE_V6( + ctx->img_width, ctx->img_height, + mb_width, mb_height), + S5P_FIMV_ME_BUFFER_ALIGN_V6); + + mfc_debug(2, "recon luma size: %zu chroma size: %zu\n", + ctx->luma_dpb_size, ctx->chroma_dpb_size); + } else { + return -EINVAL; + } + + /* Codecs have different memory requirements */ + switch (ctx->codec_mode) { + case S5P_MFC_CODEC_H264_DEC: + case S5P_MFC_CODEC_H264_MVC_DEC: + if (IS_MFCV10(dev)) + mfc_debug(2, "Use min scratch buffer size\n"); + else if (IS_MFCV8_PLUS(dev)) + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V8( + mb_width, + mb_height); + else + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_H264_DEC_V6( + mb_width, + mb_height); + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = + ctx->scratch_buf_size + + (ctx->mv_count * ctx->mv_size); + break; + case S5P_MFC_CODEC_MPEG4_DEC: + if (IS_MFCV10(dev)) + mfc_debug(2, "Use min scratch buffer size\n"); + else if (IS_MFCV7_PLUS(dev)) { + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V7( + mb_width, + mb_height); + } else { + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_DEC_V6( + mb_width, + mb_height); + } + + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = ctx->scratch_buf_size; + break; + case S5P_MFC_CODEC_VC1RCV_DEC: + case S5P_MFC_CODEC_VC1_DEC: + if (IS_MFCV10(dev)) + mfc_debug(2, "Use min scratch buffer size\n"); + else + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_VC1_DEC_V6( + mb_width, + mb_height); + + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = ctx->scratch_buf_size; + break; + case S5P_MFC_CODEC_MPEG2_DEC: + ctx->bank1.size = 0; + ctx->bank2.size = 0; + break; + case S5P_MFC_CODEC_H263_DEC: + if (IS_MFCV10(dev)) + mfc_debug(2, "Use min scratch buffer size\n"); + else + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_H263_DEC_V6( + mb_width, + mb_height); + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = ctx->scratch_buf_size; + break; + case S5P_MFC_CODEC_VP8_DEC: + if (IS_MFCV10(dev)) + mfc_debug(2, "Use min scratch buffer size\n"); + else if (IS_MFCV8_PLUS(dev)) + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_VP8_DEC_V8( + mb_width, + mb_height); + else + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_VP8_DEC_V6( + mb_width, + mb_height); + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = ctx->scratch_buf_size; + break; + case S5P_MFC_CODEC_HEVC_DEC: + mfc_debug(2, "Use min scratch buffer size\n"); + ctx->bank1.size = + ctx->scratch_buf_size + + (ctx->mv_count * ctx->mv_size); + break; + case S5P_MFC_CODEC_VP9_DEC: + mfc_debug(2, "Use min scratch buffer size\n"); + ctx->bank1.size = + ctx->scratch_buf_size + + DEC_VP9_STATIC_BUFFER_SIZE; + break; + case S5P_MFC_CODEC_H264_ENC: + if (IS_MFCV10(dev)) { + mfc_debug(2, "Use min scratch buffer size\n"); + ctx->me_buffer_size = + ALIGN(ENC_V100_H264_ME_SIZE(mb_width, mb_height), 16); + } else if (IS_MFCV8_PLUS(dev)) + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_H264_ENC_V8( + mb_width, + mb_height); + else + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_H264_ENC_V6( + mb_width, + mb_height); + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = + ctx->scratch_buf_size + ctx->tmv_buffer_size + + (ctx->pb_count * (ctx->luma_dpb_size + + ctx->chroma_dpb_size + ctx->me_buffer_size)); + ctx->bank2.size = 0; + break; + case S5P_MFC_CODEC_MPEG4_ENC: + case S5P_MFC_CODEC_H263_ENC: + if (IS_MFCV10(dev)) { + mfc_debug(2, "Use min scratch buffer size\n"); + ctx->me_buffer_size = + ALIGN(ENC_V100_MPEG4_ME_SIZE(mb_width, + mb_height), 16); + } else + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_MPEG4_ENC_V6( + mb_width, + mb_height); + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = + ctx->scratch_buf_size + ctx->tmv_buffer_size + + (ctx->pb_count * (ctx->luma_dpb_size + + ctx->chroma_dpb_size + ctx->me_buffer_size)); + ctx->bank2.size = 0; + break; + case S5P_MFC_CODEC_VP8_ENC: + if (IS_MFCV10(dev)) { + mfc_debug(2, "Use min scratch buffer size\n"); + ctx->me_buffer_size = + ALIGN(ENC_V100_VP8_ME_SIZE(mb_width, mb_height), + 16); + } else if (IS_MFCV8_PLUS(dev)) + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V8( + mb_width, + mb_height); + else + ctx->scratch_buf_size = + S5P_FIMV_SCRATCH_BUF_SIZE_VP8_ENC_V7( + mb_width, + mb_height); + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, + S5P_FIMV_SCRATCH_BUFFER_ALIGN_V6); + ctx->bank1.size = + ctx->scratch_buf_size + ctx->tmv_buffer_size + + (ctx->pb_count * (ctx->luma_dpb_size + + ctx->chroma_dpb_size + ctx->me_buffer_size)); + ctx->bank2.size = 0; + break; + case S5P_MFC_CODEC_HEVC_ENC: + mfc_debug(2, "Use min scratch buffer size\n"); + ctx->me_buffer_size = + ALIGN(ENC_V100_HEVC_ME_SIZE(lcu_width, lcu_height), 16); + ctx->scratch_buf_size = ALIGN(ctx->scratch_buf_size, 256); + ctx->bank1.size = + ctx->scratch_buf_size + ctx->tmv_buffer_size + + (ctx->pb_count * (ctx->luma_dpb_size + + ctx->chroma_dpb_size + ctx->me_buffer_size)); + ctx->bank2.size = 0; + break; + default: + break; + } + + /* Allocate only if memory from bank 1 is necessary */ + if (ctx->bank1.size > 0) { + ret = s5p_mfc_alloc_generic_buf(dev, BANK_L_CTX, &ctx->bank1); + if (ret) { + mfc_err("Failed to allocate Bank1 memory\n"); + return ret; + } + BUG_ON(ctx->bank1.dma & ((1 << MFC_BANK1_ALIGN_ORDER) - 1)); + } + return 0; +} + +/* Release buffers allocated for codec */ +static void s5p_mfc_release_codec_buffers_v6(struct s5p_mfc_ctx *ctx) +{ + s5p_mfc_release_generic_buf(ctx->dev, &ctx->bank1); +} + +/* Allocate memory for instance data buffer */ +static int s5p_mfc_alloc_instance_buffer_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf_size_v6 *buf_size = dev->variant->buf_size->priv; + int ret; + + mfc_debug_enter(); + + switch (ctx->codec_mode) { + case S5P_MFC_CODEC_H264_DEC: + case S5P_MFC_CODEC_H264_MVC_DEC: + case S5P_MFC_CODEC_HEVC_DEC: + ctx->ctx.size = buf_size->h264_dec_ctx; + break; + case S5P_MFC_CODEC_MPEG4_DEC: + case S5P_MFC_CODEC_H263_DEC: + case S5P_MFC_CODEC_VC1RCV_DEC: + case S5P_MFC_CODEC_VC1_DEC: + case S5P_MFC_CODEC_MPEG2_DEC: + case S5P_MFC_CODEC_VP8_DEC: + case S5P_MFC_CODEC_VP9_DEC: + ctx->ctx.size = buf_size->other_dec_ctx; + break; + case S5P_MFC_CODEC_H264_ENC: + ctx->ctx.size = buf_size->h264_enc_ctx; + break; + case S5P_MFC_CODEC_HEVC_ENC: + ctx->ctx.size = buf_size->hevc_enc_ctx; + break; + case S5P_MFC_CODEC_MPEG4_ENC: + case S5P_MFC_CODEC_H263_ENC: + case S5P_MFC_CODEC_VP8_ENC: + ctx->ctx.size = buf_size->other_enc_ctx; + break; + default: + ctx->ctx.size = 0; + mfc_err("Codec type(%d) should be checked!\n", ctx->codec_mode); + break; + } + + ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &ctx->ctx); + if (ret) { + mfc_err("Failed to allocate instance buffer\n"); + return ret; + } + + memset(ctx->ctx.virt, 0, ctx->ctx.size); + wmb(); + + mfc_debug_leave(); + + return 0; +} + +/* Release instance buffer */ +static void s5p_mfc_release_instance_buffer_v6(struct s5p_mfc_ctx *ctx) +{ + s5p_mfc_release_priv_buf(ctx->dev, &ctx->ctx); +} + +/* Allocate context buffers for SYS_INIT */ +static int s5p_mfc_alloc_dev_context_buffer_v6(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_buf_size_v6 *buf_size = dev->variant->buf_size->priv; + int ret; + + mfc_debug_enter(); + + dev->ctx_buf.size = buf_size->dev_ctx; + ret = s5p_mfc_alloc_priv_buf(dev, BANK_L_CTX, &dev->ctx_buf); + if (ret) { + mfc_err("Failed to allocate device context buffer\n"); + return ret; + } + + memset(dev->ctx_buf.virt, 0, buf_size->dev_ctx); + wmb(); + + mfc_debug_leave(); + + return 0; +} + +/* Release context buffers for SYS_INIT */ +static void s5p_mfc_release_dev_context_buffer_v6(struct s5p_mfc_dev *dev) +{ + s5p_mfc_release_priv_buf(dev, &dev->ctx_buf); +} + +static int calc_plane(int width, int height) +{ + int mbX, mbY; + + mbX = DIV_ROUND_UP(width, S5P_FIMV_NUM_PIXELS_IN_MB_ROW_V6); + mbY = DIV_ROUND_UP(height, S5P_FIMV_NUM_PIXELS_IN_MB_COL_V6); + + if (width * height < S5P_FIMV_MAX_FRAME_SIZE_V6) + mbY = (mbY + 1) / 2 * 2; + + return (mbX * S5P_FIMV_NUM_PIXELS_IN_MB_COL_V6) * + (mbY * S5P_FIMV_NUM_PIXELS_IN_MB_ROW_V6); +} + +static void s5p_mfc_dec_calc_dpb_size_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12MT_HALIGN_V6); + ctx->buf_height = ALIGN(ctx->img_height, S5P_FIMV_NV12MT_VALIGN_V6); + mfc_debug(2, "SEQ Done: Movie dimensions %dx%d,\n" + "buffer dimensions: %dx%d\n", ctx->img_width, + ctx->img_height, ctx->buf_width, ctx->buf_height); + + ctx->luma_size = calc_plane(ctx->img_width, ctx->img_height); + ctx->chroma_size = calc_plane(ctx->img_width, (ctx->img_height >> 1)); + if (IS_MFCV8_PLUS(ctx->dev)) { + /* MFCv8 needs additional 64 bytes for luma,chroma dpb*/ + ctx->luma_size += S5P_FIMV_D_ALIGN_PLANE_SIZE_V8; + ctx->chroma_size += S5P_FIMV_D_ALIGN_PLANE_SIZE_V8; + } + + if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC || + ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC) { + if (IS_MFCV10(dev)) { + ctx->mv_size = S5P_MFC_DEC_MV_SIZE_V10(ctx->img_width, + ctx->img_height); + } else { + ctx->mv_size = S5P_MFC_DEC_MV_SIZE_V6(ctx->img_width, + ctx->img_height); + } + } else if (ctx->codec_mode == S5P_MFC_CODEC_HEVC_DEC) { + ctx->mv_size = s5p_mfc_dec_hevc_mv_size(ctx->img_width, + ctx->img_height); + ctx->mv_size = ALIGN(ctx->mv_size, 32); + } else { + ctx->mv_size = 0; + } +} + +static void s5p_mfc_enc_calc_src_size_v6(struct s5p_mfc_ctx *ctx) +{ + unsigned int mb_width, mb_height; + + mb_width = MB_WIDTH(ctx->img_width); + mb_height = MB_HEIGHT(ctx->img_height); + + ctx->buf_width = ALIGN(ctx->img_width, S5P_FIMV_NV12M_HALIGN_V6); + ctx->luma_size = ALIGN((mb_width * mb_height) * 256, 256); + ctx->chroma_size = ALIGN((mb_width * mb_height) * 128, 256); + + /* MFCv7 needs pad bytes for Luma and Chroma */ + if (IS_MFCV7_PLUS(ctx->dev)) { + ctx->luma_size += MFC_LUMA_PAD_BYTES_V7; + ctx->chroma_size += MFC_CHROMA_PAD_BYTES_V7; + } +} + +/* Set registers for decoding stream buffer */ +static int s5p_mfc_set_dec_stream_buffer_v6(struct s5p_mfc_ctx *ctx, + int buf_addr, unsigned int start_num_byte, + unsigned int strm_size) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + struct s5p_mfc_buf_size *buf_size = dev->variant->buf_size; + + mfc_debug_enter(); + mfc_debug(2, "inst_no: %d, buf_addr: 0x%08x,\n" + "buf_size: 0x%08x (%d)\n", + ctx->inst_no, buf_addr, strm_size, strm_size); + writel(strm_size, mfc_regs->d_stream_data_size); + writel(buf_addr, mfc_regs->d_cpb_buffer_addr); + writel(buf_size->cpb, mfc_regs->d_cpb_buffer_size); + writel(start_num_byte, mfc_regs->d_cpb_buffer_offset); + + mfc_debug_leave(); + return 0; +} + +/* Set decoding frame buffer */ +static int s5p_mfc_set_dec_frame_buffer_v6(struct s5p_mfc_ctx *ctx) +{ + unsigned int frame_size, i; + unsigned int frame_size_ch, frame_size_mv; + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + size_t buf_addr1; + int buf_size1; + int align_gap; + + buf_addr1 = ctx->bank1.dma; + buf_size1 = ctx->bank1.size; + + mfc_debug(2, "Buf1: %p (%d)\n", (void *)buf_addr1, buf_size1); + mfc_debug(2, "Total DPB COUNT: %d\n", ctx->total_dpb_count); + mfc_debug(2, "Setting display delay to %d\n", ctx->display_delay); + + writel(ctx->total_dpb_count, mfc_regs->d_num_dpb); + writel(ctx->luma_size, mfc_regs->d_first_plane_dpb_size); + writel(ctx->chroma_size, mfc_regs->d_second_plane_dpb_size); + + writel(buf_addr1, mfc_regs->d_scratch_buffer_addr); + writel(ctx->scratch_buf_size, mfc_regs->d_scratch_buffer_size); + + if (IS_MFCV8_PLUS(dev)) { + writel(ctx->img_width, + mfc_regs->d_first_plane_dpb_stride_size); + writel(ctx->img_width, + mfc_regs->d_second_plane_dpb_stride_size); + } + + buf_addr1 += ctx->scratch_buf_size; + buf_size1 -= ctx->scratch_buf_size; + + if (ctx->codec_mode == S5P_FIMV_CODEC_H264_DEC || + ctx->codec_mode == S5P_FIMV_CODEC_H264_MVC_DEC || + ctx->codec_mode == S5P_FIMV_CODEC_HEVC_DEC) { + writel(ctx->mv_size, mfc_regs->d_mv_buffer_size); + writel(ctx->mv_count, mfc_regs->d_num_mv); + } + + frame_size = ctx->luma_size; + frame_size_ch = ctx->chroma_size; + frame_size_mv = ctx->mv_size; + mfc_debug(2, "Frame size: %d ch: %d mv: %d\n", + frame_size, frame_size_ch, frame_size_mv); + + for (i = 0; i < ctx->total_dpb_count; i++) { + /* Bank2 */ + mfc_debug(2, "Luma %d: %zx\n", i, + ctx->dst_bufs[i].cookie.raw.luma); + writel(ctx->dst_bufs[i].cookie.raw.luma, + mfc_regs->d_first_plane_dpb + i * 4); + mfc_debug(2, "\tChroma %d: %zx\n", i, + ctx->dst_bufs[i].cookie.raw.chroma); + writel(ctx->dst_bufs[i].cookie.raw.chroma, + mfc_regs->d_second_plane_dpb + i * 4); + } + if (ctx->codec_mode == S5P_MFC_CODEC_H264_DEC || + ctx->codec_mode == S5P_MFC_CODEC_H264_MVC_DEC || + ctx->codec_mode == S5P_MFC_CODEC_HEVC_DEC) { + for (i = 0; i < ctx->mv_count; i++) { + /* To test alignment */ + align_gap = buf_addr1; + buf_addr1 = ALIGN(buf_addr1, 16); + align_gap = buf_addr1 - align_gap; + buf_size1 -= align_gap; + + mfc_debug(2, "\tBuf1: %zx, size: %d\n", + buf_addr1, buf_size1); + writel(buf_addr1, mfc_regs->d_mv_buffer + i * 4); + buf_addr1 += frame_size_mv; + buf_size1 -= frame_size_mv; + } + } + if (ctx->codec_mode == S5P_FIMV_CODEC_VP9_DEC) { + writel(buf_addr1, mfc_regs->d_static_buffer_addr); + writel(DEC_VP9_STATIC_BUFFER_SIZE, + mfc_regs->d_static_buffer_size); + buf_addr1 += DEC_VP9_STATIC_BUFFER_SIZE; + buf_size1 -= DEC_VP9_STATIC_BUFFER_SIZE; + } + + mfc_debug(2, "Buf1: %zx, buf_size1: %d (frames %d)\n", + buf_addr1, buf_size1, ctx->total_dpb_count); + if (buf_size1 < 0) { + mfc_debug(2, "Not enough memory has been allocated.\n"); + return -ENOMEM; + } + + writel(ctx->inst_no, mfc_regs->instance_id); + s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, + S5P_FIMV_CH_INIT_BUFS_V6, NULL); + + mfc_debug(2, "After setting buffers.\n"); + return 0; +} + +/* Set registers for encoding stream buffer */ +static int s5p_mfc_set_enc_stream_buffer_v6(struct s5p_mfc_ctx *ctx, + unsigned long addr, unsigned int size) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + + writel(addr, mfc_regs->e_stream_buffer_addr); /* 16B align */ + writel(size, mfc_regs->e_stream_buffer_size); + + mfc_debug(2, "stream buf addr: 0x%08lx, size: 0x%x\n", + addr, size); + + return 0; +} + +static void s5p_mfc_set_enc_frame_buffer_v6(struct s5p_mfc_ctx *ctx, + unsigned long y_addr, unsigned long c_addr) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + + writel(y_addr, mfc_regs->e_source_first_plane_addr); + writel(c_addr, mfc_regs->e_source_second_plane_addr); + + mfc_debug(2, "enc src y buf addr: 0x%08lx\n", y_addr); + mfc_debug(2, "enc src c buf addr: 0x%08lx\n", c_addr); +} + +static void s5p_mfc_get_enc_frame_buffer_v6(struct s5p_mfc_ctx *ctx, + unsigned long *y_addr, unsigned long *c_addr) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + unsigned long enc_recon_y_addr, enc_recon_c_addr; + + *y_addr = readl(mfc_regs->e_encoded_source_first_plane_addr); + *c_addr = readl(mfc_regs->e_encoded_source_second_plane_addr); + + enc_recon_y_addr = readl(mfc_regs->e_recon_luma_dpb_addr); + enc_recon_c_addr = readl(mfc_regs->e_recon_chroma_dpb_addr); + + mfc_debug(2, "recon y addr: 0x%08lx y_addr: 0x%08lx\n", enc_recon_y_addr, *y_addr); + mfc_debug(2, "recon c addr: 0x%08lx\n", enc_recon_c_addr); +} + +/* Set encoding ref & codec buffer */ +static int s5p_mfc_set_enc_ref_buffer_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + size_t buf_addr1; + int i, buf_size1; + + mfc_debug_enter(); + + buf_addr1 = ctx->bank1.dma; + buf_size1 = ctx->bank1.size; + + mfc_debug(2, "Buf1: %p (%d)\n", (void *)buf_addr1, buf_size1); + + if (IS_MFCV10(dev)) { + /* start address of per buffer is aligned */ + for (i = 0; i < ctx->pb_count; i++) { + writel(buf_addr1, mfc_regs->e_luma_dpb + (4 * i)); + buf_addr1 += ctx->luma_dpb_size; + buf_size1 -= ctx->luma_dpb_size; + } + for (i = 0; i < ctx->pb_count; i++) { + writel(buf_addr1, mfc_regs->e_chroma_dpb + (4 * i)); + buf_addr1 += ctx->chroma_dpb_size; + buf_size1 -= ctx->chroma_dpb_size; + } + for (i = 0; i < ctx->pb_count; i++) { + writel(buf_addr1, mfc_regs->e_me_buffer + (4 * i)); + buf_addr1 += ctx->me_buffer_size; + buf_size1 -= ctx->me_buffer_size; + } + } else { + for (i = 0; i < ctx->pb_count; i++) { + writel(buf_addr1, mfc_regs->e_luma_dpb + (4 * i)); + buf_addr1 += ctx->luma_dpb_size; + writel(buf_addr1, mfc_regs->e_chroma_dpb + (4 * i)); + buf_addr1 += ctx->chroma_dpb_size; + writel(buf_addr1, mfc_regs->e_me_buffer + (4 * i)); + buf_addr1 += ctx->me_buffer_size; + buf_size1 -= (ctx->luma_dpb_size + ctx->chroma_dpb_size + + ctx->me_buffer_size); + } + } + + writel(buf_addr1, mfc_regs->e_scratch_buffer_addr); + writel(ctx->scratch_buf_size, mfc_regs->e_scratch_buffer_size); + buf_addr1 += ctx->scratch_buf_size; + buf_size1 -= ctx->scratch_buf_size; + + writel(buf_addr1, mfc_regs->e_tmv_buffer0); + buf_addr1 += ctx->tmv_buffer_size >> 1; + writel(buf_addr1, mfc_regs->e_tmv_buffer1); + buf_addr1 += ctx->tmv_buffer_size >> 1; + buf_size1 -= ctx->tmv_buffer_size; + + mfc_debug(2, "Buf1: %zu, buf_size1: %d (ref frames %d)\n", + buf_addr1, buf_size1, ctx->pb_count); + if (buf_size1 < 0) { + mfc_debug(2, "Not enough memory has been allocated.\n"); + return -ENOMEM; + } + + writel(ctx->inst_no, mfc_regs->instance_id); + s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, + S5P_FIMV_CH_INIT_BUFS_V6, NULL); + + mfc_debug_leave(); + + return 0; +} + +static int s5p_mfc_set_slice_mode(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + + /* multi-slice control */ + /* multi-slice MB number or bit size */ + writel(ctx->slice_mode, mfc_regs->e_mslice_mode); + if (ctx->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB) { + writel(ctx->slice_size.mb, mfc_regs->e_mslice_size_mb); + } else if (ctx->slice_mode == + V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES) { + writel(ctx->slice_size.bits, mfc_regs->e_mslice_size_bits); + } else { + writel(0x0, mfc_regs->e_mslice_size_mb); + writel(0x0, mfc_regs->e_mslice_size_bits); + } + + return 0; +} + +static int s5p_mfc_set_enc_params(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + unsigned int reg = 0; + + mfc_debug_enter(); + + /* width */ + writel(ctx->img_width, mfc_regs->e_frame_width); /* 16 align */ + /* height */ + writel(ctx->img_height, mfc_regs->e_frame_height); /* 16 align */ + + /* cropped width */ + writel(ctx->img_width, mfc_regs->e_cropped_frame_width); + /* cropped height */ + writel(ctx->img_height, mfc_regs->e_cropped_frame_height); + /* cropped offset */ + writel(0x0, mfc_regs->e_frame_crop_offset); + + /* pictype : IDR period */ + reg = 0; + reg |= p->gop_size & 0xFFFF; + writel(reg, mfc_regs->e_gop_config); + + /* multi-slice control */ + /* multi-slice MB number or bit size */ + ctx->slice_mode = p->slice_mode; + reg = 0; + if (p->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_MB) { + reg |= (0x1 << 3); + writel(reg, mfc_regs->e_enc_options); + ctx->slice_size.mb = p->slice_mb; + } else if (p->slice_mode == V4L2_MPEG_VIDEO_MULTI_SLICE_MODE_MAX_BYTES) { + reg |= (0x1 << 3); + writel(reg, mfc_regs->e_enc_options); + ctx->slice_size.bits = p->slice_bit; + } else { + reg &= ~(0x1 << 3); + writel(reg, mfc_regs->e_enc_options); + } + + s5p_mfc_set_slice_mode(ctx); + + /* cyclic intra refresh */ + writel(p->intra_refresh_mb, mfc_regs->e_ir_size); + reg = readl(mfc_regs->e_enc_options); + if (p->intra_refresh_mb == 0) + reg &= ~(0x1 << 4); + else + reg |= (0x1 << 4); + writel(reg, mfc_regs->e_enc_options); + + /* 'NON_REFERENCE_STORE_ENABLE' for debugging */ + reg = readl(mfc_regs->e_enc_options); + reg &= ~(0x1 << 9); + writel(reg, mfc_regs->e_enc_options); + + /* memory structure cur. frame */ + if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12M) { + /* 0: Linear, 1: 2D tiled*/ + reg = readl(mfc_regs->e_enc_options); + reg &= ~(0x1 << 7); + writel(reg, mfc_regs->e_enc_options); + /* 0: NV12(CbCr), 1: NV21(CrCb) */ + writel(0x0, mfc_regs->pixel_format); + } else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV21M) { + /* 0: Linear, 1: 2D tiled*/ + reg = readl(mfc_regs->e_enc_options); + reg &= ~(0x1 << 7); + writel(reg, mfc_regs->e_enc_options); + /* 0: NV12(CbCr), 1: NV21(CrCb) */ + writel(0x1, mfc_regs->pixel_format); + } else if (ctx->src_fmt->fourcc == V4L2_PIX_FMT_NV12MT_16X16) { + /* 0: Linear, 1: 2D tiled*/ + reg = readl(mfc_regs->e_enc_options); + reg |= (0x1 << 7); + writel(reg, mfc_regs->e_enc_options); + /* 0: NV12(CbCr), 1: NV21(CrCb) */ + writel(0x0, mfc_regs->pixel_format); + } + + /* memory structure recon. frame */ + /* 0: Linear, 1: 2D tiled */ + reg = readl(mfc_regs->e_enc_options); + reg |= (0x1 << 8); + writel(reg, mfc_regs->e_enc_options); + + /* padding control & value */ + writel(0x0, mfc_regs->e_padding_ctrl); + if (p->pad) { + reg = 0; + /** enable */ + reg |= (1UL << 31); + /** cr value */ + reg |= ((p->pad_cr & 0xFF) << 16); + /** cb value */ + reg |= ((p->pad_cb & 0xFF) << 8); + /** y value */ + reg |= p->pad_luma & 0xFF; + writel(reg, mfc_regs->e_padding_ctrl); + } + + /* rate control config. */ + reg = 0; + /* frame-level rate control */ + reg |= ((p->rc_frame & 0x1) << 9); + writel(reg, mfc_regs->e_rc_config); + + /* bit rate */ + if (p->rc_frame) + writel(p->rc_bitrate, + mfc_regs->e_rc_bit_rate); + else + writel(1, mfc_regs->e_rc_bit_rate); + + /* reaction coefficient */ + if (p->rc_frame) { + if (p->rc_reaction_coeff < TIGHT_CBR_MAX) /* tight CBR */ + writel(1, mfc_regs->e_rc_mode); + else /* loose CBR */ + writel(2, mfc_regs->e_rc_mode); + } + + /* seq header ctrl */ + reg = readl(mfc_regs->e_enc_options); + reg &= ~(0x1 << 2); + reg |= ((p->seq_hdr_mode & 0x1) << 2); + + /* frame skip mode */ + reg &= ~(0x3); + reg |= (p->frame_skip_mode & 0x3); + writel(reg, mfc_regs->e_enc_options); + + /* 'DROP_CONTROL_ENABLE', disable */ + reg = readl(mfc_regs->e_rc_config); + reg &= ~(0x1 << 10); + writel(reg, mfc_regs->e_rc_config); + + /* setting for MV range [16, 256] */ + reg = (p->mv_h_range & S5P_FIMV_E_MV_RANGE_V6_MASK); + writel(reg, mfc_regs->e_mv_hor_range); + + reg = (p->mv_v_range & S5P_FIMV_E_MV_RANGE_V6_MASK); + writel(reg, mfc_regs->e_mv_ver_range); + + writel(0x0, mfc_regs->e_frame_insertion); + writel(0x0, mfc_regs->e_roi_buffer_addr); + writel(0x0, mfc_regs->e_param_change); + writel(0x0, mfc_regs->e_rc_roi_ctrl); + writel(0x0, mfc_regs->e_picture_tag); + + writel(0x0, mfc_regs->e_bit_count_enable); + writel(0x0, mfc_regs->e_max_bit_count); + writel(0x0, mfc_regs->e_min_bit_count); + + writel(0x0, mfc_regs->e_metadata_buffer_addr); + writel(0x0, mfc_regs->e_metadata_buffer_size); + + mfc_debug_leave(); + + return 0; +} + +static int s5p_mfc_set_enc_params_h264(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_h264_enc_params *p_h264 = &p->codec.h264; + unsigned int reg = 0; + int i; + + mfc_debug_enter(); + + s5p_mfc_set_enc_params(ctx); + + /* pictype : number of B */ + reg = readl(mfc_regs->e_gop_config); + reg &= ~(0x3 << 16); + reg |= ((p->num_b_frame & 0x3) << 16); + writel(reg, mfc_regs->e_gop_config); + + /* profile & level */ + reg = 0; + /** level */ + reg |= ((p_h264->level & 0xFF) << 8); + /** profile - 0 ~ 3 */ + reg |= p_h264->profile & 0x3F; + writel(reg, mfc_regs->e_picture_profile); + + /* rate control config. */ + reg = readl(mfc_regs->e_rc_config); + /** macroblock level rate control */ + reg &= ~(0x1 << 8); + reg |= ((p->rc_mb & 0x1) << 8); + writel(reg, mfc_regs->e_rc_config); + + /** frame QP */ + reg &= ~(0x3F); + reg |= p_h264->rc_frame_qp & 0x3F; + writel(reg, mfc_regs->e_rc_config); + + /* max & min value of QP */ + reg = 0; + /** max QP */ + reg |= ((p_h264->rc_max_qp & 0x3F) << 8); + /** min QP */ + reg |= p_h264->rc_min_qp & 0x3F; + writel(reg, mfc_regs->e_rc_qp_bound); + + /* other QPs */ + writel(0x0, mfc_regs->e_fixed_picture_qp); + if (!p->rc_frame && !p->rc_mb) { + reg = 0; + reg |= ((p_h264->rc_b_frame_qp & 0x3F) << 16); + reg |= ((p_h264->rc_p_frame_qp & 0x3F) << 8); + reg |= p_h264->rc_frame_qp & 0x3F; + writel(reg, mfc_regs->e_fixed_picture_qp); + } + + /* frame rate */ + if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) { + reg = 0; + reg |= ((p->rc_framerate_num & 0xFFFF) << 16); + reg |= p->rc_framerate_denom & 0xFFFF; + writel(reg, mfc_regs->e_rc_frame_rate); + } + + /* vbv buffer size */ + if (p->frame_skip_mode == + V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { + writel(p_h264->cpb_size & 0xFFFF, + mfc_regs->e_vbv_buffer_size); + + if (p->rc_frame) + writel(p->vbv_delay, mfc_regs->e_vbv_init_delay); + } + + /* interlace */ + reg = 0; + reg |= ((p_h264->interlace & 0x1) << 3); + writel(reg, mfc_regs->e_h264_options); + + /* height */ + if (p_h264->interlace) { + writel(ctx->img_height >> 1, + mfc_regs->e_frame_height); /* 32 align */ + /* cropped height */ + writel(ctx->img_height >> 1, + mfc_regs->e_cropped_frame_height); + } + + /* loop filter ctrl */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x3 << 1); + reg |= ((p_h264->loop_filter_mode & 0x3) << 1); + writel(reg, mfc_regs->e_h264_options); + + /* loopfilter alpha offset */ + if (p_h264->loop_filter_alpha < 0) { + reg = 0x10; + reg |= (0xFF - p_h264->loop_filter_alpha) + 1; + } else { + reg = 0x00; + reg |= (p_h264->loop_filter_alpha & 0xF); + } + writel(reg, mfc_regs->e_h264_lf_alpha_offset); + + /* loopfilter beta offset */ + if (p_h264->loop_filter_beta < 0) { + reg = 0x10; + reg |= (0xFF - p_h264->loop_filter_beta) + 1; + } else { + reg = 0x00; + reg |= (p_h264->loop_filter_beta & 0xF); + } + writel(reg, mfc_regs->e_h264_lf_beta_offset); + + /* entropy coding mode */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x1); + reg |= p_h264->entropy_mode & 0x1; + writel(reg, mfc_regs->e_h264_options); + + /* number of ref. picture */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x1 << 7); + reg |= (((p_h264->num_ref_pic_4p - 1) & 0x1) << 7); + writel(reg, mfc_regs->e_h264_options); + + /* 8x8 transform enable */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x3 << 12); + reg |= ((p_h264->_8x8_transform & 0x3) << 12); + writel(reg, mfc_regs->e_h264_options); + + /* macroblock adaptive scaling features */ + writel(0x0, mfc_regs->e_mb_rc_config); + if (p->rc_mb) { + reg = 0; + /** dark region */ + reg |= ((p_h264->rc_mb_dark & 0x1) << 3); + /** smooth region */ + reg |= ((p_h264->rc_mb_smooth & 0x1) << 2); + /** static region */ + reg |= ((p_h264->rc_mb_static & 0x1) << 1); + /** high activity region */ + reg |= p_h264->rc_mb_activity & 0x1; + writel(reg, mfc_regs->e_mb_rc_config); + } + + /* aspect ratio VUI */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x1 << 5); + reg |= ((p_h264->vui_sar & 0x1) << 5); + writel(reg, mfc_regs->e_h264_options); + + writel(0x0, mfc_regs->e_aspect_ratio); + writel(0x0, mfc_regs->e_extended_sar); + if (p_h264->vui_sar) { + /* aspect ration IDC */ + reg = 0; + reg |= p_h264->vui_sar_idc & 0xFF; + writel(reg, mfc_regs->e_aspect_ratio); + if (p_h264->vui_sar_idc == 0xFF) { + /* extended SAR */ + reg = 0; + reg |= (p_h264->vui_ext_sar_width & 0xFFFF) << 16; + reg |= p_h264->vui_ext_sar_height & 0xFFFF; + writel(reg, mfc_regs->e_extended_sar); + } + } + + /* intra picture period for H.264 open GOP */ + /* control */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x1 << 4); + reg |= ((p_h264->open_gop & 0x1) << 4); + writel(reg, mfc_regs->e_h264_options); + + /* value */ + writel(0x0, mfc_regs->e_h264_i_period); + if (p_h264->open_gop) { + reg = 0; + reg |= p_h264->open_gop_size & 0xFFFF; + writel(reg, mfc_regs->e_h264_i_period); + } + + /* 'WEIGHTED_BI_PREDICTION' for B is disable */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x3 << 9); + writel(reg, mfc_regs->e_h264_options); + + /* 'CONSTRAINED_INTRA_PRED_ENABLE' is disable */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x1 << 14); + writel(reg, mfc_regs->e_h264_options); + + /* ASO */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x1 << 6); + reg |= ((p_h264->aso & 0x1) << 6); + writel(reg, mfc_regs->e_h264_options); + + /* hier qp enable */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x1 << 8); + reg |= ((p_h264->open_gop & 0x1) << 8); + writel(reg, mfc_regs->e_h264_options); + reg = 0; + if (p_h264->hier_qp && p_h264->hier_qp_layer) { + reg |= (p_h264->hier_qp_type & 0x1) << 0x3; + reg |= p_h264->hier_qp_layer & 0x7; + writel(reg, mfc_regs->e_h264_num_t_layer); + /* QP value for each layer */ + for (i = 0; i < p_h264->hier_qp_layer && + i < ARRAY_SIZE(p_h264->hier_qp_layer_qp); i++) { + writel(p_h264->hier_qp_layer_qp[i], + mfc_regs->e_h264_hierarchical_qp_layer0 + + i * 4); + } + } + /* number of coding layer should be zero when hierarchical is disable */ + writel(reg, mfc_regs->e_h264_num_t_layer); + + /* frame packing SEI generation */ + reg = readl(mfc_regs->e_h264_options); + reg &= ~(0x1 << 25); + reg |= ((p_h264->sei_frame_packing & 0x1) << 25); + writel(reg, mfc_regs->e_h264_options); + if (p_h264->sei_frame_packing) { + reg = 0; + /** current frame0 flag */ + reg |= ((p_h264->sei_fp_curr_frame_0 & 0x1) << 2); + /** arrangement type */ + reg |= p_h264->sei_fp_arrangement_type & 0x3; + writel(reg, mfc_regs->e_h264_frame_packing_sei_info); + } + + if (p_h264->fmo) { + switch (p_h264->fmo_map_type) { + case V4L2_MPEG_VIDEO_H264_FMO_MAP_TYPE_INTERLEAVED_SLICES: + if (p_h264->fmo_slice_grp > 4) + p_h264->fmo_slice_grp = 4; + for (i = 0; i < (p_h264->fmo_slice_grp & 0xF); i++) + writel(p_h264->fmo_run_len[i] - 1, + mfc_regs->e_h264_fmo_run_length_minus1_0 + + i * 4); + break; + case V4L2_MPEG_VIDEO_H264_FMO_MAP_TYPE_SCATTERED_SLICES: + if (p_h264->fmo_slice_grp > 4) + p_h264->fmo_slice_grp = 4; + break; + case V4L2_MPEG_VIDEO_H264_FMO_MAP_TYPE_RASTER_SCAN: + case V4L2_MPEG_VIDEO_H264_FMO_MAP_TYPE_WIPE_SCAN: + if (p_h264->fmo_slice_grp > 2) + p_h264->fmo_slice_grp = 2; + writel(p_h264->fmo_chg_dir & 0x1, + mfc_regs->e_h264_fmo_slice_grp_change_dir); + /* the valid range is 0 ~ number of macroblocks -1 */ + writel(p_h264->fmo_chg_rate, + mfc_regs->e_h264_fmo_slice_grp_change_rate_minus1); + break; + default: + mfc_err("Unsupported map type for FMO: %d\n", + p_h264->fmo_map_type); + p_h264->fmo_map_type = 0; + p_h264->fmo_slice_grp = 1; + break; + } + + writel(p_h264->fmo_map_type, + mfc_regs->e_h264_fmo_slice_grp_map_type); + writel(p_h264->fmo_slice_grp - 1, + mfc_regs->e_h264_fmo_num_slice_grp_minus1); + } else { + writel(0, mfc_regs->e_h264_fmo_num_slice_grp_minus1); + } + + mfc_debug_leave(); + + return 0; +} + +static int s5p_mfc_set_enc_params_mpeg4(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_mpeg4_enc_params *p_mpeg4 = &p->codec.mpeg4; + unsigned int reg = 0; + + mfc_debug_enter(); + + s5p_mfc_set_enc_params(ctx); + + /* pictype : number of B */ + reg = readl(mfc_regs->e_gop_config); + reg &= ~(0x3 << 16); + reg |= ((p->num_b_frame & 0x3) << 16); + writel(reg, mfc_regs->e_gop_config); + + /* profile & level */ + reg = 0; + /** level */ + reg |= ((p_mpeg4->level & 0xFF) << 8); + /** profile - 0 ~ 1 */ + reg |= p_mpeg4->profile & 0x3F; + writel(reg, mfc_regs->e_picture_profile); + + /* rate control config. */ + reg = readl(mfc_regs->e_rc_config); + /** macroblock level rate control */ + reg &= ~(0x1 << 8); + reg |= ((p->rc_mb & 0x1) << 8); + writel(reg, mfc_regs->e_rc_config); + + /** frame QP */ + reg &= ~(0x3F); + reg |= p_mpeg4->rc_frame_qp & 0x3F; + writel(reg, mfc_regs->e_rc_config); + + /* max & min value of QP */ + reg = 0; + /** max QP */ + reg |= ((p_mpeg4->rc_max_qp & 0x3F) << 8); + /** min QP */ + reg |= p_mpeg4->rc_min_qp & 0x3F; + writel(reg, mfc_regs->e_rc_qp_bound); + + /* other QPs */ + writel(0x0, mfc_regs->e_fixed_picture_qp); + if (!p->rc_frame && !p->rc_mb) { + reg = 0; + reg |= ((p_mpeg4->rc_b_frame_qp & 0x3F) << 16); + reg |= ((p_mpeg4->rc_p_frame_qp & 0x3F) << 8); + reg |= p_mpeg4->rc_frame_qp & 0x3F; + writel(reg, mfc_regs->e_fixed_picture_qp); + } + + /* frame rate */ + if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) { + reg = 0; + reg |= ((p->rc_framerate_num & 0xFFFF) << 16); + reg |= p->rc_framerate_denom & 0xFFFF; + writel(reg, mfc_regs->e_rc_frame_rate); + } + + /* vbv buffer size */ + if (p->frame_skip_mode == + V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { + writel(p->vbv_size & 0xFFFF, mfc_regs->e_vbv_buffer_size); + + if (p->rc_frame) + writel(p->vbv_delay, mfc_regs->e_vbv_init_delay); + } + + /* Disable HEC */ + writel(0x0, mfc_regs->e_mpeg4_options); + writel(0x0, mfc_regs->e_mpeg4_hec_period); + + mfc_debug_leave(); + + return 0; +} + +static int s5p_mfc_set_enc_params_h263(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_mpeg4_enc_params *p_h263 = &p->codec.mpeg4; + unsigned int reg = 0; + + mfc_debug_enter(); + + s5p_mfc_set_enc_params(ctx); + + /* profile & level */ + reg = 0; + /** profile */ + reg |= (0x1 << 4); + writel(reg, mfc_regs->e_picture_profile); + + /* rate control config. */ + reg = readl(mfc_regs->e_rc_config); + /** macroblock level rate control */ + reg &= ~(0x1 << 8); + reg |= ((p->rc_mb & 0x1) << 8); + writel(reg, mfc_regs->e_rc_config); + + /** frame QP */ + reg &= ~(0x3F); + reg |= p_h263->rc_frame_qp & 0x3F; + writel(reg, mfc_regs->e_rc_config); + + /* max & min value of QP */ + reg = 0; + /** max QP */ + reg |= ((p_h263->rc_max_qp & 0x3F) << 8); + /** min QP */ + reg |= p_h263->rc_min_qp & 0x3F; + writel(reg, mfc_regs->e_rc_qp_bound); + + /* other QPs */ + writel(0x0, mfc_regs->e_fixed_picture_qp); + if (!p->rc_frame && !p->rc_mb) { + reg = 0; + reg |= ((p_h263->rc_b_frame_qp & 0x3F) << 16); + reg |= ((p_h263->rc_p_frame_qp & 0x3F) << 8); + reg |= p_h263->rc_frame_qp & 0x3F; + writel(reg, mfc_regs->e_fixed_picture_qp); + } + + /* frame rate */ + if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) { + reg = 0; + reg |= ((p->rc_framerate_num & 0xFFFF) << 16); + reg |= p->rc_framerate_denom & 0xFFFF; + writel(reg, mfc_regs->e_rc_frame_rate); + } + + /* vbv buffer size */ + if (p->frame_skip_mode == + V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { + writel(p->vbv_size & 0xFFFF, mfc_regs->e_vbv_buffer_size); + + if (p->rc_frame) + writel(p->vbv_delay, mfc_regs->e_vbv_init_delay); + } + + mfc_debug_leave(); + + return 0; +} + +static int s5p_mfc_set_enc_params_vp8(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_vp8_enc_params *p_vp8 = &p->codec.vp8; + unsigned int reg = 0; + unsigned int val = 0; + + mfc_debug_enter(); + + s5p_mfc_set_enc_params(ctx); + + /* pictype : number of B */ + reg = readl(mfc_regs->e_gop_config); + reg &= ~(0x3 << 16); + reg |= ((p->num_b_frame & 0x3) << 16); + writel(reg, mfc_regs->e_gop_config); + + /* profile - 0 ~ 3 */ + reg = p_vp8->profile & 0x3; + writel(reg, mfc_regs->e_picture_profile); + + /* rate control config. */ + reg = readl(mfc_regs->e_rc_config); + /** macroblock level rate control */ + reg &= ~(0x1 << 8); + reg |= ((p->rc_mb & 0x1) << 8); + writel(reg, mfc_regs->e_rc_config); + + /* frame rate */ + if (p->rc_frame && p->rc_framerate_num && p->rc_framerate_denom) { + reg = 0; + reg |= ((p->rc_framerate_num & 0xFFFF) << 16); + reg |= p->rc_framerate_denom & 0xFFFF; + writel(reg, mfc_regs->e_rc_frame_rate); + } + + /* frame QP */ + reg &= ~(0x7F); + reg |= p_vp8->rc_frame_qp & 0x7F; + writel(reg, mfc_regs->e_rc_config); + + /* other QPs */ + writel(0x0, mfc_regs->e_fixed_picture_qp); + if (!p->rc_frame && !p->rc_mb) { + reg = 0; + reg |= ((p_vp8->rc_p_frame_qp & 0x7F) << 8); + reg |= p_vp8->rc_frame_qp & 0x7F; + writel(reg, mfc_regs->e_fixed_picture_qp); + } + + /* max QP */ + reg = ((p_vp8->rc_max_qp & 0x7F) << 8); + /* min QP */ + reg |= p_vp8->rc_min_qp & 0x7F; + writel(reg, mfc_regs->e_rc_qp_bound); + + /* vbv buffer size */ + if (p->frame_skip_mode == + V4L2_MPEG_MFC51_VIDEO_FRAME_SKIP_MODE_BUF_LIMIT) { + writel(p->vbv_size & 0xFFFF, mfc_regs->e_vbv_buffer_size); + + if (p->rc_frame) + writel(p->vbv_delay, mfc_regs->e_vbv_init_delay); + } + + /* VP8 specific params */ + reg = 0; + reg |= (p_vp8->imd_4x4 & 0x1) << 10; + switch (p_vp8->num_partitions) { + case V4L2_CID_MPEG_VIDEO_VPX_1_PARTITION: + val = 0; + break; + case V4L2_CID_MPEG_VIDEO_VPX_2_PARTITIONS: + val = 2; + break; + case V4L2_CID_MPEG_VIDEO_VPX_4_PARTITIONS: + val = 4; + break; + case V4L2_CID_MPEG_VIDEO_VPX_8_PARTITIONS: + val = 8; + break; + } + reg |= (val & 0xF) << 3; + reg |= (p_vp8->num_ref & 0x2); + writel(reg, mfc_regs->e_vp8_options); + + mfc_debug_leave(); + + return 0; +} + +static int s5p_mfc_set_enc_params_hevc(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_hevc_enc_params *p_hevc = &p->codec.hevc; + unsigned int reg = 0; + int i; + + mfc_debug_enter(); + + s5p_mfc_set_enc_params(ctx); + + /* pictype : number of B */ + reg = readl(mfc_regs->e_gop_config); + /* num_b_frame - 0 ~ 2 */ + reg &= ~(0x3 << 16); + reg |= (p->num_b_frame << 16); + writel(reg, mfc_regs->e_gop_config); + + /* UHD encoding case */ + if ((ctx->img_width == 3840) && (ctx->img_height == 2160)) { + p_hevc->level = 51; + p_hevc->tier = 0; + /* this tier can be changed */ + } + + /* tier & level */ + reg = 0; + /* profile */ + reg |= p_hevc->profile & 0x3; + /* level */ + reg &= ~(0xFF << 8); + reg |= (p_hevc->level << 8); + /* tier - 0 ~ 1 */ + reg |= (p_hevc->tier << 16); + writel(reg, mfc_regs->e_picture_profile); + + switch (p_hevc->loopfilter) { + case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED: + p_hevc->loopfilter_disable = 1; + break; + case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_ENABLED: + p_hevc->loopfilter_disable = 0; + p_hevc->loopfilter_across = 1; + break; + case V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY: + p_hevc->loopfilter_disable = 0; + p_hevc->loopfilter_across = 0; + break; + } + + /* max partition depth */ + reg = 0; + reg |= (p_hevc->max_partition_depth & 0x1); + reg |= (p_hevc->num_refs_for_p-1) << 2; + reg |= (p_hevc->refreshtype & 0x3) << 3; + reg |= (p_hevc->const_intra_period_enable & 0x1) << 5; + reg |= (p_hevc->lossless_cu_enable & 0x1) << 6; + reg |= (p_hevc->wavefront_enable & 0x1) << 7; + reg |= (p_hevc->loopfilter_disable & 0x1) << 8; + reg |= (p_hevc->loopfilter_across & 0x1) << 9; + reg |= (p_hevc->enable_ltr & 0x1) << 10; + reg |= (p_hevc->hier_qp_enable & 0x1) << 11; + reg |= (p_hevc->general_pb_enable & 0x1) << 13; + reg |= (p_hevc->temporal_id_enable & 0x1) << 14; + reg |= (p_hevc->strong_intra_smooth & 0x1) << 15; + reg |= (p_hevc->intra_pu_split_disable & 0x1) << 16; + reg |= (p_hevc->tmv_prediction_disable & 0x1) << 17; + reg |= (p_hevc->max_num_merge_mv & 0x7) << 18; + reg |= (p_hevc->encoding_nostartcode_enable & 0x1) << 23; + reg |= (p_hevc->prepend_sps_pps_to_idr << 26); + + writel(reg, mfc_regs->e_hevc_options); + /* refresh period */ + if (p_hevc->refreshtype) { + reg = 0; + reg |= (p_hevc->refreshperiod & 0xFFFF); + writel(reg, mfc_regs->e_hevc_refresh_period); + } + /* loop filter setting */ + if (!(p_hevc->loopfilter_disable & 0x1)) { + reg = 0; + reg |= (p_hevc->lf_beta_offset_div2); + writel(reg, mfc_regs->e_hevc_lf_beta_offset_div2); + reg = 0; + reg |= (p_hevc->lf_tc_offset_div2); + writel(reg, mfc_regs->e_hevc_lf_tc_offset_div2); + } + /* hier qp enable */ + if (p_hevc->num_hier_layer) { + reg = 0; + reg |= (p_hevc->hier_qp_type & 0x1) << 0x3; + reg |= p_hevc->num_hier_layer & 0x7; + writel(reg, mfc_regs->e_num_t_layer); + /* QP value for each layer */ + if (p_hevc->hier_qp_enable) { + for (i = 0; i < 7; i++) + writel(p_hevc->hier_qp_layer[i], + mfc_regs->e_hier_qp_layer0 + i * 4); + } + if (p->rc_frame) { + for (i = 0; i < 7; i++) + writel(p_hevc->hier_bit_layer[i], + mfc_regs->e_hier_bit_rate_layer0 + + i * 4); + } + } + + /* rate control config. */ + reg = readl(mfc_regs->e_rc_config); + /* macroblock level rate control */ + reg &= ~(0x1 << 8); + reg |= (p->rc_mb << 8); + writel(reg, mfc_regs->e_rc_config); + /* frame QP */ + reg &= ~(0xFF); + reg |= p_hevc->rc_frame_qp; + writel(reg, mfc_regs->e_rc_config); + + /* frame rate */ + if (p->rc_frame) { + reg = 0; + reg &= ~(0xFFFF << 16); + reg |= ((p_hevc->rc_framerate) << 16); + reg &= ~(0xFFFF); + reg |= FRAME_DELTA_DEFAULT; + writel(reg, mfc_regs->e_rc_frame_rate); + } + + /* max & min value of QP */ + reg = 0; + /* max QP */ + reg &= ~(0xFF << 8); + reg |= (p_hevc->rc_max_qp << 8); + /* min QP */ + reg &= ~(0xFF); + reg |= p_hevc->rc_min_qp; + writel(reg, mfc_regs->e_rc_qp_bound); + + writel(0x0, mfc_regs->e_fixed_picture_qp); + if (!p->rc_frame && !p->rc_mb) { + reg = 0; + reg &= ~(0xFF << 16); + reg |= (p_hevc->rc_b_frame_qp << 16); + reg &= ~(0xFF << 8); + reg |= (p_hevc->rc_p_frame_qp << 8); + reg &= ~(0xFF); + reg |= p_hevc->rc_frame_qp; + writel(reg, mfc_regs->e_fixed_picture_qp); + } + mfc_debug_leave(); + + return 0; +} + +/* Initialize decoding */ +static int s5p_mfc_init_decode_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + unsigned int reg = 0; + int fmo_aso_ctrl = 0; + + mfc_debug_enter(); + mfc_debug(2, "InstNo: %d/%d\n", ctx->inst_no, + S5P_FIMV_CH_SEQ_HEADER_V6); + mfc_debug(2, "BUFs: %08x %08x %08x\n", + readl(mfc_regs->d_cpb_buffer_addr), + readl(mfc_regs->d_cpb_buffer_addr), + readl(mfc_regs->d_cpb_buffer_addr)); + + /* FMO_ASO_CTRL - 0: Enable, 1: Disable */ + reg |= (fmo_aso_ctrl << S5P_FIMV_D_OPT_FMO_ASO_CTRL_MASK_V6); + + if (ctx->display_delay_enable) { + reg |= (0x1 << S5P_FIMV_D_OPT_DDELAY_EN_SHIFT_V6); + writel(ctx->display_delay, mfc_regs->d_display_delay); + } + + if (IS_MFCV7_PLUS(dev) || IS_MFCV6_V2(dev)) { + writel(reg, mfc_regs->d_dec_options); + reg = 0; + } + + /* Setup loop filter, for decoding this is only valid for MPEG4 */ + if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_DEC) { + mfc_debug(2, "Set loop filter to: %d\n", + ctx->loop_filter_mpeg4); + reg |= (ctx->loop_filter_mpeg4 << + S5P_FIMV_D_OPT_LF_CTRL_SHIFT_V6); + } + if (ctx->dst_fmt->fourcc == V4L2_PIX_FMT_NV12MT_16X16) + reg |= (0x1 << S5P_FIMV_D_OPT_TILE_MODE_SHIFT_V6); + + if (IS_MFCV7_PLUS(dev) || IS_MFCV6_V2(dev)) + writel(reg, mfc_regs->d_init_buffer_options); + else + writel(reg, mfc_regs->d_dec_options); + + /* 0: NV12(CbCr), 1: NV21(CrCb) */ + if (ctx->dst_fmt->fourcc == V4L2_PIX_FMT_NV21M) + writel(0x1, mfc_regs->pixel_format); + else + writel(0x0, mfc_regs->pixel_format); + + + /* sei parse */ + writel(ctx->sei_fp_parse & 0x1, mfc_regs->d_sei_enable); + + writel(ctx->inst_no, mfc_regs->instance_id); + s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, + S5P_FIMV_CH_SEQ_HEADER_V6, NULL); + + mfc_debug_leave(); + return 0; +} + +static inline void s5p_mfc_set_flush(struct s5p_mfc_ctx *ctx, int flush) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + + if (flush) { + dev->curr_ctx = ctx->num; + writel(ctx->inst_no, mfc_regs->instance_id); + s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, + S5P_FIMV_H2R_CMD_FLUSH_V6, NULL); + } +} + +/* Decode a single frame */ +static int s5p_mfc_decode_one_frame_v6(struct s5p_mfc_ctx *ctx, + enum s5p_mfc_decode_arg last_frame) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + + writel(ctx->dec_dst_flag, mfc_regs->d_available_dpb_flag_lower); + writel(ctx->slice_interface & 0x1, mfc_regs->d_slice_if_enable); + + writel(ctx->inst_no, mfc_regs->instance_id); + /* Issue different commands to instance basing on whether it + * is the last frame or not. */ + switch (last_frame) { + case 0: + s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, + S5P_FIMV_CH_FRAME_START_V6, NULL); + break; + case 1: + s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, + S5P_FIMV_CH_LAST_FRAME_V6, NULL); + break; + default: + mfc_err("Unsupported last frame arg.\n"); + return -EINVAL; + } + + mfc_debug(2, "Decoding a usual frame.\n"); + return 0; +} + +static int s5p_mfc_init_encode_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + + if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC) + s5p_mfc_set_enc_params_h264(ctx); + else if (ctx->codec_mode == S5P_MFC_CODEC_MPEG4_ENC) + s5p_mfc_set_enc_params_mpeg4(ctx); + else if (ctx->codec_mode == S5P_MFC_CODEC_H263_ENC) + s5p_mfc_set_enc_params_h263(ctx); + else if (ctx->codec_mode == S5P_MFC_CODEC_VP8_ENC) + s5p_mfc_set_enc_params_vp8(ctx); + else if (ctx->codec_mode == S5P_FIMV_CODEC_HEVC_ENC) + s5p_mfc_set_enc_params_hevc(ctx); + else { + mfc_err("Unknown codec for encoding (%x).\n", + ctx->codec_mode); + return -EINVAL; + } + + /* Set stride lengths for v7 & above */ + if (IS_MFCV7_PLUS(dev)) { + writel(ctx->img_width, mfc_regs->e_source_first_plane_stride); + writel(ctx->img_width, mfc_regs->e_source_second_plane_stride); + } + + writel(ctx->inst_no, mfc_regs->instance_id); + s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, + S5P_FIMV_CH_SEQ_HEADER_V6, NULL); + + return 0; +} + +static int s5p_mfc_h264_set_aso_slice_order_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + struct s5p_mfc_enc_params *p = &ctx->enc_params; + struct s5p_mfc_h264_enc_params *p_h264 = &p->codec.h264; + int i; + + if (p_h264->aso) { + for (i = 0; i < ARRAY_SIZE(p_h264->aso_slice_order); i++) { + writel(p_h264->aso_slice_order[i], + mfc_regs->e_h264_aso_slice_order_0 + i * 4); + } + } + return 0; +} + +/* Encode a single frame */ +static int s5p_mfc_encode_one_frame_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + int cmd; + + mfc_debug(2, "++\n"); + + /* memory structure cur. frame */ + + if (ctx->codec_mode == S5P_MFC_CODEC_H264_ENC) + s5p_mfc_h264_set_aso_slice_order_v6(ctx); + + s5p_mfc_set_slice_mode(ctx); + + if (ctx->state != MFCINST_FINISHING) + cmd = S5P_FIMV_CH_FRAME_START_V6; + else + cmd = S5P_FIMV_CH_LAST_FRAME_V6; + + writel(ctx->inst_no, mfc_regs->instance_id); + s5p_mfc_hw_call(dev->mfc_cmds, cmd_host2risc, dev, cmd, NULL); + + mfc_debug(2, "--\n"); + + return 0; +} + +static inline void s5p_mfc_run_dec_last_frames(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + + s5p_mfc_set_dec_stream_buffer_v6(ctx, 0, 0, 0); + dev->curr_ctx = ctx->num; + s5p_mfc_decode_one_frame_v6(ctx, MFC_DEC_LAST_FRAME); +} + +static inline int s5p_mfc_run_dec_frame(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *temp_vb; + int last_frame = 0; + + if (ctx->state == MFCINST_FINISHING) { + last_frame = MFC_DEC_LAST_FRAME; + s5p_mfc_set_dec_stream_buffer_v6(ctx, 0, 0, 0); + dev->curr_ctx = ctx->num; + s5p_mfc_clean_ctx_int_flags(ctx); + s5p_mfc_decode_one_frame_v6(ctx, last_frame); + return 0; + } + + /* Frames are being decoded */ + if (list_empty(&ctx->src_queue)) { + mfc_debug(2, "No src buffers.\n"); + return -EAGAIN; + } + /* Get the next source buffer */ + temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); + temp_vb->flags |= MFC_BUF_FLAG_USED; + s5p_mfc_set_dec_stream_buffer_v6(ctx, + vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0), + ctx->consumed_stream, + temp_vb->b->vb2_buf.planes[0].bytesused); + + dev->curr_ctx = ctx->num; + if (temp_vb->b->vb2_buf.planes[0].bytesused == 0) { + last_frame = 1; + mfc_debug(2, "Setting ctx->state to FINISHING\n"); + ctx->state = MFCINST_FINISHING; + } + s5p_mfc_decode_one_frame_v6(ctx, last_frame); + + return 0; +} + +static inline int s5p_mfc_run_enc_frame(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *dst_mb; + struct s5p_mfc_buf *src_mb; + unsigned long src_y_addr, src_c_addr, dst_addr; + /* + unsigned int src_y_size, src_c_size; + */ + unsigned int dst_size; + + if (list_empty(&ctx->src_queue) && ctx->state != MFCINST_FINISHING) { + mfc_debug(2, "no src buffers.\n"); + return -EAGAIN; + } + + if (list_empty(&ctx->dst_queue)) { + mfc_debug(2, "no dst buffers.\n"); + return -EAGAIN; + } + + if (list_empty(&ctx->src_queue)) { + /* send null frame */ + s5p_mfc_set_enc_frame_buffer_v6(ctx, 0, 0); + src_mb = NULL; + } else { + src_mb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); + src_mb->flags |= MFC_BUF_FLAG_USED; + if (src_mb->b->vb2_buf.planes[0].bytesused == 0) { + s5p_mfc_set_enc_frame_buffer_v6(ctx, 0, 0); + ctx->state = MFCINST_FINISHING; + } else { + src_y_addr = vb2_dma_contig_plane_dma_addr(&src_mb->b->vb2_buf, 0); + src_c_addr = vb2_dma_contig_plane_dma_addr(&src_mb->b->vb2_buf, 1); + + mfc_debug(2, "enc src y addr: 0x%08lx\n", src_y_addr); + mfc_debug(2, "enc src c addr: 0x%08lx\n", src_c_addr); + + s5p_mfc_set_enc_frame_buffer_v6(ctx, src_y_addr, src_c_addr); + if (src_mb->flags & MFC_BUF_FLAG_EOS) + ctx->state = MFCINST_FINISHING; + } + } + + dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list); + dst_mb->flags |= MFC_BUF_FLAG_USED; + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0); + dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0); + + s5p_mfc_set_enc_stream_buffer_v6(ctx, dst_addr, dst_size); + + dev->curr_ctx = ctx->num; + s5p_mfc_encode_one_frame_v6(ctx); + + return 0; +} + +static inline void s5p_mfc_run_init_dec(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *temp_vb; + + /* Initializing decoding - parsing header */ + mfc_debug(2, "Preparing to init decoding.\n"); + temp_vb = list_entry(ctx->src_queue.next, struct s5p_mfc_buf, list); + mfc_debug(2, "Header size: %d\n", temp_vb->b->vb2_buf.planes[0].bytesused); + s5p_mfc_set_dec_stream_buffer_v6(ctx, + vb2_dma_contig_plane_dma_addr(&temp_vb->b->vb2_buf, 0), 0, + temp_vb->b->vb2_buf.planes[0].bytesused); + dev->curr_ctx = ctx->num; + s5p_mfc_init_decode_v6(ctx); +} + +static inline void s5p_mfc_run_init_enc(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + struct s5p_mfc_buf *dst_mb; + unsigned long dst_addr; + unsigned int dst_size; + + dst_mb = list_entry(ctx->dst_queue.next, struct s5p_mfc_buf, list); + dst_addr = vb2_dma_contig_plane_dma_addr(&dst_mb->b->vb2_buf, 0); + dst_size = vb2_plane_size(&dst_mb->b->vb2_buf, 0); + s5p_mfc_set_enc_stream_buffer_v6(ctx, dst_addr, dst_size); + dev->curr_ctx = ctx->num; + s5p_mfc_init_encode_v6(ctx); +} + +static inline int s5p_mfc_run_init_dec_buffers(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + int ret; + /* Header was parsed now start processing + * First set the output frame buffers + * s5p_mfc_alloc_dec_buffers(ctx); */ + + if (ctx->capture_state != QUEUE_BUFS_MMAPED) { + mfc_err("It seems that not all destination buffers were\n" + "mmapped.MFC requires that all destination are mmapped\n" + "before starting processing.\n"); + return -EAGAIN; + } + + dev->curr_ctx = ctx->num; + ret = s5p_mfc_set_dec_frame_buffer_v6(ctx); + if (ret) { + mfc_err("Failed to alloc frame mem.\n"); + ctx->state = MFCINST_ERROR; + } + return ret; +} + +static inline int s5p_mfc_run_init_enc_buffers(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + int ret; + + dev->curr_ctx = ctx->num; + ret = s5p_mfc_set_enc_ref_buffer_v6(ctx); + if (ret) { + mfc_err("Failed to alloc frame mem.\n"); + ctx->state = MFCINST_ERROR; + } + return ret; +} + +/* Try running an operation on hardware */ +static void s5p_mfc_try_run_v6(struct s5p_mfc_dev *dev) +{ + struct s5p_mfc_ctx *ctx; + int new_ctx; + unsigned int ret = 0; + + mfc_debug(1, "Try run dev: %p\n", dev); + + /* Check whether hardware is not running */ + if (test_and_set_bit(0, &dev->hw_lock) != 0) { + /* This is perfectly ok, the scheduled ctx should wait */ + mfc_debug(1, "Couldn't lock HW.\n"); + return; + } + + /* Choose the context to run */ + new_ctx = s5p_mfc_get_new_ctx(dev); + if (new_ctx < 0) { + /* No contexts to run */ + if (test_and_clear_bit(0, &dev->hw_lock) == 0) { + mfc_err("Failed to unlock hardware.\n"); + return; + } + + mfc_debug(1, "No ctx is scheduled to be run.\n"); + return; + } + + mfc_debug(1, "New context: %d\n", new_ctx); + ctx = dev->ctx[new_ctx]; + mfc_debug(1, "Setting new context to %p\n", ctx); + /* Got context to run in ctx */ + mfc_debug(1, "ctx->dst_queue_cnt=%d ctx->dpb_count=%d ctx->src_queue_cnt=%d\n", + ctx->dst_queue_cnt, ctx->pb_count, ctx->src_queue_cnt); + mfc_debug(1, "ctx->state=%d\n", ctx->state); + /* Last frame has already been sent to MFC + * Now obtaining frames from MFC buffer */ + + s5p_mfc_clock_on(); + s5p_mfc_clean_ctx_int_flags(ctx); + + if (ctx->type == MFCINST_DECODER) { + switch (ctx->state) { + case MFCINST_FINISHING: + s5p_mfc_run_dec_last_frames(ctx); + break; + case MFCINST_RUNNING: + ret = s5p_mfc_run_dec_frame(ctx); + break; + case MFCINST_INIT: + ret = s5p_mfc_hw_call(dev->mfc_cmds, open_inst_cmd, + ctx); + break; + case MFCINST_RETURN_INST: + ret = s5p_mfc_hw_call(dev->mfc_cmds, close_inst_cmd, + ctx); + break; + case MFCINST_GOT_INST: + s5p_mfc_run_init_dec(ctx); + break; + case MFCINST_HEAD_PARSED: + ret = s5p_mfc_run_init_dec_buffers(ctx); + break; + case MFCINST_FLUSH: + s5p_mfc_set_flush(ctx, ctx->dpb_flush_flag); + break; + case MFCINST_RES_CHANGE_INIT: + s5p_mfc_run_dec_last_frames(ctx); + break; + case MFCINST_RES_CHANGE_FLUSH: + s5p_mfc_run_dec_last_frames(ctx); + break; + case MFCINST_RES_CHANGE_END: + mfc_debug(2, "Finished remaining frames after resolution change.\n"); + ctx->capture_state = QUEUE_FREE; + mfc_debug(2, "Will re-init the codec`.\n"); + s5p_mfc_run_init_dec(ctx); + break; + default: + ret = -EAGAIN; + } + } else if (ctx->type == MFCINST_ENCODER) { + switch (ctx->state) { + case MFCINST_FINISHING: + case MFCINST_RUNNING: + ret = s5p_mfc_run_enc_frame(ctx); + break; + case MFCINST_INIT: + ret = s5p_mfc_hw_call(dev->mfc_cmds, open_inst_cmd, + ctx); + break; + case MFCINST_RETURN_INST: + ret = s5p_mfc_hw_call(dev->mfc_cmds, close_inst_cmd, + ctx); + break; + case MFCINST_GOT_INST: + s5p_mfc_run_init_enc(ctx); + break; + case MFCINST_HEAD_PRODUCED: + ret = s5p_mfc_run_init_enc_buffers(ctx); + break; + default: + ret = -EAGAIN; + } + } else { + mfc_err("invalid context type: %d\n", ctx->type); + ret = -EAGAIN; + } + + if (ret) { + /* Free hardware lock */ + if (test_and_clear_bit(0, &dev->hw_lock) == 0) + mfc_err("Failed to unlock hardware.\n"); + + /* This is in deed imporant, as no operation has been + * scheduled, reduce the clock count as no one will + * ever do this, because no interrupt related to this try_run + * will ever come from hardware. */ + s5p_mfc_clock_off(); + } +} + +static void s5p_mfc_clear_int_flags_v6(struct s5p_mfc_dev *dev) +{ + const struct s5p_mfc_regs *mfc_regs = dev->mfc_regs; + writel(0, mfc_regs->risc2host_command); + writel(0, mfc_regs->risc2host_int); +} + +static unsigned int +s5p_mfc_read_info_v6(struct s5p_mfc_ctx *ctx, unsigned long ofs) +{ + int ret; + + s5p_mfc_clock_on(); + ret = readl((void __iomem *)ofs); + s5p_mfc_clock_off(); + + return ret; +} + +static int s5p_mfc_get_dspl_y_adr_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_display_first_plane_addr); +} + +static int s5p_mfc_get_dec_y_adr_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_decoded_first_plane_addr); +} + +static int s5p_mfc_get_dspl_status_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_display_status); +} + +static int s5p_mfc_get_dec_status_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_decoded_status); +} + +static int s5p_mfc_get_dec_frame_type_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_decoded_frame_type) & + S5P_FIMV_DECODE_FRAME_MASK_V6; +} + +static int s5p_mfc_get_disp_frame_type_v6(struct s5p_mfc_ctx *ctx) +{ + struct s5p_mfc_dev *dev = ctx->dev; + return readl(dev->mfc_regs->d_display_frame_type) & + S5P_FIMV_DECODE_FRAME_MASK_V6; +} + +static int s5p_mfc_get_consumed_stream_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_decoded_nal_size); +} + +static int s5p_mfc_get_int_reason_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->risc2host_command) & + S5P_FIMV_RISC2HOST_CMD_MASK; +} + +static int s5p_mfc_get_int_err_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->error_code); +} + +static int s5p_mfc_err_dec_v6(unsigned int err) +{ + return (err & S5P_FIMV_ERR_DEC_MASK_V6) >> S5P_FIMV_ERR_DEC_SHIFT_V6; +} + +static int s5p_mfc_get_img_width_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_display_frame_width); +} + +static int s5p_mfc_get_img_height_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_display_frame_height); +} + +static int s5p_mfc_get_dpb_count_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_min_num_dpb); +} + +static int s5p_mfc_get_mv_count_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_min_num_mv); +} + +static int s5p_mfc_get_min_scratch_buf_size(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->d_min_scratch_buffer_size); +} + +static int s5p_mfc_get_e_min_scratch_buf_size(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->e_min_scratch_buffer_size); +} + +static int s5p_mfc_get_inst_no_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->ret_instance_id); +} + +static int s5p_mfc_get_enc_dpb_count_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->e_num_dpb); +} + +static int s5p_mfc_get_enc_strm_size_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->e_stream_size); +} + +static int s5p_mfc_get_enc_slice_type_v6(struct s5p_mfc_dev *dev) +{ + return readl(dev->mfc_regs->e_slice_type); +} + +static unsigned int s5p_mfc_get_pic_type_top_v6(struct s5p_mfc_ctx *ctx) +{ + return s5p_mfc_read_info_v6(ctx, + (__force unsigned long) ctx->dev->mfc_regs->d_ret_picture_tag_top); +} + +static unsigned int s5p_mfc_get_pic_type_bot_v6(struct s5p_mfc_ctx *ctx) +{ + return s5p_mfc_read_info_v6(ctx, + (__force unsigned long) ctx->dev->mfc_regs->d_ret_picture_tag_bot); +} + +static unsigned int s5p_mfc_get_crop_info_h_v6(struct s5p_mfc_ctx *ctx) +{ + return s5p_mfc_read_info_v6(ctx, + (__force unsigned long) ctx->dev->mfc_regs->d_display_crop_info1); +} + +static unsigned int s5p_mfc_get_crop_info_v_v6(struct s5p_mfc_ctx *ctx) +{ + return s5p_mfc_read_info_v6(ctx, + (__force unsigned long) ctx->dev->mfc_regs->d_display_crop_info2); +} + +static struct s5p_mfc_regs mfc_regs; + +/* Initialize registers for MFC v6 onwards */ +const struct s5p_mfc_regs *s5p_mfc_init_regs_v6_plus(struct s5p_mfc_dev *dev) +{ + memset(&mfc_regs, 0, sizeof(mfc_regs)); + +#define S5P_MFC_REG_ADDR(dev, reg) ((dev)->regs_base + (reg)) +#define R(m, r) mfc_regs.m = S5P_MFC_REG_ADDR(dev, r) + /* codec common registers */ + R(risc_on, S5P_FIMV_RISC_ON_V6); + R(risc2host_int, S5P_FIMV_RISC2HOST_INT_V6); + R(host2risc_int, S5P_FIMV_HOST2RISC_INT_V6); + R(risc_base_address, S5P_FIMV_RISC_BASE_ADDRESS_V6); + R(mfc_reset, S5P_FIMV_MFC_RESET_V6); + R(host2risc_command, S5P_FIMV_HOST2RISC_CMD_V6); + R(risc2host_command, S5P_FIMV_RISC2HOST_CMD_V6); + R(firmware_version, S5P_FIMV_FW_VERSION_V6); + R(instance_id, S5P_FIMV_INSTANCE_ID_V6); + R(codec_type, S5P_FIMV_CODEC_TYPE_V6); + R(context_mem_addr, S5P_FIMV_CONTEXT_MEM_ADDR_V6); + R(context_mem_size, S5P_FIMV_CONTEXT_MEM_SIZE_V6); + R(pixel_format, S5P_FIMV_PIXEL_FORMAT_V6); + R(ret_instance_id, S5P_FIMV_RET_INSTANCE_ID_V6); + R(error_code, S5P_FIMV_ERROR_CODE_V6); + + /* decoder registers */ + R(d_crc_ctrl, S5P_FIMV_D_CRC_CTRL_V6); + R(d_dec_options, S5P_FIMV_D_DEC_OPTIONS_V6); + R(d_display_delay, S5P_FIMV_D_DISPLAY_DELAY_V6); + R(d_sei_enable, S5P_FIMV_D_SEI_ENABLE_V6); + R(d_min_num_dpb, S5P_FIMV_D_MIN_NUM_DPB_V6); + R(d_min_num_mv, S5P_FIMV_D_MIN_NUM_MV_V6); + R(d_mvc_num_views, S5P_FIMV_D_MVC_NUM_VIEWS_V6); + R(d_num_dpb, S5P_FIMV_D_NUM_DPB_V6); + R(d_num_mv, S5P_FIMV_D_NUM_MV_V6); + R(d_init_buffer_options, S5P_FIMV_D_INIT_BUFFER_OPTIONS_V6); + R(d_first_plane_dpb_size, S5P_FIMV_D_LUMA_DPB_SIZE_V6); + R(d_second_plane_dpb_size, S5P_FIMV_D_CHROMA_DPB_SIZE_V6); + R(d_mv_buffer_size, S5P_FIMV_D_MV_BUFFER_SIZE_V6); + R(d_first_plane_dpb, S5P_FIMV_D_LUMA_DPB_V6); + R(d_second_plane_dpb, S5P_FIMV_D_CHROMA_DPB_V6); + R(d_mv_buffer, S5P_FIMV_D_MV_BUFFER_V6); + R(d_scratch_buffer_addr, S5P_FIMV_D_SCRATCH_BUFFER_ADDR_V6); + R(d_scratch_buffer_size, S5P_FIMV_D_SCRATCH_BUFFER_SIZE_V6); + R(d_cpb_buffer_addr, S5P_FIMV_D_CPB_BUFFER_ADDR_V6); + R(d_cpb_buffer_size, S5P_FIMV_D_CPB_BUFFER_SIZE_V6); + R(d_available_dpb_flag_lower, S5P_FIMV_D_AVAILABLE_DPB_FLAG_LOWER_V6); + R(d_cpb_buffer_offset, S5P_FIMV_D_CPB_BUFFER_OFFSET_V6); + R(d_slice_if_enable, S5P_FIMV_D_SLICE_IF_ENABLE_V6); + R(d_stream_data_size, S5P_FIMV_D_STREAM_DATA_SIZE_V6); + R(d_display_frame_width, S5P_FIMV_D_DISPLAY_FRAME_WIDTH_V6); + R(d_display_frame_height, S5P_FIMV_D_DISPLAY_FRAME_HEIGHT_V6); + R(d_display_status, S5P_FIMV_D_DISPLAY_STATUS_V6); + R(d_display_first_plane_addr, S5P_FIMV_D_DISPLAY_LUMA_ADDR_V6); + R(d_display_second_plane_addr, S5P_FIMV_D_DISPLAY_CHROMA_ADDR_V6); + R(d_display_frame_type, S5P_FIMV_D_DISPLAY_FRAME_TYPE_V6); + R(d_display_crop_info1, S5P_FIMV_D_DISPLAY_CROP_INFO1_V6); + R(d_display_crop_info2, S5P_FIMV_D_DISPLAY_CROP_INFO2_V6); + R(d_display_aspect_ratio, S5P_FIMV_D_DISPLAY_ASPECT_RATIO_V6); + R(d_display_extended_ar, S5P_FIMV_D_DISPLAY_EXTENDED_AR_V6); + R(d_decoded_status, S5P_FIMV_D_DECODED_STATUS_V6); + R(d_decoded_first_plane_addr, S5P_FIMV_D_DECODED_LUMA_ADDR_V6); + R(d_decoded_second_plane_addr, S5P_FIMV_D_DECODED_CHROMA_ADDR_V6); + R(d_decoded_frame_type, S5P_FIMV_D_DECODED_FRAME_TYPE_V6); + R(d_decoded_nal_size, S5P_FIMV_D_DECODED_NAL_SIZE_V6); + R(d_ret_picture_tag_top, S5P_FIMV_D_RET_PICTURE_TAG_TOP_V6); + R(d_ret_picture_tag_bot, S5P_FIMV_D_RET_PICTURE_TAG_BOT_V6); + R(d_h264_info, S5P_FIMV_D_H264_INFO_V6); + R(d_mvc_view_id, S5P_FIMV_D_MVC_VIEW_ID_V6); + R(d_frame_pack_sei_avail, S5P_FIMV_D_FRAME_PACK_SEI_AVAIL_V6); + + /* encoder registers */ + R(e_frame_width, S5P_FIMV_E_FRAME_WIDTH_V6); + R(e_frame_height, S5P_FIMV_E_FRAME_HEIGHT_V6); + R(e_cropped_frame_width, S5P_FIMV_E_CROPPED_FRAME_WIDTH_V6); + R(e_cropped_frame_height, S5P_FIMV_E_CROPPED_FRAME_HEIGHT_V6); + R(e_frame_crop_offset, S5P_FIMV_E_FRAME_CROP_OFFSET_V6); + R(e_enc_options, S5P_FIMV_E_ENC_OPTIONS_V6); + R(e_picture_profile, S5P_FIMV_E_PICTURE_PROFILE_V6); + R(e_vbv_buffer_size, S5P_FIMV_E_VBV_BUFFER_SIZE_V6); + R(e_vbv_init_delay, S5P_FIMV_E_VBV_INIT_DELAY_V6); + R(e_fixed_picture_qp, S5P_FIMV_E_FIXED_PICTURE_QP_V6); + R(e_rc_config, S5P_FIMV_E_RC_CONFIG_V6); + R(e_rc_qp_bound, S5P_FIMV_E_RC_QP_BOUND_V6); + R(e_rc_mode, S5P_FIMV_E_RC_RPARAM_V6); + R(e_mb_rc_config, S5P_FIMV_E_MB_RC_CONFIG_V6); + R(e_padding_ctrl, S5P_FIMV_E_PADDING_CTRL_V6); + R(e_mv_hor_range, S5P_FIMV_E_MV_HOR_RANGE_V6); + R(e_mv_ver_range, S5P_FIMV_E_MV_VER_RANGE_V6); + R(e_num_dpb, S5P_FIMV_E_NUM_DPB_V6); + R(e_luma_dpb, S5P_FIMV_E_LUMA_DPB_V6); + R(e_chroma_dpb, S5P_FIMV_E_CHROMA_DPB_V6); + R(e_me_buffer, S5P_FIMV_E_ME_BUFFER_V6); + R(e_scratch_buffer_addr, S5P_FIMV_E_SCRATCH_BUFFER_ADDR_V6); + R(e_scratch_buffer_size, S5P_FIMV_E_SCRATCH_BUFFER_SIZE_V6); + R(e_tmv_buffer0, S5P_FIMV_E_TMV_BUFFER0_V6); + R(e_tmv_buffer1, S5P_FIMV_E_TMV_BUFFER1_V6); + R(e_source_first_plane_addr, S5P_FIMV_E_SOURCE_LUMA_ADDR_V6); + R(e_source_second_plane_addr, S5P_FIMV_E_SOURCE_CHROMA_ADDR_V6); + R(e_stream_buffer_addr, S5P_FIMV_E_STREAM_BUFFER_ADDR_V6); + R(e_stream_buffer_size, S5P_FIMV_E_STREAM_BUFFER_SIZE_V6); + R(e_roi_buffer_addr, S5P_FIMV_E_ROI_BUFFER_ADDR_V6); + R(e_param_change, S5P_FIMV_E_PARAM_CHANGE_V6); + R(e_ir_size, S5P_FIMV_E_IR_SIZE_V6); + R(e_gop_config, S5P_FIMV_E_GOP_CONFIG_V6); + R(e_mslice_mode, S5P_FIMV_E_MSLICE_MODE_V6); + R(e_mslice_size_mb, S5P_FIMV_E_MSLICE_SIZE_MB_V6); + R(e_mslice_size_bits, S5P_FIMV_E_MSLICE_SIZE_BITS_V6); + R(e_frame_insertion, S5P_FIMV_E_FRAME_INSERTION_V6); + R(e_rc_frame_rate, S5P_FIMV_E_RC_FRAME_RATE_V6); + R(e_rc_bit_rate, S5P_FIMV_E_RC_BIT_RATE_V6); + R(e_rc_roi_ctrl, S5P_FIMV_E_RC_ROI_CTRL_V6); + R(e_picture_tag, S5P_FIMV_E_PICTURE_TAG_V6); + R(e_bit_count_enable, S5P_FIMV_E_BIT_COUNT_ENABLE_V6); + R(e_max_bit_count, S5P_FIMV_E_MAX_BIT_COUNT_V6); + R(e_min_bit_count, S5P_FIMV_E_MIN_BIT_COUNT_V6); + R(e_metadata_buffer_addr, S5P_FIMV_E_METADATA_BUFFER_ADDR_V6); + R(e_metadata_buffer_size, S5P_FIMV_E_METADATA_BUFFER_SIZE_V6); + R(e_encoded_source_first_plane_addr, + S5P_FIMV_E_ENCODED_SOURCE_LUMA_ADDR_V6); + R(e_encoded_source_second_plane_addr, + S5P_FIMV_E_ENCODED_SOURCE_CHROMA_ADDR_V6); + R(e_stream_size, S5P_FIMV_E_STREAM_SIZE_V6); + R(e_slice_type, S5P_FIMV_E_SLICE_TYPE_V6); + R(e_picture_count, S5P_FIMV_E_PICTURE_COUNT_V6); + R(e_ret_picture_tag, S5P_FIMV_E_RET_PICTURE_TAG_V6); + R(e_recon_luma_dpb_addr, S5P_FIMV_E_RECON_LUMA_DPB_ADDR_V6); + R(e_recon_chroma_dpb_addr, S5P_FIMV_E_RECON_CHROMA_DPB_ADDR_V6); + R(e_mpeg4_options, S5P_FIMV_E_MPEG4_OPTIONS_V6); + R(e_mpeg4_hec_period, S5P_FIMV_E_MPEG4_HEC_PERIOD_V6); + R(e_aspect_ratio, S5P_FIMV_E_ASPECT_RATIO_V6); + R(e_extended_sar, S5P_FIMV_E_EXTENDED_SAR_V6); + R(e_h264_options, S5P_FIMV_E_H264_OPTIONS_V6); + R(e_h264_lf_alpha_offset, S5P_FIMV_E_H264_LF_ALPHA_OFFSET_V6); + R(e_h264_lf_beta_offset, S5P_FIMV_E_H264_LF_BETA_OFFSET_V6); + R(e_h264_i_period, S5P_FIMV_E_H264_I_PERIOD_V6); + R(e_h264_fmo_slice_grp_map_type, + S5P_FIMV_E_H264_FMO_SLICE_GRP_MAP_TYPE_V6); + R(e_h264_fmo_num_slice_grp_minus1, + S5P_FIMV_E_H264_FMO_NUM_SLICE_GRP_MINUS1_V6); + R(e_h264_fmo_slice_grp_change_dir, + S5P_FIMV_E_H264_FMO_SLICE_GRP_CHANGE_DIR_V6); + R(e_h264_fmo_slice_grp_change_rate_minus1, + S5P_FIMV_E_H264_FMO_SLICE_GRP_CHANGE_RATE_MINUS1_V6); + R(e_h264_fmo_run_length_minus1_0, + S5P_FIMV_E_H264_FMO_RUN_LENGTH_MINUS1_0_V6); + R(e_h264_aso_slice_order_0, S5P_FIMV_E_H264_ASO_SLICE_ORDER_0_V6); + R(e_h264_num_t_layer, S5P_FIMV_E_H264_NUM_T_LAYER_V6); + R(e_h264_hierarchical_qp_layer0, + S5P_FIMV_E_H264_HIERARCHICAL_QP_LAYER0_V6); + R(e_h264_frame_packing_sei_info, + S5P_FIMV_E_H264_FRAME_PACKING_SEI_INFO_V6); + + if (!IS_MFCV7_PLUS(dev)) + goto done; + + /* Initialize registers used in MFC v7+ */ + R(e_source_first_plane_addr, S5P_FIMV_E_SOURCE_FIRST_ADDR_V7); + R(e_source_second_plane_addr, S5P_FIMV_E_SOURCE_SECOND_ADDR_V7); + R(e_source_third_plane_addr, S5P_FIMV_E_SOURCE_THIRD_ADDR_V7); + R(e_source_first_plane_stride, S5P_FIMV_E_SOURCE_FIRST_STRIDE_V7); + R(e_source_second_plane_stride, S5P_FIMV_E_SOURCE_SECOND_STRIDE_V7); + R(e_source_third_plane_stride, S5P_FIMV_E_SOURCE_THIRD_STRIDE_V7); + R(e_encoded_source_first_plane_addr, + S5P_FIMV_E_ENCODED_SOURCE_FIRST_ADDR_V7); + R(e_encoded_source_second_plane_addr, + S5P_FIMV_E_ENCODED_SOURCE_SECOND_ADDR_V7); + R(e_vp8_options, S5P_FIMV_E_VP8_OPTIONS_V7); + + if (!IS_MFCV8_PLUS(dev)) + goto done; + + /* Initialize registers used in MFC v8 only. + * Also, over-write the registers which have + * a different offset for MFC v8. */ + R(d_stream_data_size, S5P_FIMV_D_STREAM_DATA_SIZE_V8); + R(d_cpb_buffer_addr, S5P_FIMV_D_CPB_BUFFER_ADDR_V8); + R(d_cpb_buffer_size, S5P_FIMV_D_CPB_BUFFER_SIZE_V8); + R(d_cpb_buffer_offset, S5P_FIMV_D_CPB_BUFFER_OFFSET_V8); + R(d_first_plane_dpb_size, S5P_FIMV_D_FIRST_PLANE_DPB_SIZE_V8); + R(d_second_plane_dpb_size, S5P_FIMV_D_SECOND_PLANE_DPB_SIZE_V8); + R(d_scratch_buffer_addr, S5P_FIMV_D_SCRATCH_BUFFER_ADDR_V8); + R(d_scratch_buffer_size, S5P_FIMV_D_SCRATCH_BUFFER_SIZE_V8); + R(d_first_plane_dpb_stride_size, + S5P_FIMV_D_FIRST_PLANE_DPB_STRIDE_SIZE_V8); + R(d_second_plane_dpb_stride_size, + S5P_FIMV_D_SECOND_PLANE_DPB_STRIDE_SIZE_V8); + R(d_mv_buffer_size, S5P_FIMV_D_MV_BUFFER_SIZE_V8); + R(d_num_mv, S5P_FIMV_D_NUM_MV_V8); + R(d_first_plane_dpb, S5P_FIMV_D_FIRST_PLANE_DPB_V8); + R(d_second_plane_dpb, S5P_FIMV_D_SECOND_PLANE_DPB_V8); + R(d_mv_buffer, S5P_FIMV_D_MV_BUFFER_V8); + R(d_init_buffer_options, S5P_FIMV_D_INIT_BUFFER_OPTIONS_V8); + R(d_available_dpb_flag_lower, S5P_FIMV_D_AVAILABLE_DPB_FLAG_LOWER_V8); + R(d_slice_if_enable, S5P_FIMV_D_SLICE_IF_ENABLE_V8); + R(d_display_first_plane_addr, S5P_FIMV_D_DISPLAY_FIRST_PLANE_ADDR_V8); + R(d_display_second_plane_addr, S5P_FIMV_D_DISPLAY_SECOND_PLANE_ADDR_V8); + R(d_decoded_first_plane_addr, S5P_FIMV_D_DECODED_FIRST_PLANE_ADDR_V8); + R(d_decoded_second_plane_addr, S5P_FIMV_D_DECODED_SECOND_PLANE_ADDR_V8); + R(d_display_status, S5P_FIMV_D_DISPLAY_STATUS_V8); + R(d_decoded_status, S5P_FIMV_D_DECODED_STATUS_V8); + R(d_decoded_frame_type, S5P_FIMV_D_DECODED_FRAME_TYPE_V8); + R(d_display_frame_type, S5P_FIMV_D_DISPLAY_FRAME_TYPE_V8); + R(d_decoded_nal_size, S5P_FIMV_D_DECODED_NAL_SIZE_V8); + R(d_display_frame_width, S5P_FIMV_D_DISPLAY_FRAME_WIDTH_V8); + R(d_display_frame_height, S5P_FIMV_D_DISPLAY_FRAME_HEIGHT_V8); + R(d_frame_pack_sei_avail, S5P_FIMV_D_FRAME_PACK_SEI_AVAIL_V8); + R(d_mvc_num_views, S5P_FIMV_D_MVC_NUM_VIEWS_V8); + R(d_mvc_view_id, S5P_FIMV_D_MVC_VIEW_ID_V8); + R(d_ret_picture_tag_top, S5P_FIMV_D_RET_PICTURE_TAG_TOP_V8); + R(d_ret_picture_tag_bot, S5P_FIMV_D_RET_PICTURE_TAG_BOT_V8); + R(d_display_crop_info1, S5P_FIMV_D_DISPLAY_CROP_INFO1_V8); + R(d_display_crop_info2, S5P_FIMV_D_DISPLAY_CROP_INFO2_V8); + R(d_min_scratch_buffer_size, S5P_FIMV_D_MIN_SCRATCH_BUFFER_SIZE_V8); + + /* encoder registers */ + R(e_padding_ctrl, S5P_FIMV_E_PADDING_CTRL_V8); + R(e_rc_config, S5P_FIMV_E_RC_CONFIG_V8); + R(e_rc_mode, S5P_FIMV_E_RC_RPARAM_V8); + R(e_mv_hor_range, S5P_FIMV_E_MV_HOR_RANGE_V8); + R(e_mv_ver_range, S5P_FIMV_E_MV_VER_RANGE_V8); + R(e_rc_qp_bound, S5P_FIMV_E_RC_QP_BOUND_V8); + R(e_fixed_picture_qp, S5P_FIMV_E_FIXED_PICTURE_QP_V8); + R(e_vbv_buffer_size, S5P_FIMV_E_VBV_BUFFER_SIZE_V8); + R(e_vbv_init_delay, S5P_FIMV_E_VBV_INIT_DELAY_V8); + R(e_mb_rc_config, S5P_FIMV_E_MB_RC_CONFIG_V8); + R(e_aspect_ratio, S5P_FIMV_E_ASPECT_RATIO_V8); + R(e_extended_sar, S5P_FIMV_E_EXTENDED_SAR_V8); + R(e_h264_options, S5P_FIMV_E_H264_OPTIONS_V8); + R(e_min_scratch_buffer_size, S5P_FIMV_E_MIN_SCRATCH_BUFFER_SIZE_V8); + + if (!IS_MFCV10(dev)) + goto done; + + /* Initialize registers used in MFC v10 only. + * Also, over-write the registers which have + * a different offset for MFC v10. + */ + + /* decoder registers */ + R(d_static_buffer_addr, S5P_FIMV_D_STATIC_BUFFER_ADDR_V10); + R(d_static_buffer_size, S5P_FIMV_D_STATIC_BUFFER_SIZE_V10); + + /* encoder registers */ + R(e_num_t_layer, S5P_FIMV_E_NUM_T_LAYER_V10); + R(e_hier_qp_layer0, S5P_FIMV_E_HIERARCHICAL_QP_LAYER0_V10); + R(e_hier_bit_rate_layer0, S5P_FIMV_E_HIERARCHICAL_BIT_RATE_LAYER0_V10); + R(e_hevc_options, S5P_FIMV_E_HEVC_OPTIONS_V10); + R(e_hevc_refresh_period, S5P_FIMV_E_HEVC_REFRESH_PERIOD_V10); + R(e_hevc_lf_beta_offset_div2, S5P_FIMV_E_HEVC_LF_BETA_OFFSET_DIV2_V10); + R(e_hevc_lf_tc_offset_div2, S5P_FIMV_E_HEVC_LF_TC_OFFSET_DIV2_V10); + R(e_hevc_nal_control, S5P_FIMV_E_HEVC_NAL_CONTROL_V10); + +done: + return &mfc_regs; +#undef S5P_MFC_REG_ADDR +#undef R +} + +/* Initialize opr function pointers for MFC v6 */ +static struct s5p_mfc_hw_ops s5p_mfc_ops_v6 = { + .alloc_dec_temp_buffers = s5p_mfc_alloc_dec_temp_buffers_v6, + .release_dec_desc_buffer = s5p_mfc_release_dec_desc_buffer_v6, + .alloc_codec_buffers = s5p_mfc_alloc_codec_buffers_v6, + .release_codec_buffers = s5p_mfc_release_codec_buffers_v6, + .alloc_instance_buffer = s5p_mfc_alloc_instance_buffer_v6, + .release_instance_buffer = s5p_mfc_release_instance_buffer_v6, + .alloc_dev_context_buffer = + s5p_mfc_alloc_dev_context_buffer_v6, + .release_dev_context_buffer = + s5p_mfc_release_dev_context_buffer_v6, + .dec_calc_dpb_size = s5p_mfc_dec_calc_dpb_size_v6, + .enc_calc_src_size = s5p_mfc_enc_calc_src_size_v6, + .set_enc_stream_buffer = s5p_mfc_set_enc_stream_buffer_v6, + .set_enc_frame_buffer = s5p_mfc_set_enc_frame_buffer_v6, + .get_enc_frame_buffer = s5p_mfc_get_enc_frame_buffer_v6, + .try_run = s5p_mfc_try_run_v6, + .clear_int_flags = s5p_mfc_clear_int_flags_v6, + .get_dspl_y_adr = s5p_mfc_get_dspl_y_adr_v6, + .get_dec_y_adr = s5p_mfc_get_dec_y_adr_v6, + .get_dspl_status = s5p_mfc_get_dspl_status_v6, + .get_dec_status = s5p_mfc_get_dec_status_v6, + .get_dec_frame_type = s5p_mfc_get_dec_frame_type_v6, + .get_disp_frame_type = s5p_mfc_get_disp_frame_type_v6, + .get_consumed_stream = s5p_mfc_get_consumed_stream_v6, + .get_int_reason = s5p_mfc_get_int_reason_v6, + .get_int_err = s5p_mfc_get_int_err_v6, + .err_dec = s5p_mfc_err_dec_v6, + .get_img_width = s5p_mfc_get_img_width_v6, + .get_img_height = s5p_mfc_get_img_height_v6, + .get_dpb_count = s5p_mfc_get_dpb_count_v6, + .get_mv_count = s5p_mfc_get_mv_count_v6, + .get_inst_no = s5p_mfc_get_inst_no_v6, + .get_enc_strm_size = s5p_mfc_get_enc_strm_size_v6, + .get_enc_slice_type = s5p_mfc_get_enc_slice_type_v6, + .get_enc_dpb_count = s5p_mfc_get_enc_dpb_count_v6, + .get_pic_type_top = s5p_mfc_get_pic_type_top_v6, + .get_pic_type_bot = s5p_mfc_get_pic_type_bot_v6, + .get_crop_info_h = s5p_mfc_get_crop_info_h_v6, + .get_crop_info_v = s5p_mfc_get_crop_info_v_v6, + .get_min_scratch_buf_size = s5p_mfc_get_min_scratch_buf_size, + .get_e_min_scratch_buf_size = s5p_mfc_get_e_min_scratch_buf_size, +}; + +struct s5p_mfc_hw_ops *s5p_mfc_init_hw_ops_v6(void) +{ + return &s5p_mfc_ops_v6; +} diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h new file mode 100644 index 000000000..e4dd03c54 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * drivers/media/platform/samsung/s5p-mfc/s5p_mfc_opr_v6.h + * + * Header file for Samsung MFC (Multi Function Codec - FIMV) driver + * Contains declarations of hw related functions. + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_OPR_V6_H_ +#define S5P_MFC_OPR_V6_H_ + +#include "s5p_mfc_common.h" +#include "s5p_mfc_opr.h" + +#define MFC_CTRL_MODE_CUSTOM MFC_CTRL_MODE_SFR + +#define MB_WIDTH(x_size) DIV_ROUND_UP(x_size, 16) +#define MB_HEIGHT(y_size) DIV_ROUND_UP(y_size, 16) +#define S5P_MFC_DEC_MV_SIZE_V6(x, y) (MB_WIDTH(x) * \ + (((MB_HEIGHT(y)+1)/2)*2) * 64 + 128) +#define S5P_MFC_DEC_MV_SIZE_V10(x, y) (MB_WIDTH(x) * \ + (((MB_HEIGHT(y)+1)/2)*2) * 64 + 512) +#define S5P_MFC_LCU_WIDTH(x_size) DIV_ROUND_UP(x_size, 32) +#define S5P_MFC_LCU_HEIGHT(y_size) DIV_ROUND_UP(y_size, 32) + +#define s5p_mfc_dec_hevc_mv_size(x, y) \ + (DIV_ROUND_UP(x, 64) * DIV_ROUND_UP(y, 64) * 256 + 512) + +/* Definition */ +#define ENC_MULTI_SLICE_MB_MAX ((1 << 30) - 1) +#define ENC_MULTI_SLICE_BIT_MIN 2800 +#define ENC_INTRA_REFRESH_MB_MAX ((1 << 18) - 1) +#define ENC_VBV_BUF_SIZE_MAX ((1 << 30) - 1) +#define ENC_H264_LOOP_FILTER_AB_MIN -12 +#define ENC_H264_LOOP_FILTER_AB_MAX 12 +#define ENC_H264_RC_FRAME_RATE_MAX ((1 << 16) - 1) +#define ENC_H263_RC_FRAME_RATE_MAX ((1 << 16) - 1) +#define ENC_H264_PROFILE_MAX 3 +#define ENC_H264_LEVEL_MAX 42 +#define ENC_MPEG4_VOP_TIME_RES_MAX ((1 << 16) - 1) +#define FRAME_DELTA_H264_H263 1 +#define TIGHT_CBR_MAX 10 +#define ENC_HEVC_RC_FRAME_RATE_MAX ((1 << 16) - 1) +#define ENC_HEVC_QP_INDEX_MIN -12 +#define ENC_HEVC_QP_INDEX_MAX 12 +#define ENC_HEVC_LOOP_FILTER_MIN -12 +#define ENC_HEVC_LOOP_FILTER_MAX 12 +#define ENC_HEVC_LEVEL_MAX 62 + +#define FRAME_DELTA_DEFAULT 1 + +struct s5p_mfc_hw_ops *s5p_mfc_init_hw_ops_v6(void); +const struct s5p_mfc_regs *s5p_mfc_init_regs_v6_plus(struct s5p_mfc_dev *dev); +#endif /* S5P_MFC_OPR_V6_H_ */ diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c new file mode 100644 index 000000000..187849841 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.c + * + * Copyright (c) 2010 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#include +#include +#include +#include +#include "s5p_mfc_common.h" +#include "s5p_mfc_debug.h" +#include "s5p_mfc_pm.h" + +static struct s5p_mfc_pm *pm; +static struct s5p_mfc_dev *p_dev; +static atomic_t clk_ref; + +int s5p_mfc_init_pm(struct s5p_mfc_dev *dev) +{ + int i; + + pm = &dev->pm; + p_dev = dev; + + pm->num_clocks = dev->variant->num_clocks; + pm->clk_names = dev->variant->clk_names; + pm->device = &dev->plat_dev->dev; + pm->clock_gate = NULL; + + /* clock control */ + for (i = 0; i < pm->num_clocks; i++) { + pm->clocks[i] = devm_clk_get(pm->device, pm->clk_names[i]); + if (IS_ERR(pm->clocks[i])) { + /* additional clocks are optional */ + if (i && PTR_ERR(pm->clocks[i]) == -ENOENT) { + pm->clocks[i] = NULL; + continue; + } + mfc_err("Failed to get clock: %s\n", + pm->clk_names[i]); + return PTR_ERR(pm->clocks[i]); + } + } + + if (dev->variant->use_clock_gating) + pm->clock_gate = pm->clocks[0]; + + pm_runtime_enable(pm->device); + atomic_set(&clk_ref, 0); + return 0; +} + +void s5p_mfc_final_pm(struct s5p_mfc_dev *dev) +{ + pm_runtime_disable(pm->device); +} + +int s5p_mfc_clock_on(void) +{ + atomic_inc(&clk_ref); + mfc_debug(3, "+ %d\n", atomic_read(&clk_ref)); + + return clk_enable(pm->clock_gate); +} + +void s5p_mfc_clock_off(void) +{ + atomic_dec(&clk_ref); + mfc_debug(3, "- %d\n", atomic_read(&clk_ref)); + + clk_disable(pm->clock_gate); +} + +int s5p_mfc_power_on(void) +{ + int i, ret = 0; + + ret = pm_runtime_resume_and_get(pm->device); + if (ret < 0) + return ret; + + /* clock control */ + for (i = 0; i < pm->num_clocks; i++) { + ret = clk_prepare_enable(pm->clocks[i]); + if (ret < 0) { + mfc_err("clock prepare failed for clock: %s\n", + pm->clk_names[i]); + goto err; + } + } + + /* prepare for software clock gating */ + clk_disable(pm->clock_gate); + + return 0; +err: + while (--i >= 0) + clk_disable_unprepare(pm->clocks[i]); + pm_runtime_put(pm->device); + return ret; +} + +int s5p_mfc_power_off(void) +{ + int i; + + /* finish software clock gating */ + clk_enable(pm->clock_gate); + + for (i = 0; i < pm->num_clocks; i++) + clk_disable_unprepare(pm->clocks[i]); + + return pm_runtime_put_sync(pm->device); +} + diff --git a/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h new file mode 100644 index 000000000..4159d2364 --- /dev/null +++ b/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h @@ -0,0 +1,20 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * linux/drivers/media/platform/samsung/s5p-mfc/s5p_mfc_pm.h + * + * Copyright (C) 2011 Samsung Electronics Co., Ltd. + * http://www.samsung.com/ + */ + +#ifndef S5P_MFC_PM_H_ +#define S5P_MFC_PM_H_ + +int s5p_mfc_init_pm(struct s5p_mfc_dev *dev); +void s5p_mfc_final_pm(struct s5p_mfc_dev *dev); + +int s5p_mfc_clock_on(void); +void s5p_mfc_clock_off(void); +int s5p_mfc_power_on(void); +int s5p_mfc_power_off(void); + +#endif /* S5P_MFC_PM_H_ */ diff --git a/drivers/media/platform/st/Kconfig b/drivers/media/platform/st/Kconfig new file mode 100644 index 000000000..b29c258ea --- /dev/null +++ b/drivers/media/platform/st/Kconfig @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "STMicroelectronics media platform drivers" + +source "drivers/media/platform/st/sti/Kconfig" +source "drivers/media/platform/st/stm32/Kconfig" diff --git a/drivers/media/platform/st/Makefile b/drivers/media/platform/st/Makefile new file mode 100644 index 000000000..a1f75b2a8 --- /dev/null +++ b/drivers/media/platform/st/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only + +obj-y += sti/bdisp/ +obj-y += sti/c8sectpfe/ +obj-y += sti/delta/ +obj-y += sti/hva/ +obj-y += stm32/ diff --git a/drivers/media/platform/st/sti/Kconfig b/drivers/media/platform/st/sti/Kconfig new file mode 100644 index 000000000..60068e8b4 --- /dev/null +++ b/drivers/media/platform/st/sti/Kconfig @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +source "drivers/media/platform/st/sti/bdisp/Kconfig" +source "drivers/media/platform/st/sti/c8sectpfe/Kconfig" +source "drivers/media/platform/st/sti/delta/Kconfig" +source "drivers/media/platform/st/sti/hva/Kconfig" diff --git a/drivers/media/platform/st/sti/Makefile b/drivers/media/platform/st/sti/Makefile new file mode 100644 index 000000000..f9ce8169b --- /dev/null +++ b/drivers/media/platform/st/sti/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += bdisp/ +obj-y += c8sectpfe/ +obj-y += delta/ +obj-y += hva/ +obj-y += stm32/ diff --git a/drivers/media/platform/st/sti/bdisp/Kconfig b/drivers/media/platform/st/sti/bdisp/Kconfig new file mode 100644 index 000000000..496f8aedf --- /dev/null +++ b/drivers/media/platform/st/sti/bdisp/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_STI_BDISP + tristate "STMicroelectronics BDISP 2D blitter driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_STI || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This v4l2 mem2mem driver is a 2D blitter for STMicroelectronics SoC. diff --git a/drivers/media/platform/st/sti/bdisp/Makefile b/drivers/media/platform/st/sti/bdisp/Makefile new file mode 100644 index 000000000..39ade0a34 --- /dev/null +++ b/drivers/media/platform/st/sti/bdisp/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_STI_BDISP) += bdisp.o + +bdisp-objs := bdisp-v4l2.o bdisp-hw.o bdisp-debug.o diff --git a/drivers/media/platform/st/sti/bdisp/bdisp-debug.c b/drivers/media/platform/st/sti/bdisp/bdisp-debug.c new file mode 100644 index 000000000..a27f638df --- /dev/null +++ b/drivers/media/platform/st/sti/bdisp/bdisp-debug.c @@ -0,0 +1,658 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne for STMicroelectronics. + */ + +#include +#include + +#include "bdisp.h" +#include "bdisp-filter.h" +#include "bdisp-reg.h" + +void bdisp_dbg_perf_begin(struct bdisp_dev *bdisp) +{ + bdisp->dbg.hw_start = ktime_get(); +} + +void bdisp_dbg_perf_end(struct bdisp_dev *bdisp) +{ + s64 time_us; + + time_us = ktime_us_delta(ktime_get(), bdisp->dbg.hw_start); + + if (!bdisp->dbg.min_duration) + bdisp->dbg.min_duration = time_us; + else + bdisp->dbg.min_duration = min(time_us, bdisp->dbg.min_duration); + + bdisp->dbg.last_duration = time_us; + bdisp->dbg.max_duration = max(time_us, bdisp->dbg.max_duration); + bdisp->dbg.tot_duration += time_us; +} + +static void bdisp_dbg_dump_ins(struct seq_file *s, u32 val) +{ + seq_printf(s, "INS\t0x%08X\t", val); + + switch (val & BLT_INS_S1_MASK) { + case BLT_INS_S1_OFF: + break; + case BLT_INS_S1_MEM: + seq_puts(s, "SRC1=mem - "); + break; + case BLT_INS_S1_CF: + seq_puts(s, "SRC1=ColorFill - "); + break; + case BLT_INS_S1_COPY: + seq_puts(s, "SRC1=copy - "); + break; + case BLT_INS_S1_FILL: + seq_puts(s, "SRC1=fil - "); + break; + default: + seq_puts(s, "SRC1=??? - "); + break; + } + + switch (val & BLT_INS_S2_MASK) { + case BLT_INS_S2_OFF: + break; + case BLT_INS_S2_MEM: + seq_puts(s, "SRC2=mem - "); + break; + case BLT_INS_S2_CF: + seq_puts(s, "SRC2=ColorFill - "); + break; + default: + seq_puts(s, "SRC2=??? - "); + break; + } + + if ((val & BLT_INS_S3_MASK) == BLT_INS_S3_MEM) + seq_puts(s, "SRC3=mem - "); + + if (val & BLT_INS_IVMX) + seq_puts(s, "IVMX - "); + if (val & BLT_INS_CLUT) + seq_puts(s, "CLUT - "); + if (val & BLT_INS_SCALE) + seq_puts(s, "Scale - "); + if (val & BLT_INS_FLICK) + seq_puts(s, "Flicker - "); + if (val & BLT_INS_CLIP) + seq_puts(s, "Clip - "); + if (val & BLT_INS_CKEY) + seq_puts(s, "ColorKey - "); + if (val & BLT_INS_OVMX) + seq_puts(s, "OVMX - "); + if (val & BLT_INS_DEI) + seq_puts(s, "Deint - "); + if (val & BLT_INS_PMASK) + seq_puts(s, "PlaneMask - "); + if (val & BLT_INS_VC1R) + seq_puts(s, "VC1R - "); + if (val & BLT_INS_ROTATE) + seq_puts(s, "Rotate - "); + if (val & BLT_INS_GRAD) + seq_puts(s, "GradFill - "); + if (val & BLT_INS_AQLOCK) + seq_puts(s, "AQLock - "); + if (val & BLT_INS_PACE) + seq_puts(s, "Pace - "); + if (val & BLT_INS_IRQ) + seq_puts(s, "IRQ - "); + + seq_putc(s, '\n'); +} + +static void bdisp_dbg_dump_tty(struct seq_file *s, u32 val) +{ + seq_printf(s, "TTY\t0x%08X\t", val); + seq_printf(s, "Pitch=%d - ", val & 0xFFFF); + + switch ((val & BLT_TTY_COL_MASK) >> BLT_TTY_COL_SHIFT) { + case BDISP_RGB565: + seq_puts(s, "RGB565 - "); + break; + case BDISP_RGB888: + seq_puts(s, "RGB888 - "); + break; + case BDISP_XRGB8888: + seq_puts(s, "xRGB888 - "); + break; + case BDISP_ARGB8888: + seq_puts(s, "ARGB8888 - "); + break; + case BDISP_NV12: + seq_puts(s, "NV12 - "); + break; + case BDISP_YUV_3B: + seq_puts(s, "YUV420P - "); + break; + default: + seq_puts(s, "ColorFormat ??? - "); + break; + } + + if (val & BLT_TTY_ALPHA_R) + seq_puts(s, "AlphaRange - "); + if (val & BLT_TTY_CR_NOT_CB) + seq_puts(s, "CrNotCb - "); + if (val & BLT_TTY_MB) + seq_puts(s, "MB - "); + if (val & BLT_TTY_HSO) + seq_puts(s, "HSO inverse - "); + if (val & BLT_TTY_VSO) + seq_puts(s, "VSO inverse - "); + if (val & BLT_TTY_DITHER) + seq_puts(s, "Dither - "); + if (val & BLT_TTY_CHROMA) + seq_puts(s, "Write CHROMA - "); + if (val & BLT_TTY_BIG_END) + seq_puts(s, "BigEndian - "); + + seq_putc(s, '\n'); +} + +static void bdisp_dbg_dump_xy(struct seq_file *s, u32 val, char *name) +{ + seq_printf(s, "%s\t0x%08X\t", name, val); + seq_printf(s, "(%d,%d)\n", val & 0xFFFF, (val >> 16)); +} + +static void bdisp_dbg_dump_sz(struct seq_file *s, u32 val, char *name) +{ + seq_printf(s, "%s\t0x%08X\t", name, val); + seq_printf(s, "%dx%d\n", val & 0x1FFF, (val >> 16) & 0x1FFF); +} + +static void bdisp_dbg_dump_sty(struct seq_file *s, + u32 val, u32 addr, char *name) +{ + bool s1, s2, s3; + + seq_printf(s, "%s\t0x%08X\t", name, val); + + if (!addr || !name || (strlen(name) < 2)) + goto done; + + s1 = name[strlen(name) - 1] == '1'; + s2 = name[strlen(name) - 1] == '2'; + s3 = name[strlen(name) - 1] == '3'; + + seq_printf(s, "Pitch=%d - ", val & 0xFFFF); + + switch ((val & BLT_TTY_COL_MASK) >> BLT_TTY_COL_SHIFT) { + case BDISP_RGB565: + seq_puts(s, "RGB565 - "); + break; + case BDISP_RGB888: + seq_puts(s, "RGB888 - "); + break; + case BDISP_XRGB8888: + seq_puts(s, "xRGB888 - "); + break; + case BDISP_ARGB8888: + seq_puts(s, "ARGB888 - "); + break; + case BDISP_NV12: + seq_puts(s, "NV12 - "); + break; + case BDISP_YUV_3B: + seq_puts(s, "YUV420P - "); + break; + default: + seq_puts(s, "ColorFormat ??? - "); + break; + } + + if ((val & BLT_TTY_ALPHA_R) && !s3) + seq_puts(s, "AlphaRange - "); + if ((val & BLT_S1TY_A1_SUBSET) && !s3) + seq_puts(s, "A1SubSet - "); + if ((val & BLT_TTY_MB) && !s1) + seq_puts(s, "MB - "); + if (val & BLT_TTY_HSO) + seq_puts(s, "HSO inverse - "); + if (val & BLT_TTY_VSO) + seq_puts(s, "VSO inverse - "); + if ((val & BLT_S1TY_CHROMA_EXT) && (s1 || s2)) + seq_puts(s, "ChromaExt - "); + if ((val & BLT_S3TY_BLANK_ACC) && s3) + seq_puts(s, "Blank Acc - "); + if ((val & BTL_S1TY_SUBBYTE) && !s3) + seq_puts(s, "SubByte - "); + if ((val & BLT_S1TY_RGB_EXP) && !s3) + seq_puts(s, "RGBExpand - "); + if ((val & BLT_TTY_BIG_END) && !s3) + seq_puts(s, "BigEndian - "); + +done: + seq_putc(s, '\n'); +} + +static void bdisp_dbg_dump_fctl(struct seq_file *s, u32 val) +{ + seq_printf(s, "FCTL\t0x%08X\t", val); + + if ((val & BLT_FCTL_Y_HV_SCALE) == BLT_FCTL_Y_HV_SCALE) + seq_puts(s, "Resize Luma - "); + else if ((val & BLT_FCTL_Y_HV_SCALE) == BLT_FCTL_Y_HV_SAMPLE) + seq_puts(s, "Sample Luma - "); + + if ((val & BLT_FCTL_HV_SCALE) == BLT_FCTL_HV_SCALE) + seq_puts(s, "Resize Chroma"); + else if ((val & BLT_FCTL_HV_SCALE) == BLT_FCTL_HV_SAMPLE) + seq_puts(s, "Sample Chroma"); + + seq_putc(s, '\n'); +} + +static void bdisp_dbg_dump_rsf(struct seq_file *s, u32 val, char *name) +{ + u32 inc; + + seq_printf(s, "%s\t0x%08X\t", name, val); + + if (!val) + goto done; + + inc = val & 0xFFFF; + seq_printf(s, "H: %d(6.10) / scale~%dx0.1 - ", inc, 1024 * 10 / inc); + + inc = val >> 16; + seq_printf(s, "V: %d(6.10) / scale~%dx0.1", inc, 1024 * 10 / inc); + +done: + seq_putc(s, '\n'); +} + +static void bdisp_dbg_dump_rzi(struct seq_file *s, u32 val, char *name) +{ + seq_printf(s, "%s\t0x%08X\t", name, val); + + if (!val) + goto done; + + seq_printf(s, "H: init=%d repeat=%d - ", val & 0x3FF, (val >> 12) & 7); + val >>= 16; + seq_printf(s, "V: init=%d repeat=%d", val & 0x3FF, (val >> 12) & 7); + +done: + seq_putc(s, '\n'); +} + +static void bdisp_dbg_dump_ivmx(struct seq_file *s, + u32 c0, u32 c1, u32 c2, u32 c3) +{ + seq_printf(s, "IVMX0\t0x%08X\n", c0); + seq_printf(s, "IVMX1\t0x%08X\n", c1); + seq_printf(s, "IVMX2\t0x%08X\n", c2); + seq_printf(s, "IVMX3\t0x%08X\t", c3); + + if (!c0 && !c1 && !c2 && !c3) { + seq_putc(s, '\n'); + return; + } + + if ((c0 == bdisp_rgb_to_yuv[0]) && + (c1 == bdisp_rgb_to_yuv[1]) && + (c2 == bdisp_rgb_to_yuv[2]) && + (c3 == bdisp_rgb_to_yuv[3])) { + seq_puts(s, "RGB to YUV\n"); + return; + } + + if ((c0 == bdisp_yuv_to_rgb[0]) && + (c1 == bdisp_yuv_to_rgb[1]) && + (c2 == bdisp_yuv_to_rgb[2]) && + (c3 == bdisp_yuv_to_rgb[3])) { + seq_puts(s, "YUV to RGB\n"); + return; + } + seq_puts(s, "Unknown conversion\n"); +} + +static int last_nodes_show(struct seq_file *s, void *data) +{ + /* Not dumping all fields, focusing on significant ones */ + struct bdisp_dev *bdisp = s->private; + struct bdisp_node *node; + int i = 0; + + if (!bdisp->dbg.copy_node[0]) { + seq_puts(s, "No node built yet\n"); + return 0; + } + + do { + node = bdisp->dbg.copy_node[i]; + if (!node) + break; + seq_printf(s, "--------\nNode %d:\n", i); + seq_puts(s, "-- General --\n"); + seq_printf(s, "NIP\t0x%08X\n", node->nip); + seq_printf(s, "CIC\t0x%08X\n", node->cic); + bdisp_dbg_dump_ins(s, node->ins); + seq_printf(s, "ACK\t0x%08X\n", node->ack); + seq_puts(s, "-- Target --\n"); + seq_printf(s, "TBA\t0x%08X\n", node->tba); + bdisp_dbg_dump_tty(s, node->tty); + bdisp_dbg_dump_xy(s, node->txy, "TXY"); + bdisp_dbg_dump_sz(s, node->tsz, "TSZ"); + /* Color Fill not dumped */ + seq_puts(s, "-- Source 1 --\n"); + seq_printf(s, "S1BA\t0x%08X\n", node->s1ba); + bdisp_dbg_dump_sty(s, node->s1ty, node->s1ba, "S1TY"); + bdisp_dbg_dump_xy(s, node->s1xy, "S1XY"); + seq_puts(s, "-- Source 2 --\n"); + seq_printf(s, "S2BA\t0x%08X\n", node->s2ba); + bdisp_dbg_dump_sty(s, node->s2ty, node->s2ba, "S2TY"); + bdisp_dbg_dump_xy(s, node->s2xy, "S2XY"); + bdisp_dbg_dump_sz(s, node->s2sz, "S2SZ"); + seq_puts(s, "-- Source 3 --\n"); + seq_printf(s, "S3BA\t0x%08X\n", node->s3ba); + bdisp_dbg_dump_sty(s, node->s3ty, node->s3ba, "S3TY"); + bdisp_dbg_dump_xy(s, node->s3xy, "S3XY"); + bdisp_dbg_dump_sz(s, node->s3sz, "S3SZ"); + /* Clipping not dumped */ + /* CLUT not dumped */ + seq_puts(s, "-- Filter & Mask --\n"); + bdisp_dbg_dump_fctl(s, node->fctl); + /* PMK not dumped */ + seq_puts(s, "-- Chroma Filter --\n"); + bdisp_dbg_dump_rsf(s, node->rsf, "RSF"); + bdisp_dbg_dump_rzi(s, node->rzi, "RZI"); + seq_printf(s, "HFP\t0x%08X\n", node->hfp); + seq_printf(s, "VFP\t0x%08X\n", node->vfp); + seq_puts(s, "-- Luma Filter --\n"); + bdisp_dbg_dump_rsf(s, node->y_rsf, "Y_RSF"); + bdisp_dbg_dump_rzi(s, node->y_rzi, "Y_RZI"); + seq_printf(s, "Y_HFP\t0x%08X\n", node->y_hfp); + seq_printf(s, "Y_VFP\t0x%08X\n", node->y_vfp); + /* Flicker not dumped */ + /* Color key not dumped */ + /* Reserved not dumped */ + /* Static Address & User not dumped */ + seq_puts(s, "-- Input Versatile Matrix --\n"); + bdisp_dbg_dump_ivmx(s, node->ivmx0, node->ivmx1, + node->ivmx2, node->ivmx3); + /* Output Versatile Matrix not dumped */ + /* Pace not dumped */ + /* VC1R & DEI not dumped */ + /* Gradient Fill not dumped */ + } while ((++i < MAX_NB_NODE) && node->nip); + + return 0; +} + +static int last_nodes_raw_show(struct seq_file *s, void *data) +{ + struct bdisp_dev *bdisp = s->private; + struct bdisp_node *node; + u32 *val; + int j, i = 0; + + if (!bdisp->dbg.copy_node[0]) { + seq_puts(s, "No node built yet\n"); + return 0; + } + + do { + node = bdisp->dbg.copy_node[i]; + if (!node) + break; + + seq_printf(s, "--------\nNode %d:\n", i); + val = (u32 *)node; + for (j = 0; j < sizeof(struct bdisp_node) / sizeof(u32); j++) + seq_printf(s, "0x%08X\n", *val++); + } while ((++i < MAX_NB_NODE) && node->nip); + + return 0; +} + +static const char *bdisp_fmt_to_str(struct bdisp_frame frame) +{ + switch (frame.fmt->pixelformat) { + case V4L2_PIX_FMT_YUV420: + return "YUV420P"; + case V4L2_PIX_FMT_NV12: + if (frame.field == V4L2_FIELD_INTERLACED) + return "NV12 interlaced"; + else + return "NV12"; + case V4L2_PIX_FMT_RGB565: + return "RGB16"; + case V4L2_PIX_FMT_RGB24: + return "RGB24"; + case V4L2_PIX_FMT_XBGR32: + return "XRGB"; + case V4L2_PIX_FMT_ABGR32: + return "ARGB"; + default: + return "????"; + } +} + +static int last_request_show(struct seq_file *s, void *data) +{ + struct bdisp_dev *bdisp = s->private; + struct bdisp_request *request = &bdisp->dbg.copy_request; + struct bdisp_frame src, dst; + + if (!request->nb_req) { + seq_puts(s, "No request\n"); + return 0; + } + + src = request->src; + dst = request->dst; + + seq_printf(s, "\nRequest #%d\n", request->nb_req); + + seq_printf(s, "Format: %s\t\t\t%s\n", + bdisp_fmt_to_str(src), bdisp_fmt_to_str(dst)); + seq_printf(s, "Crop area: %dx%d @ %d,%d ==>\t%dx%d @ %d,%d\n", + src.crop.width, src.crop.height, + src.crop.left, src.crop.top, + dst.crop.width, dst.crop.height, + dst.crop.left, dst.crop.top); + seq_printf(s, "Buff size: %dx%d\t\t%dx%d\n\n", + src.width, src.height, dst.width, dst.height); + + if (request->hflip) + seq_puts(s, "Horizontal flip\n\n"); + + if (request->vflip) + seq_puts(s, "Vertical flip\n\n"); + + return 0; +} + +#define DUMP(reg) seq_printf(s, #reg " \t0x%08X\n", readl(bdisp->regs + reg)) + +static int regs_show(struct seq_file *s, void *data) +{ + struct bdisp_dev *bdisp = s->private; + int ret; + unsigned int i; + + ret = pm_runtime_resume_and_get(bdisp->dev); + if (ret < 0) { + seq_puts(s, "Cannot wake up IP\n"); + return 0; + } + + seq_printf(s, "Reg @ = 0x%p\n", bdisp->regs); + + seq_puts(s, "\nStatic:\n"); + DUMP(BLT_CTL); + DUMP(BLT_ITS); + DUMP(BLT_STA1); + DUMP(BLT_AQ1_CTL); + DUMP(BLT_AQ1_IP); + DUMP(BLT_AQ1_LNA); + DUMP(BLT_AQ1_STA); + DUMP(BLT_ITM0); + + seq_puts(s, "\nPlugs:\n"); + DUMP(BLT_PLUGS1_OP2); + DUMP(BLT_PLUGS1_CHZ); + DUMP(BLT_PLUGS1_MSZ); + DUMP(BLT_PLUGS1_PGZ); + DUMP(BLT_PLUGS2_OP2); + DUMP(BLT_PLUGS2_CHZ); + DUMP(BLT_PLUGS2_MSZ); + DUMP(BLT_PLUGS2_PGZ); + DUMP(BLT_PLUGS3_OP2); + DUMP(BLT_PLUGS3_CHZ); + DUMP(BLT_PLUGS3_MSZ); + DUMP(BLT_PLUGS3_PGZ); + DUMP(BLT_PLUGT_OP2); + DUMP(BLT_PLUGT_CHZ); + DUMP(BLT_PLUGT_MSZ); + DUMP(BLT_PLUGT_PGZ); + + seq_puts(s, "\nNode:\n"); + DUMP(BLT_NIP); + DUMP(BLT_CIC); + DUMP(BLT_INS); + DUMP(BLT_ACK); + DUMP(BLT_TBA); + DUMP(BLT_TTY); + DUMP(BLT_TXY); + DUMP(BLT_TSZ); + DUMP(BLT_S1BA); + DUMP(BLT_S1TY); + DUMP(BLT_S1XY); + DUMP(BLT_S2BA); + DUMP(BLT_S2TY); + DUMP(BLT_S2XY); + DUMP(BLT_S2SZ); + DUMP(BLT_S3BA); + DUMP(BLT_S3TY); + DUMP(BLT_S3XY); + DUMP(BLT_S3SZ); + DUMP(BLT_FCTL); + DUMP(BLT_RSF); + DUMP(BLT_RZI); + DUMP(BLT_HFP); + DUMP(BLT_VFP); + DUMP(BLT_Y_RSF); + DUMP(BLT_Y_RZI); + DUMP(BLT_Y_HFP); + DUMP(BLT_Y_VFP); + DUMP(BLT_IVMX0); + DUMP(BLT_IVMX1); + DUMP(BLT_IVMX2); + DUMP(BLT_IVMX3); + DUMP(BLT_OVMX0); + DUMP(BLT_OVMX1); + DUMP(BLT_OVMX2); + DUMP(BLT_OVMX3); + DUMP(BLT_DEI); + + seq_puts(s, "\nFilter:\n"); + for (i = 0; i < BLT_NB_H_COEF; i++) { + seq_printf(s, "BLT_HFC%d \t0x%08X\n", i, + readl(bdisp->regs + BLT_HFC_N + i * 4)); + } + for (i = 0; i < BLT_NB_V_COEF; i++) { + seq_printf(s, "BLT_VFC%d \t0x%08X\n", i, + readl(bdisp->regs + BLT_VFC_N + i * 4)); + } + + seq_puts(s, "\nLuma filter:\n"); + for (i = 0; i < BLT_NB_H_COEF; i++) { + seq_printf(s, "BLT_Y_HFC%d \t0x%08X\n", i, + readl(bdisp->regs + BLT_Y_HFC_N + i * 4)); + } + for (i = 0; i < BLT_NB_V_COEF; i++) { + seq_printf(s, "BLT_Y_VFC%d \t0x%08X\n", i, + readl(bdisp->regs + BLT_Y_VFC_N + i * 4)); + } + + pm_runtime_put(bdisp->dev); + + return 0; +} + +#define SECOND 1000000 + +static int perf_show(struct seq_file *s, void *data) +{ + struct bdisp_dev *bdisp = s->private; + struct bdisp_request *request = &bdisp->dbg.copy_request; + s64 avg_time_us; + int avg_fps, min_fps, max_fps, last_fps; + + if (!request->nb_req) { + seq_puts(s, "No request\n"); + return 0; + } + + avg_time_us = div64_s64(bdisp->dbg.tot_duration, request->nb_req); + if (avg_time_us > SECOND) + avg_fps = 0; + else + avg_fps = SECOND / (s32)avg_time_us; + + if (bdisp->dbg.min_duration > SECOND) + min_fps = 0; + else + min_fps = SECOND / (s32)bdisp->dbg.min_duration; + + if (bdisp->dbg.max_duration > SECOND) + max_fps = 0; + else + max_fps = SECOND / (s32)bdisp->dbg.max_duration; + + if (bdisp->dbg.last_duration > SECOND) + last_fps = 0; + else + last_fps = SECOND / (s32)bdisp->dbg.last_duration; + + seq_printf(s, "HW processing (%d requests):\n", request->nb_req); + seq_printf(s, " Average: %5lld us (%3d fps)\n", + avg_time_us, avg_fps); + seq_printf(s, " Min-Max: %5lld us (%3d fps) - %5lld us (%3d fps)\n", + bdisp->dbg.min_duration, min_fps, + bdisp->dbg.max_duration, max_fps); + seq_printf(s, " Last: %5lld us (%3d fps)\n", + bdisp->dbg.last_duration, last_fps); + + return 0; +} + +#define bdisp_dbg_create_entry(name) \ + debugfs_create_file(#name, S_IRUGO, bdisp->dbg.debugfs_entry, bdisp, \ + &name##_fops) + +DEFINE_SHOW_ATTRIBUTE(regs); +DEFINE_SHOW_ATTRIBUTE(last_nodes); +DEFINE_SHOW_ATTRIBUTE(last_nodes_raw); +DEFINE_SHOW_ATTRIBUTE(last_request); +DEFINE_SHOW_ATTRIBUTE(perf); + +void bdisp_debugfs_create(struct bdisp_dev *bdisp) +{ + char dirname[16]; + + snprintf(dirname, sizeof(dirname), "%s%d", BDISP_NAME, bdisp->id); + bdisp->dbg.debugfs_entry = debugfs_create_dir(dirname, NULL); + + bdisp_dbg_create_entry(regs); + bdisp_dbg_create_entry(last_nodes); + bdisp_dbg_create_entry(last_nodes_raw); + bdisp_dbg_create_entry(last_request); + bdisp_dbg_create_entry(perf); +} + +void bdisp_debugfs_remove(struct bdisp_dev *bdisp) +{ + debugfs_remove_recursive(bdisp->dbg.debugfs_entry); + bdisp->dbg.debugfs_entry = NULL; +} diff --git a/drivers/media/platform/st/sti/bdisp/bdisp-filter.h b/drivers/media/platform/st/sti/bdisp/bdisp-filter.h new file mode 100644 index 000000000..9e1a95fd2 --- /dev/null +++ b/drivers/media/platform/st/sti/bdisp/bdisp-filter.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne for STMicroelectronics. + */ + +#define BDISP_HF_NB 64 +#define BDISP_VF_NB 40 + +/** + * struct bdisp_filter_h_spec - Horizontal filter specification + * + * @min: min scale factor for this filter (6.10 fixed point) + * @max: max scale factor for this filter (6.10 fixed point) + * @coef: filter coefficients + */ +struct bdisp_filter_h_spec { + const u16 min; + const u16 max; + const u8 coef[BDISP_HF_NB]; +}; +/** + * struct bdisp_filter_v_spec - Vertical filter specification + * + * @min: min scale factor for this filter (6.10 fixed point) + * @max: max scale factor for this filter (6.10 fixed point) + * @coef: filter coefficients + */ +struct bdisp_filter_v_spec { + const u16 min; + const u16 max; + const u8 coef[BDISP_VF_NB]; +}; + +/* RGB YUV 601 standard conversion */ +static const u32 bdisp_rgb_to_yuv[] = { + 0x0e1e8bee, 0x08420419, 0xfb5ed471, 0x08004080, +}; + +static const u32 bdisp_yuv_to_rgb[] = { + 0x3324a800, 0xe604ab9c, 0x0004a957, 0x32121eeb, +}; diff --git a/drivers/media/platform/st/sti/bdisp/bdisp-hw.c b/drivers/media/platform/st/sti/bdisp/bdisp-hw.c new file mode 100644 index 000000000..a74e9fd65 --- /dev/null +++ b/drivers/media/platform/st/sti/bdisp/bdisp-hw.c @@ -0,0 +1,1118 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne for STMicroelectronics. + */ + +#include + +#include "bdisp.h" +#include "bdisp-filter.h" +#include "bdisp-reg.h" + +/* Max width of the source frame in a single node */ +#define MAX_SRC_WIDTH 2048 + +/* Reset & boot poll config */ +#define POLL_RST_MAX 500 +#define POLL_RST_DELAY_MS 2 + +enum bdisp_target_plan { + BDISP_RGB, + BDISP_Y, + BDISP_CBCR +}; + +struct bdisp_op_cfg { + bool cconv; /* RGB - YUV conversion */ + bool hflip; /* Horizontal flip */ + bool vflip; /* Vertical flip */ + bool wide; /* Wide (>MAX_SRC_WIDTH) */ + bool scale; /* Scale */ + u16 h_inc; /* Horizontal increment in 6.10 format */ + u16 v_inc; /* Vertical increment in 6.10 format */ + bool src_interlaced; /* is the src an interlaced buffer */ + u8 src_nbp; /* nb of planes of the src */ + bool src_yuv; /* is the src a YUV color format */ + bool src_420; /* is the src 4:2:0 chroma subsampled */ + u8 dst_nbp; /* nb of planes of the dst */ + bool dst_yuv; /* is the dst a YUV color format */ + bool dst_420; /* is the dst 4:2:0 chroma subsampled */ +}; + +struct bdisp_filter_addr { + u16 min; /* Filter min scale factor (6.10 fixed point) */ + u16 max; /* Filter max scale factor (6.10 fixed point) */ + void *virt; /* Virtual address for filter table */ + dma_addr_t paddr; /* Physical address for filter table */ +}; + +static const struct bdisp_filter_h_spec bdisp_h_spec[] = { + { + .min = 0, + .max = 921, + .coef = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xff, 0x07, 0x3d, 0xfc, 0x01, 0x00, + 0x00, 0x01, 0xfd, 0x11, 0x36, 0xf9, 0x02, 0x00, + 0x00, 0x01, 0xfb, 0x1b, 0x2e, 0xf9, 0x02, 0x00, + 0x00, 0x01, 0xf9, 0x26, 0x26, 0xf9, 0x01, 0x00, + 0x00, 0x02, 0xf9, 0x30, 0x19, 0xfb, 0x01, 0x00, + 0x00, 0x02, 0xf9, 0x39, 0x0e, 0xfd, 0x01, 0x00, + 0x00, 0x01, 0xfc, 0x3e, 0x06, 0xff, 0x00, 0x00 + } + }, + { + .min = 921, + .max = 1024, + .coef = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, + 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, + 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, + 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, + 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff + } + }, + { + .min = 1024, + .max = 1126, + .coef = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, + 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, + 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, + 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, + 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff + } + }, + { + .min = 1126, + .max = 1228, + .coef = { + 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, + 0xff, 0x03, 0xfd, 0x08, 0x3e, 0xf9, 0x04, 0xfe, + 0xfd, 0x06, 0xf8, 0x13, 0x3b, 0xf4, 0x07, 0xfc, + 0xfb, 0x08, 0xf5, 0x1f, 0x34, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x2b, 0x2a, 0xf1, 0x09, 0xfb, + 0xfb, 0x09, 0xf2, 0x35, 0x1e, 0xf4, 0x08, 0xfb, + 0xfc, 0x07, 0xf5, 0x3c, 0x12, 0xf7, 0x06, 0xfd, + 0xfe, 0x04, 0xfa, 0x3f, 0x07, 0xfc, 0x03, 0xff + } + }, + { + .min = 1228, + .max = 1331, + .coef = { + 0xfd, 0x04, 0xfc, 0x05, 0x39, 0x05, 0xfc, 0x04, + 0xfc, 0x06, 0xf9, 0x0c, 0x39, 0xfe, 0x00, 0x02, + 0xfb, 0x08, 0xf6, 0x17, 0x35, 0xf9, 0x02, 0x00, + 0xfc, 0x08, 0xf4, 0x20, 0x30, 0xf4, 0x05, 0xff, + 0xfd, 0x07, 0xf4, 0x29, 0x28, 0xf3, 0x07, 0xfd, + 0xff, 0x05, 0xf5, 0x31, 0x1f, 0xf3, 0x08, 0xfc, + 0x00, 0x02, 0xf9, 0x38, 0x14, 0xf6, 0x08, 0xfb, + 0x02, 0x00, 0xff, 0x3a, 0x0b, 0xf8, 0x06, 0xfc + } + }, + { + .min = 1331, + .max = 1433, + .coef = { + 0xfc, 0x06, 0xf9, 0x09, 0x34, 0x09, 0xf9, 0x06, + 0xfd, 0x07, 0xf7, 0x10, 0x32, 0x02, 0xfc, 0x05, + 0xfe, 0x07, 0xf6, 0x17, 0x2f, 0xfc, 0xff, 0x04, + 0xff, 0x06, 0xf5, 0x20, 0x2a, 0xf9, 0x01, 0x02, + 0x00, 0x04, 0xf6, 0x27, 0x25, 0xf6, 0x04, 0x00, + 0x02, 0x01, 0xf9, 0x2d, 0x1d, 0xf5, 0x06, 0xff, + 0x04, 0xff, 0xfd, 0x31, 0x15, 0xf5, 0x07, 0xfe, + 0x05, 0xfc, 0x02, 0x35, 0x0d, 0xf7, 0x07, 0xfd + } + }, + { + .min = 1433, + .max = 1536, + .coef = { + 0xfe, 0x06, 0xf8, 0x0b, 0x30, 0x0b, 0xf8, 0x06, + 0xff, 0x06, 0xf7, 0x12, 0x2d, 0x05, 0xfa, 0x06, + 0x00, 0x04, 0xf6, 0x18, 0x2c, 0x00, 0xfc, 0x06, + 0x01, 0x02, 0xf7, 0x1f, 0x27, 0xfd, 0xff, 0x04, + 0x03, 0x00, 0xf9, 0x24, 0x24, 0xf9, 0x00, 0x03, + 0x04, 0xff, 0xfd, 0x29, 0x1d, 0xf7, 0x02, 0x01, + 0x06, 0xfc, 0x00, 0x2d, 0x17, 0xf6, 0x04, 0x00, + 0x06, 0xfa, 0x05, 0x30, 0x0f, 0xf7, 0x06, 0xff + } + }, + { + .min = 1536, + .max = 2048, + .coef = { + 0x05, 0xfd, 0xfb, 0x13, 0x25, 0x13, 0xfb, 0xfd, + 0x05, 0xfc, 0xfd, 0x17, 0x24, 0x0f, 0xf9, 0xff, + 0x04, 0xfa, 0xff, 0x1b, 0x24, 0x0b, 0xf9, 0x00, + 0x03, 0xf9, 0x01, 0x1f, 0x23, 0x08, 0xf8, 0x01, + 0x02, 0xf9, 0x04, 0x22, 0x20, 0x04, 0xf9, 0x02, + 0x01, 0xf8, 0x08, 0x25, 0x1d, 0x01, 0xf9, 0x03, + 0x00, 0xf9, 0x0c, 0x25, 0x1a, 0xfe, 0xfa, 0x04, + 0xff, 0xf9, 0x10, 0x26, 0x15, 0xfc, 0xfc, 0x05 + } + }, + { + .min = 2048, + .max = 3072, + .coef = { + 0xfc, 0xfd, 0x06, 0x13, 0x18, 0x13, 0x06, 0xfd, + 0xfc, 0xfe, 0x08, 0x15, 0x17, 0x12, 0x04, 0xfc, + 0xfb, 0xfe, 0x0a, 0x16, 0x18, 0x10, 0x03, 0xfc, + 0xfb, 0x00, 0x0b, 0x18, 0x17, 0x0f, 0x01, 0xfb, + 0xfb, 0x00, 0x0d, 0x19, 0x17, 0x0d, 0x00, 0xfb, + 0xfb, 0x01, 0x0f, 0x19, 0x16, 0x0b, 0x00, 0xfb, + 0xfc, 0x03, 0x11, 0x19, 0x15, 0x09, 0xfe, 0xfb, + 0xfc, 0x04, 0x12, 0x1a, 0x12, 0x08, 0xfe, 0xfc + } + }, + { + .min = 3072, + .max = 4096, + .coef = { + 0xfe, 0x02, 0x09, 0x0f, 0x0e, 0x0f, 0x09, 0x02, + 0xff, 0x02, 0x09, 0x0f, 0x10, 0x0e, 0x08, 0x01, + 0xff, 0x03, 0x0a, 0x10, 0x10, 0x0d, 0x07, 0x00, + 0x00, 0x04, 0x0b, 0x10, 0x0f, 0x0c, 0x06, 0x00, + 0x00, 0x05, 0x0c, 0x10, 0x0e, 0x0c, 0x05, 0x00, + 0x00, 0x06, 0x0c, 0x11, 0x0e, 0x0b, 0x04, 0x00, + 0x00, 0x07, 0x0d, 0x11, 0x0f, 0x0a, 0x03, 0xff, + 0x01, 0x08, 0x0e, 0x11, 0x0e, 0x09, 0x02, 0xff + } + }, + { + .min = 4096, + .max = 5120, + .coef = { + 0x00, 0x04, 0x09, 0x0c, 0x0e, 0x0c, 0x09, 0x04, + 0x01, 0x05, 0x09, 0x0c, 0x0d, 0x0c, 0x08, 0x04, + 0x01, 0x05, 0x0a, 0x0c, 0x0e, 0x0b, 0x08, 0x03, + 0x02, 0x06, 0x0a, 0x0d, 0x0c, 0x0b, 0x07, 0x03, + 0x02, 0x07, 0x0a, 0x0d, 0x0d, 0x0a, 0x07, 0x02, + 0x03, 0x07, 0x0b, 0x0d, 0x0c, 0x0a, 0x06, 0x02, + 0x03, 0x08, 0x0b, 0x0d, 0x0d, 0x0a, 0x05, 0x01, + 0x04, 0x08, 0x0c, 0x0d, 0x0c, 0x09, 0x05, 0x01 + } + }, + { + .min = 5120, + .max = 65535, + .coef = { + 0x03, 0x06, 0x09, 0x0b, 0x09, 0x0b, 0x09, 0x06, + 0x03, 0x06, 0x09, 0x0b, 0x0c, 0x0a, 0x08, 0x05, + 0x03, 0x06, 0x09, 0x0b, 0x0c, 0x0a, 0x08, 0x05, + 0x04, 0x07, 0x09, 0x0b, 0x0b, 0x0a, 0x08, 0x04, + 0x04, 0x07, 0x0a, 0x0b, 0x0b, 0x0a, 0x07, 0x04, + 0x04, 0x08, 0x0a, 0x0b, 0x0b, 0x09, 0x07, 0x04, + 0x05, 0x08, 0x0a, 0x0b, 0x0c, 0x09, 0x06, 0x03, + 0x05, 0x08, 0x0a, 0x0b, 0x0c, 0x09, 0x06, 0x03 + } + } +}; + +#define NB_H_FILTER ARRAY_SIZE(bdisp_h_spec) + + +static const struct bdisp_filter_v_spec bdisp_v_spec[] = { + { + .min = 0, + .max = 1024, + .coef = { + 0x00, 0x00, 0x40, 0x00, 0x00, + 0x00, 0x06, 0x3d, 0xfd, 0x00, + 0xfe, 0x0f, 0x38, 0xfb, 0x00, + 0xfd, 0x19, 0x2f, 0xfb, 0x00, + 0xfc, 0x24, 0x24, 0xfc, 0x00, + 0xfb, 0x2f, 0x19, 0xfd, 0x00, + 0xfb, 0x38, 0x0f, 0xfe, 0x00, + 0xfd, 0x3d, 0x06, 0x00, 0x00 + } + }, + { + .min = 1024, + .max = 1331, + .coef = { + 0xfc, 0x05, 0x3e, 0x05, 0xfc, + 0xf8, 0x0e, 0x3b, 0xff, 0x00, + 0xf5, 0x18, 0x38, 0xf9, 0x02, + 0xf4, 0x21, 0x31, 0xf5, 0x05, + 0xf4, 0x2a, 0x27, 0xf4, 0x07, + 0xf6, 0x30, 0x1e, 0xf4, 0x08, + 0xf9, 0x35, 0x15, 0xf6, 0x07, + 0xff, 0x37, 0x0b, 0xf9, 0x06 + } + }, + { + .min = 1331, + .max = 1433, + .coef = { + 0xf8, 0x0a, 0x3c, 0x0a, 0xf8, + 0xf6, 0x12, 0x3b, 0x02, 0xfb, + 0xf4, 0x1b, 0x35, 0xfd, 0xff, + 0xf4, 0x23, 0x30, 0xf8, 0x01, + 0xf6, 0x29, 0x27, 0xf6, 0x04, + 0xf9, 0x2e, 0x1e, 0xf5, 0x06, + 0xfd, 0x31, 0x16, 0xf6, 0x06, + 0x02, 0x32, 0x0d, 0xf8, 0x07 + } + }, + { + .min = 1433, + .max = 1536, + .coef = { + 0xf6, 0x0e, 0x38, 0x0e, 0xf6, + 0xf5, 0x15, 0x38, 0x06, 0xf8, + 0xf5, 0x1d, 0x33, 0x00, 0xfb, + 0xf6, 0x23, 0x2d, 0xfc, 0xfe, + 0xf9, 0x28, 0x26, 0xf9, 0x00, + 0xfc, 0x2c, 0x1e, 0xf7, 0x03, + 0x00, 0x2e, 0x18, 0xf6, 0x04, + 0x05, 0x2e, 0x11, 0xf7, 0x05 + } + }, + { + .min = 1536, + .max = 2048, + .coef = { + 0xfb, 0x13, 0x24, 0x13, 0xfb, + 0xfd, 0x17, 0x23, 0x0f, 0xfa, + 0xff, 0x1a, 0x23, 0x0b, 0xf9, + 0x01, 0x1d, 0x22, 0x07, 0xf9, + 0x04, 0x20, 0x1f, 0x04, 0xf9, + 0x07, 0x22, 0x1c, 0x01, 0xfa, + 0x0b, 0x24, 0x17, 0xff, 0xfb, + 0x0f, 0x24, 0x14, 0xfd, 0xfc + } + }, + { + .min = 2048, + .max = 3072, + .coef = { + 0x05, 0x10, 0x16, 0x10, 0x05, + 0x06, 0x11, 0x16, 0x0f, 0x04, + 0x08, 0x13, 0x15, 0x0e, 0x02, + 0x09, 0x14, 0x16, 0x0c, 0x01, + 0x0b, 0x15, 0x15, 0x0b, 0x00, + 0x0d, 0x16, 0x13, 0x0a, 0x00, + 0x0f, 0x17, 0x13, 0x08, 0xff, + 0x11, 0x18, 0x12, 0x07, 0xfe + } + }, + { + .min = 3072, + .max = 4096, + .coef = { + 0x09, 0x0f, 0x10, 0x0f, 0x09, + 0x09, 0x0f, 0x12, 0x0e, 0x08, + 0x0a, 0x10, 0x11, 0x0e, 0x07, + 0x0b, 0x11, 0x11, 0x0d, 0x06, + 0x0c, 0x11, 0x12, 0x0c, 0x05, + 0x0d, 0x12, 0x11, 0x0c, 0x04, + 0x0e, 0x12, 0x11, 0x0b, 0x04, + 0x0f, 0x13, 0x11, 0x0a, 0x03 + } + }, + { + .min = 4096, + .max = 5120, + .coef = { + 0x0a, 0x0e, 0x10, 0x0e, 0x0a, + 0x0b, 0x0e, 0x0f, 0x0e, 0x0a, + 0x0b, 0x0f, 0x10, 0x0d, 0x09, + 0x0c, 0x0f, 0x10, 0x0d, 0x08, + 0x0d, 0x0f, 0x0f, 0x0d, 0x08, + 0x0d, 0x10, 0x10, 0x0c, 0x07, + 0x0e, 0x10, 0x0f, 0x0c, 0x07, + 0x0f, 0x10, 0x10, 0x0b, 0x06 + } + }, + { + .min = 5120, + .max = 65535, + .coef = { + 0x0b, 0x0e, 0x0e, 0x0e, 0x0b, + 0x0b, 0x0e, 0x0f, 0x0d, 0x0b, + 0x0c, 0x0e, 0x0f, 0x0d, 0x0a, + 0x0c, 0x0e, 0x0f, 0x0d, 0x0a, + 0x0d, 0x0f, 0x0e, 0x0d, 0x09, + 0x0d, 0x0f, 0x0f, 0x0c, 0x09, + 0x0e, 0x0f, 0x0e, 0x0c, 0x09, + 0x0e, 0x0f, 0x0f, 0x0c, 0x08 + } + } +}; + +#define NB_V_FILTER ARRAY_SIZE(bdisp_v_spec) + +static struct bdisp_filter_addr bdisp_h_filter[NB_H_FILTER]; +static struct bdisp_filter_addr bdisp_v_filter[NB_V_FILTER]; + +/** + * bdisp_hw_reset + * @bdisp: bdisp entity + * + * Resets HW + * + * RETURNS: + * 0 on success. + */ +int bdisp_hw_reset(struct bdisp_dev *bdisp) +{ + unsigned int i; + + dev_dbg(bdisp->dev, "%s\n", __func__); + + /* Mask Interrupt */ + writel(0, bdisp->regs + BLT_ITM0); + + /* Reset */ + writel(readl(bdisp->regs + BLT_CTL) | BLT_CTL_RESET, + bdisp->regs + BLT_CTL); + writel(0, bdisp->regs + BLT_CTL); + + /* Wait for reset done */ + for (i = 0; i < POLL_RST_MAX; i++) { + if (readl(bdisp->regs + BLT_STA1) & BLT_STA1_IDLE) + break; + udelay(POLL_RST_DELAY_MS * 1000); + } + if (i == POLL_RST_MAX) + dev_err(bdisp->dev, "Reset timeout\n"); + + return (i == POLL_RST_MAX) ? -EAGAIN : 0; +} + +/** + * bdisp_hw_get_and_clear_irq + * @bdisp: bdisp entity + * + * Read then reset interrupt status + * + * RETURNS: + * 0 if expected interrupt was raised. + */ +int bdisp_hw_get_and_clear_irq(struct bdisp_dev *bdisp) +{ + u32 its; + + its = readl(bdisp->regs + BLT_ITS); + + /* Check for the only expected IT: LastNode of AQ1 */ + if (!(its & BLT_ITS_AQ1_LNA)) { + dev_dbg(bdisp->dev, "Unexpected IT status: 0x%08X\n", its); + writel(its, bdisp->regs + BLT_ITS); + return -1; + } + + /* Clear and mask */ + writel(its, bdisp->regs + BLT_ITS); + writel(0, bdisp->regs + BLT_ITM0); + + return 0; +} + +/** + * bdisp_hw_free_nodes + * @ctx: bdisp context + * + * Free node memory + * + * RETURNS: + * None + */ +void bdisp_hw_free_nodes(struct bdisp_ctx *ctx) +{ + if (ctx && ctx->node[0]) + dma_free_attrs(ctx->bdisp_dev->dev, + sizeof(struct bdisp_node) * MAX_NB_NODE, + ctx->node[0], ctx->node_paddr[0], + DMA_ATTR_WRITE_COMBINE); +} + +/** + * bdisp_hw_alloc_nodes + * @ctx: bdisp context + * + * Allocate dma memory for nodes + * + * RETURNS: + * 0 on success + */ +int bdisp_hw_alloc_nodes(struct bdisp_ctx *ctx) +{ + struct device *dev = ctx->bdisp_dev->dev; + unsigned int i, node_size = sizeof(struct bdisp_node); + void *base; + dma_addr_t paddr; + + /* Allocate all the nodes within a single memory page */ + base = dma_alloc_attrs(dev, node_size * MAX_NB_NODE, &paddr, + GFP_KERNEL, DMA_ATTR_WRITE_COMBINE); + if (!base) { + dev_err(dev, "%s no mem\n", __func__); + return -ENOMEM; + } + + memset(base, 0, node_size * MAX_NB_NODE); + + for (i = 0; i < MAX_NB_NODE; i++) { + ctx->node[i] = base; + ctx->node_paddr[i] = paddr; + dev_dbg(dev, "node[%d]=0x%p (paddr=%pad)\n", i, ctx->node[i], + &paddr); + base += node_size; + paddr += node_size; + } + + return 0; +} + +/** + * bdisp_hw_free_filters + * @dev: device + * + * Free filters memory + * + * RETURNS: + * None + */ +void bdisp_hw_free_filters(struct device *dev) +{ + int size = (BDISP_HF_NB * NB_H_FILTER) + (BDISP_VF_NB * NB_V_FILTER); + + if (bdisp_h_filter[0].virt) + dma_free_attrs(dev, size, bdisp_h_filter[0].virt, + bdisp_h_filter[0].paddr, DMA_ATTR_WRITE_COMBINE); +} + +/** + * bdisp_hw_alloc_filters + * @dev: device + * + * Allocate dma memory for filters + * + * RETURNS: + * 0 on success + */ +int bdisp_hw_alloc_filters(struct device *dev) +{ + unsigned int i, size; + void *base; + dma_addr_t paddr; + + /* Allocate all the filters within a single memory page */ + size = (BDISP_HF_NB * NB_H_FILTER) + (BDISP_VF_NB * NB_V_FILTER); + base = dma_alloc_attrs(dev, size, &paddr, GFP_KERNEL, + DMA_ATTR_WRITE_COMBINE); + if (!base) + return -ENOMEM; + + /* Setup filter addresses */ + for (i = 0; i < NB_H_FILTER; i++) { + bdisp_h_filter[i].min = bdisp_h_spec[i].min; + bdisp_h_filter[i].max = bdisp_h_spec[i].max; + memcpy(base, bdisp_h_spec[i].coef, BDISP_HF_NB); + bdisp_h_filter[i].virt = base; + bdisp_h_filter[i].paddr = paddr; + base += BDISP_HF_NB; + paddr += BDISP_HF_NB; + } + + for (i = 0; i < NB_V_FILTER; i++) { + bdisp_v_filter[i].min = bdisp_v_spec[i].min; + bdisp_v_filter[i].max = bdisp_v_spec[i].max; + memcpy(base, bdisp_v_spec[i].coef, BDISP_VF_NB); + bdisp_v_filter[i].virt = base; + bdisp_v_filter[i].paddr = paddr; + base += BDISP_VF_NB; + paddr += BDISP_VF_NB; + } + + return 0; +} + +/** + * bdisp_hw_get_hf_addr + * @inc: resize increment + * + * Find the horizontal filter table that fits the resize increment + * + * RETURNS: + * table physical address + */ +static dma_addr_t bdisp_hw_get_hf_addr(u16 inc) +{ + unsigned int i; + + for (i = NB_H_FILTER - 1; i > 0; i--) + if ((bdisp_h_filter[i].min < inc) && + (inc <= bdisp_h_filter[i].max)) + break; + + return bdisp_h_filter[i].paddr; +} + +/** + * bdisp_hw_get_vf_addr + * @inc: resize increment + * + * Find the vertical filter table that fits the resize increment + * + * RETURNS: + * table physical address + */ +static dma_addr_t bdisp_hw_get_vf_addr(u16 inc) +{ + unsigned int i; + + for (i = NB_V_FILTER - 1; i > 0; i--) + if ((bdisp_v_filter[i].min < inc) && + (inc <= bdisp_v_filter[i].max)) + break; + + return bdisp_v_filter[i].paddr; +} + +/** + * bdisp_hw_get_inc + * @from: input size + * @to: output size + * @inc: resize increment in 6.10 format + * + * Computes the increment (inverse of scale) in 6.10 format + * + * RETURNS: + * 0 on success + */ +static int bdisp_hw_get_inc(u32 from, u32 to, u16 *inc) +{ + u32 tmp; + + if (!to) + return -EINVAL; + + if (to == from) { + *inc = 1 << 10; + return 0; + } + + tmp = (from << 10) / to; + if ((tmp > 0xFFFF) || (!tmp)) + /* overflow (downscale x 63) or too small (upscale x 1024) */ + return -EINVAL; + + *inc = (u16)tmp; + + return 0; +} + +/** + * bdisp_hw_get_hv_inc + * @ctx: device context + * @h_inc: horizontal increment + * @v_inc: vertical increment + * + * Computes the horizontal & vertical increments (inverse of scale) + * + * RETURNS: + * 0 on success + */ +static int bdisp_hw_get_hv_inc(struct bdisp_ctx *ctx, u16 *h_inc, u16 *v_inc) +{ + u32 src_w, src_h, dst_w, dst_h; + + src_w = ctx->src.crop.width; + src_h = ctx->src.crop.height; + dst_w = ctx->dst.crop.width; + dst_h = ctx->dst.crop.height; + + if (bdisp_hw_get_inc(src_w, dst_w, h_inc) || + bdisp_hw_get_inc(src_h, dst_h, v_inc)) { + dev_err(ctx->bdisp_dev->dev, + "scale factors failed (%dx%d)->(%dx%d)\n", + src_w, src_h, dst_w, dst_h); + return -EINVAL; + } + + return 0; +} + +/** + * bdisp_hw_get_op_cfg + * @ctx: device context + * @c: operation configuration + * + * Check which blitter operations are expected and sets the scaling increments + * + * RETURNS: + * 0 on success + */ +static int bdisp_hw_get_op_cfg(struct bdisp_ctx *ctx, struct bdisp_op_cfg *c) +{ + struct device *dev = ctx->bdisp_dev->dev; + struct bdisp_frame *src = &ctx->src; + struct bdisp_frame *dst = &ctx->dst; + + if (src->width > MAX_SRC_WIDTH * MAX_VERTICAL_STRIDES) { + dev_err(dev, "Image width out of HW caps\n"); + return -EINVAL; + } + + c->wide = src->width > MAX_SRC_WIDTH; + + c->hflip = ctx->hflip; + c->vflip = ctx->vflip; + + c->src_interlaced = (src->field == V4L2_FIELD_INTERLACED); + + c->src_nbp = src->fmt->nb_planes; + c->src_yuv = (src->fmt->pixelformat == V4L2_PIX_FMT_NV12) || + (src->fmt->pixelformat == V4L2_PIX_FMT_YUV420); + c->src_420 = c->src_yuv; + + c->dst_nbp = dst->fmt->nb_planes; + c->dst_yuv = (dst->fmt->pixelformat == V4L2_PIX_FMT_NV12) || + (dst->fmt->pixelformat == V4L2_PIX_FMT_YUV420); + c->dst_420 = c->dst_yuv; + + c->cconv = (c->src_yuv != c->dst_yuv); + + if (bdisp_hw_get_hv_inc(ctx, &c->h_inc, &c->v_inc)) { + dev_err(dev, "Scale factor out of HW caps\n"); + return -EINVAL; + } + + /* Deinterlacing adjustment : stretch a field to a frame */ + if (c->src_interlaced) + c->v_inc /= 2; + + if ((c->h_inc != (1 << 10)) || (c->v_inc != (1 << 10))) + c->scale = true; + else + c->scale = false; + + return 0; +} + +/** + * bdisp_hw_color_format + * @pixelformat: v4l2 pixel format + * + * v4l2 to bdisp pixel format convert + * + * RETURNS: + * bdisp pixel format + */ +static u32 bdisp_hw_color_format(u32 pixelformat) +{ + u32 ret; + + switch (pixelformat) { + case V4L2_PIX_FMT_YUV420: + ret = (BDISP_YUV_3B << BLT_TTY_COL_SHIFT); + break; + case V4L2_PIX_FMT_NV12: + ret = (BDISP_NV12 << BLT_TTY_COL_SHIFT) | BLT_TTY_BIG_END; + break; + case V4L2_PIX_FMT_RGB565: + ret = (BDISP_RGB565 << BLT_TTY_COL_SHIFT); + break; + case V4L2_PIX_FMT_XBGR32: /* This V4L format actually refers to xRGB */ + ret = (BDISP_XRGB8888 << BLT_TTY_COL_SHIFT); + break; + case V4L2_PIX_FMT_RGB24: /* RGB888 format */ + ret = (BDISP_RGB888 << BLT_TTY_COL_SHIFT) | BLT_TTY_BIG_END; + break; + case V4L2_PIX_FMT_ABGR32: /* This V4L format actually refers to ARGB */ + + default: + ret = (BDISP_ARGB8888 << BLT_TTY_COL_SHIFT) | BLT_TTY_ALPHA_R; + break; + } + + return ret; +} + +/** + * bdisp_hw_build_node + * @ctx: device context + * @cfg: operation configuration + * @node: node to be set + * @t_plan: whether the node refers to a RGB/Y or a CbCr plane + * @src_x_offset: x offset in the source image + * + * Build a node + * + * RETURNS: + * None + */ +static void bdisp_hw_build_node(struct bdisp_ctx *ctx, + struct bdisp_op_cfg *cfg, + struct bdisp_node *node, + enum bdisp_target_plan t_plan, int src_x_offset) +{ + struct bdisp_frame *src = &ctx->src; + struct bdisp_frame *dst = &ctx->dst; + u16 h_inc, v_inc, yh_inc, yv_inc; + struct v4l2_rect src_rect = src->crop; + struct v4l2_rect dst_rect = dst->crop; + int dst_x_offset; + s32 dst_width = dst->crop.width; + u32 src_fmt, dst_fmt; + const u32 *ivmx; + + dev_dbg(ctx->bdisp_dev->dev, "%s\n", __func__); + + memset(node, 0, sizeof(*node)); + + /* Adjust src and dst areas wrt src_x_offset */ + src_rect.left += src_x_offset; + src_rect.width -= src_x_offset; + src_rect.width = min_t(__s32, MAX_SRC_WIDTH, src_rect.width); + + dst_x_offset = (src_x_offset * dst_width) / ctx->src.crop.width; + dst_rect.left += dst_x_offset; + dst_rect.width = (src_rect.width * dst_width) / ctx->src.crop.width; + + /* General */ + src_fmt = src->fmt->pixelformat; + dst_fmt = dst->fmt->pixelformat; + + node->nip = 0; + node->cic = BLT_CIC_ALL_GRP; + node->ack = BLT_ACK_BYPASS_S2S3; + + switch (cfg->src_nbp) { + case 1: + /* Src2 = RGB / Src1 = Src3 = off */ + node->ins = BLT_INS_S1_OFF | BLT_INS_S2_MEM | BLT_INS_S3_OFF; + break; + case 2: + /* Src3 = Y + * Src2 = CbCr or ColorFill if writing the Y plane + * Src1 = off */ + node->ins = BLT_INS_S1_OFF | BLT_INS_S3_MEM; + if (t_plan == BDISP_Y) + node->ins |= BLT_INS_S2_CF; + else + node->ins |= BLT_INS_S2_MEM; + break; + case 3: + default: + /* Src3 = Y + * Src2 = Cb or ColorFill if writing the Y plane + * Src1 = Cr or ColorFill if writing the Y plane */ + node->ins = BLT_INS_S3_MEM; + if (t_plan == BDISP_Y) + node->ins |= BLT_INS_S2_CF | BLT_INS_S1_CF; + else + node->ins |= BLT_INS_S2_MEM | BLT_INS_S1_MEM; + break; + } + + /* Color convert */ + node->ins |= cfg->cconv ? BLT_INS_IVMX : 0; + /* Scale needed if scaling OR 4:2:0 up/downsampling */ + node->ins |= (cfg->scale || cfg->src_420 || cfg->dst_420) ? + BLT_INS_SCALE : 0; + + /* Target */ + node->tba = (t_plan == BDISP_CBCR) ? dst->paddr[1] : dst->paddr[0]; + + node->tty = dst->bytesperline; + node->tty |= bdisp_hw_color_format(dst_fmt); + node->tty |= BLT_TTY_DITHER; + node->tty |= (t_plan == BDISP_CBCR) ? BLT_TTY_CHROMA : 0; + node->tty |= cfg->hflip ? BLT_TTY_HSO : 0; + node->tty |= cfg->vflip ? BLT_TTY_VSO : 0; + + if (cfg->dst_420 && (t_plan == BDISP_CBCR)) { + /* 420 chroma downsampling */ + dst_rect.height /= 2; + dst_rect.width /= 2; + dst_rect.left /= 2; + dst_rect.top /= 2; + dst_x_offset /= 2; + dst_width /= 2; + } + + node->txy = cfg->vflip ? (dst_rect.height - 1) : dst_rect.top; + node->txy <<= 16; + node->txy |= cfg->hflip ? (dst_width - dst_x_offset - 1) : + dst_rect.left; + + node->tsz = dst_rect.height << 16 | dst_rect.width; + + if (cfg->src_interlaced) { + /* handle only the top field which is half height of a frame */ + src_rect.top /= 2; + src_rect.height /= 2; + } + + if (cfg->src_nbp == 1) { + /* Src 2 : RGB */ + node->s2ba = src->paddr[0]; + + node->s2ty = src->bytesperline; + if (cfg->src_interlaced) + node->s2ty *= 2; + + node->s2ty |= bdisp_hw_color_format(src_fmt); + + node->s2xy = src_rect.top << 16 | src_rect.left; + node->s2sz = src_rect.height << 16 | src_rect.width; + } else { + /* Src 2 : Cb or CbCr */ + if (cfg->src_420) { + /* 420 chroma upsampling */ + src_rect.top /= 2; + src_rect.left /= 2; + src_rect.width /= 2; + src_rect.height /= 2; + } + + node->s2ba = src->paddr[1]; + + node->s2ty = src->bytesperline; + if (cfg->src_nbp == 3) + node->s2ty /= 2; + if (cfg->src_interlaced) + node->s2ty *= 2; + + node->s2ty |= bdisp_hw_color_format(src_fmt); + + node->s2xy = src_rect.top << 16 | src_rect.left; + node->s2sz = src_rect.height << 16 | src_rect.width; + + if (cfg->src_nbp == 3) { + /* Src 1 : Cr */ + node->s1ba = src->paddr[2]; + + node->s1ty = node->s2ty; + node->s1xy = node->s2xy; + } + + /* Src 3 : Y */ + node->s3ba = src->paddr[0]; + + node->s3ty = src->bytesperline; + if (cfg->src_interlaced) + node->s3ty *= 2; + node->s3ty |= bdisp_hw_color_format(src_fmt); + + if ((t_plan != BDISP_CBCR) && cfg->src_420) { + /* No chroma upsampling for output RGB / Y plane */ + node->s3xy = node->s2xy * 2; + node->s3sz = node->s2sz * 2; + } else { + /* No need to read Y (Src3) when writing Chroma */ + node->s3ty |= BLT_S3TY_BLANK_ACC; + node->s3xy = node->s2xy; + node->s3sz = node->s2sz; + } + } + + /* Resize (scale OR 4:2:0: chroma up/downsampling) */ + if (node->ins & BLT_INS_SCALE) { + /* no need to compute Y when writing CbCr from RGB input */ + bool skip_y = (t_plan == BDISP_CBCR) && !cfg->src_yuv; + + /* FCTL */ + if (cfg->scale) { + node->fctl = BLT_FCTL_HV_SCALE; + if (!skip_y) + node->fctl |= BLT_FCTL_Y_HV_SCALE; + } else { + node->fctl = BLT_FCTL_HV_SAMPLE; + if (!skip_y) + node->fctl |= BLT_FCTL_Y_HV_SAMPLE; + } + + /* RSF - Chroma may need to be up/downsampled */ + h_inc = cfg->h_inc; + v_inc = cfg->v_inc; + if (!cfg->src_420 && cfg->dst_420 && (t_plan == BDISP_CBCR)) { + /* RGB to 4:2:0 for Chroma: downsample */ + h_inc *= 2; + v_inc *= 2; + } else if (cfg->src_420 && !cfg->dst_420) { + /* 4:2:0: to RGB: upsample*/ + h_inc /= 2; + v_inc /= 2; + } + node->rsf = v_inc << 16 | h_inc; + + /* RZI */ + node->rzi = BLT_RZI_DEFAULT; + + /* Filter table physical addr */ + node->hfp = bdisp_hw_get_hf_addr(h_inc); + node->vfp = bdisp_hw_get_vf_addr(v_inc); + + /* Y version */ + if (!skip_y) { + yh_inc = cfg->h_inc; + yv_inc = cfg->v_inc; + + node->y_rsf = yv_inc << 16 | yh_inc; + node->y_rzi = BLT_RZI_DEFAULT; + node->y_hfp = bdisp_hw_get_hf_addr(yh_inc); + node->y_vfp = bdisp_hw_get_vf_addr(yv_inc); + } + } + + /* Versatile matrix for RGB / YUV conversion */ + if (cfg->cconv) { + ivmx = cfg->src_yuv ? bdisp_yuv_to_rgb : bdisp_rgb_to_yuv; + + node->ivmx0 = ivmx[0]; + node->ivmx1 = ivmx[1]; + node->ivmx2 = ivmx[2]; + node->ivmx3 = ivmx[3]; + } +} + +/** + * bdisp_hw_build_all_nodes + * @ctx: device context + * + * Build all the nodes for the blitter operation + * + * RETURNS: + * 0 on success + */ +static int bdisp_hw_build_all_nodes(struct bdisp_ctx *ctx) +{ + struct bdisp_op_cfg cfg; + unsigned int i, nid = 0; + int src_x_offset = 0; + + for (i = 0; i < MAX_NB_NODE; i++) + if (!ctx->node[i]) { + dev_err(ctx->bdisp_dev->dev, "node %d is null\n", i); + return -EINVAL; + } + + /* Get configuration (scale, flip, ...) */ + if (bdisp_hw_get_op_cfg(ctx, &cfg)) + return -EINVAL; + + /* Split source in vertical strides (HW constraint) */ + for (i = 0; i < MAX_VERTICAL_STRIDES; i++) { + /* Build RGB/Y node and link it to the previous node */ + bdisp_hw_build_node(ctx, &cfg, ctx->node[nid], + cfg.dst_nbp == 1 ? BDISP_RGB : BDISP_Y, + src_x_offset); + if (nid) + ctx->node[nid - 1]->nip = ctx->node_paddr[nid]; + nid++; + + /* Build additional Cb(Cr) node, link it to the previous one */ + if (cfg.dst_nbp > 1) { + bdisp_hw_build_node(ctx, &cfg, ctx->node[nid], + BDISP_CBCR, src_x_offset); + ctx->node[nid - 1]->nip = ctx->node_paddr[nid]; + nid++; + } + + /* Next stride until full width covered */ + src_x_offset += MAX_SRC_WIDTH; + if (src_x_offset >= ctx->src.crop.width) + break; + } + + /* Mark last node as the last */ + ctx->node[nid - 1]->nip = 0; + + return 0; +} + +/** + * bdisp_hw_save_request + * @ctx: device context + * + * Save a copy of the request and of the built nodes + * + * RETURNS: + * None + */ +static void bdisp_hw_save_request(struct bdisp_ctx *ctx) +{ + struct bdisp_node **copy_node = ctx->bdisp_dev->dbg.copy_node; + struct bdisp_request *request = &ctx->bdisp_dev->dbg.copy_request; + struct bdisp_node **node = ctx->node; + int i; + + /* Request copy */ + request->src = ctx->src; + request->dst = ctx->dst; + request->hflip = ctx->hflip; + request->vflip = ctx->vflip; + request->nb_req++; + + /* Nodes copy */ + for (i = 0; i < MAX_NB_NODE; i++) { + /* Allocate memory if not done yet */ + if (!copy_node[i]) { + copy_node[i] = devm_kzalloc(ctx->bdisp_dev->dev, + sizeof(*copy_node[i]), + GFP_ATOMIC); + if (!copy_node[i]) + return; + } + *copy_node[i] = *node[i]; + } +} + +/** + * bdisp_hw_update + * @ctx: device context + * + * Send the request to the HW + * + * RETURNS: + * 0 on success + */ +int bdisp_hw_update(struct bdisp_ctx *ctx) +{ + int ret; + struct bdisp_dev *bdisp = ctx->bdisp_dev; + struct device *dev = bdisp->dev; + unsigned int node_id; + + dev_dbg(dev, "%s\n", __func__); + + /* build nodes */ + ret = bdisp_hw_build_all_nodes(ctx); + if (ret) { + dev_err(dev, "cannot build nodes (%d)\n", ret); + return ret; + } + + /* Save a copy of the request */ + bdisp_hw_save_request(ctx); + + /* Configure interrupt to 'Last Node Reached for AQ1' */ + writel(BLT_AQ1_CTL_CFG, bdisp->regs + BLT_AQ1_CTL); + writel(BLT_ITS_AQ1_LNA, bdisp->regs + BLT_ITM0); + + /* Write first node addr */ + writel(ctx->node_paddr[0], bdisp->regs + BLT_AQ1_IP); + + /* Find and write last node addr : this starts the HW processing */ + for (node_id = 0; node_id < MAX_NB_NODE - 1; node_id++) { + if (!ctx->node[node_id]->nip) + break; + } + writel(ctx->node_paddr[node_id], bdisp->regs + BLT_AQ1_LNA); + + return 0; +} diff --git a/drivers/media/platform/st/sti/bdisp/bdisp-reg.h b/drivers/media/platform/st/sti/bdisp/bdisp-reg.h new file mode 100644 index 000000000..b07ecc903 --- /dev/null +++ b/drivers/media/platform/st/sti/bdisp/bdisp-reg.h @@ -0,0 +1,235 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne for STMicroelectronics. + */ + +struct bdisp_node { + /* 0 - General */ + u32 nip; + u32 cic; + u32 ins; + u32 ack; + /* 1 - Target */ + u32 tba; + u32 tty; + u32 txy; + u32 tsz; + /* 2 - Color Fill */ + u32 s1cf; + u32 s2cf; + /* 3 - Source 1 */ + u32 s1ba; + u32 s1ty; + u32 s1xy; + u32 s1sz_tsz; + /* 4 - Source 2 */ + u32 s2ba; + u32 s2ty; + u32 s2xy; + u32 s2sz; + /* 5 - Source 3 */ + u32 s3ba; + u32 s3ty; + u32 s3xy; + u32 s3sz; + /* 6 - Clipping */ + u32 cwo; + u32 cws; + /* 7 - CLUT */ + u32 cco; + u32 cml; + /* 8 - Filter & Mask */ + u32 fctl; + u32 pmk; + /* 9 - Chroma Filter */ + u32 rsf; + u32 rzi; + u32 hfp; + u32 vfp; + /* 10 - Luma Filter */ + u32 y_rsf; + u32 y_rzi; + u32 y_hfp; + u32 y_vfp; + /* 11 - Flicker */ + u32 ff0; + u32 ff1; + u32 ff2; + u32 ff3; + /* 12 - Color Key */ + u32 key1; + u32 key2; + /* 14 - Static Address & User */ + u32 sar; + u32 usr; + /* 15 - Input Versatile Matrix */ + u32 ivmx0; + u32 ivmx1; + u32 ivmx2; + u32 ivmx3; + /* 16 - Output Versatile Matrix */ + u32 ovmx0; + u32 ovmx1; + u32 ovmx2; + u32 ovmx3; + /* 17 - Pace */ + u32 pace; + /* 18 - VC1R & DEI */ + u32 vc1r; + u32 dei; + /* 19 - Gradient Fill */ + u32 hgf; + u32 vgf; +}; + +/* HW registers : static */ +#define BLT_CTL 0x0A00 +#define BLT_ITS 0x0A04 +#define BLT_STA1 0x0A08 +#define BLT_AQ1_CTL 0x0A60 +#define BLT_AQ1_IP 0x0A64 +#define BLT_AQ1_LNA 0x0A68 +#define BLT_AQ1_STA 0x0A6C +#define BLT_ITM0 0x0AD0 +/* HW registers : plugs */ +#define BLT_PLUGS1_OP2 0x0B04 +#define BLT_PLUGS1_CHZ 0x0B08 +#define BLT_PLUGS1_MSZ 0x0B0C +#define BLT_PLUGS1_PGZ 0x0B10 +#define BLT_PLUGS2_OP2 0x0B24 +#define BLT_PLUGS2_CHZ 0x0B28 +#define BLT_PLUGS2_MSZ 0x0B2C +#define BLT_PLUGS2_PGZ 0x0B30 +#define BLT_PLUGS3_OP2 0x0B44 +#define BLT_PLUGS3_CHZ 0x0B48 +#define BLT_PLUGS3_MSZ 0x0B4C +#define BLT_PLUGS3_PGZ 0x0B50 +#define BLT_PLUGT_OP2 0x0B84 +#define BLT_PLUGT_CHZ 0x0B88 +#define BLT_PLUGT_MSZ 0x0B8C +#define BLT_PLUGT_PGZ 0x0B90 +/* HW registers : node */ +#define BLT_NIP 0x0C00 +#define BLT_CIC 0x0C04 +#define BLT_INS 0x0C08 +#define BLT_ACK 0x0C0C +#define BLT_TBA 0x0C10 +#define BLT_TTY 0x0C14 +#define BLT_TXY 0x0C18 +#define BLT_TSZ 0x0C1C +#define BLT_S1BA 0x0C28 +#define BLT_S1TY 0x0C2C +#define BLT_S1XY 0x0C30 +#define BLT_S2BA 0x0C38 +#define BLT_S2TY 0x0C3C +#define BLT_S2XY 0x0C40 +#define BLT_S2SZ 0x0C44 +#define BLT_S3BA 0x0C48 +#define BLT_S3TY 0x0C4C +#define BLT_S3XY 0x0C50 +#define BLT_S3SZ 0x0C54 +#define BLT_FCTL 0x0C68 +#define BLT_RSF 0x0C70 +#define BLT_RZI 0x0C74 +#define BLT_HFP 0x0C78 +#define BLT_VFP 0x0C7C +#define BLT_Y_RSF 0x0C80 +#define BLT_Y_RZI 0x0C84 +#define BLT_Y_HFP 0x0C88 +#define BLT_Y_VFP 0x0C8C +#define BLT_IVMX0 0x0CC0 +#define BLT_IVMX1 0x0CC4 +#define BLT_IVMX2 0x0CC8 +#define BLT_IVMX3 0x0CCC +#define BLT_OVMX0 0x0CD0 +#define BLT_OVMX1 0x0CD4 +#define BLT_OVMX2 0x0CD8 +#define BLT_OVMX3 0x0CDC +#define BLT_DEI 0x0CEC +/* HW registers : filters */ +#define BLT_HFC_N 0x0D00 +#define BLT_VFC_N 0x0D90 +#define BLT_Y_HFC_N 0x0E00 +#define BLT_Y_VFC_N 0x0E90 +#define BLT_NB_H_COEF 16 +#define BLT_NB_V_COEF 10 + +/* Registers values */ +#define BLT_CTL_RESET BIT(31) /* Global soft reset */ + +#define BLT_ITS_AQ1_LNA BIT(12) /* AQ1 LNA reached */ + +#define BLT_STA1_IDLE BIT(0) /* BDISP idle */ + +#define BLT_AQ1_CTL_CFG 0x80400003 /* Enable, P3, LNA reached */ + +#define BLT_INS_S1_MASK (BIT(0) | BIT(1) | BIT(2)) +#define BLT_INS_S1_OFF 0x00000000 /* src1 disabled */ +#define BLT_INS_S1_MEM 0x00000001 /* src1 fetched from memory */ +#define BLT_INS_S1_CF 0x00000003 /* src1 color fill */ +#define BLT_INS_S1_COPY 0x00000004 /* src1 direct copy */ +#define BLT_INS_S1_FILL 0x00000007 /* src1 firect fill */ +#define BLT_INS_S2_MASK (BIT(3) | BIT(4)) +#define BLT_INS_S2_OFF 0x00000000 /* src2 disabled */ +#define BLT_INS_S2_MEM 0x00000008 /* src2 fetched from memory */ +#define BLT_INS_S2_CF 0x00000018 /* src2 color fill */ +#define BLT_INS_S3_MASK BIT(5) +#define BLT_INS_S3_OFF 0x00000000 /* src3 disabled */ +#define BLT_INS_S3_MEM 0x00000020 /* src3 fetched from memory */ +#define BLT_INS_IVMX BIT(6) /* Input versatile matrix */ +#define BLT_INS_CLUT BIT(7) /* Color Look Up Table */ +#define BLT_INS_SCALE BIT(8) /* Scaling */ +#define BLT_INS_FLICK BIT(9) /* Flicker filter */ +#define BLT_INS_CLIP BIT(10) /* Clipping */ +#define BLT_INS_CKEY BIT(11) /* Color key */ +#define BLT_INS_OVMX BIT(12) /* Output versatile matrix */ +#define BLT_INS_DEI BIT(13) /* Deinterlace */ +#define BLT_INS_PMASK BIT(14) /* Plane mask */ +#define BLT_INS_VC1R BIT(17) /* VC1 Range mapping */ +#define BLT_INS_ROTATE BIT(18) /* Rotation */ +#define BLT_INS_GRAD BIT(19) /* Gradient fill */ +#define BLT_INS_AQLOCK BIT(29) /* AQ lock */ +#define BLT_INS_PACE BIT(30) /* Pace down */ +#define BLT_INS_IRQ BIT(31) /* Raise IRQ when node done */ +#define BLT_CIC_ALL_GRP 0x000FDFFC /* all valid groups present */ +#define BLT_ACK_BYPASS_S2S3 0x00000007 /* Bypass src2 and src3 */ + +#define BLT_TTY_COL_SHIFT 16 /* Color format */ +#define BLT_TTY_COL_MASK 0x001F0000 /* Color format mask */ +#define BLT_TTY_ALPHA_R BIT(21) /* Alpha range */ +#define BLT_TTY_CR_NOT_CB BIT(22) /* CR not Cb */ +#define BLT_TTY_MB BIT(23) /* MB frame / field*/ +#define BLT_TTY_HSO BIT(24) /* H scan order */ +#define BLT_TTY_VSO BIT(25) /* V scan order */ +#define BLT_TTY_DITHER BIT(26) /* Dithering */ +#define BLT_TTY_CHROMA BIT(27) /* Write chroma / luma */ +#define BLT_TTY_BIG_END BIT(30) /* Big endianness */ + +#define BLT_S1TY_A1_SUBSET BIT(22) /* A1 subset */ +#define BLT_S1TY_CHROMA_EXT BIT(26) /* Chroma Extended */ +#define BTL_S1TY_SUBBYTE BIT(28) /* Sub-byte fmt, pixel order */ +#define BLT_S1TY_RGB_EXP BIT(29) /* RGB expansion mode */ + +#define BLT_S2TY_A1_SUBSET BIT(22) /* A1 subset */ +#define BLT_S2TY_CHROMA_EXT BIT(26) /* Chroma Extended */ +#define BTL_S2TY_SUBBYTE BIT(28) /* Sub-byte fmt, pixel order */ +#define BLT_S2TY_RGB_EXP BIT(29) /* RGB expansion mode */ + +#define BLT_S3TY_BLANK_ACC BIT(26) /* Blank access */ + +#define BLT_FCTL_HV_SCALE 0x00000055 /* H/V resize + color filter */ +#define BLT_FCTL_Y_HV_SCALE 0x33000000 /* Luma version */ + +#define BLT_FCTL_HV_SAMPLE 0x00000044 /* H/V resize */ +#define BLT_FCTL_Y_HV_SAMPLE 0x22000000 /* Luma version */ + +#define BLT_RZI_DEFAULT 0x20003000 /* H/VNB_repeat = 3/2 */ + +/* Color format */ +#define BDISP_RGB565 0x00 /* RGB565 */ +#define BDISP_RGB888 0x01 /* RGB888 */ +#define BDISP_XRGB8888 0x02 /* RGB888_32 */ +#define BDISP_ARGB8888 0x05 /* ARGB888 */ +#define BDISP_NV12 0x16 /* YCbCr42x R2B */ +#define BDISP_YUV_3B 0x1E /* YUV (3 buffer) */ diff --git a/drivers/media/platform/st/sti/bdisp/bdisp-v4l2.c b/drivers/media/platform/st/sti/bdisp/bdisp-v4l2.c new file mode 100644 index 000000000..080da254b --- /dev/null +++ b/drivers/media/platform/st/sti/bdisp/bdisp-v4l2.c @@ -0,0 +1,1428 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne for STMicroelectronics. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "bdisp.h" + +#define BDISP_MAX_CTRL_NUM 10 + +#define BDISP_WORK_TIMEOUT ((100 * HZ) / 1000) + +/* User configuration change */ +#define BDISP_PARAMS BIT(0) /* Config updated */ +#define BDISP_SRC_FMT BIT(1) /* Source set */ +#define BDISP_DST_FMT BIT(2) /* Destination set */ +#define BDISP_CTX_STOP_REQ BIT(3) /* Stop request */ +#define BDISP_CTX_ABORT BIT(4) /* Abort while device run */ + +#define BDISP_MIN_W 1 +#define BDISP_MAX_W 8191 +#define BDISP_MIN_H 1 +#define BDISP_MAX_H 8191 + +#define fh_to_ctx(__fh) container_of(__fh, struct bdisp_ctx, fh) + +enum bdisp_dev_flags { + ST_M2M_OPEN, /* Driver opened */ + ST_M2M_RUNNING, /* HW device running */ + ST_M2M_SUSPENDED, /* Driver suspended */ + ST_M2M_SUSPENDING, /* Driver being suspended */ +}; + +static const struct bdisp_fmt bdisp_formats[] = { + /* ARGB888. [31:0] A:R:G:B 8:8:8:8 little endian */ + { + .pixelformat = V4L2_PIX_FMT_ABGR32, /* is actually ARGB */ + .nb_planes = 1, + .bpp = 32, + .bpp_plane0 = 32, + .w_align = 1, + .h_align = 1 + }, + /* XRGB888. [31:0] x:R:G:B 8:8:8:8 little endian */ + { + .pixelformat = V4L2_PIX_FMT_XBGR32, /* is actually xRGB */ + .nb_planes = 1, + .bpp = 32, + .bpp_plane0 = 32, + .w_align = 1, + .h_align = 1 + }, + /* RGB565. [15:0] R:G:B 5:6:5 little endian */ + { + .pixelformat = V4L2_PIX_FMT_RGB565, + .nb_planes = 1, + .bpp = 16, + .bpp_plane0 = 16, + .w_align = 1, + .h_align = 1 + }, + /* NV12. YUV420SP - 1 plane for Y + 1 plane for (CbCr) */ + { + .pixelformat = V4L2_PIX_FMT_NV12, + .nb_planes = 2, + .bpp = 12, + .bpp_plane0 = 8, + .w_align = 2, + .h_align = 2 + }, + /* RGB888. [23:0] B:G:R 8:8:8 little endian */ + { + .pixelformat = V4L2_PIX_FMT_RGB24, + .nb_planes = 1, + .bpp = 24, + .bpp_plane0 = 24, + .w_align = 1, + .h_align = 1 + }, + /* YU12. YUV420P - 1 plane for Y + 1 plane for Cb + 1 plane for Cr + * To keep as the LAST element of this table (no support on capture) + */ + { + .pixelformat = V4L2_PIX_FMT_YUV420, + .nb_planes = 3, + .bpp = 12, + .bpp_plane0 = 8, + .w_align = 2, + .h_align = 2 + } +}; + +/* Default format : HD ARGB32*/ +#define BDISP_DEF_WIDTH 1920 +#define BDISP_DEF_HEIGHT 1080 + +static const struct bdisp_frame bdisp_dflt_fmt = { + .width = BDISP_DEF_WIDTH, + .height = BDISP_DEF_HEIGHT, + .fmt = &bdisp_formats[0], + .field = V4L2_FIELD_NONE, + .bytesperline = BDISP_DEF_WIDTH * 4, + .sizeimage = BDISP_DEF_WIDTH * BDISP_DEF_HEIGHT * 4, + .colorspace = V4L2_COLORSPACE_REC709, + .crop = {0, 0, BDISP_DEF_WIDTH, BDISP_DEF_HEIGHT}, + .paddr = {0, 0, 0, 0} +}; + +static inline void bdisp_ctx_state_lock_set(u32 state, struct bdisp_ctx *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->bdisp_dev->slock, flags); + ctx->state |= state; + spin_unlock_irqrestore(&ctx->bdisp_dev->slock, flags); +} + +static inline void bdisp_ctx_state_lock_clear(u32 state, struct bdisp_ctx *ctx) +{ + unsigned long flags; + + spin_lock_irqsave(&ctx->bdisp_dev->slock, flags); + ctx->state &= ~state; + spin_unlock_irqrestore(&ctx->bdisp_dev->slock, flags); +} + +static inline bool bdisp_ctx_state_is_set(u32 mask, struct bdisp_ctx *ctx) +{ + unsigned long flags; + bool ret; + + spin_lock_irqsave(&ctx->bdisp_dev->slock, flags); + ret = (ctx->state & mask) == mask; + spin_unlock_irqrestore(&ctx->bdisp_dev->slock, flags); + + return ret; +} + +static const struct bdisp_fmt *bdisp_find_fmt(u32 pixelformat) +{ + const struct bdisp_fmt *fmt; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(bdisp_formats); i++) { + fmt = &bdisp_formats[i]; + if (fmt->pixelformat == pixelformat) + return fmt; + } + + return NULL; +} + +static struct bdisp_frame *ctx_get_frame(struct bdisp_ctx *ctx, + enum v4l2_buf_type type) +{ + switch (type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + return &ctx->src; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + return &ctx->dst; + default: + dev_err(ctx->bdisp_dev->dev, + "Wrong buffer/video queue type (%d)\n", type); + break; + } + + return ERR_PTR(-EINVAL); +} + +static void bdisp_job_finish(struct bdisp_ctx *ctx, int vb_state) +{ + struct vb2_v4l2_buffer *src_vb, *dst_vb; + + if (WARN(!ctx || !ctx->fh.m2m_ctx, "Null hardware context\n")) + return; + + dev_dbg(ctx->bdisp_dev->dev, "%s\n", __func__); + + src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (src_vb && dst_vb) { + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + dst_vb->timecode = src_vb->timecode; + dst_vb->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + dst_vb->flags |= src_vb->flags & + V4L2_BUF_FLAG_TSTAMP_SRC_MASK; + + v4l2_m2m_buf_done(src_vb, vb_state); + v4l2_m2m_buf_done(dst_vb, vb_state); + + v4l2_m2m_job_finish(ctx->bdisp_dev->m2m.m2m_dev, + ctx->fh.m2m_ctx); + } +} + +static int bdisp_ctx_stop_req(struct bdisp_ctx *ctx) +{ + struct bdisp_ctx *curr_ctx; + struct bdisp_dev *bdisp = ctx->bdisp_dev; + int ret; + + dev_dbg(ctx->bdisp_dev->dev, "%s\n", __func__); + + cancel_delayed_work(&bdisp->timeout_work); + + curr_ctx = v4l2_m2m_get_curr_priv(bdisp->m2m.m2m_dev); + if (!test_bit(ST_M2M_RUNNING, &bdisp->state) || (curr_ctx != ctx)) + return 0; + + bdisp_ctx_state_lock_set(BDISP_CTX_STOP_REQ, ctx); + + ret = wait_event_timeout(bdisp->irq_queue, + !bdisp_ctx_state_is_set(BDISP_CTX_STOP_REQ, ctx), + BDISP_WORK_TIMEOUT); + + if (!ret) { + dev_err(ctx->bdisp_dev->dev, "%s IRQ timeout\n", __func__); + return -ETIMEDOUT; + } + + return 0; +} + +static void __bdisp_job_abort(struct bdisp_ctx *ctx) +{ + int ret; + + ret = bdisp_ctx_stop_req(ctx); + if ((ret == -ETIMEDOUT) || (ctx->state & BDISP_CTX_ABORT)) { + bdisp_ctx_state_lock_clear(BDISP_CTX_STOP_REQ | BDISP_CTX_ABORT, + ctx); + bdisp_job_finish(ctx, VB2_BUF_STATE_ERROR); + } +} + +static void bdisp_job_abort(void *priv) +{ + __bdisp_job_abort((struct bdisp_ctx *)priv); +} + +static int bdisp_get_addr(struct bdisp_ctx *ctx, struct vb2_buffer *vb, + struct bdisp_frame *frame, dma_addr_t *paddr) +{ + if (!vb || !frame) + return -EINVAL; + + paddr[0] = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (frame->fmt->nb_planes > 1) + /* UV (NV12) or U (420P) */ + paddr[1] = (dma_addr_t)(paddr[0] + + frame->bytesperline * frame->height); + + if (frame->fmt->nb_planes > 2) + /* V (420P) */ + paddr[2] = (dma_addr_t)(paddr[1] + + (frame->bytesperline * frame->height) / 4); + + if (frame->fmt->nb_planes > 3) + dev_dbg(ctx->bdisp_dev->dev, "ignoring some planes\n"); + + dev_dbg(ctx->bdisp_dev->dev, + "%s plane[0]=%pad plane[1]=%pad plane[2]=%pad\n", + __func__, &paddr[0], &paddr[1], &paddr[2]); + + return 0; +} + +static int bdisp_get_bufs(struct bdisp_ctx *ctx) +{ + struct bdisp_frame *src, *dst; + struct vb2_v4l2_buffer *src_vb, *dst_vb; + int ret; + + src = &ctx->src; + dst = &ctx->dst; + + src_vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + ret = bdisp_get_addr(ctx, &src_vb->vb2_buf, src, src->paddr); + if (ret) + return ret; + + dst_vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + ret = bdisp_get_addr(ctx, &dst_vb->vb2_buf, dst, dst->paddr); + if (ret) + return ret; + + dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp; + + return 0; +} + +static void bdisp_device_run(void *priv) +{ + struct bdisp_ctx *ctx = priv; + struct bdisp_dev *bdisp; + unsigned long flags; + int err = 0; + + if (WARN(!ctx, "Null hardware context\n")) + return; + + bdisp = ctx->bdisp_dev; + dev_dbg(bdisp->dev, "%s\n", __func__); + spin_lock_irqsave(&bdisp->slock, flags); + + if (bdisp->m2m.ctx != ctx) { + dev_dbg(bdisp->dev, "ctx updated: %p -> %p\n", + bdisp->m2m.ctx, ctx); + ctx->state |= BDISP_PARAMS; + bdisp->m2m.ctx = ctx; + } + + if (ctx->state & BDISP_CTX_STOP_REQ) { + ctx->state &= ~BDISP_CTX_STOP_REQ; + ctx->state |= BDISP_CTX_ABORT; + wake_up(&bdisp->irq_queue); + goto out; + } + + err = bdisp_get_bufs(ctx); + if (err) { + dev_err(bdisp->dev, "cannot get address\n"); + goto out; + } + + bdisp_dbg_perf_begin(bdisp); + + err = bdisp_hw_reset(bdisp); + if (err) { + dev_err(bdisp->dev, "could not get HW ready\n"); + goto out; + } + + err = bdisp_hw_update(ctx); + if (err) { + dev_err(bdisp->dev, "could not send HW request\n"); + goto out; + } + + queue_delayed_work(bdisp->work_queue, &bdisp->timeout_work, + BDISP_WORK_TIMEOUT); + set_bit(ST_M2M_RUNNING, &bdisp->state); +out: + ctx->state &= ~BDISP_PARAMS; + spin_unlock_irqrestore(&bdisp->slock, flags); + if (err) + bdisp_job_finish(ctx, VB2_BUF_STATE_ERROR); +} + +static const struct v4l2_m2m_ops bdisp_m2m_ops = { + .device_run = bdisp_device_run, + .job_abort = bdisp_job_abort, +}; + +static int __bdisp_s_ctrl(struct bdisp_ctx *ctx, struct v4l2_ctrl *ctrl) +{ + if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE) + return 0; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->hflip = ctrl->val; + break; + case V4L2_CID_VFLIP: + ctx->vflip = ctrl->val; + break; + default: + dev_err(ctx->bdisp_dev->dev, "unknown control %d\n", ctrl->id); + return -EINVAL; + } + + ctx->state |= BDISP_PARAMS; + + return 0; +} + +static int bdisp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct bdisp_ctx *ctx = container_of(ctrl->handler, struct bdisp_ctx, + ctrl_handler); + unsigned long flags; + int ret; + + spin_lock_irqsave(&ctx->bdisp_dev->slock, flags); + ret = __bdisp_s_ctrl(ctx, ctrl); + spin_unlock_irqrestore(&ctx->bdisp_dev->slock, flags); + + return ret; +} + +static const struct v4l2_ctrl_ops bdisp_c_ops = { + .s_ctrl = bdisp_s_ctrl, +}; + +static int bdisp_ctrls_create(struct bdisp_ctx *ctx) +{ + if (ctx->ctrls_rdy) + return 0; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, BDISP_MAX_CTRL_NUM); + + ctx->bdisp_ctrls.hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &bdisp_c_ops, V4L2_CID_HFLIP, 0, 1, 1, 0); + ctx->bdisp_ctrls.vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler, + &bdisp_c_ops, V4L2_CID_VFLIP, 0, 1, 1, 0); + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; + } + + ctx->ctrls_rdy = true; + + return 0; +} + +static void bdisp_ctrls_delete(struct bdisp_ctx *ctx) +{ + if (ctx->ctrls_rdy) { + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + ctx->ctrls_rdy = false; + } +} + +static int bdisp_queue_setup(struct vb2_queue *vq, + unsigned int *nb_buf, unsigned int *nb_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct bdisp_ctx *ctx = vb2_get_drv_priv(vq); + struct bdisp_frame *frame = ctx_get_frame(ctx, vq->type); + + if (IS_ERR(frame)) { + dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); + return PTR_ERR(frame); + } + + if (!frame->fmt) { + dev_err(ctx->bdisp_dev->dev, "Invalid format\n"); + return -EINVAL; + } + + if (*nb_planes) + return sizes[0] < frame->sizeimage ? -EINVAL : 0; + + *nb_planes = 1; + sizes[0] = frame->sizeimage; + + return 0; +} + +static int bdisp_buf_prepare(struct vb2_buffer *vb) +{ + struct bdisp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct bdisp_frame *frame = ctx_get_frame(ctx, vb->vb2_queue->type); + + if (IS_ERR(frame)) { + dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); + return PTR_ERR(frame); + } + + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + vb2_set_plane_payload(vb, 0, frame->sizeimage); + + return 0; +} + +static void bdisp_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct bdisp_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + /* return to V4L2 any 0-size buffer so it can be dequeued by user */ + if (!vb2_get_plane_payload(vb, 0)) { + dev_dbg(ctx->bdisp_dev->dev, "0 data buffer, skip it\n"); + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + return; + } + + if (ctx->fh.m2m_ctx) + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int bdisp_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct bdisp_ctx *ctx = q->drv_priv; + struct vb2_v4l2_buffer *buf; + int ret = pm_runtime_resume_and_get(ctx->bdisp_dev->dev); + + if (ret < 0) { + dev_err(ctx->bdisp_dev->dev, "failed to set runtime PM\n"); + + if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + while ((buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + } else { + while ((buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(buf, VB2_BUF_STATE_QUEUED); + } + + return ret; + } + + return 0; +} + +static void bdisp_stop_streaming(struct vb2_queue *q) +{ + struct bdisp_ctx *ctx = q->drv_priv; + + __bdisp_job_abort(ctx); + + pm_runtime_put(ctx->bdisp_dev->dev); +} + +static const struct vb2_ops bdisp_qops = { + .queue_setup = bdisp_queue_setup, + .buf_prepare = bdisp_buf_prepare, + .buf_queue = bdisp_buf_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = bdisp_stop_streaming, + .start_streaming = bdisp_start_streaming, +}; + +static int queue_init(void *priv, + struct vb2_queue *src_vq, struct vb2_queue *dst_vq) +{ + struct bdisp_ctx *ctx = priv; + int ret; + + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->ops = &bdisp_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->bdisp_dev->lock; + src_vq->dev = ctx->bdisp_dev->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->ops = &bdisp_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->bdisp_dev->lock; + dst_vq->dev = ctx->bdisp_dev->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +static int bdisp_open(struct file *file) +{ + struct bdisp_dev *bdisp = video_drvdata(file); + struct bdisp_ctx *ctx = NULL; + int ret; + + if (mutex_lock_interruptible(&bdisp->lock)) + return -ERESTARTSYS; + + /* Allocate memory for both context and node */ + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto unlock; + } + ctx->bdisp_dev = bdisp; + + if (bdisp_hw_alloc_nodes(ctx)) { + dev_err(bdisp->dev, "no memory for nodes\n"); + ret = -ENOMEM; + goto mem_ctx; + } + + v4l2_fh_init(&ctx->fh, bdisp->m2m.vdev); + + ret = bdisp_ctrls_create(ctx); + if (ret) { + dev_err(bdisp->dev, "Failed to create control\n"); + goto error_fh; + } + + /* Use separate control handler per file handle */ + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + /* Default format */ + ctx->src = bdisp_dflt_fmt; + ctx->dst = bdisp_dflt_fmt; + + /* Setup the device context for mem2mem mode. */ + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(bdisp->m2m.m2m_dev, ctx, + queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + dev_err(bdisp->dev, "Failed to initialize m2m context\n"); + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto error_ctrls; + } + + bdisp->m2m.refcnt++; + set_bit(ST_M2M_OPEN, &bdisp->state); + + dev_dbg(bdisp->dev, "driver opened, ctx = 0x%p\n", ctx); + + mutex_unlock(&bdisp->lock); + + return 0; + +error_ctrls: + bdisp_ctrls_delete(ctx); + v4l2_fh_del(&ctx->fh); +error_fh: + v4l2_fh_exit(&ctx->fh); + bdisp_hw_free_nodes(ctx); +mem_ctx: + kfree(ctx); +unlock: + mutex_unlock(&bdisp->lock); + + return ret; +} + +static int bdisp_release(struct file *file) +{ + struct bdisp_ctx *ctx = fh_to_ctx(file->private_data); + struct bdisp_dev *bdisp = ctx->bdisp_dev; + + dev_dbg(bdisp->dev, "%s\n", __func__); + + mutex_lock(&bdisp->lock); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + bdisp_ctrls_delete(ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + if (--bdisp->m2m.refcnt <= 0) + clear_bit(ST_M2M_OPEN, &bdisp->state); + + bdisp_hw_free_nodes(ctx); + + kfree(ctx); + + mutex_unlock(&bdisp->lock); + + return 0; +} + +static const struct v4l2_file_operations bdisp_fops = { + .owner = THIS_MODULE, + .open = bdisp_open, + .release = bdisp_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static int bdisp_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct bdisp_ctx *ctx = fh_to_ctx(fh); + struct bdisp_dev *bdisp = ctx->bdisp_dev; + + strscpy(cap->driver, bdisp->pdev->name, sizeof(cap->driver)); + strscpy(cap->card, bdisp->pdev->name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s%d", + BDISP_NAME, bdisp->id); + return 0; +} + +static int bdisp_enum_fmt(struct file *file, void *fh, struct v4l2_fmtdesc *f) +{ + struct bdisp_ctx *ctx = fh_to_ctx(fh); + const struct bdisp_fmt *fmt; + + if (f->index >= ARRAY_SIZE(bdisp_formats)) + return -EINVAL; + + fmt = &bdisp_formats[f->index]; + + if ((fmt->pixelformat == V4L2_PIX_FMT_YUV420) && + (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)) { + dev_dbg(ctx->bdisp_dev->dev, "No YU12 on capture\n"); + return -EINVAL; + } + f->pixelformat = fmt->pixelformat; + + return 0; +} + +static int bdisp_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct bdisp_ctx *ctx = fh_to_ctx(fh); + struct v4l2_pix_format *pix; + struct bdisp_frame *frame = ctx_get_frame(ctx, f->type); + + if (IS_ERR(frame)) { + dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); + return PTR_ERR(frame); + } + + pix = &f->fmt.pix; + pix->width = frame->width; + pix->height = frame->height; + pix->pixelformat = frame->fmt->pixelformat; + pix->field = frame->field; + pix->bytesperline = frame->bytesperline; + pix->sizeimage = frame->sizeimage; + pix->colorspace = (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? + frame->colorspace : bdisp_dflt_fmt.colorspace; + + return 0; +} + +static int bdisp_try_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct bdisp_ctx *ctx = fh_to_ctx(fh); + struct v4l2_pix_format *pix = &f->fmt.pix; + const struct bdisp_fmt *format; + u32 in_w, in_h; + + format = bdisp_find_fmt(pix->pixelformat); + if (!format) { + dev_dbg(ctx->bdisp_dev->dev, "Unknown format 0x%x\n", + pix->pixelformat); + return -EINVAL; + } + + /* YUV420P only supported for VIDEO_OUTPUT */ + if ((format->pixelformat == V4L2_PIX_FMT_YUV420) && + (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)) { + dev_dbg(ctx->bdisp_dev->dev, "No YU12 on capture\n"); + return -EINVAL; + } + + /* Field (interlaced only supported on OUTPUT) */ + if ((f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) || + (pix->field != V4L2_FIELD_INTERLACED)) + pix->field = V4L2_FIELD_NONE; + + /* Adjust width & height */ + in_w = pix->width; + in_h = pix->height; + v4l_bound_align_image(&pix->width, + BDISP_MIN_W, BDISP_MAX_W, + ffs(format->w_align) - 1, + &pix->height, + BDISP_MIN_H, BDISP_MAX_H, + ffs(format->h_align) - 1, + 0); + if ((pix->width != in_w) || (pix->height != in_h)) + dev_dbg(ctx->bdisp_dev->dev, + "%s size updated: %dx%d -> %dx%d\n", __func__, + in_w, in_h, pix->width, pix->height); + + pix->bytesperline = (pix->width * format->bpp_plane0) / 8; + pix->sizeimage = (pix->width * pix->height * format->bpp) / 8; + + if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + pix->colorspace = bdisp_dflt_fmt.colorspace; + + return 0; +} + +static int bdisp_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct bdisp_ctx *ctx = fh_to_ctx(fh); + struct vb2_queue *vq; + struct bdisp_frame *frame; + struct v4l2_pix_format *pix; + int ret; + u32 state; + + ret = bdisp_try_fmt(file, fh, f); + if (ret) { + dev_err(ctx->bdisp_dev->dev, "Cannot set format\n"); + return ret; + } + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_streaming(vq)) { + dev_err(ctx->bdisp_dev->dev, "queue (%d) busy\n", f->type); + return -EBUSY; + } + + frame = (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? + &ctx->src : &ctx->dst; + pix = &f->fmt.pix; + frame->fmt = bdisp_find_fmt(pix->pixelformat); + if (!frame->fmt) { + dev_err(ctx->bdisp_dev->dev, "Unknown format 0x%x\n", + pix->pixelformat); + return -EINVAL; + } + + frame->width = pix->width; + frame->height = pix->height; + frame->bytesperline = pix->bytesperline; + frame->sizeimage = pix->sizeimage; + frame->field = pix->field; + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + frame->colorspace = pix->colorspace; + + frame->crop.width = frame->width; + frame->crop.height = frame->height; + frame->crop.left = 0; + frame->crop.top = 0; + + state = BDISP_PARAMS; + state |= (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) ? + BDISP_DST_FMT : BDISP_SRC_FMT; + bdisp_ctx_state_lock_set(state, ctx); + + return 0; +} + +static int bdisp_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct bdisp_frame *frame; + struct bdisp_ctx *ctx = fh_to_ctx(fh); + + frame = ctx_get_frame(ctx, s->type); + if (IS_ERR(frame)) { + dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); + return PTR_ERR(frame); + } + + switch (s->type) { + case V4L2_BUF_TYPE_VIDEO_OUTPUT: + switch (s->target) { + case V4L2_SEL_TGT_CROP: + /* cropped frame */ + s->r = frame->crop; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + /* complete frame */ + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->width; + s->r.height = frame->height; + break; + default: + dev_err(ctx->bdisp_dev->dev, "Invalid target\n"); + return -EINVAL; + } + break; + + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_PADDED: + /* composed (cropped) frame */ + s->r = frame->crop; + break; + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + /* complete frame */ + s->r.left = 0; + s->r.top = 0; + s->r.width = frame->width; + s->r.height = frame->height; + break; + default: + dev_err(ctx->bdisp_dev->dev, "Invalid target\n"); + return -EINVAL; + } + break; + + default: + dev_err(ctx->bdisp_dev->dev, "Invalid type\n"); + return -EINVAL; + } + + return 0; +} + +static int is_rect_enclosed(struct v4l2_rect *a, struct v4l2_rect *b) +{ + /* Return 1 if a is enclosed in b, or 0 otherwise. */ + + if (a->left < b->left || a->top < b->top) + return 0; + + if (a->left + a->width > b->left + b->width) + return 0; + + if (a->top + a->height > b->top + b->height) + return 0; + + return 1; +} + +static int bdisp_s_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct bdisp_frame *frame; + struct bdisp_ctx *ctx = fh_to_ctx(fh); + struct v4l2_rect *in, out; + bool valid = false; + + if ((s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) && + (s->target == V4L2_SEL_TGT_CROP)) + valid = true; + + if ((s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) && + (s->target == V4L2_SEL_TGT_COMPOSE)) + valid = true; + + if (!valid) { + dev_err(ctx->bdisp_dev->dev, "Invalid type / target\n"); + return -EINVAL; + } + + frame = ctx_get_frame(ctx, s->type); + if (IS_ERR(frame)) { + dev_err(ctx->bdisp_dev->dev, "Invalid frame (%p)\n", frame); + return PTR_ERR(frame); + } + + in = &s->r; + out = *in; + + /* Align and check origin */ + out.left = ALIGN(in->left, frame->fmt->w_align); + out.top = ALIGN(in->top, frame->fmt->h_align); + + if ((out.left < 0) || (out.left >= frame->width) || + (out.top < 0) || (out.top >= frame->height)) { + dev_err(ctx->bdisp_dev->dev, + "Invalid crop: %dx%d@(%d,%d) vs frame: %dx%d\n", + out.width, out.height, out.left, out.top, + frame->width, frame->height); + return -EINVAL; + } + + /* Align and check size */ + out.width = ALIGN(in->width, frame->fmt->w_align); + out.height = ALIGN(in->height, frame->fmt->w_align); + + if (((out.left + out.width) > frame->width) || + ((out.top + out.height) > frame->height)) { + dev_err(ctx->bdisp_dev->dev, + "Invalid crop: %dx%d@(%d,%d) vs frame: %dx%d\n", + out.width, out.height, out.left, out.top, + frame->width, frame->height); + return -EINVAL; + } + + /* Checks adjust constraints flags */ + if (s->flags & V4L2_SEL_FLAG_LE && !is_rect_enclosed(&out, in)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && !is_rect_enclosed(in, &out)) + return -ERANGE; + + if ((out.left != in->left) || (out.top != in->top) || + (out.width != in->width) || (out.height != in->height)) { + dev_dbg(ctx->bdisp_dev->dev, + "%s crop updated: %dx%d@(%d,%d) -> %dx%d@(%d,%d)\n", + __func__, in->width, in->height, in->left, in->top, + out.width, out.height, out.left, out.top); + *in = out; + } + + frame->crop = out; + + bdisp_ctx_state_lock_set(BDISP_PARAMS, ctx); + + return 0; +} + +static int bdisp_streamon(struct file *file, void *fh, enum v4l2_buf_type type) +{ + struct bdisp_ctx *ctx = fh_to_ctx(fh); + + if ((type == V4L2_BUF_TYPE_VIDEO_OUTPUT) && + !bdisp_ctx_state_is_set(BDISP_SRC_FMT, ctx)) { + dev_err(ctx->bdisp_dev->dev, "src not defined\n"); + return -EINVAL; + } + + if ((type == V4L2_BUF_TYPE_VIDEO_CAPTURE) && + !bdisp_ctx_state_is_set(BDISP_DST_FMT, ctx)) { + dev_err(ctx->bdisp_dev->dev, "dst not defined\n"); + return -EINVAL; + } + + return v4l2_m2m_streamon(file, ctx->fh.m2m_ctx, type); +} + +static const struct v4l2_ioctl_ops bdisp_ioctl_ops = { + .vidioc_querycap = bdisp_querycap, + .vidioc_enum_fmt_vid_cap = bdisp_enum_fmt, + .vidioc_enum_fmt_vid_out = bdisp_enum_fmt, + .vidioc_g_fmt_vid_cap = bdisp_g_fmt, + .vidioc_g_fmt_vid_out = bdisp_g_fmt, + .vidioc_try_fmt_vid_cap = bdisp_try_fmt, + .vidioc_try_fmt_vid_out = bdisp_try_fmt, + .vidioc_s_fmt_vid_cap = bdisp_s_fmt, + .vidioc_s_fmt_vid_out = bdisp_s_fmt, + .vidioc_g_selection = bdisp_g_selection, + .vidioc_s_selection = bdisp_s_selection, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = bdisp_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int bdisp_register_device(struct bdisp_dev *bdisp) +{ + int ret; + + if (!bdisp) + return -ENODEV; + + bdisp->vdev.fops = &bdisp_fops; + bdisp->vdev.ioctl_ops = &bdisp_ioctl_ops; + bdisp->vdev.release = video_device_release_empty; + bdisp->vdev.lock = &bdisp->lock; + bdisp->vdev.vfl_dir = VFL_DIR_M2M; + bdisp->vdev.v4l2_dev = &bdisp->v4l2_dev; + bdisp->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M; + snprintf(bdisp->vdev.name, sizeof(bdisp->vdev.name), "%s.%d", + BDISP_NAME, bdisp->id); + + video_set_drvdata(&bdisp->vdev, bdisp); + + bdisp->m2m.vdev = &bdisp->vdev; + bdisp->m2m.m2m_dev = v4l2_m2m_init(&bdisp_m2m_ops); + if (IS_ERR(bdisp->m2m.m2m_dev)) { + dev_err(bdisp->dev, "failed to initialize v4l2-m2m device\n"); + return PTR_ERR(bdisp->m2m.m2m_dev); + } + + ret = video_register_device(&bdisp->vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(bdisp->dev, + "%s(): failed to register video device\n", __func__); + v4l2_m2m_release(bdisp->m2m.m2m_dev); + return ret; + } + + return 0; +} + +static void bdisp_unregister_device(struct bdisp_dev *bdisp) +{ + if (!bdisp) + return; + + if (bdisp->m2m.m2m_dev) + v4l2_m2m_release(bdisp->m2m.m2m_dev); + + video_unregister_device(bdisp->m2m.vdev); +} + +static irqreturn_t bdisp_irq_thread(int irq, void *priv) +{ + struct bdisp_dev *bdisp = priv; + struct bdisp_ctx *ctx; + + spin_lock(&bdisp->slock); + + bdisp_dbg_perf_end(bdisp); + + cancel_delayed_work(&bdisp->timeout_work); + + if (!test_and_clear_bit(ST_M2M_RUNNING, &bdisp->state)) + goto isr_unlock; + + if (test_and_clear_bit(ST_M2M_SUSPENDING, &bdisp->state)) { + set_bit(ST_M2M_SUSPENDED, &bdisp->state); + wake_up(&bdisp->irq_queue); + goto isr_unlock; + } + + ctx = v4l2_m2m_get_curr_priv(bdisp->m2m.m2m_dev); + if (!ctx || !ctx->fh.m2m_ctx) + goto isr_unlock; + + spin_unlock(&bdisp->slock); + + bdisp_job_finish(ctx, VB2_BUF_STATE_DONE); + + if (bdisp_ctx_state_is_set(BDISP_CTX_STOP_REQ, ctx)) { + bdisp_ctx_state_lock_clear(BDISP_CTX_STOP_REQ, ctx); + wake_up(&bdisp->irq_queue); + } + + return IRQ_HANDLED; + +isr_unlock: + spin_unlock(&bdisp->slock); + + return IRQ_HANDLED; +} + +static irqreturn_t bdisp_irq_handler(int irq, void *priv) +{ + if (bdisp_hw_get_and_clear_irq((struct bdisp_dev *)priv)) + return IRQ_NONE; + else + return IRQ_WAKE_THREAD; +} + +static void bdisp_irq_timeout(struct work_struct *ptr) +{ + struct delayed_work *twork = to_delayed_work(ptr); + struct bdisp_dev *bdisp = container_of(twork, struct bdisp_dev, + timeout_work); + struct bdisp_ctx *ctx; + + ctx = v4l2_m2m_get_curr_priv(bdisp->m2m.m2m_dev); + + dev_err(ctx->bdisp_dev->dev, "Device work timeout\n"); + + spin_lock(&bdisp->slock); + clear_bit(ST_M2M_RUNNING, &bdisp->state); + spin_unlock(&bdisp->slock); + + bdisp_hw_reset(bdisp); + + bdisp_job_finish(ctx, VB2_BUF_STATE_ERROR); +} + +static int bdisp_m2m_suspend(struct bdisp_dev *bdisp) +{ + unsigned long flags; + int timeout; + + spin_lock_irqsave(&bdisp->slock, flags); + if (!test_bit(ST_M2M_RUNNING, &bdisp->state)) { + spin_unlock_irqrestore(&bdisp->slock, flags); + return 0; + } + clear_bit(ST_M2M_SUSPENDED, &bdisp->state); + set_bit(ST_M2M_SUSPENDING, &bdisp->state); + spin_unlock_irqrestore(&bdisp->slock, flags); + + timeout = wait_event_timeout(bdisp->irq_queue, + test_bit(ST_M2M_SUSPENDED, &bdisp->state), + BDISP_WORK_TIMEOUT); + + clear_bit(ST_M2M_SUSPENDING, &bdisp->state); + + if (!timeout) { + dev_err(bdisp->dev, "%s IRQ timeout\n", __func__); + return -EAGAIN; + } + + return 0; +} + +static int bdisp_m2m_resume(struct bdisp_dev *bdisp) +{ + struct bdisp_ctx *ctx; + unsigned long flags; + + spin_lock_irqsave(&bdisp->slock, flags); + ctx = bdisp->m2m.ctx; + bdisp->m2m.ctx = NULL; + spin_unlock_irqrestore(&bdisp->slock, flags); + + if (test_and_clear_bit(ST_M2M_SUSPENDED, &bdisp->state)) + bdisp_job_finish(ctx, VB2_BUF_STATE_ERROR); + + return 0; +} + +static int bdisp_runtime_resume(struct device *dev) +{ + struct bdisp_dev *bdisp = dev_get_drvdata(dev); + int ret = clk_enable(bdisp->clock); + + if (ret) + return ret; + + return bdisp_m2m_resume(bdisp); +} + +static int bdisp_runtime_suspend(struct device *dev) +{ + struct bdisp_dev *bdisp = dev_get_drvdata(dev); + int ret = bdisp_m2m_suspend(bdisp); + + if (!ret) + clk_disable(bdisp->clock); + + return ret; +} + +static int bdisp_resume(struct device *dev) +{ + struct bdisp_dev *bdisp = dev_get_drvdata(dev); + unsigned long flags; + int opened; + + spin_lock_irqsave(&bdisp->slock, flags); + opened = test_bit(ST_M2M_OPEN, &bdisp->state); + spin_unlock_irqrestore(&bdisp->slock, flags); + + if (!opened) + return 0; + + if (!pm_runtime_suspended(dev)) + return bdisp_runtime_resume(dev); + + return 0; +} + +static int bdisp_suspend(struct device *dev) +{ + if (!pm_runtime_suspended(dev)) + return bdisp_runtime_suspend(dev); + + return 0; +} + +static const struct dev_pm_ops bdisp_pm_ops = { + .suspend = bdisp_suspend, + .resume = bdisp_resume, + .runtime_suspend = bdisp_runtime_suspend, + .runtime_resume = bdisp_runtime_resume, +}; + +static int bdisp_remove(struct platform_device *pdev) +{ + struct bdisp_dev *bdisp = platform_get_drvdata(pdev); + + bdisp_unregister_device(bdisp); + + bdisp_hw_free_filters(bdisp->dev); + + pm_runtime_disable(&pdev->dev); + + bdisp_debugfs_remove(bdisp); + + v4l2_device_unregister(&bdisp->v4l2_dev); + + if (!IS_ERR(bdisp->clock)) + clk_unprepare(bdisp->clock); + + destroy_workqueue(bdisp->work_queue); + + dev_dbg(&pdev->dev, "%s driver unloaded\n", pdev->name); + + return 0; +} + +static int bdisp_probe(struct platform_device *pdev) +{ + struct bdisp_dev *bdisp; + struct device *dev = &pdev->dev; + int ret; + + dev_dbg(dev, "%s\n", __func__); + + bdisp = devm_kzalloc(dev, sizeof(struct bdisp_dev), GFP_KERNEL); + if (!bdisp) + return -ENOMEM; + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + bdisp->pdev = pdev; + bdisp->dev = dev; + platform_set_drvdata(pdev, bdisp); + + if (dev->of_node) + bdisp->id = of_alias_get_id(pdev->dev.of_node, BDISP_NAME); + else + bdisp->id = pdev->id; + + init_waitqueue_head(&bdisp->irq_queue); + INIT_DELAYED_WORK(&bdisp->timeout_work, bdisp_irq_timeout); + bdisp->work_queue = create_workqueue(BDISP_NAME); + if (!bdisp->work_queue) + return -ENOMEM; + + spin_lock_init(&bdisp->slock); + mutex_init(&bdisp->lock); + + /* get resources */ + bdisp->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(bdisp->regs)) { + ret = PTR_ERR(bdisp->regs); + goto err_wq; + } + + bdisp->clock = devm_clk_get(dev, BDISP_NAME); + if (IS_ERR(bdisp->clock)) { + dev_err(dev, "failed to get clock\n"); + ret = PTR_ERR(bdisp->clock); + goto err_wq; + } + + ret = clk_prepare(bdisp->clock); + if (ret < 0) { + dev_err(dev, "clock prepare failed\n"); + bdisp->clock = ERR_PTR(-EINVAL); + goto err_wq; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto err_clk; + + ret = devm_request_threaded_irq(dev, ret, bdisp_irq_handler, + bdisp_irq_thread, IRQF_ONESHOT, + pdev->name, bdisp); + if (ret) { + dev_err(dev, "failed to install irq\n"); + goto err_clk; + } + + /* v4l2 register */ + ret = v4l2_device_register(dev, &bdisp->v4l2_dev); + if (ret) { + dev_err(dev, "failed to register\n"); + goto err_clk; + } + + /* Debug */ + bdisp_debugfs_create(bdisp); + + /* Power management */ + pm_runtime_enable(dev); + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "failed to set PM\n"); + goto err_remove; + } + + /* Filters */ + if (bdisp_hw_alloc_filters(bdisp->dev)) { + dev_err(bdisp->dev, "no memory for filters\n"); + ret = -ENOMEM; + goto err_pm; + } + + /* Register */ + ret = bdisp_register_device(bdisp); + if (ret) { + dev_err(dev, "failed to register\n"); + goto err_filter; + } + + dev_info(dev, "%s%d registered as /dev/video%d\n", BDISP_NAME, + bdisp->id, bdisp->vdev.num); + + pm_runtime_put(dev); + + return 0; + +err_filter: + bdisp_hw_free_filters(bdisp->dev); +err_pm: + pm_runtime_put(dev); +err_remove: + pm_runtime_disable(dev); + bdisp_debugfs_remove(bdisp); + v4l2_device_unregister(&bdisp->v4l2_dev); +err_clk: + clk_unprepare(bdisp->clock); +err_wq: + destroy_workqueue(bdisp->work_queue); + return ret; +} + +static const struct of_device_id bdisp_match_types[] = { + { + .compatible = "st,stih407-bdisp", + }, + { /* end node */ } +}; + +MODULE_DEVICE_TABLE(of, bdisp_match_types); + +static struct platform_driver bdisp_driver = { + .probe = bdisp_probe, + .remove = bdisp_remove, + .driver = { + .name = BDISP_NAME, + .of_match_table = bdisp_match_types, + .pm = &bdisp_pm_ops, + }, +}; + +module_platform_driver(bdisp_driver); + +MODULE_DESCRIPTION("2D blitter for STMicroelectronics SoC"); +MODULE_AUTHOR("Fabien Dessenne "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/st/sti/bdisp/bdisp.h b/drivers/media/platform/st/sti/bdisp/bdisp.h new file mode 100644 index 000000000..3fb009d24 --- /dev/null +++ b/drivers/media/platform/st/sti/bdisp/bdisp.h @@ -0,0 +1,214 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2014 + * Authors: Fabien Dessenne for STMicroelectronics. + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#define BDISP_NAME "bdisp" + +/* + * Max nb of nodes in node-list: + * - 2 nodes to handle wide 4K pictures + * - 2 nodes to handle two planes (Y & CbCr) */ +#define MAX_OUTPUT_PLANES 2 +#define MAX_VERTICAL_STRIDES 2 +#define MAX_NB_NODE (MAX_OUTPUT_PLANES * MAX_VERTICAL_STRIDES) + +/* struct bdisp_ctrls - bdisp control set + * @hflip: horizontal flip + * @vflip: vertical flip + */ +struct bdisp_ctrls { + struct v4l2_ctrl *hflip; + struct v4l2_ctrl *vflip; +}; + +/** + * struct bdisp_fmt - driver's internal color format data + * @pixelformat:fourcc code for this format + * @nb_planes: number of planes (ex: [0]=RGB/Y - [1]=Cb/Cr, ...) + * @bpp: bits per pixel (general) + * @bpp_plane0: byte per pixel for the 1st plane + * @w_align: width alignment in pixel (multiple of) + * @h_align: height alignment in pixel (multiple of) + */ +struct bdisp_fmt { + u32 pixelformat; + u8 nb_planes; + u8 bpp; + u8 bpp_plane0; + u8 w_align; + u8 h_align; +}; + +/** + * struct bdisp_frame - frame properties + * + * @width: frame width (including padding) + * @height: frame height (including padding) + * @fmt: pointer to frame format descriptor + * @field: frame / field type + * @bytesperline: stride of the 1st plane + * @sizeimage: image size in bytes + * @colorspace: colorspace + * @crop: crop area + * @paddr: image physical addresses per plane ([0]=RGB/Y - [1]=Cb/Cr, ...) + */ +struct bdisp_frame { + u32 width; + u32 height; + const struct bdisp_fmt *fmt; + enum v4l2_field field; + u32 bytesperline; + u32 sizeimage; + enum v4l2_colorspace colorspace; + struct v4l2_rect crop; + dma_addr_t paddr[4]; +}; + +/** + * struct bdisp_request - bdisp request + * + * @src: source frame properties + * @dst: destination frame properties + * @hflip: horizontal flip + * @vflip: vertical flip + * @nb_req: number of run request + */ +struct bdisp_request { + struct bdisp_frame src; + struct bdisp_frame dst; + unsigned int hflip:1; + unsigned int vflip:1; + int nb_req; +}; + +/** + * struct bdisp_ctx - device context data + * + * @src: source frame properties + * @dst: destination frame properties + * @state: flags to keep track of user configuration + * @hflip: horizontal flip + * @vflip: vertical flip + * @bdisp_dev: the device this context applies to + * @node: node array + * @node_paddr: node physical address array + * @fh: v4l2 file handle + * @ctrl_handler: v4l2 controls handler + * @bdisp_ctrls: bdisp control set + * @ctrls_rdy: true if the control handler is initialized + */ +struct bdisp_ctx { + struct bdisp_frame src; + struct bdisp_frame dst; + u32 state; + unsigned int hflip:1; + unsigned int vflip:1; + struct bdisp_dev *bdisp_dev; + struct bdisp_node *node[MAX_NB_NODE]; + dma_addr_t node_paddr[MAX_NB_NODE]; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; + struct bdisp_ctrls bdisp_ctrls; + bool ctrls_rdy; +}; + +/** + * struct bdisp_m2m_device - v4l2 memory-to-memory device data + * + * @vdev: video device node for v4l2 m2m mode + * @m2m_dev: v4l2 m2m device data + * @ctx: hardware context data + * @refcnt: reference counter + */ +struct bdisp_m2m_device { + struct video_device *vdev; + struct v4l2_m2m_dev *m2m_dev; + struct bdisp_ctx *ctx; + int refcnt; +}; + +/** + * struct bdisp_dbg - debug info + * + * @debugfs_entry: debugfs + * @copy_node: array of last used nodes + * @copy_request: last bdisp request + * @hw_start: start time of last HW request + * @last_duration: last HW processing duration in microsecs + * @min_duration: min HW processing duration in microsecs + * @max_duration: max HW processing duration in microsecs + * @tot_duration: total HW processing duration in microsecs + */ +struct bdisp_dbg { + struct dentry *debugfs_entry; + struct bdisp_node *copy_node[MAX_NB_NODE]; + struct bdisp_request copy_request; + ktime_t hw_start; + s64 last_duration; + s64 min_duration; + s64 max_duration; + s64 tot_duration; +}; + +/** + * struct bdisp_dev - abstraction for bdisp entity + * + * @v4l2_dev: v4l2 device + * @vdev: video device + * @pdev: platform device + * @dev: device + * @lock: mutex protecting this data structure + * @slock: spinlock protecting this data structure + * @id: device index + * @m2m: memory-to-memory V4L2 device information + * @state: flags used to synchronize m2m and capture mode operation + * @clock: IP clock + * @regs: registers + * @irq_queue: interrupt handler waitqueue + * @work_queue: workqueue to handle timeouts + * @timeout_work: IRQ timeout structure + * @dbg: debug info + */ +struct bdisp_dev { + struct v4l2_device v4l2_dev; + struct video_device vdev; + struct platform_device *pdev; + struct device *dev; + spinlock_t slock; + struct mutex lock; + u16 id; + struct bdisp_m2m_device m2m; + unsigned long state; + struct clk *clock; + void __iomem *regs; + wait_queue_head_t irq_queue; + struct workqueue_struct *work_queue; + struct delayed_work timeout_work; + struct bdisp_dbg dbg; +}; + +void bdisp_hw_free_nodes(struct bdisp_ctx *ctx); +int bdisp_hw_alloc_nodes(struct bdisp_ctx *ctx); +void bdisp_hw_free_filters(struct device *dev); +int bdisp_hw_alloc_filters(struct device *dev); +int bdisp_hw_reset(struct bdisp_dev *bdisp); +int bdisp_hw_get_and_clear_irq(struct bdisp_dev *bdisp); +int bdisp_hw_update(struct bdisp_ctx *ctx); + +void bdisp_debugfs_remove(struct bdisp_dev *bdisp); +void bdisp_debugfs_create(struct bdisp_dev *bdisp); +void bdisp_dbg_perf_begin(struct bdisp_dev *bdisp); +void bdisp_dbg_perf_end(struct bdisp_dev *bdisp); diff --git a/drivers/media/platform/st/sti/c8sectpfe/Kconfig b/drivers/media/platform/st/sti/c8sectpfe/Kconfig new file mode 100644 index 000000000..702b91050 --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/Kconfig @@ -0,0 +1,29 @@ +# SPDX-License-Identifier: GPL-2.0-only +config DVB_C8SECTPFE + tristate "STMicroelectronics C8SECTPFE DVB support" + depends on DVB_PLATFORM_DRIVERS + depends on PINCTRL && DVB_CORE && I2C + depends on ARCH_STI || ARCH_MULTIPLATFORM || COMPILE_TEST + select FW_LOADER + select DEBUG_FS + select DVB_LNBP21 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV090x if MEDIA_SUBDRV_AUTOSELECT + select DVB_STB6100 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV6110 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0900 if MEDIA_SUBDRV_AUTOSELECT + select DVB_STV0367 if MEDIA_SUBDRV_AUTOSELECT + select MEDIA_TUNER_TDA18212 if MEDIA_SUBDRV_AUTOSELECT + + help + This adds support for DVB front-end cards connected + to TS inputs of STiH407/410 SoC. + + The driver currently supports C8SECTPFE's TS input block, + memdma engine, and HW PID filtering. + + Supported DVB front-end cards are: + - STMicroelectronics DVB-T B2100A (STV0367 + TDA18212) + - STMicroelectronics DVB-S/S2 STV0903 + STV6110 + LNBP24 board + + To compile this driver as a module, choose M here: the + module will be called c8sectpfe. diff --git a/drivers/media/platform/st/sti/c8sectpfe/Makefile b/drivers/media/platform/st/sti/c8sectpfe/Makefile new file mode 100644 index 000000000..aedfc725c --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +c8sectpfe-y += c8sectpfe-core.o c8sectpfe-common.o c8sectpfe-dvb.o \ + c8sectpfe-debugfs.o + +obj-$(CONFIG_DVB_C8SECTPFE) += c8sectpfe.o + +ccflags-y += -I $(srctree)/drivers/media/dvb-frontends/ +ccflags-y += -I $(srctree)/drivers/media/tuners/ diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-common.c b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-common.c new file mode 100644 index 000000000..5df67da25 --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-common.c @@ -0,0 +1,262 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * c8sectpfe-common.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "c8sectpfe-common.h" +#include "c8sectpfe-core.h" +#include "c8sectpfe-dvb.h" + +static int register_dvb(struct stdemux *demux, struct dvb_adapter *adap, + void *start_feed, void *stop_feed, + struct c8sectpfei *fei) +{ + int result; + + demux->dvb_demux.dmx.capabilities = DMX_TS_FILTERING | + DMX_SECTION_FILTERING | + DMX_MEMORY_BASED_FILTERING; + + demux->dvb_demux.priv = demux; + demux->dvb_demux.filternum = C8SECTPFE_MAXCHANNEL; + demux->dvb_demux.feednum = C8SECTPFE_MAXCHANNEL; + + demux->dvb_demux.start_feed = start_feed; + demux->dvb_demux.stop_feed = stop_feed; + demux->dvb_demux.write_to_decoder = NULL; + + result = dvb_dmx_init(&demux->dvb_demux); + if (result < 0) { + dev_err(fei->dev, "dvb_dmx_init failed (errno = %d)\n", + result); + goto err_dmx; + } + + demux->dmxdev.filternum = demux->dvb_demux.filternum; + demux->dmxdev.demux = &demux->dvb_demux.dmx; + demux->dmxdev.capabilities = 0; + + result = dvb_dmxdev_init(&demux->dmxdev, adap); + if (result < 0) { + dev_err(fei->dev, "dvb_dmxdev_init failed (errno = %d)\n", + result); + + goto err_dmxdev; + } + + demux->hw_frontend.source = DMX_FRONTEND_0 + demux->tsin_index; + + result = demux->dvb_demux.dmx.add_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + if (result < 0) { + dev_err(fei->dev, "add_frontend failed (errno = %d)\n", result); + goto err_fe_hw; + } + + demux->mem_frontend.source = DMX_MEMORY_FE; + result = demux->dvb_demux.dmx.add_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); + if (result < 0) { + dev_err(fei->dev, "add_frontend failed (%d)\n", result); + goto err_fe_mem; + } + + result = demux->dvb_demux.dmx.connect_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + if (result < 0) { + dev_err(fei->dev, "connect_frontend (%d)\n", result); + goto err_fe_con; + } + + return 0; + +err_fe_con: + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); +err_fe_mem: + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); +err_fe_hw: + dvb_dmxdev_release(&demux->dmxdev); +err_dmxdev: + dvb_dmx_release(&demux->dvb_demux); +err_dmx: + return result; + +} + +static void unregister_dvb(struct stdemux *demux) +{ + + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->mem_frontend); + + demux->dvb_demux.dmx.remove_frontend(&demux->dvb_demux.dmx, + &demux->hw_frontend); + + dvb_dmxdev_release(&demux->dmxdev); + + dvb_dmx_release(&demux->dvb_demux); +} + +static struct c8sectpfe *c8sectpfe_create(struct c8sectpfei *fei, + void *start_feed, + void *stop_feed) +{ + struct c8sectpfe *c8sectpfe; + int result; + int i, j; + + short int ids[] = { -1 }; + + c8sectpfe = kzalloc(sizeof(struct c8sectpfe), GFP_KERNEL); + if (!c8sectpfe) + goto err1; + + mutex_init(&c8sectpfe->lock); + + c8sectpfe->device = fei->dev; + + result = dvb_register_adapter(&c8sectpfe->adapter, "STi c8sectpfe", + THIS_MODULE, fei->dev, ids); + if (result < 0) { + dev_err(fei->dev, "dvb_register_adapter failed (errno = %d)\n", + result); + goto err2; + } + + c8sectpfe->adapter.priv = fei; + + for (i = 0; i < fei->tsin_count; i++) { + + c8sectpfe->demux[i].tsin_index = i; + c8sectpfe->demux[i].c8sectpfei = fei; + + result = register_dvb(&c8sectpfe->demux[i], &c8sectpfe->adapter, + start_feed, stop_feed, fei); + if (result < 0) { + dev_err(fei->dev, + "register_dvb feed=%d failed (errno = %d)\n", + result, i); + + /* we take a all or nothing approach */ + for (j = 0; j < i; j++) + unregister_dvb(&c8sectpfe->demux[j]); + goto err3; + } + } + + c8sectpfe->num_feeds = fei->tsin_count; + + return c8sectpfe; +err3: + dvb_unregister_adapter(&c8sectpfe->adapter); +err2: + kfree(c8sectpfe); +err1: + return NULL; +}; + +static void c8sectpfe_delete(struct c8sectpfe *c8sectpfe) +{ + int i; + + if (!c8sectpfe) + return; + + for (i = 0; i < c8sectpfe->num_feeds; i++) + unregister_dvb(&c8sectpfe->demux[i]); + + dvb_unregister_adapter(&c8sectpfe->adapter); + + kfree(c8sectpfe); +}; + +void c8sectpfe_tuner_unregister_frontend(struct c8sectpfe *c8sectpfe, + struct c8sectpfei *fei) +{ + int n; + struct channel_info *tsin; + + for (n = 0; n < fei->tsin_count; n++) { + + tsin = fei->channel_data[n]; + + if (tsin) { + if (tsin->frontend) { + dvb_unregister_frontend(tsin->frontend); + dvb_frontend_detach(tsin->frontend); + } + + i2c_put_adapter(tsin->i2c_adapter); + + if (tsin->i2c_client) { + module_put(tsin->i2c_client->dev.driver->owner); + i2c_unregister_device(tsin->i2c_client); + } + } + } + + c8sectpfe_delete(c8sectpfe); +}; + +int c8sectpfe_tuner_register_frontend(struct c8sectpfe **c8sectpfe, + struct c8sectpfei *fei, + void *start_feed, + void *stop_feed) +{ + struct channel_info *tsin; + struct dvb_frontend *frontend; + int n, res; + + *c8sectpfe = c8sectpfe_create(fei, start_feed, stop_feed); + if (!*c8sectpfe) + return -ENOMEM; + + for (n = 0; n < fei->tsin_count; n++) { + tsin = fei->channel_data[n]; + + res = c8sectpfe_frontend_attach(&frontend, *c8sectpfe, tsin, n); + if (res) + goto err; + + res = dvb_register_frontend(&c8sectpfe[0]->adapter, frontend); + if (res < 0) { + dev_err(fei->dev, "dvb_register_frontend failed (%d)\n", + res); + goto err; + } + + tsin->frontend = frontend; + } + + return 0; + +err: + c8sectpfe_tuner_unregister_frontend(*c8sectpfe, fei); + return res; +} diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-common.h b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-common.h new file mode 100644 index 000000000..f8d97841f --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-common.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * c8sectpfe-common.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin + * + */ +#ifndef _C8SECTPFE_COMMON_H_ +#define _C8SECTPFE_COMMON_H_ + +#include +#include +#include + +#include +#include +#include +#include + +/* Maximum number of channels */ +#define C8SECTPFE_MAXADAPTER (4) +#define C8SECTPFE_MAXCHANNEL 64 +#define STPTI_MAXCHANNEL 64 + +#define MAX_INPUTBLOCKS 7 + +struct c8sectpfe; +struct stdemux; + +struct stdemux { + struct dvb_demux dvb_demux; + struct dmxdev dmxdev; + struct dmx_frontend hw_frontend; + struct dmx_frontend mem_frontend; + int tsin_index; + int running_feed_count; + struct c8sectpfei *c8sectpfei; +}; + +struct c8sectpfe { + struct stdemux demux[MAX_INPUTBLOCKS]; + struct mutex lock; + struct dvb_adapter adapter; + struct device *device; + int mapping; + int num_feeds; +}; + +/* Channel registration */ +int c8sectpfe_tuner_register_frontend(struct c8sectpfe **c8sectpfe, + struct c8sectpfei *fei, + void *start_feed, + void *stop_feed); + +void c8sectpfe_tuner_unregister_frontend(struct c8sectpfe *c8sectpfe, + struct c8sectpfei *fei); + +#endif diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c new file mode 100644 index 000000000..1dbb89f0d --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c @@ -0,0 +1,1187 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * c8sectpfe-core.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett + * Peter Griffin + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c8sectpfe-core.h" +#include "c8sectpfe-common.h" +#include "c8sectpfe-debugfs.h" +#include +#include +#include +#include + +#define FIRMWARE_MEMDMA "pti_memdma_h407.elf" +MODULE_FIRMWARE(FIRMWARE_MEMDMA); + +#define PID_TABLE_SIZE 1024 +#define POLL_MSECS 50 + +static int load_c8sectpfe_fw(struct c8sectpfei *fei); + +#define TS_PKT_SIZE 188 +#define HEADER_SIZE (4) +#define PACKET_SIZE (TS_PKT_SIZE+HEADER_SIZE) + +#define FEI_ALIGNMENT (32) +/* hw requires minimum of 8*PACKET_SIZE and padded to 8byte boundary */ +#define FEI_BUFFER_SIZE (8*PACKET_SIZE*340) + +#define FIFO_LEN 1024 + +static void c8sectpfe_timer_interrupt(struct timer_list *t) +{ + struct c8sectpfei *fei = from_timer(fei, t, timer); + struct channel_info *channel; + int chan_num; + + /* iterate through input block channels */ + for (chan_num = 0; chan_num < fei->tsin_count; chan_num++) { + channel = fei->channel_data[chan_num]; + + /* is this descriptor initialised and TP enabled */ + if (channel->irec && readl(channel->irec + DMA_PRDS_TPENABLE)) + tasklet_schedule(&channel->tsklet); + } + + fei->timer.expires = jiffies + msecs_to_jiffies(POLL_MSECS); + add_timer(&fei->timer); +} + +static void channel_swdemux_tsklet(struct tasklet_struct *t) +{ + struct channel_info *channel = from_tasklet(channel, t, tsklet); + struct c8sectpfei *fei; + unsigned long wp, rp; + int pos, num_packets, n, size; + u8 *buf; + + if (unlikely(!channel || !channel->irec)) + return; + + fei = channel->fei; + + wp = readl(channel->irec + DMA_PRDS_BUSWP_TP(0)); + rp = readl(channel->irec + DMA_PRDS_BUSRP_TP(0)); + + pos = rp - channel->back_buffer_busaddr; + + /* has it wrapped */ + if (wp < rp) + wp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE; + + size = wp - rp; + num_packets = size / PACKET_SIZE; + + /* manage cache so data is visible to CPU */ + dma_sync_single_for_cpu(fei->dev, + rp, + size, + DMA_FROM_DEVICE); + + buf = channel->back_buffer_aligned; + + dev_dbg(fei->dev, + "chan=%d channel=%p num_packets = %d, buf = %p, pos = 0x%x\n\trp=0x%lx, wp=0x%lx\n", + channel->tsin_id, channel, num_packets, buf, pos, rp, wp); + + for (n = 0; n < num_packets; n++) { + dvb_dmx_swfilter_packets( + &fei->c8sectpfe[0]-> + demux[channel->demux_mapping].dvb_demux, + &buf[pos], 1); + + pos += PACKET_SIZE; + } + + /* advance the read pointer */ + if (wp == (channel->back_buffer_busaddr + FEI_BUFFER_SIZE)) + writel(channel->back_buffer_busaddr, channel->irec + + DMA_PRDS_BUSRP_TP(0)); + else + writel(wp, channel->irec + DMA_PRDS_BUSRP_TP(0)); +} + +static int c8sectpfe_start_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + struct dvb_demux *demux = dvbdmxfeed->demux; + struct stdemux *stdemux = (struct stdemux *)demux->priv; + struct c8sectpfei *fei = stdemux->c8sectpfei; + struct channel_info *channel; + u32 tmp; + unsigned long *bitmap; + int ret; + + switch (dvbdmxfeed->type) { + case DMX_TYPE_TS: + break; + case DMX_TYPE_SEC: + break; + default: + dev_err(fei->dev, "%s:%d Error bailing\n" + , __func__, __LINE__); + return -EINVAL; + } + + if (dvbdmxfeed->type == DMX_TYPE_TS) { + switch (dvbdmxfeed->pes_type) { + case DMX_PES_VIDEO: + case DMX_PES_AUDIO: + case DMX_PES_TELETEXT: + case DMX_PES_PCR: + case DMX_PES_OTHER: + break; + default: + dev_err(fei->dev, "%s:%d Error bailing\n" + , __func__, __LINE__); + return -EINVAL; + } + } + + if (!atomic_read(&fei->fw_loaded)) { + ret = load_c8sectpfe_fw(fei); + if (ret) + return ret; + } + + mutex_lock(&fei->lock); + + channel = fei->channel_data[stdemux->tsin_index]; + + bitmap = channel->pid_buffer_aligned; + + /* 8192 is a special PID */ + if (dvbdmxfeed->pid == 8192) { + tmp = readl(fei->io + C8SECTPFE_IB_PID_SET(channel->tsin_id)); + tmp &= ~C8SECTPFE_PID_ENABLE; + writel(tmp, fei->io + C8SECTPFE_IB_PID_SET(channel->tsin_id)); + + } else { + bitmap_set(bitmap, dvbdmxfeed->pid, 1); + } + + /* manage cache so PID bitmap is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + channel->active = 1; + + if (fei->global_feed_count == 0) { + fei->timer.expires = jiffies + + msecs_to_jiffies(msecs_to_jiffies(POLL_MSECS)); + + add_timer(&fei->timer); + } + + if (stdemux->running_feed_count == 0) { + + dev_dbg(fei->dev, "Starting channel=%p\n", channel); + + tasklet_setup(&channel->tsklet, channel_swdemux_tsklet); + + /* Reset the internal inputblock sram pointers */ + writel(channel->fifo, + fei->io + C8SECTPFE_IB_BUFF_STRT(channel->tsin_id)); + writel(channel->fifo + FIFO_LEN - 1, + fei->io + C8SECTPFE_IB_BUFF_END(channel->tsin_id)); + + writel(channel->fifo, + fei->io + C8SECTPFE_IB_READ_PNT(channel->tsin_id)); + writel(channel->fifo, + fei->io + C8SECTPFE_IB_WRT_PNT(channel->tsin_id)); + + + /* reset read / write memdma ptrs for this channel */ + writel(channel->back_buffer_busaddr, channel->irec + + DMA_PRDS_BUSBASE_TP(0)); + + tmp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, channel->irec + DMA_PRDS_BUSTOP_TP(0)); + + writel(channel->back_buffer_busaddr, channel->irec + + DMA_PRDS_BUSWP_TP(0)); + + /* Issue a reset and enable InputBlock */ + writel(C8SECTPFE_SYS_ENABLE | C8SECTPFE_SYS_RESET + , fei->io + C8SECTPFE_IB_SYS(channel->tsin_id)); + + /* and enable the tp */ + writel(0x1, channel->irec + DMA_PRDS_TPENABLE); + + dev_dbg(fei->dev, "%s:%d Starting DMA feed on stdemux=%p\n" + , __func__, __LINE__, stdemux); + } + + stdemux->running_feed_count++; + fei->global_feed_count++; + + mutex_unlock(&fei->lock); + + return 0; +} + +static int c8sectpfe_stop_feed(struct dvb_demux_feed *dvbdmxfeed) +{ + + struct dvb_demux *demux = dvbdmxfeed->demux; + struct stdemux *stdemux = (struct stdemux *)demux->priv; + struct c8sectpfei *fei = stdemux->c8sectpfei; + struct channel_info *channel; + int idlereq; + u32 tmp; + int ret; + unsigned long *bitmap; + + if (!atomic_read(&fei->fw_loaded)) { + ret = load_c8sectpfe_fw(fei); + if (ret) + return ret; + } + + mutex_lock(&fei->lock); + + channel = fei->channel_data[stdemux->tsin_index]; + + bitmap = channel->pid_buffer_aligned; + + if (dvbdmxfeed->pid == 8192) { + tmp = readl(fei->io + C8SECTPFE_IB_PID_SET(channel->tsin_id)); + tmp |= C8SECTPFE_PID_ENABLE; + writel(tmp, fei->io + C8SECTPFE_IB_PID_SET(channel->tsin_id)); + } else { + bitmap_clear(bitmap, dvbdmxfeed->pid, 1); + } + + /* manage cache so data is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + if (--stdemux->running_feed_count == 0) { + + channel = fei->channel_data[stdemux->tsin_index]; + + /* TP re-configuration on page 168 of functional spec */ + + /* disable IB (prevents more TS data going to memdma) */ + writel(0, fei->io + C8SECTPFE_IB_SYS(channel->tsin_id)); + + /* disable this channels descriptor */ + writel(0, channel->irec + DMA_PRDS_TPENABLE); + + tasklet_disable(&channel->tsklet); + + /* now request memdma channel goes idle */ + idlereq = (1 << channel->tsin_id) | IDLEREQ; + writel(idlereq, fei->io + DMA_IDLE_REQ); + + /* wait for idle irq handler to signal completion */ + ret = wait_for_completion_timeout(&channel->idle_completion, + msecs_to_jiffies(100)); + + if (ret == 0) + dev_warn(fei->dev, + "Timeout waiting for idle irq on tsin%d\n", + channel->tsin_id); + + reinit_completion(&channel->idle_completion); + + /* reset read / write ptrs for this channel */ + + writel(channel->back_buffer_busaddr, + channel->irec + DMA_PRDS_BUSBASE_TP(0)); + + tmp = channel->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, channel->irec + DMA_PRDS_BUSTOP_TP(0)); + + writel(channel->back_buffer_busaddr, + channel->irec + DMA_PRDS_BUSWP_TP(0)); + + dev_dbg(fei->dev, + "%s:%d stopping DMA feed on stdemux=%p channel=%d\n", + __func__, __LINE__, stdemux, channel->tsin_id); + + /* turn off all PIDS in the bitmap */ + memset(channel->pid_buffer_aligned, 0, PID_TABLE_SIZE); + + /* manage cache so data is visible to HW */ + dma_sync_single_for_device(fei->dev, + channel->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + channel->active = 0; + } + + if (--fei->global_feed_count == 0) { + dev_dbg(fei->dev, "%s:%d global_feed_count=%d\n" + , __func__, __LINE__, fei->global_feed_count); + + del_timer(&fei->timer); + } + + mutex_unlock(&fei->lock); + + return 0; +} + +static struct channel_info *find_channel(struct c8sectpfei *fei, int tsin_num) +{ + int i; + + for (i = 0; i < C8SECTPFE_MAX_TSIN_CHAN; i++) { + if (!fei->channel_data[i]) + continue; + + if (fei->channel_data[i]->tsin_id == tsin_num) + return fei->channel_data[i]; + } + + return NULL; +} + +static void c8sectpfe_getconfig(struct c8sectpfei *fei) +{ + struct c8sectpfe_hw *hw = &fei->hw_stats; + + hw->num_ib = readl(fei->io + SYS_CFG_NUM_IB); + hw->num_mib = readl(fei->io + SYS_CFG_NUM_MIB); + hw->num_swts = readl(fei->io + SYS_CFG_NUM_SWTS); + hw->num_tsout = readl(fei->io + SYS_CFG_NUM_TSOUT); + hw->num_ccsc = readl(fei->io + SYS_CFG_NUM_CCSC); + hw->num_ram = readl(fei->io + SYS_CFG_NUM_RAM); + hw->num_tp = readl(fei->io + SYS_CFG_NUM_TP); + + dev_info(fei->dev, "C8SECTPFE hw supports the following:\n"); + dev_info(fei->dev, "Input Blocks: %d\n", hw->num_ib); + dev_info(fei->dev, "Merged Input Blocks: %d\n", hw->num_mib); + dev_info(fei->dev, "Software Transport Stream Inputs: %d\n" + , hw->num_swts); + dev_info(fei->dev, "Transport Stream Output: %d\n", hw->num_tsout); + dev_info(fei->dev, "Cable Card Converter: %d\n", hw->num_ccsc); + dev_info(fei->dev, "RAMs supported by C8SECTPFE: %d\n", hw->num_ram); + dev_info(fei->dev, "Tango TPs supported by C8SECTPFE: %d\n" + , hw->num_tp); +} + +static irqreturn_t c8sectpfe_idle_irq_handler(int irq, void *priv) +{ + struct c8sectpfei *fei = priv; + struct channel_info *chan; + int bit; + unsigned long tmp = readl(fei->io + DMA_IDLE_REQ); + + /* page 168 of functional spec: Clear the idle request + by writing 0 to the C8SECTPFE_DMA_IDLE_REQ register. */ + + /* signal idle completion */ + for_each_set_bit(bit, &tmp, fei->hw_stats.num_ib) { + + chan = find_channel(fei, bit); + + if (chan) + complete(&chan->idle_completion); + } + + writel(0, fei->io + DMA_IDLE_REQ); + + return IRQ_HANDLED; +} + + +static void free_input_block(struct c8sectpfei *fei, struct channel_info *tsin) +{ + if (!fei || !tsin) + return; + + if (tsin->back_buffer_busaddr) + if (!dma_mapping_error(fei->dev, tsin->back_buffer_busaddr)) + dma_unmap_single(fei->dev, tsin->back_buffer_busaddr, + FEI_BUFFER_SIZE, DMA_BIDIRECTIONAL); + + kfree(tsin->back_buffer_start); + + if (tsin->pid_buffer_busaddr) + if (!dma_mapping_error(fei->dev, tsin->pid_buffer_busaddr)) + dma_unmap_single(fei->dev, tsin->pid_buffer_busaddr, + PID_TABLE_SIZE, DMA_BIDIRECTIONAL); + + kfree(tsin->pid_buffer_start); +} + +#define MAX_NAME 20 + +static int configure_memdma_and_inputblock(struct c8sectpfei *fei, + struct channel_info *tsin) +{ + int ret; + u32 tmp; + char tsin_pin_name[MAX_NAME]; + + if (!fei || !tsin) + return -EINVAL; + + dev_dbg(fei->dev, "%s:%d Configuring channel=%p tsin=%d\n" + , __func__, __LINE__, tsin, tsin->tsin_id); + + init_completion(&tsin->idle_completion); + + tsin->back_buffer_start = kzalloc(FEI_BUFFER_SIZE + FEI_ALIGNMENT, GFP_KERNEL); + if (!tsin->back_buffer_start) { + ret = -ENOMEM; + goto err_unmap; + } + + /* Ensure backbuffer is 32byte aligned */ + tsin->back_buffer_aligned = tsin->back_buffer_start + FEI_ALIGNMENT; + + tsin->back_buffer_aligned = PTR_ALIGN(tsin->back_buffer_aligned, FEI_ALIGNMENT); + + tsin->back_buffer_busaddr = dma_map_single(fei->dev, + tsin->back_buffer_aligned, + FEI_BUFFER_SIZE, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(fei->dev, tsin->back_buffer_busaddr)) { + dev_err(fei->dev, "failed to map back_buffer\n"); + ret = -EFAULT; + goto err_unmap; + } + + /* + * The pid buffer can be configured (in hw) for byte or bit + * per pid. By powers of deduction we conclude stih407 family + * is configured (at SoC design stage) for bit per pid. + */ + tsin->pid_buffer_start = kzalloc(PID_TABLE_SIZE + PID_TABLE_SIZE, GFP_KERNEL); + if (!tsin->pid_buffer_start) { + ret = -ENOMEM; + goto err_unmap; + } + + /* + * PID buffer needs to be aligned to size of the pid table + * which at bit per pid is 1024 bytes (8192 pids / 8). + * PIDF_BASE register enforces this alignment when writing + * the register. + */ + + tsin->pid_buffer_aligned = tsin->pid_buffer_start + PID_TABLE_SIZE; + + tsin->pid_buffer_aligned = PTR_ALIGN(tsin->pid_buffer_aligned, PID_TABLE_SIZE); + + tsin->pid_buffer_busaddr = dma_map_single(fei->dev, + tsin->pid_buffer_aligned, + PID_TABLE_SIZE, + DMA_BIDIRECTIONAL); + + if (dma_mapping_error(fei->dev, tsin->pid_buffer_busaddr)) { + dev_err(fei->dev, "failed to map pid_bitmap\n"); + ret = -EFAULT; + goto err_unmap; + } + + /* manage cache so pid bitmap is visible to HW */ + dma_sync_single_for_device(fei->dev, + tsin->pid_buffer_busaddr, + PID_TABLE_SIZE, + DMA_TO_DEVICE); + + snprintf(tsin_pin_name, MAX_NAME, "tsin%d-%s", tsin->tsin_id, + (tsin->serial_not_parallel ? "serial" : "parallel")); + + tsin->pstate = pinctrl_lookup_state(fei->pinctrl, tsin_pin_name); + if (IS_ERR(tsin->pstate)) { + dev_err(fei->dev, "%s: pinctrl_lookup_state couldn't find %s state\n" + , __func__, tsin_pin_name); + ret = PTR_ERR(tsin->pstate); + goto err_unmap; + } + + ret = pinctrl_select_state(fei->pinctrl, tsin->pstate); + + if (ret) { + dev_err(fei->dev, "%s: pinctrl_select_state failed\n" + , __func__); + goto err_unmap; + } + + /* Enable this input block */ + tmp = readl(fei->io + SYS_INPUT_CLKEN); + tmp |= BIT(tsin->tsin_id); + writel(tmp, fei->io + SYS_INPUT_CLKEN); + + if (tsin->serial_not_parallel) + tmp |= C8SECTPFE_SERIAL_NOT_PARALLEL; + + if (tsin->invert_ts_clk) + tmp |= C8SECTPFE_INVERT_TSCLK; + + if (tsin->async_not_sync) + tmp |= C8SECTPFE_ASYNC_NOT_SYNC; + + tmp |= C8SECTPFE_ALIGN_BYTE_SOP | C8SECTPFE_BYTE_ENDIANNESS_MSB; + + writel(tmp, fei->io + C8SECTPFE_IB_IP_FMT_CFG(tsin->tsin_id)); + + writel(C8SECTPFE_SYNC(0x9) | + C8SECTPFE_DROP(0x9) | + C8SECTPFE_TOKEN(0x47), + fei->io + C8SECTPFE_IB_SYNCLCKDRP_CFG(tsin->tsin_id)); + + writel(TS_PKT_SIZE, fei->io + C8SECTPFE_IB_PKT_LEN(tsin->tsin_id)); + + /* Place the FIFO's at the end of the irec descriptors */ + + tsin->fifo = (tsin->tsin_id * FIFO_LEN); + + writel(tsin->fifo, fei->io + C8SECTPFE_IB_BUFF_STRT(tsin->tsin_id)); + writel(tsin->fifo + FIFO_LEN - 1, + fei->io + C8SECTPFE_IB_BUFF_END(tsin->tsin_id)); + + writel(tsin->fifo, fei->io + C8SECTPFE_IB_READ_PNT(tsin->tsin_id)); + writel(tsin->fifo, fei->io + C8SECTPFE_IB_WRT_PNT(tsin->tsin_id)); + + writel(tsin->pid_buffer_busaddr, + fei->io + PIDF_BASE(tsin->tsin_id)); + + dev_dbg(fei->dev, "chan=%d PIDF_BASE=0x%x pid_bus_addr=%pad\n", + tsin->tsin_id, readl(fei->io + PIDF_BASE(tsin->tsin_id)), + &tsin->pid_buffer_busaddr); + + /* Configure and enable HW PID filtering */ + + /* + * The PID value is created by assembling the first 8 bytes of + * the TS packet into a 64-bit word in big-endian format. A + * slice of that 64-bit word is taken from + * (PID_OFFSET+PID_NUM_BITS-1) to PID_OFFSET. + */ + tmp = (C8SECTPFE_PID_ENABLE | C8SECTPFE_PID_NUMBITS(13) + | C8SECTPFE_PID_OFFSET(40)); + + writel(tmp, fei->io + C8SECTPFE_IB_PID_SET(tsin->tsin_id)); + + dev_dbg(fei->dev, "chan=%d setting wp: %d, rp: %d, buf: %d-%d\n", + tsin->tsin_id, + readl(fei->io + C8SECTPFE_IB_WRT_PNT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_READ_PNT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_BUFF_STRT(tsin->tsin_id)), + readl(fei->io + C8SECTPFE_IB_BUFF_END(tsin->tsin_id))); + + /* Get base addpress of pointer record block from DMEM */ + tsin->irec = fei->io + DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + + readl(fei->io + DMA_PTRREC_BASE); + + /* fill out pointer record data structure */ + + /* advance pointer record block to our channel */ + tsin->irec += (tsin->tsin_id * DMA_PRDS_SIZE); + + writel(tsin->fifo, tsin->irec + DMA_PRDS_MEMBASE); + + writel(tsin->fifo + FIFO_LEN - 1, tsin->irec + DMA_PRDS_MEMTOP); + + writel((188 + 7)&~7, tsin->irec + DMA_PRDS_PKTSIZE); + + writel(0x1, tsin->irec + DMA_PRDS_TPENABLE); + + /* read/write pointers with physical bus address */ + + writel(tsin->back_buffer_busaddr, tsin->irec + DMA_PRDS_BUSBASE_TP(0)); + + tmp = tsin->back_buffer_busaddr + FEI_BUFFER_SIZE - 1; + writel(tmp, tsin->irec + DMA_PRDS_BUSTOP_TP(0)); + + writel(tsin->back_buffer_busaddr, tsin->irec + DMA_PRDS_BUSWP_TP(0)); + writel(tsin->back_buffer_busaddr, tsin->irec + DMA_PRDS_BUSRP_TP(0)); + + /* initialize tasklet */ + tasklet_setup(&tsin->tsklet, channel_swdemux_tsklet); + + return 0; + +err_unmap: + free_input_block(fei, tsin); + return ret; +} + +static irqreturn_t c8sectpfe_error_irq_handler(int irq, void *priv) +{ + struct c8sectpfei *fei = priv; + + dev_err(fei->dev, "%s: error handling not yet implemented\n" + , __func__); + + /* + * TODO FIXME we should detect some error conditions here + * and ideally do something about them! + */ + + return IRQ_HANDLED; +} + +static int c8sectpfe_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *child, *np = dev->of_node; + struct c8sectpfei *fei; + struct resource *res; + int ret, index = 0; + struct channel_info *tsin; + + /* Allocate the c8sectpfei structure */ + fei = devm_kzalloc(dev, sizeof(struct c8sectpfei), GFP_KERNEL); + if (!fei) + return -ENOMEM; + + fei->dev = dev; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "c8sectpfe"); + fei->io = devm_ioremap_resource(dev, res); + if (IS_ERR(fei->io)) + return PTR_ERR(fei->io); + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "c8sectpfe-ram"); + fei->sram = devm_ioremap_resource(dev, res); + if (IS_ERR(fei->sram)) + return PTR_ERR(fei->sram); + + fei->sram_size = resource_size(res); + + fei->idle_irq = platform_get_irq_byname(pdev, "c8sectpfe-idle-irq"); + if (fei->idle_irq < 0) + return fei->idle_irq; + + fei->error_irq = platform_get_irq_byname(pdev, "c8sectpfe-error-irq"); + if (fei->error_irq < 0) + return fei->error_irq; + + platform_set_drvdata(pdev, fei); + + fei->c8sectpfeclk = devm_clk_get(dev, "c8sectpfe"); + if (IS_ERR(fei->c8sectpfeclk)) { + dev_err(dev, "c8sectpfe clk not found\n"); + return PTR_ERR(fei->c8sectpfeclk); + } + + ret = clk_prepare_enable(fei->c8sectpfeclk); + if (ret) { + dev_err(dev, "Failed to enable c8sectpfe clock\n"); + return ret; + } + + /* to save power disable all IP's (on by default) */ + writel(0, fei->io + SYS_INPUT_CLKEN); + + /* Enable memdma clock */ + writel(MEMDMAENABLE, fei->io + SYS_OTHER_CLKEN); + + /* clear internal sram */ + memset_io(fei->sram, 0x0, fei->sram_size); + + c8sectpfe_getconfig(fei); + + ret = devm_request_irq(dev, fei->idle_irq, c8sectpfe_idle_irq_handler, + 0, "c8sectpfe-idle-irq", fei); + if (ret) { + dev_err(dev, "Can't register c8sectpfe-idle-irq IRQ.\n"); + goto err_clk_disable; + } + + ret = devm_request_irq(dev, fei->error_irq, + c8sectpfe_error_irq_handler, 0, + "c8sectpfe-error-irq", fei); + if (ret) { + dev_err(dev, "Can't register c8sectpfe-error-irq IRQ.\n"); + goto err_clk_disable; + } + + fei->tsin_count = of_get_child_count(np); + + if (fei->tsin_count > C8SECTPFE_MAX_TSIN_CHAN || + fei->tsin_count > fei->hw_stats.num_ib) { + + dev_err(dev, "More tsin declared than exist on SoC!\n"); + ret = -EINVAL; + goto err_clk_disable; + } + + fei->pinctrl = devm_pinctrl_get(dev); + + if (IS_ERR(fei->pinctrl)) { + dev_err(dev, "Error getting tsin pins\n"); + ret = PTR_ERR(fei->pinctrl); + goto err_clk_disable; + } + + for_each_child_of_node(np, child) { + struct device_node *i2c_bus; + + fei->channel_data[index] = devm_kzalloc(dev, + sizeof(struct channel_info), + GFP_KERNEL); + + if (!fei->channel_data[index]) { + ret = -ENOMEM; + goto err_node_put; + } + + tsin = fei->channel_data[index]; + + tsin->fei = fei; + + ret = of_property_read_u32(child, "tsin-num", &tsin->tsin_id); + if (ret) { + dev_err(&pdev->dev, "No tsin_num found\n"); + goto err_node_put; + } + + /* sanity check value */ + if (tsin->tsin_id > fei->hw_stats.num_ib) { + dev_err(&pdev->dev, + "tsin-num %d specified greater than number\n\tof input block hw in SoC! (%d)", + tsin->tsin_id, fei->hw_stats.num_ib); + ret = -EINVAL; + goto err_node_put; + } + + tsin->invert_ts_clk = of_property_read_bool(child, + "invert-ts-clk"); + + tsin->serial_not_parallel = of_property_read_bool(child, + "serial-not-parallel"); + + tsin->async_not_sync = of_property_read_bool(child, + "async-not-sync"); + + ret = of_property_read_u32(child, "dvb-card", + &tsin->dvb_card); + if (ret) { + dev_err(&pdev->dev, "No dvb-card found\n"); + goto err_node_put; + } + + i2c_bus = of_parse_phandle(child, "i2c-bus", 0); + if (!i2c_bus) { + dev_err(&pdev->dev, "No i2c-bus found\n"); + ret = -ENODEV; + goto err_node_put; + } + tsin->i2c_adapter = + of_find_i2c_adapter_by_node(i2c_bus); + if (!tsin->i2c_adapter) { + dev_err(&pdev->dev, "No i2c adapter found\n"); + of_node_put(i2c_bus); + ret = -ENODEV; + goto err_node_put; + } + of_node_put(i2c_bus); + + tsin->rst_gpio = of_get_named_gpio(child, "reset-gpios", 0); + + ret = gpio_is_valid(tsin->rst_gpio); + if (!ret) { + dev_err(dev, + "reset gpio for tsin%d not valid (gpio=%d)\n", + tsin->tsin_id, tsin->rst_gpio); + ret = -EINVAL; + goto err_node_put; + } + + ret = devm_gpio_request_one(dev, tsin->rst_gpio, + GPIOF_OUT_INIT_LOW, "NIM reset"); + if (ret && ret != -EBUSY) { + dev_err(dev, "Can't request tsin%d reset gpio\n" + , fei->channel_data[index]->tsin_id); + goto err_node_put; + } + + if (!ret) { + /* toggle reset lines */ + gpio_direction_output(tsin->rst_gpio, 0); + usleep_range(3500, 5000); + gpio_direction_output(tsin->rst_gpio, 1); + usleep_range(3000, 5000); + } + + tsin->demux_mapping = index; + + dev_dbg(fei->dev, + "channel=%p n=%d tsin_num=%d, invert-ts-clk=%d\n\tserial-not-parallel=%d pkt-clk-valid=%d dvb-card=%d\n", + fei->channel_data[index], index, + tsin->tsin_id, tsin->invert_ts_clk, + tsin->serial_not_parallel, tsin->async_not_sync, + tsin->dvb_card); + + index++; + } + + /* Setup timer interrupt */ + timer_setup(&fei->timer, c8sectpfe_timer_interrupt, 0); + + mutex_init(&fei->lock); + + /* Get the configuration information about the tuners */ + ret = c8sectpfe_tuner_register_frontend(&fei->c8sectpfe[0], + (void *)fei, + c8sectpfe_start_feed, + c8sectpfe_stop_feed); + if (ret) { + dev_err(dev, "c8sectpfe_tuner_register_frontend failed (%d)\n", + ret); + goto err_clk_disable; + } + + c8sectpfe_debugfs_init(fei); + + return 0; + +err_node_put: + of_node_put(child); +err_clk_disable: + clk_disable_unprepare(fei->c8sectpfeclk); + return ret; +} + +static int c8sectpfe_remove(struct platform_device *pdev) +{ + struct c8sectpfei *fei = platform_get_drvdata(pdev); + struct channel_info *channel; + int i; + + wait_for_completion(&fei->fw_ack); + + c8sectpfe_tuner_unregister_frontend(fei->c8sectpfe[0], fei); + + /* + * Now loop through and un-configure each of the InputBlock resources + */ + for (i = 0; i < fei->tsin_count; i++) { + channel = fei->channel_data[i]; + free_input_block(fei, channel); + } + + c8sectpfe_debugfs_exit(fei); + + dev_info(fei->dev, "Stopping memdma SLIM core\n"); + if (readl(fei->io + DMA_CPU_RUN)) + writel(0x0, fei->io + DMA_CPU_RUN); + + /* unclock all internal IP's */ + if (readl(fei->io + SYS_INPUT_CLKEN)) + writel(0, fei->io + SYS_INPUT_CLKEN); + + if (readl(fei->io + SYS_OTHER_CLKEN)) + writel(0, fei->io + SYS_OTHER_CLKEN); + + clk_disable_unprepare(fei->c8sectpfeclk); + + return 0; +} + + +static int configure_channels(struct c8sectpfei *fei) +{ + int index = 0, ret; + struct device_node *child, *np = fei->dev->of_node; + + /* iterate round each tsin and configure memdma descriptor and IB hw */ + for_each_child_of_node(np, child) { + ret = configure_memdma_and_inputblock(fei, + fei->channel_data[index]); + if (ret) { + dev_err(fei->dev, + "configure_memdma_and_inputblock failed\n"); + of_node_put(child); + goto err_unmap; + } + index++; + } + + return 0; + +err_unmap: + while (--index >= 0) + free_input_block(fei, fei->channel_data[index]); + + return ret; +} + +static int +c8sectpfe_elf_sanity_check(struct c8sectpfei *fei, const struct firmware *fw) +{ + struct elf32_hdr *ehdr; + char class; + + if (!fw) { + dev_err(fei->dev, "failed to load %s\n", FIRMWARE_MEMDMA); + return -EINVAL; + } + + if (fw->size < sizeof(struct elf32_hdr)) { + dev_err(fei->dev, "Image is too small\n"); + return -EINVAL; + } + + ehdr = (struct elf32_hdr *)fw->data; + + /* We only support ELF32 at this point */ + class = ehdr->e_ident[EI_CLASS]; + if (class != ELFCLASS32) { + dev_err(fei->dev, "Unsupported class: %d\n", class); + return -EINVAL; + } + + if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) { + dev_err(fei->dev, "Unsupported firmware endianness\n"); + return -EINVAL; + } + + if (fw->size < ehdr->e_shoff + sizeof(struct elf32_shdr)) { + dev_err(fei->dev, "Image is too small\n"); + return -EINVAL; + } + + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) { + dev_err(fei->dev, "Image is corrupted (bad magic)\n"); + return -EINVAL; + } + + /* Check ELF magic */ + ehdr = (Elf32_Ehdr *)fw->data; + if (ehdr->e_ident[EI_MAG0] != ELFMAG0 || + ehdr->e_ident[EI_MAG1] != ELFMAG1 || + ehdr->e_ident[EI_MAG2] != ELFMAG2 || + ehdr->e_ident[EI_MAG3] != ELFMAG3) { + dev_err(fei->dev, "Invalid ELF magic\n"); + return -EINVAL; + } + + if (ehdr->e_type != ET_EXEC) { + dev_err(fei->dev, "Unsupported ELF header type\n"); + return -EINVAL; + } + + if (ehdr->e_phoff > fw->size) { + dev_err(fei->dev, "Firmware size is too small\n"); + return -EINVAL; + } + + return 0; +} + + +static void load_imem_segment(struct c8sectpfei *fei, Elf32_Phdr *phdr, + const struct firmware *fw, u8 __iomem *dest, + int seg_num) +{ + const u8 *imem_src = fw->data + phdr->p_offset; + int i; + + /* + * For IMEM segments, the segment contains 24-bit + * instructions which must be padded to 32-bit + * instructions before being written. The written + * segment is padded with NOP instructions. + */ + + dev_dbg(fei->dev, + "Loading IMEM segment %d 0x%08x\n\t (0x%x bytes) -> 0x%p (0x%x bytes)\n", + seg_num, phdr->p_paddr, phdr->p_filesz, dest, + phdr->p_memsz + phdr->p_memsz / 3); + + for (i = 0; i < phdr->p_filesz; i++) { + + writeb(readb((void __iomem *)imem_src), (void __iomem *)dest); + + /* Every 3 bytes, add an additional + * padding zero in destination */ + if (i % 3 == 2) { + dest++; + writeb(0x00, (void __iomem *)dest); + } + + dest++; + imem_src++; + } +} + +static void load_dmem_segment(struct c8sectpfei *fei, Elf32_Phdr *phdr, + const struct firmware *fw, u8 __iomem *dst, int seg_num) +{ + /* + * For DMEM segments copy the segment data from the ELF + * file and pad segment with zeroes + */ + + dev_dbg(fei->dev, + "Loading DMEM segment %d 0x%08x\n\t(0x%x bytes) -> 0x%p (0x%x bytes)\n", + seg_num, phdr->p_paddr, phdr->p_filesz, + dst, phdr->p_memsz); + + memcpy((void __force *)dst, (void *)fw->data + phdr->p_offset, + phdr->p_filesz); + + memset((void __force *)dst + phdr->p_filesz, 0, + phdr->p_memsz - phdr->p_filesz); +} + +static int load_slim_core_fw(const struct firmware *fw, struct c8sectpfei *fei) +{ + Elf32_Ehdr *ehdr; + Elf32_Phdr *phdr; + u8 __iomem *dst; + int err = 0, i; + + if (!fw || !fei) + return -EINVAL; + + ehdr = (Elf32_Ehdr *)fw->data; + phdr = (Elf32_Phdr *)(fw->data + ehdr->e_phoff); + + /* go through the available ELF segments */ + for (i = 0; i < ehdr->e_phnum; i++, phdr++) { + + /* Only consider LOAD segments */ + if (phdr->p_type != PT_LOAD) + continue; + + /* + * Check segment is contained within the fw->data buffer + */ + if (phdr->p_offset + phdr->p_filesz > fw->size) { + dev_err(fei->dev, + "Segment %d is outside of firmware file\n", i); + err = -EINVAL; + break; + } + + /* + * MEMDMA IMEM has executable flag set, otherwise load + * this segment into DMEM. + * + */ + + if (phdr->p_flags & PF_X) { + dst = (u8 __iomem *) fei->io + DMA_MEMDMA_IMEM; + /* + * The Slim ELF file uses 32-bit word addressing for + * load offsets. + */ + dst += (phdr->p_paddr & 0xFFFFF) * sizeof(unsigned int); + load_imem_segment(fei, phdr, fw, dst, i); + } else { + dst = (u8 __iomem *) fei->io + DMA_MEMDMA_DMEM; + /* + * The Slim ELF file uses 32-bit word addressing for + * load offsets. + */ + dst += (phdr->p_paddr & 0xFFFFF) * sizeof(unsigned int); + load_dmem_segment(fei, phdr, fw, dst, i); + } + } + + release_firmware(fw); + return err; +} + +static int load_c8sectpfe_fw(struct c8sectpfei *fei) +{ + const struct firmware *fw; + int err; + + dev_info(fei->dev, "Loading firmware: %s\n", FIRMWARE_MEMDMA); + + err = request_firmware(&fw, FIRMWARE_MEMDMA, fei->dev); + if (err) + return err; + + err = c8sectpfe_elf_sanity_check(fei, fw); + if (err) { + dev_err(fei->dev, "c8sectpfe_elf_sanity_check failed err=(%d)\n" + , err); + release_firmware(fw); + return err; + } + + err = load_slim_core_fw(fw, fei); + if (err) { + dev_err(fei->dev, "load_slim_core_fw failed err=(%d)\n", err); + return err; + } + + /* now the firmware is loaded configure the input blocks */ + err = configure_channels(fei); + if (err) { + dev_err(fei->dev, "configure_channels failed err=(%d)\n", err); + return err; + } + + /* + * STBus target port can access IMEM and DMEM ports + * without waiting for CPU + */ + writel(0x1, fei->io + DMA_PER_STBUS_SYNC); + + dev_info(fei->dev, "Boot the memdma SLIM core\n"); + writel(0x1, fei->io + DMA_CPU_RUN); + + atomic_set(&fei->fw_loaded, 1); + + return 0; +} + +static const struct of_device_id c8sectpfe_match[] = { + { .compatible = "st,stih407-c8sectpfe" }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, c8sectpfe_match); + +static struct platform_driver c8sectpfe_driver = { + .driver = { + .name = "c8sectpfe", + .of_match_table = of_match_ptr(c8sectpfe_match), + }, + .probe = c8sectpfe_probe, + .remove = c8sectpfe_remove, +}; + +module_platform_driver(c8sectpfe_driver); + +MODULE_AUTHOR("Peter Bennett "); +MODULE_AUTHOR("Peter Griffin "); +MODULE_DESCRIPTION("C8SECTPFE STi DVB Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.h b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.h new file mode 100644 index 000000000..c9d602190 --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.h @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * c8sectpfe-core.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author:Peter Bennett + * Peter Griffin + * + */ +#ifndef _C8SECTPFE_CORE_H_ +#define _C8SECTPFE_CORE_H_ + +#define C8SECTPFEI_MAXCHANNEL 16 +#define C8SECTPFEI_MAXADAPTER 3 + +#define C8SECTPFE_MAX_TSIN_CHAN 8 + +struct channel_info { + + int tsin_id; + bool invert_ts_clk; + bool serial_not_parallel; + bool async_not_sync; + int i2c; + int dvb_card; + + int rst_gpio; + + struct i2c_adapter *i2c_adapter; + struct i2c_adapter *tuner_i2c; + struct i2c_adapter *lnb_i2c; + struct i2c_client *i2c_client; + struct dvb_frontend *frontend; + + struct pinctrl_state *pstate; + + int demux_mapping; + int active; + + void *back_buffer_start; + void *back_buffer_aligned; + dma_addr_t back_buffer_busaddr; + + void *pid_buffer_start; + void *pid_buffer_aligned; + dma_addr_t pid_buffer_busaddr; + + unsigned long fifo; + + struct completion idle_completion; + struct tasklet_struct tsklet; + + struct c8sectpfei *fei; + void __iomem *irec; + +}; + +struct c8sectpfe_hw { + int num_ib; + int num_mib; + int num_swts; + int num_tsout; + int num_ccsc; + int num_ram; + int num_tp; +}; + +struct c8sectpfei { + + struct device *dev; + struct pinctrl *pinctrl; + + struct dentry *root; + struct debugfs_regset32 *regset; + struct completion fw_ack; + atomic_t fw_loaded; + + int tsin_count; + + struct c8sectpfe_hw hw_stats; + + struct c8sectpfe *c8sectpfe[C8SECTPFEI_MAXADAPTER]; + + int mapping[C8SECTPFEI_MAXCHANNEL]; + + struct mutex lock; + + struct timer_list timer; /* timer interrupts for outputs */ + + void __iomem *io; + void __iomem *sram; + + unsigned long sram_size; + + struct channel_info *channel_data[C8SECTPFE_MAX_TSIN_CHAN]; + + struct clk *c8sectpfeclk; + int nima_rst_gpio; + int nimb_rst_gpio; + + int idle_irq; + int error_irq; + + int global_feed_count; +}; + +/* C8SECTPFE SYS Regs list */ + +#define SYS_INPUT_ERR_STATUS 0x0 +#define SYS_OTHER_ERR_STATUS 0x8 +#define SYS_INPUT_ERR_MASK 0x10 +#define SYS_OTHER_ERR_MASK 0x18 +#define SYS_DMA_ROUTE 0x20 +#define SYS_INPUT_CLKEN 0x30 +#define IBENABLE_MASK 0x7F + +#define SYS_OTHER_CLKEN 0x38 +#define TSDMAENABLE BIT(1) +#define MEMDMAENABLE BIT(0) + +#define SYS_CFG_NUM_IB 0x200 +#define SYS_CFG_NUM_MIB 0x204 +#define SYS_CFG_NUM_SWTS 0x208 +#define SYS_CFG_NUM_TSOUT 0x20C +#define SYS_CFG_NUM_CCSC 0x210 +#define SYS_CFG_NUM_RAM 0x214 +#define SYS_CFG_NUM_TP 0x218 + +/* Input Block Regs */ + +#define C8SECTPFE_INPUTBLK_OFFSET 0x1000 +#define C8SECTPFE_CHANNEL_OFFSET(x) ((x*0x40) + C8SECTPFE_INPUTBLK_OFFSET) + +#define C8SECTPFE_IB_IP_FMT_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x00) +#define C8SECTPFE_IGNORE_ERR_AT_SOP BIT(7) +#define C8SECTPFE_IGNORE_ERR_IN_PKT BIT(6) +#define C8SECTPFE_IGNORE_ERR_IN_BYTE BIT(5) +#define C8SECTPFE_INVERT_TSCLK BIT(4) +#define C8SECTPFE_ALIGN_BYTE_SOP BIT(3) +#define C8SECTPFE_ASYNC_NOT_SYNC BIT(2) +#define C8SECTPFE_BYTE_ENDIANNESS_MSB BIT(1) +#define C8SECTPFE_SERIAL_NOT_PARALLEL BIT(0) + +#define C8SECTPFE_IB_SYNCLCKDRP_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x04) +#define C8SECTPFE_SYNC(x) (x & 0xf) +#define C8SECTPFE_DROP(x) ((x<<4) & 0xf) +#define C8SECTPFE_TOKEN(x) ((x<<8) & 0xff00) +#define C8SECTPFE_SLDENDIANNESS BIT(16) + +#define C8SECTPFE_IB_TAGBYTES_CFG(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x08) +#define C8SECTPFE_TAG_HEADER(x) (x << 16) +#define C8SECTPFE_TAG_COUNTER(x) ((x<<1) & 0x7fff) +#define C8SECTPFE_TAG_ENABLE BIT(0) + +#define C8SECTPFE_IB_PID_SET(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x0C) +#define C8SECTPFE_PID_OFFSET(x) (x & 0x3f) +#define C8SECTPFE_PID_NUMBITS(x) ((x << 6) & 0xfff) +#define C8SECTPFE_PID_ENABLE BIT(31) + +#define C8SECTPFE_IB_PKT_LEN(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x10) + +#define C8SECTPFE_IB_BUFF_STRT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x14) +#define C8SECTPFE_IB_BUFF_END(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x18) +#define C8SECTPFE_IB_READ_PNT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x1C) +#define C8SECTPFE_IB_WRT_PNT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x20) + +#define C8SECTPFE_IB_PRI_THRLD(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x24) +#define C8SECTPFE_PRI_VALUE(x) (x & 0x7fffff) +#define C8SECTPFE_PRI_LOWPRI(x) ((x & 0xf) << 24) +#define C8SECTPFE_PRI_HIGHPRI(x) ((x & 0xf) << 28) + +#define C8SECTPFE_IB_STAT(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x28) +#define C8SECTPFE_STAT_FIFO_OVERFLOW(x) (x & 0x1) +#define C8SECTPFE_STAT_BUFFER_OVERFLOW(x) (x & 0x2) +#define C8SECTPFE_STAT_OUTOFORDERRP(x) (x & 0x4) +#define C8SECTPFE_STAT_PID_OVERFLOW(x) (x & 0x8) +#define C8SECTPFE_STAT_PKT_OVERFLOW(x) (x & 0x10) +#define C8SECTPFE_STAT_ERROR_PACKETS(x) ((x >> 8) & 0xf) +#define C8SECTPFE_STAT_SHORT_PACKETS(x) ((x >> 12) & 0xf) + +#define C8SECTPFE_IB_MASK(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x2C) +#define C8SECTPFE_MASK_FIFO_OVERFLOW BIT(0) +#define C8SECTPFE_MASK_BUFFER_OVERFLOW BIT(1) +#define C8SECTPFE_MASK_OUTOFORDERRP(x) BIT(2) +#define C8SECTPFE_MASK_PID_OVERFLOW(x) BIT(3) +#define C8SECTPFE_MASK_PKT_OVERFLOW(x) BIT(4) +#define C8SECTPFE_MASK_ERROR_PACKETS(x) ((x & 0xf) << 8) +#define C8SECTPFE_MASK_SHORT_PACKETS(x) ((x & 0xf) >> 12) + +#define C8SECTPFE_IB_SYS(x) (C8SECTPFE_CHANNEL_OFFSET(x) + 0x30) +#define C8SECTPFE_SYS_RESET BIT(1) +#define C8SECTPFE_SYS_ENABLE BIT(0) + +/* + * Pointer record data structure required for each input block + * see Table 82 on page 167 of functional specification. + */ + +#define DMA_PRDS_MEMBASE 0x0 /* Internal sram base address */ +#define DMA_PRDS_MEMTOP 0x4 /* Internal sram top address */ + +/* + * TS packet size, including tag bytes added by input block, + * rounded up to the next multiple of 8 bytes. The packet size, + * including any tagging bytes and rounded up to the nearest + * multiple of 8 bytes must be less than 255 bytes. + */ +#define DMA_PRDS_PKTSIZE 0x8 +#define DMA_PRDS_TPENABLE 0xc + +#define TP0_OFFSET 0x10 +#define DMA_PRDS_BUSBASE_TP(x) ((0x10*x) + TP0_OFFSET) +#define DMA_PRDS_BUSTOP_TP(x) ((0x10*x) + TP0_OFFSET + 0x4) +#define DMA_PRDS_BUSWP_TP(x) ((0x10*x) + TP0_OFFSET + 0x8) +#define DMA_PRDS_BUSRP_TP(x) ((0x10*x) + TP0_OFFSET + 0xc) + +#define DMA_PRDS_SIZE (0x20) + +#define DMA_MEMDMA_OFFSET 0x4000 +#define DMA_IMEM_OFFSET 0x0 +#define DMA_DMEM_OFFSET 0x4000 +#define DMA_CPU 0x8000 +#define DMA_PER_OFFSET 0xb000 + +#define DMA_MEMDMA_DMEM (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET) +#define DMA_MEMDMA_IMEM (DMA_MEMDMA_OFFSET + DMA_IMEM_OFFSET) + +/* XP70 Slim core regs */ +#define DMA_CPU_ID (DMA_MEMDMA_OFFSET + DMA_CPU + 0x0) +#define DMA_CPU_VCR (DMA_MEMDMA_OFFSET + DMA_CPU + 0x4) +#define DMA_CPU_RUN (DMA_MEMDMA_OFFSET + DMA_CPU + 0x8) +#define DMA_CPU_CLOCKGATE (DMA_MEMDMA_OFFSET + DMA_CPU + 0xc) +#define DMA_CPU_PC (DMA_MEMDMA_OFFSET + DMA_CPU + 0x20) + +/* Enable Interrupt for a IB */ +#define DMA_PER_TPn_DREQ_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xd00) +/* Ack interrupt by setting corresponding bit */ +#define DMA_PER_TPn_DACK_SET (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xd80) +#define DMA_PER_TPn_DREQ (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xe00) +#define DMA_PER_TPn_DACK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xe80) +#define DMA_PER_DREQ_MODE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf80) +#define DMA_PER_STBUS_SYNC (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf88) +#define DMA_PER_STBUS_ACCESS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf8c) +#define DMA_PER_STBUS_ADDRESS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xf90) +#define DMA_PER_IDLE_INT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfa8) +#define DMA_PER_PRIORITY (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfac) +#define DMA_PER_MAX_OPCODE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfb0) +#define DMA_PER_MAX_CHUNK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfb4) +#define DMA_PER_PAGE_SIZE (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfbc) +#define DMA_PER_MBOX_STATUS (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfc0) +#define DMA_PER_MBOX_SET (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfc8) +#define DMA_PER_MBOX_CLEAR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfd0) +#define DMA_PER_MBOX_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfd8) +#define DMA_PER_INJECT_PKT_SRC (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe0) +#define DMA_PER_INJECT_PKT_DEST (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe4) +#define DMA_PER_INJECT_PKT_ADDR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfe8) +#define DMA_PER_INJECT_PKT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xfec) +#define DMA_PER_PAT_PTR_INIT (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff0) +#define DMA_PER_PAT_PTR (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff4) +#define DMA_PER_SLEEP_MASK (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xff8) +#define DMA_PER_SLEEP_COUNTER (DMA_MEMDMA_OFFSET + DMA_PER_OFFSET + 0xffc) +/* #define DMA_RF_CPUREGn DMA_RFBASEADDR n=0 to 15) slim regsa */ + +/* The following are from DMA_DMEM_BaseAddress */ +#define DMA_FIRMWARE_VERSION (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x0) +#define DMA_PTRREC_BASE (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x4) +#define DMA_PTRREC_INPUT_OFFSET (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x8) +#define DMA_ERRREC_BASE (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0xc) +#define DMA_ERROR_RECORD(n) ((n*4) + DMA_ERRREC_BASE + 0x4) +#define DMA_IDLE_REQ (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x10) +#define IDLEREQ BIT(31) + +#define DMA_FIRMWARE_CONFIG (DMA_MEMDMA_OFFSET + DMA_DMEM_OFFSET + 0x14) + +/* Regs for PID Filter */ + +#define PIDF_OFFSET 0x2800 +#define PIDF_BASE(n) ((n*4) + PIDF_OFFSET) +#define PIDF_LEAK_ENABLE (PIDF_OFFSET + 0x100) +#define PIDF_LEAK_STATUS (PIDF_OFFSET + 0x108) +#define PIDF_LEAK_COUNT_RESET (PIDF_OFFSET + 0x110) +#define PIDF_LEAK_COUNTER (PIDF_OFFSET + 0x114) + +#endif /* _C8SECTPFE_CORE_H_ */ diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-debugfs.c b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-debugfs.c new file mode 100644 index 000000000..301fa10f4 --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-debugfs.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * c8sectpfe-debugfs.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin + * + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "c8sectpfe-debugfs.h" + +#define dump_register(nm ...) \ +{ \ + .name = #nm, \ + .offset = nm, \ +} + +static const struct debugfs_reg32 fei_sys_regs[] = { + dump_register(SYS_INPUT_ERR_STATUS), + dump_register(SYS_OTHER_ERR_STATUS), + dump_register(SYS_INPUT_ERR_MASK), + dump_register(SYS_DMA_ROUTE), + dump_register(SYS_INPUT_CLKEN), + dump_register(IBENABLE_MASK), + dump_register(SYS_OTHER_CLKEN), + dump_register(SYS_CFG_NUM_IB), + dump_register(SYS_CFG_NUM_MIB), + dump_register(SYS_CFG_NUM_SWTS), + dump_register(SYS_CFG_NUM_TSOUT), + dump_register(SYS_CFG_NUM_CCSC), + dump_register(SYS_CFG_NUM_RAM), + dump_register(SYS_CFG_NUM_TP), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(0)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(0)), + dump_register(C8SECTPFE_IB_PID_SET(0)), + dump_register(C8SECTPFE_IB_PKT_LEN(0)), + dump_register(C8SECTPFE_IB_BUFF_STRT(0)), + dump_register(C8SECTPFE_IB_BUFF_END(0)), + dump_register(C8SECTPFE_IB_READ_PNT(0)), + dump_register(C8SECTPFE_IB_WRT_PNT(0)), + dump_register(C8SECTPFE_IB_PRI_THRLD(0)), + dump_register(C8SECTPFE_IB_STAT(0)), + dump_register(C8SECTPFE_IB_MASK(0)), + dump_register(C8SECTPFE_IB_SYS(0)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(1)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(1)), + dump_register(C8SECTPFE_IB_PID_SET(1)), + dump_register(C8SECTPFE_IB_PKT_LEN(1)), + dump_register(C8SECTPFE_IB_BUFF_STRT(1)), + dump_register(C8SECTPFE_IB_BUFF_END(1)), + dump_register(C8SECTPFE_IB_READ_PNT(1)), + dump_register(C8SECTPFE_IB_WRT_PNT(1)), + dump_register(C8SECTPFE_IB_PRI_THRLD(1)), + dump_register(C8SECTPFE_IB_STAT(1)), + dump_register(C8SECTPFE_IB_MASK(1)), + dump_register(C8SECTPFE_IB_SYS(1)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(2)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(2)), + dump_register(C8SECTPFE_IB_PID_SET(2)), + dump_register(C8SECTPFE_IB_PKT_LEN(2)), + dump_register(C8SECTPFE_IB_BUFF_STRT(2)), + dump_register(C8SECTPFE_IB_BUFF_END(2)), + dump_register(C8SECTPFE_IB_READ_PNT(2)), + dump_register(C8SECTPFE_IB_WRT_PNT(2)), + dump_register(C8SECTPFE_IB_PRI_THRLD(2)), + dump_register(C8SECTPFE_IB_STAT(2)), + dump_register(C8SECTPFE_IB_MASK(2)), + dump_register(C8SECTPFE_IB_SYS(2)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(3)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(3)), + dump_register(C8SECTPFE_IB_PID_SET(3)), + dump_register(C8SECTPFE_IB_PKT_LEN(3)), + dump_register(C8SECTPFE_IB_BUFF_STRT(3)), + dump_register(C8SECTPFE_IB_BUFF_END(3)), + dump_register(C8SECTPFE_IB_READ_PNT(3)), + dump_register(C8SECTPFE_IB_WRT_PNT(3)), + dump_register(C8SECTPFE_IB_PRI_THRLD(3)), + dump_register(C8SECTPFE_IB_STAT(3)), + dump_register(C8SECTPFE_IB_MASK(3)), + dump_register(C8SECTPFE_IB_SYS(3)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(4)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(4)), + dump_register(C8SECTPFE_IB_PID_SET(4)), + dump_register(C8SECTPFE_IB_PKT_LEN(4)), + dump_register(C8SECTPFE_IB_BUFF_STRT(4)), + dump_register(C8SECTPFE_IB_BUFF_END(4)), + dump_register(C8SECTPFE_IB_READ_PNT(4)), + dump_register(C8SECTPFE_IB_WRT_PNT(4)), + dump_register(C8SECTPFE_IB_PRI_THRLD(4)), + dump_register(C8SECTPFE_IB_STAT(4)), + dump_register(C8SECTPFE_IB_MASK(4)), + dump_register(C8SECTPFE_IB_SYS(4)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(5)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(5)), + dump_register(C8SECTPFE_IB_PID_SET(5)), + dump_register(C8SECTPFE_IB_PKT_LEN(5)), + dump_register(C8SECTPFE_IB_BUFF_STRT(5)), + dump_register(C8SECTPFE_IB_BUFF_END(5)), + dump_register(C8SECTPFE_IB_READ_PNT(5)), + dump_register(C8SECTPFE_IB_WRT_PNT(5)), + dump_register(C8SECTPFE_IB_PRI_THRLD(5)), + dump_register(C8SECTPFE_IB_STAT(5)), + dump_register(C8SECTPFE_IB_MASK(5)), + dump_register(C8SECTPFE_IB_SYS(5)), + + dump_register(C8SECTPFE_IB_IP_FMT_CFG(6)), + dump_register(C8SECTPFE_IB_TAGBYTES_CFG(6)), + dump_register(C8SECTPFE_IB_PID_SET(6)), + dump_register(C8SECTPFE_IB_PKT_LEN(6)), + dump_register(C8SECTPFE_IB_BUFF_STRT(6)), + dump_register(C8SECTPFE_IB_BUFF_END(6)), + dump_register(C8SECTPFE_IB_READ_PNT(6)), + dump_register(C8SECTPFE_IB_WRT_PNT(6)), + dump_register(C8SECTPFE_IB_PRI_THRLD(6)), + dump_register(C8SECTPFE_IB_STAT(6)), + dump_register(C8SECTPFE_IB_MASK(6)), + dump_register(C8SECTPFE_IB_SYS(6)), + + dump_register(DMA_CPU_ID), + dump_register(DMA_CPU_VCR), + dump_register(DMA_CPU_RUN), + dump_register(DMA_CPU_PC), + + dump_register(DMA_PER_TPn_DREQ_MASK), + dump_register(DMA_PER_TPn_DACK_SET), + dump_register(DMA_PER_TPn_DREQ), + dump_register(DMA_PER_TPn_DACK), + dump_register(DMA_PER_DREQ_MODE), + dump_register(DMA_PER_STBUS_SYNC), + dump_register(DMA_PER_STBUS_ACCESS), + dump_register(DMA_PER_STBUS_ADDRESS), + dump_register(DMA_PER_IDLE_INT), + dump_register(DMA_PER_PRIORITY), + dump_register(DMA_PER_MAX_OPCODE), + dump_register(DMA_PER_MAX_CHUNK), + dump_register(DMA_PER_PAGE_SIZE), + dump_register(DMA_PER_MBOX_STATUS), + dump_register(DMA_PER_MBOX_SET), + dump_register(DMA_PER_MBOX_CLEAR), + dump_register(DMA_PER_MBOX_MASK), + dump_register(DMA_PER_INJECT_PKT_SRC), + dump_register(DMA_PER_INJECT_PKT_DEST), + dump_register(DMA_PER_INJECT_PKT_ADDR), + dump_register(DMA_PER_INJECT_PKT), + dump_register(DMA_PER_PAT_PTR_INIT), + dump_register(DMA_PER_PAT_PTR), + dump_register(DMA_PER_SLEEP_MASK), + dump_register(DMA_PER_SLEEP_COUNTER), + + dump_register(DMA_FIRMWARE_VERSION), + dump_register(DMA_PTRREC_BASE), + dump_register(DMA_PTRREC_INPUT_OFFSET), + dump_register(DMA_ERRREC_BASE), + + dump_register(DMA_ERROR_RECORD(0)), + dump_register(DMA_ERROR_RECORD(1)), + dump_register(DMA_ERROR_RECORD(2)), + dump_register(DMA_ERROR_RECORD(3)), + dump_register(DMA_ERROR_RECORD(4)), + dump_register(DMA_ERROR_RECORD(5)), + dump_register(DMA_ERROR_RECORD(6)), + dump_register(DMA_ERROR_RECORD(7)), + dump_register(DMA_ERROR_RECORD(8)), + dump_register(DMA_ERROR_RECORD(9)), + dump_register(DMA_ERROR_RECORD(10)), + dump_register(DMA_ERROR_RECORD(11)), + dump_register(DMA_ERROR_RECORD(12)), + dump_register(DMA_ERROR_RECORD(13)), + dump_register(DMA_ERROR_RECORD(14)), + dump_register(DMA_ERROR_RECORD(15)), + dump_register(DMA_ERROR_RECORD(16)), + dump_register(DMA_ERROR_RECORD(17)), + dump_register(DMA_ERROR_RECORD(18)), + dump_register(DMA_ERROR_RECORD(19)), + dump_register(DMA_ERROR_RECORD(20)), + dump_register(DMA_ERROR_RECORD(21)), + dump_register(DMA_ERROR_RECORD(22)), + + dump_register(DMA_IDLE_REQ), + dump_register(DMA_FIRMWARE_CONFIG), + + dump_register(PIDF_BASE(0)), + dump_register(PIDF_BASE(1)), + dump_register(PIDF_BASE(2)), + dump_register(PIDF_BASE(3)), + dump_register(PIDF_BASE(4)), + dump_register(PIDF_BASE(5)), + dump_register(PIDF_BASE(6)), + dump_register(PIDF_BASE(7)), + dump_register(PIDF_BASE(8)), + dump_register(PIDF_BASE(9)), + dump_register(PIDF_BASE(10)), + dump_register(PIDF_BASE(11)), + dump_register(PIDF_BASE(12)), + dump_register(PIDF_BASE(13)), + dump_register(PIDF_BASE(14)), + dump_register(PIDF_BASE(15)), + dump_register(PIDF_BASE(16)), + dump_register(PIDF_BASE(17)), + dump_register(PIDF_BASE(18)), + dump_register(PIDF_BASE(19)), + dump_register(PIDF_BASE(20)), + dump_register(PIDF_BASE(21)), + dump_register(PIDF_BASE(22)), + dump_register(PIDF_LEAK_ENABLE), + dump_register(PIDF_LEAK_STATUS), + dump_register(PIDF_LEAK_COUNT_RESET), + dump_register(PIDF_LEAK_COUNTER), +}; + +void c8sectpfe_debugfs_init(struct c8sectpfei *fei) +{ + fei->regset = devm_kzalloc(fei->dev, sizeof(*fei->regset), GFP_KERNEL); + if (!fei->regset) + return; + + fei->regset->regs = fei_sys_regs; + fei->regset->nregs = ARRAY_SIZE(fei_sys_regs); + fei->regset->base = fei->io; + + fei->root = debugfs_create_dir("c8sectpfe", NULL); + debugfs_create_regset32("registers", S_IRUGO, fei->root, fei->regset); +} + +void c8sectpfe_debugfs_exit(struct c8sectpfei *fei) +{ + debugfs_remove_recursive(fei->root); + fei->root = NULL; +} diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-debugfs.h b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-debugfs.h new file mode 100644 index 000000000..d2c35fb32 --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-debugfs.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * c8sectpfe-debugfs.h - C8SECTPFE STi DVB driver debugfs header + * + * Copyright (c) STMicroelectronics 2015 + * + * Authors: Peter Griffin + */ + +#ifndef __C8SECTPFE_DEBUG_H +#define __C8SECTPFE_DEBUG_H + +#include "c8sectpfe-core.h" + +void c8sectpfe_debugfs_init(struct c8sectpfei *); +void c8sectpfe_debugfs_exit(struct c8sectpfei *); + +#endif /* __C8SECTPFE_DEBUG_H */ diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-dvb.c b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-dvb.c new file mode 100644 index 000000000..feb48cb54 --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-dvb.c @@ -0,0 +1,235 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * c8sectpfe-dvb.c - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author Peter Griffin + * + */ +#include +#include +#include +#include + +#include + +#include "c8sectpfe-common.h" +#include "c8sectpfe-core.h" +#include "c8sectpfe-dvb.h" + +#include "dvb-pll.h" +#include "lnbh24.h" +#include "stv0367.h" +#include "stv0367_priv.h" +#include "stv6110x.h" +#include "stv090x.h" +#include "tda18212.h" + +static inline const char *dvb_card_str(unsigned int c) +{ + switch (c) { + case STV0367_TDA18212_NIMA_1: return "STV0367_TDA18212_NIMA_1"; + case STV0367_TDA18212_NIMA_2: return "STV0367_TDA18212_NIMA_2"; + case STV0367_TDA18212_NIMB_1: return "STV0367_TDA18212_NIMB_1"; + case STV0367_TDA18212_NIMB_2: return "STV0367_TDA18212_NIMB_2"; + case STV0903_6110_LNB24_NIMA: return "STV0903_6110_LNB24_NIMA"; + case STV0903_6110_LNB24_NIMB: return "STV0903_6110_LNB24_NIMB"; + default: return "unknown dvb frontend card"; + } +} + +static struct stv090x_config stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + .xtal = 16000000, + .address = 0x69, + + .ts1_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + .ts2_mode = STV090x_TSMODE_SERIAL_CONTINUOUS, + + .repeater_level = STV090x_RPTLEVEL_64, + + .tuner_init = NULL, + .tuner_set_mode = NULL, + .tuner_set_frequency = NULL, + .tuner_get_frequency = NULL, + .tuner_set_bandwidth = NULL, + .tuner_get_bandwidth = NULL, + .tuner_set_bbgain = NULL, + .tuner_get_bbgain = NULL, + .tuner_set_refclk = NULL, + .tuner_get_status = NULL, +}; + +static struct stv6110x_config stv6110x_config = { + .addr = 0x60, + .refclk = 16000000, +}; + +#define NIMA 0 +#define NIMB 1 + +static struct stv0367_config stv0367_tda18212_config[] = { + { + .demod_address = 0x1c, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1d, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, { + .demod_address = 0x1e, + .xtal = 16000000, + .if_khz = 4500, + .if_iq_mode = FE_TER_NORMAL_IF_TUNER, + .ts_mode = STV0367_SERIAL_PUNCT_CLOCK, + .clk_pol = STV0367_CLOCKPOLARITY_DEFAULT, + }, +}; + +static struct tda18212_config tda18212_conf = { + .if_dvbt_6 = 4150, + .if_dvbt_7 = 4150, + .if_dvbt_8 = 4500, + .if_dvbc = 5000, +}; + +int c8sectpfe_frontend_attach(struct dvb_frontend **fe, + struct c8sectpfe *c8sectpfe, + struct channel_info *tsin, int chan_num) +{ + struct tda18212_config *tda18212; + const struct stv6110x_devctl *fe2; + struct i2c_client *client; + struct i2c_board_info tda18212_info = { + .type = "tda18212", + .addr = 0x60, + }; + + if (!tsin) + return -EINVAL; + + switch (tsin->dvb_card) { + + case STV0367_TDA18212_NIMA_1: + case STV0367_TDA18212_NIMA_2: + case STV0367_TDA18212_NIMB_1: + case STV0367_TDA18212_NIMB_2: + if (tsin->dvb_card == STV0367_TDA18212_NIMA_1) + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[0], + tsin->i2c_adapter); + else if (tsin->dvb_card == STV0367_TDA18212_NIMB_1) + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[1], + tsin->i2c_adapter); + else + *fe = dvb_attach(stv0367ter_attach, + &stv0367_tda18212_config[2], + tsin->i2c_adapter); + + if (!*fe) { + dev_err(c8sectpfe->device, + "%s: stv0367ter_attach failed for NIM card %s\n" + , __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } + + /* + * init the demod so that i2c gate_ctrl + * to the tuner works correctly + */ + (*fe)->ops.init(*fe); + + /* Allocate the tda18212 structure */ + tda18212 = devm_kzalloc(c8sectpfe->device, + sizeof(struct tda18212_config), + GFP_KERNEL); + if (!tda18212) { + dev_err(c8sectpfe->device, + "%s: devm_kzalloc failed\n", __func__); + return -ENOMEM; + } + + memcpy(tda18212, &tda18212_conf, + sizeof(struct tda18212_config)); + + tda18212->fe = (*fe); + + tda18212_info.platform_data = tda18212; + + /* attach tuner */ + request_module("tda18212"); + client = i2c_new_client_device(tsin->i2c_adapter, + &tda18212_info); + if (!i2c_client_has_driver(client)) { + dvb_frontend_detach(*fe); + return -ENODEV; + } + + if (!try_module_get(client->dev.driver->owner)) { + i2c_unregister_device(client); + dvb_frontend_detach(*fe); + return -ENODEV; + } + + tsin->i2c_client = client; + + break; + + case STV0903_6110_LNB24_NIMA: + *fe = dvb_attach(stv090x_attach, &stv090x_config, + tsin->i2c_adapter, STV090x_DEMODULATOR_0); + if (!*fe) { + dev_err(c8sectpfe->device, "%s: stv090x_attach failed\n" + "\tfor NIM card %s\n", + __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } + + fe2 = dvb_attach(stv6110x_attach, *fe, + &stv6110x_config, tsin->i2c_adapter); + if (!fe2) { + dev_err(c8sectpfe->device, + "%s: stv6110x_attach failed for NIM card %s\n" + , __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } + + stv090x_config.tuner_init = fe2->tuner_init; + stv090x_config.tuner_set_mode = fe2->tuner_set_mode; + stv090x_config.tuner_set_frequency = fe2->tuner_set_frequency; + stv090x_config.tuner_get_frequency = fe2->tuner_get_frequency; + stv090x_config.tuner_set_bandwidth = fe2->tuner_set_bandwidth; + stv090x_config.tuner_get_bandwidth = fe2->tuner_get_bandwidth; + stv090x_config.tuner_set_bbgain = fe2->tuner_set_bbgain; + stv090x_config.tuner_get_bbgain = fe2->tuner_get_bbgain; + stv090x_config.tuner_set_refclk = fe2->tuner_set_refclk; + stv090x_config.tuner_get_status = fe2->tuner_get_status; + + dvb_attach(lnbh24_attach, *fe, tsin->i2c_adapter, 0, 0, 0x9); + break; + + default: + dev_err(c8sectpfe->device, + "%s: DVB frontend card %s not yet supported\n", + __func__, dvb_card_str(tsin->dvb_card)); + return -ENODEV; + } + + (*fe)->id = chan_num; + + dev_info(c8sectpfe->device, + "DVB frontend card %s successfully attached", + dvb_card_str(tsin->dvb_card)); + return 0; +} diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-dvb.h b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-dvb.h new file mode 100644 index 000000000..3d87a9ae8 --- /dev/null +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-dvb.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * c8sectpfe-common.h - C8SECTPFE STi DVB driver + * + * Copyright (c) STMicroelectronics 2015 + * + * Author: Peter Griffin + * + */ +#ifndef _C8SECTPFE_DVB_H_ +#define _C8SECTPFE_DVB_H_ + +int c8sectpfe_frontend_attach(struct dvb_frontend **fe, + struct c8sectpfe *c8sectpfe, struct channel_info *tsin, + int chan_num); + +#endif diff --git a/drivers/media/platform/st/sti/delta/Kconfig b/drivers/media/platform/st/sti/delta/Kconfig new file mode 100644 index 000000000..efa936b1c --- /dev/null +++ b/drivers/media/platform/st/sti/delta/Kconfig @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_STI_DELTA + tristate "STMicroelectronics DELTA multi-format video decoder V4L2 driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_STI || COMPILE_TEST + help + This V4L2 driver enables DELTA multi-format video decoder + of STMicroelectronics STiH4xx SoC series allowing hardware + decoding of various compressed video bitstream format in + raw uncompressed format. + + Use this option to see the decoders available for such + hardware. + + Please notice that the driver will only be built if + at least one of the DELTA decoder below is selected. + +config VIDEO_STI_DELTA_MJPEG + bool "STMicroelectronics DELTA MJPEG support" + default y + depends on VIDEO_STI_DELTA + help + Enables DELTA MJPEG hardware support. + + To compile this driver as a module, choose M here: + the module will be called st-delta. + +config VIDEO_STI_DELTA_DRIVER + tristate + depends on VIDEO_STI_DELTA + depends on VIDEO_STI_DELTA_MJPEG + default VIDEO_STI_DELTA_MJPEG + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select RPMSG diff --git a/drivers/media/platform/st/sti/delta/Makefile b/drivers/media/platform/st/sti/delta/Makefile new file mode 100644 index 000000000..32412fa4c --- /dev/null +++ b/drivers/media/platform/st/sti/delta/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_STI_DELTA_DRIVER) += st-delta.o +st-delta-y := delta-v4l2.o delta-mem.o delta-ipc.o delta-debug.o + +# MJPEG support +st-delta-$(CONFIG_VIDEO_STI_DELTA_MJPEG) += delta-mjpeg-hdr.o +st-delta-$(CONFIG_VIDEO_STI_DELTA_MJPEG) += delta-mjpeg-dec.o diff --git a/drivers/media/platform/st/sti/delta/delta-cfg.h b/drivers/media/platform/st/sti/delta/delta-cfg.h new file mode 100644 index 000000000..f47c6e6ff --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-cfg.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#ifndef DELTA_CFG_H +#define DELTA_CFG_H + +#define DELTA_FW_VERSION "21.1-3" + +#define DELTA_MIN_WIDTH 32 +#define DELTA_MAX_WIDTH 4096 +#define DELTA_MIN_HEIGHT 32 +#define DELTA_MAX_HEIGHT 2400 + +/* DELTA requires a 32x32 pixels alignment for frames */ +#define DELTA_WIDTH_ALIGNMENT 32 +#define DELTA_HEIGHT_ALIGNMENT 32 + +#define DELTA_DEFAULT_WIDTH DELTA_MIN_WIDTH +#define DELTA_DEFAULT_HEIGHT DELTA_MIN_HEIGHT +#define DELTA_DEFAULT_FRAMEFORMAT V4L2_PIX_FMT_NV12 +#define DELTA_DEFAULT_STREAMFORMAT V4L2_PIX_FMT_MJPEG + +#define DELTA_MAX_RESO (DELTA_MAX_WIDTH * DELTA_MAX_HEIGHT) + +/* guard value for number of access units */ +#define DELTA_MAX_AUS 10 + +/* IP perf dependent, can be tuned */ +#define DELTA_PEAK_FRAME_SMOOTHING 2 + +/* + * guard output frame count: + * - at least 1 frame needed for display + * - at worst 21 + * ( max h264 dpb (16) + + * decoding peak smoothing (2) + + * user display pipeline (3) ) + */ +#define DELTA_MIN_FRAME_USER 1 +#define DELTA_MAX_DPB 16 +#define DELTA_MAX_FRAME_USER 3 /* platform/use-case dependent */ +#define DELTA_MAX_FRAMES (DELTA_MAX_DPB + DELTA_PEAK_FRAME_SMOOTHING +\ + DELTA_MAX_FRAME_USER) + +#if DELTA_MAX_FRAMES > VIDEO_MAX_FRAME +#undef DELTA_MAX_FRAMES +#define DELTA_MAX_FRAMES (VIDEO_MAX_FRAME) +#endif + +/* extra space to be allocated to store codec specific data per frame */ +#define DELTA_MAX_FRAME_PRIV_SIZE 100 + +/* PM runtime auto power-off after 5ms of inactivity */ +#define DELTA_HW_AUTOSUSPEND_DELAY_MS 5 + +#define DELTA_MAX_DECODERS 10 +#ifdef CONFIG_VIDEO_STI_DELTA_MJPEG +extern const struct delta_dec mjpegdec; +#endif + +#endif /* DELTA_CFG_H */ diff --git a/drivers/media/platform/st/sti/delta/delta-debug.c b/drivers/media/platform/st/sti/delta/delta-debug.c new file mode 100644 index 000000000..4b2eb6b63 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-debug.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Hugues Fruchet + * Fabrice Lecoultre + * for STMicroelectronics. + */ + +#include "delta.h" +#include "delta-debug.h" + +char *delta_streaminfo_str(struct delta_streaminfo *s, char *str, + unsigned int len) +{ + if (!s) + return NULL; + + snprintf(str, len, + "%4.4s %dx%d %s %s dpb=%d %s %s %s%dx%d@(%d,%d) %s%d/%d", + (char *)&s->streamformat, s->width, s->height, + s->profile, s->level, s->dpb, + (s->field == V4L2_FIELD_NONE) ? "progressive" : "interlaced", + s->other, + s->flags & DELTA_STREAMINFO_FLAG_CROP ? "crop=" : "", + s->crop.width, s->crop.height, + s->crop.left, s->crop.top, + s->flags & DELTA_STREAMINFO_FLAG_PIXELASPECT ? "par=" : "", + s->pixelaspect.numerator, + s->pixelaspect.denominator); + + return str; +} + +char *delta_frameinfo_str(struct delta_frameinfo *f, char *str, + unsigned int len) +{ + if (!f) + return NULL; + + snprintf(str, len, + "%4.4s %dx%d aligned %dx%d %s %s%dx%d@(%d,%d) %s%d/%d", + (char *)&f->pixelformat, f->width, f->height, + f->aligned_width, f->aligned_height, + (f->field == V4L2_FIELD_NONE) ? "progressive" : "interlaced", + f->flags & DELTA_STREAMINFO_FLAG_CROP ? "crop=" : "", + f->crop.width, f->crop.height, + f->crop.left, f->crop.top, + f->flags & DELTA_STREAMINFO_FLAG_PIXELASPECT ? "par=" : "", + f->pixelaspect.numerator, + f->pixelaspect.denominator); + + return str; +} + +void delta_trace_summary(struct delta_ctx *ctx) +{ + struct delta_dev *delta = ctx->dev; + struct delta_streaminfo *s = &ctx->streaminfo; + unsigned char str[100] = ""; + + if (!(ctx->flags & DELTA_FLAG_STREAMINFO)) + return; + + dev_dbg(delta->dev, "%s %s, %d frames decoded, %d frames output, %d frames dropped, %d stream errors, %d decode errors", + ctx->name, + delta_streaminfo_str(s, str, sizeof(str)), + ctx->decoded_frames, + ctx->output_frames, + ctx->dropped_frames, + ctx->stream_errors, + ctx->decode_errors); +} diff --git a/drivers/media/platform/st/sti/delta/delta-debug.h b/drivers/media/platform/st/sti/delta/delta-debug.h new file mode 100644 index 000000000..fa9025262 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-debug.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Hugues Fruchet + * Fabrice Lecoultre + * for STMicroelectronics. + */ + +#ifndef DELTA_DEBUG_H +#define DELTA_DEBUG_H + +char *delta_streaminfo_str(struct delta_streaminfo *s, char *str, + unsigned int len); +char *delta_frameinfo_str(struct delta_frameinfo *f, char *str, + unsigned int len); +void delta_trace_summary(struct delta_ctx *ctx); + +#endif /* DELTA_DEBUG_H */ diff --git a/drivers/media/platform/st/sti/delta/delta-ipc.c b/drivers/media/platform/st/sti/delta/delta-ipc.c new file mode 100644 index 000000000..21d3e08e2 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-ipc.c @@ -0,0 +1,591 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#include + +#include "delta.h" +#include "delta-ipc.h" +#include "delta-mem.h" + +#define IPC_TIMEOUT 100 +#define IPC_SANITY_TAG 0xDEADBEEF + +enum delta_ipc_fw_command { + DELTA_IPC_OPEN, + DELTA_IPC_SET_STREAM, + DELTA_IPC_DECODE, + DELTA_IPC_CLOSE +}; + +#define to_rpmsg_driver(__drv) container_of(__drv, struct rpmsg_driver, drv) +#define to_delta(__d) container_of(__d, struct delta_dev, rpmsg_driver) + +#define to_ctx(hdl) ((struct delta_ipc_ctx *)hdl) +#define to_pctx(ctx) container_of(ctx, struct delta_ctx, ipc_ctx) + +struct delta_ipc_header_msg { + u32 tag; + void *host_hdl; + u32 copro_hdl; + u32 command; +}; + +#define to_host_hdl(ctx) ((void *)ctx) + +#define msg_to_ctx(msg) ((struct delta_ipc_ctx *)(msg)->header.host_hdl) +#define msg_to_copro_hdl(msg) ((msg)->header.copro_hdl) + +static inline dma_addr_t to_paddr(struct delta_ipc_ctx *ctx, void *vaddr) +{ + return (ctx->ipc_buf->paddr + (vaddr - ctx->ipc_buf->vaddr)); +} + +static inline bool is_valid_data(struct delta_ipc_ctx *ctx, + void *data, u32 size) +{ + return ((data >= ctx->ipc_buf->vaddr) && + ((data + size) <= (ctx->ipc_buf->vaddr + ctx->ipc_buf->size))); +} + +/* + * IPC shared memory (@ipc_buf_size, @ipc_buf_paddr) is sent to copro + * at each instance opening. This memory is allocated by IPC client + * and given through delta_ipc_open(). All messages parameters + * (open, set_stream, decode) will have their phy address within + * this IPC shared memory, avoiding de-facto recopies inside delta-ipc. + * All the below messages structures are used on both host and firmware + * side and are packed (use only of 32 bits size fields in messages + * structures to ensure packing): + * - struct delta_ipc_open_msg + * - struct delta_ipc_set_stream_msg + * - struct delta_ipc_decode_msg + * - struct delta_ipc_close_msg + * - struct delta_ipc_cb_msg + */ +struct delta_ipc_open_msg { + struct delta_ipc_header_msg header; + u32 ipc_buf_size; + dma_addr_t ipc_buf_paddr; + char name[32]; + u32 param_size; + dma_addr_t param_paddr; +}; + +struct delta_ipc_set_stream_msg { + struct delta_ipc_header_msg header; + u32 param_size; + dma_addr_t param_paddr; +}; + +struct delta_ipc_decode_msg { + struct delta_ipc_header_msg header; + u32 param_size; + dma_addr_t param_paddr; + u32 status_size; + dma_addr_t status_paddr; +}; + +struct delta_ipc_close_msg { + struct delta_ipc_header_msg header; +}; + +struct delta_ipc_cb_msg { + struct delta_ipc_header_msg header; + int err; +}; + +static void build_msg_header(struct delta_ipc_ctx *ctx, + enum delta_ipc_fw_command command, + struct delta_ipc_header_msg *header) +{ + header->tag = IPC_SANITY_TAG; + header->host_hdl = to_host_hdl(ctx); + header->copro_hdl = ctx->copro_hdl; + header->command = command; +} + +int delta_ipc_open(struct delta_ctx *pctx, const char *name, + struct delta_ipc_param *param, u32 ipc_buf_size, + struct delta_buf **ipc_buf, void **hdl) +{ + struct delta_dev *delta = pctx->dev; + struct rpmsg_device *rpmsg_device = delta->rpmsg_device; + struct delta_ipc_ctx *ctx = &pctx->ipc_ctx; + struct delta_ipc_open_msg msg; + struct delta_buf *buf = &ctx->ipc_buf_struct; + int ret; + + if (!rpmsg_device) { + dev_err(delta->dev, + "%s ipc: failed to open, rpmsg is not initialized\n", + pctx->name); + pctx->sys_errors++; + return -EINVAL; + } + + if (!name) { + dev_err(delta->dev, + "%s ipc: failed to open, no name given\n", + pctx->name); + return -EINVAL; + } + + if (!param || !param->data || !param->size) { + dev_err(delta->dev, + "%s ipc: failed to open, empty parameter\n", + pctx->name); + return -EINVAL; + } + + if (!ipc_buf_size) { + dev_err(delta->dev, + "%s ipc: failed to open, no size given for ipc buffer\n", + pctx->name); + return -EINVAL; + } + + if (param->size > ipc_buf_size) { + dev_err(delta->dev, + "%s ipc: failed to open, too large ipc parameter (%d bytes while max %d expected)\n", + pctx->name, + param->size, ctx->ipc_buf->size); + return -EINVAL; + } + + /* init */ + init_completion(&ctx->done); + + /* + * allocation of contiguous buffer for + * data of commands exchanged between + * host and firmware coprocessor + */ + ret = hw_alloc(pctx, ipc_buf_size, + "ipc data buffer", buf); + if (ret) + return ret; + ctx->ipc_buf = buf; + + /* build rpmsg message */ + build_msg_header(ctx, DELTA_IPC_OPEN, &msg.header); + + msg.ipc_buf_size = ipc_buf_size; + msg.ipc_buf_paddr = ctx->ipc_buf->paddr; + + strscpy(msg.name, name, sizeof(msg.name)); + + msg.param_size = param->size; + memcpy(ctx->ipc_buf->vaddr, param->data, msg.param_size); + msg.param_paddr = ctx->ipc_buf->paddr; + + /* send it */ + ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg)); + if (ret) { + dev_err(delta->dev, + "%s ipc: failed to open, rpmsg_send failed (%d) for DELTA_IPC_OPEN (name=%s, size=%d, data=%p)\n", + pctx->name, + ret, name, param->size, param->data); + goto err; + } + + /* wait for acknowledge */ + if (!wait_for_completion_timeout + (&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) { + dev_err(delta->dev, + "%s ipc: failed to open, timeout waiting for DELTA_IPC_OPEN callback (name=%s, size=%d, data=%p)\n", + pctx->name, + name, param->size, param->data); + ret = -ETIMEDOUT; + goto err; + } + + /* command completed, check error */ + if (ctx->cb_err) { + dev_err(delta->dev, + "%s ipc: failed to open, DELTA_IPC_OPEN completed but with error (%d) (name=%s, size=%d, data=%p)\n", + pctx->name, + ctx->cb_err, name, param->size, param->data); + ret = -EIO; + goto err; + } + + *ipc_buf = ctx->ipc_buf; + *hdl = (void *)ctx; + + return 0; + +err: + pctx->sys_errors++; + hw_free(pctx, ctx->ipc_buf); + ctx->ipc_buf = NULL; + + return ret; +}; + +int delta_ipc_set_stream(void *hdl, struct delta_ipc_param *param) +{ + struct delta_ipc_ctx *ctx = to_ctx(hdl); + struct delta_ctx *pctx = to_pctx(ctx); + struct delta_dev *delta = pctx->dev; + struct rpmsg_device *rpmsg_device = delta->rpmsg_device; + struct delta_ipc_set_stream_msg msg; + int ret; + + if (!hdl) { + dev_err(delta->dev, + "%s ipc: failed to set stream, invalid ipc handle\n", + pctx->name); + return -EINVAL; + } + + if (!rpmsg_device) { + dev_err(delta->dev, + "%s ipc: failed to set stream, rpmsg is not initialized\n", + pctx->name); + return -EINVAL; + } + + if (!param || !param->data || !param->size) { + dev_err(delta->dev, + "%s ipc: failed to set stream, empty parameter\n", + pctx->name); + return -EINVAL; + } + + if (param->size > ctx->ipc_buf->size) { + dev_err(delta->dev, + "%s ipc: failed to set stream, too large ipc parameter(%d bytes while max %d expected)\n", + pctx->name, + param->size, ctx->ipc_buf->size); + return -EINVAL; + } + + if (!is_valid_data(ctx, param->data, param->size)) { + dev_err(delta->dev, + "%s ipc: failed to set stream, parameter is not in expected address range (size=%d, data=%p not in %p..%p)\n", + pctx->name, + param->size, + param->data, + ctx->ipc_buf->vaddr, + ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1); + return -EINVAL; + } + + /* build rpmsg message */ + build_msg_header(ctx, DELTA_IPC_SET_STREAM, &msg.header); + + msg.param_size = param->size; + msg.param_paddr = to_paddr(ctx, param->data); + + /* send it */ + ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg)); + if (ret) { + dev_err(delta->dev, + "%s ipc: failed to set stream, rpmsg_send failed (%d) for DELTA_IPC_SET_STREAM (size=%d, data=%p)\n", + pctx->name, + ret, param->size, param->data); + pctx->sys_errors++; + return ret; + } + + /* wait for acknowledge */ + if (!wait_for_completion_timeout + (&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) { + dev_err(delta->dev, + "%s ipc: failed to set stream, timeout waiting for DELTA_IPC_SET_STREAM callback (size=%d, data=%p)\n", + pctx->name, + param->size, param->data); + pctx->sys_errors++; + return -ETIMEDOUT; + } + + /* command completed, check status */ + if (ctx->cb_err) { + dev_err(delta->dev, + "%s ipc: failed to set stream, DELTA_IPC_SET_STREAM completed but with error (%d) (size=%d, data=%p)\n", + pctx->name, + ctx->cb_err, param->size, param->data); + pctx->sys_errors++; + return -EIO; + } + + return 0; +} + +int delta_ipc_decode(void *hdl, struct delta_ipc_param *param, + struct delta_ipc_param *status) +{ + struct delta_ipc_ctx *ctx = to_ctx(hdl); + struct delta_ctx *pctx = to_pctx(ctx); + struct delta_dev *delta = pctx->dev; + struct rpmsg_device *rpmsg_device = delta->rpmsg_device; + struct delta_ipc_decode_msg msg; + int ret; + + if (!hdl) { + dev_err(delta->dev, + "%s ipc: failed to decode, invalid ipc handle\n", + pctx->name); + return -EINVAL; + } + + if (!rpmsg_device) { + dev_err(delta->dev, + "%s ipc: failed to decode, rpmsg is not initialized\n", + pctx->name); + return -EINVAL; + } + + if (!param || !param->data || !param->size) { + dev_err(delta->dev, + "%s ipc: failed to decode, empty parameter\n", + pctx->name); + return -EINVAL; + } + + if (!status || !status->data || !status->size) { + dev_err(delta->dev, + "%s ipc: failed to decode, empty status\n", + pctx->name); + return -EINVAL; + } + + if (param->size + status->size > ctx->ipc_buf->size) { + dev_err(delta->dev, + "%s ipc: failed to decode, too large ipc parameter (%d bytes (param) + %d bytes (status) while max %d expected)\n", + pctx->name, + param->size, + status->size, + ctx->ipc_buf->size); + return -EINVAL; + } + + if (!is_valid_data(ctx, param->data, param->size)) { + dev_err(delta->dev, + "%s ipc: failed to decode, parameter is not in expected address range (size=%d, data=%p not in %p..%p)\n", + pctx->name, + param->size, + param->data, + ctx->ipc_buf->vaddr, + ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1); + return -EINVAL; + } + + if (!is_valid_data(ctx, status->data, status->size)) { + dev_err(delta->dev, + "%s ipc: failed to decode, status is not in expected address range (size=%d, data=%p not in %p..%p)\n", + pctx->name, + status->size, + status->data, + ctx->ipc_buf->vaddr, + ctx->ipc_buf->vaddr + ctx->ipc_buf->size - 1); + return -EINVAL; + } + + /* build rpmsg message */ + build_msg_header(ctx, DELTA_IPC_DECODE, &msg.header); + + msg.param_size = param->size; + msg.param_paddr = to_paddr(ctx, param->data); + + msg.status_size = status->size; + msg.status_paddr = to_paddr(ctx, status->data); + + /* send it */ + ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg)); + if (ret) { + dev_err(delta->dev, + "%s ipc: failed to decode, rpmsg_send failed (%d) for DELTA_IPC_DECODE (size=%d, data=%p)\n", + pctx->name, + ret, param->size, param->data); + pctx->sys_errors++; + return ret; + } + + /* wait for acknowledge */ + if (!wait_for_completion_timeout + (&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) { + dev_err(delta->dev, + "%s ipc: failed to decode, timeout waiting for DELTA_IPC_DECODE callback (size=%d, data=%p)\n", + pctx->name, + param->size, param->data); + pctx->sys_errors++; + return -ETIMEDOUT; + } + + /* command completed, check status */ + if (ctx->cb_err) { + dev_err(delta->dev, + "%s ipc: failed to decode, DELTA_IPC_DECODE completed but with error (%d) (size=%d, data=%p)\n", + pctx->name, + ctx->cb_err, param->size, param->data); + pctx->sys_errors++; + return -EIO; + } + + return 0; +}; + +void delta_ipc_close(void *hdl) +{ + struct delta_ipc_ctx *ctx = to_ctx(hdl); + struct delta_ctx *pctx = to_pctx(ctx); + struct delta_dev *delta = pctx->dev; + struct rpmsg_device *rpmsg_device = delta->rpmsg_device; + struct delta_ipc_close_msg msg; + int ret; + + if (!hdl) { + dev_err(delta->dev, + "%s ipc: failed to close, invalid ipc handle\n", + pctx->name); + return; + } + + if (ctx->ipc_buf) { + hw_free(pctx, ctx->ipc_buf); + ctx->ipc_buf = NULL; + } + + if (!rpmsg_device) { + dev_err(delta->dev, + "%s ipc: failed to close, rpmsg is not initialized\n", + pctx->name); + return; + } + + /* build rpmsg message */ + build_msg_header(ctx, DELTA_IPC_CLOSE, &msg.header); + + /* send it */ + ret = rpmsg_send(rpmsg_device->ept, &msg, sizeof(msg)); + if (ret) { + dev_err(delta->dev, + "%s ipc: failed to close, rpmsg_send failed (%d) for DELTA_IPC_CLOSE\n", + pctx->name, ret); + pctx->sys_errors++; + return; + } + + /* wait for acknowledge */ + if (!wait_for_completion_timeout + (&ctx->done, msecs_to_jiffies(IPC_TIMEOUT))) { + dev_err(delta->dev, + "%s ipc: failed to close, timeout waiting for DELTA_IPC_CLOSE callback\n", + pctx->name); + pctx->sys_errors++; + return; + } + + /* command completed, check status */ + if (ctx->cb_err) { + dev_err(delta->dev, + "%s ipc: failed to close, DELTA_IPC_CLOSE completed but with error (%d)\n", + pctx->name, ctx->cb_err); + pctx->sys_errors++; + } +}; + +static int delta_ipc_cb(struct rpmsg_device *rpdev, void *data, + int len, void *priv, u32 src) +{ + struct delta_ipc_ctx *ctx; + struct delta_ipc_cb_msg *msg; + + /* sanity check */ + if (!rpdev) { + dev_err(NULL, "rpdev is NULL\n"); + return -EINVAL; + } + + if (!data || !len) { + dev_err(&rpdev->dev, + "unexpected empty message received from src=%d\n", src); + return -EINVAL; + } + + if (len != sizeof(*msg)) { + dev_err(&rpdev->dev, + "unexpected message length received from src=%d (received %d bytes while %zu bytes expected)\n", + len, src, sizeof(*msg)); + return -EINVAL; + } + + msg = (struct delta_ipc_cb_msg *)data; + if (msg->header.tag != IPC_SANITY_TAG) { + dev_err(&rpdev->dev, + "unexpected message tag received from src=%d (received %x tag while %x expected)\n", + src, msg->header.tag, IPC_SANITY_TAG); + return -EINVAL; + } + + ctx = msg_to_ctx(msg); + if (!ctx) { + dev_err(&rpdev->dev, + "unexpected message with NULL host_hdl received from src=%d\n", + src); + return -EINVAL; + } + + /* + * if not already known, save copro instance context + * to ensure re-entrance on copro side + */ + if (!ctx->copro_hdl) + ctx->copro_hdl = msg_to_copro_hdl(msg); + + /* + * all is fine, + * update status & complete command + */ + ctx->cb_err = msg->err; + complete(&ctx->done); + + return 0; +} + +static int delta_ipc_probe(struct rpmsg_device *rpmsg_device) +{ + struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpmsg_device->dev.driver); + struct delta_dev *delta = to_delta(rpdrv); + + delta->rpmsg_device = rpmsg_device; + + return 0; +} + +static void delta_ipc_remove(struct rpmsg_device *rpmsg_device) +{ + struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpmsg_device->dev.driver); + struct delta_dev *delta = to_delta(rpdrv); + + delta->rpmsg_device = NULL; +} + +static struct rpmsg_device_id delta_ipc_device_id_table[] = { + {.name = "rpmsg-delta"}, + {}, +}; + +static struct rpmsg_driver delta_rpmsg_driver = { + .drv = {.name = KBUILD_MODNAME}, + .id_table = delta_ipc_device_id_table, + .probe = delta_ipc_probe, + .callback = delta_ipc_cb, + .remove = delta_ipc_remove, +}; + +int delta_ipc_init(struct delta_dev *delta) +{ + delta->rpmsg_driver = delta_rpmsg_driver; + + return register_rpmsg_driver(&delta->rpmsg_driver); +} + +void delta_ipc_exit(struct delta_dev *delta) +{ + unregister_rpmsg_driver(&delta->rpmsg_driver); +} diff --git a/drivers/media/platform/st/sti/delta/delta-ipc.h b/drivers/media/platform/st/sti/delta/delta-ipc.h new file mode 100644 index 000000000..9fba6b5d1 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-ipc.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#ifndef DELTA_IPC_H +#define DELTA_IPC_H + +int delta_ipc_init(struct delta_dev *delta); +void delta_ipc_exit(struct delta_dev *delta); + +/* + * delta_ipc_open - open a decoding instance on firmware side + * @ctx: (in) delta context + * @name: (in) name of decoder to be used + * @param: (in) open command parameters specific to decoder + * @param.size: (in) size of parameter + * @param.data: (in) virtual address of parameter + * @ipc_buf_size: (in) size of IPC shared buffer between host + * and copro used to share command data. + * Client have to set here the size of the biggest + * command parameters (+ status if any). + * Allocation will be done in this function which + * will give back to client in @ipc_buf the virtual + * & physical addresses & size of shared IPC buffer. + * All the further command data (parameters + status) + * have to be written in this shared IPC buffer + * virtual memory. This is done to avoid + * unnecessary copies of command data. + * @ipc_buf: (out) allocated IPC shared buffer + * @ipc_buf.size: (out) allocated size + * @ipc_buf.vaddr: (out) virtual address where to copy + * further command data + * @hdl: (out) handle of decoding instance. + */ + +int delta_ipc_open(struct delta_ctx *ctx, const char *name, + struct delta_ipc_param *param, u32 ipc_buf_size, + struct delta_buf **ipc_buf, void **hdl); + +/* + * delta_ipc_set_stream - set information about stream to decoder + * @hdl: (in) handle of decoding instance. + * @param: (in) set stream command parameters specific to decoder + * @param.size: (in) size of parameter + * @param.data: (in) virtual address of parameter. Must be + * within IPC shared buffer range + */ +int delta_ipc_set_stream(void *hdl, struct delta_ipc_param *param); + +/* + * delta_ipc_decode - frame decoding synchronous request, returns only + * after decoding completion on firmware side. + * @hdl: (in) handle of decoding instance. + * @param: (in) decode command parameters specific to decoder + * @param.size: (in) size of parameter + * @param.data: (in) virtual address of parameter. Must be + * within IPC shared buffer range + * @status: (in/out) decode command status specific to decoder + * @status.size: (in) size of status + * @status.data: (in/out) virtual address of status. Must be + * within IPC shared buffer range. + * Status is filled by decoding instance + * after decoding completion. + */ +int delta_ipc_decode(void *hdl, struct delta_ipc_param *param, + struct delta_ipc_param *status); + +/* + * delta_ipc_close - close decoding instance + * @hdl: (in) handle of decoding instance to close. + */ +void delta_ipc_close(void *hdl); + +#endif /* DELTA_IPC_H */ diff --git a/drivers/media/platform/st/sti/delta/delta-mem.c b/drivers/media/platform/st/sti/delta/delta-mem.c new file mode 100644 index 000000000..aeccd5058 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-mem.c @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#include "delta.h" +#include "delta-mem.h" + +int hw_alloc(struct delta_ctx *ctx, u32 size, const char *name, + struct delta_buf *buf) +{ + struct delta_dev *delta = ctx->dev; + dma_addr_t dma_addr; + void *addr; + unsigned long attrs = DMA_ATTR_WRITE_COMBINE; + + addr = dma_alloc_attrs(delta->dev, size, &dma_addr, + GFP_KERNEL | __GFP_NOWARN, attrs); + if (!addr) { + dev_err(delta->dev, + "%s hw_alloc:dma_alloc_coherent failed for %s (size=%d)\n", + ctx->name, name, size); + ctx->sys_errors++; + return -ENOMEM; + } + + buf->size = size; + buf->paddr = dma_addr; + buf->vaddr = addr; + buf->name = name; + buf->attrs = attrs; + + dev_dbg(delta->dev, + "%s allocate %d bytes of HW memory @(virt=0x%p, phy=0x%pad): %s\n", + ctx->name, size, buf->vaddr, &buf->paddr, buf->name); + + return 0; +} + +void hw_free(struct delta_ctx *ctx, struct delta_buf *buf) +{ + struct delta_dev *delta = ctx->dev; + + dev_dbg(delta->dev, + "%s free %d bytes of HW memory @(virt=0x%p, phy=0x%pad): %s\n", + ctx->name, buf->size, buf->vaddr, &buf->paddr, buf->name); + + dma_free_attrs(delta->dev, buf->size, + buf->vaddr, buf->paddr, buf->attrs); +} diff --git a/drivers/media/platform/st/sti/delta/delta-mem.h b/drivers/media/platform/st/sti/delta/delta-mem.h new file mode 100644 index 000000000..ff7d02f00 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-mem.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#ifndef DELTA_MEM_H +#define DELTA_MEM_H + +int hw_alloc(struct delta_ctx *ctx, u32 size, const char *name, + struct delta_buf *buf); +void hw_free(struct delta_ctx *ctx, struct delta_buf *buf); + +#endif /* DELTA_MEM_H */ diff --git a/drivers/media/platform/st/sti/delta/delta-mjpeg-dec.c b/drivers/media/platform/st/sti/delta/delta-mjpeg-dec.c new file mode 100644 index 000000000..0533d4a08 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-mjpeg-dec.c @@ -0,0 +1,455 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#include + +#include "delta.h" +#include "delta-ipc.h" +#include "delta-mjpeg.h" +#include "delta-mjpeg-fw.h" + +#define DELTA_MJPEG_MAX_RESO DELTA_MAX_RESO + +struct delta_mjpeg_ctx { + /* jpeg header */ + struct mjpeg_header header_struct; + struct mjpeg_header *header; + + /* ipc */ + void *ipc_hdl; + struct delta_buf *ipc_buf; + + /* decoded output frame */ + struct delta_frame *out_frame; + + unsigned char str[3000]; +}; + +#define to_ctx(ctx) ((struct delta_mjpeg_ctx *)(ctx)->priv) + +static char *ipc_open_param_str(struct jpeg_video_decode_init_params_t *p, + char *str, unsigned int len) +{ + char *b = str; + + if (!p) + return ""; + + b += snprintf(b, len, + "jpeg_video_decode_init_params_t\n" + "circular_buffer_begin_addr_p 0x%x\n" + "circular_buffer_end_addr_p 0x%x\n", + p->circular_buffer_begin_addr_p, + p->circular_buffer_end_addr_p); + + return str; +} + +static char *ipc_decode_param_str(struct jpeg_decode_params_t *p, + char *str, unsigned int len) +{ + char *b = str; + + if (!p) + return ""; + + b += snprintf(b, len, + "jpeg_decode_params_t\n" + "picture_start_addr_p 0x%x\n" + "picture_end_addr_p 0x%x\n" + "decoding_mode %d\n" + "display_buffer_addr.display_decimated_luma_p 0x%x\n" + "display_buffer_addr.display_decimated_chroma_p 0x%x\n" + "main_aux_enable %d\n" + "additional_flags 0x%x\n" + "field_flag %x\n" + "is_jpeg_image %x\n", + p->picture_start_addr_p, + p->picture_end_addr_p, + p->decoding_mode, + p->display_buffer_addr.display_decimated_luma_p, + p->display_buffer_addr.display_decimated_chroma_p, + p->main_aux_enable, p->additional_flags, + p->field_flag, + p->is_jpeg_image); + + return str; +} + +static inline bool is_stream_error(enum jpeg_decoding_error_t err) +{ + switch (err) { + case JPEG_DECODER_UNDEFINED_HUFF_TABLE: + case JPEG_DECODER_BAD_RESTART_MARKER: + case JPEG_DECODER_BAD_SOS_SPECTRAL: + case JPEG_DECODER_BAD_SOS_SUCCESSIVE: + case JPEG_DECODER_BAD_HEADER_LENGTH: + case JPEG_DECODER_BAD_COUNT_VALUE: + case JPEG_DECODER_BAD_DHT_MARKER: + case JPEG_DECODER_BAD_INDEX_VALUE: + case JPEG_DECODER_BAD_NUMBER_HUFFMAN_TABLES: + case JPEG_DECODER_BAD_QUANT_TABLE_LENGTH: + case JPEG_DECODER_BAD_NUMBER_QUANT_TABLES: + case JPEG_DECODER_BAD_COMPONENT_COUNT: + return true; + default: + return false; + } +} + +static inline const char *err_str(enum jpeg_decoding_error_t err) +{ + switch (err) { + case JPEG_DECODER_NO_ERROR: + return "JPEG_DECODER_NO_ERROR"; + case JPEG_DECODER_UNDEFINED_HUFF_TABLE: + return "JPEG_DECODER_UNDEFINED_HUFF_TABLE"; + case JPEG_DECODER_UNSUPPORTED_MARKER: + return "JPEG_DECODER_UNSUPPORTED_MARKER"; + case JPEG_DECODER_UNABLE_ALLOCATE_MEMORY: + return "JPEG_DECODER_UNABLE_ALLOCATE_MEMORY"; + case JPEG_DECODER_NON_SUPPORTED_SAMP_FACTORS: + return "JPEG_DECODER_NON_SUPPORTED_SAMP_FACTORS"; + case JPEG_DECODER_BAD_PARAMETER: + return "JPEG_DECODER_BAD_PARAMETER"; + case JPEG_DECODER_DECODE_ERROR: + return "JPEG_DECODER_DECODE_ERROR"; + case JPEG_DECODER_BAD_RESTART_MARKER: + return "JPEG_DECODER_BAD_RESTART_MARKER"; + case JPEG_DECODER_UNSUPPORTED_COLORSPACE: + return "JPEG_DECODER_UNSUPPORTED_COLORSPACE"; + case JPEG_DECODER_BAD_SOS_SPECTRAL: + return "JPEG_DECODER_BAD_SOS_SPECTRAL"; + case JPEG_DECODER_BAD_SOS_SUCCESSIVE: + return "JPEG_DECODER_BAD_SOS_SUCCESSIVE"; + case JPEG_DECODER_BAD_HEADER_LENGTH: + return "JPEG_DECODER_BAD_HEADER_LENGTH"; + case JPEG_DECODER_BAD_COUNT_VALUE: + return "JPEG_DECODER_BAD_COUNT_VALUE"; + case JPEG_DECODER_BAD_DHT_MARKER: + return "JPEG_DECODER_BAD_DHT_MARKER"; + case JPEG_DECODER_BAD_INDEX_VALUE: + return "JPEG_DECODER_BAD_INDEX_VALUE"; + case JPEG_DECODER_BAD_NUMBER_HUFFMAN_TABLES: + return "JPEG_DECODER_BAD_NUMBER_HUFFMAN_TABLES"; + case JPEG_DECODER_BAD_QUANT_TABLE_LENGTH: + return "JPEG_DECODER_BAD_QUANT_TABLE_LENGTH"; + case JPEG_DECODER_BAD_NUMBER_QUANT_TABLES: + return "JPEG_DECODER_BAD_NUMBER_QUANT_TABLES"; + case JPEG_DECODER_BAD_COMPONENT_COUNT: + return "JPEG_DECODER_BAD_COMPONENT_COUNT"; + case JPEG_DECODER_DIVIDE_BY_ZERO_ERROR: + return "JPEG_DECODER_DIVIDE_BY_ZERO_ERROR"; + case JPEG_DECODER_NOT_JPG_IMAGE: + return "JPEG_DECODER_NOT_JPG_IMAGE"; + case JPEG_DECODER_UNSUPPORTED_ROTATION_ANGLE: + return "JPEG_DECODER_UNSUPPORTED_ROTATION_ANGLE"; + case JPEG_DECODER_UNSUPPORTED_SCALING: + return "JPEG_DECODER_UNSUPPORTED_SCALING"; + case JPEG_DECODER_INSUFFICIENT_OUTPUTBUFFER_SIZE: + return "JPEG_DECODER_INSUFFICIENT_OUTPUTBUFFER_SIZE"; + case JPEG_DECODER_BAD_HWCFG_GP_VERSION_VALUE: + return "JPEG_DECODER_BAD_HWCFG_GP_VERSION_VALUE"; + case JPEG_DECODER_BAD_VALUE_FROM_RED: + return "JPEG_DECODER_BAD_VALUE_FROM_RED"; + case JPEG_DECODER_BAD_SUBREGION_PARAMETERS: + return "JPEG_DECODER_BAD_SUBREGION_PARAMETERS"; + case JPEG_DECODER_PROGRESSIVE_DECODE_NOT_SUPPORTED: + return "JPEG_DECODER_PROGRESSIVE_DECODE_NOT_SUPPORTED"; + case JPEG_DECODER_ERROR_TASK_TIMEOUT: + return "JPEG_DECODER_ERROR_TASK_TIMEOUT"; + case JPEG_DECODER_ERROR_FEATURE_NOT_SUPPORTED: + return "JPEG_DECODER_ERROR_FEATURE_NOT_SUPPORTED"; + default: + return "!unknown MJPEG error!"; + } +} + +static bool delta_mjpeg_check_status(struct delta_ctx *pctx, + struct jpeg_decode_return_params_t *status) +{ + struct delta_dev *delta = pctx->dev; + bool dump = false; + + if (status->error_code == JPEG_DECODER_NO_ERROR) + goto out; + + if (is_stream_error(status->error_code)) { + dev_warn_ratelimited(delta->dev, + "%s firmware: stream error @ frame %d (%s)\n", + pctx->name, pctx->decoded_frames, + err_str(status->error_code)); + pctx->stream_errors++; + } else { + dev_warn_ratelimited(delta->dev, + "%s firmware: decode error @ frame %d (%s)\n", + pctx->name, pctx->decoded_frames, + err_str(status->error_code)); + pctx->decode_errors++; + dump = true; + } + +out: + dev_dbg(delta->dev, + "%s firmware: decoding time(us)=%d\n", pctx->name, + status->decode_time_in_us); + + return dump; +} + +static int delta_mjpeg_ipc_open(struct delta_ctx *pctx) +{ + struct delta_dev *delta = pctx->dev; + struct delta_mjpeg_ctx *ctx = to_ctx(pctx); + int ret = 0; + struct jpeg_video_decode_init_params_t params_struct; + struct jpeg_video_decode_init_params_t *params = ¶ms_struct; + struct delta_buf *ipc_buf; + u32 ipc_buf_size; + struct delta_ipc_param ipc_param; + void *hdl; + + memset(params, 0, sizeof(*params)); + params->circular_buffer_begin_addr_p = 0x00000000; + params->circular_buffer_end_addr_p = 0xffffffff; + + dev_vdbg(delta->dev, + "%s %s\n", pctx->name, + ipc_open_param_str(params, ctx->str, sizeof(ctx->str))); + + ipc_param.size = sizeof(*params); + ipc_param.data = params; + ipc_buf_size = sizeof(struct jpeg_decode_params_t) + + sizeof(struct jpeg_decode_return_params_t); + ret = delta_ipc_open(pctx, "JPEG_DECODER_HW0", &ipc_param, + ipc_buf_size, &ipc_buf, &hdl); + if (ret) { + dev_err(delta->dev, + "%s dumping command %s\n", pctx->name, + ipc_open_param_str(params, ctx->str, sizeof(ctx->str))); + return ret; + } + + ctx->ipc_buf = ipc_buf; + ctx->ipc_hdl = hdl; + + return 0; +} + +static int delta_mjpeg_ipc_decode(struct delta_ctx *pctx, struct delta_au *au) +{ + struct delta_dev *delta = pctx->dev; + struct delta_mjpeg_ctx *ctx = to_ctx(pctx); + int ret = 0; + struct jpeg_decode_params_t *params = ctx->ipc_buf->vaddr; + struct jpeg_decode_return_params_t *status = + ctx->ipc_buf->vaddr + sizeof(*params); + struct delta_frame *frame; + struct delta_ipc_param ipc_param, ipc_status; + + ret = delta_get_free_frame(pctx, &frame); + if (ret) + return ret; + + memset(params, 0, sizeof(*params)); + + params->picture_start_addr_p = (u32)(au->paddr); + params->picture_end_addr_p = (u32)(au->paddr + au->size - 1); + + /* + * !WARNING! + * the NV12 decoded frame is only available + * on decimated output when enabling flag + * "JPEG_ADDITIONAL_FLAG_420MB"... + * the non decimated output gives YUV422SP + */ + params->main_aux_enable = JPEG_DISP_AUX_EN; + params->additional_flags = JPEG_ADDITIONAL_FLAG_420MB; + params->horizontal_decimation_factor = JPEG_HDEC_1; + params->vertical_decimation_factor = JPEG_VDEC_1; + params->decoding_mode = JPEG_NORMAL_DECODE; + + params->display_buffer_addr.struct_size = + sizeof(struct jpeg_display_buffer_address_t); + params->display_buffer_addr.display_decimated_luma_p = + (u32)frame->paddr; + params->display_buffer_addr.display_decimated_chroma_p = + (u32)(frame->paddr + + frame->info.aligned_width * frame->info.aligned_height); + + dev_vdbg(delta->dev, + "%s %s\n", pctx->name, + ipc_decode_param_str(params, ctx->str, sizeof(ctx->str))); + + /* status */ + memset(status, 0, sizeof(*status)); + status->error_code = JPEG_DECODER_NO_ERROR; + + ipc_param.size = sizeof(*params); + ipc_param.data = params; + ipc_status.size = sizeof(*status); + ipc_status.data = status; + ret = delta_ipc_decode(ctx->ipc_hdl, &ipc_param, &ipc_status); + if (ret) { + dev_err(delta->dev, + "%s dumping command %s\n", pctx->name, + ipc_decode_param_str(params, ctx->str, + sizeof(ctx->str))); + return ret; + } + + pctx->decoded_frames++; + + /* check firmware decoding status */ + if (delta_mjpeg_check_status(pctx, status)) { + dev_err(delta->dev, + "%s dumping command %s\n", pctx->name, + ipc_decode_param_str(params, ctx->str, + sizeof(ctx->str))); + } + + frame->field = V4L2_FIELD_NONE; + frame->flags = V4L2_BUF_FLAG_KEYFRAME; + frame->state |= DELTA_FRAME_DEC; + + ctx->out_frame = frame; + + return 0; +} + +static int delta_mjpeg_open(struct delta_ctx *pctx) +{ + struct delta_mjpeg_ctx *ctx; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + pctx->priv = ctx; + + return 0; +} + +static int delta_mjpeg_close(struct delta_ctx *pctx) +{ + struct delta_mjpeg_ctx *ctx = to_ctx(pctx); + + if (ctx->ipc_hdl) { + delta_ipc_close(ctx->ipc_hdl); + ctx->ipc_hdl = NULL; + } + + kfree(ctx); + + return 0; +} + +static int delta_mjpeg_get_streaminfo(struct delta_ctx *pctx, + struct delta_streaminfo *streaminfo) +{ + struct delta_mjpeg_ctx *ctx = to_ctx(pctx); + + if (!ctx->header) + goto nodata; + + streaminfo->streamformat = V4L2_PIX_FMT_MJPEG; + streaminfo->width = ctx->header->frame_width; + streaminfo->height = ctx->header->frame_height; + + /* progressive stream */ + streaminfo->field = V4L2_FIELD_NONE; + + streaminfo->dpb = 1; + + return 0; + +nodata: + return -ENODATA; +} + +static int delta_mjpeg_decode(struct delta_ctx *pctx, struct delta_au *pau) +{ + struct delta_dev *delta = pctx->dev; + struct delta_mjpeg_ctx *ctx = to_ctx(pctx); + int ret; + struct delta_au au = *pau; + unsigned int data_offset = 0; + struct mjpeg_header *header = &ctx->header_struct; + + if (!ctx->header) { + ret = delta_mjpeg_read_header(pctx, au.vaddr, au.size, + header, &data_offset); + if (ret) { + pctx->stream_errors++; + goto err; + } + if (header->frame_width * header->frame_height > + DELTA_MJPEG_MAX_RESO) { + dev_err(delta->dev, + "%s stream resolution too large: %dx%d > %d pixels budget\n", + pctx->name, + header->frame_width, + header->frame_height, DELTA_MJPEG_MAX_RESO); + ret = -EINVAL; + goto err; + } + ctx->header = header; + goto out; + } + + if (!ctx->ipc_hdl) { + ret = delta_mjpeg_ipc_open(pctx); + if (ret) + goto err; + } + + ret = delta_mjpeg_read_header(pctx, au.vaddr, au.size, + ctx->header, &data_offset); + if (ret) { + pctx->stream_errors++; + goto err; + } + + au.paddr += data_offset; + au.vaddr += data_offset; + + ret = delta_mjpeg_ipc_decode(pctx, &au); + if (ret) + goto err; + +out: + return 0; + +err: + return ret; +} + +static int delta_mjpeg_get_frame(struct delta_ctx *pctx, + struct delta_frame **frame) +{ + struct delta_mjpeg_ctx *ctx = to_ctx(pctx); + + if (!ctx->out_frame) + return -ENODATA; + + *frame = ctx->out_frame; + + ctx->out_frame = NULL; + + return 0; +} + +const struct delta_dec mjpegdec = { + .name = "MJPEG", + .streamformat = V4L2_PIX_FMT_MJPEG, + .pixelformat = V4L2_PIX_FMT_NV12, + .open = delta_mjpeg_open, + .close = delta_mjpeg_close, + .get_streaminfo = delta_mjpeg_get_streaminfo, + .get_frameinfo = delta_get_frameinfo_default, + .decode = delta_mjpeg_decode, + .get_frame = delta_mjpeg_get_frame, + .recycle = delta_recycle_default, +}; diff --git a/drivers/media/platform/st/sti/delta/delta-mjpeg-fw.h b/drivers/media/platform/st/sti/delta/delta-mjpeg-fw.h new file mode 100644 index 000000000..5a9404f4d --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-mjpeg-fw.h @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#ifndef DELTA_MJPEG_FW_H +#define DELTA_MJPEG_FW_H + +/* + * struct jpeg_decoded_buffer_address_t + * + * defines the addresses where the decoded picture/additional + * info related to the block structures will be stored + * + * @display_luma_p: address of the luma buffer + * @display_chroma_p: address of the chroma buffer + */ +struct jpeg_decoded_buffer_address_t { + u32 luma_p; + u32 chroma_p; +}; + +/* + * struct jpeg_display_buffer_address_t + * + * defines the addresses (used by the Display Reconstruction block) + * where the pictures to be displayed will be stored + * + * @struct_size: size of the structure in bytes + * @display_luma_p: address of the luma buffer + * @display_chroma_p: address of the chroma buffer + * @display_decimated_luma_p: address of the decimated luma buffer + * @display_decimated_chroma_p: address of the decimated chroma buffer + */ +struct jpeg_display_buffer_address_t { + u32 struct_size; + u32 display_luma_p; + u32 display_chroma_p; + u32 display_decimated_luma_p; + u32 display_decimated_chroma_p; +}; + +/* + * used for enabling main/aux outputs for both display & + * reference reconstruction blocks + */ +enum jpeg_rcn_ref_disp_enable_t { + /* enable decimated (for display) reconstruction */ + JPEG_DISP_AUX_EN = 0x00000010, + /* enable main (for display) reconstruction */ + JPEG_DISP_MAIN_EN = 0x00000020, + /* enable both main & decimated (for display) reconstruction */ + JPEG_DISP_AUX_MAIN_EN = 0x00000030, + /* enable only reference output(ex. for trick modes) */ + JPEG_REF_MAIN_EN = 0x00000100, + /* + * enable reference output with decimated + * (for display) reconstruction + */ + JPEG_REF_MAIN_DISP_AUX_EN = 0x00000110, + /* + * enable reference output with main + * (for display) reconstruction + */ + JPEG_REF_MAIN_DISP_MAIN_EN = 0x00000120, + /* + * enable reference output with main & decimated + * (for display) reconstruction + */ + JPEG_REF_MAIN_DISP_MAIN_AUX_EN = 0x00000130 +}; + +/* identifies the horizontal decimation factor */ +enum jpeg_horizontal_deci_factor_t { + /* no resize */ + JPEG_HDEC_1 = 0x00000000, + /* Advanced H/2 resize using improved 8-tap filters */ + JPEG_HDEC_ADVANCED_2 = 0x00000101, + /* Advanced H/4 resize using improved 8-tap filters */ + JPEG_HDEC_ADVANCED_4 = 0x00000102 +}; + +/* identifies the vertical decimation factor */ +enum jpeg_vertical_deci_factor_t { + /* no resize */ + JPEG_VDEC_1 = 0x00000000, + /* V/2 , progressive resize */ + JPEG_VDEC_ADVANCED_2_PROG = 0x00000204, + /* V/2 , interlaced resize */ + JPEG_VDEC_ADVANCED_2_INT = 0x000000208 +}; + +/* status of the decoding process */ +enum jpeg_decoding_error_t { + JPEG_DECODER_NO_ERROR = 0, + JPEG_DECODER_UNDEFINED_HUFF_TABLE = 1, + JPEG_DECODER_UNSUPPORTED_MARKER = 2, + JPEG_DECODER_UNABLE_ALLOCATE_MEMORY = 3, + JPEG_DECODER_NON_SUPPORTED_SAMP_FACTORS = 4, + JPEG_DECODER_BAD_PARAMETER = 5, + JPEG_DECODER_DECODE_ERROR = 6, + JPEG_DECODER_BAD_RESTART_MARKER = 7, + JPEG_DECODER_UNSUPPORTED_COLORSPACE = 8, + JPEG_DECODER_BAD_SOS_SPECTRAL = 9, + JPEG_DECODER_BAD_SOS_SUCCESSIVE = 10, + JPEG_DECODER_BAD_HEADER_LENGTH = 11, + JPEG_DECODER_BAD_COUNT_VALUE = 12, + JPEG_DECODER_BAD_DHT_MARKER = 13, + JPEG_DECODER_BAD_INDEX_VALUE = 14, + JPEG_DECODER_BAD_NUMBER_HUFFMAN_TABLES = 15, + JPEG_DECODER_BAD_QUANT_TABLE_LENGTH = 16, + JPEG_DECODER_BAD_NUMBER_QUANT_TABLES = 17, + JPEG_DECODER_BAD_COMPONENT_COUNT = 18, + JPEG_DECODER_DIVIDE_BY_ZERO_ERROR = 19, + JPEG_DECODER_NOT_JPG_IMAGE = 20, + JPEG_DECODER_UNSUPPORTED_ROTATION_ANGLE = 21, + JPEG_DECODER_UNSUPPORTED_SCALING = 22, + JPEG_DECODER_INSUFFICIENT_OUTPUTBUFFER_SIZE = 23, + JPEG_DECODER_BAD_HWCFG_GP_VERSION_VALUE = 24, + JPEG_DECODER_BAD_VALUE_FROM_RED = 25, + JPEG_DECODER_BAD_SUBREGION_PARAMETERS = 26, + JPEG_DECODER_PROGRESSIVE_DECODE_NOT_SUPPORTED = 27, + JPEG_DECODER_ERROR_TASK_TIMEOUT = 28, + JPEG_DECODER_ERROR_FEATURE_NOT_SUPPORTED = 29 +}; + +/* identifies the decoding mode */ +enum jpeg_decoding_mode_t { + JPEG_NORMAL_DECODE = 0, +}; + +enum jpeg_additional_flags_t { + JPEG_ADDITIONAL_FLAG_NONE = 0, + /* request firmware to return values of the CEH registers */ + JPEG_ADDITIONAL_FLAG_CEH = 1, + /* output storage of auxiliary reconstruction in Raster format. */ + JPEG_ADDITIONAL_FLAG_RASTER = 64, + /* output storage of auxiliary reconstruction in 420MB format. */ + JPEG_ADDITIONAL_FLAG_420MB = 128 +}; + +/* + * struct jpeg_video_decode_init_params_t - initialization command parameters + * + * @circular_buffer_begin_addr_p: start address of fw circular buffer + * @circular_buffer_end_addr_p: end address of fw circular buffer + */ +struct jpeg_video_decode_init_params_t { + u32 circular_buffer_begin_addr_p; + u32 circular_buffer_end_addr_p; + u32 reserved; +}; + +/* + * struct jpeg_decode_params_t - decode command parameters + * + * @picture_start_addr_p: start address of jpeg picture + * @picture_end_addr_p: end address of jpeg picture + * @decoded_buffer_addr: decoded picture buffer + * @display_buffer_addr: display picture buffer + * @main_aux_enable: enable main and/or aux outputs + * @horizontal_decimation_factor:horizontal decimation factor + * @vertical_decimation_factor: vertical decimation factor + * @xvalue0: the x(0) coordinate for subregion decoding + * @xvalue1: the x(1) coordinate for subregion decoding + * @yvalue0: the y(0) coordinate for subregion decoding + * @yvalue1: the y(1) coordinate for subregion decoding + * @decoding_mode: decoding mode + * @additional_flags: additional flags + * @field_flag: determines frame/field scan + * @is_jpeg_image: 1 = still jpeg, 0 = motion jpeg + */ +struct jpeg_decode_params_t { + u32 picture_start_addr_p; + u32 picture_end_addr_p; + struct jpeg_decoded_buffer_address_t decoded_buffer_addr; + struct jpeg_display_buffer_address_t display_buffer_addr; + enum jpeg_rcn_ref_disp_enable_t main_aux_enable; + enum jpeg_horizontal_deci_factor_t horizontal_decimation_factor; + enum jpeg_vertical_deci_factor_t vertical_decimation_factor; + u32 xvalue0; + u32 xvalue1; + u32 yvalue0; + u32 yvalue1; + enum jpeg_decoding_mode_t decoding_mode; + u32 additional_flags; + u32 field_flag; + u32 reserved; + u32 is_jpeg_image; +}; + +/* + * struct jpeg_decode_return_params_t + * + * status returned by firmware after decoding + * + * @decode_time_in_us: decoding time in microseconds + * @pm_cycles: profiling information + * @pm_dmiss: profiling information + * @pm_imiss: profiling information + * @pm_bundles: profiling information + * @pm_pft: profiling information + * @error_code: status of the decoding process + * @ceh_registers: array where values of the Contrast Enhancement + * Histogram (CEH) registers will be stored. + * ceh_registers[0] correspond to register MBE_CEH_0_7, + * ceh_registers[1] correspond to register MBE_CEH_8_15 + * ceh_registers[2] correspond to register MBE_CEH_16_23 + * Note that elements of this array will be updated only + * if additional_flags has JPEG_ADDITIONAL_FLAG_CEH set. + */ +struct jpeg_decode_return_params_t { + /* profiling info */ + u32 decode_time_in_us; + u32 pm_cycles; + u32 pm_dmiss; + u32 pm_imiss; + u32 pm_bundles; + u32 pm_pft; + enum jpeg_decoding_error_t error_code; + u32 ceh_registers[32]; +}; + +#endif /* DELTA_MJPEG_FW_H */ diff --git a/drivers/media/platform/st/sti/delta/delta-mjpeg-hdr.c b/drivers/media/platform/st/sti/delta/delta-mjpeg-hdr.c new file mode 100644 index 000000000..90e5b2f72 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-mjpeg-hdr.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#include "delta.h" +#include "delta-mjpeg.h" + +#define MJPEG_SOF_0 0xc0 +#define MJPEG_SOF_1 0xc1 +#define MJPEG_SOI 0xd8 +#define MJPEG_MARKER 0xff + +static char *header_str(struct mjpeg_header *header, + char *str, + unsigned int len) +{ + char *cur = str; + unsigned int left = len; + + if (!header) + return ""; + + snprintf(cur, left, "[MJPEG header]\n" + "|- length = %d\n" + "|- precision = %d\n" + "|- width = %d\n" + "|- height = %d\n" + "|- components = %d\n", + header->length, + header->sample_precision, + header->frame_width, + header->frame_height, + header->nb_of_components); + + return str; +} + +static int delta_mjpeg_read_sof(struct delta_ctx *pctx, + unsigned char *data, unsigned int size, + struct mjpeg_header *header) +{ + struct delta_dev *delta = pctx->dev; + unsigned int offset = 0; + + if (size < 64) + goto err_no_more; + + memset(header, 0, sizeof(*header)); + header->length = be16_to_cpu(*(__be16 *)(data + offset)); + offset += sizeof(u16); + header->sample_precision = *(u8 *)(data + offset); + offset += sizeof(u8); + header->frame_height = be16_to_cpu(*(__be16 *)(data + offset)); + offset += sizeof(u16); + header->frame_width = be16_to_cpu(*(__be16 *)(data + offset)); + offset += sizeof(u16); + header->nb_of_components = *(u8 *)(data + offset); + offset += sizeof(u8); + + if (header->nb_of_components >= MJPEG_MAX_COMPONENTS) { + dev_err(delta->dev, + "%s unsupported number of components (%d > %d)\n", + pctx->name, header->nb_of_components, + MJPEG_MAX_COMPONENTS); + return -EINVAL; + } + + if ((offset + header->nb_of_components * + sizeof(header->components[0])) > size) + goto err_no_more; + + return 0; + +err_no_more: + dev_err(delta->dev, + "%s sof: reached end of %d size input stream\n", + pctx->name, size); + return -ENODATA; +} + +int delta_mjpeg_read_header(struct delta_ctx *pctx, + unsigned char *data, unsigned int size, + struct mjpeg_header *header, + unsigned int *data_offset) +{ + struct delta_dev *delta = pctx->dev; + unsigned char str[200]; + + unsigned int ret = 0; + unsigned int offset = 0; + unsigned int soi = 0; + + if (size < 2) + goto err_no_more; + + offset = 0; + while (1) { + if (data[offset] == MJPEG_MARKER) + switch (data[offset + 1]) { + case MJPEG_SOI: + soi = 1; + *data_offset = offset; + break; + + case MJPEG_SOF_0: + case MJPEG_SOF_1: + if (!soi) { + dev_err(delta->dev, + "%s wrong sequence, got SOF while SOI not seen\n", + pctx->name); + return -EINVAL; + } + + ret = delta_mjpeg_read_sof(pctx, + &data[offset + 2], + size - (offset + 2), + header); + if (ret) + goto err; + + goto done; + + default: + break; + } + + offset++; + if ((offset + 2) >= size) + goto err_no_more; + } + +done: + dev_dbg(delta->dev, + "%s found header @ offset %d:\n%s", pctx->name, + *data_offset, + header_str(header, str, sizeof(str))); + return 0; + +err_no_more: + dev_err(delta->dev, + "%s no header found within %d bytes input stream\n", + pctx->name, size); + return -ENODATA; + +err: + return ret; +} diff --git a/drivers/media/platform/st/sti/delta/delta-mjpeg.h b/drivers/media/platform/st/sti/delta/delta-mjpeg.h new file mode 100644 index 000000000..43f7a88b6 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-mjpeg.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2013 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#ifndef DELTA_MJPEG_H +#define DELTA_MJPEG_H + +#include "delta.h" + +struct mjpeg_component { + unsigned int id;/* 1=Y, 2=Cb, 3=Cr, 4=L, 5=Q */ + unsigned int h_sampling_factor; + unsigned int v_sampling_factor; + unsigned int quant_table_index; +}; + +#define MJPEG_MAX_COMPONENTS 5 + +struct mjpeg_header { + unsigned int length; + unsigned int sample_precision; + unsigned int frame_width; + unsigned int frame_height; + unsigned int nb_of_components; + struct mjpeg_component components[MJPEG_MAX_COMPONENTS]; +}; + +int delta_mjpeg_read_header(struct delta_ctx *pctx, + unsigned char *data, unsigned int size, + struct mjpeg_header *header, + unsigned int *data_offset); + +#endif /* DELTA_MJPEG_H */ diff --git a/drivers/media/platform/st/sti/delta/delta-v4l2.c b/drivers/media/platform/st/sti/delta/delta-v4l2.c new file mode 100644 index 000000000..03eaee6d1 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta-v4l2.c @@ -0,0 +1,1970 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Hugues Fruchet + * Jean-Christophe Trotin + * for STMicroelectronics. + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "delta.h" +#include "delta-debug.h" +#include "delta-ipc.h" + +#define DELTA_NAME "st-delta" + +#define DELTA_PREFIX "[---:----]" + +#define to_ctx(__fh) container_of(__fh, struct delta_ctx, fh) +#define to_au(__vbuf) container_of(__vbuf, struct delta_au, vbuf) +#define to_frame(__vbuf) container_of(__vbuf, struct delta_frame, vbuf) + +#define call_dec_op(dec, op, args...)\ + ((dec && (dec)->op) ? (dec)->op(args) : 0) + +/* registry of available decoders */ +static const struct delta_dec *delta_decoders[] = { +#ifdef CONFIG_VIDEO_STI_DELTA_MJPEG + &mjpegdec, +#endif +}; + +static inline int frame_size(u32 w, u32 h, u32 fmt) +{ + switch (fmt) { + case V4L2_PIX_FMT_NV12: + return (w * h * 3) / 2; + default: + return 0; + } +} + +static inline int frame_stride(u32 w, u32 fmt) +{ + switch (fmt) { + case V4L2_PIX_FMT_NV12: + return w; + default: + return 0; + } +} + +static void dump_au(struct delta_ctx *ctx, struct delta_au *au) +{ + struct delta_dev *delta = ctx->dev; + u32 size = 10; /* dump first & last 10 bytes */ + u8 *data = (u8 *)(au->vaddr); + + if (au->size <= (size * 2)) + dev_dbg(delta->dev, "%s dump au[%d] dts=%lld size=%d data=%*ph\n", + ctx->name, au->vbuf.vb2_buf.index, au->dts, au->size, + au->size, data); + else + dev_dbg(delta->dev, "%s dump au[%d] dts=%lld size=%d data=%*ph..%*ph\n", + ctx->name, au->vbuf.vb2_buf.index, au->dts, au->size, + size, data, size, data + au->size - size); +} + +static void dump_frame(struct delta_ctx *ctx, struct delta_frame *frame) +{ + struct delta_dev *delta = ctx->dev; + u32 size = 10; /* dump first 10 bytes */ + u8 *data = (u8 *)(frame->vaddr); + + dev_dbg(delta->dev, "%s dump frame[%d] dts=%lld type=%s field=%s data=%*ph\n", + ctx->name, frame->index, frame->dts, + frame_type_str(frame->flags), + frame_field_str(frame->field), + size, data); +} + +static void delta_au_done(struct delta_ctx *ctx, struct delta_au *au, int err) +{ + struct vb2_v4l2_buffer *vbuf; + + vbuf = &au->vbuf; + vbuf->sequence = ctx->au_num++; + v4l2_m2m_buf_done(vbuf, err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); +} + +static void delta_frame_done(struct delta_ctx *ctx, struct delta_frame *frame, + int err) +{ + struct vb2_v4l2_buffer *vbuf; + + dump_frame(ctx, frame); + + /* decoded frame is now output to user */ + frame->state |= DELTA_FRAME_OUT; + + vbuf = &frame->vbuf; + vbuf->sequence = ctx->frame_num++; + v4l2_m2m_buf_done(vbuf, err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + + if (frame->info.size) /* ignore EOS */ + ctx->output_frames++; +} + +static void requeue_free_frames(struct delta_ctx *ctx) +{ + struct vb2_v4l2_buffer *vbuf; + struct delta_frame *frame; + unsigned int i; + + /* requeue all free frames */ + for (i = 0; i < ctx->nb_of_frames; i++) { + frame = ctx->frames[i]; + if (frame->state == DELTA_FRAME_FREE) { + vbuf = &frame->vbuf; + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); + frame->state = DELTA_FRAME_M2M; + } + } +} + +static int delta_recycle(struct delta_ctx *ctx, struct delta_frame *frame) +{ + const struct delta_dec *dec = ctx->dec; + + /* recycle frame on decoder side */ + call_dec_op(dec, recycle, ctx, frame); + + /* this frame is no more output */ + frame->state &= ~DELTA_FRAME_OUT; + + /* requeue free frame */ + if (frame->state == DELTA_FRAME_FREE) { + struct vb2_v4l2_buffer *vbuf = &frame->vbuf; + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); + frame->state = DELTA_FRAME_M2M; + } + + /* reset other frame fields */ + frame->flags = 0; + frame->dts = 0; + + return 0; +} + +static void delta_push_dts(struct delta_ctx *ctx, u64 val) +{ + struct delta_dts *dts; + + dts = kzalloc(sizeof(*dts), GFP_KERNEL); + if (!dts) + return; + + INIT_LIST_HEAD(&dts->list); + + /* + * protected by global lock acquired + * by V4L2 when calling delta_vb2_au_queue + */ + dts->val = val; + list_add_tail(&dts->list, &ctx->dts); +} + +static void delta_pop_dts(struct delta_ctx *ctx, u64 *val) +{ + struct delta_dev *delta = ctx->dev; + struct delta_dts *dts; + + /* + * protected by global lock acquired + * by V4L2 when calling delta_vb2_au_queue + */ + if (list_empty(&ctx->dts)) { + dev_warn(delta->dev, "%s no dts to pop ... output dts = 0\n", + ctx->name); + *val = 0; + return; + } + + dts = list_first_entry(&ctx->dts, struct delta_dts, list); + list_del(&dts->list); + + *val = dts->val; + + kfree(dts); +} + +static void delta_flush_dts(struct delta_ctx *ctx) +{ + struct delta_dts *dts; + struct delta_dts *next; + + /* + * protected by global lock acquired + * by V4L2 when calling delta_vb2_au_queue + */ + + /* free all pending dts */ + list_for_each_entry_safe(dts, next, &ctx->dts, list) + kfree(dts); + + /* reset list */ + INIT_LIST_HEAD(&ctx->dts); +} + +static inline int frame_alignment(u32 fmt) +{ + switch (fmt) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + /* multiple of 2 */ + return 2; + default: + return 1; + } +} + +static inline int estimated_au_size(u32 w, u32 h) +{ + /* + * for a MJPEG stream encoded from YUV422 pixel format, + * assuming a compression ratio of 2, the maximum size + * of an access unit is (width x height x 2) / 2, + * so (width x height) + */ + return (w * h); +} + +static void set_default_params(struct delta_ctx *ctx) +{ + struct delta_frameinfo *frameinfo = &ctx->frameinfo; + struct delta_streaminfo *streaminfo = &ctx->streaminfo; + + memset(frameinfo, 0, sizeof(*frameinfo)); + frameinfo->pixelformat = V4L2_PIX_FMT_NV12; + frameinfo->width = DELTA_DEFAULT_WIDTH; + frameinfo->height = DELTA_DEFAULT_HEIGHT; + frameinfo->aligned_width = ALIGN(frameinfo->width, + DELTA_WIDTH_ALIGNMENT); + frameinfo->aligned_height = ALIGN(frameinfo->height, + DELTA_HEIGHT_ALIGNMENT); + frameinfo->size = frame_size(frameinfo->aligned_width, + frameinfo->aligned_height, + frameinfo->pixelformat); + frameinfo->field = V4L2_FIELD_NONE; + frameinfo->colorspace = V4L2_COLORSPACE_REC709; + frameinfo->xfer_func = V4L2_XFER_FUNC_DEFAULT; + frameinfo->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + frameinfo->quantization = V4L2_QUANTIZATION_DEFAULT; + + memset(streaminfo, 0, sizeof(*streaminfo)); + streaminfo->streamformat = DELTA_DEFAULT_STREAMFORMAT; + streaminfo->width = DELTA_DEFAULT_WIDTH; + streaminfo->height = DELTA_DEFAULT_HEIGHT; + streaminfo->field = V4L2_FIELD_NONE; + streaminfo->colorspace = V4L2_COLORSPACE_REC709; + streaminfo->xfer_func = V4L2_XFER_FUNC_DEFAULT; + streaminfo->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + streaminfo->quantization = V4L2_QUANTIZATION_DEFAULT; + + ctx->max_au_size = estimated_au_size(streaminfo->width, + streaminfo->height); +} + +static const struct delta_dec *delta_find_decoder(struct delta_ctx *ctx, + u32 streamformat, + u32 pixelformat) +{ + struct delta_dev *delta = ctx->dev; + const struct delta_dec *dec; + unsigned int i; + + for (i = 0; i < delta->nb_of_decoders; i++) { + dec = delta->decoders[i]; + if ((dec->pixelformat == pixelformat) && + (dec->streamformat == streamformat)) + return dec; + } + + return NULL; +} + +static void register_format(u32 format, u32 formats[], u32 *nb_of_formats) +{ + u32 i; + + for (i = 0; i < *nb_of_formats; i++) { + if (format == formats[i]) + return; + } + + formats[(*nb_of_formats)++] = format; +} + +static void register_formats(struct delta_dev *delta) +{ + unsigned int i; + + for (i = 0; i < delta->nb_of_decoders; i++) { + register_format(delta->decoders[i]->pixelformat, + delta->pixelformats, + &delta->nb_of_pixelformats); + + register_format(delta->decoders[i]->streamformat, + delta->streamformats, + &delta->nb_of_streamformats); + } +} + +static void register_decoders(struct delta_dev *delta) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(delta_decoders); i++) { + if (delta->nb_of_decoders >= DELTA_MAX_DECODERS) { + dev_dbg(delta->dev, + "%s failed to register %s decoder (%d maximum reached)\n", + DELTA_PREFIX, delta_decoders[i]->name, + DELTA_MAX_DECODERS); + return; + } + + delta->decoders[delta->nb_of_decoders++] = delta_decoders[i]; + dev_info(delta->dev, "%s %s decoder registered\n", + DELTA_PREFIX, delta_decoders[i]->name); + } +} + +static int delta_open_decoder(struct delta_ctx *ctx, u32 streamformat, + u32 pixelformat, const struct delta_dec **pdec) +{ + struct delta_dev *delta = ctx->dev; + const struct delta_dec *dec; + int ret; + + dec = delta_find_decoder(ctx, streamformat, ctx->frameinfo.pixelformat); + if (!dec) { + dev_err(delta->dev, "%s no decoder found matching %4.4s => %4.4s\n", + ctx->name, (char *)&streamformat, (char *)&pixelformat); + return -EINVAL; + } + + dev_dbg(delta->dev, "%s one decoder matching %4.4s => %4.4s\n", + ctx->name, (char *)&streamformat, (char *)&pixelformat); + + /* update instance name */ + snprintf(ctx->name, sizeof(ctx->name), "[%3d:%4.4s]", + delta->instance_id, (char *)&streamformat); + + /* open decoder instance */ + ret = call_dec_op(dec, open, ctx); + if (ret) { + dev_err(delta->dev, "%s failed to open decoder instance (%d)\n", + ctx->name, ret); + return ret; + } + + dev_dbg(delta->dev, "%s %s decoder opened\n", ctx->name, dec->name); + + *pdec = dec; + + return ret; +} + +/* + * V4L2 ioctl operations + */ + +static int delta_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + + strscpy(cap->driver, DELTA_NAME, sizeof(cap->driver)); + strscpy(cap->card, delta->vdev->name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + delta->pdev->name); + + return 0; +} + +static int delta_enum_fmt_stream(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + + if (unlikely(f->index >= delta->nb_of_streamformats)) + return -EINVAL; + + f->pixelformat = delta->streamformats[f->index]; + + return 0; +} + +static int delta_enum_fmt_frame(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + + if (unlikely(f->index >= delta->nb_of_pixelformats)) + return -EINVAL; + + f->pixelformat = delta->pixelformats[f->index]; + + return 0; +} + +static int delta_g_fmt_stream(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct delta_streaminfo *streaminfo = &ctx->streaminfo; + unsigned char str[100] = ""; + + if (!(ctx->flags & DELTA_FLAG_STREAMINFO)) + dev_dbg(delta->dev, + "%s V4L2 GET_FMT (OUTPUT): no stream information available, default to %s\n", + ctx->name, + delta_streaminfo_str(streaminfo, str, sizeof(str))); + + pix->pixelformat = streaminfo->streamformat; + pix->width = streaminfo->width; + pix->height = streaminfo->height; + pix->field = streaminfo->field; + pix->bytesperline = 0; + pix->sizeimage = ctx->max_au_size; + pix->colorspace = streaminfo->colorspace; + pix->xfer_func = streaminfo->xfer_func; + pix->ycbcr_enc = streaminfo->ycbcr_enc; + pix->quantization = streaminfo->quantization; + + return 0; +} + +static int delta_g_fmt_frame(struct file *file, void *fh, struct v4l2_format *f) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct delta_frameinfo *frameinfo = &ctx->frameinfo; + struct delta_streaminfo *streaminfo = &ctx->streaminfo; + unsigned char str[100] = ""; + + if (!(ctx->flags & DELTA_FLAG_FRAMEINFO)) + dev_dbg(delta->dev, + "%s V4L2 GET_FMT (CAPTURE): no frame information available, default to %s\n", + ctx->name, + delta_frameinfo_str(frameinfo, str, sizeof(str))); + + pix->pixelformat = frameinfo->pixelformat; + pix->width = frameinfo->aligned_width; + pix->height = frameinfo->aligned_height; + pix->field = frameinfo->field; + pix->bytesperline = frame_stride(frameinfo->aligned_width, + frameinfo->pixelformat); + pix->sizeimage = frameinfo->size; + + if (ctx->flags & DELTA_FLAG_STREAMINFO) { + /* align colorspace & friends on stream ones if any set */ + frameinfo->colorspace = streaminfo->colorspace; + frameinfo->xfer_func = streaminfo->xfer_func; + frameinfo->ycbcr_enc = streaminfo->ycbcr_enc; + frameinfo->quantization = streaminfo->quantization; + } + pix->colorspace = frameinfo->colorspace; + pix->xfer_func = frameinfo->xfer_func; + pix->ycbcr_enc = frameinfo->ycbcr_enc; + pix->quantization = frameinfo->quantization; + + return 0; +} + +static int delta_try_fmt_stream(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + u32 streamformat = pix->pixelformat; + const struct delta_dec *dec; + u32 width, height; + u32 au_size; + + dec = delta_find_decoder(ctx, streamformat, ctx->frameinfo.pixelformat); + if (!dec) { + dev_dbg(delta->dev, + "%s V4L2 TRY_FMT (OUTPUT): unsupported format %4.4s\n", + ctx->name, (char *)&pix->pixelformat); + return -EINVAL; + } + + /* adjust width & height */ + width = pix->width; + height = pix->height; + v4l_bound_align_image + (&pix->width, + DELTA_MIN_WIDTH, + dec->max_width ? dec->max_width : DELTA_MAX_WIDTH, + 0, + &pix->height, + DELTA_MIN_HEIGHT, + dec->max_height ? dec->max_height : DELTA_MAX_HEIGHT, + 0, 0); + + if ((pix->width != width) || (pix->height != height)) + dev_dbg(delta->dev, + "%s V4L2 TRY_FMT (OUTPUT): resolution updated %dx%d -> %dx%d to fit min/max/alignment\n", + ctx->name, width, height, + pix->width, pix->height); + + au_size = estimated_au_size(pix->width, pix->height); + if (pix->sizeimage < au_size) { + dev_dbg(delta->dev, + "%s V4L2 TRY_FMT (OUTPUT): size updated %d -> %d to fit estimated size\n", + ctx->name, pix->sizeimage, au_size); + pix->sizeimage = au_size; + } + + pix->bytesperline = 0; + + if (pix->field == V4L2_FIELD_ANY) + pix->field = V4L2_FIELD_NONE; + + return 0; +} + +static int delta_try_fmt_frame(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + struct v4l2_pix_format *pix = &f->fmt.pix; + u32 pixelformat = pix->pixelformat; + const struct delta_dec *dec; + u32 width, height; + + dec = delta_find_decoder(ctx, ctx->streaminfo.streamformat, + pixelformat); + if (!dec) { + dev_dbg(delta->dev, + "%s V4L2 TRY_FMT (CAPTURE): unsupported format %4.4s\n", + ctx->name, (char *)&pixelformat); + return -EINVAL; + } + + /* adjust width & height */ + width = pix->width; + height = pix->height; + v4l_bound_align_image(&pix->width, + DELTA_MIN_WIDTH, DELTA_MAX_WIDTH, + frame_alignment(pixelformat) - 1, + &pix->height, + DELTA_MIN_HEIGHT, DELTA_MAX_HEIGHT, + frame_alignment(pixelformat) - 1, 0); + + if ((pix->width != width) || (pix->height != height)) + dev_dbg(delta->dev, + "%s V4L2 TRY_FMT (CAPTURE): resolution updated %dx%d -> %dx%d to fit min/max/alignment\n", + ctx->name, width, height, pix->width, pix->height); + + /* default decoder alignment constraint */ + width = ALIGN(pix->width, DELTA_WIDTH_ALIGNMENT); + height = ALIGN(pix->height, DELTA_HEIGHT_ALIGNMENT); + if ((pix->width != width) || (pix->height != height)) + dev_dbg(delta->dev, + "%s V4L2 TRY_FMT (CAPTURE): resolution updated %dx%d -> %dx%d to fit decoder alignment\n", + ctx->name, width, height, pix->width, pix->height); + + if (!pix->colorspace) { + pix->colorspace = V4L2_COLORSPACE_REC709; + pix->xfer_func = V4L2_XFER_FUNC_DEFAULT; + pix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pix->quantization = V4L2_QUANTIZATION_DEFAULT; + } + + pix->width = width; + pix->height = height; + pix->bytesperline = frame_stride(pix->width, pixelformat); + pix->sizeimage = frame_size(pix->width, pix->height, pixelformat); + + if (pix->field == V4L2_FIELD_ANY) + pix->field = V4L2_FIELD_NONE; + + return 0; +} + +static int delta_s_fmt_stream(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + struct vb2_queue *vq; + struct v4l2_pix_format *pix = &f->fmt.pix; + int ret; + + ret = delta_try_fmt_stream(file, fh, f); + if (ret) { + dev_dbg(delta->dev, + "%s V4L2 S_FMT (OUTPUT): unsupported format %4.4s\n", + ctx->name, (char *)&pix->pixelformat); + return ret; + } + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_streaming(vq)) { + dev_dbg(delta->dev, "%s V4L2 S_FMT (OUTPUT): queue busy\n", + ctx->name); + return -EBUSY; + } + + ctx->max_au_size = pix->sizeimage; + ctx->streaminfo.width = pix->width; + ctx->streaminfo.height = pix->height; + ctx->streaminfo.streamformat = pix->pixelformat; + ctx->streaminfo.colorspace = pix->colorspace; + ctx->streaminfo.xfer_func = pix->xfer_func; + ctx->streaminfo.ycbcr_enc = pix->ycbcr_enc; + ctx->streaminfo.quantization = pix->quantization; + ctx->flags |= DELTA_FLAG_STREAMINFO; + + return 0; +} + +static int delta_s_fmt_frame(struct file *file, void *fh, struct v4l2_format *f) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + const struct delta_dec *dec = ctx->dec; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct delta_frameinfo frameinfo; + unsigned char str[100] = ""; + struct vb2_queue *vq; + int ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_streaming(vq)) { + dev_dbg(delta->dev, "%s V4L2 S_FMT (CAPTURE): queue busy\n", + ctx->name); + return -EBUSY; + } + + if (ctx->state < DELTA_STATE_READY) { + /* + * decoder not yet opened and valid stream header not found, + * could not negotiate format with decoder, check at least + * pixel format & negotiate resolution boundaries + * and alignment... + */ + ret = delta_try_fmt_frame(file, fh, f); + if (ret) { + dev_dbg(delta->dev, + "%s V4L2 S_FMT (CAPTURE): unsupported format %4.4s\n", + ctx->name, (char *)&pix->pixelformat); + return ret; + } + + return 0; + } + + /* set frame information to decoder */ + memset(&frameinfo, 0, sizeof(frameinfo)); + frameinfo.pixelformat = pix->pixelformat; + frameinfo.width = pix->width; + frameinfo.height = pix->height; + frameinfo.aligned_width = pix->width; + frameinfo.aligned_height = pix->height; + frameinfo.size = pix->sizeimage; + frameinfo.field = pix->field; + frameinfo.colorspace = pix->colorspace; + frameinfo.xfer_func = pix->xfer_func; + frameinfo.ycbcr_enc = pix->ycbcr_enc; + frameinfo.quantization = pix->quantization; + ret = call_dec_op(dec, set_frameinfo, ctx, &frameinfo); + if (ret) + return ret; + + /* then get what decoder can really do */ + ret = call_dec_op(dec, get_frameinfo, ctx, &frameinfo); + if (ret) + return ret; + + ctx->flags |= DELTA_FLAG_FRAMEINFO; + ctx->frameinfo = frameinfo; + dev_dbg(delta->dev, + "%s V4L2 SET_FMT (CAPTURE): frameinfo updated to %s\n", + ctx->name, + delta_frameinfo_str(&frameinfo, str, sizeof(str))); + + pix->pixelformat = frameinfo.pixelformat; + pix->width = frameinfo.aligned_width; + pix->height = frameinfo.aligned_height; + pix->bytesperline = frame_stride(pix->width, pix->pixelformat); + pix->sizeimage = frameinfo.size; + pix->field = frameinfo.field; + pix->colorspace = frameinfo.colorspace; + pix->xfer_func = frameinfo.xfer_func; + pix->ycbcr_enc = frameinfo.ycbcr_enc; + pix->quantization = frameinfo.quantization; + + return 0; +} + +static int delta_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct delta_ctx *ctx = to_ctx(fh); + struct delta_frameinfo *frameinfo = &ctx->frameinfo; + struct v4l2_rect crop; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + if ((ctx->flags & DELTA_FLAG_FRAMEINFO) && + (frameinfo->flags & DELTA_FRAMEINFO_FLAG_CROP)) { + crop = frameinfo->crop; + } else { + /* default to video dimensions */ + crop.left = 0; + crop.top = 0; + crop.width = frameinfo->width; + crop.height = frameinfo->height; + } + + switch (s->target) { + case V4L2_SEL_TGT_COMPOSE: + case V4L2_SEL_TGT_COMPOSE_DEFAULT: + /* visible area inside video */ + s->r = crop; + break; + case V4L2_SEL_TGT_COMPOSE_PADDED: + case V4L2_SEL_TGT_COMPOSE_BOUNDS: + /* up to aligned dimensions */ + s->r.left = 0; + s->r.top = 0; + s->r.width = frameinfo->aligned_width; + s->r.height = frameinfo->aligned_height; + break; + default: + return -EINVAL; + } + + return 0; +} + +static void delta_complete_eos(struct delta_ctx *ctx, + struct delta_frame *frame) +{ + struct delta_dev *delta = ctx->dev; + const struct v4l2_event ev = {.type = V4L2_EVENT_EOS}; + + /* + * Send EOS to user: + * - by returning an empty frame flagged to V4L2_BUF_FLAG_LAST + * - and then send EOS event + */ + + /* empty frame */ + frame->info.size = 0; + + /* set the last buffer flag */ + frame->flags |= V4L2_BUF_FLAG_LAST; + + /* release frame to user */ + delta_frame_done(ctx, frame, 0); + + /* send EOS event */ + v4l2_event_queue_fh(&ctx->fh, &ev); + + dev_dbg(delta->dev, "%s EOS completed\n", ctx->name); +} + +static int delta_try_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *cmd) +{ + if (cmd->cmd != V4L2_DEC_CMD_STOP) + return -EINVAL; + + if (cmd->flags & V4L2_DEC_CMD_STOP_TO_BLACK) + return -EINVAL; + + if (!(cmd->flags & V4L2_DEC_CMD_STOP_IMMEDIATELY) && + (cmd->stop.pts != 0)) + return -EINVAL; + + return 0; +} + +static int delta_decoder_stop_cmd(struct delta_ctx *ctx, void *fh) +{ + const struct delta_dec *dec = ctx->dec; + struct delta_dev *delta = ctx->dev; + struct delta_frame *frame = NULL; + int ret = 0; + + dev_dbg(delta->dev, "%s EOS received\n", ctx->name); + + if (ctx->state != DELTA_STATE_READY) + return 0; + + /* drain the decoder */ + call_dec_op(dec, drain, ctx); + + /* release to user drained frames */ + while (1) { + frame = NULL; + ret = call_dec_op(dec, get_frame, ctx, &frame); + if (ret == -ENODATA) { + /* no more decoded frames */ + break; + } + if (frame) { + dev_dbg(delta->dev, "%s drain frame[%d]\n", + ctx->name, frame->index); + + /* pop timestamp and mark frame with it */ + delta_pop_dts(ctx, &frame->dts); + + /* release decoded frame to user */ + delta_frame_done(ctx, frame, 0); + } + } + + /* try to complete EOS */ + ret = delta_get_free_frame(ctx, &frame); + if (ret) + goto delay_eos; + + /* new frame available, EOS can now be completed */ + delta_complete_eos(ctx, frame); + + ctx->state = DELTA_STATE_EOS; + + return 0; + +delay_eos: + /* + * EOS completion from driver is delayed because + * we don't have a free empty frame available. + * EOS completion is so delayed till next frame_queue() call + * to be sure to have a free empty frame available. + */ + ctx->state = DELTA_STATE_WF_EOS; + dev_dbg(delta->dev, "%s EOS delayed\n", ctx->name); + + return 0; +} + +static int delta_decoder_cmd(struct file *file, void *fh, + struct v4l2_decoder_cmd *cmd) +{ + struct delta_ctx *ctx = to_ctx(fh); + int ret = 0; + + ret = delta_try_decoder_cmd(file, fh, cmd); + if (ret) + return ret; + + return delta_decoder_stop_cmd(ctx, fh); +} + +static int delta_subscribe_event(struct v4l2_fh *fh, + const struct v4l2_event_subscription *sub) +{ + switch (sub->type) { + case V4L2_EVENT_EOS: + return v4l2_event_subscribe(fh, sub, 2, NULL); + default: + return -EINVAL; + } + + return 0; +} + +/* v4l2 ioctl ops */ +static const struct v4l2_ioctl_ops delta_ioctl_ops = { + .vidioc_querycap = delta_querycap, + .vidioc_enum_fmt_vid_cap = delta_enum_fmt_frame, + .vidioc_g_fmt_vid_cap = delta_g_fmt_frame, + .vidioc_try_fmt_vid_cap = delta_try_fmt_frame, + .vidioc_s_fmt_vid_cap = delta_s_fmt_frame, + .vidioc_enum_fmt_vid_out = delta_enum_fmt_stream, + .vidioc_g_fmt_vid_out = delta_g_fmt_stream, + .vidioc_try_fmt_vid_out = delta_try_fmt_stream, + .vidioc_s_fmt_vid_out = delta_s_fmt_stream, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_g_selection = delta_g_selection, + .vidioc_try_decoder_cmd = delta_try_decoder_cmd, + .vidioc_decoder_cmd = delta_decoder_cmd, + .vidioc_subscribe_event = delta_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * mem-to-mem operations + */ + +static void delta_run_work(struct work_struct *work) +{ + struct delta_ctx *ctx = container_of(work, struct delta_ctx, run_work); + struct delta_dev *delta = ctx->dev; + const struct delta_dec *dec = ctx->dec; + struct delta_au *au; + struct delta_frame *frame = NULL; + int ret = 0; + bool discard = false; + struct vb2_v4l2_buffer *vbuf; + + if (!dec) { + dev_err(delta->dev, "%s no decoder opened yet\n", ctx->name); + return; + } + + /* protect instance against reentrancy */ + mutex_lock(&ctx->lock); + + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) { + dev_err(delta->dev, "%s no buffer to decode\n", ctx->name); + mutex_unlock(&ctx->lock); + return; + } + au = to_au(vbuf); + au->size = vb2_get_plane_payload(&vbuf->vb2_buf, 0); + au->dts = vbuf->vb2_buf.timestamp; + + /* dump access unit */ + dump_au(ctx, au); + + /* enable the hardware */ + if (!dec->pm) { + ret = delta_get_sync(ctx); + if (ret) + goto err; + } + + /* decode this access unit */ + ret = call_dec_op(dec, decode, ctx, au); + + /* + * if the (-ENODATA) value is returned, it refers to the interlaced + * stream case for which 2 access units are needed to get 1 frame. + * So, this returned value doesn't mean that the decoding fails, but + * indicates that the timestamp information of the access unit shall + * not be taken into account, and that the V4L2 buffer associated with + * the access unit shall be flagged with V4L2_BUF_FLAG_ERROR to inform + * the user of this situation + */ + if (ret == -ENODATA) { + discard = true; + } else if (ret) { + dev_err(delta->dev, "%s decoding failed (%d)\n", + ctx->name, ret); + + /* disable the hardware */ + if (!dec->pm) + delta_put_autosuspend(ctx); + + goto err; + } + + /* disable the hardware */ + if (!dec->pm) + delta_put_autosuspend(ctx); + + /* push au timestamp in FIFO */ + if (!discard) + delta_push_dts(ctx, au->dts); + + /* get available decoded frames */ + while (1) { + ret = call_dec_op(dec, get_frame, ctx, &frame); + if (ret == -ENODATA) { + /* no more decoded frames */ + goto out; + } + if (ret) { + dev_err(delta->dev, "%s cannot get decoded frame (%d)\n", + ctx->name, ret); + goto out; + } + if (!frame) { + dev_err(delta->dev, + "%s NULL decoded frame\n", + ctx->name); + goto out; + } + + /* pop timestamp and mark frame with it */ + delta_pop_dts(ctx, &frame->dts); + + /* release decoded frame to user */ + delta_frame_done(ctx, frame, 0); + } + +out: + requeue_free_frames(ctx); + delta_au_done(ctx, au, (discard ? -ENODATA : 0)); + mutex_unlock(&ctx->lock); + v4l2_m2m_job_finish(delta->m2m_dev, ctx->fh.m2m_ctx); + return; + +err: + requeue_free_frames(ctx); + delta_au_done(ctx, au, ret); + mutex_unlock(&ctx->lock); + v4l2_m2m_job_finish(delta->m2m_dev, ctx->fh.m2m_ctx); +} + +static void delta_device_run(void *priv) +{ + struct delta_ctx *ctx = priv; + struct delta_dev *delta = ctx->dev; + + queue_work(delta->work_queue, &ctx->run_work); +} + +static void delta_job_abort(void *priv) +{ + struct delta_ctx *ctx = priv; + struct delta_dev *delta = ctx->dev; + + dev_dbg(delta->dev, "%s aborting job\n", ctx->name); + + ctx->aborting = true; +} + +static int delta_job_ready(void *priv) +{ + struct delta_ctx *ctx = priv; + struct delta_dev *delta = ctx->dev; + int src_bufs = v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx); + + if (!src_bufs) { + dev_dbg(delta->dev, "%s not ready: not enough video buffers.\n", + ctx->name); + return 0; + } + + if (!v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx)) { + dev_dbg(delta->dev, "%s not ready: not enough video capture buffers.\n", + ctx->name); + return 0; + } + + if (ctx->aborting) { + dev_dbg(delta->dev, "%s job not ready: aborting\n", ctx->name); + return 0; + } + + dev_dbg(delta->dev, "%s job ready\n", ctx->name); + + return 1; +} + +/* mem-to-mem ops */ +static const struct v4l2_m2m_ops delta_m2m_ops = { + .device_run = delta_device_run, + .job_ready = delta_job_ready, + .job_abort = delta_job_abort, +}; + +/* + * VB2 queue operations + */ + +static int delta_vb2_au_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct delta_ctx *ctx = vb2_get_drv_priv(vq); + unsigned int size = ctx->max_au_size; + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + + *num_planes = 1; + if (*num_buffers < 1) + *num_buffers = 1; + if (*num_buffers > DELTA_MAX_AUS) + *num_buffers = DELTA_MAX_AUS; + + sizes[0] = size; + + return 0; +} + +static int delta_vb2_au_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + struct delta_ctx *ctx = vb2_get_drv_priv(q); + struct delta_dev *delta = ctx->dev; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct delta_au *au = to_au(vbuf); + + if (!au->prepared) { + /* get memory addresses */ + au->vaddr = vb2_plane_vaddr(&au->vbuf.vb2_buf, 0); + au->paddr = vb2_dma_contig_plane_dma_addr + (&au->vbuf.vb2_buf, 0); + au->prepared = true; + dev_dbg(delta->dev, "%s au[%d] prepared; virt=0x%p, phy=0x%pad\n", + ctx->name, vb->index, au->vaddr, &au->paddr); + } + + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + + return 0; +} + +static int delta_setup_frame(struct delta_ctx *ctx, + struct delta_frame *frame) +{ + struct delta_dev *delta = ctx->dev; + const struct delta_dec *dec = ctx->dec; + + if (frame->index >= DELTA_MAX_FRAMES) { + dev_err(delta->dev, + "%s frame index=%d exceeds output frame count (%d)\n", + ctx->name, frame->index, DELTA_MAX_FRAMES); + return -EINVAL; + } + + if (ctx->nb_of_frames >= DELTA_MAX_FRAMES) { + dev_err(delta->dev, + "%s number of frames exceeds output frame count (%d > %d)\n", + ctx->name, ctx->nb_of_frames, DELTA_MAX_FRAMES); + return -EINVAL; + } + + if (frame->index != ctx->nb_of_frames) { + dev_warn(delta->dev, + "%s frame index discontinuity detected, expected %d, got %d\n", + ctx->name, ctx->nb_of_frames, frame->index); + } + + frame->state = DELTA_FRAME_FREE; + ctx->frames[ctx->nb_of_frames] = frame; + ctx->nb_of_frames++; + + /* setup frame on decoder side */ + return call_dec_op(dec, setup_frame, ctx, frame); +} + +/* + * default implementation of get_frameinfo decoder ops + * matching frame information from stream information + * & with default pixel format & default alignment. + */ +int delta_get_frameinfo_default(struct delta_ctx *ctx, + struct delta_frameinfo *frameinfo) +{ + struct delta_streaminfo *streaminfo = &ctx->streaminfo; + + memset(frameinfo, 0, sizeof(*frameinfo)); + frameinfo->pixelformat = V4L2_PIX_FMT_NV12; + frameinfo->width = streaminfo->width; + frameinfo->height = streaminfo->height; + frameinfo->aligned_width = ALIGN(streaminfo->width, + DELTA_WIDTH_ALIGNMENT); + frameinfo->aligned_height = ALIGN(streaminfo->height, + DELTA_HEIGHT_ALIGNMENT); + frameinfo->size = frame_size(frameinfo->aligned_width, + frameinfo->aligned_height, + frameinfo->pixelformat); + if (streaminfo->flags & DELTA_STREAMINFO_FLAG_CROP) { + frameinfo->flags |= DELTA_FRAMEINFO_FLAG_CROP; + frameinfo->crop = streaminfo->crop; + } + if (streaminfo->flags & DELTA_STREAMINFO_FLAG_PIXELASPECT) { + frameinfo->flags |= DELTA_FRAMEINFO_FLAG_PIXELASPECT; + frameinfo->pixelaspect = streaminfo->pixelaspect; + } + frameinfo->field = streaminfo->field; + + return 0; +} + +/* + * default implementation of recycle decoder ops + * consisting to relax the "decoded" frame state + */ +int delta_recycle_default(struct delta_ctx *pctx, + struct delta_frame *frame) +{ + frame->state &= ~DELTA_FRAME_DEC; + + return 0; +} + +static void dump_frames_status(struct delta_ctx *ctx) +{ + struct delta_dev *delta = ctx->dev; + unsigned int i; + struct delta_frame *frame; + unsigned char str[100] = ""; + + dev_info(delta->dev, + "%s dumping frames status...\n", ctx->name); + + for (i = 0; i < ctx->nb_of_frames; i++) { + frame = ctx->frames[i]; + dev_info(delta->dev, + "%s frame[%d] %s\n", + ctx->name, frame->index, + frame_state_str(frame->state, + str, sizeof(str))); + } +} + +int delta_get_free_frame(struct delta_ctx *ctx, + struct delta_frame **pframe) +{ + struct delta_dev *delta = ctx->dev; + struct vb2_v4l2_buffer *vbuf; + struct delta_frame *frame; + + *pframe = NULL; + + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) { + dev_err(delta->dev, "%s no frame available", + ctx->name); + return -EIO; + } + + frame = to_frame(vbuf); + frame->state &= ~DELTA_FRAME_M2M; + if (frame->state != DELTA_FRAME_FREE) { + dev_err(delta->dev, + "%s frame[%d] is not free\n", + ctx->name, frame->index); + dump_frames_status(ctx); + return -ENODATA; + } + + dev_dbg(delta->dev, + "%s get free frame[%d]\n", ctx->name, frame->index); + + *pframe = frame; + return 0; +} + +int delta_get_sync(struct delta_ctx *ctx) +{ + struct delta_dev *delta = ctx->dev; + int ret = 0; + + /* enable the hardware */ + ret = pm_runtime_resume_and_get(delta->dev); + if (ret < 0) { + dev_err(delta->dev, "%s pm_runtime_resume_and_get failed (%d)\n", + __func__, ret); + return ret; + } + + return 0; +} + +void delta_put_autosuspend(struct delta_ctx *ctx) +{ + struct delta_dev *delta = ctx->dev; + + pm_runtime_put_autosuspend(delta->dev); +} + +static void delta_vb2_au_queue(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + struct delta_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int delta_vb2_au_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct delta_ctx *ctx = vb2_get_drv_priv(q); + struct delta_dev *delta = ctx->dev; + const struct delta_dec *dec = ctx->dec; + struct delta_au *au; + int ret = 0; + struct vb2_v4l2_buffer *vbuf = NULL; + struct delta_streaminfo *streaminfo = &ctx->streaminfo; + struct delta_frameinfo *frameinfo = &ctx->frameinfo; + unsigned char str1[100] = ""; + unsigned char str2[100] = ""; + + if ((ctx->state != DELTA_STATE_WF_FORMAT) && + (ctx->state != DELTA_STATE_WF_STREAMINFO)) + return 0; + + if (ctx->state == DELTA_STATE_WF_FORMAT) { + /* open decoder if not yet done */ + ret = delta_open_decoder(ctx, + ctx->streaminfo.streamformat, + ctx->frameinfo.pixelformat, &dec); + if (ret) + goto err; + ctx->dec = dec; + ctx->state = DELTA_STATE_WF_STREAMINFO; + } + + /* + * first buffer should contain stream header, + * decode it to get the infos related to stream + * such as width, height, dpb, ... + */ + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) { + dev_err(delta->dev, "%s failed to start streaming, no stream header buffer enqueued\n", + ctx->name); + ret = -EINVAL; + goto err; + } + au = to_au(vbuf); + au->size = vb2_get_plane_payload(&vbuf->vb2_buf, 0); + au->dts = vbuf->vb2_buf.timestamp; + + delta_push_dts(ctx, au->dts); + + /* dump access unit */ + dump_au(ctx, au); + + /* decode this access unit */ + ret = call_dec_op(dec, decode, ctx, au); + if (ret) { + dev_err(delta->dev, "%s failed to start streaming, header decoding failed (%d)\n", + ctx->name, ret); + goto err; + } + + ret = call_dec_op(dec, get_streaminfo, ctx, streaminfo); + if (ret) { + dev_dbg_ratelimited(delta->dev, + "%s failed to start streaming, valid stream header not yet decoded\n", + ctx->name); + goto err; + } + ctx->flags |= DELTA_FLAG_STREAMINFO; + + ret = call_dec_op(dec, get_frameinfo, ctx, frameinfo); + if (ret) + goto err; + ctx->flags |= DELTA_FLAG_FRAMEINFO; + + ctx->state = DELTA_STATE_READY; + + dev_dbg(delta->dev, "%s %s => %s\n", ctx->name, + delta_streaminfo_str(streaminfo, str1, sizeof(str1)), + delta_frameinfo_str(frameinfo, str2, sizeof(str2))); + + delta_au_done(ctx, au, ret); + return 0; + +err: + /* + * return all buffers to vb2 in QUEUED state. + * This will give ownership back to userspace + */ + if (vbuf) + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED); + + while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED); + return ret; +} + +static void delta_vb2_au_stop_streaming(struct vb2_queue *q) +{ + struct delta_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + delta_flush_dts(ctx); + + /* return all buffers to vb2 in ERROR state */ + while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + + ctx->au_num = 0; + + ctx->aborting = false; +} + +static int delta_vb2_frame_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, + unsigned int *num_planes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct delta_ctx *ctx = vb2_get_drv_priv(vq); + struct delta_dev *delta = ctx->dev; + struct delta_streaminfo *streaminfo = &ctx->streaminfo; + struct delta_frameinfo *frameinfo = &ctx->frameinfo; + unsigned int size = frameinfo->size; + + /* + * the number of output buffers needed for decoding = + * user need (*num_buffers given, usually for display pipeline) + + * stream need (streaminfo->dpb) + + * decoding peak smoothing (depends on DELTA IP perf) + */ + if (*num_buffers < DELTA_MIN_FRAME_USER) { + dev_dbg(delta->dev, + "%s num_buffers too low (%d), increasing to %d\n", + ctx->name, *num_buffers, DELTA_MIN_FRAME_USER); + *num_buffers = DELTA_MIN_FRAME_USER; + } + + *num_buffers += streaminfo->dpb + DELTA_PEAK_FRAME_SMOOTHING; + + if (*num_buffers > DELTA_MAX_FRAMES) { + dev_dbg(delta->dev, + "%s output frame count too high (%d), cut to %d\n", + ctx->name, *num_buffers, DELTA_MAX_FRAMES); + *num_buffers = DELTA_MAX_FRAMES; + } + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + + /* single plane for Y and CbCr */ + *num_planes = 1; + + sizes[0] = size; + + ctx->nb_of_frames = 0; + + return 0; +} + +static int delta_vb2_frame_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + struct delta_ctx *ctx = vb2_get_drv_priv(q); + struct delta_dev *delta = ctx->dev; + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct delta_frame *frame = to_frame(vbuf); + int ret = 0; + + if (!frame->prepared) { + frame->index = vbuf->vb2_buf.index; + frame->vaddr = vb2_plane_vaddr(&vbuf->vb2_buf, 0); + frame->paddr = vb2_dma_contig_plane_dma_addr(&vbuf->vb2_buf, 0); + frame->info = ctx->frameinfo; + + ret = delta_setup_frame(ctx, frame); + if (ret) { + dev_err(delta->dev, + "%s setup_frame() failed (%d)\n", + ctx->name, ret); + return ret; + } + frame->prepared = true; + dev_dbg(delta->dev, + "%s frame[%d] prepared; virt=0x%p, phy=0x%pad\n", + ctx->name, vb->index, frame->vaddr, + &frame->paddr); + } + + frame->flags = vbuf->flags; + + return 0; +} + +static void delta_vb2_frame_finish(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct delta_frame *frame = to_frame(vbuf); + + /* update V4L2 fields for user */ + vb2_set_plane_payload(&vbuf->vb2_buf, 0, frame->info.size); + vb->timestamp = frame->dts; + vbuf->field = frame->field; + vbuf->flags = frame->flags; +} + +static void delta_vb2_frame_queue(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + struct delta_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct delta_frame *frame = to_frame(vbuf); + + if (ctx->state == DELTA_STATE_WF_EOS) { + /* new frame available, EOS can now be completed */ + delta_complete_eos(ctx, frame); + + ctx->state = DELTA_STATE_EOS; + + /* return, no need to recycle this buffer to decoder */ + return; + } + + /* recycle this frame */ + delta_recycle(ctx, frame); +} + +static void delta_vb2_frame_stop_streaming(struct vb2_queue *q) +{ + struct delta_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + struct delta_frame *frame; + const struct delta_dec *dec = ctx->dec; + unsigned int i; + + delta_flush_dts(ctx); + + call_dec_op(dec, flush, ctx); + + /* + * return all buffers to vb2 in ERROR state + * & reset each frame state to OUT + */ + for (i = 0; i < ctx->nb_of_frames; i++) { + frame = ctx->frames[i]; + if (!(frame->state & DELTA_FRAME_OUT)) { + vbuf = &frame->vbuf; + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } + frame->state = DELTA_FRAME_OUT; + } + + ctx->frame_num = 0; + + ctx->aborting = false; +} + +/* VB2 queue ops */ +static const struct vb2_ops delta_vb2_au_ops = { + .queue_setup = delta_vb2_au_queue_setup, + .buf_prepare = delta_vb2_au_prepare, + .buf_queue = delta_vb2_au_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = delta_vb2_au_start_streaming, + .stop_streaming = delta_vb2_au_stop_streaming, +}; + +static const struct vb2_ops delta_vb2_frame_ops = { + .queue_setup = delta_vb2_frame_queue_setup, + .buf_prepare = delta_vb2_frame_prepare, + .buf_finish = delta_vb2_frame_finish, + .buf_queue = delta_vb2_frame_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .stop_streaming = delta_vb2_frame_stop_streaming, +}; + +/* + * V4L2 file operations + */ + +static int queue_init(void *priv, + struct vb2_queue *src_vq, struct vb2_queue *dst_vq) +{ + struct vb2_queue *q; + struct delta_ctx *ctx = priv; + struct delta_dev *delta = ctx->dev; + int ret; + + /* setup vb2 queue for stream input */ + q = src_vq; + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = ctx; + /* overload vb2 buf with private au struct */ + q->buf_struct_size = sizeof(struct delta_au); + q->ops = &delta_vb2_au_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + q->lock = &delta->lock; + q->dev = delta->dev; + + ret = vb2_queue_init(q); + if (ret) + return ret; + + /* setup vb2 queue for frame output */ + q = dst_vq; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = ctx; + /* overload vb2 buf with private frame struct */ + q->buf_struct_size = sizeof(struct delta_frame) + + DELTA_MAX_FRAME_PRIV_SIZE; + q->ops = &delta_vb2_frame_ops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + q->lock = &delta->lock; + q->dev = delta->dev; + + return vb2_queue_init(q); +} + +static int delta_open(struct file *file) +{ + struct delta_dev *delta = video_drvdata(file); + struct delta_ctx *ctx = NULL; + int ret = 0; + + mutex_lock(&delta->lock); + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto err; + } + ctx->dev = delta; + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + INIT_WORK(&ctx->run_work, delta_run_work); + mutex_init(&ctx->lock); + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(delta->m2m_dev, ctx, + queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + dev_err(delta->dev, "%s failed to initialize m2m context (%d)\n", + DELTA_PREFIX, ret); + goto err_fh_del; + } + + /* + * wait stream format to determine which + * decoder to open + */ + ctx->state = DELTA_STATE_WF_FORMAT; + + INIT_LIST_HEAD(&ctx->dts); + + /* set the instance name */ + delta->instance_id++; + snprintf(ctx->name, sizeof(ctx->name), "[%3d:----]", + delta->instance_id); + + /* default parameters for frame and stream */ + set_default_params(ctx); + + /* enable ST231 clocks */ + if (clk_prepare_enable(delta->clk_st231)) + dev_warn(delta->dev, "failed to enable st231 clk\n"); + + /* enable FLASH_PROMIP clock */ + if (clk_prepare_enable(delta->clk_flash_promip)) + dev_warn(delta->dev, "failed to enable delta promip clk\n"); + + mutex_unlock(&delta->lock); + + dev_dbg(delta->dev, "%s decoder instance created\n", ctx->name); + + return 0; + +err_fh_del: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +err: + mutex_unlock(&delta->lock); + + return ret; +} + +static int delta_release(struct file *file) +{ + struct delta_ctx *ctx = to_ctx(file->private_data); + struct delta_dev *delta = ctx->dev; + const struct delta_dec *dec = ctx->dec; + + mutex_lock(&delta->lock); + + /* close decoder */ + call_dec_op(dec, close, ctx); + + /* + * trace a summary of instance + * before closing (debug purpose) + */ + delta_trace_summary(ctx); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + + /* disable ST231 clocks */ + clk_disable_unprepare(delta->clk_st231); + + /* disable FLASH_PROMIP clock */ + clk_disable_unprepare(delta->clk_flash_promip); + + dev_dbg(delta->dev, "%s decoder instance released\n", ctx->name); + + kfree(ctx); + + mutex_unlock(&delta->lock); + return 0; +} + +/* V4L2 file ops */ +static const struct v4l2_file_operations delta_fops = { + .owner = THIS_MODULE, + .open = delta_open, + .release = delta_release, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, + .poll = v4l2_m2m_fop_poll, +}; + +/* + * Platform device operations + */ + +static int delta_register_device(struct delta_dev *delta) +{ + int ret; + struct video_device *vdev; + + if (!delta) + return -ENODEV; + + delta->m2m_dev = v4l2_m2m_init(&delta_m2m_ops); + if (IS_ERR(delta->m2m_dev)) { + dev_err(delta->dev, "%s failed to initialize v4l2-m2m device\n", + DELTA_PREFIX); + ret = PTR_ERR(delta->m2m_dev); + goto err; + } + + vdev = video_device_alloc(); + if (!vdev) { + dev_err(delta->dev, "%s failed to allocate video device\n", + DELTA_PREFIX); + ret = -ENOMEM; + goto err_m2m_release; + } + + vdev->fops = &delta_fops; + vdev->ioctl_ops = &delta_ioctl_ops; + vdev->release = video_device_release; + vdev->lock = &delta->lock; + vdev->vfl_dir = VFL_DIR_M2M; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M; + vdev->v4l2_dev = &delta->v4l2_dev; + snprintf(vdev->name, sizeof(vdev->name), "%s-%s", + DELTA_NAME, DELTA_FW_VERSION); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(delta->dev, "%s failed to register video device\n", + DELTA_PREFIX); + goto err_vdev_release; + } + + delta->vdev = vdev; + video_set_drvdata(vdev, delta); + return 0; + +err_vdev_release: + video_device_release(vdev); +err_m2m_release: + v4l2_m2m_release(delta->m2m_dev); +err: + return ret; +} + +static void delta_unregister_device(struct delta_dev *delta) +{ + if (!delta) + return; + + if (delta->m2m_dev) + v4l2_m2m_release(delta->m2m_dev); + + video_unregister_device(delta->vdev); +} + +static int delta_probe(struct platform_device *pdev) +{ + struct delta_dev *delta; + struct device *dev = &pdev->dev; + int ret; + + delta = devm_kzalloc(dev, sizeof(*delta), GFP_KERNEL); + if (!delta) { + ret = -ENOMEM; + goto err; + } + + delta->dev = dev; + delta->pdev = pdev; + platform_set_drvdata(pdev, delta); + + mutex_init(&delta->lock); + + /* get clock resources */ + delta->clk_delta = devm_clk_get(dev, "delta"); + if (IS_ERR(delta->clk_delta)) { + dev_dbg(dev, "%s can't get delta clock\n", DELTA_PREFIX); + delta->clk_delta = NULL; + } + + delta->clk_st231 = devm_clk_get(dev, "delta-st231"); + if (IS_ERR(delta->clk_st231)) { + dev_dbg(dev, "%s can't get delta-st231 clock\n", DELTA_PREFIX); + delta->clk_st231 = NULL; + } + + delta->clk_flash_promip = devm_clk_get(dev, "delta-flash-promip"); + if (IS_ERR(delta->clk_flash_promip)) { + dev_dbg(dev, "%s can't get delta-flash-promip clock\n", + DELTA_PREFIX); + delta->clk_flash_promip = NULL; + } + + /* init pm_runtime used for power management */ + pm_runtime_set_autosuspend_delay(dev, DELTA_HW_AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_suspended(dev); + pm_runtime_enable(dev); + + /* init firmware ipc channel */ + ret = delta_ipc_init(delta); + if (ret) { + dev_err(delta->dev, "%s failed to initialize firmware ipc channel\n", + DELTA_PREFIX); + goto err_pm_disable; + } + + /* register all available decoders */ + register_decoders(delta); + + /* register all supported formats */ + register_formats(delta); + + /* register on V4L2 */ + ret = v4l2_device_register(dev, &delta->v4l2_dev); + if (ret) { + dev_err(delta->dev, "%s failed to register V4L2 device\n", + DELTA_PREFIX); + goto err_pm_disable; + } + + delta->work_queue = create_workqueue(DELTA_NAME); + if (!delta->work_queue) { + dev_err(delta->dev, "%s failed to allocate work queue\n", + DELTA_PREFIX); + ret = -ENOMEM; + goto err_v4l2; + } + + /* register device */ + ret = delta_register_device(delta); + if (ret) + goto err_work_queue; + + dev_info(dev, "%s %s registered as /dev/video%d\n", + DELTA_PREFIX, delta->vdev->name, delta->vdev->num); + + return 0; + +err_work_queue: + destroy_workqueue(delta->work_queue); +err_v4l2: + v4l2_device_unregister(&delta->v4l2_dev); +err_pm_disable: + pm_runtime_disable(dev); +err: + return ret; +} + +static int delta_remove(struct platform_device *pdev) +{ + struct delta_dev *delta = platform_get_drvdata(pdev); + + delta_ipc_exit(delta); + + delta_unregister_device(delta); + + destroy_workqueue(delta->work_queue); + + pm_runtime_put_autosuspend(delta->dev); + pm_runtime_disable(delta->dev); + + v4l2_device_unregister(&delta->v4l2_dev); + + return 0; +} + +static int delta_runtime_suspend(struct device *dev) +{ + struct delta_dev *delta = dev_get_drvdata(dev); + + clk_disable_unprepare(delta->clk_delta); + + return 0; +} + +static int delta_runtime_resume(struct device *dev) +{ + struct delta_dev *delta = dev_get_drvdata(dev); + + if (clk_prepare_enable(delta->clk_delta)) + dev_warn(dev, "failed to prepare/enable delta clk\n"); + + return 0; +} + +/* PM ops */ +static const struct dev_pm_ops delta_pm_ops = { + .runtime_suspend = delta_runtime_suspend, + .runtime_resume = delta_runtime_resume, +}; + +static const struct of_device_id delta_match_types[] = { + { + .compatible = "st,st-delta", + }, + { + /* end node */ + } +}; + +MODULE_DEVICE_TABLE(of, delta_match_types); + +static struct platform_driver delta_driver = { + .probe = delta_probe, + .remove = delta_remove, + .driver = { + .name = DELTA_NAME, + .of_match_table = delta_match_types, + .pm = &delta_pm_ops}, +}; + +module_platform_driver(delta_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Hugues Fruchet "); +MODULE_DESCRIPTION("STMicroelectronics DELTA video decoder V4L2 driver"); diff --git a/drivers/media/platform/st/sti/delta/delta.h b/drivers/media/platform/st/sti/delta/delta.h new file mode 100644 index 000000000..914556030 --- /dev/null +++ b/drivers/media/platform/st/sti/delta/delta.h @@ -0,0 +1,566 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Author: Hugues Fruchet for STMicroelectronics. + */ + +#ifndef DELTA_H +#define DELTA_H + +#include +#include +#include + +#include "delta-cfg.h" + +/* + * enum delta_state - state of decoding instance + * + *@DELTA_STATE_WF_FORMAT: + * Wait for compressed format to be set by V4L2 client in order + * to know what is the relevant decoder to open. + * + *@DELTA_STATE_WF_STREAMINFO: + * Wait for stream information to be available (bitstream + * header parsing is done). + * + *@DELTA_STATE_READY: + * Decoding instance is ready to decode compressed access unit. + * + *@DELTA_STATE_WF_EOS: + * Decoding instance is waiting for EOS (End Of Stream) completion. + * + *@DELTA_STATE_EOS: + * EOS (End Of Stream) is completed (signaled to user). Decoding instance + * should then be closed. + */ +enum delta_state { + DELTA_STATE_WF_FORMAT, + DELTA_STATE_WF_STREAMINFO, + DELTA_STATE_READY, + DELTA_STATE_WF_EOS, + DELTA_STATE_EOS +}; + +/* + * struct delta_streaminfo - information about stream to decode + * + * @flags: validity of fields (crop, pixelaspect, other) + * @width: width of video stream + * @height: height "" + * @streamformat: fourcc compressed format of video (MJPEG, MPEG2, ...) + * @dpb: number of frames needed to decode a single frame + * (h264 dpb, up to 16) + * @crop: cropping window inside decoded frame (1920x1080@0,0 + * inside 1920x1088 frame for ex.) + * @pixelaspect: pixel aspect ratio of video (4/3, 5/4) + * @field: interlaced or not + * @profile: profile string + * @level: level string + * @other: other string information from codec + * @colorspace: colorspace identifier + * @xfer_func: transfer function identifier + * @ycbcr_enc: Y'CbCr encoding identifier + * @quantization: quantization identifier + */ +struct delta_streaminfo { + u32 flags; + u32 streamformat; + u32 width; + u32 height; + u32 dpb; + struct v4l2_rect crop; + struct v4l2_fract pixelaspect; + enum v4l2_field field; + u8 profile[32]; + u8 level[32]; + u8 other[32]; + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; +}; + +#define DELTA_STREAMINFO_FLAG_CROP 0x0001 +#define DELTA_STREAMINFO_FLAG_PIXELASPECT 0x0002 +#define DELTA_STREAMINFO_FLAG_OTHER 0x0004 + +/* + * struct delta_au - access unit structure. + * + * @vbuf: video buffer information for V4L2 + * @list: V4L2 m2m list that the frame belongs to + * @prepared: if set vaddr/paddr are resolved + * @vaddr: virtual address (kernel can read/write) + * @paddr: physical address (for hardware) + * @flags: access unit type (V4L2_BUF_FLAG_KEYFRAME/PFRAME/BFRAME) + * @dts: decoding timestamp of this access unit + */ +struct delta_au { + struct vb2_v4l2_buffer vbuf; /* keep first */ + struct list_head list; /* keep second */ + + bool prepared; + u32 size; + void *vaddr; + dma_addr_t paddr; + u32 flags; + u64 dts; +}; + +/* + * struct delta_frameinfo - information about decoded frame + * + * @flags: validity of fields (crop, pixelaspect) + * @pixelformat: fourcc code for uncompressed video format + * @width: width of frame + * @height: height of frame + * @aligned_width: width of frame (with encoder or decoder alignment + * constraint) + * @aligned_height: height of frame (with encoder or decoder alignment + * constraint) + * @size: maximum size in bytes required for data + * @crop: cropping window inside frame (1920x1080@0,0 + * inside 1920x1088 frame for ex.) + * @pixelaspect: pixel aspect ratio of video (4/3, 5/4) + * @field: interlaced mode + * @colorspace: colorspace identifier + * @xfer_func: transfer function identifier + * @ycbcr_enc: Y'CbCr encoding identifier + * @quantization: quantization identifier + */ +struct delta_frameinfo { + u32 flags; + u32 pixelformat; + u32 width; + u32 height; + u32 aligned_width; + u32 aligned_height; + u32 size; + struct v4l2_rect crop; + struct v4l2_fract pixelaspect; + enum v4l2_field field; + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; +}; + +#define DELTA_FRAMEINFO_FLAG_CROP 0x0001 +#define DELTA_FRAMEINFO_FLAG_PIXELASPECT 0x0002 + +/* + * struct delta_frame - frame structure. + * + * @vbuf: video buffer information for V4L2 + * @list: V4L2 m2m list that the frame belongs to + * @info: frame information (width, height, format, alignment...) + * @prepared: if set pix/vaddr/paddr are resolved + * @index: frame index, aligned on V4L2 wow + * @vaddr: virtual address (kernel can read/write) + * @paddr: physical address (for hardware) + * @state: frame state for frame lifecycle tracking + * (DELTA_FRAME_FREE/DEC/OUT/REC/...) + * @flags: frame type (V4L2_BUF_FLAG_KEYFRAME/PFRAME/BFRAME) + * @dts: decoding timestamp of this frame + * @field: field order for interlaced frame + */ +struct delta_frame { + struct vb2_v4l2_buffer vbuf; /* keep first */ + struct list_head list; /* keep second */ + + struct delta_frameinfo info; + bool prepared; + u32 index; + void *vaddr; + dma_addr_t paddr; + u32 state; + u32 flags; + u64 dts; + enum v4l2_field field; +}; + +/* frame state for frame lifecycle tracking */ +#define DELTA_FRAME_FREE 0x00 /* is free and can be used for decoding */ +#define DELTA_FRAME_REF 0x01 /* is a reference frame */ +#define DELTA_FRAME_BSY 0x02 /* is owned by decoder and busy */ +#define DELTA_FRAME_DEC 0x04 /* contains decoded content */ +#define DELTA_FRAME_OUT 0x08 /* has been given to user */ +#define DELTA_FRAME_RDY 0x10 /* is ready but still held by decoder */ +#define DELTA_FRAME_M2M 0x20 /* is owned by mem2mem framework */ + +/* + * struct delta_dts - decoding timestamp. + * + * @list: list to chain timestamps + * @val: timestamp in microseconds + */ +struct delta_dts { + struct list_head list; + u64 val; +}; + +struct delta_buf { + u32 size; + void *vaddr; + dma_addr_t paddr; + const char *name; + unsigned long attrs; +}; + +struct delta_ipc_ctx { + int cb_err; + u32 copro_hdl; + struct completion done; + struct delta_buf ipc_buf_struct; + struct delta_buf *ipc_buf; +}; + +struct delta_ipc_param { + u32 size; + void *data; +}; + +struct delta_ctx; + +/* + * struct delta_dec - decoder structure. + * + * @name: name of this decoder + * @streamformat: input stream format that this decoder support + * @pixelformat: pixel format of decoded frame that this decoder support + * @max_width: (optional) maximum width that can decode this decoder + * if not set, maximum width is DELTA_MAX_WIDTH + * @max_height: (optional) maximum height that can decode this decoder + * if not set, maximum height is DELTA_MAX_HEIGHT + * @pm: (optional) if set, decoder will manage power on its own + * @open: open this decoder + * @close: close this decoder + * @setup_frame: setup frame to be used by decoder, see below + * @get_streaminfo: get stream related infos, see below + * @get_frameinfo: get decoded frame related infos, see below + * @set_frameinfo: (optional) set decoded frame related infos, see below + * @setup_frame: setup frame to be used by decoder, see below + * @decode: decode a single access unit, see below + * @get_frame: get the next decoded frame available, see below + * @recycle: recycle the given frame, see below + * @flush: (optional) flush decoder, see below + * @drain: (optional) drain decoder, see below + */ +struct delta_dec { + const char *name; + u32 streamformat; + u32 pixelformat; + u32 max_width; + u32 max_height; + bool pm; + + /* + * decoder ops + */ + int (*open)(struct delta_ctx *ctx); + int (*close)(struct delta_ctx *ctx); + + /* + * setup_frame() - setup frame to be used by decoder + * @ctx: (in) instance + * @frame: (in) frame to use + * @frame.index (in) identifier of frame + * @frame.vaddr (in) virtual address (kernel can read/write) + * @frame.paddr (in) physical address (for hardware) + * + * Frame is to be allocated by caller, then given + * to decoder through this call. + * Several frames must be given to decoder (dpb), + * each frame is identified using its index. + */ + int (*setup_frame)(struct delta_ctx *ctx, struct delta_frame *frame); + + /* + * get_streaminfo() - get stream related infos + * @ctx: (in) instance + * @streaminfo: (out) width, height, dpb,... + * + * Precondition: stream header must have been successfully + * parsed to have this call successful & @streaminfo valid. + * Header parsing must be done using decode(), giving + * explicitly header access unit or first access unit of bitstream. + * If no valid header is found, get_streaminfo will return -ENODATA, + * in this case the next bitstream access unit must be decoded till + * get_streaminfo becomes successful. + */ + int (*get_streaminfo)(struct delta_ctx *ctx, + struct delta_streaminfo *streaminfo); + + /* + * get_frameinfo() - get decoded frame related infos + * @ctx: (in) instance + * @frameinfo: (out) width, height, alignment, crop, ... + * + * Precondition: get_streaminfo() must be successful + */ + int (*get_frameinfo)(struct delta_ctx *ctx, + struct delta_frameinfo *frameinfo); + + /* + * set_frameinfo() - set decoded frame related infos + * @ctx: (in) instance + * @frameinfo: (out) width, height, alignment, crop, ... + * + * Optional. + * Typically used to negotiate with decoder the output + * frame if decoder can do post-processing. + */ + int (*set_frameinfo)(struct delta_ctx *ctx, + struct delta_frameinfo *frameinfo); + + /* + * decode() - decode a single access unit + * @ctx: (in) instance + * @au: (in/out) access unit + * @au.size (in) size of au to decode + * @au.vaddr (in) virtual address (kernel can read/write) + * @au.paddr (in) physical address (for hardware) + * @au.flags (out) au type (V4L2_BUF_FLAG_KEYFRAME/ + * PFRAME/BFRAME) + * + * Decode the access unit given. Decode is synchronous; + * access unit memory is no more needed after this call. + * After this call, none, one or several frames could + * have been decoded, which can be retrieved using + * get_frame(). + */ + int (*decode)(struct delta_ctx *ctx, struct delta_au *au); + + /* + * get_frame() - get the next decoded frame available + * @ctx: (in) instance + * @frame: (out) frame with decoded data: + * @frame.index (out) identifier of frame + * @frame.field (out) field order for interlaced frame + * @frame.state (out) frame state for frame lifecycle tracking + * @frame.flags (out) frame type (V4L2_BUF_FLAG_KEYFRAME/ + * PFRAME/BFRAME) + * + * Get the next available decoded frame. + * If no frame is available, -ENODATA is returned. + * If a frame is available, frame structure is filled with + * relevant data, frame.index identifying this exact frame. + * When this frame is no more needed by upper layers, + * recycle() must be called giving this frame identifier. + */ + int (*get_frame)(struct delta_ctx *ctx, struct delta_frame **frame); + + /* + * recycle() - recycle the given frame + * @ctx: (in) instance + * @frame: (in) frame to recycle: + * @frame.index (in) identifier of frame + * + * recycle() is to be called by user when the decoded frame + * is no more needed (composition/display done). + * This frame will then be reused by decoder to proceed + * with next frame decoding. + * If not enough frames have been provided through setup_frame(), + * or recycle() is not called fast enough, the decoder can run out + * of available frames to proceed with decoding (starvation). + * This case is guarded by wq_recycle wait queue which ensures that + * decoder is called only if at least one frame is available. + */ + int (*recycle)(struct delta_ctx *ctx, struct delta_frame *frame); + + /* + * flush() - flush decoder + * @ctx: (in) instance + * + * Optional. + * Reset decoder context and discard all internal buffers. + * This allows implementation of seek, which leads to discontinuity + * of input bitstream that decoder must know to restart its internal + * decoding logic. + */ + int (*flush)(struct delta_ctx *ctx); + + /* + * drain() - drain decoder + * @ctx: (in) instance + * + * Optional. + * Mark decoder pending frames (decoded but not yet output) as ready + * so that they can be output to client at EOS (End Of Stream). + * get_frame() is to be called in a loop right after drain() to + * get all those pending frames. + */ + int (*drain)(struct delta_ctx *ctx); +}; + +struct delta_dev; + +/* + * struct delta_ctx - instance structure. + * + * @flags: validity of fields (streaminfo) + * @fh: V4L2 file handle + * @dev: device context + * @dec: selected decoder context for this instance + * @ipc_ctx: context of IPC communication with firmware + * @state: instance state + * @frame_num: frame number + * @au_num: access unit number + * @max_au_size: max size of an access unit + * @streaminfo: stream information (width, height, dpb, interlacing...) + * @frameinfo: frame information (width, height, format, alignment...) + * @nb_of_frames: number of frames available for decoding + * @frames: array of decoding frames to keep track of frame + * state and manage frame recycling + * @decoded_frames: nb of decoded frames from opening + * @output_frames: nb of output frames from opening + * @dropped_frames: nb of frames dropped (ie access unit not parsed + * or frame decoded but not output) + * @stream_errors: nb of stream errors (corrupted, not supported, ...) + * @decode_errors: nb of decode errors (firmware error) + * @sys_errors: nb of system errors (memory, ipc, ...) + * @dts: FIFO of decoding timestamp. + * output frames are timestamped with incoming access + * unit timestamps using this fifo. + * @name: string naming this instance (debug purpose) + * @run_work: decoding work + * @lock: lock for decoding work serialization + * @aborting: true if current job aborted + * @priv: private decoder context for this instance, allocated + * by decoder @open time. + */ +struct delta_ctx { + u32 flags; + struct v4l2_fh fh; + struct delta_dev *dev; + const struct delta_dec *dec; + struct delta_ipc_ctx ipc_ctx; + + enum delta_state state; + u32 frame_num; + u32 au_num; + size_t max_au_size; + struct delta_streaminfo streaminfo; + struct delta_frameinfo frameinfo; + u32 nb_of_frames; + struct delta_frame *frames[DELTA_MAX_FRAMES]; + u32 decoded_frames; + u32 output_frames; + u32 dropped_frames; + u32 stream_errors; + u32 decode_errors; + u32 sys_errors; + struct list_head dts; + char name[100]; + struct work_struct run_work; + struct mutex lock; + bool aborting; + void *priv; +}; + +#define DELTA_FLAG_STREAMINFO 0x0001 +#define DELTA_FLAG_FRAMEINFO 0x0002 + +#define DELTA_MAX_FORMATS DELTA_MAX_DECODERS + +/* + * struct delta_dev - device struct, 1 per probe (so single one for + * all platform life) + * + * @v4l2_dev: v4l2 device + * @vdev: v4l2 video device + * @pdev: platform device + * @dev: device + * @m2m_dev: memory-to-memory V4L2 device + * @lock: device lock, for crit section & V4L2 ops serialization. + * @clk_delta: delta main clock + * @clk_st231: st231 coprocessor main clock + * @clk_flash_promip: flash promip clock + * @decoders: list of registered decoders + * @nb_of_decoders: nb of registered decoders + * @pixelformats: supported uncompressed video formats + * @nb_of_pixelformats: number of supported umcompressed video formats + * @streamformats: supported compressed video formats + * @nb_of_streamformats:number of supported compressed video formats + * @instance_id: rolling counter identifying an instance (debug purpose) + * @work_queue: decoding job work queue + * @rpmsg_driver: rpmsg IPC driver + * @rpmsg_device: rpmsg IPC device + */ +struct delta_dev { + struct v4l2_device v4l2_dev; + struct video_device *vdev; + struct platform_device *pdev; + struct device *dev; + struct v4l2_m2m_dev *m2m_dev; + struct mutex lock; + struct clk *clk_delta; + struct clk *clk_st231; + struct clk *clk_flash_promip; + const struct delta_dec *decoders[DELTA_MAX_DECODERS]; + u32 nb_of_decoders; + u32 pixelformats[DELTA_MAX_FORMATS]; + u32 nb_of_pixelformats; + u32 streamformats[DELTA_MAX_FORMATS]; + u32 nb_of_streamformats; + u8 instance_id; + struct workqueue_struct *work_queue; + struct rpmsg_driver rpmsg_driver; + struct rpmsg_device *rpmsg_device; +}; + +static inline char *frame_type_str(u32 flags) +{ + if (flags & V4L2_BUF_FLAG_KEYFRAME) + return "I"; + if (flags & V4L2_BUF_FLAG_PFRAME) + return "P"; + if (flags & V4L2_BUF_FLAG_BFRAME) + return "B"; + if (flags & V4L2_BUF_FLAG_LAST) + return "EOS"; + return "?"; +} + +static inline char *frame_field_str(enum v4l2_field field) +{ + if (field == V4L2_FIELD_NONE) + return "-"; + if (field == V4L2_FIELD_TOP) + return "T"; + if (field == V4L2_FIELD_BOTTOM) + return "B"; + if (field == V4L2_FIELD_INTERLACED) + return "I"; + if (field == V4L2_FIELD_INTERLACED_TB) + return "TB"; + if (field == V4L2_FIELD_INTERLACED_BT) + return "BT"; + return "?"; +} + +static inline char *frame_state_str(u32 state, char *str, unsigned int len) +{ + snprintf(str, len, "%s %s %s %s %s %s", + (state & DELTA_FRAME_REF) ? "ref" : " ", + (state & DELTA_FRAME_BSY) ? "bsy" : " ", + (state & DELTA_FRAME_DEC) ? "dec" : " ", + (state & DELTA_FRAME_OUT) ? "out" : " ", + (state & DELTA_FRAME_M2M) ? "m2m" : " ", + (state & DELTA_FRAME_RDY) ? "rdy" : " "); + return str; +} + +int delta_get_frameinfo_default(struct delta_ctx *ctx, + struct delta_frameinfo *frameinfo); +int delta_recycle_default(struct delta_ctx *pctx, + struct delta_frame *frame); + +int delta_get_free_frame(struct delta_ctx *ctx, + struct delta_frame **pframe); + +int delta_get_sync(struct delta_ctx *ctx); +void delta_put_autosuspend(struct delta_ctx *ctx); + +#endif /* DELTA_H */ diff --git a/drivers/media/platform/st/sti/hva/Kconfig b/drivers/media/platform/st/sti/hva/Kconfig new file mode 100644 index 000000000..46d6f82f6 --- /dev/null +++ b/drivers/media/platform/st/sti/hva/Kconfig @@ -0,0 +1,26 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_STI_HVA + tristate "STMicroelectronics HVA multi-format video encoder V4L2 driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_STI || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + This V4L2 driver enables HVA (Hardware Video Accelerator) multi-format + video encoder of STMicroelectronics SoC, allowing hardware encoding of + raw uncompressed formats in various compressed video bitstreams format. + + To compile this driver as a module, choose M here: + the module will be called st-hva. + +config VIDEO_STI_HVA_DEBUGFS + bool "Export STMicroelectronics HVA internals in debugfs" + depends on VIDEO_STI_HVA + depends on DEBUG_FS + help + Select this to see information about the internal state and the last + operation of STMicroelectronics HVA multi-format video encoder in + debugfs. + + Choose N unless you know you need this. diff --git a/drivers/media/platform/st/sti/hva/Makefile b/drivers/media/platform/st/sti/hva/Makefile new file mode 100644 index 000000000..b5a5478bd --- /dev/null +++ b/drivers/media/platform/st/sti/hva/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_STI_HVA) += st-hva.o +st-hva-y := hva-v4l2.o hva-hw.o hva-mem.o hva-h264.o +st-hva-$(CONFIG_VIDEO_STI_HVA_DEBUGFS) += hva-debugfs.o diff --git a/drivers/media/platform/st/sti/hva/hva-debugfs.c b/drivers/media/platform/st/sti/hva/hva-debugfs.c new file mode 100644 index 000000000..a86a07b6f --- /dev/null +++ b/drivers/media/platform/st/sti/hva/hva-debugfs.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Yannick Fertre + * Hugues Fruchet + */ + +#include + +#include "hva.h" +#include "hva-hw.h" + +static void format_ctx(struct seq_file *s, struct hva_ctx *ctx) +{ + struct hva_streaminfo *stream = &ctx->streaminfo; + struct hva_frameinfo *frame = &ctx->frameinfo; + struct hva_controls *ctrls = &ctx->ctrls; + struct hva_ctx_dbg *dbg = &ctx->dbg; + u32 bitrate_mode, aspect, entropy, vui_sar, sei_fp; + + seq_printf(s, "|-%s\n |\n", ctx->name); + + seq_printf(s, " |-[%sframe info]\n", + ctx->flags & HVA_FLAG_FRAMEINFO ? "" : "default "); + seq_printf(s, " | |- pixel format=%4.4s\n" + " | |- wxh=%dx%d\n" + " | |- wxh (w/ encoder alignment constraint)=%dx%d\n" + " |\n", + (char *)&frame->pixelformat, + frame->width, frame->height, + frame->aligned_width, frame->aligned_height); + + seq_printf(s, " |-[%sstream info]\n", + ctx->flags & HVA_FLAG_STREAMINFO ? "" : "default "); + seq_printf(s, " | |- stream format=%4.4s\n" + " | |- wxh=%dx%d\n" + " | |- %s\n" + " | |- %s\n" + " |\n", + (char *)&stream->streamformat, + stream->width, stream->height, + stream->profile, stream->level); + + bitrate_mode = V4L2_CID_MPEG_VIDEO_BITRATE_MODE; + aspect = V4L2_CID_MPEG_VIDEO_ASPECT; + seq_puts(s, " |-[parameters]\n"); + seq_printf(s, " | |- %s\n" + " | |- bitrate=%d bps\n" + " | |- GOP size=%d\n" + " | |- video aspect=%s\n" + " | |- framerate=%d/%d\n", + v4l2_ctrl_get_menu(bitrate_mode)[ctrls->bitrate_mode], + ctrls->bitrate, + ctrls->gop_size, + v4l2_ctrl_get_menu(aspect)[ctrls->aspect], + ctrls->time_per_frame.denominator, + ctrls->time_per_frame.numerator); + + entropy = V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE; + vui_sar = V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC; + sei_fp = V4L2_CID_MPEG_VIDEO_H264_SEI_FP_ARRANGEMENT_TYPE; + if (stream->streamformat == V4L2_PIX_FMT_H264) { + seq_printf(s, " | |- %s entropy mode\n" + " | |- CPB size=%d kB\n" + " | |- DCT8x8 enable=%s\n" + " | |- qpmin=%d\n" + " | |- qpmax=%d\n" + " | |- PAR enable=%s\n" + " | |- PAR id=%s\n" + " | |- SEI frame packing enable=%s\n" + " | |- SEI frame packing type=%s\n", + v4l2_ctrl_get_menu(entropy)[ctrls->entropy_mode], + ctrls->cpb_size, + ctrls->dct8x8 ? "true" : "false", + ctrls->qpmin, + ctrls->qpmax, + ctrls->vui_sar ? "true" : "false", + v4l2_ctrl_get_menu(vui_sar)[ctrls->vui_sar_idc], + ctrls->sei_fp ? "true" : "false", + v4l2_ctrl_get_menu(sei_fp)[ctrls->sei_fp_type]); + } + + if (ctx->sys_errors || ctx->encode_errors || ctx->frame_errors) { + seq_puts(s, " |\n |-[errors]\n"); + seq_printf(s, " | |- system=%d\n" + " | |- encoding=%d\n" + " | |- frame=%d\n", + ctx->sys_errors, + ctx->encode_errors, + ctx->frame_errors); + } + + seq_puts(s, " |\n |-[performances]\n"); + seq_printf(s, " | |- frames encoded=%d\n" + " | |- avg HW processing duration (0.1ms)=%d [min=%d, max=%d]\n" + " | |- avg encoding period (0.1ms)=%d [min=%d, max=%d]\n" + " | |- avg fps (0.1Hz)=%d\n" + " | |- max reachable fps (0.1Hz)=%d\n" + " | |- avg bitrate (kbps)=%d [min=%d, max=%d]\n" + " | |- last bitrate (kbps)=%d\n", + dbg->cnt_duration, + dbg->avg_duration, + dbg->min_duration, + dbg->max_duration, + dbg->avg_period, + dbg->min_period, + dbg->max_period, + dbg->avg_fps, + dbg->max_fps, + dbg->avg_bitrate, + dbg->min_bitrate, + dbg->max_bitrate, + dbg->last_bitrate); +} + +/* + * performance debug info + */ +void hva_dbg_perf_begin(struct hva_ctx *ctx) +{ + u64 div; + u32 period; + u32 bitrate; + struct hva_ctx_dbg *dbg = &ctx->dbg; + ktime_t prev = dbg->begin; + + dbg->begin = ktime_get(); + + if (dbg->is_valid_period) { + /* encoding period */ + div = (u64)ktime_us_delta(dbg->begin, prev); + do_div(div, 100); + period = (u32)div; + dbg->min_period = min(period, dbg->min_period); + dbg->max_period = max(period, dbg->max_period); + dbg->total_period += period; + dbg->cnt_period++; + + /* + * minimum and maximum bitrates are based on the + * encoding period values upon a window of 32 samples + */ + dbg->window_duration += period; + dbg->cnt_window++; + if (dbg->cnt_window >= 32) { + /* + * bitrate in kbps = (size * 8 / 1000) / + * (duration / 10000) + * = size * 80 / duration + */ + if (dbg->window_duration > 0) { + div = (u64)dbg->window_stream_size * 80; + do_div(div, dbg->window_duration); + bitrate = (u32)div; + dbg->last_bitrate = bitrate; + dbg->min_bitrate = min(bitrate, + dbg->min_bitrate); + dbg->max_bitrate = max(bitrate, + dbg->max_bitrate); + } + dbg->window_stream_size = 0; + dbg->window_duration = 0; + dbg->cnt_window = 0; + } + } + + /* + * filter sequences valid for performance: + * - begin/begin (no stream available) is an invalid sequence + * - begin/end is a valid sequence + */ + dbg->is_valid_period = false; +} + +void hva_dbg_perf_end(struct hva_ctx *ctx, struct hva_stream *stream) +{ + struct device *dev = ctx_to_dev(ctx); + u64 div; + u32 duration; + u32 bytesused; + u32 timestamp; + struct hva_ctx_dbg *dbg = &ctx->dbg; + ktime_t end = ktime_get(); + + /* stream bytesused and timestamp in us */ + bytesused = vb2_get_plane_payload(&stream->vbuf.vb2_buf, 0); + div = stream->vbuf.vb2_buf.timestamp; + do_div(div, 1000); + timestamp = (u32)div; + + /* encoding duration */ + div = (u64)ktime_us_delta(end, dbg->begin); + + dev_dbg(dev, + "%s perf stream[%d] dts=%d encoded using %d bytes in %d us", + ctx->name, + stream->vbuf.sequence, + timestamp, + bytesused, (u32)div); + + do_div(div, 100); + duration = (u32)div; + + dbg->min_duration = min(duration, dbg->min_duration); + dbg->max_duration = max(duration, dbg->max_duration); + dbg->total_duration += duration; + dbg->cnt_duration++; + + /* + * the average bitrate is based on the total stream size + * and the total encoding periods + */ + dbg->total_stream_size += bytesused; + dbg->window_stream_size += bytesused; + + dbg->is_valid_period = true; +} + +static void hva_dbg_perf_compute(struct hva_ctx *ctx) +{ + u64 div; + struct hva_ctx_dbg *dbg = &ctx->dbg; + + if (dbg->cnt_duration > 0) { + div = (u64)dbg->total_duration; + do_div(div, dbg->cnt_duration); + dbg->avg_duration = (u32)div; + } else { + dbg->avg_duration = 0; + } + + if (dbg->total_duration > 0) { + div = (u64)dbg->cnt_duration * 100000; + do_div(div, dbg->total_duration); + dbg->max_fps = (u32)div; + } else { + dbg->max_fps = 0; + } + + if (dbg->cnt_period > 0) { + div = (u64)dbg->total_period; + do_div(div, dbg->cnt_period); + dbg->avg_period = (u32)div; + } else { + dbg->avg_period = 0; + } + + if (dbg->total_period > 0) { + div = (u64)dbg->cnt_period * 100000; + do_div(div, dbg->total_period); + dbg->avg_fps = (u32)div; + } else { + dbg->avg_fps = 0; + } + + if (dbg->total_period > 0) { + /* + * bitrate in kbps = (video size * 8 / 1000) / + * (video duration / 10000) + * = video size * 80 / video duration + */ + div = (u64)dbg->total_stream_size * 80; + do_div(div, dbg->total_period); + dbg->avg_bitrate = (u32)div; + } else { + dbg->avg_bitrate = 0; + } +} + +/* + * device debug info + */ + +static int device_show(struct seq_file *s, void *data) +{ + struct hva_dev *hva = s->private; + + seq_printf(s, "[%s]\n", hva->v4l2_dev.name); + seq_printf(s, "registered as /dev/video%d\n", hva->vdev->num); + + return 0; +} + +static int encoders_show(struct seq_file *s, void *data) +{ + struct hva_dev *hva = s->private; + unsigned int i = 0; + + seq_printf(s, "[encoders]\n|- %d registered encoders:\n", + hva->nb_of_encoders); + + while (hva->encoders[i]) { + seq_printf(s, "|- %s: %4.4s => %4.4s\n", hva->encoders[i]->name, + (char *)&hva->encoders[i]->pixelformat, + (char *)&hva->encoders[i]->streamformat); + i++; + } + + return 0; +} + +static int last_show(struct seq_file *s, void *data) +{ + struct hva_dev *hva = s->private; + struct hva_ctx *last_ctx = &hva->dbg.last_ctx; + + if (last_ctx->flags & HVA_FLAG_STREAMINFO) { + seq_puts(s, "[last encoding]\n"); + + hva_dbg_perf_compute(last_ctx); + format_ctx(s, last_ctx); + } else { + seq_puts(s, "[no information recorded about last encoding]\n"); + } + + return 0; +} + +static int regs_show(struct seq_file *s, void *data) +{ + struct hva_dev *hva = s->private; + + hva_hw_dump_regs(hva, s); + + return 0; +} + +#define hva_dbg_create_entry(name) \ + debugfs_create_file(#name, 0444, hva->dbg.debugfs_entry, hva, \ + &name##_fops) + +DEFINE_SHOW_ATTRIBUTE(device); +DEFINE_SHOW_ATTRIBUTE(encoders); +DEFINE_SHOW_ATTRIBUTE(last); +DEFINE_SHOW_ATTRIBUTE(regs); + +void hva_debugfs_create(struct hva_dev *hva) +{ + hva->dbg.debugfs_entry = debugfs_create_dir(HVA_NAME, NULL); + + hva_dbg_create_entry(device); + hva_dbg_create_entry(encoders); + hva_dbg_create_entry(last); + hva_dbg_create_entry(regs); +} + +void hva_debugfs_remove(struct hva_dev *hva) +{ + debugfs_remove_recursive(hva->dbg.debugfs_entry); + hva->dbg.debugfs_entry = NULL; +} + +/* + * context (instance) debug info + */ + +static int ctx_show(struct seq_file *s, void *data) +{ + struct hva_ctx *ctx = s->private; + + seq_printf(s, "[running encoding %d]\n", ctx->id); + + hva_dbg_perf_compute(ctx); + format_ctx(s, ctx); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(ctx); + +void hva_dbg_ctx_create(struct hva_ctx *ctx) +{ + struct hva_dev *hva = ctx->hva_dev; + char name[4] = ""; + + ctx->dbg.min_duration = UINT_MAX; + ctx->dbg.min_period = UINT_MAX; + ctx->dbg.min_bitrate = UINT_MAX; + + snprintf(name, sizeof(name), "%d", hva->instance_id); + + ctx->dbg.debugfs_entry = debugfs_create_file(name, 0444, + hva->dbg.debugfs_entry, + ctx, &ctx_fops); +} + +void hva_dbg_ctx_remove(struct hva_ctx *ctx) +{ + struct hva_dev *hva = ctx->hva_dev; + + if (ctx->flags & HVA_FLAG_STREAMINFO) + /* save context before removing */ + memcpy(&hva->dbg.last_ctx, ctx, sizeof(*ctx)); + + debugfs_remove(ctx->dbg.debugfs_entry); +} diff --git a/drivers/media/platform/st/sti/hva/hva-h264.c b/drivers/media/platform/st/sti/hva/hva-h264.c new file mode 100644 index 000000000..98cb00d2d --- /dev/null +++ b/drivers/media/platform/st/sti/hva/hva-h264.c @@ -0,0 +1,1063 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Yannick Fertre + * Hugues Fruchet + */ + +#include "hva.h" +#include "hva-hw.h" + +#define MAX_SPS_PPS_SIZE 128 + +#define BITSTREAM_OFFSET_MASK 0x7F + +/* video max size*/ +#define H264_MAX_SIZE_W 1920 +#define H264_MAX_SIZE_H 1920 + +/* macroBlocs number (width & height) */ +#define MB_W(w) ((w + 0xF) / 0x10) +#define MB_H(h) ((h + 0xF) / 0x10) + +/* formula to get temporal or spatial data size */ +#define DATA_SIZE(w, h) (MB_W(w) * MB_H(h) * 16) + +#define SEARCH_WINDOW_BUFFER_MAX_SIZE(w) ((4 * MB_W(w) + 42) * 256 * 3 / 2) +#define CABAC_CONTEXT_BUFFER_MAX_SIZE(w) (MB_W(w) * 16) +#define CTX_MB_BUFFER_MAX_SIZE(w) (MB_W(w) * 16 * 8) +#define SLICE_HEADER_SIZE (4 * 16) +#define BRC_DATA_SIZE (5 * 16) + +/* source buffer copy in YUV 420 MB-tiled format with size=16*256*3/2 */ +#define CURRENT_WINDOW_BUFFER_MAX_SIZE (16 * 256 * 3 / 2) + +/* + * 4 lines of pixels (in Luma, Chroma blue and Chroma red) of top MB + * for deblocking with size=4*16*MBx*2 + */ +#define LOCAL_RECONSTRUCTED_BUFFER_MAX_SIZE(w) (4 * 16 * MB_W(w) * 2) + +/* factor for bitrate and cpb buffer size max values if profile >= high */ +#define H264_FACTOR_HIGH 1200 + +/* factor for bitrate and cpb buffer size max values if profile < high */ +#define H264_FACTOR_BASELINE 1000 + +/* number of bytes for NALU_TYPE_FILLER_DATA header and footer */ +#define H264_FILLER_DATA_SIZE 6 + +struct h264_profile { + enum v4l2_mpeg_video_h264_level level; + u32 max_mb_per_seconds; + u32 max_frame_size; + u32 max_bitrate; + u32 max_cpb_size; + u32 min_comp_ratio; +}; + +static const struct h264_profile h264_infos_list[] = { + {V4L2_MPEG_VIDEO_H264_LEVEL_1_0, 1485, 99, 64, 175, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_1B, 1485, 99, 128, 350, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_1_1, 3000, 396, 192, 500, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_1_2, 6000, 396, 384, 1000, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_1_3, 11880, 396, 768, 2000, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_2_0, 11880, 396, 2000, 2000, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_2_1, 19800, 792, 4000, 4000, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_2_2, 20250, 1620, 4000, 4000, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_3_0, 40500, 1620, 10000, 10000, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_3_1, 108000, 3600, 14000, 14000, 4}, + {V4L2_MPEG_VIDEO_H264_LEVEL_3_2, 216000, 5120, 20000, 20000, 4}, + {V4L2_MPEG_VIDEO_H264_LEVEL_4_0, 245760, 8192, 20000, 25000, 4}, + {V4L2_MPEG_VIDEO_H264_LEVEL_4_1, 245760, 8192, 50000, 62500, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_4_2, 522240, 8704, 50000, 62500, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_5_0, 589824, 22080, 135000, 135000, 2}, + {V4L2_MPEG_VIDEO_H264_LEVEL_5_1, 983040, 36864, 240000, 240000, 2} +}; + +enum hva_brc_type { + BRC_TYPE_NONE = 0, + BRC_TYPE_CBR = 1, + BRC_TYPE_VBR = 2, + BRC_TYPE_VBR_LOW_DELAY = 3 +}; + +enum hva_entropy_coding_mode { + CAVLC = 0, + CABAC = 1 +}; + +enum hva_picture_coding_type { + PICTURE_CODING_TYPE_I = 0, + PICTURE_CODING_TYPE_P = 1, + PICTURE_CODING_TYPE_B = 2 +}; + +enum hva_h264_sampling_mode { + SAMPLING_MODE_NV12 = 0, + SAMPLING_MODE_UYVY = 1, + SAMPLING_MODE_RGB3 = 3, + SAMPLING_MODE_XRGB4 = 4, + SAMPLING_MODE_NV21 = 8, + SAMPLING_MODE_VYUY = 9, + SAMPLING_MODE_BGR3 = 11, + SAMPLING_MODE_XBGR4 = 12, + SAMPLING_MODE_RGBX4 = 20, + SAMPLING_MODE_BGRX4 = 28 +}; + +enum hva_h264_nalu_type { + NALU_TYPE_UNKNOWN = 0, + NALU_TYPE_SLICE = 1, + NALU_TYPE_SLICE_DPA = 2, + NALU_TYPE_SLICE_DPB = 3, + NALU_TYPE_SLICE_DPC = 4, + NALU_TYPE_SLICE_IDR = 5, + NALU_TYPE_SEI = 6, + NALU_TYPE_SPS = 7, + NALU_TYPE_PPS = 8, + NALU_TYPE_AU_DELIMITER = 9, + NALU_TYPE_SEQ_END = 10, + NALU_TYPE_STREAM_END = 11, + NALU_TYPE_FILLER_DATA = 12, + NALU_TYPE_SPS_EXT = 13, + NALU_TYPE_PREFIX_UNIT = 14, + NALU_TYPE_SUBSET_SPS = 15, + NALU_TYPE_SLICE_AUX = 19, + NALU_TYPE_SLICE_EXT = 20 +}; + +enum hva_h264_sei_payload_type { + SEI_BUFFERING_PERIOD = 0, + SEI_PICTURE_TIMING = 1, + SEI_STEREO_VIDEO_INFO = 21, + SEI_FRAME_PACKING_ARRANGEMENT = 45 +}; + +/* + * stereo Video Info struct + */ +struct hva_h264_stereo_video_sei { + u8 field_views_flag; + u8 top_field_is_left_view_flag; + u8 current_frame_is_left_view_flag; + u8 next_frame_is_second_view_flag; + u8 left_view_self_contained_flag; + u8 right_view_self_contained_flag; +}; + +/* + * struct hva_h264_td + * + * @frame_width: width in pixels of the buffer containing the input frame + * @frame_height: height in pixels of the buffer containing the input frame + * @frame_num: the parameter to be written in the slice header + * @picture_coding_type: type I, P or B + * @pic_order_cnt_type: POC mode, as defined in H264 std : can be 0,1,2 + * @first_picture_in_sequence: flag telling to encoder that this is the + * first picture in a video sequence. + * Used for VBR + * @slice_size_type: 0 = no constraint to close the slice + * 1= a slice is closed as soon as the slice_mb_size limit + * is reached + * 2= a slice is closed as soon as the slice_byte_size limit + * is reached + * 3= a slice is closed as soon as either the slice_byte_size + * limit or the slice_mb_size limit is reached + * @slice_mb_size: defines the slice size in number of macroblocks + * (used when slice_size_type=1 or slice_size_type=3) + * @ir_param_option: defines the number of macroblocks per frame to be + * refreshed by AIR algorithm OR the refresh period + * by CIR algorithm + * @intra_refresh_type: enables the adaptive intra refresh algorithm. + * Disable=0 / Adaptative=1 and Cycle=2 as intra refresh + * @use_constrained_intra_flag: constrained_intra_pred_flag from PPS + * @transform_mode: controls the use of 4x4/8x8 transform mode + * @disable_deblocking_filter_idc: + * 0: specifies that all luma and chroma block edges of + * the slice are filtered. + * 1: specifies that deblocking is disabled for all block + * edges of the slice. + * 2: specifies that all luma and chroma block edges of + * the slice are filtered with exception of the block edges + * that coincide with slice boundaries + * @slice_alpha_c0_offset_div2: to be written in slice header, + * controls deblocking + * @slice_beta_offset_div2: to be written in slice header, + * controls deblocking + * @encoder_complexity: encoder complexity control (IME). + * 0 = I_16x16, P_16x16, Full ME Complexity + * 1 = I_16x16, I_NxN, P_16x16, Full ME Complexity + * 2 = I_16x16, I_NXN, P_16x16, P_WxH, Full ME Complexity + * 4 = I_16x16, P_16x16, Reduced ME Complexity + * 5 = I_16x16, I_NxN, P_16x16, Reduced ME Complexity + * 6 = I_16x16, I_NXN, P_16x16, P_WxH, Reduced ME Complexity + * @chroma_qp_index_offset: coming from picture parameter set + * (PPS see [H.264 STD] 7.4.2.2) + * @entropy_coding_mode: entropy coding mode. + * 0 = CAVLC + * 1 = CABAC + * @brc_type: selects the bit-rate control algorithm + * 0 = constant Qp, (no BRC) + * 1 = CBR + * 2 = VBR + * @quant: Quantization param used in case of fix QP encoding (no BRC) + * @non_VCL_NALU_Size: size of non-VCL NALUs (SPS, PPS, filler), + * used by BRC + * @cpb_buffer_size: size of Coded Picture Buffer, used by BRC + * @bit_rate: target bitrate, for BRC + * @qp_min: min QP threshold + * @qp_max: max QP threshold + * @framerate_num: target framerate numerator , used by BRC + * @framerate_den: target framerate denomurator , used by BRC + * @delay: End-to-End Initial Delay + * @strict_HRD_compliancy: flag for HDR compliancy (1) + * May impact quality encoding + * @addr_source_buffer: address of input frame buffer for current frame + * @addr_fwd_Ref_Buffer: address of reference frame buffer + * @addr_rec_buffer: address of reconstructed frame buffer + * @addr_output_bitstream_start: output bitstream start address + * @addr_output_bitstream_end: output bitstream end address + * @addr_external_sw : address of external search window + * @addr_lctx : address of context picture buffer + * @addr_local_rec_buffer: address of local reconstructed buffer + * @addr_spatial_context: address of spatial context buffer + * @bitstream_offset: offset in bits between aligned bitstream start + * address and first bit to be written by HVA. + * Range value is [0..63] + * @sampling_mode: Input picture format . + * 0: YUV420 semi_planar Interleaved + * 1: YUV422 raster Interleaved + * @addr_param_out: address of output parameters structure + * @addr_scaling_matrix: address to the coefficient of + * the inverse scaling matrix + * @addr_scaling_matrix_dir: address to the coefficient of + * the direct scaling matrix + * @addr_cabac_context_buffer: address of cabac context buffer + * @GmvX: Input information about the horizontal global displacement of + * the encoded frame versus the previous one + * @GmvY: Input information about the vertical global displacement of + * the encoded frame versus the previous one + * @window_width: width in pixels of the window to be encoded inside + * the input frame + * @window_height: width in pixels of the window to be encoded inside + * the input frame + * @window_horizontal_offset: horizontal offset in pels for input window + * within input frame + * @window_vertical_offset: vertical offset in pels for input window + * within input frame + * @addr_roi: Map of QP offset for the Region of Interest algorithm and + * also used for Error map. + * Bit 0-6 used for qp offset (value -64 to 63). + * Bit 7 used to force intra + * @addr_slice_header: address to slice header + * @slice_header_size_in_bits: size in bits of the Slice header + * @slice_header_offset0: Slice header offset where to insert + * first_Mb_in_slice + * @slice_header_offset1: Slice header offset where to insert + * slice_qp_delta + * @slice_header_offset2: Slice header offset where to insert + * num_MBs_in_slice + * @slice_synchro_enable: enable "slice ready" interrupt after each slice + * @max_slice_number: Maximum number of slice in a frame + * (0 is strictly forbidden) + * @rgb2_yuv_y_coeff: Four coefficients (C0C1C2C3) to convert from RGB to + * YUV for the Y component. + * Y = C0*R + C1*G + C2*B + C3 (C0 is on byte 0) + * @rgb2_yuv_u_coeff: four coefficients (C0C1C2C3) to convert from RGB to + * YUV for the Y component. + * Y = C0*R + C1*G + C2*B + C3 (C0 is on byte 0) + * @rgb2_yuv_v_coeff: Four coefficients (C0C1C2C3) to convert from RGB to + * YUV for the U (Cb) component. + * U = C0*R + C1*G + C2*B + C3 (C0 is on byte 0) + * @slice_byte_size: maximum slice size in bytes + * (used when slice_size_type=2 or slice_size_type=3) + * @max_air_intra_mb_nb: Maximum number of intra macroblock in a frame + * for the AIR algorithm + * @brc_no_skip: Disable skipping in the Bitrate Controller + * @addr_brc_in_out_parameter: address of static buffer for BRC parameters + */ +struct hva_h264_td { + u16 frame_width; + u16 frame_height; + u32 frame_num; + u16 picture_coding_type; + u16 reserved1; + u16 pic_order_cnt_type; + u16 first_picture_in_sequence; + u16 slice_size_type; + u16 reserved2; + u32 slice_mb_size; + u16 ir_param_option; + u16 intra_refresh_type; + u16 use_constrained_intra_flag; + u16 transform_mode; + u16 disable_deblocking_filter_idc; + s16 slice_alpha_c0_offset_div2; + s16 slice_beta_offset_div2; + u16 encoder_complexity; + s16 chroma_qp_index_offset; + u16 entropy_coding_mode; + u16 brc_type; + u16 quant; + u32 non_vcl_nalu_size; + u32 cpb_buffer_size; + u32 bit_rate; + u16 qp_min; + u16 qp_max; + u16 framerate_num; + u16 framerate_den; + u16 delay; + u16 strict_hrd_compliancy; + u32 addr_source_buffer; + u32 addr_fwd_ref_buffer; + u32 addr_rec_buffer; + u32 addr_output_bitstream_start; + u32 addr_output_bitstream_end; + u32 addr_external_sw; + u32 addr_lctx; + u32 addr_local_rec_buffer; + u32 addr_spatial_context; + u16 bitstream_offset; + u16 sampling_mode; + u32 addr_param_out; + u32 addr_scaling_matrix; + u32 addr_scaling_matrix_dir; + u32 addr_cabac_context_buffer; + u32 reserved3; + u32 reserved4; + s16 gmv_x; + s16 gmv_y; + u16 window_width; + u16 window_height; + u16 window_horizontal_offset; + u16 window_vertical_offset; + u32 addr_roi; + u32 addr_slice_header; + u16 slice_header_size_in_bits; + u16 slice_header_offset0; + u16 slice_header_offset1; + u16 slice_header_offset2; + u32 reserved5; + u32 reserved6; + u16 reserved7; + u16 reserved8; + u16 slice_synchro_enable; + u16 max_slice_number; + u32 rgb2_yuv_y_coeff; + u32 rgb2_yuv_u_coeff; + u32 rgb2_yuv_v_coeff; + u32 slice_byte_size; + u16 max_air_intra_mb_nb; + u16 brc_no_skip; + u32 addr_temporal_context; + u32 addr_brc_in_out_parameter; +}; + +/* + * struct hva_h264_slice_po + * + * @ slice_size: slice size + * @ slice_start_time: start time + * @ slice_stop_time: stop time + * @ slice_num: slice number + */ +struct hva_h264_slice_po { + u32 slice_size; + u32 slice_start_time; + u32 slice_end_time; + u32 slice_num; +}; + +/* + * struct hva_h264_po + * + * @ bitstream_size: bitstream size + * @ dct_bitstream_size: dtc bitstream size + * @ stuffing_bits: number of stuffing bits inserted by the encoder + * @ removal_time: removal time of current frame (nb of ticks 1/framerate) + * @ hvc_start_time: hvc start time + * @ hvc_stop_time: hvc stop time + * @ slice_count: slice count + */ +struct hva_h264_po { + u32 bitstream_size; + u32 dct_bitstream_size; + u32 stuffing_bits; + u32 removal_time; + u32 hvc_start_time; + u32 hvc_stop_time; + u32 slice_count; + u32 reserved0; + struct hva_h264_slice_po slice_params[16]; +}; + +struct hva_h264_task { + struct hva_h264_td td; + struct hva_h264_po po; +}; + +/* + * struct hva_h264_ctx + * + * @seq_info: sequence information buffer + * @ref_frame: reference frame buffer + * @rec_frame: reconstructed frame buffer + * @task: task descriptor + */ +struct hva_h264_ctx { + struct hva_buffer *seq_info; + struct hva_buffer *ref_frame; + struct hva_buffer *rec_frame; + struct hva_buffer *task; +}; + +static int hva_h264_fill_slice_header(struct hva_ctx *pctx, + u8 *slice_header_addr, + struct hva_controls *ctrls, + int frame_num, + u16 *header_size, + u16 *header_offset0, + u16 *header_offset1, + u16 *header_offset2) +{ + /* + * with this HVA hardware version, part of the slice header is computed + * on host and part by hardware. + * The part of host is precomputed and available through this array. + */ + struct device *dev = ctx_to_dev(pctx); + int cabac = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC; + static const unsigned char slice_header[] = { + 0x00, 0x00, 0x00, 0x01, + 0x41, 0x34, 0x07, 0x00 + }; + int idr_pic_id = frame_num % 2; + enum hva_picture_coding_type type; + u32 frame_order = frame_num % ctrls->gop_size; + + if (!(frame_num % ctrls->gop_size)) + type = PICTURE_CODING_TYPE_I; + else + type = PICTURE_CODING_TYPE_P; + + memcpy(slice_header_addr, slice_header, sizeof(slice_header)); + + *header_size = 56; + *header_offset0 = 40; + *header_offset1 = 13; + *header_offset2 = 0; + + if (type == PICTURE_CODING_TYPE_I) { + slice_header_addr[4] = 0x65; + slice_header_addr[5] = 0x11; + + /* toggle the I frame */ + if ((frame_num / ctrls->gop_size) % 2) { + *header_size += 4; + *header_offset1 += 4; + slice_header_addr[6] = 0x04; + slice_header_addr[7] = 0x70; + + } else { + *header_size += 2; + *header_offset1 += 2; + slice_header_addr[6] = 0x09; + slice_header_addr[7] = 0xC0; + } + } else { + if (ctrls->entropy_mode == cabac) { + *header_size += 1; + *header_offset1 += 1; + slice_header_addr[7] = 0x80; + } + /* + * update slice header with P frame order + * frame order is limited to 16 (coded on 4bits only) + */ + slice_header_addr[5] += ((frame_order & 0x0C) >> 2); + slice_header_addr[6] += ((frame_order & 0x03) << 6); + } + + dev_dbg(dev, + "%s %s slice header order %d idrPicId %d header size %d\n", + pctx->name, __func__, frame_order, idr_pic_id, *header_size); + return 0; +} + +static int hva_h264_fill_data_nal(struct hva_ctx *pctx, + unsigned int stuffing_bytes, u8 *addr, + unsigned int stream_size, unsigned int *size) +{ + struct device *dev = ctx_to_dev(pctx); + static const u8 start[] = { 0x00, 0x00, 0x00, 0x01 }; + + dev_dbg(dev, "%s %s stuffing bytes %d\n", pctx->name, __func__, + stuffing_bytes); + + if ((*size + stuffing_bytes + H264_FILLER_DATA_SIZE) > stream_size) { + dev_dbg(dev, "%s %s too many stuffing bytes %d\n", + pctx->name, __func__, stuffing_bytes); + return 0; + } + + /* start code */ + memcpy(addr + *size, start, sizeof(start)); + *size += sizeof(start); + + /* nal_unit_type */ + addr[*size] = NALU_TYPE_FILLER_DATA; + *size += 1; + + memset(addr + *size, 0xff, stuffing_bytes); + *size += stuffing_bytes; + + addr[*size] = 0x80; + *size += 1; + + return 0; +} + +static int hva_h264_fill_sei_nal(struct hva_ctx *pctx, + enum hva_h264_sei_payload_type type, + u8 *addr, u32 *size) +{ + struct device *dev = ctx_to_dev(pctx); + static const u8 start[] = { 0x00, 0x00, 0x00, 0x01 }; + struct hva_h264_stereo_video_sei info; + u8 offset = 7; + u8 msg = 0; + + /* start code */ + memcpy(addr + *size, start, sizeof(start)); + *size += sizeof(start); + + /* nal_unit_type */ + addr[*size] = NALU_TYPE_SEI; + *size += 1; + + /* payload type */ + addr[*size] = type; + *size += 1; + + switch (type) { + case SEI_STEREO_VIDEO_INFO: + memset(&info, 0, sizeof(info)); + + /* set to top/bottom frame packing arrangement */ + info.field_views_flag = 1; + info.top_field_is_left_view_flag = 1; + + /* payload size */ + addr[*size] = 1; + *size += 1; + + /* payload */ + msg = info.field_views_flag << offset--; + + if (info.field_views_flag) { + msg |= info.top_field_is_left_view_flag << + offset--; + } else { + msg |= info.current_frame_is_left_view_flag << + offset--; + msg |= info.next_frame_is_second_view_flag << + offset--; + } + msg |= info.left_view_self_contained_flag << offset--; + msg |= info.right_view_self_contained_flag << offset--; + + addr[*size] = msg; + *size += 1; + + addr[*size] = 0x80; + *size += 1; + + return 0; + case SEI_BUFFERING_PERIOD: + case SEI_PICTURE_TIMING: + case SEI_FRAME_PACKING_ARRANGEMENT: + default: + dev_err(dev, "%s sei nal type not supported %d\n", + pctx->name, type); + return -EINVAL; + } +} + +static int hva_h264_prepare_task(struct hva_ctx *pctx, + struct hva_h264_task *task, + struct hva_frame *frame, + struct hva_stream *stream) +{ + struct hva_dev *hva = ctx_to_hdev(pctx); + struct device *dev = ctx_to_dev(pctx); + struct hva_h264_ctx *ctx = (struct hva_h264_ctx *)pctx->priv; + struct hva_buffer *seq_info = ctx->seq_info; + struct hva_buffer *fwd_ref_frame = ctx->ref_frame; + struct hva_buffer *loc_rec_frame = ctx->rec_frame; + struct hva_h264_td *td = &task->td; + struct hva_controls *ctrls = &pctx->ctrls; + struct v4l2_fract *time_per_frame = &pctx->ctrls.time_per_frame; + int cavlc = V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC; + u32 frame_num = pctx->stream_num; + u32 addr_esram = hva->esram_addr; + enum v4l2_mpeg_video_h264_level level; + dma_addr_t paddr = 0; + u8 *slice_header_vaddr; + u32 frame_width = frame->info.aligned_width; + u32 frame_height = frame->info.aligned_height; + u32 max_cpb_buffer_size; + unsigned int payload = stream->bytesused; + u32 max_bitrate; + + /* check width and height parameters */ + if ((frame_width > max(H264_MAX_SIZE_W, H264_MAX_SIZE_H)) || + (frame_height > max(H264_MAX_SIZE_W, H264_MAX_SIZE_H))) { + dev_err(dev, + "%s width(%d) or height(%d) exceeds limits (%dx%d)\n", + pctx->name, frame_width, frame_height, + H264_MAX_SIZE_W, H264_MAX_SIZE_H); + pctx->frame_errors++; + return -EINVAL; + } + + level = ctrls->level; + + memset(td, 0, sizeof(struct hva_h264_td)); + + td->frame_width = frame_width; + td->frame_height = frame_height; + + /* set frame alignment */ + td->window_width = frame_width; + td->window_height = frame_height; + td->window_horizontal_offset = 0; + td->window_vertical_offset = 0; + + td->first_picture_in_sequence = (!frame_num) ? 1 : 0; + + /* pic_order_cnt_type hard coded to '2' as only I & P frames */ + td->pic_order_cnt_type = 2; + + /* useConstrainedIntraFlag set to false for better coding efficiency */ + td->use_constrained_intra_flag = false; + td->brc_type = (ctrls->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) + ? BRC_TYPE_CBR : BRC_TYPE_VBR; + + td->entropy_coding_mode = (ctrls->entropy_mode == cavlc) ? CAVLC : + CABAC; + + td->bit_rate = ctrls->bitrate; + + /* set framerate, framerate = 1 n/ time per frame */ + if (time_per_frame->numerator >= 536) { + /* + * due to a hardware bug, framerate denominator can't exceed + * 536 (BRC overflow). Compute nearest framerate + */ + td->framerate_den = 1; + td->framerate_num = (time_per_frame->denominator + + (time_per_frame->numerator >> 1) - 1) / + time_per_frame->numerator; + + /* + * update bitrate to introduce a correction due to + * the new framerate + * new bitrate = (old bitrate * new framerate) / old framerate + */ + td->bit_rate /= time_per_frame->numerator; + td->bit_rate *= time_per_frame->denominator; + td->bit_rate /= td->framerate_num; + } else { + td->framerate_den = time_per_frame->numerator; + td->framerate_num = time_per_frame->denominator; + } + + /* compute maximum bitrate depending on profile */ + if (ctrls->profile >= V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) + max_bitrate = h264_infos_list[level].max_bitrate * + H264_FACTOR_HIGH; + else + max_bitrate = h264_infos_list[level].max_bitrate * + H264_FACTOR_BASELINE; + + /* check if bitrate doesn't exceed max size */ + if (td->bit_rate > max_bitrate) { + dev_dbg(dev, + "%s bitrate (%d) larger than level and profile allow, clip to %d\n", + pctx->name, td->bit_rate, max_bitrate); + td->bit_rate = max_bitrate; + } + + /* convert cpb_buffer_size in bits */ + td->cpb_buffer_size = ctrls->cpb_size * 8000; + + /* compute maximum cpb buffer size depending on profile */ + if (ctrls->profile >= V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) + max_cpb_buffer_size = + h264_infos_list[level].max_cpb_size * H264_FACTOR_HIGH; + else + max_cpb_buffer_size = + h264_infos_list[level].max_cpb_size * H264_FACTOR_BASELINE; + + /* check if cpb buffer size doesn't exceed max size */ + if (td->cpb_buffer_size > max_cpb_buffer_size) { + dev_dbg(dev, + "%s cpb size larger than level %d allows, clip to %d\n", + pctx->name, td->cpb_buffer_size, max_cpb_buffer_size); + td->cpb_buffer_size = max_cpb_buffer_size; + } + + /* enable skipping in the Bitrate Controller */ + td->brc_no_skip = 0; + + /* initial delay */ + if ((ctrls->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CBR) && + td->bit_rate) + td->delay = 1000 * (td->cpb_buffer_size / td->bit_rate); + else + td->delay = 0; + + switch (frame->info.pixelformat) { + case V4L2_PIX_FMT_NV12: + td->sampling_mode = SAMPLING_MODE_NV12; + break; + case V4L2_PIX_FMT_NV21: + td->sampling_mode = SAMPLING_MODE_NV21; + break; + default: + dev_err(dev, "%s invalid source pixel format\n", + pctx->name); + pctx->frame_errors++; + return -EINVAL; + } + + /* + * fill matrix color converter (RGB to YUV) + * Y = 0,299 R + 0,587 G + 0,114 B + * Cb = -0,1687 R -0,3313 G + 0,5 B + 128 + * Cr = 0,5 R - 0,4187 G - 0,0813 B + 128 + */ + td->rgb2_yuv_y_coeff = 0x12031008; + td->rgb2_yuv_u_coeff = 0x800EF7FB; + td->rgb2_yuv_v_coeff = 0x80FEF40E; + + /* enable/disable transform mode */ + td->transform_mode = ctrls->dct8x8; + + /* encoder complexity fix to 2, ENCODE_I_16x16_I_NxN_P_16x16_P_WxH */ + td->encoder_complexity = 2; + + /* quant fix to 28, default VBR value */ + td->quant = 28; + + if (td->framerate_den == 0) { + dev_err(dev, "%s invalid framerate\n", pctx->name); + pctx->frame_errors++; + return -EINVAL; + } + + /* if automatic framerate, deactivate bitrate controller */ + if (td->framerate_num == 0) + td->brc_type = 0; + + /* compliancy fix to true */ + td->strict_hrd_compliancy = 1; + + /* set minimum & maximum quantizers */ + td->qp_min = clamp_val(ctrls->qpmin, 0, 51); + td->qp_max = clamp_val(ctrls->qpmax, 0, 51); + + td->addr_source_buffer = frame->paddr; + td->addr_fwd_ref_buffer = fwd_ref_frame->paddr; + td->addr_rec_buffer = loc_rec_frame->paddr; + + td->addr_output_bitstream_end = (u32)stream->paddr + stream->size; + + td->addr_output_bitstream_start = (u32)stream->paddr; + td->bitstream_offset = (((u32)stream->paddr & 0xF) << 3) & + BITSTREAM_OFFSET_MASK; + + td->addr_param_out = (u32)ctx->task->paddr + + offsetof(struct hva_h264_task, po); + + /* swap spatial and temporal context */ + if (frame_num % 2) { + paddr = seq_info->paddr; + td->addr_spatial_context = ALIGN(paddr, 0x100); + paddr = seq_info->paddr + DATA_SIZE(frame_width, + frame_height); + td->addr_temporal_context = ALIGN(paddr, 0x100); + } else { + paddr = seq_info->paddr; + td->addr_temporal_context = ALIGN(paddr, 0x100); + paddr = seq_info->paddr + DATA_SIZE(frame_width, + frame_height); + td->addr_spatial_context = ALIGN(paddr, 0x100); + } + + paddr = seq_info->paddr + 2 * DATA_SIZE(frame_width, frame_height); + + td->addr_brc_in_out_parameter = ALIGN(paddr, 0x100); + + paddr = td->addr_brc_in_out_parameter + BRC_DATA_SIZE; + td->addr_slice_header = ALIGN(paddr, 0x100); + td->addr_external_sw = ALIGN(addr_esram, 0x100); + + addr_esram += SEARCH_WINDOW_BUFFER_MAX_SIZE(frame_width); + td->addr_local_rec_buffer = ALIGN(addr_esram, 0x100); + + addr_esram += LOCAL_RECONSTRUCTED_BUFFER_MAX_SIZE(frame_width); + td->addr_lctx = ALIGN(addr_esram, 0x100); + + addr_esram += CTX_MB_BUFFER_MAX_SIZE(max(frame_width, frame_height)); + td->addr_cabac_context_buffer = ALIGN(addr_esram, 0x100); + + if (!(frame_num % ctrls->gop_size)) { + td->picture_coding_type = PICTURE_CODING_TYPE_I; + stream->vbuf.flags |= V4L2_BUF_FLAG_KEYFRAME; + } else { + td->picture_coding_type = PICTURE_CODING_TYPE_P; + stream->vbuf.flags &= ~V4L2_BUF_FLAG_KEYFRAME; + } + + /* fill the slice header part */ + slice_header_vaddr = seq_info->vaddr + (td->addr_slice_header - + seq_info->paddr); + + hva_h264_fill_slice_header(pctx, slice_header_vaddr, ctrls, frame_num, + &td->slice_header_size_in_bits, + &td->slice_header_offset0, + &td->slice_header_offset1, + &td->slice_header_offset2); + + td->chroma_qp_index_offset = 2; + td->slice_synchro_enable = 0; + td->max_slice_number = 1; + + /* + * check the sps/pps header size for key frame only + * sps/pps header was previously fill by libv4l + * during qbuf of stream buffer + */ + if ((stream->vbuf.flags == V4L2_BUF_FLAG_KEYFRAME) && + (payload > MAX_SPS_PPS_SIZE)) { + dev_err(dev, "%s invalid sps/pps size %d\n", pctx->name, + payload); + pctx->frame_errors++; + return -EINVAL; + } + + if (stream->vbuf.flags != V4L2_BUF_FLAG_KEYFRAME) + payload = 0; + + /* add SEI nal (video stereo info) */ + if (ctrls->sei_fp && hva_h264_fill_sei_nal(pctx, SEI_STEREO_VIDEO_INFO, + (u8 *)stream->vaddr, + &payload)) { + dev_err(dev, "%s fail to get SEI nal\n", pctx->name); + pctx->frame_errors++; + return -EINVAL; + } + + /* fill size of non-VCL NAL units (SPS, PPS, filler and SEI) */ + td->non_vcl_nalu_size = payload * 8; + + /* compute bitstream offset & new start address of bitstream */ + td->addr_output_bitstream_start += ((payload >> 4) << 4); + td->bitstream_offset += (payload - ((payload >> 4) << 4)) * 8; + + stream->bytesused = payload; + + return 0; +} + +static unsigned int hva_h264_get_stream_size(struct hva_h264_task *task) +{ + struct hva_h264_po *po = &task->po; + + return po->bitstream_size; +} + +static u32 hva_h264_get_stuffing_bytes(struct hva_h264_task *task) +{ + struct hva_h264_po *po = &task->po; + + return po->stuffing_bits >> 3; +} + +static int hva_h264_open(struct hva_ctx *pctx) +{ + struct device *dev = ctx_to_dev(pctx); + struct hva_h264_ctx *ctx; + struct hva_dev *hva = ctx_to_hdev(pctx); + u32 frame_width = pctx->frameinfo.aligned_width; + u32 frame_height = pctx->frameinfo.aligned_height; + u32 size; + int ret; + + /* check esram size necessary to encode a frame */ + size = SEARCH_WINDOW_BUFFER_MAX_SIZE(frame_width) + + LOCAL_RECONSTRUCTED_BUFFER_MAX_SIZE(frame_width) + + CTX_MB_BUFFER_MAX_SIZE(max(frame_width, frame_height)) + + CABAC_CONTEXT_BUFFER_MAX_SIZE(frame_width); + + if (hva->esram_size < size) { + dev_err(dev, "%s not enough esram (max:%d request:%d)\n", + pctx->name, hva->esram_size, size); + ret = -EINVAL; + goto err; + } + + /* allocate context for codec */ + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto err; + } + + /* allocate sequence info buffer */ + ret = hva_mem_alloc(pctx, + 2 * DATA_SIZE(frame_width, frame_height) + + SLICE_HEADER_SIZE + + BRC_DATA_SIZE, + "hva sequence info", + &ctx->seq_info); + if (ret) { + dev_err(dev, + "%s failed to allocate sequence info buffer\n", + pctx->name); + goto err_ctx; + } + + /* allocate reference frame buffer */ + ret = hva_mem_alloc(pctx, + frame_width * frame_height * 3 / 2, + "hva reference frame", + &ctx->ref_frame); + if (ret) { + dev_err(dev, "%s failed to allocate reference frame buffer\n", + pctx->name); + goto err_seq_info; + } + + /* allocate reconstructed frame buffer */ + ret = hva_mem_alloc(pctx, + frame_width * frame_height * 3 / 2, + "hva reconstructed frame", + &ctx->rec_frame); + if (ret) { + dev_err(dev, + "%s failed to allocate reconstructed frame buffer\n", + pctx->name); + goto err_ref_frame; + } + + /* allocate task descriptor */ + ret = hva_mem_alloc(pctx, + sizeof(struct hva_h264_task), + "hva task descriptor", + &ctx->task); + if (ret) { + dev_err(dev, + "%s failed to allocate task descriptor\n", + pctx->name); + goto err_rec_frame; + } + + pctx->priv = (void *)ctx; + + return 0; + +err_rec_frame: + hva_mem_free(pctx, ctx->rec_frame); +err_ref_frame: + hva_mem_free(pctx, ctx->ref_frame); +err_seq_info: + hva_mem_free(pctx, ctx->seq_info); +err_ctx: + devm_kfree(dev, ctx); +err: + pctx->sys_errors++; + return ret; +} + +static int hva_h264_close(struct hva_ctx *pctx) +{ + struct hva_h264_ctx *ctx = (struct hva_h264_ctx *)pctx->priv; + struct device *dev = ctx_to_dev(pctx); + + if (ctx->seq_info) + hva_mem_free(pctx, ctx->seq_info); + + if (ctx->ref_frame) + hva_mem_free(pctx, ctx->ref_frame); + + if (ctx->rec_frame) + hva_mem_free(pctx, ctx->rec_frame); + + if (ctx->task) + hva_mem_free(pctx, ctx->task); + + devm_kfree(dev, ctx); + + return 0; +} + +static int hva_h264_encode(struct hva_ctx *pctx, struct hva_frame *frame, + struct hva_stream *stream) +{ + struct hva_h264_ctx *ctx = (struct hva_h264_ctx *)pctx->priv; + struct hva_h264_task *task = (struct hva_h264_task *)ctx->task->vaddr; + u32 stuffing_bytes = 0; + int ret = 0; + + ret = hva_h264_prepare_task(pctx, task, frame, stream); + if (ret) + goto err; + + ret = hva_hw_execute_task(pctx, H264_ENC, ctx->task); + if (ret) + goto err; + + pctx->stream_num++; + stream->bytesused += hva_h264_get_stream_size(task); + + stuffing_bytes = hva_h264_get_stuffing_bytes(task); + + if (stuffing_bytes) + hva_h264_fill_data_nal(pctx, stuffing_bytes, + (u8 *)stream->vaddr, + stream->size, + &stream->bytesused); + + /* switch reference & reconstructed frame */ + swap(ctx->ref_frame, ctx->rec_frame); + + return 0; +err: + stream->bytesused = 0; + return ret; +} + +const struct hva_enc nv12h264enc = { + .name = "H264(NV12)", + .pixelformat = V4L2_PIX_FMT_NV12, + .streamformat = V4L2_PIX_FMT_H264, + .max_width = H264_MAX_SIZE_W, + .max_height = H264_MAX_SIZE_H, + .open = hva_h264_open, + .close = hva_h264_close, + .encode = hva_h264_encode, +}; + +const struct hva_enc nv21h264enc = { + .name = "H264(NV21)", + .pixelformat = V4L2_PIX_FMT_NV21, + .streamformat = V4L2_PIX_FMT_H264, + .max_width = H264_MAX_SIZE_W, + .max_height = H264_MAX_SIZE_H, + .open = hva_h264_open, + .close = hva_h264_close, + .encode = hva_h264_encode, +}; diff --git a/drivers/media/platform/st/sti/hva/hva-hw.c b/drivers/media/platform/st/sti/hva/hva-hw.c new file mode 100644 index 000000000..fe4ea2e7f --- /dev/null +++ b/drivers/media/platform/st/sti/hva/hva-hw.c @@ -0,0 +1,585 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Yannick Fertre + * Hugues Fruchet + */ + +#include +#include +#include +#include +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS +#include +#endif + +#include "hva.h" +#include "hva-hw.h" + +/* HVA register offsets */ +#define HVA_HIF_REG_RST 0x0100U +#define HVA_HIF_REG_RST_ACK 0x0104U +#define HVA_HIF_REG_MIF_CFG 0x0108U +#define HVA_HIF_REG_HEC_MIF_CFG 0x010CU +#define HVA_HIF_REG_CFL 0x0110U +#define HVA_HIF_FIFO_CMD 0x0114U +#define HVA_HIF_FIFO_STS 0x0118U +#define HVA_HIF_REG_SFL 0x011CU +#define HVA_HIF_REG_IT_ACK 0x0120U +#define HVA_HIF_REG_ERR_IT_ACK 0x0124U +#define HVA_HIF_REG_LMI_ERR 0x0128U +#define HVA_HIF_REG_EMI_ERR 0x012CU +#define HVA_HIF_REG_HEC_MIF_ERR 0x0130U +#define HVA_HIF_REG_HEC_STS 0x0134U +#define HVA_HIF_REG_HVC_STS 0x0138U +#define HVA_HIF_REG_HJE_STS 0x013CU +#define HVA_HIF_REG_CNT 0x0140U +#define HVA_HIF_REG_HEC_CHKSYN_DIS 0x0144U +#define HVA_HIF_REG_CLK_GATING 0x0148U +#define HVA_HIF_REG_VERSION 0x014CU +#define HVA_HIF_REG_BSM 0x0150U + +/* define value for version id register (HVA_HIF_REG_VERSION) */ +#define VERSION_ID_MASK 0x0000FFFF + +/* define values for BSM register (HVA_HIF_REG_BSM) */ +#define BSM_CFG_VAL1 0x0003F000 +#define BSM_CFG_VAL2 0x003F0000 + +/* define values for memory interface register (HVA_HIF_REG_MIF_CFG) */ +#define MIF_CFG_VAL1 0x04460446 +#define MIF_CFG_VAL2 0x04460806 +#define MIF_CFG_VAL3 0x00000000 + +/* define value for HEC memory interface register (HVA_HIF_REG_MIF_CFG) */ +#define HEC_MIF_CFG_VAL 0x000000C4 + +/* Bits definition for clock gating register (HVA_HIF_REG_CLK_GATING) */ +#define CLK_GATING_HVC BIT(0) +#define CLK_GATING_HEC BIT(1) +#define CLK_GATING_HJE BIT(2) + +/* fix hva clock rate */ +#define CLK_RATE 300000000 + +/* fix delay for pmruntime */ +#define AUTOSUSPEND_DELAY_MS 3 + +/* + * hw encode error values + * NO_ERROR: Success, Task OK + * H264_BITSTREAM_OVERSIZE: VECH264 Bitstream size > bitstream buffer + * H264_FRAME_SKIPPED: VECH264 Frame skipped (refers to CPB Buffer Size) + * H264_SLICE_LIMIT_SIZE: VECH264 MB > slice limit size + * H264_MAX_SLICE_NUMBER: VECH264 max slice number reached + * H264_SLICE_READY: VECH264 Slice ready + * TASK_LIST_FULL: HVA/FPC task list full + (discard latest transform command) + * UNKNOWN_COMMAND: Transform command not known by HVA/FPC + * WRONG_CODEC_OR_RESOLUTION: Wrong Codec or Resolution Selection + * NO_INT_COMPLETION: Time-out on interrupt completion + * LMI_ERR: Local Memory Interface Error + * EMI_ERR: External Memory Interface Error + * HECMI_ERR: HEC Memory Interface Error + */ +enum hva_hw_error { + NO_ERROR = 0x0, + H264_BITSTREAM_OVERSIZE = 0x2, + H264_FRAME_SKIPPED = 0x4, + H264_SLICE_LIMIT_SIZE = 0x5, + H264_MAX_SLICE_NUMBER = 0x7, + H264_SLICE_READY = 0x8, + TASK_LIST_FULL = 0xF0, + UNKNOWN_COMMAND = 0xF1, + WRONG_CODEC_OR_RESOLUTION = 0xF4, + NO_INT_COMPLETION = 0x100, + LMI_ERR = 0x101, + EMI_ERR = 0x102, + HECMI_ERR = 0x103, +}; + +static irqreturn_t hva_hw_its_interrupt(int irq, void *data) +{ + struct hva_dev *hva = data; + + /* read status registers */ + hva->sts_reg = readl_relaxed(hva->regs + HVA_HIF_FIFO_STS); + hva->sfl_reg = readl_relaxed(hva->regs + HVA_HIF_REG_SFL); + + /* acknowledge interruption */ + writel_relaxed(0x1, hva->regs + HVA_HIF_REG_IT_ACK); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t hva_hw_its_irq_thread(int irq, void *arg) +{ + struct hva_dev *hva = arg; + struct device *dev = hva_to_dev(hva); + u32 status = hva->sts_reg & 0xFF; + u8 ctx_id = 0; + struct hva_ctx *ctx = NULL; + + dev_dbg(dev, "%s %s: status: 0x%02x fifo level: 0x%02x\n", + HVA_PREFIX, __func__, hva->sts_reg & 0xFF, hva->sfl_reg & 0xF); + + /* + * status: task_id[31:16] client_id[15:8] status[7:0] + * the context identifier is retrieved from the client identifier + */ + ctx_id = (hva->sts_reg & 0xFF00) >> 8; + if (ctx_id >= HVA_MAX_INSTANCES) { + dev_err(dev, "%s %s: bad context identifier: %d\n", + HVA_PREFIX, __func__, ctx_id); + goto out; + } + + ctx = hva->instances[ctx_id]; + if (!ctx) + goto out; + + switch (status) { + case NO_ERROR: + dev_dbg(dev, "%s %s: no error\n", + ctx->name, __func__); + ctx->hw_err = false; + break; + case H264_SLICE_READY: + dev_dbg(dev, "%s %s: h264 slice ready\n", + ctx->name, __func__); + ctx->hw_err = false; + break; + case H264_FRAME_SKIPPED: + dev_dbg(dev, "%s %s: h264 frame skipped\n", + ctx->name, __func__); + ctx->hw_err = false; + break; + case H264_BITSTREAM_OVERSIZE: + dev_err(dev, "%s %s:h264 bitstream oversize\n", + ctx->name, __func__); + ctx->hw_err = true; + break; + case H264_SLICE_LIMIT_SIZE: + dev_err(dev, "%s %s: h264 slice limit size is reached\n", + ctx->name, __func__); + ctx->hw_err = true; + break; + case H264_MAX_SLICE_NUMBER: + dev_err(dev, "%s %s: h264 max slice number is reached\n", + ctx->name, __func__); + ctx->hw_err = true; + break; + case TASK_LIST_FULL: + dev_err(dev, "%s %s:task list full\n", + ctx->name, __func__); + ctx->hw_err = true; + break; + case UNKNOWN_COMMAND: + dev_err(dev, "%s %s: command not known\n", + ctx->name, __func__); + ctx->hw_err = true; + break; + case WRONG_CODEC_OR_RESOLUTION: + dev_err(dev, "%s %s: wrong codec or resolution\n", + ctx->name, __func__); + ctx->hw_err = true; + break; + default: + dev_err(dev, "%s %s: status not recognized\n", + ctx->name, __func__); + ctx->hw_err = true; + break; + } +out: + complete(&hva->interrupt); + + return IRQ_HANDLED; +} + +static irqreturn_t hva_hw_err_interrupt(int irq, void *data) +{ + struct hva_dev *hva = data; + + /* read status registers */ + hva->sts_reg = readl_relaxed(hva->regs + HVA_HIF_FIFO_STS); + hva->sfl_reg = readl_relaxed(hva->regs + HVA_HIF_REG_SFL); + + /* read error registers */ + hva->lmi_err_reg = readl_relaxed(hva->regs + HVA_HIF_REG_LMI_ERR); + hva->emi_err_reg = readl_relaxed(hva->regs + HVA_HIF_REG_EMI_ERR); + hva->hec_mif_err_reg = readl_relaxed(hva->regs + + HVA_HIF_REG_HEC_MIF_ERR); + + /* acknowledge interruption */ + writel_relaxed(0x1, hva->regs + HVA_HIF_REG_IT_ACK); + + return IRQ_WAKE_THREAD; +} + +static irqreturn_t hva_hw_err_irq_thread(int irq, void *arg) +{ + struct hva_dev *hva = arg; + struct device *dev = hva_to_dev(hva); + u8 ctx_id = 0; + struct hva_ctx *ctx; + + dev_dbg(dev, "%s status: 0x%02x fifo level: 0x%02x\n", + HVA_PREFIX, hva->sts_reg & 0xFF, hva->sfl_reg & 0xF); + + /* + * status: task_id[31:16] client_id[15:8] status[7:0] + * the context identifier is retrieved from the client identifier + */ + ctx_id = (hva->sts_reg & 0xFF00) >> 8; + if (ctx_id >= HVA_MAX_INSTANCES) { + dev_err(dev, "%s bad context identifier: %d\n", HVA_PREFIX, + ctx_id); + goto out; + } + + ctx = hva->instances[ctx_id]; + if (!ctx) + goto out; + + if (hva->lmi_err_reg) { + dev_err(dev, "%s local memory interface error: 0x%08x\n", + ctx->name, hva->lmi_err_reg); + ctx->hw_err = true; + } + + if (hva->emi_err_reg) { + dev_err(dev, "%s external memory interface error: 0x%08x\n", + ctx->name, hva->emi_err_reg); + ctx->hw_err = true; + } + + if (hva->hec_mif_err_reg) { + dev_err(dev, "%s hec memory interface error: 0x%08x\n", + ctx->name, hva->hec_mif_err_reg); + ctx->hw_err = true; + } +out: + complete(&hva->interrupt); + + return IRQ_HANDLED; +} + +static unsigned long int hva_hw_get_ip_version(struct hva_dev *hva) +{ + struct device *dev = hva_to_dev(hva); + unsigned long int version; + + if (pm_runtime_resume_and_get(dev) < 0) { + dev_err(dev, "%s failed to get pm_runtime\n", HVA_PREFIX); + mutex_unlock(&hva->protect_mutex); + return -EFAULT; + } + + version = readl_relaxed(hva->regs + HVA_HIF_REG_VERSION) & + VERSION_ID_MASK; + + pm_runtime_put_autosuspend(dev); + + switch (version) { + case HVA_VERSION_V400: + dev_dbg(dev, "%s IP hardware version 0x%lx\n", + HVA_PREFIX, version); + break; + default: + dev_err(dev, "%s unknown IP hardware version 0x%lx\n", + HVA_PREFIX, version); + version = HVA_VERSION_UNKNOWN; + break; + } + + return version; +} + +int hva_hw_probe(struct platform_device *pdev, struct hva_dev *hva) +{ + struct device *dev = &pdev->dev; + struct resource *esram; + int ret; + + WARN_ON(!hva); + + /* get memory for registers */ + hva->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(hva->regs)) { + dev_err(dev, "%s failed to get regs\n", HVA_PREFIX); + return PTR_ERR(hva->regs); + } + + /* get memory for esram */ + esram = platform_get_resource(pdev, IORESOURCE_MEM, 1); + if (!esram) { + dev_err(dev, "%s failed to get esram\n", HVA_PREFIX); + return -ENODEV; + } + hva->esram_addr = esram->start; + hva->esram_size = resource_size(esram); + + dev_info(dev, "%s esram reserved for address: 0x%x size:%d\n", + HVA_PREFIX, hva->esram_addr, hva->esram_size); + + /* get clock resource */ + hva->clk = devm_clk_get(dev, "clk_hva"); + if (IS_ERR(hva->clk)) { + dev_err(dev, "%s failed to get clock\n", HVA_PREFIX); + return PTR_ERR(hva->clk); + } + + ret = clk_prepare(hva->clk); + if (ret < 0) { + dev_err(dev, "%s failed to prepare clock\n", HVA_PREFIX); + hva->clk = ERR_PTR(-EINVAL); + return ret; + } + + /* get status interruption resource */ + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto err_clk; + hva->irq_its = ret; + + ret = devm_request_threaded_irq(dev, hva->irq_its, hva_hw_its_interrupt, + hva_hw_its_irq_thread, + IRQF_ONESHOT, + "hva_its_irq", hva); + if (ret) { + dev_err(dev, "%s failed to install status IRQ 0x%x\n", + HVA_PREFIX, hva->irq_its); + goto err_clk; + } + disable_irq(hva->irq_its); + + /* get error interruption resource */ + ret = platform_get_irq(pdev, 1); + if (ret < 0) + goto err_clk; + hva->irq_err = ret; + + ret = devm_request_threaded_irq(dev, hva->irq_err, hva_hw_err_interrupt, + hva_hw_err_irq_thread, + IRQF_ONESHOT, + "hva_err_irq", hva); + if (ret) { + dev_err(dev, "%s failed to install error IRQ 0x%x\n", + HVA_PREFIX, hva->irq_err); + goto err_clk; + } + disable_irq(hva->irq_err); + + /* initialise protection mutex */ + mutex_init(&hva->protect_mutex); + + /* initialise completion signal */ + init_completion(&hva->interrupt); + + /* initialise runtime power management */ + pm_runtime_set_autosuspend_delay(dev, AUTOSUSPEND_DELAY_MS); + pm_runtime_use_autosuspend(dev); + pm_runtime_set_suspended(dev); + pm_runtime_enable(dev); + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "%s failed to set PM\n", HVA_PREFIX); + goto err_disable; + } + + /* check IP hardware version */ + hva->ip_version = hva_hw_get_ip_version(hva); + + if (hva->ip_version == HVA_VERSION_UNKNOWN) { + ret = -EINVAL; + goto err_pm; + } + + dev_info(dev, "%s found hva device (version 0x%lx)\n", HVA_PREFIX, + hva->ip_version); + + return 0; + +err_pm: + pm_runtime_put(dev); +err_disable: + pm_runtime_disable(dev); +err_clk: + if (hva->clk) + clk_unprepare(hva->clk); + + return ret; +} + +void hva_hw_remove(struct hva_dev *hva) +{ + struct device *dev = hva_to_dev(hva); + + disable_irq(hva->irq_its); + disable_irq(hva->irq_err); + + pm_runtime_put_autosuspend(dev); + pm_runtime_disable(dev); +} + +int hva_hw_runtime_suspend(struct device *dev) +{ + struct hva_dev *hva = dev_get_drvdata(dev); + + clk_disable_unprepare(hva->clk); + + return 0; +} + +int hva_hw_runtime_resume(struct device *dev) +{ + struct hva_dev *hva = dev_get_drvdata(dev); + + if (clk_prepare_enable(hva->clk)) { + dev_err(hva->dev, "%s failed to prepare hva clk\n", + HVA_PREFIX); + return -EINVAL; + } + + if (clk_set_rate(hva->clk, CLK_RATE)) { + dev_err(dev, "%s failed to set clock frequency\n", + HVA_PREFIX); + clk_disable_unprepare(hva->clk); + return -EINVAL; + } + + return 0; +} + +int hva_hw_execute_task(struct hva_ctx *ctx, enum hva_hw_cmd_type cmd, + struct hva_buffer *task) +{ + struct hva_dev *hva = ctx_to_hdev(ctx); + struct device *dev = hva_to_dev(hva); + u8 client_id = ctx->id; + int ret; + u32 reg = 0; + bool got_pm = false; + + mutex_lock(&hva->protect_mutex); + + /* enable irqs */ + enable_irq(hva->irq_its); + enable_irq(hva->irq_err); + + if (pm_runtime_resume_and_get(dev) < 0) { + dev_err(dev, "%s failed to get pm_runtime\n", ctx->name); + ctx->sys_errors++; + ret = -EFAULT; + goto out; + } + got_pm = true; + + reg = readl_relaxed(hva->regs + HVA_HIF_REG_CLK_GATING); + switch (cmd) { + case H264_ENC: + reg |= CLK_GATING_HVC; + break; + default: + dev_dbg(dev, "%s unknown command 0x%x\n", ctx->name, cmd); + ctx->encode_errors++; + ret = -EFAULT; + goto out; + } + writel_relaxed(reg, hva->regs + HVA_HIF_REG_CLK_GATING); + + dev_dbg(dev, "%s %s: write configuration registers\n", ctx->name, + __func__); + + /* byte swap config */ + writel_relaxed(BSM_CFG_VAL1, hva->regs + HVA_HIF_REG_BSM); + + /* define Max Opcode Size and Max Message Size for LMI and EMI */ + writel_relaxed(MIF_CFG_VAL3, hva->regs + HVA_HIF_REG_MIF_CFG); + writel_relaxed(HEC_MIF_CFG_VAL, hva->regs + HVA_HIF_REG_HEC_MIF_CFG); + + /* + * command FIFO: task_id[31:16] client_id[15:8] command_type[7:0] + * the context identifier is provided as client identifier to the + * hardware, and is retrieved in the interrupt functions from the + * status register + */ + dev_dbg(dev, "%s %s: send task (cmd: %d, task_desc: %pad)\n", + ctx->name, __func__, cmd + (client_id << 8), &task->paddr); + writel_relaxed(cmd + (client_id << 8), hva->regs + HVA_HIF_FIFO_CMD); + writel_relaxed(task->paddr, hva->regs + HVA_HIF_FIFO_CMD); + + if (!wait_for_completion_timeout(&hva->interrupt, + msecs_to_jiffies(2000))) { + dev_err(dev, "%s %s: time out on completion\n", ctx->name, + __func__); + ctx->encode_errors++; + ret = -EFAULT; + goto out; + } + + /* get encoding status */ + ret = ctx->hw_err ? -EFAULT : 0; + + ctx->encode_errors += ctx->hw_err ? 1 : 0; + +out: + disable_irq(hva->irq_its); + disable_irq(hva->irq_err); + + switch (cmd) { + case H264_ENC: + reg &= ~CLK_GATING_HVC; + writel_relaxed(reg, hva->regs + HVA_HIF_REG_CLK_GATING); + break; + default: + dev_dbg(dev, "%s unknown command 0x%x\n", ctx->name, cmd); + } + + if (got_pm) + pm_runtime_put_autosuspend(dev); + mutex_unlock(&hva->protect_mutex); + + return ret; +} + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS +#define DUMP(reg) seq_printf(s, "%-30s: 0x%08X\n",\ + #reg, readl_relaxed(hva->regs + reg)) + +void hva_hw_dump_regs(struct hva_dev *hva, struct seq_file *s) +{ + struct device *dev = hva_to_dev(hva); + + mutex_lock(&hva->protect_mutex); + + if (pm_runtime_resume_and_get(dev) < 0) { + seq_puts(s, "Cannot wake up IP\n"); + mutex_unlock(&hva->protect_mutex); + return; + } + + seq_printf(s, "Registers:\nReg @ = 0x%p\n", hva->regs); + + DUMP(HVA_HIF_REG_RST); + DUMP(HVA_HIF_REG_RST_ACK); + DUMP(HVA_HIF_REG_MIF_CFG); + DUMP(HVA_HIF_REG_HEC_MIF_CFG); + DUMP(HVA_HIF_REG_CFL); + DUMP(HVA_HIF_REG_SFL); + DUMP(HVA_HIF_REG_LMI_ERR); + DUMP(HVA_HIF_REG_EMI_ERR); + DUMP(HVA_HIF_REG_HEC_MIF_ERR); + DUMP(HVA_HIF_REG_HEC_STS); + DUMP(HVA_HIF_REG_HVC_STS); + DUMP(HVA_HIF_REG_HJE_STS); + DUMP(HVA_HIF_REG_CNT); + DUMP(HVA_HIF_REG_HEC_CHKSYN_DIS); + DUMP(HVA_HIF_REG_CLK_GATING); + DUMP(HVA_HIF_REG_VERSION); + + pm_runtime_put_autosuspend(dev); + mutex_unlock(&hva->protect_mutex); +} +#endif diff --git a/drivers/media/platform/st/sti/hva/hva-hw.h b/drivers/media/platform/st/sti/hva/hva-hw.h new file mode 100644 index 000000000..b29899026 --- /dev/null +++ b/drivers/media/platform/st/sti/hva/hva-hw.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Yannick Fertre + * Hugues Fruchet + */ + +#ifndef HVA_HW_H +#define HVA_HW_H + +#include "hva-mem.h" + +/* HVA Versions */ +#define HVA_VERSION_UNKNOWN 0x000 +#define HVA_VERSION_V400 0x400 + +/* HVA command types */ +enum hva_hw_cmd_type { + /* RESERVED = 0x00 */ + /* RESERVED = 0x01 */ + H264_ENC = 0x02, + /* RESERVED = 0x03 */ + /* RESERVED = 0x04 */ + /* RESERVED = 0x05 */ + /* RESERVED = 0x06 */ + /* RESERVED = 0x07 */ + REMOVE_CLIENT = 0x08, + FREEZE_CLIENT = 0x09, + START_CLIENT = 0x0A, + FREEZE_ALL = 0x0B, + START_ALL = 0x0C, + REMOVE_ALL = 0x0D +}; + +int hva_hw_probe(struct platform_device *pdev, struct hva_dev *hva); +void hva_hw_remove(struct hva_dev *hva); +int hva_hw_runtime_suspend(struct device *dev); +int hva_hw_runtime_resume(struct device *dev); +int hva_hw_execute_task(struct hva_ctx *ctx, enum hva_hw_cmd_type cmd, + struct hva_buffer *task); +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS +void hva_hw_dump_regs(struct hva_dev *hva, struct seq_file *s); +#endif + +#endif /* HVA_HW_H */ diff --git a/drivers/media/platform/st/sti/hva/hva-mem.c b/drivers/media/platform/st/sti/hva/hva-mem.c new file mode 100644 index 000000000..68047b60b --- /dev/null +++ b/drivers/media/platform/st/sti/hva/hva-mem.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Yannick Fertre + * Hugues Fruchet + */ + +#include "hva.h" +#include "hva-mem.h" + +int hva_mem_alloc(struct hva_ctx *ctx, u32 size, const char *name, + struct hva_buffer **buf) +{ + struct device *dev = ctx_to_dev(ctx); + struct hva_buffer *b; + dma_addr_t paddr; + void *base; + + b = devm_kzalloc(dev, sizeof(*b), GFP_KERNEL); + if (!b) { + ctx->sys_errors++; + return -ENOMEM; + } + + base = dma_alloc_attrs(dev, size, &paddr, GFP_KERNEL, + DMA_ATTR_WRITE_COMBINE); + if (!base) { + dev_err(dev, "%s %s : dma_alloc_attrs failed for %s (size=%d)\n", + ctx->name, __func__, name, size); + ctx->sys_errors++; + devm_kfree(dev, b); + return -ENOMEM; + } + + b->size = size; + b->paddr = paddr; + b->vaddr = base; + b->name = name; + + dev_dbg(dev, + "%s allocate %d bytes of HW memory @(virt=%p, phy=%pad): %s\n", + ctx->name, size, b->vaddr, &b->paddr, b->name); + + /* return hva buffer to user */ + *buf = b; + + return 0; +} + +void hva_mem_free(struct hva_ctx *ctx, struct hva_buffer *buf) +{ + struct device *dev = ctx_to_dev(ctx); + + dev_dbg(dev, + "%s free %d bytes of HW memory @(virt=%p, phy=%pad): %s\n", + ctx->name, buf->size, buf->vaddr, &buf->paddr, buf->name); + + dma_free_attrs(dev, buf->size, buf->vaddr, buf->paddr, + DMA_ATTR_WRITE_COMBINE); + + devm_kfree(dev, buf); +} diff --git a/drivers/media/platform/st/sti/hva/hva-mem.h b/drivers/media/platform/st/sti/hva/hva-mem.h new file mode 100644 index 000000000..fec549dff --- /dev/null +++ b/drivers/media/platform/st/sti/hva/hva-mem.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Yannick Fertre + * Hugues Fruchet + */ + +#ifndef HVA_MEM_H +#define HVA_MEM_H + +/** + * struct hva_buffer - hva buffer + * + * @name: name of requester + * @paddr: physical address (for hardware) + * @vaddr: virtual address (kernel can read/write) + * @size: size of buffer + */ +struct hva_buffer { + const char *name; + dma_addr_t paddr; + void *vaddr; + u32 size; +}; + +int hva_mem_alloc(struct hva_ctx *ctx, + __u32 size, + const char *name, + struct hva_buffer **buf); + +void hva_mem_free(struct hva_ctx *ctx, + struct hva_buffer *buf); + +#endif /* HVA_MEM_H */ diff --git a/drivers/media/platform/st/sti/hva/hva-v4l2.c b/drivers/media/platform/st/sti/hva/hva-v4l2.c new file mode 100644 index 000000000..bb34d6997 --- /dev/null +++ b/drivers/media/platform/st/sti/hva/hva-v4l2.c @@ -0,0 +1,1476 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Yannick Fertre + * Hugues Fruchet + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "hva.h" +#include "hva-hw.h" + +#define MIN_FRAMES 1 +#define MIN_STREAMS 1 + +#define HVA_MIN_WIDTH 32 +#define HVA_MAX_WIDTH 1920 +#define HVA_MIN_HEIGHT 32 +#define HVA_MAX_HEIGHT 1920 + +/* HVA requires a 16x16 pixels alignment for frames */ +#define HVA_WIDTH_ALIGNMENT 16 +#define HVA_HEIGHT_ALIGNMENT 16 + +#define HVA_DEFAULT_WIDTH HVA_MIN_WIDTH +#define HVA_DEFAULT_HEIGHT HVA_MIN_HEIGHT +#define HVA_DEFAULT_FRAME_NUM 1 +#define HVA_DEFAULT_FRAME_DEN 30 + +#define to_type_str(type) (type == V4L2_BUF_TYPE_VIDEO_OUTPUT ? \ + "frame" : "stream") + +#define fh_to_ctx(f) (container_of(f, struct hva_ctx, fh)) + +/* registry of available encoders */ +static const struct hva_enc *hva_encoders[] = { + &nv12h264enc, + &nv21h264enc, +}; + +static inline int frame_size(u32 w, u32 h, u32 fmt) +{ + switch (fmt) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + return (w * h * 3) / 2; + default: + return 0; + } +} + +static inline int frame_stride(u32 w, u32 fmt) +{ + switch (fmt) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + return w; + default: + return 0; + } +} + +static inline int frame_alignment(u32 fmt) +{ + switch (fmt) { + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + /* multiple of 2 */ + return 2; + default: + return 1; + } +} + +static inline int estimated_stream_size(u32 w, u32 h) +{ + /* + * HVA only encodes in YUV420 format, whatever the frame format. + * A compression ratio of 2 is assumed: thus, the maximum size + * of a stream is estimated to ((width x height x 3 / 2) / 2) + */ + return (w * h * 3) / 4; +} + +static void set_default_params(struct hva_ctx *ctx) +{ + struct hva_frameinfo *frameinfo = &ctx->frameinfo; + struct hva_streaminfo *streaminfo = &ctx->streaminfo; + + frameinfo->pixelformat = V4L2_PIX_FMT_NV12; + frameinfo->width = HVA_DEFAULT_WIDTH; + frameinfo->height = HVA_DEFAULT_HEIGHT; + frameinfo->aligned_width = ALIGN(frameinfo->width, + HVA_WIDTH_ALIGNMENT); + frameinfo->aligned_height = ALIGN(frameinfo->height, + HVA_HEIGHT_ALIGNMENT); + frameinfo->size = frame_size(frameinfo->aligned_width, + frameinfo->aligned_height, + frameinfo->pixelformat); + + streaminfo->streamformat = V4L2_PIX_FMT_H264; + streaminfo->width = HVA_DEFAULT_WIDTH; + streaminfo->height = HVA_DEFAULT_HEIGHT; + + ctx->colorspace = V4L2_COLORSPACE_REC709; + ctx->xfer_func = V4L2_XFER_FUNC_DEFAULT; + ctx->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + ctx->quantization = V4L2_QUANTIZATION_DEFAULT; + + ctx->max_stream_size = estimated_stream_size(streaminfo->width, + streaminfo->height); +} + +static const struct hva_enc *hva_find_encoder(struct hva_ctx *ctx, + u32 pixelformat, + u32 streamformat) +{ + struct hva_dev *hva = ctx_to_hdev(ctx); + const struct hva_enc *enc; + unsigned int i; + + for (i = 0; i < hva->nb_of_encoders; i++) { + enc = hva->encoders[i]; + if ((enc->pixelformat == pixelformat) && + (enc->streamformat == streamformat)) + return enc; + } + + return NULL; +} + +static void register_format(u32 format, u32 formats[], u32 *nb_of_formats) +{ + u32 i; + bool found = false; + + for (i = 0; i < *nb_of_formats; i++) { + if (format == formats[i]) { + found = true; + break; + } + } + + if (!found) + formats[(*nb_of_formats)++] = format; +} + +static void register_formats(struct hva_dev *hva) +{ + unsigned int i; + + for (i = 0; i < hva->nb_of_encoders; i++) { + register_format(hva->encoders[i]->pixelformat, + hva->pixelformats, + &hva->nb_of_pixelformats); + + register_format(hva->encoders[i]->streamformat, + hva->streamformats, + &hva->nb_of_streamformats); + } +} + +static void register_encoders(struct hva_dev *hva) +{ + struct device *dev = hva_to_dev(hva); + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(hva_encoders); i++) { + if (hva->nb_of_encoders >= HVA_MAX_ENCODERS) { + dev_dbg(dev, + "%s failed to register %s encoder (%d maximum reached)\n", + HVA_PREFIX, hva_encoders[i]->name, + HVA_MAX_ENCODERS); + return; + } + + hva->encoders[hva->nb_of_encoders++] = hva_encoders[i]; + dev_info(dev, "%s %s encoder registered\n", HVA_PREFIX, + hva_encoders[i]->name); + } +} + +static int hva_open_encoder(struct hva_ctx *ctx, u32 streamformat, + u32 pixelformat, struct hva_enc **penc) +{ + struct hva_dev *hva = ctx_to_hdev(ctx); + struct device *dev = ctx_to_dev(ctx); + struct hva_enc *enc; + int ret; + + /* find an encoder which can deal with these formats */ + enc = (struct hva_enc *)hva_find_encoder(ctx, pixelformat, + streamformat); + if (!enc) { + dev_err(dev, "%s no encoder found matching %4.4s => %4.4s\n", + ctx->name, (char *)&pixelformat, (char *)&streamformat); + return -EINVAL; + } + + dev_dbg(dev, "%s one encoder matching %4.4s => %4.4s\n", + ctx->name, (char *)&pixelformat, (char *)&streamformat); + + /* update instance name */ + snprintf(ctx->name, sizeof(ctx->name), "[%3d:%4.4s]", + hva->instance_id, (char *)&streamformat); + + /* open encoder instance */ + ret = enc->open(ctx); + if (ret) { + dev_err(dev, "%s failed to open encoder instance (%d)\n", + ctx->name, ret); + return ret; + } + + dev_dbg(dev, "%s %s encoder opened\n", ctx->name, enc->name); + + *penc = enc; + + return ret; +} + +static void hva_dbg_summary(struct hva_ctx *ctx) +{ + struct device *dev = ctx_to_dev(ctx); + struct hva_streaminfo *stream = &ctx->streaminfo; + struct hva_frameinfo *frame = &ctx->frameinfo; + + if (!(ctx->flags & HVA_FLAG_STREAMINFO)) + return; + + dev_dbg(dev, "%s %4.4s %dx%d > %4.4s %dx%d %s %s: %d frames encoded, %d system errors, %d encoding errors, %d frame errors\n", + ctx->name, + (char *)&frame->pixelformat, + frame->aligned_width, frame->aligned_height, + (char *)&stream->streamformat, + stream->width, stream->height, + stream->profile, stream->level, + ctx->encoded_frames, + ctx->sys_errors, + ctx->encode_errors, + ctx->frame_errors); +} + +/* + * V4L2 ioctl operations + */ + +static int hva_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct hva_dev *hva = ctx_to_hdev(ctx); + + strscpy(cap->driver, HVA_NAME, sizeof(cap->driver)); + strscpy(cap->card, hva->vdev->name, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", + hva->pdev->name); + + return 0; +} + +static int hva_enum_fmt_stream(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct hva_dev *hva = ctx_to_hdev(ctx); + + if (unlikely(f->index >= hva->nb_of_streamformats)) + return -EINVAL; + + f->pixelformat = hva->streamformats[f->index]; + + return 0; +} + +static int hva_enum_fmt_frame(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct hva_dev *hva = ctx_to_hdev(ctx); + + if (unlikely(f->index >= hva->nb_of_pixelformats)) + return -EINVAL; + + f->pixelformat = hva->pixelformats[f->index]; + + return 0; +} + +static int hva_g_fmt_stream(struct file *file, void *fh, struct v4l2_format *f) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct hva_streaminfo *streaminfo = &ctx->streaminfo; + + f->fmt.pix.width = streaminfo->width; + f->fmt.pix.height = streaminfo->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quantization; + f->fmt.pix.pixelformat = streaminfo->streamformat; + f->fmt.pix.bytesperline = 0; + f->fmt.pix.sizeimage = ctx->max_stream_size; + + return 0; +} + +static int hva_g_fmt_frame(struct file *file, void *fh, struct v4l2_format *f) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct hva_frameinfo *frameinfo = &ctx->frameinfo; + + f->fmt.pix.width = frameinfo->width; + f->fmt.pix.height = frameinfo->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quantization; + f->fmt.pix.pixelformat = frameinfo->pixelformat; + f->fmt.pix.bytesperline = frame_stride(frameinfo->aligned_width, + frameinfo->pixelformat); + f->fmt.pix.sizeimage = frameinfo->size; + + return 0; +} + +static int hva_try_fmt_stream(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct device *dev = ctx_to_dev(ctx); + struct v4l2_pix_format *pix = &f->fmt.pix; + u32 streamformat = pix->pixelformat; + const struct hva_enc *enc; + u32 width, height; + u32 stream_size; + + enc = hva_find_encoder(ctx, ctx->frameinfo.pixelformat, streamformat); + if (!enc) { + dev_dbg(dev, + "%s V4L2 TRY_FMT (CAPTURE): unsupported format %.4s\n", + ctx->name, (char *)&pix->pixelformat); + return -EINVAL; + } + + width = pix->width; + height = pix->height; + if (ctx->flags & HVA_FLAG_FRAMEINFO) { + /* + * if the frame resolution is already fixed, only allow the + * same stream resolution + */ + pix->width = ctx->frameinfo.width; + pix->height = ctx->frameinfo.height; + if ((pix->width != width) || (pix->height != height)) + dev_dbg(dev, + "%s V4L2 TRY_FMT (CAPTURE): resolution updated %dx%d -> %dx%d to fit frame resolution\n", + ctx->name, width, height, + pix->width, pix->height); + } else { + /* adjust width & height */ + v4l_bound_align_image(&pix->width, + HVA_MIN_WIDTH, enc->max_width, + 0, + &pix->height, + HVA_MIN_HEIGHT, enc->max_height, + 0, + 0); + + if ((pix->width != width) || (pix->height != height)) + dev_dbg(dev, + "%s V4L2 TRY_FMT (CAPTURE): resolution updated %dx%d -> %dx%d to fit min/max/alignment\n", + ctx->name, width, height, + pix->width, pix->height); + } + + stream_size = estimated_stream_size(pix->width, pix->height); + if (pix->sizeimage < stream_size) + pix->sizeimage = stream_size; + + pix->bytesperline = 0; + pix->colorspace = ctx->colorspace; + pix->xfer_func = ctx->xfer_func; + pix->ycbcr_enc = ctx->ycbcr_enc; + pix->quantization = ctx->quantization; + pix->field = V4L2_FIELD_NONE; + + return 0; +} + +static int hva_try_fmt_frame(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct device *dev = ctx_to_dev(ctx); + struct v4l2_pix_format *pix = &f->fmt.pix; + u32 pixelformat = pix->pixelformat; + const struct hva_enc *enc; + u32 width, height; + + enc = hva_find_encoder(ctx, pixelformat, ctx->streaminfo.streamformat); + if (!enc) { + dev_dbg(dev, + "%s V4L2 TRY_FMT (OUTPUT): unsupported format %.4s\n", + ctx->name, (char *)&pixelformat); + return -EINVAL; + } + + /* adjust width & height */ + width = pix->width; + height = pix->height; + v4l_bound_align_image(&pix->width, + HVA_MIN_WIDTH, HVA_MAX_WIDTH, + frame_alignment(pixelformat) - 1, + &pix->height, + HVA_MIN_HEIGHT, HVA_MAX_HEIGHT, + frame_alignment(pixelformat) - 1, + 0); + + if ((pix->width != width) || (pix->height != height)) + dev_dbg(dev, + "%s V4L2 TRY_FMT (OUTPUT): resolution updated %dx%d -> %dx%d to fit min/max/alignment\n", + ctx->name, width, height, pix->width, pix->height); + + width = ALIGN(pix->width, HVA_WIDTH_ALIGNMENT); + height = ALIGN(pix->height, HVA_HEIGHT_ALIGNMENT); + + if (!pix->colorspace) { + pix->colorspace = V4L2_COLORSPACE_REC709; + pix->xfer_func = V4L2_XFER_FUNC_DEFAULT; + pix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pix->quantization = V4L2_QUANTIZATION_DEFAULT; + } + + pix->bytesperline = frame_stride(width, pixelformat); + pix->sizeimage = frame_size(width, height, pixelformat); + pix->field = V4L2_FIELD_NONE; + + return 0; +} + +static int hva_s_fmt_stream(struct file *file, void *fh, struct v4l2_format *f) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct device *dev = ctx_to_dev(ctx); + struct vb2_queue *vq; + int ret; + + ret = hva_try_fmt_stream(file, fh, f); + if (ret) { + dev_dbg(dev, "%s V4L2 S_FMT (CAPTURE): unsupported format %.4s\n", + ctx->name, (char *)&f->fmt.pix.pixelformat); + return ret; + } + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_streaming(vq)) { + dev_dbg(dev, "%s V4L2 S_FMT (CAPTURE): queue busy\n", + ctx->name); + return -EBUSY; + } + + ctx->max_stream_size = f->fmt.pix.sizeimage; + ctx->streaminfo.width = f->fmt.pix.width; + ctx->streaminfo.height = f->fmt.pix.height; + ctx->streaminfo.streamformat = f->fmt.pix.pixelformat; + ctx->flags |= HVA_FLAG_STREAMINFO; + + return 0; +} + +static int hva_s_fmt_frame(struct file *file, void *fh, struct v4l2_format *f) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct device *dev = ctx_to_dev(ctx); + struct v4l2_pix_format *pix = &f->fmt.pix; + struct vb2_queue *vq; + int ret; + + ret = hva_try_fmt_frame(file, fh, f); + if (ret) { + dev_dbg(dev, "%s V4L2 S_FMT (OUTPUT): unsupported format %.4s\n", + ctx->name, (char *)&pix->pixelformat); + return ret; + } + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_streaming(vq)) { + dev_dbg(dev, "%s V4L2 S_FMT (OUTPUT): queue busy\n", ctx->name); + return -EBUSY; + } + + ctx->colorspace = pix->colorspace; + ctx->xfer_func = pix->xfer_func; + ctx->ycbcr_enc = pix->ycbcr_enc; + ctx->quantization = pix->quantization; + + ctx->frameinfo.aligned_width = ALIGN(pix->width, HVA_WIDTH_ALIGNMENT); + ctx->frameinfo.aligned_height = ALIGN(pix->height, + HVA_HEIGHT_ALIGNMENT); + ctx->frameinfo.size = pix->sizeimage; + ctx->frameinfo.pixelformat = pix->pixelformat; + ctx->frameinfo.width = pix->width; + ctx->frameinfo.height = pix->height; + ctx->flags |= HVA_FLAG_FRAMEINFO; + + return 0; +} + +static int hva_g_parm(struct file *file, void *fh, struct v4l2_streamparm *sp) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct v4l2_fract *time_per_frame = &ctx->ctrls.time_per_frame; + + if (sp->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + sp->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + sp->parm.output.timeperframe.numerator = time_per_frame->numerator; + sp->parm.output.timeperframe.denominator = + time_per_frame->denominator; + + return 0; +} + +static int hva_s_parm(struct file *file, void *fh, struct v4l2_streamparm *sp) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct v4l2_fract *time_per_frame = &ctx->ctrls.time_per_frame; + + if (sp->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + if (!sp->parm.output.timeperframe.numerator || + !sp->parm.output.timeperframe.denominator) + return hva_g_parm(file, fh, sp); + + sp->parm.output.capability = V4L2_CAP_TIMEPERFRAME; + time_per_frame->numerator = sp->parm.output.timeperframe.numerator; + time_per_frame->denominator = + sp->parm.output.timeperframe.denominator; + + return 0; +} + +static int hva_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct device *dev = ctx_to_dev(ctx); + + if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + /* + * depending on the targeted compressed video format, the + * capture buffer might contain headers (e.g. H.264 SPS/PPS) + * filled in by the driver client; the size of these data is + * copied from the bytesused field of the V4L2 buffer in the + * payload field of the hva stream buffer + */ + struct vb2_queue *vq; + struct hva_stream *stream; + struct vb2_buffer *vb2_buf; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, buf->type); + + if (buf->index >= vq->num_buffers) { + dev_dbg(dev, "%s buffer index %d out of range (%d)\n", + ctx->name, buf->index, vq->num_buffers); + return -EINVAL; + } + + vb2_buf = vb2_get_buffer(vq, buf->index); + stream = to_hva_stream(to_vb2_v4l2_buffer(vb2_buf)); + stream->bytesused = buf->bytesused; + } + + return v4l2_m2m_qbuf(file, ctx->fh.m2m_ctx, buf); +} + +/* V4L2 ioctl ops */ +static const struct v4l2_ioctl_ops hva_ioctl_ops = { + .vidioc_querycap = hva_querycap, + .vidioc_enum_fmt_vid_cap = hva_enum_fmt_stream, + .vidioc_enum_fmt_vid_out = hva_enum_fmt_frame, + .vidioc_g_fmt_vid_cap = hva_g_fmt_stream, + .vidioc_g_fmt_vid_out = hva_g_fmt_frame, + .vidioc_try_fmt_vid_cap = hva_try_fmt_stream, + .vidioc_try_fmt_vid_out = hva_try_fmt_frame, + .vidioc_s_fmt_vid_cap = hva_s_fmt_stream, + .vidioc_s_fmt_vid_out = hva_s_fmt_frame, + .vidioc_g_parm = hva_g_parm, + .vidioc_s_parm = hva_s_parm, + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + .vidioc_qbuf = hva_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +/* + * V4L2 control operations + */ + +static int hva_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct hva_ctx *ctx = container_of(ctrl->handler, struct hva_ctx, + ctrl_handler); + struct device *dev = ctx_to_dev(ctx); + + dev_dbg(dev, "%s S_CTRL: id = %d, val = %d\n", ctx->name, + ctrl->id, ctrl->val); + + switch (ctrl->id) { + case V4L2_CID_MPEG_VIDEO_BITRATE_MODE: + ctx->ctrls.bitrate_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_GOP_SIZE: + ctx->ctrls.gop_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_BITRATE: + ctx->ctrls.bitrate = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_ASPECT: + ctx->ctrls.aspect = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_PROFILE: + ctx->ctrls.profile = ctrl->val; + snprintf(ctx->streaminfo.profile, + sizeof(ctx->streaminfo.profile), + "%s profile", + v4l2_ctrl_get_menu(ctrl->id)[ctrl->val]); + break; + case V4L2_CID_MPEG_VIDEO_H264_LEVEL: + ctx->ctrls.level = ctrl->val; + snprintf(ctx->streaminfo.level, + sizeof(ctx->streaminfo.level), + "level %s", + v4l2_ctrl_get_menu(ctrl->id)[ctrl->val]); + break; + case V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE: + ctx->ctrls.entropy_mode = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE: + ctx->ctrls.cpb_size = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM: + ctx->ctrls.dct8x8 = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MIN_QP: + ctx->ctrls.qpmin = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_MAX_QP: + ctx->ctrls.qpmax = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE: + ctx->ctrls.vui_sar = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC: + ctx->ctrls.vui_sar_idc = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_SEI_FRAME_PACKING: + ctx->ctrls.sei_fp = ctrl->val; + break; + case V4L2_CID_MPEG_VIDEO_H264_SEI_FP_ARRANGEMENT_TYPE: + ctx->ctrls.sei_fp_type = ctrl->val; + break; + default: + dev_dbg(dev, "%s S_CTRL: invalid control (id = %d)\n", + ctx->name, ctrl->id); + return -EINVAL; + } + + return 0; +} + +/* V4L2 control ops */ +static const struct v4l2_ctrl_ops hva_ctrl_ops = { + .s_ctrl = hva_s_ctrl, +}; + +static int hva_ctrls_setup(struct hva_ctx *ctx) +{ + struct device *dev = ctx_to_dev(ctx); + u64 mask; + enum v4l2_mpeg_video_h264_sei_fp_arrangement_type sei_fp_type = + V4L2_MPEG_VIDEO_H264_SEI_FP_ARRANGEMENT_TYPE_TOP_BOTTOM; + + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 15); + + v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE_MODE, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR, + 0, + V4L2_MPEG_VIDEO_BITRATE_MODE_CBR); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_GOP_SIZE, + 1, 60, 1, 16); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_BITRATE, + 1000, 60000000, 1000, 20000000); + + mask = ~(1 << V4L2_MPEG_VIDEO_ASPECT_1x1); + v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_ASPECT, + V4L2_MPEG_VIDEO_ASPECT_1x1, + mask, + V4L2_MPEG_VIDEO_ASPECT_1x1); + + mask = ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH)); + v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_STEREO_HIGH, + mask, + V4L2_MPEG_VIDEO_H264_PROFILE_HIGH); + + v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_4_2, + 0, + V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + + v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE, + V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC, + 0, + V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CAVLC); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_CPB_SIZE, + 1, 10000, 1, 3000); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM, + 0, 1, 1, 0); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MIN_QP, + 0, 51, 1, 5); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_MAX_QP, + 0, 51, 1, 51); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_ENABLE, + 0, 1, 1, 1); + + mask = ~(1 << V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_1x1); + v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_VUI_SAR_IDC, + V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_1x1, + mask, + V4L2_MPEG_VIDEO_H264_VUI_SAR_IDC_1x1); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_SEI_FRAME_PACKING, + 0, 1, 1, 0); + + mask = ~(1 << sei_fp_type); + v4l2_ctrl_new_std_menu(&ctx->ctrl_handler, &hva_ctrl_ops, + V4L2_CID_MPEG_VIDEO_H264_SEI_FP_ARRANGEMENT_TYPE, + sei_fp_type, + mask, + sei_fp_type); + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + + dev_dbg(dev, "%s controls setup failed (%d)\n", + ctx->name, err); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + return err; + } + + v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + + /* set default time per frame */ + ctx->ctrls.time_per_frame.numerator = HVA_DEFAULT_FRAME_NUM; + ctx->ctrls.time_per_frame.denominator = HVA_DEFAULT_FRAME_DEN; + + return 0; +} + +/* + * mem-to-mem operations + */ + +static void hva_run_work(struct work_struct *work) +{ + struct hva_ctx *ctx = container_of(work, struct hva_ctx, run_work); + struct vb2_v4l2_buffer *src_buf, *dst_buf; + const struct hva_enc *enc = ctx->enc; + struct hva_frame *frame; + struct hva_stream *stream; + int ret; + + /* protect instance against reentrancy */ + mutex_lock(&ctx->lock); + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + hva_dbg_perf_begin(ctx); +#endif + + src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + frame = to_hva_frame(src_buf); + stream = to_hva_stream(dst_buf); + frame->vbuf.sequence = ctx->frame_num++; + + ret = enc->encode(ctx, frame, stream); + + vb2_set_plane_payload(&dst_buf->vb2_buf, 0, stream->bytesused); + if (ret) { + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR); + } else { + /* propagate frame timestamp */ + dst_buf->vb2_buf.timestamp = src_buf->vb2_buf.timestamp; + dst_buf->field = V4L2_FIELD_NONE; + dst_buf->sequence = ctx->stream_num - 1; + + ctx->encoded_frames++; + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + hva_dbg_perf_end(ctx, stream); +#endif + + v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_DONE); + } + + mutex_unlock(&ctx->lock); + + v4l2_m2m_job_finish(ctx->hva_dev->m2m_dev, ctx->fh.m2m_ctx); +} + +static void hva_device_run(void *priv) +{ + struct hva_ctx *ctx = priv; + struct hva_dev *hva = ctx_to_hdev(ctx); + + queue_work(hva->work_queue, &ctx->run_work); +} + +static void hva_job_abort(void *priv) +{ + struct hva_ctx *ctx = priv; + struct device *dev = ctx_to_dev(ctx); + + dev_dbg(dev, "%s aborting job\n", ctx->name); + + ctx->aborting = true; +} + +static int hva_job_ready(void *priv) +{ + struct hva_ctx *ctx = priv; + struct device *dev = ctx_to_dev(ctx); + + if (!v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx)) { + dev_dbg(dev, "%s job not ready: no frame buffers\n", + ctx->name); + return 0; + } + + if (!v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx)) { + dev_dbg(dev, "%s job not ready: no stream buffers\n", + ctx->name); + return 0; + } + + if (ctx->aborting) { + dev_dbg(dev, "%s job not ready: aborting\n", ctx->name); + return 0; + } + + return 1; +} + +/* mem-to-mem ops */ +static const struct v4l2_m2m_ops hva_m2m_ops = { + .device_run = hva_device_run, + .job_abort = hva_job_abort, + .job_ready = hva_job_ready, +}; + +/* + * VB2 queue operations + */ + +static int hva_queue_setup(struct vb2_queue *vq, + unsigned int *num_buffers, unsigned int *num_planes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct hva_ctx *ctx = vb2_get_drv_priv(vq); + struct device *dev = ctx_to_dev(ctx); + unsigned int size; + + dev_dbg(dev, "%s %s queue setup: num_buffers %d\n", ctx->name, + to_type_str(vq->type), *num_buffers); + + size = vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT ? + ctx->frameinfo.size : ctx->max_stream_size; + + if (*num_planes) + return sizes[0] < size ? -EINVAL : 0; + + /* only one plane supported */ + *num_planes = 1; + sizes[0] = size; + + return 0; +} + +static int hva_buf_prepare(struct vb2_buffer *vb) +{ + struct hva_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct device *dev = ctx_to_dev(ctx); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + if (vb->vb2_queue->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + struct hva_frame *frame = to_hva_frame(vbuf); + + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) { + dev_dbg(dev, + "%s frame[%d] prepare: %d field not supported\n", + ctx->name, vb->index, vbuf->field); + return -EINVAL; + } + + if (!frame->prepared) { + /* get memory addresses */ + frame->vaddr = vb2_plane_vaddr(&vbuf->vb2_buf, 0); + frame->paddr = vb2_dma_contig_plane_dma_addr( + &vbuf->vb2_buf, 0); + frame->info = ctx->frameinfo; + frame->prepared = true; + + dev_dbg(dev, + "%s frame[%d] prepared; virt=%p, phy=%pad\n", + ctx->name, vb->index, + frame->vaddr, &frame->paddr); + } + } else { + struct hva_stream *stream = to_hva_stream(vbuf); + + if (!stream->prepared) { + /* get memory addresses */ + stream->vaddr = vb2_plane_vaddr(&vbuf->vb2_buf, 0); + stream->paddr = vb2_dma_contig_plane_dma_addr( + &vbuf->vb2_buf, 0); + stream->size = vb2_plane_size(&vbuf->vb2_buf, 0); + stream->prepared = true; + + dev_dbg(dev, + "%s stream[%d] prepared; virt=%p, phy=%pad\n", + ctx->name, vb->index, + stream->vaddr, &stream->paddr); + } + } + + return 0; +} + +static void hva_buf_queue(struct vb2_buffer *vb) +{ + struct hva_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + if (ctx->fh.m2m_ctx) + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int hva_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct hva_ctx *ctx = vb2_get_drv_priv(vq); + struct hva_dev *hva = ctx_to_hdev(ctx); + struct device *dev = ctx_to_dev(ctx); + struct vb2_v4l2_buffer *vbuf; + int ret; + unsigned int i; + bool found = false; + + dev_dbg(dev, "%s %s start streaming\n", ctx->name, + to_type_str(vq->type)); + + /* open encoder when both start_streaming have been called */ + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + if (!vb2_start_streaming_called(&ctx->fh.m2m_ctx->cap_q_ctx.q)) + return 0; + } else { + if (!vb2_start_streaming_called(&ctx->fh.m2m_ctx->out_q_ctx.q)) + return 0; + } + + /* store the instance context in the instances array */ + for (i = 0; i < HVA_MAX_INSTANCES; i++) { + if (!hva->instances[i]) { + hva->instances[i] = ctx; + /* save the context identifier in the context */ + ctx->id = i; + found = true; + break; + } + } + + if (!found) { + dev_err(dev, "%s maximum instances reached\n", ctx->name); + ret = -ENOMEM; + goto err; + } + + hva->nb_of_instances++; + + if (!ctx->enc) { + ret = hva_open_encoder(ctx, + ctx->streaminfo.streamformat, + ctx->frameinfo.pixelformat, + &ctx->enc); + if (ret < 0) + goto err_ctx; + } + + return 0; + +err_ctx: + hva->instances[ctx->id] = NULL; + hva->nb_of_instances--; +err: + if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + /* return of all pending buffers to vb2 (in queued state) */ + while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED); + } else { + /* return of all pending buffers to vb2 (in queued state) */ + while ((vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED); + } + + ctx->sys_errors++; + + return ret; +} + +static void hva_stop_streaming(struct vb2_queue *vq) +{ + struct hva_ctx *ctx = vb2_get_drv_priv(vq); + struct hva_dev *hva = ctx_to_hdev(ctx); + struct device *dev = ctx_to_dev(ctx); + const struct hva_enc *enc = ctx->enc; + struct vb2_v4l2_buffer *vbuf; + + dev_dbg(dev, "%s %s stop streaming\n", ctx->name, + to_type_str(vq->type)); + + if (vq->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + /* return of all pending buffers to vb2 (in error state) */ + ctx->frame_num = 0; + while ((vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } else { + /* return of all pending buffers to vb2 (in error state) */ + ctx->stream_num = 0; + while ((vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx))) + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } + + if ((V4L2_TYPE_IS_OUTPUT(vq->type) && + vb2_is_streaming(&ctx->fh.m2m_ctx->cap_q_ctx.q)) || + (V4L2_TYPE_IS_CAPTURE(vq->type) && + vb2_is_streaming(&ctx->fh.m2m_ctx->out_q_ctx.q))) { + dev_dbg(dev, "%s %s out=%d cap=%d\n", + ctx->name, to_type_str(vq->type), + vb2_is_streaming(&ctx->fh.m2m_ctx->out_q_ctx.q), + vb2_is_streaming(&ctx->fh.m2m_ctx->cap_q_ctx.q)); + return; + } + + /* close encoder when both stop_streaming have been called */ + if (enc) { + dev_dbg(dev, "%s %s encoder closed\n", ctx->name, enc->name); + enc->close(ctx); + ctx->enc = NULL; + + /* clear instance context in instances array */ + hva->instances[ctx->id] = NULL; + hva->nb_of_instances--; + } + + ctx->aborting = false; +} + +/* VB2 queue ops */ +static const struct vb2_ops hva_qops = { + .queue_setup = hva_queue_setup, + .buf_prepare = hva_buf_prepare, + .buf_queue = hva_buf_queue, + .start_streaming = hva_start_streaming, + .stop_streaming = hva_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* + * V4L2 file operations + */ + +static int queue_init(struct hva_ctx *ctx, struct vb2_queue *vq) +{ + vq->io_modes = VB2_MMAP | VB2_DMABUF; + vq->drv_priv = ctx; + vq->ops = &hva_qops; + vq->mem_ops = &vb2_dma_contig_memops; + vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + vq->lock = &ctx->hva_dev->lock; + + return vb2_queue_init(vq); +} + +static int hva_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct hva_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->buf_struct_size = sizeof(struct hva_frame); + src_vq->min_buffers_needed = MIN_FRAMES; + src_vq->dev = ctx->hva_dev->dev; + + ret = queue_init(ctx, src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->buf_struct_size = sizeof(struct hva_stream); + dst_vq->min_buffers_needed = MIN_STREAMS; + dst_vq->dev = ctx->hva_dev->dev; + + return queue_init(ctx, dst_vq); +} + +static int hva_open(struct file *file) +{ + struct hva_dev *hva = video_drvdata(file); + struct device *dev = hva_to_dev(hva); + struct hva_ctx *ctx; + int ret; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + ret = -ENOMEM; + goto out; + } + ctx->hva_dev = hva; + + INIT_WORK(&ctx->run_work, hva_run_work); + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + ret = hva_ctrls_setup(ctx); + if (ret) { + dev_err(dev, "%s [x:x] failed to setup controls\n", + HVA_PREFIX); + ctx->sys_errors++; + goto err_fh; + } + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + + mutex_init(&ctx->lock); + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(hva->m2m_dev, ctx, + &hva_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + dev_err(dev, "%s failed to initialize m2m context (%d)\n", + HVA_PREFIX, ret); + ctx->sys_errors++; + goto err_ctrls; + } + + /* set the instance name */ + mutex_lock(&hva->lock); + hva->instance_id++; + snprintf(ctx->name, sizeof(ctx->name), "[%3d:----]", + hva->instance_id); + mutex_unlock(&hva->lock); + + /* default parameters for frame and stream */ + set_default_params(ctx); + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + hva_dbg_ctx_create(ctx); +#endif + + dev_info(dev, "%s encoder instance created\n", ctx->name); + + return 0; + +err_ctrls: + v4l2_ctrl_handler_free(&ctx->ctrl_handler); +err_fh: + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); +out: + return ret; +} + +static int hva_release(struct file *file) +{ + struct hva_ctx *ctx = fh_to_ctx(file->private_data); + struct hva_dev *hva = ctx_to_hdev(ctx); + struct device *dev = ctx_to_dev(ctx); + const struct hva_enc *enc = ctx->enc; + + if (enc) { + dev_dbg(dev, "%s %s encoder closed\n", ctx->name, enc->name); + enc->close(ctx); + ctx->enc = NULL; + + /* clear instance context in instances array */ + hva->instances[ctx->id] = NULL; + hva->nb_of_instances--; + } + + /* trace a summary of instance before closing (debug purpose) */ + hva_dbg_summary(ctx); + + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + hva_dbg_ctx_remove(ctx); +#endif + + dev_info(dev, "%s encoder instance released\n", ctx->name); + + kfree(ctx); + + return 0; +} + +/* V4L2 file ops */ +static const struct v4l2_file_operations hva_fops = { + .owner = THIS_MODULE, + .open = hva_open, + .release = hva_release, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, + .poll = v4l2_m2m_fop_poll, +}; + +/* + * Platform device operations + */ + +static int hva_register_device(struct hva_dev *hva) +{ + int ret; + struct video_device *vdev; + struct device *dev; + + if (!hva) + return -ENODEV; + dev = hva_to_dev(hva); + + hva->m2m_dev = v4l2_m2m_init(&hva_m2m_ops); + if (IS_ERR(hva->m2m_dev)) { + dev_err(dev, "%s failed to initialize v4l2-m2m device\n", + HVA_PREFIX); + ret = PTR_ERR(hva->m2m_dev); + goto err; + } + + vdev = video_device_alloc(); + if (!vdev) { + dev_err(dev, "%s failed to allocate video device\n", + HVA_PREFIX); + ret = -ENOMEM; + goto err_m2m_release; + } + + vdev->fops = &hva_fops; + vdev->ioctl_ops = &hva_ioctl_ops; + vdev->release = video_device_release; + vdev->lock = &hva->lock; + vdev->vfl_dir = VFL_DIR_M2M; + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M; + vdev->v4l2_dev = &hva->v4l2_dev; + snprintf(vdev->name, sizeof(vdev->name), "%s%lx", HVA_NAME, + hva->ip_version); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(dev, "%s failed to register video device\n", + HVA_PREFIX); + goto err_vdev_release; + } + + hva->vdev = vdev; + video_set_drvdata(vdev, hva); + return 0; + +err_vdev_release: + video_device_release(vdev); +err_m2m_release: + v4l2_m2m_release(hva->m2m_dev); +err: + return ret; +} + +static void hva_unregister_device(struct hva_dev *hva) +{ + if (!hva) + return; + + if (hva->m2m_dev) + v4l2_m2m_release(hva->m2m_dev); + + video_unregister_device(hva->vdev); +} + +static int hva_probe(struct platform_device *pdev) +{ + struct hva_dev *hva; + struct device *dev = &pdev->dev; + int ret; + + hva = devm_kzalloc(dev, sizeof(*hva), GFP_KERNEL); + if (!hva) { + ret = -ENOMEM; + goto err; + } + + ret = dma_coerce_mask_and_coherent(dev, DMA_BIT_MASK(32)); + if (ret) + return ret; + + hva->dev = dev; + hva->pdev = pdev; + platform_set_drvdata(pdev, hva); + + mutex_init(&hva->lock); + + /* probe hardware */ + ret = hva_hw_probe(pdev, hva); + if (ret) + goto err; + + /* register all available encoders */ + register_encoders(hva); + + /* register all supported formats */ + register_formats(hva); + + /* register on V4L2 */ + ret = v4l2_device_register(dev, &hva->v4l2_dev); + if (ret) { + dev_err(dev, "%s %s failed to register V4L2 device\n", + HVA_PREFIX, HVA_NAME); + goto err_hw; + } + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + hva_debugfs_create(hva); +#endif + + hva->work_queue = create_workqueue(HVA_NAME); + if (!hva->work_queue) { + dev_err(dev, "%s %s failed to allocate work queue\n", + HVA_PREFIX, HVA_NAME); + ret = -ENOMEM; + goto err_v4l2; + } + + /* register device */ + ret = hva_register_device(hva); + if (ret) + goto err_work_queue; + + dev_info(dev, "%s %s registered as /dev/video%d\n", HVA_PREFIX, + HVA_NAME, hva->vdev->num); + + return 0; + +err_work_queue: + destroy_workqueue(hva->work_queue); +err_v4l2: +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + hva_debugfs_remove(hva); +#endif + v4l2_device_unregister(&hva->v4l2_dev); +err_hw: + hva_hw_remove(hva); +err: + return ret; +} + +static int hva_remove(struct platform_device *pdev) +{ + struct hva_dev *hva = platform_get_drvdata(pdev); + struct device *dev = hva_to_dev(hva); + + hva_unregister_device(hva); + + destroy_workqueue(hva->work_queue); + + hva_hw_remove(hva); + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + hva_debugfs_remove(hva); +#endif + + v4l2_device_unregister(&hva->v4l2_dev); + + dev_info(dev, "%s %s removed\n", HVA_PREFIX, pdev->name); + + return 0; +} + +/* PM ops */ +static const struct dev_pm_ops hva_pm_ops = { + .runtime_suspend = hva_hw_runtime_suspend, + .runtime_resume = hva_hw_runtime_resume, +}; + +static const struct of_device_id hva_match_types[] = { + { + .compatible = "st,st-hva", + }, + { /* end node */ } +}; + +MODULE_DEVICE_TABLE(of, hva_match_types); + +static struct platform_driver hva_driver = { + .probe = hva_probe, + .remove = hva_remove, + .driver = { + .name = HVA_NAME, + .of_match_table = hva_match_types, + .pm = &hva_pm_ops, + }, +}; + +module_platform_driver(hva_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yannick Fertre "); +MODULE_DESCRIPTION("STMicroelectronics HVA video encoder V4L2 driver"); diff --git a/drivers/media/platform/st/sti/hva/hva.h b/drivers/media/platform/st/sti/hva/hva.h new file mode 100644 index 000000000..ba6b89341 --- /dev/null +++ b/drivers/media/platform/st/sti/hva/hva.h @@ -0,0 +1,409 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) STMicroelectronics SA 2015 + * Authors: Yannick Fertre + * Hugues Fruchet + */ + +#ifndef HVA_H +#define HVA_H + +#include +#include +#include +#include + +#define fh_to_ctx(f) (container_of(f, struct hva_ctx, fh)) + +#define hva_to_dev(h) (h->dev) + +#define ctx_to_dev(c) (c->hva_dev->dev) + +#define ctx_to_hdev(c) (c->hva_dev) + +#define HVA_NAME "st-hva" +#define HVA_PREFIX "[---:----]" + +extern const struct hva_enc nv12h264enc; +extern const struct hva_enc nv21h264enc; + +/** + * struct hva_frameinfo - information about hva frame + * + * @pixelformat: fourcc code for uncompressed video format + * @width: width of frame + * @height: height of frame + * @aligned_width: width of frame (with encoder alignment constraint) + * @aligned_height: height of frame (with encoder alignment constraint) + * @size: maximum size in bytes required for data +*/ +struct hva_frameinfo { + u32 pixelformat; + u32 width; + u32 height; + u32 aligned_width; + u32 aligned_height; + u32 size; +}; + +/** + * struct hva_streaminfo - information about hva stream + * + * @streamformat: fourcc code of compressed video format (H.264...) + * @width: width of stream + * @height: height of stream + * @profile: profile string + * @level: level string + */ +struct hva_streaminfo { + u32 streamformat; + u32 width; + u32 height; + u8 profile[32]; + u8 level[32]; +}; + +/** + * struct hva_controls - hva controls set + * + * @time_per_frame: time per frame in seconds + * @bitrate_mode: bitrate mode (constant bitrate or variable bitrate) + * @gop_size: groupe of picture size + * @bitrate: bitrate (in bps) + * @aspect: video aspect + * @profile: H.264 profile + * @level: H.264 level + * @entropy_mode: H.264 entropy mode (CABAC or CVLC) + * @cpb_size: coded picture buffer size (in kB) + * @dct8x8: transform mode 8x8 enable + * @qpmin: minimum quantizer + * @qpmax: maximum quantizer + * @vui_sar: pixel aspect ratio enable + * @vui_sar_idc: pixel aspect ratio identifier + * @sei_fp: sei frame packing arrangement enable + * @sei_fp_type: sei frame packing arrangement type + */ +struct hva_controls { + struct v4l2_fract time_per_frame; + enum v4l2_mpeg_video_bitrate_mode bitrate_mode; + u32 gop_size; + u32 bitrate; + enum v4l2_mpeg_video_aspect aspect; + enum v4l2_mpeg_video_h264_profile profile; + enum v4l2_mpeg_video_h264_level level; + enum v4l2_mpeg_video_h264_entropy_mode entropy_mode; + u32 cpb_size; + bool dct8x8; + u32 qpmin; + u32 qpmax; + bool vui_sar; + enum v4l2_mpeg_video_h264_vui_sar_idc vui_sar_idc; + bool sei_fp; + enum v4l2_mpeg_video_h264_sei_fp_arrangement_type sei_fp_type; +}; + +/** + * struct hva_frame - hva frame buffer (output) + * + * @vbuf: video buffer information for V4L2 + * @list: V4L2 m2m list that the frame belongs to + * @info: frame information (width, height, format, alignment...) + * @paddr: physical address (for hardware) + * @vaddr: virtual address (kernel can read/write) + * @prepared: true if vaddr/paddr are resolved + */ +struct hva_frame { + struct vb2_v4l2_buffer vbuf; + struct list_head list; + struct hva_frameinfo info; + dma_addr_t paddr; + void *vaddr; + bool prepared; +}; + +/* + * to_hva_frame() - cast struct vb2_v4l2_buffer * to struct hva_frame * + */ +#define to_hva_frame(vb) \ + container_of(vb, struct hva_frame, vbuf) + +/** + * struct hva_stream - hva stream buffer (capture) + * + * @vbuf: video buffer information for V4L2 + * @list: V4L2 m2m list that the frame belongs to + * @paddr: physical address (for hardware) + * @vaddr: virtual address (kernel can read/write) + * @prepared: true if vaddr/paddr are resolved + * @size: size of the buffer in bytes + * @bytesused: number of bytes occupied by data in the buffer + */ +struct hva_stream { + struct vb2_v4l2_buffer vbuf; + struct list_head list; + dma_addr_t paddr; + void *vaddr; + bool prepared; + unsigned int size; + unsigned int bytesused; +}; + +/* + * to_hva_stream() - cast struct vb2_v4l2_buffer * to struct hva_stream * + */ +#define to_hva_stream(vb) \ + container_of(vb, struct hva_stream, vbuf) + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS +/** + * struct hva_ctx_dbg - instance context debug info + * + * @debugfs_entry: debugfs entry + * @is_valid_period: true if the sequence is valid for performance + * @begin: start time of last HW task + * @total_duration: total HW processing durations in 0.1ms + * @cnt_duration: number of HW processings + * @min_duration: minimum HW processing duration in 0.1ms + * @max_duration: maximum HW processing duration in 0.1ms + * @avg_duration: average HW processing duration in 0.1ms + * @max_fps: maximum frames encoded per second (in 0.1Hz) + * @total_period: total encoding periods in 0.1ms + * @cnt_period: number of periods + * @min_period: minimum encoding period in 0.1ms + * @max_period: maximum encoding period in 0.1ms + * @avg_period: average encoding period in 0.1ms + * @total_stream_size: total number of encoded bytes + * @avg_fps: average frames encoded per second (in 0.1Hz) + * @window_duration: duration of the sampling window in 0.1ms + * @cnt_window: number of samples in the window + * @window_stream_size: number of encoded bytes upon the sampling window + * @last_bitrate: bitrate upon the last sampling window + * @min_bitrate: minimum bitrate in kbps + * @max_bitrate: maximum bitrate in kbps + * @avg_bitrate: average bitrate in kbps + */ +struct hva_ctx_dbg { + struct dentry *debugfs_entry; + bool is_valid_period; + ktime_t begin; + u32 total_duration; + u32 cnt_duration; + u32 min_duration; + u32 max_duration; + u32 avg_duration; + u32 max_fps; + u32 total_period; + u32 cnt_period; + u32 min_period; + u32 max_period; + u32 avg_period; + u32 total_stream_size; + u32 avg_fps; + u32 window_duration; + u32 cnt_window; + u32 window_stream_size; + u32 last_bitrate; + u32 min_bitrate; + u32 max_bitrate; + u32 avg_bitrate; +}; +#endif + +struct hva_dev; +struct hva_enc; + +/** + * struct hva_ctx - context of hva instance + * + * @hva_dev: the device that this instance is associated with + * @fh: V4L2 file handle + * @ctrl_handler: V4L2 controls handler + * @ctrls: hva controls set + * @id: instance identifier + * @aborting: true if current job aborted + * @name: instance name (debug purpose) + * @run_work: encode work + * @lock: mutex used to lock access of this context + * @flags: validity of streaminfo and frameinfo fields + * @frame_num: frame number + * @stream_num: stream number + * @max_stream_size: maximum size in bytes required for stream data + * @colorspace: colorspace identifier + * @xfer_func: transfer function identifier + * @ycbcr_enc: Y'CbCr encoding identifier + * @quantization: quantization identifier + * @streaminfo: stream properties + * @frameinfo: frame properties + * @enc: current encoder + * @priv: private codec data for this instance, allocated + * by encoder @open time + * @hw_err: true if hardware error detected + * @encoded_frames: number of encoded frames + * @sys_errors: number of system errors (memory, resource, pm...) + * @encode_errors: number of encoding errors (hw/driver errors) + * @frame_errors: number of frame errors (format, size, header...) + * @dbg: context debug info + */ +struct hva_ctx { + struct hva_dev *hva_dev; + struct v4l2_fh fh; + struct v4l2_ctrl_handler ctrl_handler; + struct hva_controls ctrls; + u8 id; + bool aborting; + char name[100]; + struct work_struct run_work; + /* mutex protecting this data structure */ + struct mutex lock; + u32 flags; + u32 frame_num; + u32 stream_num; + u32 max_stream_size; + enum v4l2_colorspace colorspace; + enum v4l2_xfer_func xfer_func; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_quantization quantization; + struct hva_streaminfo streaminfo; + struct hva_frameinfo frameinfo; + struct hva_enc *enc; + void *priv; + bool hw_err; + u32 encoded_frames; + u32 sys_errors; + u32 encode_errors; + u32 frame_errors; +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + struct hva_ctx_dbg dbg; +#endif +}; + +#define HVA_FLAG_STREAMINFO 0x0001 +#define HVA_FLAG_FRAMEINFO 0x0002 + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS +/** + * struct hva_dev_dbg - device debug info + * + * @debugfs_entry: debugfs entry + * @last_ctx: debug information about last running instance context + */ +struct hva_dev_dbg { + struct dentry *debugfs_entry; + struct hva_ctx last_ctx; +}; +#endif + +#define HVA_MAX_INSTANCES 16 +#define HVA_MAX_ENCODERS 10 +#define HVA_MAX_FORMATS HVA_MAX_ENCODERS + +/** + * struct hva_dev - abstraction for hva entity + * + * @v4l2_dev: V4L2 device + * @vdev: video device + * @pdev: platform device + * @dev: device + * @lock: mutex used for critical sections & V4L2 ops + * serialization + * @m2m_dev: memory-to-memory V4L2 device information + * @instances: opened instances + * @nb_of_instances: number of opened instances + * @instance_id: rolling counter identifying an instance (debug purpose) + * @regs: register io memory access + * @esram_addr: esram address + * @esram_size: esram size + * @clk: hva clock + * @irq_its: status interruption + * @irq_err: error interruption + * @work_queue: work queue to handle the encode jobs + * @protect_mutex: mutex used to lock access of hardware + * @interrupt: completion interrupt + * @ip_version: IP hardware version + * @encoders: registered encoders + * @nb_of_encoders: number of registered encoders + * @pixelformats: supported uncompressed video formats + * @nb_of_pixelformats: number of supported umcompressed video formats + * @streamformats: supported compressed video formats + * @nb_of_streamformats: number of supported compressed video formats + * @sfl_reg: status fifo level register value + * @sts_reg: status register value + * @lmi_err_reg: local memory interface error register value + * @emi_err_reg: external memory interface error register value + * @hec_mif_err_reg: HEC memory interface error register value + * @dbg: device debug info + */ +struct hva_dev { + struct v4l2_device v4l2_dev; + struct video_device *vdev; + struct platform_device *pdev; + struct device *dev; + /* mutex protecting vb2_queue structure */ + struct mutex lock; + struct v4l2_m2m_dev *m2m_dev; + struct hva_ctx *instances[HVA_MAX_INSTANCES]; + unsigned int nb_of_instances; + unsigned int instance_id; + void __iomem *regs; + u32 esram_addr; + u32 esram_size; + struct clk *clk; + int irq_its; + int irq_err; + struct workqueue_struct *work_queue; + /* mutex protecting hardware access */ + struct mutex protect_mutex; + struct completion interrupt; + unsigned long int ip_version; + const struct hva_enc *encoders[HVA_MAX_ENCODERS]; + u32 nb_of_encoders; + u32 pixelformats[HVA_MAX_FORMATS]; + u32 nb_of_pixelformats; + u32 streamformats[HVA_MAX_FORMATS]; + u32 nb_of_streamformats; + u32 sfl_reg; + u32 sts_reg; + u32 lmi_err_reg; + u32 emi_err_reg; + u32 hec_mif_err_reg; +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS + struct hva_dev_dbg dbg; +#endif +}; + +/** + * struct hva_enc - hva encoder + * + * @name: encoder name + * @streamformat: fourcc code for compressed video format (H.264...) + * @pixelformat: fourcc code for uncompressed video format + * @max_width: maximum width of frame for this encoder + * @max_height: maximum height of frame for this encoder + * @open: open encoder + * @close: close encoder + * @encode: encode a frame (struct hva_frame) in a stream + * (struct hva_stream) + */ + +struct hva_enc { + const char *name; + u32 streamformat; + u32 pixelformat; + u32 max_width; + u32 max_height; + int (*open)(struct hva_ctx *ctx); + int (*close)(struct hva_ctx *ctx); + int (*encode)(struct hva_ctx *ctx, struct hva_frame *frame, + struct hva_stream *stream); +}; + +#ifdef CONFIG_VIDEO_STI_HVA_DEBUGFS +void hva_debugfs_create(struct hva_dev *hva); +void hva_debugfs_remove(struct hva_dev *hva); +void hva_dbg_ctx_create(struct hva_ctx *ctx); +void hva_dbg_ctx_remove(struct hva_ctx *ctx); +void hva_dbg_perf_begin(struct hva_ctx *ctx); +void hva_dbg_perf_end(struct hva_ctx *ctx, struct hva_stream *stream); +#endif + +#endif /* HVA_H */ diff --git a/drivers/media/platform/st/stm32/Kconfig b/drivers/media/platform/st/stm32/Kconfig new file mode 100644 index 000000000..b22dd4753 --- /dev/null +++ b/drivers/media/platform/st/stm32/Kconfig @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: GPL-2.0-only + +# V4L drivers +config VIDEO_STM32_DCMI + tristate "STM32 Digital Camera Memory Interface (DCMI) support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + depends on ARCH_STM32 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select MEDIA_CONTROLLER + select V4L2_FWNODE + help + This module makes the STM32 Digital Camera Memory Interface (DCMI) + available as a v4l2 device. + + To compile this driver as a module, choose M here: the module + will be called stm32-dcmi. + +# Mem2mem drivers +config VIDEO_STM32_DMA2D + tristate "STM32 Chrom-Art Accelerator (DMA2D)" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_STM32 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + Enables DMA2D hardware support on stm32. + + The STM32 DMA2D is a memory-to-memory engine for pixel conversion + and specialized DMA dedicated to image manipulation. diff --git a/drivers/media/platform/st/stm32/Makefile b/drivers/media/platform/st/stm32/Makefile new file mode 100644 index 000000000..896ef98a7 --- /dev/null +++ b/drivers/media/platform/st/stm32/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_VIDEO_STM32_DCMI) += stm32-dcmi.o +stm32-dma2d-objs := dma2d/dma2d.o dma2d/dma2d-hw.o +obj-$(CONFIG_VIDEO_STM32_DMA2D) += stm32-dma2d.o diff --git a/drivers/media/platform/st/stm32/dma2d/dma2d-hw.c b/drivers/media/platform/st/stm32/dma2d/dma2d-hw.c new file mode 100644 index 000000000..ea4cc84d8 --- /dev/null +++ b/drivers/media/platform/st/stm32/dma2d/dma2d-hw.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ST stm32 Chrom-Art - 2D Graphics Accelerator Driver + * + * Copyright (c) 2021 Dillon Min + * Dillon Min, + * + * based on s5p-g2d + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#include + +#include "dma2d.h" +#include "dma2d-regs.h" + +static inline u32 reg_read(void __iomem *base, u32 reg) +{ + return readl_relaxed(base + reg); +} + +static inline void reg_write(void __iomem *base, u32 reg, u32 val) +{ + writel_relaxed(val, base + reg); +} + +static inline void reg_update_bits(void __iomem *base, u32 reg, u32 mask, + u32 val) +{ + reg_write(base, reg, (reg_read(base, reg) & ~mask) | val); +} + +void dma2d_start(struct dma2d_dev *d) +{ + reg_update_bits(d->regs, DMA2D_CR_REG, CR_START, CR_START); +} + +u32 dma2d_get_int(struct dma2d_dev *d) +{ + return reg_read(d->regs, DMA2D_ISR_REG); +} + +void dma2d_clear_int(struct dma2d_dev *d) +{ + u32 isr_val = reg_read(d->regs, DMA2D_ISR_REG); + + reg_write(d->regs, DMA2D_IFCR_REG, isr_val & 0x003f); +} + +void dma2d_config_common(struct dma2d_dev *d, enum dma2d_op_mode op_mode, + u16 width, u16 height) +{ + reg_update_bits(d->regs, DMA2D_CR_REG, CR_MODE_MASK, + op_mode << CR_MODE_SHIFT); + + reg_write(d->regs, DMA2D_NLR_REG, (width << 16) | height); +} + +void dma2d_config_out(struct dma2d_dev *d, struct dma2d_frame *frm, + dma_addr_t o_addr) +{ + reg_update_bits(d->regs, DMA2D_CR_REG, CR_CEIE, CR_CEIE); + reg_update_bits(d->regs, DMA2D_CR_REG, CR_CTCIE, CR_CTCIE); + reg_update_bits(d->regs, DMA2D_CR_REG, CR_CAEIE, CR_CAEIE); + reg_update_bits(d->regs, DMA2D_CR_REG, CR_TCIE, CR_TCIE); + reg_update_bits(d->regs, DMA2D_CR_REG, CR_TEIE, CR_TEIE); + + if (frm->fmt->cmode >= CM_MODE_ARGB8888 && + frm->fmt->cmode <= CM_MODE_ARGB4444) + reg_update_bits(d->regs, DMA2D_OPFCCR_REG, OPFCCR_CM_MASK, + frm->fmt->cmode); + + reg_write(d->regs, DMA2D_OMAR_REG, o_addr); + + reg_write(d->regs, DMA2D_OCOLR_REG, + (frm->a_rgb[3] << 24) | + (frm->a_rgb[2] << 16) | + (frm->a_rgb[1] << 8) | + frm->a_rgb[0]); + + reg_update_bits(d->regs, DMA2D_OOR_REG, OOR_LO_MASK, + frm->line_offset & 0x3fff); +} + +void dma2d_config_fg(struct dma2d_dev *d, struct dma2d_frame *frm, + dma_addr_t f_addr) +{ + reg_write(d->regs, DMA2D_FGMAR_REG, f_addr); + reg_update_bits(d->regs, DMA2D_FGOR_REG, FGOR_LO_MASK, + frm->line_offset); + + if (frm->fmt->cmode >= CM_MODE_ARGB8888 && + frm->fmt->cmode <= CM_MODE_A4) + reg_update_bits(d->regs, DMA2D_FGPFCCR_REG, FGPFCCR_CM_MASK, + frm->fmt->cmode); + + reg_update_bits(d->regs, DMA2D_FGPFCCR_REG, FGPFCCR_AM_MASK, + (frm->a_mode << 16) & 0x03); + + reg_update_bits(d->regs, DMA2D_FGPFCCR_REG, FGPFCCR_ALPHA_MASK, + frm->a_rgb[3] << 24); + + reg_write(d->regs, DMA2D_FGCOLR_REG, + (frm->a_rgb[2] << 16) | + (frm->a_rgb[1] << 8) | + frm->a_rgb[0]); +} + +void dma2d_config_bg(struct dma2d_dev *d, struct dma2d_frame *frm, + dma_addr_t b_addr) +{ + reg_write(d->regs, DMA2D_BGMAR_REG, b_addr); + reg_update_bits(d->regs, DMA2D_BGOR_REG, BGOR_LO_MASK, + frm->line_offset); + + if (frm->fmt->cmode >= CM_MODE_ARGB8888 && + frm->fmt->cmode <= CM_MODE_A4) + reg_update_bits(d->regs, DMA2D_BGPFCCR_REG, BGPFCCR_CM_MASK, + frm->fmt->cmode); + + reg_update_bits(d->regs, DMA2D_BGPFCCR_REG, BGPFCCR_AM_MASK, + (frm->a_mode << 16) & 0x03); + + reg_update_bits(d->regs, DMA2D_BGPFCCR_REG, BGPFCCR_ALPHA_MASK, + frm->a_rgb[3] << 24); + + reg_write(d->regs, DMA2D_BGCOLR_REG, + (frm->a_rgb[2] << 16) | + (frm->a_rgb[1] << 8) | + frm->a_rgb[0]); +} diff --git a/drivers/media/platform/st/stm32/dma2d/dma2d-regs.h b/drivers/media/platform/st/stm32/dma2d/dma2d-regs.h new file mode 100644 index 000000000..6444592d4 --- /dev/null +++ b/drivers/media/platform/st/stm32/dma2d/dma2d-regs.h @@ -0,0 +1,113 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ST stm32 Chrom-Art - 2D Graphics Accelerator Driver + * + * Copyright (c) 2021 Dillon Min + * Dillon Min, + * + * based on s5p-g2d + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#ifndef __DMA2D_REGS_H__ +#define __DMA2D_REGS_H__ + +#define DMA2D_CR_REG 0x0000 +#define CR_MODE_MASK GENMASK(17, 16) +#define CR_MODE_SHIFT 16 +#define CR_M2M 0x0000 +#define CR_M2M_PFC BIT(16) +#define CR_M2M_BLEND BIT(17) +#define CR_R2M (BIT(17) | BIT(16)) +#define CR_CEIE BIT(13) +#define CR_CTCIE BIT(12) +#define CR_CAEIE BIT(11) +#define CR_TWIE BIT(10) +#define CR_TCIE BIT(9) +#define CR_TEIE BIT(8) +#define CR_ABORT BIT(2) +#define CR_SUSP BIT(1) +#define CR_START BIT(0) + +#define DMA2D_ISR_REG 0x0004 +#define ISR_CEIF BIT(5) +#define ISR_CTCIF BIT(4) +#define ISR_CAEIF BIT(3) +#define ISR_TWIF BIT(2) +#define ISR_TCIF BIT(1) +#define ISR_TEIF BIT(0) + +#define DMA2D_IFCR_REG 0x0008 +#define IFCR_CCEIF BIT(5) +#define IFCR_CCTCIF BIT(4) +#define IFCR_CAECIF BIT(3) +#define IFCR_CTWIF BIT(2) +#define IFCR_CTCIF BIT(1) +#define IFCR_CTEIF BIT(0) + +#define DMA2D_FGMAR_REG 0x000c +#define DMA2D_FGOR_REG 0x0010 +#define FGOR_LO_MASK GENMASK(13, 0) + +#define DMA2D_BGMAR_REG 0x0014 +#define DMA2D_BGOR_REG 0x0018 +#define BGOR_LO_MASK GENMASK(13, 0) + +#define DMA2D_FGPFCCR_REG 0x001c +#define FGPFCCR_ALPHA_MASK GENMASK(31, 24) +#define FGPFCCR_AM_MASK GENMASK(17, 16) +#define FGPFCCR_CS_MASK GENMASK(15, 8) +#define FGPFCCR_START BIT(5) +#define FGPFCCR_CCM_RGB888 BIT(4) +#define FGPFCCR_CM_MASK GENMASK(3, 0) + +#define DMA2D_FGCOLR_REG 0x0020 +#define FGCOLR_REG_MASK GENMASK(23, 16) +#define FGCOLR_GREEN_MASK GENMASK(15, 8) +#define FGCOLR_BLUE_MASK GENMASK(7, 0) + +#define DMA2D_BGPFCCR_REG 0x0024 +#define BGPFCCR_ALPHA_MASK GENMASK(31, 24) +#define BGPFCCR_AM_MASK GENMASK(17, 16) +#define BGPFCCR_CS_MASK GENMASK(15, 8) +#define BGPFCCR_START BIT(5) +#define BGPFCCR_CCM_RGB888 BIT(4) +#define BGPFCCR_CM_MASK GENMASK(3, 0) + +#define DMA2D_BGCOLR_REG 0x0028 +#define BGCOLR_REG_MASK GENMASK(23, 16) +#define BGCOLR_GREEN_MASK GENMASK(15, 8) +#define BGCOLR_BLUE_MASK GENMASK(7, 0) + +#define DMA2D_OPFCCR_REG 0x0034 +#define OPFCCR_CM_MASK GENMASK(2, 0) + +#define DMA2D_OCOLR_REG 0x0038 +#define OCOLR_ALPHA_MASK GENMASK(31, 24) +#define OCOLR_RED_MASK GENMASK(23, 16) +#define OCOLR_GREEN_MASK GENMASK(15, 8) +#define OCOLR_BLUE_MASK GENMASK(7, 0) + +#define DMA2D_OMAR_REG 0x003c + +#define DMA2D_OOR_REG 0x0040 +#define OOR_LO_MASK GENMASK(13, 0) + +#define DMA2D_NLR_REG 0x0044 +#define NLR_PL_MASK GENMASK(29, 16) +#define NLR_NL_MASK GENMASK(15, 0) + +/* Hardware limits */ +#define MAX_WIDTH 2592 +#define MAX_HEIGHT 2592 + +#define DEFAULT_WIDTH 240 +#define DEFAULT_HEIGHT 320 +#define DEFAULT_SIZE 307200 + +#define CM_MODE_ARGB8888 0x00 +#define CM_MODE_ARGB4444 0x04 +#define CM_MODE_A4 0x0a +#endif /* __DMA2D_REGS_H__ */ diff --git a/drivers/media/platform/st/stm32/dma2d/dma2d.c b/drivers/media/platform/st/stm32/dma2d/dma2d.c new file mode 100644 index 000000000..9706aa41b --- /dev/null +++ b/drivers/media/platform/st/stm32/dma2d/dma2d.c @@ -0,0 +1,736 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * STM32 DMA2D - 2D Graphics Accelerator Driver + * + * Copyright (c) 2021 Dillon Min + * Dillon Min, + * + * based on s5p-g2d + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "dma2d.h" +#include "dma2d-regs.h" + +/* + * This V4L2 subdev m2m driver enables Chrom-Art Accelerator unit + * of STMicroelectronics STM32 SoC series. + * + * Currently support r2m, m2m, m2m_pfc. + * + * - r2m, Filling a part or the whole of a destination image with a specific + * color. + * - m2m, Copying a part or the whole of a source image into a part or the + * whole of a destination. + * - m2m_pfc, Copying a part or the whole of a source image into a part or the + * whole of a destination image with a pixel format conversion. + */ + +#define fh2ctx(__fh) container_of(__fh, struct dma2d_ctx, fh) + +static const struct dma2d_fmt formats[] = { + { + .fourcc = V4L2_PIX_FMT_ARGB32, + .cmode = DMA2D_CMODE_ARGB8888, + .depth = 32, + }, + { + .fourcc = V4L2_PIX_FMT_RGB24, + .cmode = DMA2D_CMODE_RGB888, + .depth = 24, + }, + { + .fourcc = V4L2_PIX_FMT_RGB565, + .cmode = DMA2D_CMODE_RGB565, + .depth = 16, + }, + { + .fourcc = V4L2_PIX_FMT_ARGB555, + .cmode = DMA2D_CMODE_ARGB1555, + .depth = 16, + }, + { + .fourcc = V4L2_PIX_FMT_ARGB444, + .cmode = DMA2D_CMODE_ARGB4444, + .depth = 16, + }, +}; + +#define NUM_FORMATS ARRAY_SIZE(formats) + +static const struct dma2d_frame def_frame = { + .width = DEFAULT_WIDTH, + .height = DEFAULT_HEIGHT, + .line_offset = 0, + .a_rgb = {0x00, 0x00, 0x00, 0xff}, + .a_mode = DMA2D_ALPHA_MODE_NO_MODIF, + .fmt = (struct dma2d_fmt *)&formats[0], + .size = DEFAULT_SIZE, +}; + +static struct dma2d_fmt *find_fmt(int pixelformat) +{ + unsigned int i; + + for (i = 0; i < NUM_FORMATS; i++) { + if (formats[i].fourcc == pixelformat) + return (struct dma2d_fmt *)&formats[i]; + } + + return NULL; +} + +static struct dma2d_frame *get_frame(struct dma2d_ctx *ctx, + enum v4l2_buf_type type) +{ + return V4L2_TYPE_IS_OUTPUT(type) ? &ctx->cap : &ctx->out; +} + +static int dma2d_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct dma2d_ctx *ctx = vb2_get_drv_priv(vq); + struct dma2d_frame *f = get_frame(ctx, vq->type); + + if (*nplanes) + return sizes[0] < f->size ? -EINVAL : 0; + + sizes[0] = f->size; + *nplanes = 1; + + return 0; +} + +static int dma2d_buf_out_validate(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + + if (vbuf->field == V4L2_FIELD_ANY) + vbuf->field = V4L2_FIELD_NONE; + if (vbuf->field != V4L2_FIELD_NONE) + return -EINVAL; + + return 0; +} + +static int dma2d_buf_prepare(struct vb2_buffer *vb) +{ + struct dma2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct dma2d_frame *f = get_frame(ctx, vb->vb2_queue->type); + + if (vb2_plane_size(vb, 0) < f->size) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, f->size); + + return 0; +} + +static void dma2d_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dma2d_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static int dma2d_start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct dma2d_ctx *ctx = vb2_get_drv_priv(q); + struct dma2d_frame *f = get_frame(ctx, q->type); + + f->sequence = 0; + return 0; +} + +static void dma2d_stop_streaming(struct vb2_queue *q) +{ + struct dma2d_ctx *ctx = vb2_get_drv_priv(q); + struct vb2_v4l2_buffer *vbuf; + + for (;;) { + if (V4L2_TYPE_IS_OUTPUT(q->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + if (!vbuf) + return; + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR); + } +} + +static const struct vb2_ops dma2d_qops = { + .queue_setup = dma2d_queue_setup, + .buf_out_validate = dma2d_buf_out_validate, + .buf_prepare = dma2d_buf_prepare, + .buf_queue = dma2d_buf_queue, + .start_streaming = dma2d_start_streaming, + .stop_streaming = dma2d_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct dma2d_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->ops = &dma2d_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->mutex; + src_vq->dev = ctx->dev->v4l2_dev.dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->ops = &dma2d_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->mutex; + dst_vq->dev = ctx->dev->v4l2_dev.dev; + + return vb2_queue_init(dst_vq); +} + +static int dma2d_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct dma2d_frame *frm; + struct dma2d_ctx *ctx = container_of(ctrl->handler, struct dma2d_ctx, + ctrl_handler); + unsigned long flags; + + spin_lock_irqsave(&ctx->dev->ctrl_lock, flags); + switch (ctrl->id) { + case V4L2_CID_COLORFX: + if (ctrl->val == V4L2_COLORFX_SET_RGB) + ctx->op_mode = DMA2D_MODE_R2M; + else if (ctrl->val == V4L2_COLORFX_NONE) + ctx->op_mode = DMA2D_MODE_M2M; + break; + case V4L2_CID_COLORFX_RGB: + frm = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + frm->a_rgb[2] = (ctrl->val >> 16) & 0xff; + frm->a_rgb[1] = (ctrl->val >> 8) & 0xff; + frm->a_rgb[0] = (ctrl->val >> 0) & 0xff; + break; + default: + spin_unlock_irqrestore(&ctx->dev->ctrl_lock, flags); + return -EINVAL; + } + spin_unlock_irqrestore(&ctx->dev->ctrl_lock, flags); + + return 0; +} + +static const struct v4l2_ctrl_ops dma2d_ctrl_ops = { + .s_ctrl = dma2d_s_ctrl, +}; + +static int dma2d_setup_ctrls(struct dma2d_ctx *ctx) +{ + struct v4l2_ctrl_handler *handler = &ctx->ctrl_handler; + + v4l2_ctrl_handler_init(handler, 2); + + v4l2_ctrl_new_std_menu(handler, &dma2d_ctrl_ops, V4L2_CID_COLORFX, + V4L2_COLORFX_SET_RGB, ~0x10001, + V4L2_COLORFX_NONE); + + v4l2_ctrl_new_std(handler, &dma2d_ctrl_ops, V4L2_CID_COLORFX_RGB, 0, + 0xffffff, 1, 0); + + return 0; +} + +static int dma2d_open(struct file *file) +{ + struct dma2d_dev *dev = video_drvdata(file); + struct dma2d_ctx *ctx = NULL; + int ret = 0; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + ctx->dev = dev; + /* Set default formats */ + ctx->cap = def_frame; + ctx->bg = def_frame; + ctx->out = def_frame; + ctx->op_mode = DMA2D_MODE_M2M_FPC; + ctx->colorspace = V4L2_COLORSPACE_REC709; + if (mutex_lock_interruptible(&dev->mutex)) { + kfree(ctx); + return -ERESTARTSYS; + } + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + mutex_unlock(&dev->mutex); + kfree(ctx); + return ret; + } + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + v4l2_fh_add(&ctx->fh); + + dma2d_setup_ctrls(ctx); + + /* Write the default values to the ctx struct */ + v4l2_ctrl_handler_setup(&ctx->ctrl_handler); + + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + mutex_unlock(&dev->mutex); + + return 0; +} + +static int dma2d_release(struct file *file) +{ + struct dma2d_dev *dev = video_drvdata(file); + struct dma2d_ctx *ctx = fh2ctx(file->private_data); + + mutex_lock(&dev->mutex); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + mutex_unlock(&dev->mutex); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + kfree(ctx); + + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DMA2D_NAME, sizeof(cap->driver)); + strscpy(cap->card, DMA2D_NAME, sizeof(cap->card)); + strscpy(cap->bus_info, BUS_INFO, sizeof(cap->bus_info)); + + return 0; +} + +static int vidioc_enum_fmt(struct file *file, void *prv, struct v4l2_fmtdesc *f) +{ + if (f->index >= NUM_FORMATS) + return -EINVAL; + + f->pixelformat = formats[f->index].fourcc; + return 0; +} + +static int vidioc_g_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct dma2d_ctx *ctx = prv; + struct vb2_queue *vq; + struct dma2d_frame *frm; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (!vq) + return -EINVAL; + + frm = get_frame(ctx, f->type); + f->fmt.pix.width = frm->width; + f->fmt.pix.height = frm->height; + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.pixelformat = frm->fmt->fourcc; + f->fmt.pix.bytesperline = (frm->width * frm->fmt->depth) >> 3; + f->fmt.pix.sizeimage = frm->size; + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quant; + + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct dma2d_ctx *ctx = prv; + struct dma2d_fmt *fmt; + enum v4l2_field *field; + u32 fourcc = f->fmt.pix.pixelformat; + + fmt = find_fmt(fourcc); + if (!fmt) { + f->fmt.pix.pixelformat = formats[0].fourcc; + fmt = find_fmt(f->fmt.pix.pixelformat); + } + + field = &f->fmt.pix.field; + if (*field == V4L2_FIELD_ANY) + *field = V4L2_FIELD_NONE; + else if (*field != V4L2_FIELD_NONE) + return -EINVAL; + + if (f->fmt.pix.width > MAX_WIDTH) + f->fmt.pix.width = MAX_WIDTH; + if (f->fmt.pix.height > MAX_HEIGHT) + f->fmt.pix.height = MAX_HEIGHT; + + if (f->fmt.pix.width < 1) + f->fmt.pix.width = 1; + if (f->fmt.pix.height < 1) + f->fmt.pix.height = 1; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT && !f->fmt.pix.colorspace) { + f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + f->fmt.pix.colorspace = ctx->colorspace; + f->fmt.pix.xfer_func = ctx->xfer_func; + f->fmt.pix.ycbcr_enc = ctx->ycbcr_enc; + f->fmt.pix.quantization = ctx->quant; + } + f->fmt.pix.bytesperline = (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *prv, struct v4l2_format *f) +{ + struct dma2d_ctx *ctx = prv; + struct vb2_queue *vq; + struct dma2d_frame *frm; + struct dma2d_fmt *fmt; + int ret = 0; + + /* Adjust all values accordingly to the hardware capabilities + * and chosen format. + */ + ret = vidioc_try_fmt(file, prv, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) + return -EBUSY; + + fmt = find_fmt(f->fmt.pix.pixelformat); + if (!fmt) + return -EINVAL; + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + ctx->colorspace = f->fmt.pix.colorspace; + ctx->xfer_func = f->fmt.pix.xfer_func; + ctx->ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->quant = f->fmt.pix.quantization; + } + + frm = get_frame(ctx, f->type); + frm->width = f->fmt.pix.width; + frm->height = f->fmt.pix.height; + frm->size = f->fmt.pix.sizeimage; + /* Reset crop settings */ + frm->o_width = 0; + frm->o_height = 0; + frm->c_width = frm->width; + frm->c_height = frm->height; + frm->right = frm->width; + frm->bottom = frm->height; + frm->fmt = fmt; + frm->line_offset = 0; + + return 0; +} + +static void device_run(void *prv) +{ + struct dma2d_ctx *ctx = prv; + struct dma2d_dev *dev = ctx->dev; + struct dma2d_frame *frm_out, *frm_cap; + struct vb2_v4l2_buffer *src, *dst; + unsigned long flags; + + spin_lock_irqsave(&dev->ctrl_lock, flags); + dev->curr = ctx; + + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + if (!dst || !src) + goto end; + + frm_cap = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + frm_out = get_frame(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); + if (!frm_cap || !frm_out) + goto end; + + src->sequence = frm_out->sequence++; + dst->sequence = frm_cap->sequence++; + v4l2_m2m_buf_copy_metadata(src, dst, true); + + clk_enable(dev->gate); + + dma2d_config_fg(dev, frm_out, + vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0)); + + /* TODO: add M2M_BLEND handler here */ + + if (ctx->op_mode != DMA2D_MODE_R2M) { + if (frm_out->fmt->fourcc == frm_cap->fmt->fourcc) + ctx->op_mode = DMA2D_MODE_M2M; + else + ctx->op_mode = DMA2D_MODE_M2M_FPC; + } + + dma2d_config_out(dev, frm_cap, + vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0)); + dma2d_config_common(dev, ctx->op_mode, frm_cap->width, frm_cap->height); + + dma2d_start(dev); +end: + spin_unlock_irqrestore(&dev->ctrl_lock, flags); +} + +static irqreturn_t dma2d_isr(int irq, void *prv) +{ + struct dma2d_dev *dev = prv; + struct dma2d_ctx *ctx = dev->curr; + struct vb2_v4l2_buffer *src, *dst; + u32 s = dma2d_get_int(dev); + + dma2d_clear_int(dev); + if (s & ISR_TCIF || s == 0) { + clk_disable(dev->gate); + + WARN_ON(!ctx); + + src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + WARN_ON(!dst); + WARN_ON(!src); + + v4l2_m2m_buf_done(src, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst, VB2_BUF_STATE_DONE); + v4l2_m2m_job_finish(dev->m2m_dev, ctx->fh.m2m_ctx); + + dev->curr = NULL; + } + + return IRQ_HANDLED; +} + +static const struct v4l2_file_operations dma2d_fops = { + .owner = THIS_MODULE, + .open = dma2d_open, + .release = dma2d_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = v4l2_m2m_get_unmapped_area, +#endif +}; + +static const struct v4l2_ioctl_ops dma2d_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt, + + .vidioc_enum_fmt_vid_out = vidioc_enum_fmt, + .vidioc_g_fmt_vid_out = vidioc_g_fmt, + .vidioc_try_fmt_vid_out = vidioc_try_fmt, + .vidioc_s_fmt_vid_out = vidioc_s_fmt, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct video_device dma2d_videodev = { + .name = DMA2D_NAME, + .fops = &dma2d_fops, + .ioctl_ops = &dma2d_ioctl_ops, + .minor = -1, + .release = video_device_release, + .vfl_dir = VFL_DIR_M2M, +}; + +static const struct v4l2_m2m_ops dma2d_m2m_ops = { + .device_run = device_run, +}; + +static const struct of_device_id stm32_dma2d_match[]; + +static int dma2d_probe(struct platform_device *pdev) +{ + struct dma2d_dev *dev; + struct video_device *vfd; + struct resource *res; + int ret = 0; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + spin_lock_init(&dev->ctrl_lock); + mutex_init(&dev->mutex); + atomic_set(&dev->num_inst, 0); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + dev->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(dev->regs)) + return PTR_ERR(dev->regs); + + dev->gate = clk_get(&pdev->dev, "dma2d"); + if (IS_ERR(dev->gate)) { + dev_err(&pdev->dev, "failed to get dma2d clock gate\n"); + ret = -ENXIO; + return ret; + } + + ret = clk_prepare(dev->gate); + if (ret) { + dev_err(&pdev->dev, "failed to prepare dma2d clock gate\n"); + goto put_clk_gate; + } + + ret = platform_get_irq(pdev, 0); + if (ret < 0) + goto unprep_clk_gate; + + dev->irq = ret; + + ret = devm_request_irq(&pdev->dev, dev->irq, dma2d_isr, + 0, pdev->name, dev); + if (ret) { + dev_err(&pdev->dev, "failed to install IRQ\n"); + goto unprep_clk_gate; + } + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) + goto unprep_clk_gate; + + vfd = video_device_alloc(); + if (!vfd) { + v4l2_err(&dev->v4l2_dev, "Failed to allocate video device\n"); + ret = -ENOMEM; + goto unreg_v4l2_dev; + } + + *vfd = dma2d_videodev; + vfd->lock = &dev->mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + vfd->device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING; + + platform_set_drvdata(pdev, dev); + dev->m2m_dev = v4l2_m2m_init(&dma2d_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, "Failed to init mem2mem device\n"); + ret = PTR_ERR(dev->m2m_dev); + goto rel_vdev; + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + goto free_m2m; + } + + video_set_drvdata(vfd, dev); + dev->vfd = vfd; + v4l2_info(&dev->v4l2_dev, "device registered as /dev/video%d\n", + vfd->num); + return 0; + +free_m2m: + v4l2_m2m_release(dev->m2m_dev); +rel_vdev: + video_device_release(vfd); +unreg_v4l2_dev: + v4l2_device_unregister(&dev->v4l2_dev); +unprep_clk_gate: + clk_unprepare(dev->gate); +put_clk_gate: + clk_put(dev->gate); + + return ret; +} + +static int dma2d_remove(struct platform_device *pdev) +{ + struct dma2d_dev *dev = platform_get_drvdata(pdev); + + v4l2_info(&dev->v4l2_dev, "Removing " DMA2D_NAME); + v4l2_m2m_release(dev->m2m_dev); + video_unregister_device(dev->vfd); + v4l2_device_unregister(&dev->v4l2_dev); + vb2_dma_contig_clear_max_seg_size(&pdev->dev); + clk_unprepare(dev->gate); + clk_put(dev->gate); + + return 0; +} + +static const struct of_device_id stm32_dma2d_match[] = { + { + .compatible = "st,stm32-dma2d", + .data = NULL, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, stm32_dma2d_match); + +static struct platform_driver dma2d_pdrv = { + .probe = dma2d_probe, + .remove = dma2d_remove, + .driver = { + .name = DMA2D_NAME, + .of_match_table = stm32_dma2d_match, + }, +}; + +module_platform_driver(dma2d_pdrv); + +MODULE_AUTHOR("Dillon Min "); +MODULE_DESCRIPTION("STM32 Chrom-Art Accelerator DMA2D driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/st/stm32/dma2d/dma2d.h b/drivers/media/platform/st/stm32/dma2d/dma2d.h new file mode 100644 index 000000000..3f03a7ca9 --- /dev/null +++ b/drivers/media/platform/st/stm32/dma2d/dma2d.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * ST stm32 DMA2D - 2D Graphics Accelerator Driver + * + * Copyright (c) 2021 Dillon Min + * Dillon Min, + * + * based on s5p-g2d + * + * Copyright (c) 2011 Samsung Electronics Co., Ltd. + * Kamil Debski, + */ + +#ifndef __DMA2D_H__ +#define __DMA2D_H__ + +#include +#include +#include + +#define DMA2D_NAME "stm-dma2d" +#define BUS_INFO "platform:stm-dma2d" +enum dma2d_op_mode { + DMA2D_MODE_M2M, + DMA2D_MODE_M2M_FPC, + DMA2D_MODE_M2M_BLEND, + DMA2D_MODE_R2M +}; + +enum dma2d_cmode { + /* output pfc cmode from ARGB888 to ARGB4444 */ + DMA2D_CMODE_ARGB8888, + DMA2D_CMODE_RGB888, + DMA2D_CMODE_RGB565, + DMA2D_CMODE_ARGB1555, + DMA2D_CMODE_ARGB4444, + /* bg or fg pfc cmode from L8 to A4 */ + DMA2D_CMODE_L8, + DMA2D_CMODE_AL44, + DMA2D_CMODE_AL88, + DMA2D_CMODE_L4, + DMA2D_CMODE_A8, + DMA2D_CMODE_A4 +}; + +enum dma2d_alpha_mode { + DMA2D_ALPHA_MODE_NO_MODIF, + DMA2D_ALPHA_MODE_REPLACE, + DMA2D_ALPHA_MODE_COMBINE +}; + +struct dma2d_fmt { + u32 fourcc; + int depth; + enum dma2d_cmode cmode; +}; + +struct dma2d_frame { + /* Original dimensions */ + u32 width; + u32 height; + /* Crop size */ + u32 c_width; + u32 c_height; + /* Offset */ + u32 o_width; + u32 o_height; + u32 bottom; + u32 right; + u16 line_offset; + /* Image format */ + struct dma2d_fmt *fmt; + /* [0]: blue + * [1]: green + * [2]: red + * [3]: alpha + */ + u8 a_rgb[4]; + /* + * AM[1:0] of DMA2D_FGPFCCR + */ + enum dma2d_alpha_mode a_mode; + u32 size; + unsigned int sequence; +}; + +struct dma2d_ctx { + struct v4l2_fh fh; + struct dma2d_dev *dev; + struct dma2d_frame cap; + struct dma2d_frame out; + struct dma2d_frame bg; + /* fb_buf always point to bg address */ + struct v4l2_framebuffer fb_buf; + /* + * MODE[17:16] of DMA2D_CR + */ + enum dma2d_op_mode op_mode; + struct v4l2_ctrl_handler ctrl_handler; + enum v4l2_colorspace colorspace; + enum v4l2_ycbcr_encoding ycbcr_enc; + enum v4l2_xfer_func xfer_func; + enum v4l2_quantization quant; +}; + +struct dma2d_dev { + struct v4l2_device v4l2_dev; + struct v4l2_m2m_dev *m2m_dev; + struct video_device *vfd; + /* for device open/close etc */ + struct mutex mutex; + /* to avoid the conflict with device running and user setting + * at the same time + */ + spinlock_t ctrl_lock; + atomic_t num_inst; + void __iomem *regs; + struct clk *gate; + struct dma2d_ctx *curr; + int irq; +}; + +void dma2d_start(struct dma2d_dev *d); +u32 dma2d_get_int(struct dma2d_dev *d); +void dma2d_clear_int(struct dma2d_dev *d); +void dma2d_config_out(struct dma2d_dev *d, struct dma2d_frame *frm, + dma_addr_t o_addr); +void dma2d_config_fg(struct dma2d_dev *d, struct dma2d_frame *frm, + dma_addr_t f_addr); +void dma2d_config_bg(struct dma2d_dev *d, struct dma2d_frame *frm, + dma_addr_t b_addr); +void dma2d_config_common(struct dma2d_dev *d, enum dma2d_op_mode op_mode, + u16 width, u16 height); + +#endif /* __DMA2D_H__ */ diff --git a/drivers/media/platform/st/stm32/stm32-dcmi.c b/drivers/media/platform/st/stm32/stm32-dcmi.c new file mode 100644 index 000000000..37458d4d9 --- /dev/null +++ b/drivers/media/platform/st/stm32/stm32-dcmi.c @@ -0,0 +1,2229 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for STM32 Digital Camera Memory Interface + * + * Copyright (C) STMicroelectronics SA 2017 + * Authors: Yannick Fertre + * Hugues Fruchet + * for STMicroelectronics. + * + * This driver is based on atmel_isi.c + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRV_NAME "stm32-dcmi" + +/* Registers offset for DCMI */ +#define DCMI_CR 0x00 /* Control Register */ +#define DCMI_SR 0x04 /* Status Register */ +#define DCMI_RIS 0x08 /* Raw Interrupt Status register */ +#define DCMI_IER 0x0C /* Interrupt Enable Register */ +#define DCMI_MIS 0x10 /* Masked Interrupt Status register */ +#define DCMI_ICR 0x14 /* Interrupt Clear Register */ +#define DCMI_ESCR 0x18 /* Embedded Synchronization Code Register */ +#define DCMI_ESUR 0x1C /* Embedded Synchronization Unmask Register */ +#define DCMI_CWSTRT 0x20 /* Crop Window STaRT */ +#define DCMI_CWSIZE 0x24 /* Crop Window SIZE */ +#define DCMI_DR 0x28 /* Data Register */ +#define DCMI_IDR 0x2C /* IDentifier Register */ + +/* Bits definition for control register (DCMI_CR) */ +#define CR_CAPTURE BIT(0) +#define CR_CM BIT(1) +#define CR_CROP BIT(2) +#define CR_JPEG BIT(3) +#define CR_ESS BIT(4) +#define CR_PCKPOL BIT(5) +#define CR_HSPOL BIT(6) +#define CR_VSPOL BIT(7) +#define CR_FCRC_0 BIT(8) +#define CR_FCRC_1 BIT(9) +#define CR_EDM_0 BIT(10) +#define CR_EDM_1 BIT(11) +#define CR_ENABLE BIT(14) + +/* Bits definition for status register (DCMI_SR) */ +#define SR_HSYNC BIT(0) +#define SR_VSYNC BIT(1) +#define SR_FNE BIT(2) + +/* + * Bits definition for interrupt registers + * (DCMI_RIS, DCMI_IER, DCMI_MIS, DCMI_ICR) + */ +#define IT_FRAME BIT(0) +#define IT_OVR BIT(1) +#define IT_ERR BIT(2) +#define IT_VSYNC BIT(3) +#define IT_LINE BIT(4) + +enum state { + STOPPED = 0, + WAIT_FOR_BUFFER, + RUNNING, +}; + +#define MIN_WIDTH 16U +#define MAX_WIDTH 2592U +#define MIN_HEIGHT 16U +#define MAX_HEIGHT 2592U + +#define TIMEOUT_MS 1000 + +#define OVERRUN_ERROR_THRESHOLD 3 + +struct dcmi_format { + u32 fourcc; + u32 mbus_code; + u8 bpp; +}; + +struct dcmi_framesize { + u32 width; + u32 height; +}; + +struct dcmi_buf { + struct vb2_v4l2_buffer vb; + bool prepared; + struct sg_table sgt; + size_t size; + struct list_head list; +}; + +struct stm32_dcmi { + /* Protects the access of variables shared within the interrupt */ + spinlock_t irqlock; + struct device *dev; + void __iomem *regs; + struct resource *res; + struct reset_control *rstc; + int sequence; + struct list_head buffers; + struct dcmi_buf *active; + int irq; + + struct v4l2_device v4l2_dev; + struct video_device *vdev; + struct v4l2_async_notifier notifier; + struct v4l2_subdev *source; + struct v4l2_format fmt; + struct v4l2_rect crop; + bool do_crop; + + const struct dcmi_format **sd_formats; + unsigned int num_of_sd_formats; + const struct dcmi_format *sd_format; + struct dcmi_framesize *sd_framesizes; + unsigned int num_of_sd_framesizes; + struct dcmi_framesize sd_framesize; + struct v4l2_rect sd_bounds; + + /* Protect this data structure */ + struct mutex lock; + struct vb2_queue queue; + + struct v4l2_mbus_config_parallel bus; + enum v4l2_mbus_type bus_type; + struct completion complete; + struct clk *mclk; + enum state state; + struct dma_chan *dma_chan; + dma_cookie_t dma_cookie; + u32 dma_max_burst; + u32 misr; + int errors_count; + int overrun_count; + int buffers_count; + + /* Ensure DMA operations atomicity */ + struct mutex dma_lock; + + struct media_device mdev; + struct media_pad vid_cap_pad; + struct media_pipeline pipeline; +}; + +static inline struct stm32_dcmi *notifier_to_dcmi(struct v4l2_async_notifier *n) +{ + return container_of(n, struct stm32_dcmi, notifier); +} + +static inline u32 reg_read(void __iomem *base, u32 reg) +{ + return readl_relaxed(base + reg); +} + +static inline void reg_write(void __iomem *base, u32 reg, u32 val) +{ + writel_relaxed(val, base + reg); +} + +static inline void reg_set(void __iomem *base, u32 reg, u32 mask) +{ + reg_write(base, reg, reg_read(base, reg) | mask); +} + +static inline void reg_clear(void __iomem *base, u32 reg, u32 mask) +{ + reg_write(base, reg, reg_read(base, reg) & ~mask); +} + +static int dcmi_start_capture(struct stm32_dcmi *dcmi, struct dcmi_buf *buf); + +static void dcmi_buffer_done(struct stm32_dcmi *dcmi, + struct dcmi_buf *buf, + size_t bytesused, + int err) +{ + struct vb2_v4l2_buffer *vbuf; + + if (!buf) + return; + + list_del_init(&buf->list); + + vbuf = &buf->vb; + + vbuf->sequence = dcmi->sequence++; + vbuf->field = V4L2_FIELD_NONE; + vbuf->vb2_buf.timestamp = ktime_get_ns(); + vb2_set_plane_payload(&vbuf->vb2_buf, 0, bytesused); + vb2_buffer_done(&vbuf->vb2_buf, + err ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE); + dev_dbg(dcmi->dev, "buffer[%d] done seq=%d, bytesused=%zu\n", + vbuf->vb2_buf.index, vbuf->sequence, bytesused); + + dcmi->buffers_count++; + dcmi->active = NULL; +} + +static int dcmi_restart_capture(struct stm32_dcmi *dcmi) +{ + struct dcmi_buf *buf; + + spin_lock_irq(&dcmi->irqlock); + + if (dcmi->state != RUNNING) { + spin_unlock_irq(&dcmi->irqlock); + return -EINVAL; + } + + /* Restart a new DMA transfer with next buffer */ + if (list_empty(&dcmi->buffers)) { + dev_dbg(dcmi->dev, "Capture restart is deferred to next buffer queueing\n"); + dcmi->state = WAIT_FOR_BUFFER; + spin_unlock_irq(&dcmi->irqlock); + return 0; + } + buf = list_entry(dcmi->buffers.next, struct dcmi_buf, list); + dcmi->active = buf; + + spin_unlock_irq(&dcmi->irqlock); + + return dcmi_start_capture(dcmi, buf); +} + +static void dcmi_dma_callback(void *param) +{ + struct stm32_dcmi *dcmi = (struct stm32_dcmi *)param; + struct dma_tx_state state; + enum dma_status status; + struct dcmi_buf *buf = dcmi->active; + + spin_lock_irq(&dcmi->irqlock); + + /* Check DMA status */ + status = dmaengine_tx_status(dcmi->dma_chan, dcmi->dma_cookie, &state); + + switch (status) { + case DMA_IN_PROGRESS: + dev_dbg(dcmi->dev, "%s: Received DMA_IN_PROGRESS\n", __func__); + break; + case DMA_PAUSED: + dev_err(dcmi->dev, "%s: Received DMA_PAUSED\n", __func__); + break; + case DMA_ERROR: + dev_err(dcmi->dev, "%s: Received DMA_ERROR\n", __func__); + + /* Return buffer to V4L2 in error state */ + dcmi_buffer_done(dcmi, buf, 0, -EIO); + break; + case DMA_COMPLETE: + dev_dbg(dcmi->dev, "%s: Received DMA_COMPLETE\n", __func__); + + /* Return buffer to V4L2 */ + dcmi_buffer_done(dcmi, buf, buf->size, 0); + + spin_unlock_irq(&dcmi->irqlock); + + /* Restart capture */ + if (dcmi_restart_capture(dcmi)) + dev_err(dcmi->dev, "%s: Cannot restart capture on DMA complete\n", + __func__); + return; + default: + dev_err(dcmi->dev, "%s: Received unknown status\n", __func__); + break; + } + + spin_unlock_irq(&dcmi->irqlock); +} + +static int dcmi_start_dma(struct stm32_dcmi *dcmi, + struct dcmi_buf *buf) +{ + struct dma_async_tx_descriptor *desc = NULL; + struct dma_slave_config config; + int ret; + + memset(&config, 0, sizeof(config)); + + config.src_addr = (dma_addr_t)dcmi->res->start + DCMI_DR; + config.src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + config.dst_maxburst = 4; + + /* Configure DMA channel */ + ret = dmaengine_slave_config(dcmi->dma_chan, &config); + if (ret < 0) { + dev_err(dcmi->dev, "%s: DMA channel config failed (%d)\n", + __func__, ret); + return ret; + } + + /* + * Avoid call of dmaengine_terminate_sync() between + * dmaengine_prep_slave_single() and dmaengine_submit() + * by locking the whole DMA submission sequence + */ + mutex_lock(&dcmi->dma_lock); + + /* Prepare a DMA transaction */ + desc = dmaengine_prep_slave_sg(dcmi->dma_chan, buf->sgt.sgl, buf->sgt.nents, + DMA_DEV_TO_MEM, + DMA_PREP_INTERRUPT); + if (!desc) { + dev_err(dcmi->dev, "%s: DMA dmaengine_prep_slave_sg failed\n", __func__); + mutex_unlock(&dcmi->dma_lock); + return -EINVAL; + } + + /* Set completion callback routine for notification */ + desc->callback = dcmi_dma_callback; + desc->callback_param = dcmi; + + /* Push current DMA transaction in the pending queue */ + dcmi->dma_cookie = dmaengine_submit(desc); + if (dma_submit_error(dcmi->dma_cookie)) { + dev_err(dcmi->dev, "%s: DMA submission failed\n", __func__); + mutex_unlock(&dcmi->dma_lock); + return -ENXIO; + } + + mutex_unlock(&dcmi->dma_lock); + + dma_async_issue_pending(dcmi->dma_chan); + + return 0; +} + +static int dcmi_start_capture(struct stm32_dcmi *dcmi, struct dcmi_buf *buf) +{ + int ret; + + if (!buf) + return -EINVAL; + + ret = dcmi_start_dma(dcmi, buf); + if (ret) { + dcmi->errors_count++; + return ret; + } + + /* Enable capture */ + reg_set(dcmi->regs, DCMI_CR, CR_CAPTURE); + + return 0; +} + +static void dcmi_set_crop(struct stm32_dcmi *dcmi) +{ + u32 size, start; + + /* Crop resolution */ + size = ((dcmi->crop.height - 1) << 16) | + ((dcmi->crop.width << 1) - 1); + reg_write(dcmi->regs, DCMI_CWSIZE, size); + + /* Crop start point */ + start = ((dcmi->crop.top) << 16) | + ((dcmi->crop.left << 1)); + reg_write(dcmi->regs, DCMI_CWSTRT, start); + + dev_dbg(dcmi->dev, "Cropping to %ux%u@%u:%u\n", + dcmi->crop.width, dcmi->crop.height, + dcmi->crop.left, dcmi->crop.top); + + /* Enable crop */ + reg_set(dcmi->regs, DCMI_CR, CR_CROP); +} + +static void dcmi_process_jpeg(struct stm32_dcmi *dcmi) +{ + struct dma_tx_state state; + enum dma_status status; + struct dcmi_buf *buf = dcmi->active; + + if (!buf) + return; + + /* + * Because of variable JPEG buffer size sent by sensor, + * DMA transfer never completes due to transfer size never reached. + * In order to ensure that all the JPEG data are transferred + * in active buffer memory, DMA is drained. + * Then DMA tx status gives the amount of data transferred + * to memory, which is then returned to V4L2 through the active + * buffer payload. + */ + + /* Drain DMA */ + dmaengine_synchronize(dcmi->dma_chan); + + /* Get DMA residue to get JPEG size */ + status = dmaengine_tx_status(dcmi->dma_chan, dcmi->dma_cookie, &state); + if (status != DMA_ERROR && state.residue < buf->size) { + /* Return JPEG buffer to V4L2 with received JPEG buffer size */ + dcmi_buffer_done(dcmi, buf, buf->size - state.residue, 0); + } else { + dcmi->errors_count++; + dev_err(dcmi->dev, "%s: Cannot get JPEG size from DMA\n", + __func__); + /* Return JPEG buffer to V4L2 in ERROR state */ + dcmi_buffer_done(dcmi, buf, 0, -EIO); + } + + /* Abort DMA operation */ + dmaengine_terminate_sync(dcmi->dma_chan); + + /* Restart capture */ + if (dcmi_restart_capture(dcmi)) + dev_err(dcmi->dev, "%s: Cannot restart capture on JPEG received\n", + __func__); +} + +static irqreturn_t dcmi_irq_thread(int irq, void *arg) +{ + struct stm32_dcmi *dcmi = arg; + + spin_lock_irq(&dcmi->irqlock); + + if (dcmi->misr & IT_OVR) { + dcmi->overrun_count++; + if (dcmi->overrun_count > OVERRUN_ERROR_THRESHOLD) + dcmi->errors_count++; + } + if (dcmi->misr & IT_ERR) + dcmi->errors_count++; + + if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG && + dcmi->misr & IT_FRAME) { + /* JPEG received */ + spin_unlock_irq(&dcmi->irqlock); + dcmi_process_jpeg(dcmi); + return IRQ_HANDLED; + } + + spin_unlock_irq(&dcmi->irqlock); + return IRQ_HANDLED; +} + +static irqreturn_t dcmi_irq_callback(int irq, void *arg) +{ + struct stm32_dcmi *dcmi = arg; + unsigned long flags; + + spin_lock_irqsave(&dcmi->irqlock, flags); + + dcmi->misr = reg_read(dcmi->regs, DCMI_MIS); + + /* Clear interrupt */ + reg_set(dcmi->regs, DCMI_ICR, IT_FRAME | IT_OVR | IT_ERR); + + spin_unlock_irqrestore(&dcmi->irqlock, flags); + + return IRQ_WAKE_THREAD; +} + +static int dcmi_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + unsigned int size; + + size = dcmi->fmt.fmt.pix.sizeimage; + + /* Make sure the image size is large enough */ + if (*nplanes) + return sizes[0] < size ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = size; + + dev_dbg(dcmi->dev, "Setup queue, count=%d, size=%d\n", + *nbuffers, size); + + return 0; +} + +static int dcmi_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + + INIT_LIST_HEAD(&buf->list); + + return 0; +} + +static int dcmi_buf_prepare(struct vb2_buffer *vb) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + unsigned long size; + unsigned int num_sgs = 1; + dma_addr_t dma_buf; + struct scatterlist *sg; + int i, ret; + + size = dcmi->fmt.fmt.pix.sizeimage; + + if (vb2_plane_size(vb, 0) < size) { + dev_err(dcmi->dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, 0, size); + + if (!buf->prepared) { + /* Get memory addresses */ + buf->size = vb2_plane_size(&buf->vb.vb2_buf, 0); + if (buf->size > dcmi->dma_max_burst) + num_sgs = DIV_ROUND_UP(buf->size, dcmi->dma_max_burst); + + ret = sg_alloc_table(&buf->sgt, num_sgs, GFP_ATOMIC); + if (ret) { + dev_err(dcmi->dev, "sg table alloc failed\n"); + return ret; + } + + dma_buf = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + + dev_dbg(dcmi->dev, "buffer[%d] phy=%pad size=%zu\n", + vb->index, &dma_buf, buf->size); + + for_each_sg(buf->sgt.sgl, sg, num_sgs, i) { + size_t bytes = min_t(size_t, size, dcmi->dma_max_burst); + + sg_dma_address(sg) = dma_buf; + sg_dma_len(sg) = bytes; + dma_buf += bytes; + size -= bytes; + } + + buf->prepared = true; + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, buf->size); + } + + return 0; +} + +static void dcmi_buf_queue(struct vb2_buffer *vb) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct dcmi_buf *buf = container_of(vbuf, struct dcmi_buf, vb); + + spin_lock_irq(&dcmi->irqlock); + + /* Enqueue to video buffers list */ + list_add_tail(&buf->list, &dcmi->buffers); + + if (dcmi->state == WAIT_FOR_BUFFER) { + dcmi->state = RUNNING; + dcmi->active = buf; + + dev_dbg(dcmi->dev, "Starting capture on buffer[%d] queued\n", + buf->vb.vb2_buf.index); + + spin_unlock_irq(&dcmi->irqlock); + if (dcmi_start_capture(dcmi, buf)) + dev_err(dcmi->dev, "%s: Cannot restart capture on overflow or error\n", + __func__); + return; + } + + spin_unlock_irq(&dcmi->irqlock); +} + +static struct media_entity *dcmi_find_source(struct stm32_dcmi *dcmi) +{ + struct media_entity *entity = &dcmi->vdev->entity; + struct media_pad *pad; + + /* Walk searching for entity having no sink */ + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + } + + return entity; +} + +static int dcmi_pipeline_s_fmt(struct stm32_dcmi *dcmi, + struct v4l2_subdev_format *format) +{ + struct media_entity *entity = &dcmi->source->entity; + struct v4l2_subdev *subdev; + struct media_pad *sink_pad = NULL; + struct media_pad *src_pad = NULL; + struct media_pad *pad = NULL; + struct v4l2_subdev_format fmt = *format; + bool found = false; + int ret; + + /* + * Starting from sensor subdevice, walk within + * pipeline and set format on each subdevice + */ + while (1) { + unsigned int i; + + /* Search if current entity has a source pad */ + for (i = 0; i < entity->num_pads; i++) { + pad = &entity->pads[i]; + if (pad->flags & MEDIA_PAD_FL_SOURCE) { + src_pad = pad; + found = true; + break; + } + } + if (!found) + break; + + subdev = media_entity_to_v4l2_subdev(entity); + + /* Propagate format on sink pad if any, otherwise source pad */ + if (sink_pad) + pad = sink_pad; + + dev_dbg(dcmi->dev, "\"%s\":%d pad format set to 0x%x %ux%u\n", + subdev->name, pad->index, format->format.code, + format->format.width, format->format.height); + + fmt.pad = pad->index; + ret = v4l2_subdev_call(subdev, pad, set_fmt, NULL, &fmt); + if (ret < 0) { + dev_err(dcmi->dev, "%s: Failed to set format 0x%x %ux%u on \"%s\":%d pad (%d)\n", + __func__, format->format.code, + format->format.width, format->format.height, + subdev->name, pad->index, ret); + return ret; + } + + if (fmt.format.code != format->format.code || + fmt.format.width != format->format.width || + fmt.format.height != format->format.height) { + dev_dbg(dcmi->dev, "\"%s\":%d pad format has been changed to 0x%x %ux%u\n", + subdev->name, pad->index, fmt.format.code, + fmt.format.width, fmt.format.height); + } + + /* Walk to next entity */ + sink_pad = media_pad_remote_pad_first(src_pad); + if (!sink_pad || !is_media_entity_v4l2_subdev(sink_pad->entity)) + break; + + entity = sink_pad->entity; + } + *format = fmt; + + return 0; +} + +static int dcmi_pipeline_s_stream(struct stm32_dcmi *dcmi, int state) +{ + struct media_entity *entity = &dcmi->vdev->entity; + struct v4l2_subdev *subdev; + struct media_pad *pad; + int ret; + + /* Start/stop all entities within pipeline */ + while (1) { + pad = &entity->pads[0]; + if (!(pad->flags & MEDIA_PAD_FL_SINK)) + break; + + pad = media_pad_remote_pad_first(pad); + if (!pad || !is_media_entity_v4l2_subdev(pad->entity)) + break; + + entity = pad->entity; + subdev = media_entity_to_v4l2_subdev(entity); + + ret = v4l2_subdev_call(subdev, video, s_stream, state); + if (ret < 0 && ret != -ENOIOCTLCMD) { + dev_err(dcmi->dev, "%s: \"%s\" failed to %s streaming (%d)\n", + __func__, subdev->name, + state ? "start" : "stop", ret); + return ret; + } + + dev_dbg(dcmi->dev, "\"%s\" is %s\n", + subdev->name, state ? "started" : "stopped"); + } + + return 0; +} + +static int dcmi_pipeline_start(struct stm32_dcmi *dcmi) +{ + return dcmi_pipeline_s_stream(dcmi, 1); +} + +static void dcmi_pipeline_stop(struct stm32_dcmi *dcmi) +{ + dcmi_pipeline_s_stream(dcmi, 0); +} + +static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + struct dcmi_buf *buf, *node; + u32 val = 0; + int ret; + + ret = pm_runtime_resume_and_get(dcmi->dev); + if (ret < 0) { + dev_err(dcmi->dev, "%s: Failed to start streaming, cannot get sync (%d)\n", + __func__, ret); + goto err_unlocked; + } + + ret = video_device_pipeline_start(dcmi->vdev, &dcmi->pipeline); + if (ret < 0) { + dev_err(dcmi->dev, "%s: Failed to start streaming, media pipeline start error (%d)\n", + __func__, ret); + goto err_pm_put; + } + + ret = dcmi_pipeline_start(dcmi); + if (ret) + goto err_media_pipeline_stop; + + spin_lock_irq(&dcmi->irqlock); + + /* Set bus width */ + switch (dcmi->bus.bus_width) { + case 14: + val |= CR_EDM_0 | CR_EDM_1; + break; + case 12: + val |= CR_EDM_1; + break; + case 10: + val |= CR_EDM_0; + break; + default: + /* Set bus width to 8 bits by default */ + break; + } + + /* Set vertical synchronization polarity */ + if (dcmi->bus.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + val |= CR_VSPOL; + + /* Set horizontal synchronization polarity */ + if (dcmi->bus.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + val |= CR_HSPOL; + + /* Set pixel clock polarity */ + if (dcmi->bus.flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + val |= CR_PCKPOL; + + /* + * BT656 embedded synchronisation bus mode. + * + * Default SAV/EAV mode is supported here with default codes + * SAV=0xff000080 & EAV=0xff00009d. + * With DCMI this means LSC=SAV=0x80 & LEC=EAV=0x9d. + */ + if (dcmi->bus_type == V4L2_MBUS_BT656) { + val |= CR_ESS; + + /* Unmask all codes */ + reg_write(dcmi->regs, DCMI_ESUR, 0xffffffff);/* FEC:LEC:LSC:FSC */ + + /* Trig on LSC=0x80 & LEC=0x9d codes, ignore FSC and FEC */ + reg_write(dcmi->regs, DCMI_ESCR, 0xff9d80ff);/* FEC:LEC:LSC:FSC */ + } + + reg_write(dcmi->regs, DCMI_CR, val); + + /* Set crop */ + if (dcmi->do_crop) + dcmi_set_crop(dcmi); + + /* Enable jpeg capture */ + if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG) + reg_set(dcmi->regs, DCMI_CR, CR_CM);/* Snapshot mode */ + + /* Enable dcmi */ + reg_set(dcmi->regs, DCMI_CR, CR_ENABLE); + + dcmi->sequence = 0; + dcmi->errors_count = 0; + dcmi->overrun_count = 0; + dcmi->buffers_count = 0; + + /* + * Start transfer if at least one buffer has been queued, + * otherwise transfer is deferred at buffer queueing + */ + if (list_empty(&dcmi->buffers)) { + dev_dbg(dcmi->dev, "Start streaming is deferred to next buffer queueing\n"); + dcmi->state = WAIT_FOR_BUFFER; + spin_unlock_irq(&dcmi->irqlock); + return 0; + } + + buf = list_entry(dcmi->buffers.next, struct dcmi_buf, list); + dcmi->active = buf; + + dcmi->state = RUNNING; + + dev_dbg(dcmi->dev, "Start streaming, starting capture\n"); + + spin_unlock_irq(&dcmi->irqlock); + ret = dcmi_start_capture(dcmi, buf); + if (ret) { + dev_err(dcmi->dev, "%s: Start streaming failed, cannot start capture\n", + __func__); + goto err_pipeline_stop; + } + + /* Enable interruptions */ + if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG) + reg_set(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); + else + reg_set(dcmi->regs, DCMI_IER, IT_OVR | IT_ERR); + + return 0; + +err_pipeline_stop: + dcmi_pipeline_stop(dcmi); + +err_media_pipeline_stop: + video_device_pipeline_stop(dcmi->vdev); + +err_pm_put: + pm_runtime_put(dcmi->dev); +err_unlocked: + spin_lock_irq(&dcmi->irqlock); + /* + * Return all buffers to vb2 in QUEUED state. + * This will give ownership back to userspace + */ + list_for_each_entry_safe(buf, node, &dcmi->buffers, list) { + list_del_init(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + dcmi->active = NULL; + spin_unlock_irq(&dcmi->irqlock); + + return ret; +} + +static void dcmi_stop_streaming(struct vb2_queue *vq) +{ + struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq); + struct dcmi_buf *buf, *node; + + dcmi_pipeline_stop(dcmi); + + video_device_pipeline_stop(dcmi->vdev); + + spin_lock_irq(&dcmi->irqlock); + + /* Disable interruptions */ + reg_clear(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR); + + /* Disable DCMI */ + reg_clear(dcmi->regs, DCMI_CR, CR_ENABLE); + + /* Return all queued buffers to vb2 in ERROR state */ + list_for_each_entry_safe(buf, node, &dcmi->buffers, list) { + list_del_init(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + dcmi->active = NULL; + dcmi->state = STOPPED; + + spin_unlock_irq(&dcmi->irqlock); + + /* Stop all pending DMA operations */ + mutex_lock(&dcmi->dma_lock); + dmaengine_terminate_sync(dcmi->dma_chan); + mutex_unlock(&dcmi->dma_lock); + + pm_runtime_put(dcmi->dev); + + if (dcmi->errors_count) + dev_warn(dcmi->dev, "Some errors found while streaming: errors=%d (overrun=%d), buffers=%d\n", + dcmi->errors_count, dcmi->overrun_count, + dcmi->buffers_count); + dev_dbg(dcmi->dev, "Stop streaming, errors=%d (overrun=%d), buffers=%d\n", + dcmi->errors_count, dcmi->overrun_count, + dcmi->buffers_count); +} + +static const struct vb2_ops dcmi_video_qops = { + .queue_setup = dcmi_queue_setup, + .buf_init = dcmi_buf_init, + .buf_prepare = dcmi_buf_prepare, + .buf_queue = dcmi_buf_queue, + .start_streaming = dcmi_start_streaming, + .stop_streaming = dcmi_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int dcmi_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + *fmt = dcmi->fmt; + + return 0; +} + +static const struct dcmi_format *find_format_by_fourcc(struct stm32_dcmi *dcmi, + unsigned int fourcc) +{ + unsigned int num_formats = dcmi->num_of_sd_formats; + const struct dcmi_format *fmt; + unsigned int i; + + for (i = 0; i < num_formats; i++) { + fmt = dcmi->sd_formats[i]; + if (fmt->fourcc == fourcc) + return fmt; + } + + return NULL; +} + +static void __find_outer_frame_size(struct stm32_dcmi *dcmi, + struct v4l2_pix_format *pix, + struct dcmi_framesize *framesize) +{ + struct dcmi_framesize *match = NULL; + unsigned int i; + unsigned int min_err = UINT_MAX; + + for (i = 0; i < dcmi->num_of_sd_framesizes; i++) { + struct dcmi_framesize *fsize = &dcmi->sd_framesizes[i]; + int w_err = (fsize->width - pix->width); + int h_err = (fsize->height - pix->height); + int err = w_err + h_err; + + if (w_err >= 0 && h_err >= 0 && err < min_err) { + min_err = err; + match = fsize; + } + } + if (!match) + match = &dcmi->sd_framesizes[0]; + + *framesize = *match; +} + +static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f, + const struct dcmi_format **sd_format, + struct dcmi_framesize *sd_framesize) +{ + const struct dcmi_format *sd_fmt; + struct dcmi_framesize sd_fsize; + struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + bool do_crop; + int ret; + + sd_fmt = find_format_by_fourcc(dcmi, pix->pixelformat); + if (!sd_fmt) { + if (!dcmi->num_of_sd_formats) + return -ENODATA; + + sd_fmt = dcmi->sd_formats[dcmi->num_of_sd_formats - 1]; + pix->pixelformat = sd_fmt->fourcc; + } + + /* Limit to hardware capabilities */ + pix->width = clamp(pix->width, MIN_WIDTH, MAX_WIDTH); + pix->height = clamp(pix->height, MIN_HEIGHT, MAX_HEIGHT); + + /* No crop if JPEG is requested */ + do_crop = dcmi->do_crop && (pix->pixelformat != V4L2_PIX_FMT_JPEG); + + if (do_crop && dcmi->num_of_sd_framesizes) { + struct dcmi_framesize outer_sd_fsize; + /* + * If crop is requested and sensor have discrete frame sizes, + * select the frame size that is just larger than request + */ + __find_outer_frame_size(dcmi, pix, &outer_sd_fsize); + pix->width = outer_sd_fsize.width; + pix->height = outer_sd_fsize.height; + } + + v4l2_fill_mbus_format(&format.format, pix, sd_fmt->mbus_code); + ret = v4l2_subdev_call_state_try(dcmi->source, pad, set_fmt, &format); + if (ret < 0) + return ret; + + /* Update pix regarding to what sensor can do */ + v4l2_fill_pix_format(pix, &format.format); + + /* Save resolution that sensor can actually do */ + sd_fsize.width = pix->width; + sd_fsize.height = pix->height; + + if (do_crop) { + struct v4l2_rect c = dcmi->crop; + struct v4l2_rect max_rect; + + /* + * Adjust crop by making the intersection between + * format resolution request and crop request + */ + max_rect.top = 0; + max_rect.left = 0; + max_rect.width = pix->width; + max_rect.height = pix->height; + v4l2_rect_map_inside(&c, &max_rect); + c.top = clamp_t(s32, c.top, 0, pix->height - c.height); + c.left = clamp_t(s32, c.left, 0, pix->width - c.width); + dcmi->crop = c; + + /* Adjust format resolution request to crop */ + pix->width = dcmi->crop.width; + pix->height = dcmi->crop.height; + } + + pix->field = V4L2_FIELD_NONE; + pix->bytesperline = pix->width * sd_fmt->bpp; + pix->sizeimage = pix->bytesperline * pix->height; + + if (sd_format) + *sd_format = sd_fmt; + if (sd_framesize) + *sd_framesize = sd_fsize; + + return 0; +} + +static int dcmi_set_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f) +{ + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + const struct dcmi_format *sd_format; + struct dcmi_framesize sd_framesize; + struct v4l2_mbus_framefmt *mf = &format.format; + struct v4l2_pix_format *pix = &f->fmt.pix; + int ret; + + /* + * Try format, fmt.width/height could have been changed + * to match sensor capability or crop request + * sd_format & sd_framesize will contain what subdev + * can do for this request. + */ + ret = dcmi_try_fmt(dcmi, f, &sd_format, &sd_framesize); + if (ret) + return ret; + + /* Disable crop if JPEG is requested or BT656 bus is selected */ + if (pix->pixelformat == V4L2_PIX_FMT_JPEG && + dcmi->bus_type != V4L2_MBUS_BT656) + dcmi->do_crop = false; + + /* pix to mbus format */ + v4l2_fill_mbus_format(mf, pix, + sd_format->mbus_code); + mf->width = sd_framesize.width; + mf->height = sd_framesize.height; + + ret = dcmi_pipeline_s_fmt(dcmi, &format); + if (ret < 0) + return ret; + + dev_dbg(dcmi->dev, "Sensor format set to 0x%x %ux%u\n", + mf->code, mf->width, mf->height); + dev_dbg(dcmi->dev, "Buffer format set to %4.4s %ux%u\n", + (char *)&pix->pixelformat, + pix->width, pix->height); + + dcmi->fmt = *f; + dcmi->sd_format = sd_format; + dcmi->sd_framesize = sd_framesize; + + return 0; +} + +static int dcmi_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + if (vb2_is_streaming(&dcmi->queue)) + return -EBUSY; + + return dcmi_set_fmt(dcmi, f); +} + +static int dcmi_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + return dcmi_try_fmt(dcmi, f, NULL, NULL); +} + +static int dcmi_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + if (f->index >= dcmi->num_of_sd_formats) + return -EINVAL; + + f->pixelformat = dcmi->sd_formats[f->index]->fourcc; + return 0; +} + +static int dcmi_get_sensor_format(struct stm32_dcmi *dcmi, + struct v4l2_pix_format *pix) +{ + struct v4l2_subdev_format fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + ret = v4l2_subdev_call(dcmi->source, pad, get_fmt, NULL, &fmt); + if (ret) + return ret; + + v4l2_fill_pix_format(pix, &fmt.format); + + return 0; +} + +static int dcmi_set_sensor_format(struct stm32_dcmi *dcmi, + struct v4l2_pix_format *pix) +{ + const struct dcmi_format *sd_fmt; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_TRY, + }; + int ret; + + sd_fmt = find_format_by_fourcc(dcmi, pix->pixelformat); + if (!sd_fmt) { + if (!dcmi->num_of_sd_formats) + return -ENODATA; + + sd_fmt = dcmi->sd_formats[dcmi->num_of_sd_formats - 1]; + pix->pixelformat = sd_fmt->fourcc; + } + + v4l2_fill_mbus_format(&format.format, pix, sd_fmt->mbus_code); + ret = v4l2_subdev_call_state_try(dcmi->source, pad, set_fmt, &format); + if (ret < 0) + return ret; + + return 0; +} + +static int dcmi_get_sensor_bounds(struct stm32_dcmi *dcmi, + struct v4l2_rect *r) +{ + struct v4l2_subdev_selection bounds = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .target = V4L2_SEL_TGT_CROP_BOUNDS, + }; + unsigned int max_width, max_height, max_pixsize; + struct v4l2_pix_format pix; + unsigned int i; + int ret; + + /* + * Get sensor bounds first + */ + ret = v4l2_subdev_call(dcmi->source, pad, get_selection, + NULL, &bounds); + if (!ret) + *r = bounds.r; + if (ret != -ENOIOCTLCMD) + return ret; + + /* + * If selection is not implemented, + * fallback by enumerating sensor frame sizes + * and take the largest one + */ + max_width = 0; + max_height = 0; + max_pixsize = 0; + for (i = 0; i < dcmi->num_of_sd_framesizes; i++) { + struct dcmi_framesize *fsize = &dcmi->sd_framesizes[i]; + unsigned int pixsize = fsize->width * fsize->height; + + if (pixsize > max_pixsize) { + max_pixsize = pixsize; + max_width = fsize->width; + max_height = fsize->height; + } + } + if (max_pixsize > 0) { + r->top = 0; + r->left = 0; + r->width = max_width; + r->height = max_height; + return 0; + } + + /* + * If frame sizes enumeration is not implemented, + * fallback by getting current sensor frame size + */ + ret = dcmi_get_sensor_format(dcmi, &pix); + if (ret) + return ret; + + r->top = 0; + r->left = 0; + r->width = pix.width; + r->height = pix.height; + + return 0; +} + +static int dcmi_g_selection(struct file *file, void *fh, + struct v4l2_selection *s) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + s->r = dcmi->sd_bounds; + return 0; + case V4L2_SEL_TGT_CROP: + if (dcmi->do_crop) { + s->r = dcmi->crop; + } else { + s->r.top = 0; + s->r.left = 0; + s->r.width = dcmi->fmt.fmt.pix.width; + s->r.height = dcmi->fmt.fmt.pix.height; + } + break; + default: + return -EINVAL; + } + + return 0; +} + +static int dcmi_s_selection(struct file *file, void *priv, + struct v4l2_selection *s) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + struct v4l2_rect r = s->r; + struct v4l2_rect max_rect; + struct v4l2_pix_format pix; + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + /* Reset sensor resolution to max resolution */ + pix.pixelformat = dcmi->fmt.fmt.pix.pixelformat; + pix.width = dcmi->sd_bounds.width; + pix.height = dcmi->sd_bounds.height; + dcmi_set_sensor_format(dcmi, &pix); + + /* + * Make the intersection between + * sensor resolution + * and crop request + */ + max_rect.top = 0; + max_rect.left = 0; + max_rect.width = pix.width; + max_rect.height = pix.height; + v4l2_rect_map_inside(&r, &max_rect); + r.top = clamp_t(s32, r.top, 0, pix.height - r.height); + r.left = clamp_t(s32, r.left, 0, pix.width - r.width); + + if (!(r.top == dcmi->sd_bounds.top && + r.left == dcmi->sd_bounds.left && + r.width == dcmi->sd_bounds.width && + r.height == dcmi->sd_bounds.height)) { + /* Crop if request is different than sensor resolution */ + dcmi->do_crop = true; + dcmi->crop = r; + dev_dbg(dcmi->dev, "s_selection: crop %ux%u@(%u,%u) from %ux%u\n", + r.width, r.height, r.left, r.top, + pix.width, pix.height); + } else { + /* Disable crop */ + dcmi->do_crop = false; + dev_dbg(dcmi->dev, "s_selection: crop is disabled\n"); + } + + s->r = r; + return 0; +} + +static int dcmi_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DRV_NAME, sizeof(cap->driver)); + strscpy(cap->card, "STM32 Camera Memory Interface", + sizeof(cap->card)); + strscpy(cap->bus_info, "platform:dcmi", sizeof(cap->bus_info)); + return 0; +} + +static int dcmi_enum_input(struct file *file, void *priv, + struct v4l2_input *i) +{ + if (i->index != 0) + return -EINVAL; + + i->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(i->name, "Camera", sizeof(i->name)); + return 0; +} + +static int dcmi_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int dcmi_s_input(struct file *file, void *priv, unsigned int i) +{ + if (i > 0) + return -EINVAL; + return 0; +} + +static int dcmi_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + const struct dcmi_format *sd_fmt; + struct v4l2_subdev_frame_size_enum fse = { + .index = fsize->index, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + sd_fmt = find_format_by_fourcc(dcmi, fsize->pixel_format); + if (!sd_fmt) + return -EINVAL; + + fse.code = sd_fmt->mbus_code; + + ret = v4l2_subdev_call(dcmi->source, pad, enum_frame_size, + NULL, &fse); + if (ret) + return ret; + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.max_width; + fsize->discrete.height = fse.max_height; + + return 0; +} + +static int dcmi_g_parm(struct file *file, void *priv, + struct v4l2_streamparm *p) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + return v4l2_g_parm_cap(video_devdata(file), dcmi->source, p); +} + +static int dcmi_s_parm(struct file *file, void *priv, + struct v4l2_streamparm *p) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + + return v4l2_s_parm_cap(video_devdata(file), dcmi->source, p); +} + +static int dcmi_enum_frameintervals(struct file *file, void *fh, + struct v4l2_frmivalenum *fival) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + const struct dcmi_format *sd_fmt; + struct v4l2_subdev_frame_interval_enum fie = { + .index = fival->index, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + sd_fmt = find_format_by_fourcc(dcmi, fival->pixel_format); + if (!sd_fmt) + return -EINVAL; + + fie.code = sd_fmt->mbus_code; + + ret = v4l2_subdev_call(dcmi->source, pad, + enum_frame_interval, NULL, &fie); + if (ret) + return ret; + + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + + return 0; +} + +static const struct of_device_id stm32_dcmi_of_match[] = { + { .compatible = "st,stm32-dcmi"}, + { /* end node */ }, +}; +MODULE_DEVICE_TABLE(of, stm32_dcmi_of_match); + +static int dcmi_open(struct file *file) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + struct v4l2_subdev *sd = dcmi->source; + int ret; + + if (mutex_lock_interruptible(&dcmi->lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto unlock; + + if (!v4l2_fh_is_singular_file(file)) + goto fh_rel; + + ret = v4l2_subdev_call(sd, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto fh_rel; + + ret = dcmi_set_fmt(dcmi, &dcmi->fmt); + if (ret) + v4l2_subdev_call(sd, core, s_power, 0); +fh_rel: + if (ret) + v4l2_fh_release(file); +unlock: + mutex_unlock(&dcmi->lock); + return ret; +} + +static int dcmi_release(struct file *file) +{ + struct stm32_dcmi *dcmi = video_drvdata(file); + struct v4l2_subdev *sd = dcmi->source; + bool fh_singular; + int ret; + + mutex_lock(&dcmi->lock); + + fh_singular = v4l2_fh_is_singular_file(file); + + ret = _vb2_fop_release(file, NULL); + + if (fh_singular) + v4l2_subdev_call(sd, core, s_power, 0); + + mutex_unlock(&dcmi->lock); + + return ret; +} + +static const struct v4l2_ioctl_ops dcmi_ioctl_ops = { + .vidioc_querycap = dcmi_querycap, + + .vidioc_try_fmt_vid_cap = dcmi_try_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = dcmi_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = dcmi_s_fmt_vid_cap, + .vidioc_enum_fmt_vid_cap = dcmi_enum_fmt_vid_cap, + .vidioc_g_selection = dcmi_g_selection, + .vidioc_s_selection = dcmi_s_selection, + + .vidioc_enum_input = dcmi_enum_input, + .vidioc_g_input = dcmi_g_input, + .vidioc_s_input = dcmi_s_input, + + .vidioc_g_parm = dcmi_g_parm, + .vidioc_s_parm = dcmi_s_parm, + + .vidioc_enum_framesizes = dcmi_enum_framesizes, + .vidioc_enum_frameintervals = dcmi_enum_frameintervals, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations dcmi_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = dcmi_open, + .release = dcmi_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +#ifndef CONFIG_MMU + .get_unmapped_area = vb2_fop_get_unmapped_area, +#endif + .read = vb2_fop_read, +}; + +static int dcmi_set_default_fmt(struct stm32_dcmi *dcmi) +{ + struct v4l2_format f = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = CIF_WIDTH, + .height = CIF_HEIGHT, + .field = V4L2_FIELD_NONE, + .pixelformat = dcmi->sd_formats[0]->fourcc, + }, + }; + int ret; + + ret = dcmi_try_fmt(dcmi, &f, NULL, NULL); + if (ret) + return ret; + dcmi->sd_format = dcmi->sd_formats[0]; + dcmi->fmt = f; + return 0; +} + +static const struct dcmi_format dcmi_formats[] = { + { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_1X16, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_JPEG, + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + .bpp = 1, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .bpp = 1, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .bpp = 1, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .bpp = 1, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .bpp = 1, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR14, + .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG14, + .mbus_code = MEDIA_BUS_FMT_SGBRG14_1X14, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG14, + .mbus_code = MEDIA_BUS_FMT_SGRBG14_1X14, + .bpp = 2, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB14, + .mbus_code = MEDIA_BUS_FMT_SRGGB14_1X14, + .bpp = 2, + }, +}; + +static int dcmi_formats_init(struct stm32_dcmi *dcmi) +{ + const struct dcmi_format *sd_fmts[ARRAY_SIZE(dcmi_formats)]; + unsigned int num_fmts = 0, i, j; + struct v4l2_subdev *subdev = dcmi->source; + struct v4l2_subdev_mbus_code_enum mbus_code = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + while (!v4l2_subdev_call(subdev, pad, enum_mbus_code, + NULL, &mbus_code)) { + for (i = 0; i < ARRAY_SIZE(dcmi_formats); i++) { + if (dcmi_formats[i].mbus_code != mbus_code.code) + continue; + + /* Exclude JPEG if BT656 bus is selected */ + if (dcmi_formats[i].fourcc == V4L2_PIX_FMT_JPEG && + dcmi->bus_type == V4L2_MBUS_BT656) + continue; + + /* Code supported, have we got this fourcc yet? */ + for (j = 0; j < num_fmts; j++) + if (sd_fmts[j]->fourcc == + dcmi_formats[i].fourcc) { + /* Already available */ + dev_dbg(dcmi->dev, "Skipping fourcc/code: %4.4s/0x%x\n", + (char *)&sd_fmts[j]->fourcc, + mbus_code.code); + break; + } + if (j == num_fmts) { + /* New */ + sd_fmts[num_fmts++] = dcmi_formats + i; + dev_dbg(dcmi->dev, "Supported fourcc/code: %4.4s/0x%x\n", + (char *)&sd_fmts[num_fmts - 1]->fourcc, + sd_fmts[num_fmts - 1]->mbus_code); + } + } + mbus_code.index++; + } + + if (!num_fmts) + return -ENXIO; + + dcmi->num_of_sd_formats = num_fmts; + dcmi->sd_formats = devm_kcalloc(dcmi->dev, + num_fmts, sizeof(struct dcmi_format *), + GFP_KERNEL); + if (!dcmi->sd_formats) { + dev_err(dcmi->dev, "Could not allocate memory\n"); + return -ENOMEM; + } + + memcpy(dcmi->sd_formats, sd_fmts, + num_fmts * sizeof(struct dcmi_format *)); + dcmi->sd_format = dcmi->sd_formats[0]; + + return 0; +} + +static int dcmi_framesizes_init(struct stm32_dcmi *dcmi) +{ + unsigned int num_fsize = 0; + struct v4l2_subdev *subdev = dcmi->source; + struct v4l2_subdev_frame_size_enum fse = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .code = dcmi->sd_format->mbus_code, + }; + unsigned int ret; + unsigned int i; + + /* Allocate discrete framesizes array */ + while (!v4l2_subdev_call(subdev, pad, enum_frame_size, + NULL, &fse)) + fse.index++; + + num_fsize = fse.index; + if (!num_fsize) + return 0; + + dcmi->num_of_sd_framesizes = num_fsize; + dcmi->sd_framesizes = devm_kcalloc(dcmi->dev, num_fsize, + sizeof(struct dcmi_framesize), + GFP_KERNEL); + if (!dcmi->sd_framesizes) { + dev_err(dcmi->dev, "Could not allocate memory\n"); + return -ENOMEM; + } + + /* Fill array with sensor supported framesizes */ + dev_dbg(dcmi->dev, "Sensor supports %u frame sizes:\n", num_fsize); + for (i = 0; i < dcmi->num_of_sd_framesizes; i++) { + fse.index = i; + ret = v4l2_subdev_call(subdev, pad, enum_frame_size, + NULL, &fse); + if (ret) + return ret; + dcmi->sd_framesizes[fse.index].width = fse.max_width; + dcmi->sd_framesizes[fse.index].height = fse.max_height; + dev_dbg(dcmi->dev, "%ux%u\n", fse.max_width, fse.max_height); + } + + return 0; +} + +static int dcmi_graph_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + int ret; + + /* + * Now that the graph is complete, + * we search for the source subdevice + * in order to expose it through V4L2 interface + */ + dcmi->source = media_entity_to_v4l2_subdev(dcmi_find_source(dcmi)); + if (!dcmi->source) { + dev_err(dcmi->dev, "Source subdevice not found\n"); + return -ENODEV; + } + + dcmi->vdev->ctrl_handler = dcmi->source->ctrl_handler; + + ret = dcmi_formats_init(dcmi); + if (ret) { + dev_err(dcmi->dev, "No supported mediabus format found\n"); + return ret; + } + + ret = dcmi_framesizes_init(dcmi); + if (ret) { + dev_err(dcmi->dev, "Could not initialize framesizes\n"); + return ret; + } + + ret = dcmi_get_sensor_bounds(dcmi, &dcmi->sd_bounds); + if (ret) { + dev_err(dcmi->dev, "Could not get sensor bounds\n"); + return ret; + } + + ret = dcmi_set_default_fmt(dcmi); + if (ret) { + dev_err(dcmi->dev, "Could not set default format\n"); + return ret; + } + + ret = devm_request_threaded_irq(dcmi->dev, dcmi->irq, dcmi_irq_callback, + dcmi_irq_thread, IRQF_ONESHOT, + dev_name(dcmi->dev), dcmi); + if (ret) { + dev_err(dcmi->dev, "Unable to request irq %d\n", dcmi->irq); + return ret; + } + + return 0; +} + +static void dcmi_graph_notify_unbind(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_subdev *asd) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + + dev_dbg(dcmi->dev, "Removing %s\n", video_device_node_name(dcmi->vdev)); + + /* Checks internally if vdev has been init or not */ + video_unregister_device(dcmi->vdev); +} + +static int dcmi_graph_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier); + unsigned int ret; + int src_pad; + + dev_dbg(dcmi->dev, "Subdev \"%s\" bound\n", subdev->name); + + /* + * Link this sub-device to DCMI, it could be + * a parallel camera sensor or a bridge + */ + src_pad = media_entity_get_fwnode_pad(&subdev->entity, + subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + + ret = media_create_pad_link(&subdev->entity, src_pad, + &dcmi->vdev->entity, 0, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) + dev_err(dcmi->dev, "Failed to create media pad link with subdev \"%s\"\n", + subdev->name); + else + dev_dbg(dcmi->dev, "DCMI is now linked to \"%s\"\n", + subdev->name); + + return ret; +} + +static const struct v4l2_async_notifier_operations dcmi_graph_notify_ops = { + .bound = dcmi_graph_notify_bound, + .unbind = dcmi_graph_notify_unbind, + .complete = dcmi_graph_notify_complete, +}; + +static int dcmi_graph_init(struct stm32_dcmi *dcmi) +{ + struct v4l2_async_subdev *asd; + struct device_node *ep; + int ret; + + ep = of_graph_get_next_endpoint(dcmi->dev->of_node, NULL); + if (!ep) { + dev_err(dcmi->dev, "Failed to get next endpoint\n"); + return -EINVAL; + } + + v4l2_async_nf_init(&dcmi->notifier); + + asd = v4l2_async_nf_add_fwnode_remote(&dcmi->notifier, + of_fwnode_handle(ep), + struct v4l2_async_subdev); + + of_node_put(ep); + + if (IS_ERR(asd)) { + dev_err(dcmi->dev, "Failed to add subdev notifier\n"); + return PTR_ERR(asd); + } + + dcmi->notifier.ops = &dcmi_graph_notify_ops; + + ret = v4l2_async_nf_register(&dcmi->v4l2_dev, &dcmi->notifier); + if (ret < 0) { + dev_err(dcmi->dev, "Failed to register notifier\n"); + v4l2_async_nf_cleanup(&dcmi->notifier); + return ret; + } + + return 0; +} + +static int dcmi_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + const struct of_device_id *match = NULL; + struct v4l2_fwnode_endpoint ep = { .bus_type = 0 }; + struct stm32_dcmi *dcmi; + struct vb2_queue *q; + struct dma_chan *chan; + struct dma_slave_caps caps; + struct clk *mclk; + int irq; + int ret = 0; + + match = of_match_device(of_match_ptr(stm32_dcmi_of_match), &pdev->dev); + if (!match) { + dev_err(&pdev->dev, "Could not find a match in devicetree\n"); + return -ENODEV; + } + + dcmi = devm_kzalloc(&pdev->dev, sizeof(struct stm32_dcmi), GFP_KERNEL); + if (!dcmi) + return -ENOMEM; + + dcmi->rstc = devm_reset_control_get_exclusive(&pdev->dev, NULL); + if (IS_ERR(dcmi->rstc)) { + if (PTR_ERR(dcmi->rstc) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Could not get reset control\n"); + + return PTR_ERR(dcmi->rstc); + } + + /* Get bus characteristics from devicetree */ + np = of_graph_get_next_endpoint(np, NULL); + if (!np) { + dev_err(&pdev->dev, "Could not find the endpoint\n"); + return -ENODEV; + } + + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(np), &ep); + of_node_put(np); + if (ret) { + dev_err(&pdev->dev, "Could not parse the endpoint\n"); + return ret; + } + + if (ep.bus_type == V4L2_MBUS_CSI2_DPHY) { + dev_err(&pdev->dev, "CSI bus not supported\n"); + return -ENODEV; + } + + if (ep.bus_type == V4L2_MBUS_BT656 && + ep.bus.parallel.bus_width != 8) { + dev_err(&pdev->dev, "BT656 bus conflicts with %u bits bus width (8 bits required)\n", + ep.bus.parallel.bus_width); + return -ENODEV; + } + + dcmi->bus.flags = ep.bus.parallel.flags; + dcmi->bus.bus_width = ep.bus.parallel.bus_width; + dcmi->bus.data_shift = ep.bus.parallel.data_shift; + dcmi->bus_type = ep.bus_type; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq ? irq : -ENXIO; + + dcmi->irq = irq; + + dcmi->res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!dcmi->res) { + dev_err(&pdev->dev, "Could not get resource\n"); + return -ENODEV; + } + + dcmi->regs = devm_ioremap_resource(&pdev->dev, dcmi->res); + if (IS_ERR(dcmi->regs)) { + dev_err(&pdev->dev, "Could not map registers\n"); + return PTR_ERR(dcmi->regs); + } + + mclk = devm_clk_get(&pdev->dev, "mclk"); + if (IS_ERR(mclk)) { + if (PTR_ERR(mclk) != -EPROBE_DEFER) + dev_err(&pdev->dev, "Unable to get mclk\n"); + return PTR_ERR(mclk); + } + + chan = dma_request_chan(&pdev->dev, "tx"); + if (IS_ERR(chan)) { + ret = PTR_ERR(chan); + if (ret != -EPROBE_DEFER) + dev_err(&pdev->dev, + "Failed to request DMA channel: %d\n", ret); + return ret; + } + + dcmi->dma_max_burst = UINT_MAX; + ret = dma_get_slave_caps(chan, &caps); + if (!ret && caps.max_sg_burst) + dcmi->dma_max_burst = caps.max_sg_burst * DMA_SLAVE_BUSWIDTH_4_BYTES; + + spin_lock_init(&dcmi->irqlock); + mutex_init(&dcmi->lock); + mutex_init(&dcmi->dma_lock); + init_completion(&dcmi->complete); + INIT_LIST_HEAD(&dcmi->buffers); + + dcmi->dev = &pdev->dev; + dcmi->mclk = mclk; + dcmi->state = STOPPED; + dcmi->dma_chan = chan; + + q = &dcmi->queue; + + dcmi->v4l2_dev.mdev = &dcmi->mdev; + + /* Initialize media device */ + strscpy(dcmi->mdev.model, DRV_NAME, sizeof(dcmi->mdev.model)); + dcmi->mdev.dev = &pdev->dev; + media_device_init(&dcmi->mdev); + + /* Initialize the top-level structure */ + ret = v4l2_device_register(&pdev->dev, &dcmi->v4l2_dev); + if (ret) + goto err_media_device_cleanup; + + dcmi->vdev = video_device_alloc(); + if (!dcmi->vdev) { + ret = -ENOMEM; + goto err_device_unregister; + } + + /* Video node */ + dcmi->vdev->fops = &dcmi_fops; + dcmi->vdev->v4l2_dev = &dcmi->v4l2_dev; + dcmi->vdev->queue = &dcmi->queue; + strscpy(dcmi->vdev->name, KBUILD_MODNAME, sizeof(dcmi->vdev->name)); + dcmi->vdev->release = video_device_release; + dcmi->vdev->ioctl_ops = &dcmi_ioctl_ops; + dcmi->vdev->lock = &dcmi->lock; + dcmi->vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + video_set_drvdata(dcmi->vdev, dcmi); + + /* Media entity pads */ + dcmi->vid_cap_pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&dcmi->vdev->entity, + 1, &dcmi->vid_cap_pad); + if (ret) { + dev_err(dcmi->dev, "Failed to init media entity pad\n"); + goto err_device_release; + } + dcmi->vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT; + + ret = video_register_device(dcmi->vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(dcmi->dev, "Failed to register video device\n"); + goto err_media_entity_cleanup; + } + + dev_dbg(dcmi->dev, "Device registered as %s\n", + video_device_node_name(dcmi->vdev)); + + /* Buffer queue */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF; + q->lock = &dcmi->lock; + q->drv_priv = dcmi; + q->buf_struct_size = sizeof(struct dcmi_buf); + q->ops = &dcmi_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 2; + q->dev = &pdev->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(&pdev->dev, "Failed to initialize vb2 queue\n"); + goto err_media_entity_cleanup; + } + + ret = dcmi_graph_init(dcmi); + if (ret < 0) + goto err_media_entity_cleanup; + + /* Reset device */ + ret = reset_control_assert(dcmi->rstc); + if (ret) { + dev_err(&pdev->dev, "Failed to assert the reset line\n"); + goto err_cleanup; + } + + usleep_range(3000, 5000); + + ret = reset_control_deassert(dcmi->rstc); + if (ret) { + dev_err(&pdev->dev, "Failed to deassert the reset line\n"); + goto err_cleanup; + } + + dev_info(&pdev->dev, "Probe done\n"); + + platform_set_drvdata(pdev, dcmi); + + pm_runtime_enable(&pdev->dev); + + return 0; + +err_cleanup: + v4l2_async_nf_cleanup(&dcmi->notifier); +err_media_entity_cleanup: + media_entity_cleanup(&dcmi->vdev->entity); +err_device_release: + video_device_release(dcmi->vdev); +err_device_unregister: + v4l2_device_unregister(&dcmi->v4l2_dev); +err_media_device_cleanup: + media_device_cleanup(&dcmi->mdev); + dma_release_channel(dcmi->dma_chan); + + return ret; +} + +static int dcmi_remove(struct platform_device *pdev) +{ + struct stm32_dcmi *dcmi = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + v4l2_async_nf_unregister(&dcmi->notifier); + v4l2_async_nf_cleanup(&dcmi->notifier); + media_entity_cleanup(&dcmi->vdev->entity); + v4l2_device_unregister(&dcmi->v4l2_dev); + media_device_cleanup(&dcmi->mdev); + + dma_release_channel(dcmi->dma_chan); + + return 0; +} + +static __maybe_unused int dcmi_runtime_suspend(struct device *dev) +{ + struct stm32_dcmi *dcmi = dev_get_drvdata(dev); + + clk_disable_unprepare(dcmi->mclk); + + return 0; +} + +static __maybe_unused int dcmi_runtime_resume(struct device *dev) +{ + struct stm32_dcmi *dcmi = dev_get_drvdata(dev); + int ret; + + ret = clk_prepare_enable(dcmi->mclk); + if (ret) + dev_err(dev, "%s: Failed to prepare_enable clock\n", __func__); + + return ret; +} + +static __maybe_unused int dcmi_suspend(struct device *dev) +{ + /* disable clock */ + pm_runtime_force_suspend(dev); + + /* change pinctrl state */ + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static __maybe_unused int dcmi_resume(struct device *dev) +{ + /* restore pinctl default state */ + pinctrl_pm_select_default_state(dev); + + /* clock enable */ + pm_runtime_force_resume(dev); + + return 0; +} + +static const struct dev_pm_ops dcmi_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(dcmi_suspend, dcmi_resume) + SET_RUNTIME_PM_OPS(dcmi_runtime_suspend, + dcmi_runtime_resume, NULL) +}; + +static struct platform_driver stm32_dcmi_driver = { + .probe = dcmi_probe, + .remove = dcmi_remove, + .driver = { + .name = DRV_NAME, + .of_match_table = of_match_ptr(stm32_dcmi_of_match), + .pm = &dcmi_pm_ops, + }, +}; + +module_platform_driver(stm32_dcmi_driver); + +MODULE_AUTHOR("Yannick Fertre "); +MODULE_AUTHOR("Hugues Fruchet "); +MODULE_DESCRIPTION("STMicroelectronics STM32 Digital Camera Memory Interface driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/sunxi/Kconfig b/drivers/media/platform/sunxi/Kconfig new file mode 100644 index 000000000..2dd15083a --- /dev/null +++ b/drivers/media/platform/sunxi/Kconfig @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 + +comment "Sunxi media platform drivers" + +source "drivers/media/platform/sunxi/sun4i-csi/Kconfig" +source "drivers/media/platform/sunxi/sun6i-csi/Kconfig" +source "drivers/media/platform/sunxi/sun6i-mipi-csi2/Kconfig" +source "drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Kconfig" +source "drivers/media/platform/sunxi/sun8i-di/Kconfig" +source "drivers/media/platform/sunxi/sun8i-rotate/Kconfig" diff --git a/drivers/media/platform/sunxi/Makefile b/drivers/media/platform/sunxi/Makefile new file mode 100644 index 000000000..9aa01cb01 --- /dev/null +++ b/drivers/media/platform/sunxi/Makefile @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 + +obj-y += sun4i-csi/ +obj-y += sun6i-csi/ +obj-y += sun6i-mipi-csi2/ +obj-y += sun8i-a83t-mipi-csi2/ +obj-y += sun8i-di/ +obj-y += sun8i-rotate/ diff --git a/drivers/media/platform/sunxi/sun4i-csi/Kconfig b/drivers/media/platform/sunxi/sun4i-csi/Kconfig new file mode 100644 index 000000000..60610c04d --- /dev/null +++ b/drivers/media/platform/sunxi/sun4i-csi/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +config VIDEO_SUN4I_CSI + tristate "Allwinner A10 CMOS Sensor Interface Support" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && COMMON_CLK && RESET_CONTROLLER && HAS_DMA + depends on ARCH_SUNXI || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + This is a V4L2 driver for the Allwinner A10 CSI + + To compile this driver as a module, choose M here: the module + will be called sun4i_csi. diff --git a/drivers/media/platform/sunxi/sun4i-csi/Makefile b/drivers/media/platform/sunxi/sun4i-csi/Makefile new file mode 100644 index 000000000..5062b006d --- /dev/null +++ b/drivers/media/platform/sunxi/sun4i-csi/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 + +sun4i-csi-y += sun4i_csi.o +sun4i-csi-y += sun4i_dma.o +sun4i-csi-y += sun4i_v4l2.o + +obj-$(CONFIG_VIDEO_SUN4I_CSI) += sun4i-csi.o diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c new file mode 100644 index 000000000..18e6c65f4 --- /dev/null +++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.c @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 NextThing Co + * Copyright (C) 2016-2019 Bootlin + * + * Author: Maxime Ripard + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "sun4i_csi.h" + +struct sun4i_csi_traits { + unsigned int channels; + unsigned int max_width; + bool has_isp; +}; + +static const struct media_entity_operations sun4i_csi_video_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +static int sun4i_csi_notify_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi, + notifier); + + csi->src_subdev = subdev; + csi->src_pad = media_entity_get_fwnode_pad(&subdev->entity, + subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (csi->src_pad < 0) { + dev_err(csi->dev, "Couldn't find output pad for subdev %s\n", + subdev->name); + return csi->src_pad; + } + + dev_dbg(csi->dev, "Bound %s pad: %d\n", subdev->name, csi->src_pad); + return 0; +} + +static int sun4i_csi_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct sun4i_csi *csi = container_of(notifier, struct sun4i_csi, + notifier); + struct v4l2_subdev *subdev = &csi->subdev; + struct video_device *vdev = &csi->vdev; + int ret; + + ret = v4l2_device_register_subdev(&csi->v4l, subdev); + if (ret < 0) + return ret; + + ret = sun4i_csi_v4l2_register(csi); + if (ret < 0) + return ret; + + ret = media_device_register(&csi->mdev); + if (ret) + return ret; + + /* Create link from subdev to main device */ + ret = media_create_pad_link(&subdev->entity, CSI_SUBDEV_SOURCE, + &vdev->entity, 0, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) + goto err_clean_media; + + ret = media_create_pad_link(&csi->src_subdev->entity, csi->src_pad, + &subdev->entity, CSI_SUBDEV_SINK, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) + goto err_clean_media; + + ret = v4l2_device_register_subdev_nodes(&csi->v4l); + if (ret < 0) + goto err_clean_media; + + return 0; + +err_clean_media: + media_device_unregister(&csi->mdev); + + return ret; +} + +static const struct v4l2_async_notifier_operations sun4i_csi_notify_ops = { + .bound = sun4i_csi_notify_bound, + .complete = sun4i_csi_notify_complete, +}; + +static int sun4i_csi_notifier_init(struct sun4i_csi *csi) +{ + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_PARALLEL, + }; + struct v4l2_async_subdev *asd; + struct fwnode_handle *ep; + int ret; + + v4l2_async_nf_init(&csi->notifier); + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(csi->dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + return -EINVAL; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto out; + + csi->bus = vep.bus.parallel; + + asd = v4l2_async_nf_add_fwnode_remote(&csi->notifier, ep, + struct v4l2_async_subdev); + if (IS_ERR(asd)) { + ret = PTR_ERR(asd); + goto out; + } + + csi->notifier.ops = &sun4i_csi_notify_ops; + +out: + fwnode_handle_put(ep); + return ret; +} + +static int sun4i_csi_probe(struct platform_device *pdev) +{ + struct v4l2_subdev *subdev; + struct video_device *vdev; + struct sun4i_csi *csi; + int ret; + int irq; + + csi = devm_kzalloc(&pdev->dev, sizeof(*csi), GFP_KERNEL); + if (!csi) + return -ENOMEM; + platform_set_drvdata(pdev, csi); + csi->dev = &pdev->dev; + subdev = &csi->subdev; + vdev = &csi->vdev; + + csi->traits = of_device_get_match_data(&pdev->dev); + if (!csi->traits) + return -EINVAL; + + csi->mdev.dev = csi->dev; + strscpy(csi->mdev.model, "Allwinner Video Capture Device", + sizeof(csi->mdev.model)); + csi->mdev.hw_revision = 0; + media_device_init(&csi->mdev); + csi->v4l.mdev = &csi->mdev; + + csi->regs = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(csi->regs)) + return PTR_ERR(csi->regs); + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + csi->bus_clk = devm_clk_get(&pdev->dev, "bus"); + if (IS_ERR(csi->bus_clk)) { + dev_err(&pdev->dev, "Couldn't get our bus clock\n"); + return PTR_ERR(csi->bus_clk); + } + + if (csi->traits->has_isp) { + csi->isp_clk = devm_clk_get(&pdev->dev, "isp"); + if (IS_ERR(csi->isp_clk)) { + dev_err(&pdev->dev, "Couldn't get our ISP clock\n"); + return PTR_ERR(csi->isp_clk); + } + } + + csi->ram_clk = devm_clk_get(&pdev->dev, "ram"); + if (IS_ERR(csi->ram_clk)) { + dev_err(&pdev->dev, "Couldn't get our ram clock\n"); + return PTR_ERR(csi->ram_clk); + } + + csi->rst = devm_reset_control_get(&pdev->dev, NULL); + if (IS_ERR(csi->rst)) { + dev_err(&pdev->dev, "Couldn't get our reset line\n"); + return PTR_ERR(csi->rst); + } + + /* Initialize subdev */ + v4l2_subdev_init(subdev, &sun4i_csi_subdev_ops); + subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + subdev->owner = THIS_MODULE; + snprintf(subdev->name, sizeof(subdev->name), "sun4i-csi-0"); + v4l2_set_subdevdata(subdev, csi); + + csi->subdev_pads[CSI_SUBDEV_SINK].flags = + MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + csi->subdev_pads[CSI_SUBDEV_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(&subdev->entity, CSI_SUBDEV_PADS, + csi->subdev_pads); + if (ret < 0) + return ret; + + csi->vdev_pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + vdev->entity.ops = &sun4i_csi_video_entity_ops; + ret = media_entity_pads_init(&vdev->entity, 1, &csi->vdev_pad); + if (ret < 0) + return ret; + + ret = sun4i_csi_dma_register(csi, irq); + if (ret) + goto err_clean_pad; + + ret = sun4i_csi_notifier_init(csi); + if (ret) + goto err_unregister_media; + + ret = v4l2_async_nf_register(&csi->v4l, &csi->notifier); + if (ret) { + dev_err(csi->dev, "Couldn't register our notifier.\n"); + goto err_unregister_media; + } + + pm_runtime_enable(&pdev->dev); + + return 0; + +err_unregister_media: + media_device_unregister(&csi->mdev); + sun4i_csi_dma_unregister(csi); + +err_clean_pad: + media_device_cleanup(&csi->mdev); + + return ret; +} + +static int sun4i_csi_remove(struct platform_device *pdev) +{ + struct sun4i_csi *csi = platform_get_drvdata(pdev); + + v4l2_async_nf_unregister(&csi->notifier); + v4l2_async_nf_cleanup(&csi->notifier); + vb2_video_unregister_device(&csi->vdev); + media_device_unregister(&csi->mdev); + sun4i_csi_dma_unregister(csi); + media_device_cleanup(&csi->mdev); + + return 0; +} + +static const struct sun4i_csi_traits sun4i_a10_csi1_traits = { + .channels = 1, + .max_width = 24, + .has_isp = false, +}; + +static const struct sun4i_csi_traits sun7i_a20_csi0_traits = { + .channels = 4, + .max_width = 16, + .has_isp = true, +}; + +static const struct of_device_id sun4i_csi_of_match[] = { + { .compatible = "allwinner,sun4i-a10-csi1", .data = &sun4i_a10_csi1_traits }, + { .compatible = "allwinner,sun7i-a20-csi0", .data = &sun7i_a20_csi0_traits }, + { /* Sentinel */ } +}; +MODULE_DEVICE_TABLE(of, sun4i_csi_of_match); + +static int __maybe_unused sun4i_csi_runtime_resume(struct device *dev) +{ + struct sun4i_csi *csi = dev_get_drvdata(dev); + + reset_control_deassert(csi->rst); + clk_prepare_enable(csi->bus_clk); + clk_prepare_enable(csi->ram_clk); + clk_set_rate(csi->isp_clk, 80000000); + clk_prepare_enable(csi->isp_clk); + + writel(1, csi->regs + CSI_EN_REG); + + return 0; +} + +static int __maybe_unused sun4i_csi_runtime_suspend(struct device *dev) +{ + struct sun4i_csi *csi = dev_get_drvdata(dev); + + clk_disable_unprepare(csi->isp_clk); + clk_disable_unprepare(csi->ram_clk); + clk_disable_unprepare(csi->bus_clk); + + reset_control_assert(csi->rst); + + return 0; +} + +static const struct dev_pm_ops sun4i_csi_pm_ops = { + SET_RUNTIME_PM_OPS(sun4i_csi_runtime_suspend, + sun4i_csi_runtime_resume, + NULL) +}; + +static struct platform_driver sun4i_csi_driver = { + .probe = sun4i_csi_probe, + .remove = sun4i_csi_remove, + .driver = { + .name = "sun4i-csi", + .of_match_table = sun4i_csi_of_match, + .pm = &sun4i_csi_pm_ops, + }, +}; +module_platform_driver(sun4i_csi_driver); + +MODULE_DESCRIPTION("Allwinner A10 Camera Sensor Interface driver"); +MODULE_AUTHOR("Maxime Ripard "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.h b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.h new file mode 100644 index 000000000..8eeed87bf --- /dev/null +++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_csi.h @@ -0,0 +1,161 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2016 NextThing Co + * Copyright (C) 2016-2019 Bootlin + * + * Author: Maxime Ripard + */ + +#ifndef _SUN4I_CSI_H_ +#define _SUN4I_CSI_H_ + +#include +#include +#include +#include +#include +#include + +#define CSI_EN_REG 0x00 + +#define CSI_CFG_REG 0x04 +#define CSI_CFG_INPUT_FMT(fmt) ((fmt) << 20) +#define CSI_CFG_OUTPUT_FMT(fmt) ((fmt) << 16) +#define CSI_CFG_YUV_DATA_SEQ(seq) ((seq) << 8) +#define CSI_CFG_VREF_POL(pol) ((pol) << 2) +#define CSI_CFG_HREF_POL(pol) ((pol) << 1) +#define CSI_CFG_PCLK_POL(pol) ((pol) << 0) + +#define CSI_CPT_CTRL_REG 0x08 +#define CSI_CPT_CTRL_VIDEO_START BIT(1) +#define CSI_CPT_CTRL_IMAGE_START BIT(0) + +#define CSI_BUF_ADDR_REG(fifo, buf) (0x10 + (0x8 * (fifo)) + (0x4 * (buf))) + +#define CSI_BUF_CTRL_REG 0x28 +#define CSI_BUF_CTRL_DBN BIT(2) +#define CSI_BUF_CTRL_DBS BIT(1) +#define CSI_BUF_CTRL_DBE BIT(0) + +#define CSI_INT_EN_REG 0x30 +#define CSI_INT_FRM_DONE BIT(1) +#define CSI_INT_CPT_DONE BIT(0) + +#define CSI_INT_STA_REG 0x34 + +#define CSI_WIN_CTRL_W_REG 0x40 +#define CSI_WIN_CTRL_W_ACTIVE(w) ((w) << 16) + +#define CSI_WIN_CTRL_H_REG 0x44 +#define CSI_WIN_CTRL_H_ACTIVE(h) ((h) << 16) + +#define CSI_BUF_LEN_REG 0x48 + +#define CSI_MAX_BUFFER 2 +#define CSI_MAX_HEIGHT 8192U +#define CSI_MAX_WIDTH 8192U + +enum csi_input { + CSI_INPUT_RAW = 0, + CSI_INPUT_BT656 = 2, + CSI_INPUT_YUV = 3, +}; + +enum csi_output_raw { + CSI_OUTPUT_RAW_PASSTHROUGH = 0, +}; + +enum csi_output_yuv { + CSI_OUTPUT_YUV_422_PLANAR = 0, + CSI_OUTPUT_YUV_420_PLANAR = 1, + CSI_OUTPUT_YUV_422_UV = 4, + CSI_OUTPUT_YUV_420_UV = 5, + CSI_OUTPUT_YUV_422_MACRO = 8, + CSI_OUTPUT_YUV_420_MACRO = 9, +}; + +enum csi_yuv_data_seq { + CSI_YUV_DATA_SEQ_YUYV = 0, + CSI_YUV_DATA_SEQ_YVYU = 1, + CSI_YUV_DATA_SEQ_UYVY = 2, + CSI_YUV_DATA_SEQ_VYUY = 3, +}; + +enum csi_subdev_pads { + CSI_SUBDEV_SINK, + CSI_SUBDEV_SOURCE, + + CSI_SUBDEV_PADS, +}; + +extern const struct v4l2_subdev_ops sun4i_csi_subdev_ops; + +struct sun4i_csi_format { + u32 mbus; + u32 fourcc; + enum csi_input input; + u32 output; + unsigned int num_planes; + u8 bpp[3]; + unsigned int hsub; + unsigned int vsub; +}; + +const struct sun4i_csi_format *sun4i_csi_find_format(const u32 *fourcc, + const u32 *mbus); + +struct sun4i_csi { + /* Device resources */ + struct device *dev; + + const struct sun4i_csi_traits *traits; + + void __iomem *regs; + struct clk *bus_clk; + struct clk *isp_clk; + struct clk *ram_clk; + struct reset_control *rst; + + struct vb2_v4l2_buffer *current_buf[CSI_MAX_BUFFER]; + + struct { + size_t size; + void *vaddr; + dma_addr_t paddr; + } scratch; + + struct v4l2_mbus_config_parallel bus; + + /* Main Device */ + struct v4l2_device v4l; + struct media_device mdev; + struct video_device vdev; + struct media_pad vdev_pad; + struct v4l2_pix_format_mplane fmt; + + /* Local subdev */ + struct v4l2_subdev subdev; + struct media_pad subdev_pads[CSI_SUBDEV_PADS]; + struct v4l2_mbus_framefmt subdev_fmt; + + /* V4L2 Async variables */ + struct v4l2_async_notifier notifier; + struct v4l2_subdev *src_subdev; + int src_pad; + + /* V4L2 variables */ + struct mutex lock; + + /* Videobuf2 */ + struct vb2_queue queue; + struct list_head buf_list; + spinlock_t qlock; + unsigned int sequence; +}; + +int sun4i_csi_dma_register(struct sun4i_csi *csi, int irq); +void sun4i_csi_dma_unregister(struct sun4i_csi *csi); + +int sun4i_csi_v4l2_register(struct sun4i_csi *csi); + +#endif /* _SUN4I_CSI_H_ */ diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c new file mode 100644 index 000000000..a3e826a75 --- /dev/null +++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_dma.c @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 NextThing Co + * Copyright (C) 2016-2019 Bootlin + * + * Author: Maxime Ripard + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "sun4i_csi.h" + +struct sun4i_csi_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +static inline struct sun4i_csi_buffer * +vb2_v4l2_to_csi_buffer(const struct vb2_v4l2_buffer *p) +{ + return container_of(p, struct sun4i_csi_buffer, vb); +} + +static inline struct sun4i_csi_buffer * +vb2_to_csi_buffer(const struct vb2_buffer *p) +{ + return vb2_v4l2_to_csi_buffer(to_vb2_v4l2_buffer(p)); +} + +static void sun4i_csi_capture_start(struct sun4i_csi *csi) +{ + writel(CSI_CPT_CTRL_VIDEO_START, csi->regs + CSI_CPT_CTRL_REG); +} + +static void sun4i_csi_capture_stop(struct sun4i_csi *csi) +{ + writel(0, csi->regs + CSI_CPT_CTRL_REG); +} + +static int sun4i_csi_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, + unsigned int *nplanes, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct sun4i_csi *csi = vb2_get_drv_priv(vq); + unsigned int num_planes = csi->fmt.num_planes; + unsigned int i; + + if (*nplanes) { + if (*nplanes != num_planes) + return -EINVAL; + + for (i = 0; i < num_planes; i++) + if (sizes[i] < csi->fmt.plane_fmt[i].sizeimage) + return -EINVAL; + return 0; + } + + *nplanes = num_planes; + for (i = 0; i < num_planes; i++) + sizes[i] = csi->fmt.plane_fmt[i].sizeimage; + + return 0; +}; + +static int sun4i_csi_buffer_prepare(struct vb2_buffer *vb) +{ + struct sun4i_csi *csi = vb2_get_drv_priv(vb->vb2_queue); + unsigned int i; + + for (i = 0; i < csi->fmt.num_planes; i++) { + unsigned long size = csi->fmt.plane_fmt[i].sizeimage; + + if (vb2_plane_size(vb, i) < size) { + dev_err(csi->dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static int sun4i_csi_setup_scratch_buffer(struct sun4i_csi *csi, + unsigned int slot) +{ + dma_addr_t addr = csi->scratch.paddr; + unsigned int plane; + + dev_dbg(csi->dev, + "No more available buffer, using the scratch buffer\n"); + + for (plane = 0; plane < csi->fmt.num_planes; plane++) { + writel(addr, csi->regs + CSI_BUF_ADDR_REG(plane, slot)); + addr += csi->fmt.plane_fmt[plane].sizeimage; + } + + csi->current_buf[slot] = NULL; + return 0; +} + +static int sun4i_csi_buffer_fill_slot(struct sun4i_csi *csi, unsigned int slot) +{ + struct sun4i_csi_buffer *c_buf; + struct vb2_v4l2_buffer *v_buf; + unsigned int plane; + + /* + * We should never end up in a situation where we overwrite an + * already filled slot. + */ + if (WARN_ON(csi->current_buf[slot])) + return -EINVAL; + + if (list_empty(&csi->buf_list)) + return sun4i_csi_setup_scratch_buffer(csi, slot); + + c_buf = list_first_entry(&csi->buf_list, struct sun4i_csi_buffer, list); + list_del_init(&c_buf->list); + + v_buf = &c_buf->vb; + csi->current_buf[slot] = v_buf; + + for (plane = 0; plane < csi->fmt.num_planes; plane++) { + dma_addr_t buf_addr; + + buf_addr = vb2_dma_contig_plane_dma_addr(&v_buf->vb2_buf, + plane); + writel(buf_addr, csi->regs + CSI_BUF_ADDR_REG(plane, slot)); + } + + return 0; +} + +static int sun4i_csi_buffer_fill_all(struct sun4i_csi *csi) +{ + unsigned int slot; + int ret; + + for (slot = 0; slot < CSI_MAX_BUFFER; slot++) { + ret = sun4i_csi_buffer_fill_slot(csi, slot); + if (ret) + return ret; + } + + return 0; +} + +static void sun4i_csi_buffer_mark_done(struct sun4i_csi *csi, + unsigned int slot, + unsigned int sequence) +{ + struct vb2_v4l2_buffer *v_buf; + + if (!csi->current_buf[slot]) { + dev_dbg(csi->dev, "Scratch buffer was used, ignoring..\n"); + return; + } + + v_buf = csi->current_buf[slot]; + v_buf->field = csi->fmt.field; + v_buf->sequence = sequence; + v_buf->vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&v_buf->vb2_buf, VB2_BUF_STATE_DONE); + + csi->current_buf[slot] = NULL; +} + +static int sun4i_csi_buffer_flip(struct sun4i_csi *csi, unsigned int sequence) +{ + u32 reg = readl(csi->regs + CSI_BUF_CTRL_REG); + unsigned int next; + + /* Our next buffer is not the current buffer */ + next = !(reg & CSI_BUF_CTRL_DBS); + + /* Report the previous buffer as done */ + sun4i_csi_buffer_mark_done(csi, next, sequence); + + /* Put a new buffer in there */ + return sun4i_csi_buffer_fill_slot(csi, next); +} + +static void sun4i_csi_buffer_queue(struct vb2_buffer *vb) +{ + struct sun4i_csi *csi = vb2_get_drv_priv(vb->vb2_queue); + struct sun4i_csi_buffer *buf = vb2_to_csi_buffer(vb); + unsigned long flags; + + spin_lock_irqsave(&csi->qlock, flags); + list_add_tail(&buf->list, &csi->buf_list); + spin_unlock_irqrestore(&csi->qlock, flags); +} + +static void return_all_buffers(struct sun4i_csi *csi, + enum vb2_buffer_state state) +{ + struct sun4i_csi_buffer *buf, *node; + unsigned int slot; + + list_for_each_entry_safe(buf, node, &csi->buf_list, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + + for (slot = 0; slot < CSI_MAX_BUFFER; slot++) { + struct vb2_v4l2_buffer *v_buf = csi->current_buf[slot]; + + if (!v_buf) + continue; + + vb2_buffer_done(&v_buf->vb2_buf, state); + csi->current_buf[slot] = NULL; + } +} + +static int sun4i_csi_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct sun4i_csi *csi = vb2_get_drv_priv(vq); + struct v4l2_mbus_config_parallel *bus = &csi->bus; + const struct sun4i_csi_format *csi_fmt; + unsigned long href_pol, pclk_pol, vref_pol; + unsigned long flags; + unsigned int i; + int ret; + + csi_fmt = sun4i_csi_find_format(&csi->fmt.pixelformat, NULL); + if (!csi_fmt) + return -EINVAL; + + dev_dbg(csi->dev, "Starting capture\n"); + + csi->sequence = 0; + + /* + * We need a scratch buffer in case where we'll not have any + * more buffer queued so that we don't error out. One of those + * cases is when you end up at the last frame to capture, you + * don't havea any buffer queued any more, and yet it doesn't + * really matter since you'll never reach the next buffer. + * + * Since we support the multi-planar API, we need to have a + * buffer for each plane. Allocating a single one large enough + * to hold all the buffers is simpler, so let's go for that. + */ + csi->scratch.size = 0; + for (i = 0; i < csi->fmt.num_planes; i++) + csi->scratch.size += csi->fmt.plane_fmt[i].sizeimage; + + csi->scratch.vaddr = dma_alloc_coherent(csi->dev, + csi->scratch.size, + &csi->scratch.paddr, + GFP_KERNEL); + if (!csi->scratch.vaddr) { + dev_err(csi->dev, "Failed to allocate scratch buffer\n"); + ret = -ENOMEM; + goto err_clear_dma_queue; + } + + ret = video_device_pipeline_alloc_start(&csi->vdev); + if (ret < 0) + goto err_free_scratch_buffer; + + spin_lock_irqsave(&csi->qlock, flags); + + /* Setup timings */ + writel(CSI_WIN_CTRL_W_ACTIVE(csi->fmt.width * 2), + csi->regs + CSI_WIN_CTRL_W_REG); + writel(CSI_WIN_CTRL_H_ACTIVE(csi->fmt.height), + csi->regs + CSI_WIN_CTRL_H_REG); + + /* + * This hardware uses [HV]REF instead of [HV]SYNC. Based on the + * provided timing diagrams in the manual, positive polarity + * equals active high [HV]REF. + * + * When the back porch is 0, [HV]REF is more or less equivalent + * to [HV]SYNC inverted. + */ + href_pol = !!(bus->flags & V4L2_MBUS_HSYNC_ACTIVE_LOW); + vref_pol = !!(bus->flags & V4L2_MBUS_VSYNC_ACTIVE_LOW); + pclk_pol = !!(bus->flags & V4L2_MBUS_PCLK_SAMPLE_RISING); + writel(CSI_CFG_INPUT_FMT(csi_fmt->input) | + CSI_CFG_OUTPUT_FMT(csi_fmt->output) | + CSI_CFG_VREF_POL(vref_pol) | + CSI_CFG_HREF_POL(href_pol) | + CSI_CFG_PCLK_POL(pclk_pol), + csi->regs + CSI_CFG_REG); + + /* Setup buffer length */ + writel(csi->fmt.plane_fmt[0].bytesperline, + csi->regs + CSI_BUF_LEN_REG); + + /* Prepare our buffers in hardware */ + ret = sun4i_csi_buffer_fill_all(csi); + if (ret) { + spin_unlock_irqrestore(&csi->qlock, flags); + goto err_disable_pipeline; + } + + /* Enable double buffering */ + writel(CSI_BUF_CTRL_DBE, csi->regs + CSI_BUF_CTRL_REG); + + /* Clear the pending interrupts */ + writel(CSI_INT_FRM_DONE, csi->regs + 0x34); + + /* Enable frame done interrupt */ + writel(CSI_INT_FRM_DONE, csi->regs + CSI_INT_EN_REG); + + sun4i_csi_capture_start(csi); + + spin_unlock_irqrestore(&csi->qlock, flags); + + ret = v4l2_subdev_call(csi->src_subdev, video, s_stream, 1); + if (ret < 0 && ret != -ENOIOCTLCMD) + goto err_disable_device; + + return 0; + +err_disable_device: + sun4i_csi_capture_stop(csi); + +err_disable_pipeline: + video_device_pipeline_stop(&csi->vdev); + +err_free_scratch_buffer: + dma_free_coherent(csi->dev, csi->scratch.size, csi->scratch.vaddr, + csi->scratch.paddr); + +err_clear_dma_queue: + spin_lock_irqsave(&csi->qlock, flags); + return_all_buffers(csi, VB2_BUF_STATE_QUEUED); + spin_unlock_irqrestore(&csi->qlock, flags); + + return ret; +} + +static void sun4i_csi_stop_streaming(struct vb2_queue *vq) +{ + struct sun4i_csi *csi = vb2_get_drv_priv(vq); + unsigned long flags; + + dev_dbg(csi->dev, "Stopping capture\n"); + + v4l2_subdev_call(csi->src_subdev, video, s_stream, 0); + sun4i_csi_capture_stop(csi); + + /* Release all active buffers */ + spin_lock_irqsave(&csi->qlock, flags); + return_all_buffers(csi, VB2_BUF_STATE_ERROR); + spin_unlock_irqrestore(&csi->qlock, flags); + + video_device_pipeline_stop(&csi->vdev); + + dma_free_coherent(csi->dev, csi->scratch.size, csi->scratch.vaddr, + csi->scratch.paddr); +} + +static const struct vb2_ops sun4i_csi_qops = { + .queue_setup = sun4i_csi_queue_setup, + .buf_prepare = sun4i_csi_buffer_prepare, + .buf_queue = sun4i_csi_buffer_queue, + .start_streaming = sun4i_csi_start_streaming, + .stop_streaming = sun4i_csi_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static irqreturn_t sun4i_csi_irq(int irq, void *data) +{ + struct sun4i_csi *csi = data; + u32 reg; + + reg = readl(csi->regs + CSI_INT_STA_REG); + + /* Acknowledge the interrupts */ + writel(reg, csi->regs + CSI_INT_STA_REG); + + if (!(reg & CSI_INT_FRM_DONE)) + return IRQ_HANDLED; + + spin_lock(&csi->qlock); + if (sun4i_csi_buffer_flip(csi, csi->sequence++)) { + dev_warn(csi->dev, "%s: Flip failed\n", __func__); + sun4i_csi_capture_stop(csi); + } + spin_unlock(&csi->qlock); + + return IRQ_HANDLED; +} + +int sun4i_csi_dma_register(struct sun4i_csi *csi, int irq) +{ + struct vb2_queue *q = &csi->queue; + int ret; + int i; + + spin_lock_init(&csi->qlock); + mutex_init(&csi->lock); + + INIT_LIST_HEAD(&csi->buf_list); + for (i = 0; i < CSI_MAX_BUFFER; i++) + csi->current_buf[i] = NULL; + + q->min_buffers_needed = 3; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->lock = &csi->lock; + q->drv_priv = csi; + q->buf_struct_size = sizeof(struct sun4i_csi_buffer); + q->ops = &sun4i_csi_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->dev = csi->dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + dev_err(csi->dev, "failed to initialize VB2 queue\n"); + goto err_free_mutex; + } + + ret = v4l2_device_register(csi->dev, &csi->v4l); + if (ret) { + dev_err(csi->dev, "Couldn't register the v4l2 device\n"); + goto err_free_mutex; + } + + ret = devm_request_irq(csi->dev, irq, sun4i_csi_irq, 0, + dev_name(csi->dev), csi); + if (ret) { + dev_err(csi->dev, "Couldn't register our interrupt\n"); + goto err_unregister_device; + } + + return 0; + +err_unregister_device: + v4l2_device_unregister(&csi->v4l); + +err_free_mutex: + mutex_destroy(&csi->lock); + return ret; +} + +void sun4i_csi_dma_unregister(struct sun4i_csi *csi) +{ + v4l2_device_unregister(&csi->v4l); + mutex_destroy(&csi->lock); +} diff --git a/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c b/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c new file mode 100644 index 000000000..48702134c --- /dev/null +++ b/drivers/media/platform/sunxi/sun4i-csi/sun4i_v4l2.c @@ -0,0 +1,380 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 NextThing Co + * Copyright (C) 2016-2019 Bootlin + * + * Author: Maxime Ripard + */ + +#include +#include + +#include +#include +#include + +#include "sun4i_csi.h" + +#define CSI_DEFAULT_WIDTH 640 +#define CSI_DEFAULT_HEIGHT 480 + +static const struct sun4i_csi_format sun4i_csi_formats[] = { + /* YUV422 inputs */ + { + .mbus = MEDIA_BUS_FMT_YUYV8_2X8, + .fourcc = V4L2_PIX_FMT_YUV420M, + .input = CSI_INPUT_YUV, + .output = CSI_OUTPUT_YUV_420_PLANAR, + .num_planes = 3, + .bpp = { 8, 8, 8 }, + .hsub = 2, + .vsub = 2, + }, +}; + +const struct sun4i_csi_format *sun4i_csi_find_format(const u32 *fourcc, + const u32 *mbus) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun4i_csi_formats); i++) { + if (fourcc && *fourcc != sun4i_csi_formats[i].fourcc) + continue; + + if (mbus && *mbus != sun4i_csi_formats[i].mbus) + continue; + + return &sun4i_csi_formats[i]; + } + + return NULL; +} + +static int sun4i_csi_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, "sun4i-csi", sizeof(cap->card)); + + return 0; +} + +static int sun4i_csi_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + if (inp->index != 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(inp->name, "Camera", sizeof(inp->name)); + + return 0; +} + +static int sun4i_csi_g_input(struct file *file, void *fh, + unsigned int *i) +{ + *i = 0; + + return 0; +} + +static int sun4i_csi_s_input(struct file *file, void *fh, + unsigned int i) +{ + if (i != 0) + return -EINVAL; + + return 0; +} + +static void _sun4i_csi_try_fmt(struct sun4i_csi *csi, + struct v4l2_pix_format_mplane *pix) +{ + const struct sun4i_csi_format *_fmt; + unsigned int height, width; + unsigned int i; + + _fmt = sun4i_csi_find_format(&pix->pixelformat, NULL); + if (!_fmt) + _fmt = &sun4i_csi_formats[0]; + + pix->field = V4L2_FIELD_NONE; + pix->colorspace = V4L2_COLORSPACE_SRGB; + pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace); + pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace); + pix->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(true, pix->colorspace, + pix->ycbcr_enc); + + pix->num_planes = _fmt->num_planes; + pix->pixelformat = _fmt->fourcc; + + /* Align the width and height on the subsampling */ + width = ALIGN(pix->width, _fmt->hsub); + height = ALIGN(pix->height, _fmt->vsub); + + /* Clamp the width and height to our capabilities */ + pix->width = clamp(width, _fmt->hsub, CSI_MAX_WIDTH); + pix->height = clamp(height, _fmt->vsub, CSI_MAX_HEIGHT); + + for (i = 0; i < _fmt->num_planes; i++) { + unsigned int hsub = i > 0 ? _fmt->hsub : 1; + unsigned int vsub = i > 0 ? _fmt->vsub : 1; + unsigned int bpl; + + bpl = pix->width / hsub * _fmt->bpp[i] / 8; + pix->plane_fmt[i].bytesperline = bpl; + pix->plane_fmt[i].sizeimage = bpl * pix->height / vsub; + } +} + +static int sun4i_csi_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct sun4i_csi *csi = video_drvdata(file); + + _sun4i_csi_try_fmt(csi, &f->fmt.pix_mp); + + return 0; +} + +static int sun4i_csi_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct sun4i_csi *csi = video_drvdata(file); + + _sun4i_csi_try_fmt(csi, &f->fmt.pix_mp); + csi->fmt = f->fmt.pix_mp; + + return 0; +} + +static int sun4i_csi_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct sun4i_csi *csi = video_drvdata(file); + + f->fmt.pix_mp = csi->fmt; + + return 0; +} + +static int sun4i_csi_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(sun4i_csi_formats)) + return -EINVAL; + + f->pixelformat = sun4i_csi_formats[f->index].fourcc; + + return 0; +} + +static const struct v4l2_ioctl_ops sun4i_csi_ioctl_ops = { + .vidioc_querycap = sun4i_csi_querycap, + + .vidioc_enum_fmt_vid_cap = sun4i_csi_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap_mplane = sun4i_csi_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap_mplane = sun4i_csi_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap_mplane = sun4i_csi_try_fmt_vid_cap, + + .vidioc_enum_input = sun4i_csi_enum_input, + .vidioc_g_input = sun4i_csi_g_input, + .vidioc_s_input = sun4i_csi_s_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +static int sun4i_csi_open(struct file *file) +{ + struct sun4i_csi *csi = video_drvdata(file); + int ret; + + ret = mutex_lock_interruptible(&csi->lock); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(csi->dev); + if (ret < 0) + goto err_unlock; + + ret = v4l2_pipeline_pm_get(&csi->vdev.entity); + if (ret) + goto err_pm_put; + + ret = v4l2_fh_open(file); + if (ret) + goto err_pipeline_pm_put; + + mutex_unlock(&csi->lock); + + return 0; + +err_pipeline_pm_put: + v4l2_pipeline_pm_put(&csi->vdev.entity); + +err_pm_put: + pm_runtime_put(csi->dev); + +err_unlock: + mutex_unlock(&csi->lock); + + return ret; +} + +static int sun4i_csi_release(struct file *file) +{ + struct sun4i_csi *csi = video_drvdata(file); + + mutex_lock(&csi->lock); + + _vb2_fop_release(file, NULL); + + v4l2_pipeline_pm_put(&csi->vdev.entity); + pm_runtime_put(csi->dev); + + mutex_unlock(&csi->lock); + + return 0; +} + +static const struct v4l2_file_operations sun4i_csi_fops = { + .owner = THIS_MODULE, + .open = sun4i_csi_open, + .release = sun4i_csi_release, + .unlocked_ioctl = video_ioctl2, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, +}; + +static const struct v4l2_mbus_framefmt sun4i_csi_pad_fmt_default = { + .width = CSI_DEFAULT_WIDTH, + .height = CSI_DEFAULT_HEIGHT, + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_RAW, + .ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT, + .quantization = V4L2_QUANTIZATION_DEFAULT, + .xfer_func = V4L2_XFER_FUNC_DEFAULT, +}; + +static int sun4i_csi_subdev_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state) +{ + struct v4l2_mbus_framefmt *fmt; + + fmt = v4l2_subdev_get_try_format(subdev, sd_state, CSI_SUBDEV_SINK); + *fmt = sun4i_csi_pad_fmt_default; + + return 0; +} + +static int sun4i_csi_subdev_get_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct sun4i_csi *csi = container_of(subdev, struct sun4i_csi, subdev); + struct v4l2_mbus_framefmt *subdev_fmt; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + subdev_fmt = v4l2_subdev_get_try_format(subdev, sd_state, + fmt->pad); + else + subdev_fmt = &csi->subdev_fmt; + + fmt->format = *subdev_fmt; + + return 0; +} + +static int sun4i_csi_subdev_set_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_format *fmt) +{ + struct sun4i_csi *csi = container_of(subdev, struct sun4i_csi, subdev); + struct v4l2_mbus_framefmt *subdev_fmt; + + if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) + subdev_fmt = v4l2_subdev_get_try_format(subdev, sd_state, + fmt->pad); + else + subdev_fmt = &csi->subdev_fmt; + + /* We can only set the format on the sink pad */ + if (fmt->pad == CSI_SUBDEV_SINK) { + /* It's the sink, only allow changing the frame size */ + subdev_fmt->width = fmt->format.width; + subdev_fmt->height = fmt->format.height; + subdev_fmt->code = fmt->format.code; + } + + fmt->format = *subdev_fmt; + + return 0; +} + +static int +sun4i_csi_subdev_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *sd_state, + struct v4l2_subdev_mbus_code_enum *mbus) +{ + if (mbus->index >= ARRAY_SIZE(sun4i_csi_formats)) + return -EINVAL; + + mbus->code = sun4i_csi_formats[mbus->index].mbus; + + return 0; +} + +static const struct v4l2_subdev_pad_ops sun4i_csi_subdev_pad_ops = { + .link_validate = v4l2_subdev_link_validate_default, + .init_cfg = sun4i_csi_subdev_init_cfg, + .get_fmt = sun4i_csi_subdev_get_fmt, + .set_fmt = sun4i_csi_subdev_set_fmt, + .enum_mbus_code = sun4i_csi_subdev_enum_mbus_code, +}; + +const struct v4l2_subdev_ops sun4i_csi_subdev_ops = { + .pad = &sun4i_csi_subdev_pad_ops, +}; + +int sun4i_csi_v4l2_register(struct sun4i_csi *csi) +{ + struct video_device *vdev = &csi->vdev; + int ret; + + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_STREAMING; + vdev->v4l2_dev = &csi->v4l; + vdev->queue = &csi->queue; + strscpy(vdev->name, KBUILD_MODNAME, sizeof(vdev->name)); + vdev->release = video_device_release_empty; + vdev->lock = &csi->lock; + + /* Set a default format */ + csi->fmt.pixelformat = sun4i_csi_formats[0].fourcc; + csi->fmt.width = CSI_DEFAULT_WIDTH; + csi->fmt.height = CSI_DEFAULT_HEIGHT; + _sun4i_csi_try_fmt(csi, &csi->fmt); + csi->subdev_fmt = sun4i_csi_pad_fmt_default; + + vdev->fops = &sun4i_csi_fops; + vdev->ioctl_ops = &sun4i_csi_ioctl_ops; + video_set_drvdata(vdev, csi); + + ret = video_register_device(&csi->vdev, VFL_TYPE_VIDEO, -1); + if (ret) + return ret; + + dev_info(csi->dev, "Device registered as %s\n", + video_device_node_name(vdev)); + + return 0; +} diff --git a/drivers/media/platform/sunxi/sun6i-csi/Kconfig b/drivers/media/platform/sunxi/sun6i-csi/Kconfig new file mode 100644 index 000000000..886006f6a --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SUN6I_CSI + tristate "Allwinner A31 Camera Sensor Interface (CSI) Driver" + depends on V4L_PLATFORM_DRIVERS && VIDEO_DEV + depends on ARCH_SUNXI || COMPILE_TEST + depends on PM && COMMON_CLK && RESET_CONTROLLER && HAS_DMA + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + select REGMAP_MMIO + help + Support for the Allwinner A31 Camera Sensor Interface (CSI) + controller, also found on other platforms such as the A83T, H3, + V3/V3s or A64. diff --git a/drivers/media/platform/sunxi/sun6i-csi/Makefile b/drivers/media/platform/sunxi/sun6i-csi/Makefile new file mode 100644 index 000000000..e7e315347 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +sun6i-csi-y += sun6i_video.o sun6i_csi.o + +obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c new file mode 100644 index 000000000..8b99c17e8 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c @@ -0,0 +1,1033 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * All rights reserved. + * Author: Yong Deng + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun6i_csi.h" +#include "sun6i_csi_reg.h" + +/* Helpers */ + +/* TODO add 10&12 bit YUV, RGB support */ +bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev, + u32 pixformat, u32 mbus_code) +{ + struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; + + /* + * Some video receivers have the ability to be compatible with + * 8bit and 16bit bus width. + * Identify the media bus format from device tree. + */ + if ((v4l2->v4l2_ep.bus_type == V4L2_MBUS_PARALLEL + || v4l2->v4l2_ep.bus_type == V4L2_MBUS_BT656) + && v4l2->v4l2_ep.bus.parallel.bus_width == 16) { + switch (pixformat) { + case V4L2_PIX_FMT_NV12_16L16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_YUV422P: + switch (mbus_code) { + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_VYUY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + case MEDIA_BUS_FMT_YVYU8_1X16: + return true; + default: + dev_dbg(csi_dev->dev, + "Unsupported mbus code: 0x%x\n", + mbus_code); + break; + } + break; + default: + dev_dbg(csi_dev->dev, "Unsupported pixformat: 0x%x\n", + pixformat); + break; + } + return false; + } + + switch (pixformat) { + case V4L2_PIX_FMT_SBGGR8: + return (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8); + case V4L2_PIX_FMT_SGBRG8: + return (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8); + case V4L2_PIX_FMT_SGRBG8: + return (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8); + case V4L2_PIX_FMT_SRGGB8: + return (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8); + case V4L2_PIX_FMT_SBGGR10: + return (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10); + case V4L2_PIX_FMT_SGBRG10: + return (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10); + case V4L2_PIX_FMT_SGRBG10: + return (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10); + case V4L2_PIX_FMT_SRGGB10: + return (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10); + case V4L2_PIX_FMT_SBGGR12: + return (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12); + case V4L2_PIX_FMT_SGBRG12: + return (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12); + case V4L2_PIX_FMT_SGRBG12: + return (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12); + case V4L2_PIX_FMT_SRGGB12: + return (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12); + + case V4L2_PIX_FMT_YUYV: + return (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8); + case V4L2_PIX_FMT_YVYU: + return (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8); + case V4L2_PIX_FMT_UYVY: + return (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8); + case V4L2_PIX_FMT_VYUY: + return (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8); + + case V4L2_PIX_FMT_NV12_16L16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + case V4L2_PIX_FMT_YUV422P: + switch (mbus_code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YVYU8_2X8: + return true; + default: + dev_dbg(csi_dev->dev, "Unsupported mbus code: 0x%x\n", + mbus_code); + break; + } + break; + + case V4L2_PIX_FMT_RGB565: + return (mbus_code == MEDIA_BUS_FMT_RGB565_2X8_LE); + case V4L2_PIX_FMT_RGB565X: + return (mbus_code == MEDIA_BUS_FMT_RGB565_2X8_BE); + + case V4L2_PIX_FMT_JPEG: + return (mbus_code == MEDIA_BUS_FMT_JPEG_1X8); + + default: + dev_dbg(csi_dev->dev, "Unsupported pixformat: 0x%x\n", + pixformat); + break; + } + + return false; +} + +int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable) +{ + struct device *dev = csi_dev->dev; + struct regmap *regmap = csi_dev->regmap; + int ret; + + if (!enable) { + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0); + pm_runtime_put(dev); + + return 0; + } + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN); + + return 0; +} + +static enum csi_input_fmt get_csi_input_format(struct sun6i_csi_device *csi_dev, + u32 mbus_code, u32 pixformat) +{ + /* non-YUV */ + if ((mbus_code & 0xF000) != 0x2000) + return CSI_INPUT_FORMAT_RAW; + + switch (pixformat) { + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + return CSI_INPUT_FORMAT_RAW; + default: + break; + } + + /* not support YUV420 input format yet */ + dev_dbg(csi_dev->dev, "Select YUV422 as default input format of CSI.\n"); + return CSI_INPUT_FORMAT_YUV422; +} + +static enum csi_output_fmt +get_csi_output_format(struct sun6i_csi_device *csi_dev, u32 pixformat, + u32 field) +{ + bool buf_interlaced = false; + + if (field == V4L2_FIELD_INTERLACED + || field == V4L2_FIELD_INTERLACED_TB + || field == V4L2_FIELD_INTERLACED_BT) + buf_interlaced = true; + + switch (pixformat) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SRGGB10: + return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10; + case V4L2_PIX_FMT_SBGGR12: + case V4L2_PIX_FMT_SGBRG12: + case V4L2_PIX_FMT_SGRBG12: + case V4L2_PIX_FMT_SRGGB12: + return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12; + + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; + + case V4L2_PIX_FMT_NV12_16L16: + return buf_interlaced ? CSI_FRAME_MB_YUV420 : + CSI_FIELD_MB_YUV420; + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 : + CSI_FIELD_UV_CB_YUV420; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 : + CSI_FIELD_PLANAR_YUV420; + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 : + CSI_FIELD_UV_CB_YUV422; + case V4L2_PIX_FMT_YUV422P: + return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 : + CSI_FIELD_PLANAR_YUV422; + + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB565X: + return buf_interlaced ? CSI_FRAME_RGB565 : CSI_FIELD_RGB565; + + case V4L2_PIX_FMT_JPEG: + return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; + + default: + dev_warn(csi_dev->dev, "Unsupported pixformat: 0x%x\n", pixformat); + break; + } + + return CSI_FIELD_RAW_8; +} + +static enum csi_input_seq get_csi_input_seq(struct sun6i_csi_device *csi_dev, + u32 mbus_code, u32 pixformat) +{ + /* Input sequence does not apply to non-YUV formats */ + if ((mbus_code & 0xF000) != 0x2000) + return 0; + + switch (pixformat) { + case V4L2_PIX_FMT_NV12_16L16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YUV422P: + switch (mbus_code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_UYVY8_1X16: + return CSI_INPUT_SEQ_UYVY; + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_VYUY8_1X16: + return CSI_INPUT_SEQ_VYUY; + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YUYV8_1X16: + return CSI_INPUT_SEQ_YUYV; + case MEDIA_BUS_FMT_YVYU8_1X16: + case MEDIA_BUS_FMT_YVYU8_2X8: + return CSI_INPUT_SEQ_YVYU; + default: + dev_warn(csi_dev->dev, "Unsupported mbus code: 0x%x\n", + mbus_code); + break; + } + break; + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YVU420: + switch (mbus_code) { + case MEDIA_BUS_FMT_UYVY8_2X8: + case MEDIA_BUS_FMT_UYVY8_1X16: + return CSI_INPUT_SEQ_VYUY; + case MEDIA_BUS_FMT_VYUY8_2X8: + case MEDIA_BUS_FMT_VYUY8_1X16: + return CSI_INPUT_SEQ_UYVY; + case MEDIA_BUS_FMT_YUYV8_2X8: + case MEDIA_BUS_FMT_YUYV8_1X16: + return CSI_INPUT_SEQ_YVYU; + case MEDIA_BUS_FMT_YVYU8_1X16: + case MEDIA_BUS_FMT_YVYU8_2X8: + return CSI_INPUT_SEQ_YUYV; + default: + dev_warn(csi_dev->dev, "Unsupported mbus code: 0x%x\n", + mbus_code); + break; + } + break; + + case V4L2_PIX_FMT_YUYV: + return CSI_INPUT_SEQ_YUYV; + + default: + dev_warn(csi_dev->dev, "Unsupported pixformat: 0x%x, defaulting to YUYV\n", + pixformat); + break; + } + + return CSI_INPUT_SEQ_YUYV; +} + +static void sun6i_csi_setup_bus(struct sun6i_csi_device *csi_dev) +{ + struct v4l2_fwnode_endpoint *endpoint = &csi_dev->v4l2.v4l2_ep; + struct sun6i_csi_config *config = &csi_dev->config; + unsigned char bus_width; + u32 flags; + u32 cfg; + bool input_interlaced = false; + + if (config->field == V4L2_FIELD_INTERLACED + || config->field == V4L2_FIELD_INTERLACED_TB + || config->field == V4L2_FIELD_INTERLACED_BT) + input_interlaced = true; + + bus_width = endpoint->bus.parallel.bus_width; + + regmap_read(csi_dev->regmap, CSI_IF_CFG_REG, &cfg); + + cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK | + CSI_IF_CFG_IF_DATA_WIDTH_MASK | + CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK | + CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK | + CSI_IF_CFG_SRC_TYPE_MASK); + + if (input_interlaced) + cfg |= CSI_IF_CFG_SRC_TYPE_INTERLACED; + else + cfg |= CSI_IF_CFG_SRC_TYPE_PROGRESSED; + + switch (endpoint->bus_type) { + case V4L2_MBUS_PARALLEL: + cfg |= CSI_IF_CFG_MIPI_IF_CSI; + + flags = endpoint->bus.parallel.flags; + + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT : + CSI_IF_CFG_CSI_IF_YUV422_INTLV; + + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + cfg |= CSI_IF_CFG_FIELD_POSITIVE; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + cfg |= CSI_IF_CFG_VREF_POL_POSITIVE; + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + cfg |= CSI_IF_CFG_HREF_POL_POSITIVE; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE; + break; + case V4L2_MBUS_BT656: + cfg |= CSI_IF_CFG_MIPI_IF_CSI; + + flags = endpoint->bus.parallel.flags; + + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 : + CSI_IF_CFG_CSI_IF_BT656; + + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + cfg |= CSI_IF_CFG_FIELD_POSITIVE; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE; + break; + default: + dev_warn(csi_dev->dev, "Unsupported bus type: %d\n", + endpoint->bus_type); + break; + } + + switch (bus_width) { + case 8: + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT; + break; + case 10: + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT; + break; + case 12: + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT; + break; + case 16: /* No need to configure DATA_WIDTH for 16bit */ + break; + default: + dev_warn(csi_dev->dev, "Unsupported bus width: %u\n", bus_width); + break; + } + + regmap_write(csi_dev->regmap, CSI_IF_CFG_REG, cfg); +} + +static void sun6i_csi_set_format(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_config *config = &csi_dev->config; + u32 cfg; + u32 val; + + regmap_read(csi_dev->regmap, CSI_CH_CFG_REG, &cfg); + + cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK | + CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN | + CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK | + CSI_CH_CFG_INPUT_SEQ_MASK); + + val = get_csi_input_format(csi_dev, config->code, + config->pixelformat); + cfg |= CSI_CH_CFG_INPUT_FMT(val); + + val = get_csi_output_format(csi_dev, config->pixelformat, + config->field); + cfg |= CSI_CH_CFG_OUTPUT_FMT(val); + + val = get_csi_input_seq(csi_dev, config->code, + config->pixelformat); + cfg |= CSI_CH_CFG_INPUT_SEQ(val); + + if (config->field == V4L2_FIELD_TOP) + cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0; + else if (config->field == V4L2_FIELD_BOTTOM) + cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1; + else + cfg |= CSI_CH_CFG_FIELD_SEL_BOTH; + + regmap_write(csi_dev->regmap, CSI_CH_CFG_REG, cfg); +} + +static void sun6i_csi_set_window(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_config *config = &csi_dev->config; + u32 bytesperline_y; + u32 bytesperline_c; + int *planar_offset = csi_dev->planar_offset; + u32 width = config->width; + u32 height = config->height; + u32 hor_len = width; + + switch (config->pixelformat) { + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + dev_dbg(csi_dev->dev, + "Horizontal length should be 2 times of width for packed YUV formats!\n"); + hor_len = width * 2; + break; + default: + break; + } + + regmap_write(csi_dev->regmap, CSI_CH_HSIZE_REG, + CSI_CH_HSIZE_HOR_LEN(hor_len) | + CSI_CH_HSIZE_HOR_START(0)); + regmap_write(csi_dev->regmap, CSI_CH_VSIZE_REG, + CSI_CH_VSIZE_VER_LEN(height) | + CSI_CH_VSIZE_VER_START(0)); + + planar_offset[0] = 0; + switch (config->pixelformat) { + case V4L2_PIX_FMT_NV12_16L16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + bytesperline_y = width; + bytesperline_c = width; + planar_offset[1] = bytesperline_y * height; + planar_offset[2] = -1; + break; + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + bytesperline_y = width; + bytesperline_c = width / 2; + planar_offset[1] = bytesperline_y * height; + planar_offset[2] = planar_offset[1] + + bytesperline_c * height / 2; + break; + case V4L2_PIX_FMT_YUV422P: + bytesperline_y = width; + bytesperline_c = width / 2; + planar_offset[1] = bytesperline_y * height; + planar_offset[2] = planar_offset[1] + + bytesperline_c * height; + break; + default: /* raw */ + dev_dbg(csi_dev->dev, + "Calculating pixelformat(0x%x)'s bytesperline as a packed format\n", + config->pixelformat); + bytesperline_y = (sun6i_csi_get_bpp(config->pixelformat) * + config->width) / 8; + bytesperline_c = 0; + planar_offset[1] = -1; + planar_offset[2] = -1; + break; + } + + regmap_write(csi_dev->regmap, CSI_CH_BUF_LEN_REG, + CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) | + CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y)); +} + +int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_config *config) +{ + if (!config) + return -EINVAL; + + memcpy(&csi_dev->config, config, sizeof(csi_dev->config)); + + sun6i_csi_setup_bus(csi_dev); + sun6i_csi_set_format(csi_dev); + sun6i_csi_set_window(csi_dev); + + return 0; +} + +void sun6i_csi_update_buf_addr(struct sun6i_csi_device *csi_dev, + dma_addr_t addr) +{ + regmap_write(csi_dev->regmap, CSI_CH_F0_BUFA_REG, + (addr + csi_dev->planar_offset[0]) >> 2); + if (csi_dev->planar_offset[1] != -1) + regmap_write(csi_dev->regmap, CSI_CH_F1_BUFA_REG, + (addr + csi_dev->planar_offset[1]) >> 2); + if (csi_dev->planar_offset[2] != -1) + regmap_write(csi_dev->regmap, CSI_CH_F2_BUFA_REG, + (addr + csi_dev->planar_offset[2]) >> 2); +} + +void sun6i_csi_set_stream(struct sun6i_csi_device *csi_dev, bool enable) +{ + struct regmap *regmap = csi_dev->regmap; + + if (!enable) { + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0); + regmap_write(regmap, CSI_CH_INT_EN_REG, 0); + return; + } + + regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF); + regmap_write(regmap, CSI_CH_INT_EN_REG, + CSI_CH_INT_EN_HB_OF_INT_EN | + CSI_CH_INT_EN_FIFO2_OF_INT_EN | + CSI_CH_INT_EN_FIFO1_OF_INT_EN | + CSI_CH_INT_EN_FIFO0_OF_INT_EN | + CSI_CH_INT_EN_FD_INT_EN | + CSI_CH_INT_EN_CD_INT_EN); + + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, + CSI_CAP_CH0_VCAP_ON); +} + +/* Media */ + +static const struct media_device_ops sun6i_csi_media_ops = { + .link_notify = v4l2_pipeline_link_notify, +}; + +/* V4L2 */ + +static int sun6i_csi_link_entity(struct sun6i_csi_device *csi_dev, + struct media_entity *entity, + struct fwnode_handle *fwnode) +{ + struct media_entity *sink; + struct media_pad *sink_pad; + int src_pad_index; + int ret; + + ret = media_entity_get_fwnode_pad(entity, fwnode, MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(csi_dev->dev, + "%s: no source pad in external entity %s\n", __func__, + entity->name); + return -EINVAL; + } + + src_pad_index = ret; + + sink = &csi_dev->video.video_dev.entity; + sink_pad = &csi_dev->video.pad; + + dev_dbg(csi_dev->dev, "creating %s:%u -> %s:%u link\n", + entity->name, src_pad_index, sink->name, sink_pad->index); + ret = media_create_pad_link(entity, src_pad_index, sink, + sink_pad->index, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + dev_err(csi_dev->dev, "failed to create %s:%u -> %s:%u link\n", + entity->name, src_pad_index, + sink->name, sink_pad->index); + return ret; + } + + return 0; +} + +static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier) +{ + struct sun6i_csi_device *csi_dev = + container_of(notifier, struct sun6i_csi_device, + v4l2.notifier); + struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; + struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; + struct v4l2_subdev *sd; + int ret; + + dev_dbg(csi_dev->dev, "notify complete, all subdevs registered\n"); + + sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list); + if (!sd) + return -EINVAL; + + ret = sun6i_csi_link_entity(csi_dev, &sd->entity, sd->fwnode); + if (ret < 0) + return ret; + + ret = v4l2_device_register_subdev_nodes(v4l2_dev); + if (ret < 0) + return ret; + + return 0; +} + +static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = { + .complete = sun6i_subdev_notify_complete, +}; + +static int sun6i_csi_fwnode_parse(struct device *dev, + struct v4l2_fwnode_endpoint *vep, + struct v4l2_async_subdev *asd) +{ + struct sun6i_csi_device *csi_dev = dev_get_drvdata(dev); + + if (vep->base.port || vep->base.id) { + dev_warn(dev, "Only support a single port with one endpoint\n"); + return -ENOTCONN; + } + + switch (vep->bus_type) { + case V4L2_MBUS_PARALLEL: + case V4L2_MBUS_BT656: + csi_dev->v4l2.v4l2_ep = *vep; + return 0; + default: + dev_err(dev, "Unsupported media bus type\n"); + return -ENOTCONN; + } +} + +static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; + struct media_device *media_dev = &v4l2->media_dev; + struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; + struct v4l2_async_notifier *notifier = &v4l2->notifier; + struct device *dev = csi_dev->dev; + int ret; + + /* Media Device */ + + strscpy(media_dev->model, SUN6I_CSI_DESCRIPTION, + sizeof(media_dev->model)); + media_dev->hw_revision = 0; + media_dev->ops = &sun6i_csi_media_ops; + media_dev->dev = dev; + + media_device_init(media_dev); + + ret = media_device_register(media_dev); + if (ret) { + dev_err(dev, "failed to register media device: %d\n", ret); + goto error_media; + } + + /* V4L2 Device */ + + v4l2_dev->mdev = media_dev; + + ret = v4l2_device_register(dev, v4l2_dev); + if (ret) { + dev_err(dev, "failed to register v4l2 device: %d\n", ret); + goto error_media; + } + + /* Video */ + + ret = sun6i_video_setup(csi_dev); + if (ret) + goto error_v4l2_device; + + /* V4L2 Async */ + + v4l2_async_nf_init(notifier); + notifier->ops = &sun6i_csi_async_ops; + + ret = v4l2_async_nf_parse_fwnode_endpoints(dev, notifier, + sizeof(struct + v4l2_async_subdev), + sun6i_csi_fwnode_parse); + if (ret) + goto error_video; + + ret = v4l2_async_nf_register(v4l2_dev, notifier); + if (ret) { + dev_err(dev, "failed to register v4l2 async notifier: %d\n", + ret); + goto error_v4l2_async_notifier; + } + + return 0; + +error_v4l2_async_notifier: + v4l2_async_nf_cleanup(notifier); + +error_video: + sun6i_video_cleanup(csi_dev); + +error_v4l2_device: + v4l2_device_unregister(&v4l2->v4l2_dev); + +error_media: + media_device_unregister(media_dev); + media_device_cleanup(media_dev); + + return ret; +} + +static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; + + media_device_unregister(&v4l2->media_dev); + v4l2_async_nf_unregister(&v4l2->notifier); + v4l2_async_nf_cleanup(&v4l2->notifier); + sun6i_video_cleanup(csi_dev); + v4l2_device_unregister(&v4l2->v4l2_dev); + media_device_cleanup(&v4l2->media_dev); +} + +/* Platform */ + +static irqreturn_t sun6i_csi_interrupt(int irq, void *private) +{ + struct sun6i_csi_device *csi_dev = private; + struct regmap *regmap = csi_dev->regmap; + u32 status; + + regmap_read(regmap, CSI_CH_INT_STA_REG, &status); + + if (!(status & 0xFF)) + return IRQ_NONE; + + if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) || + (status & CSI_CH_INT_STA_FIFO1_OF_PD) || + (status & CSI_CH_INT_STA_FIFO2_OF_PD) || + (status & CSI_CH_INT_STA_HB_OF_PD)) { + regmap_write(regmap, CSI_CH_INT_STA_REG, status); + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0); + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, + CSI_EN_CSI_EN); + return IRQ_HANDLED; + } + + if (status & CSI_CH_INT_STA_FD_PD) + sun6i_video_frame_done(csi_dev); + + regmap_write(regmap, CSI_CH_INT_STA_REG, status); + + return IRQ_HANDLED; +} + +static int sun6i_csi_suspend(struct device *dev) +{ + struct sun6i_csi_device *csi_dev = dev_get_drvdata(dev); + + reset_control_assert(csi_dev->reset); + clk_disable_unprepare(csi_dev->clock_ram); + clk_disable_unprepare(csi_dev->clock_mod); + + return 0; +} + +static int sun6i_csi_resume(struct device *dev) +{ + struct sun6i_csi_device *csi_dev = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(csi_dev->reset); + if (ret) { + dev_err(dev, "failed to deassert reset\n"); + return ret; + } + + ret = clk_prepare_enable(csi_dev->clock_mod); + if (ret) { + dev_err(dev, "failed to enable module clock\n"); + goto error_reset; + } + + ret = clk_prepare_enable(csi_dev->clock_ram); + if (ret) { + dev_err(dev, "failed to enable ram clock\n"); + goto error_clock_mod; + } + + return 0; + +error_clock_mod: + clk_disable_unprepare(csi_dev->clock_mod); + +error_reset: + reset_control_assert(csi_dev->reset); + + return ret; +} + +static const struct dev_pm_ops sun6i_csi_pm_ops = { + .runtime_suspend = sun6i_csi_suspend, + .runtime_resume = sun6i_csi_resume, +}; + +static const struct regmap_config sun6i_csi_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x9c, +}; + +static int sun6i_csi_resources_setup(struct sun6i_csi_device *csi_dev, + struct platform_device *platform_dev) +{ + struct device *dev = csi_dev->dev; + const struct sun6i_csi_variant *variant; + void __iomem *io_base; + int ret; + int irq; + + variant = of_device_get_match_data(dev); + if (!variant) + return -EINVAL; + + /* Registers */ + + io_base = devm_platform_ioremap_resource(platform_dev, 0); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + csi_dev->regmap = devm_regmap_init_mmio_clk(dev, "bus", io_base, + &sun6i_csi_regmap_config); + if (IS_ERR(csi_dev->regmap)) { + dev_err(dev, "failed to init register map\n"); + return PTR_ERR(csi_dev->regmap); + } + + /* Clocks */ + + csi_dev->clock_mod = devm_clk_get(dev, "mod"); + if (IS_ERR(csi_dev->clock_mod)) { + dev_err(dev, "failed to acquire module clock\n"); + return PTR_ERR(csi_dev->clock_mod); + } + + csi_dev->clock_ram = devm_clk_get(dev, "ram"); + if (IS_ERR(csi_dev->clock_ram)) { + dev_err(dev, "failed to acquire ram clock\n"); + return PTR_ERR(csi_dev->clock_ram); + } + + ret = clk_set_rate_exclusive(csi_dev->clock_mod, + variant->clock_mod_rate); + if (ret) { + dev_err(dev, "failed to set mod clock rate\n"); + return ret; + } + + /* Reset */ + + csi_dev->reset = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(csi_dev->reset)) { + dev_err(dev, "failed to acquire reset\n"); + ret = PTR_ERR(csi_dev->reset); + goto error_clock_rate_exclusive; + } + + /* Interrupt */ + + irq = platform_get_irq(platform_dev, 0); + if (irq < 0) { + dev_err(dev, "failed to get interrupt\n"); + ret = -ENXIO; + goto error_clock_rate_exclusive; + } + + ret = devm_request_irq(dev, irq, sun6i_csi_interrupt, 0, SUN6I_CSI_NAME, + csi_dev); + if (ret) { + dev_err(dev, "failed to request interrupt\n"); + goto error_clock_rate_exclusive; + } + + /* Runtime PM */ + + pm_runtime_enable(dev); + + return 0; + +error_clock_rate_exclusive: + clk_rate_exclusive_put(csi_dev->clock_mod); + + return ret; +} + +static void sun6i_csi_resources_cleanup(struct sun6i_csi_device *csi_dev) +{ + pm_runtime_disable(csi_dev->dev); + clk_rate_exclusive_put(csi_dev->clock_mod); +} + +static int sun6i_csi_probe(struct platform_device *platform_dev) +{ + struct sun6i_csi_device *csi_dev; + struct device *dev = &platform_dev->dev; + int ret; + + csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL); + if (!csi_dev) + return -ENOMEM; + + csi_dev->dev = &platform_dev->dev; + platform_set_drvdata(platform_dev, csi_dev); + + ret = sun6i_csi_resources_setup(csi_dev, platform_dev); + if (ret) + return ret; + + ret = sun6i_csi_v4l2_setup(csi_dev); + if (ret) + goto error_resources; + + return 0; + +error_resources: + sun6i_csi_resources_cleanup(csi_dev); + + return ret; +} + +static int sun6i_csi_remove(struct platform_device *pdev) +{ + struct sun6i_csi_device *csi_dev = platform_get_drvdata(pdev); + + sun6i_csi_v4l2_cleanup(csi_dev); + sun6i_csi_resources_cleanup(csi_dev); + + return 0; +} + +static const struct sun6i_csi_variant sun6i_a31_csi_variant = { + .clock_mod_rate = 297000000, +}; + +static const struct sun6i_csi_variant sun50i_a64_csi_variant = { + .clock_mod_rate = 300000000, +}; + +static const struct of_device_id sun6i_csi_of_match[] = { + { + .compatible = "allwinner,sun6i-a31-csi", + .data = &sun6i_a31_csi_variant, + }, + { + .compatible = "allwinner,sun8i-a83t-csi", + .data = &sun6i_a31_csi_variant, + }, + { + .compatible = "allwinner,sun8i-h3-csi", + .data = &sun6i_a31_csi_variant, + }, + { + .compatible = "allwinner,sun8i-v3s-csi", + .data = &sun6i_a31_csi_variant, + }, + { + .compatible = "allwinner,sun50i-a64-csi", + .data = &sun50i_a64_csi_variant, + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, sun6i_csi_of_match); + +static struct platform_driver sun6i_csi_platform_driver = { + .probe = sun6i_csi_probe, + .remove = sun6i_csi_remove, + .driver = { + .name = SUN6I_CSI_NAME, + .of_match_table = of_match_ptr(sun6i_csi_of_match), + .pm = &sun6i_csi_pm_ops, + }, +}; + +module_platform_driver(sun6i_csi_platform_driver); + +MODULE_DESCRIPTION("Allwinner A31 Camera Sensor Interface driver"); +MODULE_AUTHOR("Yong Deng "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h new file mode 100644 index 000000000..bab705678 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h @@ -0,0 +1,166 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * All rights reserved. + * Author: Yong Deng + */ + +#ifndef __SUN6I_CSI_H__ +#define __SUN6I_CSI_H__ + +#include +#include +#include + +#include "sun6i_video.h" + +#define SUN6I_CSI_NAME "sun6i-csi" +#define SUN6I_CSI_DESCRIPTION "Allwinner A31 CSI Device" + +struct sun6i_csi_buffer { + struct vb2_v4l2_buffer v4l2_buffer; + struct list_head list; + + dma_addr_t dma_addr; + bool queued_to_csi; +}; + +/** + * struct sun6i_csi_config - configs for sun6i csi + * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*) + * @code: media bus format code (MEDIA_BUS_FMT_*) + * @field: used interlacing type (enum v4l2_field) + * @width: frame width + * @height: frame height + */ +struct sun6i_csi_config { + u32 pixelformat; + u32 code; + u32 field; + u32 width; + u32 height; +}; + +struct sun6i_csi_v4l2 { + struct v4l2_device v4l2_dev; + struct media_device media_dev; + + struct v4l2_async_notifier notifier; + /* video port settings */ + struct v4l2_fwnode_endpoint v4l2_ep; +}; + +struct sun6i_csi_device { + struct device *dev; + + struct sun6i_csi_config config; + struct sun6i_csi_v4l2 v4l2; + struct sun6i_video video; + + struct regmap *regmap; + struct clk *clock_mod; + struct clk *clock_ram; + struct reset_control *reset; + + int planar_offset[3]; +}; + +struct sun6i_csi_variant { + unsigned long clock_mod_rate; +}; + +/** + * sun6i_csi_is_format_supported() - check if the format supported by csi + * @csi_dev: pointer to the csi device + * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*) + * @mbus_code: media bus format code (MEDIA_BUS_FMT_*) + * + * Return: true if format is supported, false otherwise. + */ +bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev, + u32 pixformat, u32 mbus_code); + +/** + * sun6i_csi_set_power() - power on/off the csi + * @csi_dev: pointer to the csi device + * @enable: on/off + * + * Return: 0 if successful, error code otherwise. + */ +int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable); + +/** + * sun6i_csi_update_config() - update the csi register settings + * @csi_dev: pointer to the csi device + * @config: see struct sun6i_csi_config + * + * Return: 0 if successful, error code otherwise. + */ +int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_config *config); + +/** + * sun6i_csi_update_buf_addr() - update the csi frame buffer address + * @csi_dev: pointer to the csi device + * @addr: frame buffer's physical address + */ +void sun6i_csi_update_buf_addr(struct sun6i_csi_device *csi_dev, + dma_addr_t addr); + +/** + * sun6i_csi_set_stream() - start/stop csi streaming + * @csi_dev: pointer to the csi device + * @enable: start/stop + */ +void sun6i_csi_set_stream(struct sun6i_csi_device *csi_dev, bool enable); + +/* get bpp form v4l2 pixformat */ +static inline int sun6i_csi_get_bpp(unsigned int pixformat) +{ + switch (pixformat) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + case V4L2_PIX_FMT_JPEG: + return 8; + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SRGGB10: + return 10; + case V4L2_PIX_FMT_SBGGR12: + case V4L2_PIX_FMT_SGBRG12: + case V4L2_PIX_FMT_SGRBG12: + case V4L2_PIX_FMT_SRGGB12: + case V4L2_PIX_FMT_NV12_16L16: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: + return 12; + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_YVYU: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_VYUY: + case V4L2_PIX_FMT_NV16: + case V4L2_PIX_FMT_NV61: + case V4L2_PIX_FMT_YUV422P: + case V4L2_PIX_FMT_RGB565: + case V4L2_PIX_FMT_RGB565X: + return 16; + case V4L2_PIX_FMT_RGB24: + case V4L2_PIX_FMT_BGR24: + return 24; + case V4L2_PIX_FMT_RGB32: + case V4L2_PIX_FMT_BGR32: + return 32; + default: + WARN(1, "Unsupported pixformat: 0x%x\n", pixformat); + break; + } + + return 0; +} + +#endif /* __SUN6I_CSI_H__ */ diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h new file mode 100644 index 000000000..703fa14bb --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * All rights reserved. + * Author: Yong Deng + */ + +#ifndef __SUN6I_CSI_REG_H__ +#define __SUN6I_CSI_REG_H__ + +#include + +#define CSI_EN_REG 0x0 +#define CSI_EN_VER_EN BIT(30) +#define CSI_EN_CSI_EN BIT(0) + +#define CSI_IF_CFG_REG 0x4 +#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21) +#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK) +#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK) +#define CSI_IF_CFG_FPS_DS_EN BIT(20) +#define CSI_IF_CFG_FIELD_MASK BIT(19) +#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK) +#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK) +#define CSI_IF_CFG_VREF_POL_MASK BIT(18) +#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK) +#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK) +#define CSI_IF_CFG_HREF_POL_MASK BIT(17) +#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK) +#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK) +#define CSI_IF_CFG_CLK_POL_MASK BIT(16) +#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK) +#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK) +#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8) +#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) +#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) +#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) +#define CSI_IF_CFG_MIPI_IF_MASK BIT(7) +#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7) +#define CSI_IF_CFG_MIPI_IF_MIPI BIT(7) +#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0) +#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK) +#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK) +#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK) +#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK) + +#define CSI_CAP_REG 0x8 +#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2) +#define CSI_CAP_CH0_CAP_MASK(count) (((count) << 2) & CSI_CAP_CH0_CAP_MASK_MASK) +#define CSI_CAP_CH0_VCAP_ON BIT(1) +#define CSI_CAP_CH0_SCAP_ON BIT(0) + +#define CSI_SYNC_CNT_REG 0xc +#define CSI_FIFO_THRS_REG 0x10 +#define CSI_BT656_HEAD_CFG_REG 0x14 +#define CSI_PTN_LEN_REG 0x30 +#define CSI_PTN_ADDR_REG 0x34 +#define CSI_VER_REG 0x3c + +#define CSI_CH_CFG_REG 0x44 +#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20) +#define CSI_CH_CFG_INPUT_FMT(fmt) (((fmt) << 20) & CSI_CH_CFG_INPUT_FMT_MASK) +#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16) +#define CSI_CH_CFG_OUTPUT_FMT(fmt) (((fmt) << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK) +#define CSI_CH_CFG_VFLIP_EN BIT(13) +#define CSI_CH_CFG_HFLIP_EN BIT(12) +#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10) +#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) +#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) +#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) +#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8) +#define CSI_CH_CFG_INPUT_SEQ(seq) (((seq) << 8) & CSI_CH_CFG_INPUT_SEQ_MASK) + +#define CSI_CH_SCALE_REG 0x4c +#define CSI_CH_SCALE_QUART_EN BIT(0) + +#define CSI_CH_F0_BUFA_REG 0x50 + +#define CSI_CH_F1_BUFA_REG 0x58 + +#define CSI_CH_F2_BUFA_REG 0x60 + +#define CSI_CH_STA_REG 0x6c +#define CSI_CH_STA_FIELD_STA_MASK BIT(2) +#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK) +#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK) +#define CSI_CH_STA_VCAP_STA BIT(1) +#define CSI_CH_STA_SCAP_STA BIT(0) + +#define CSI_CH_INT_EN_REG 0x70 +#define CSI_CH_INT_EN_VS_INT_EN BIT(7) +#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6) +#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5) +#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4) +#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3) +#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2) +#define CSI_CH_INT_EN_FD_INT_EN BIT(1) +#define CSI_CH_INT_EN_CD_INT_EN BIT(0) + +#define CSI_CH_INT_STA_REG 0x74 +#define CSI_CH_INT_STA_VS_PD BIT(7) +#define CSI_CH_INT_STA_HB_OF_PD BIT(6) +#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5) +#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4) +#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3) +#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2) +#define CSI_CH_INT_STA_FD_PD BIT(1) +#define CSI_CH_INT_STA_CD_PD BIT(0) + +#define CSI_CH_FLD1_VSIZE_REG 0x78 + +#define CSI_CH_HSIZE_REG 0x80 +#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16) +#define CSI_CH_HSIZE_HOR_LEN(len) (((len) << 16) & CSI_CH_HSIZE_HOR_LEN_MASK) +#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0) +#define CSI_CH_HSIZE_HOR_START(start) (((start) << 0) & CSI_CH_HSIZE_HOR_START_MASK) + +#define CSI_CH_VSIZE_REG 0x84 +#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16) +#define CSI_CH_VSIZE_VER_LEN(len) (((len) << 16) & CSI_CH_VSIZE_VER_LEN_MASK) +#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0) +#define CSI_CH_VSIZE_VER_START(start) (((start) << 0) & CSI_CH_VSIZE_VER_START_MASK) + +#define CSI_CH_BUF_LEN_REG 0x88 +#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16) +#define CSI_CH_BUF_LEN_BUF_LEN_C(len) (((len) << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK) +#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0) +#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) (((len) << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK) + +#define CSI_CH_FLIP_SIZE_REG 0x8c +#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16) +#define CSI_CH_FLIP_SIZE_VER_LEN(len) (((len) << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK) +#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0) +#define CSI_CH_FLIP_SIZE_VALID_LEN(len) (((len) << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK) + +#define CSI_CH_FRM_CLK_CNT_REG 0x90 +#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94 +#define CSI_CH_FIFO_STAT_REG 0x98 +#define CSI_CH_PCLK_STAT_REG 0x9c + +/* + * csi input data format + */ +enum csi_input_fmt { + CSI_INPUT_FORMAT_RAW = 0, + CSI_INPUT_FORMAT_YUV422 = 3, + CSI_INPUT_FORMAT_YUV420 = 4, +}; + +/* + * csi output data format + */ +enum csi_output_fmt { + /* only when input format is RAW */ + CSI_FIELD_RAW_8 = 0, + CSI_FIELD_RAW_10 = 1, + CSI_FIELD_RAW_12 = 2, + CSI_FIELD_RGB565 = 4, + CSI_FIELD_RGB888 = 5, + CSI_FIELD_PRGB888 = 6, + CSI_FRAME_RAW_8 = 8, + CSI_FRAME_RAW_10 = 9, + CSI_FRAME_RAW_12 = 10, + CSI_FRAME_RGB565 = 12, + CSI_FRAME_RGB888 = 13, + CSI_FRAME_PRGB888 = 14, + + /* only when input format is YUV422 */ + CSI_FIELD_PLANAR_YUV422 = 0, + CSI_FIELD_PLANAR_YUV420 = 1, + CSI_FRAME_PLANAR_YUV420 = 2, + CSI_FRAME_PLANAR_YUV422 = 3, + CSI_FIELD_UV_CB_YUV422 = 4, + CSI_FIELD_UV_CB_YUV420 = 5, + CSI_FRAME_UV_CB_YUV420 = 6, + CSI_FRAME_UV_CB_YUV422 = 7, + CSI_FIELD_MB_YUV422 = 8, + CSI_FIELD_MB_YUV420 = 9, + CSI_FRAME_MB_YUV420 = 10, + CSI_FRAME_MB_YUV422 = 11, + CSI_FIELD_UV_CB_YUV422_10 = 12, + CSI_FIELD_UV_CB_YUV420_10 = 13, +}; + +/* + * csi YUV input data sequence + */ +enum csi_input_seq { + /* only when input format is YUV422 */ + CSI_INPUT_SEQ_YUYV = 0, + CSI_INPUT_SEQ_YVYU, + CSI_INPUT_SEQ_UYVY, + CSI_INPUT_SEQ_VYUY, +}; + +#endif /* __SUN6I_CSI_REG_H__ */ diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c new file mode 100644 index 000000000..791583d23 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c @@ -0,0 +1,733 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * All rights reserved. + * Author: Yong Deng + */ + +#include + +#include +#include +#include +#include +#include +#include + +#include "sun6i_csi.h" +#include "sun6i_video.h" + +/* This is got from BSP sources. */ +#define MIN_WIDTH (32) +#define MIN_HEIGHT (32) +#define MAX_WIDTH (4800) +#define MAX_HEIGHT (4800) + +/* Helpers */ + +static struct v4l2_subdev * +sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad) +{ + struct media_pad *remote; + + remote = media_pad_remote_pad_first(&video->pad); + + if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) + return NULL; + + if (pad) + *pad = remote->index; + + return media_entity_to_v4l2_subdev(remote->entity); +} + +/* Format */ + +static const u32 sun6i_video_formats[] = { + V4L2_PIX_FMT_SBGGR8, + V4L2_PIX_FMT_SGBRG8, + V4L2_PIX_FMT_SGRBG8, + V4L2_PIX_FMT_SRGGB8, + V4L2_PIX_FMT_SBGGR10, + V4L2_PIX_FMT_SGBRG10, + V4L2_PIX_FMT_SGRBG10, + V4L2_PIX_FMT_SRGGB10, + V4L2_PIX_FMT_SBGGR12, + V4L2_PIX_FMT_SGBRG12, + V4L2_PIX_FMT_SGRBG12, + V4L2_PIX_FMT_SRGGB12, + V4L2_PIX_FMT_YUYV, + V4L2_PIX_FMT_YVYU, + V4L2_PIX_FMT_UYVY, + V4L2_PIX_FMT_VYUY, + V4L2_PIX_FMT_NV12_16L16, + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, + V4L2_PIX_FMT_YUV420, + V4L2_PIX_FMT_YVU420, + V4L2_PIX_FMT_NV16, + V4L2_PIX_FMT_NV61, + V4L2_PIX_FMT_YUV422P, + V4L2_PIX_FMT_RGB565, + V4L2_PIX_FMT_RGB565X, + V4L2_PIX_FMT_JPEG, +}; + +static bool sun6i_video_format_check(u32 format) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_video_formats); i++) + if (sun6i_video_formats[i] == format) + return true; + + return false; +} + +/* Video */ + +static void sun6i_video_buffer_configure(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_buffer *csi_buffer) +{ + csi_buffer->queued_to_csi = true; + sun6i_csi_update_buf_addr(csi_dev, csi_buffer->dma_addr); +} + +static void sun6i_video_configure(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_video *video = &csi_dev->video; + struct sun6i_csi_config config = { 0 }; + + config.pixelformat = video->format.fmt.pix.pixelformat; + config.code = video->mbus_code; + config.field = video->format.fmt.pix.field; + config.width = video->format.fmt.pix.width; + config.height = video->format.fmt.pix.height; + + sun6i_csi_update_config(csi_dev, &config); +} + +/* Queue */ + +static int sun6i_video_queue_setup(struct vb2_queue *queue, + unsigned int *buffers_count, + unsigned int *planes_count, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_video *video = &csi_dev->video; + unsigned int size = video->format.fmt.pix.sizeimage; + + if (*planes_count) + return sizes[0] < size ? -EINVAL : 0; + + *planes_count = 1; + sizes[0] = size; + + return 0; +} + +static int sun6i_video_buffer_prepare(struct vb2_buffer *buffer) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); + struct sun6i_video *video = &csi_dev->video; + struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); + struct sun6i_csi_buffer *csi_buffer = + container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); + unsigned long size = video->format.fmt.pix.sizeimage; + + if (vb2_plane_size(buffer, 0) < size) { + v4l2_err(v4l2_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(buffer, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(buffer, 0, size); + + csi_buffer->dma_addr = vb2_dma_contig_plane_dma_addr(buffer, 0); + v4l2_buffer->field = video->format.fmt.pix.field; + + return 0; +} + +static void sun6i_video_buffer_queue(struct vb2_buffer *buffer) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); + struct sun6i_video *video = &csi_dev->video; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); + struct sun6i_csi_buffer *csi_buffer = + container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); + unsigned long flags; + + spin_lock_irqsave(&video->dma_queue_lock, flags); + csi_buffer->queued_to_csi = false; + list_add_tail(&csi_buffer->list, &video->dma_queue); + spin_unlock_irqrestore(&video->dma_queue_lock, flags); +} + +static int sun6i_video_start_streaming(struct vb2_queue *queue, + unsigned int count) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_video *video = &csi_dev->video; + struct video_device *video_dev = &video->video_dev; + struct sun6i_csi_buffer *buf; + struct sun6i_csi_buffer *next_buf; + struct v4l2_subdev *subdev; + unsigned long flags; + int ret; + + video->sequence = 0; + + ret = video_device_pipeline_alloc_start(video_dev); + if (ret < 0) + goto error_dma_queue_flush; + + if (video->mbus_code == 0) { + ret = -EINVAL; + goto error_media_pipeline; + } + + subdev = sun6i_video_remote_subdev(video, NULL); + if (!subdev) { + ret = -EINVAL; + goto error_media_pipeline; + } + + sun6i_video_configure(csi_dev); + + spin_lock_irqsave(&video->dma_queue_lock, flags); + + buf = list_first_entry(&video->dma_queue, + struct sun6i_csi_buffer, list); + sun6i_video_buffer_configure(csi_dev, buf); + + sun6i_csi_set_stream(csi_dev, true); + + /* + * CSI will lookup the next dma buffer for next frame before the + * current frame done IRQ triggered. This is not documented + * but reported by OndÅ™ej Jirman. + * The BSP code has workaround for this too. It skip to mark the + * first buffer as frame done for VB2 and pass the second buffer + * to CSI in the first frame done ISR call. Then in second frame + * done ISR call, it mark the first buffer as frame done for VB2 + * and pass the third buffer to CSI. And so on. The bad thing is + * that the first buffer will be written twice and the first frame + * is dropped even the queued buffer is sufficient. + * So, I make some improvement here. Pass the next buffer to CSI + * just follow starting the CSI. In this case, the first frame + * will be stored in first buffer, second frame in second buffer. + * This method is used to avoid dropping the first frame, it + * would also drop frame when lacking of queued buffer. + */ + next_buf = list_next_entry(buf, list); + sun6i_video_buffer_configure(csi_dev, next_buf); + + spin_unlock_irqrestore(&video->dma_queue_lock, flags); + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto error_stream; + + return 0; + +error_stream: + sun6i_csi_set_stream(csi_dev, false); + +error_media_pipeline: + video_device_pipeline_stop(video_dev); + +error_dma_queue_flush: + spin_lock_irqsave(&video->dma_queue_lock, flags); + list_for_each_entry(buf, &video->dma_queue, list) + vb2_buffer_done(&buf->v4l2_buffer.vb2_buf, + VB2_BUF_STATE_QUEUED); + INIT_LIST_HEAD(&video->dma_queue); + spin_unlock_irqrestore(&video->dma_queue_lock, flags); + + return ret; +} + +static void sun6i_video_stop_streaming(struct vb2_queue *queue) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_video *video = &csi_dev->video; + struct v4l2_subdev *subdev; + unsigned long flags; + struct sun6i_csi_buffer *buf; + + subdev = sun6i_video_remote_subdev(video, NULL); + if (subdev) + v4l2_subdev_call(subdev, video, s_stream, 0); + + sun6i_csi_set_stream(csi_dev, false); + + video_device_pipeline_stop(&video->video_dev); + + /* Release all active buffers */ + spin_lock_irqsave(&video->dma_queue_lock, flags); + list_for_each_entry(buf, &video->dma_queue, list) + vb2_buffer_done(&buf->v4l2_buffer.vb2_buf, VB2_BUF_STATE_ERROR); + INIT_LIST_HEAD(&video->dma_queue); + spin_unlock_irqrestore(&video->dma_queue_lock, flags); +} + +void sun6i_video_frame_done(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_video *video = &csi_dev->video; + struct sun6i_csi_buffer *buf; + struct sun6i_csi_buffer *next_buf; + struct vb2_v4l2_buffer *v4l2_buffer; + + spin_lock(&video->dma_queue_lock); + + buf = list_first_entry(&video->dma_queue, + struct sun6i_csi_buffer, list); + if (list_is_last(&buf->list, &video->dma_queue)) { + dev_dbg(csi_dev->dev, "Frame dropped!\n"); + goto complete; + } + + next_buf = list_next_entry(buf, list); + /* If a new buffer (#next_buf) had not been queued to CSI, the old + * buffer (#buf) is still holding by CSI for storing the next + * frame. So, we queue a new buffer (#next_buf) to CSI then wait + * for next ISR call. + */ + if (!next_buf->queued_to_csi) { + sun6i_video_buffer_configure(csi_dev, next_buf); + dev_dbg(csi_dev->dev, "Frame dropped!\n"); + goto complete; + } + + list_del(&buf->list); + v4l2_buffer = &buf->v4l2_buffer; + v4l2_buffer->vb2_buf.timestamp = ktime_get_ns(); + v4l2_buffer->sequence = video->sequence; + vb2_buffer_done(&v4l2_buffer->vb2_buf, VB2_BUF_STATE_DONE); + + /* Prepare buffer for next frame but one. */ + if (!list_is_last(&next_buf->list, &video->dma_queue)) { + next_buf = list_next_entry(next_buf, list); + sun6i_video_buffer_configure(csi_dev, next_buf); + } else { + dev_dbg(csi_dev->dev, "Next frame will be dropped!\n"); + } + +complete: + video->sequence++; + spin_unlock(&video->dma_queue_lock); +} + +static const struct vb2_ops sun6i_video_queue_ops = { + .queue_setup = sun6i_video_queue_setup, + .buf_prepare = sun6i_video_buffer_prepare, + .buf_queue = sun6i_video_buffer_queue, + .start_streaming = sun6i_video_start_streaming, + .stop_streaming = sun6i_video_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* V4L2 Device */ + +static int sun6i_video_querycap(struct file *file, void *private, + struct v4l2_capability *capability) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct video_device *video_dev = &csi_dev->video.video_dev; + + strscpy(capability->driver, SUN6I_CSI_NAME, sizeof(capability->driver)); + strscpy(capability->card, video_dev->name, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", dev_name(csi_dev->dev)); + + return 0; +} + +static int sun6i_video_enum_fmt(struct file *file, void *private, + struct v4l2_fmtdesc *fmtdesc) +{ + u32 index = fmtdesc->index; + + if (index >= ARRAY_SIZE(sun6i_video_formats)) + return -EINVAL; + + fmtdesc->pixelformat = sun6i_video_formats[index]; + + return 0; +} + +static int sun6i_video_g_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_video *video = &csi_dev->video; + + *format = video->format; + + return 0; +} + +static int sun6i_video_format_try(struct sun6i_video *video, + struct v4l2_format *format) +{ + struct v4l2_pix_format *pix_format = &format->fmt.pix; + int bpp; + + if (!sun6i_video_format_check(pix_format->pixelformat)) + pix_format->pixelformat = sun6i_video_formats[0]; + + v4l_bound_align_image(&pix_format->width, MIN_WIDTH, MAX_WIDTH, 1, + &pix_format->height, MIN_HEIGHT, MAX_WIDTH, 1, 1); + + bpp = sun6i_csi_get_bpp(pix_format->pixelformat); + pix_format->bytesperline = (pix_format->width * bpp) >> 3; + pix_format->sizeimage = pix_format->bytesperline * pix_format->height; + + if (pix_format->field == V4L2_FIELD_ANY) + pix_format->field = V4L2_FIELD_NONE; + + if (pix_format->pixelformat == V4L2_PIX_FMT_JPEG) + pix_format->colorspace = V4L2_COLORSPACE_JPEG; + else + pix_format->colorspace = V4L2_COLORSPACE_SRGB; + + pix_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pix_format->quantization = V4L2_QUANTIZATION_DEFAULT; + pix_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; + + return 0; +} + +static int sun6i_video_format_set(struct sun6i_video *video, + struct v4l2_format *format) +{ + int ret; + + ret = sun6i_video_format_try(video, format); + if (ret) + return ret; + + video->format = *format; + + return 0; +} + +static int sun6i_video_s_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_video *video = &csi_dev->video; + + if (vb2_is_busy(&video->queue)) + return -EBUSY; + + return sun6i_video_format_set(video, format); +} + +static int sun6i_video_try_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_video *video = &csi_dev->video; + + return sun6i_video_format_try(video, format); +} + +static int sun6i_video_enum_input(struct file *file, void *private, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, "Camera", sizeof(input->name)); + + return 0; +} + +static int sun6i_video_g_input(struct file *file, void *private, + unsigned int *index) +{ + *index = 0; + + return 0; +} + +static int sun6i_video_s_input(struct file *file, void *private, + unsigned int index) +{ + if (index != 0) + return -EINVAL; + + return 0; +} + +static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = { + .vidioc_querycap = sun6i_video_querycap, + + .vidioc_enum_fmt_vid_cap = sun6i_video_enum_fmt, + .vidioc_g_fmt_vid_cap = sun6i_video_g_fmt, + .vidioc_s_fmt_vid_cap = sun6i_video_s_fmt, + .vidioc_try_fmt_vid_cap = sun6i_video_try_fmt, + + .vidioc_enum_input = sun6i_video_enum_input, + .vidioc_g_input = sun6i_video_g_input, + .vidioc_s_input = sun6i_video_s_input, + + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* V4L2 File */ + +static int sun6i_video_open(struct file *file) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_video *video = &csi_dev->video; + int ret = 0; + + if (mutex_lock_interruptible(&video->lock)) + return -ERESTARTSYS; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto error_lock; + + ret = v4l2_pipeline_pm_get(&video->video_dev.entity); + if (ret < 0) + goto error_v4l2_fh; + + /* Power on at first open. */ + if (v4l2_fh_is_singular_file(file)) { + ret = sun6i_csi_set_power(csi_dev, true); + if (ret < 0) + goto error_v4l2_fh; + } + + mutex_unlock(&video->lock); + + return 0; + +error_v4l2_fh: + v4l2_fh_release(file); + +error_lock: + mutex_unlock(&video->lock); + + return ret; +} + +static int sun6i_video_close(struct file *file) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_video *video = &csi_dev->video; + bool last_close; + + mutex_lock(&video->lock); + + last_close = v4l2_fh_is_singular_file(file); + + _vb2_fop_release(file, NULL); + v4l2_pipeline_pm_put(&video->video_dev.entity); + + /* Power off at last close. */ + if (last_close) + sun6i_csi_set_power(csi_dev, false); + + mutex_unlock(&video->lock); + + return 0; +} + +static const struct v4l2_file_operations sun6i_video_fops = { + .owner = THIS_MODULE, + .open = sun6i_video_open, + .release = sun6i_video_close, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll +}; + +/* Media Entity */ + +static int sun6i_video_link_validate_get_format(struct media_pad *pad, + struct v4l2_subdev_format *fmt) +{ + if (is_media_entity_v4l2_subdev(pad->entity)) { + struct v4l2_subdev *sd = + media_entity_to_v4l2_subdev(pad->entity); + + fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; + fmt->pad = pad->index; + return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); + } + + return -EINVAL; +} + +static int sun6i_video_link_validate(struct media_link *link) +{ + struct video_device *vdev = container_of(link->sink->entity, + struct video_device, entity); + struct sun6i_csi_device *csi_dev = video_get_drvdata(vdev); + struct sun6i_video *video = &csi_dev->video; + struct v4l2_subdev_format source_fmt; + int ret; + + video->mbus_code = 0; + + if (!media_pad_remote_pad_first(link->sink->entity->pads)) { + dev_info(csi_dev->dev, "video node %s pad not connected\n", + vdev->name); + return -ENOLINK; + } + + ret = sun6i_video_link_validate_get_format(link->source, &source_fmt); + if (ret < 0) + return ret; + + if (!sun6i_csi_is_format_supported(csi_dev, + video->format.fmt.pix.pixelformat, + source_fmt.format.code)) { + dev_err(csi_dev->dev, + "Unsupported pixformat: 0x%x with mbus code: 0x%x!\n", + video->format.fmt.pix.pixelformat, + source_fmt.format.code); + return -EPIPE; + } + + if (source_fmt.format.width != video->format.fmt.pix.width || + source_fmt.format.height != video->format.fmt.pix.height) { + dev_err(csi_dev->dev, + "Wrong width or height %ux%u (%ux%u expected)\n", + video->format.fmt.pix.width, video->format.fmt.pix.height, + source_fmt.format.width, source_fmt.format.height); + return -EPIPE; + } + + video->mbus_code = source_fmt.format.code; + + return 0; +} + +static const struct media_entity_operations sun6i_video_media_ops = { + .link_validate = sun6i_video_link_validate +}; + +/* Video */ + +int sun6i_video_setup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_video *video = &csi_dev->video; + struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; + struct video_device *video_dev = &video->video_dev; + struct vb2_queue *queue = &video->queue; + struct media_pad *pad = &video->pad; + struct v4l2_format format = { 0 }; + struct v4l2_pix_format *pix_format = &format.fmt.pix; + int ret; + + /* Media Entity */ + + video_dev->entity.ops = &sun6i_video_media_ops; + + /* Media Pad */ + + pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&video_dev->entity, 1, pad); + if (ret < 0) + return ret; + + /* DMA queue */ + + INIT_LIST_HEAD(&video->dma_queue); + spin_lock_init(&video->dma_queue_lock); + + video->sequence = 0; + + /* Queue */ + + mutex_init(&video->lock); + + queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + queue->io_modes = VB2_MMAP | VB2_DMABUF; + queue->buf_struct_size = sizeof(struct sun6i_csi_buffer); + queue->ops = &sun6i_video_queue_ops; + queue->mem_ops = &vb2_dma_contig_memops; + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + queue->lock = &video->lock; + queue->dev = csi_dev->dev; + queue->drv_priv = csi_dev; + + /* Make sure non-dropped frame. */ + queue->min_buffers_needed = 3; + + ret = vb2_queue_init(queue); + if (ret) { + v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Format */ + + format.type = queue->type; + pix_format->pixelformat = sun6i_video_formats[0]; + pix_format->width = 1280; + pix_format->height = 720; + pix_format->field = V4L2_FIELD_NONE; + + sun6i_video_format_set(video, &format); + + /* Video Device */ + + strscpy(video_dev->name, SUN6I_CSI_NAME, sizeof(video_dev->name)); + video_dev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + video_dev->vfl_dir = VFL_DIR_RX; + video_dev->release = video_device_release_empty; + video_dev->fops = &sun6i_video_fops; + video_dev->ioctl_ops = &sun6i_video_ioctl_ops; + video_dev->v4l2_dev = v4l2_dev; + video_dev->queue = queue; + video_dev->lock = &video->lock; + + video_set_drvdata(video_dev, csi_dev); + + ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to register video device: %d\n", + ret); + goto error_media_entity; + } + + return 0; + +error_media_entity: + media_entity_cleanup(&video_dev->entity); + + mutex_destroy(&video->lock); + + return ret; +} + +void sun6i_video_cleanup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_video *video = &csi_dev->video; + struct video_device *video_dev = &video->video_dev; + + vb2_video_unregister_device(video_dev); + media_entity_cleanup(&video_dev->entity); + mutex_destroy(&video->lock); +} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h new file mode 100644 index 000000000..a917d2da6 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * All rights reserved. + * Author: Yong Deng + */ + +#ifndef __SUN6I_VIDEO_H__ +#define __SUN6I_VIDEO_H__ + +#include +#include + +struct sun6i_csi_device; + +struct sun6i_video { + struct video_device video_dev; + struct vb2_queue queue; + struct mutex lock; /* Queue lock. */ + struct media_pad pad; + + struct list_head dma_queue; + spinlock_t dma_queue_lock; /* DMA queue lock. */ + + struct v4l2_format format; + u32 mbus_code; + unsigned int sequence; +}; + +int sun6i_video_setup(struct sun6i_csi_device *csi_dev); +void sun6i_video_cleanup(struct sun6i_csi_device *csi_dev); + +void sun6i_video_frame_done(struct sun6i_csi_device *csi_dev); + +#endif /* __SUN6I_VIDEO_H__ */ diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/Kconfig b/drivers/media/platform/sunxi/sun6i-mipi-csi2/Kconfig new file mode 100644 index 000000000..08852f636 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SUN6I_MIPI_CSI2 + tristate "Allwinner A31 MIPI CSI-2 Controller Driver" + depends on V4L_PLATFORM_DRIVERS && VIDEO_DEV + depends on ARCH_SUNXI || COMPILE_TEST + depends on PM && COMMON_CLK && RESET_CONTROLLER + depends on PHY_SUN6I_MIPI_DPHY + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + select GENERIC_PHY_MIPI_DPHY + select REGMAP_MMIO + help + Support for the Allwinner A31 MIPI CSI-2 controller, also found on + other platforms such as the V3/V3s. diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/Makefile b/drivers/media/platform/sunxi/sun6i-mipi-csi2/Makefile new file mode 100644 index 000000000..14e4e0381 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +sun6i-mipi-csi2-y += sun6i_mipi_csi2.o + +obj-$(CONFIG_VIDEO_SUN6I_MIPI_CSI2) += sun6i-mipi-csi2.o diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c new file mode 100644 index 000000000..484ac5f05 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c @@ -0,0 +1,771 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020-2022 Bootlin + * Author: Paul Kocialkowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun6i_mipi_csi2.h" +#include "sun6i_mipi_csi2_reg.h" + +/* Format */ + +static const struct sun6i_mipi_csi2_format sun6i_mipi_csi2_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .data_type = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .data_type = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .data_type = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .data_type = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .data_type = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .data_type = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .data_type = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .data_type = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, +}; + +static const struct sun6i_mipi_csi2_format * +sun6i_mipi_csi2_format_find(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_mipi_csi2_formats); i++) + if (sun6i_mipi_csi2_formats[i].mbus_code == mbus_code) + return &sun6i_mipi_csi2_formats[i]; + + return NULL; +} + +/* Controller */ + +static void sun6i_mipi_csi2_enable(struct sun6i_mipi_csi2_device *csi2_dev) +{ + struct regmap *regmap = csi2_dev->regmap; + + regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG, + SUN6I_MIPI_CSI2_CTL_EN, SUN6I_MIPI_CSI2_CTL_EN); +} + +static void sun6i_mipi_csi2_disable(struct sun6i_mipi_csi2_device *csi2_dev) +{ + struct regmap *regmap = csi2_dev->regmap; + + regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG, + SUN6I_MIPI_CSI2_CTL_EN, 0); +} + +static void sun6i_mipi_csi2_configure(struct sun6i_mipi_csi2_device *csi2_dev) +{ + struct regmap *regmap = csi2_dev->regmap; + unsigned int lanes_count = + csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; + struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; + const struct sun6i_mipi_csi2_format *format; + struct device *dev = csi2_dev->dev; + u32 version = 0; + + format = sun6i_mipi_csi2_format_find(mbus_format->code); + if (WARN_ON(!format)) + return; + + /* + * The enable flow in the Allwinner BSP is a bit different: the enable + * and reset bits are set together before starting the CSI controller. + * + * In mainline we enable the CSI controller first (due to subdev logic). + * One reliable way to make this work is to deassert reset, configure + * registers and enable the controller when everything's ready. + * + * However, setting the version enable bit and removing it afterwards + * appears necessary for capture to work reliably, while replacing it + * with a delay doesn't do the trick. + */ + regmap_write(regmap, SUN6I_MIPI_CSI2_CTL_REG, + SUN6I_MIPI_CSI2_CTL_RESET_N | + SUN6I_MIPI_CSI2_CTL_VERSION_EN | + SUN6I_MIPI_CSI2_CTL_UNPK_EN); + + regmap_read(regmap, SUN6I_MIPI_CSI2_VERSION_REG, &version); + + regmap_update_bits(regmap, SUN6I_MIPI_CSI2_CTL_REG, + SUN6I_MIPI_CSI2_CTL_VERSION_EN, 0); + + dev_dbg(dev, "A31 MIPI CSI-2 version: %04x\n", version); + + regmap_write(regmap, SUN6I_MIPI_CSI2_CFG_REG, + SUN6I_MIPI_CSI2_CFG_CHANNEL_MODE(1) | + SUN6I_MIPI_CSI2_CFG_LANE_COUNT(lanes_count)); + + /* + * Only a single virtual channel (index 0) is currently supported. + * While the registers do mention multiple physical channels being + * available (which can be configured to match a specific virtual + * channel or data type), it's unclear whether channels > 0 are actually + * connected and available and the reference source code only makes use + * of channel 0. + * + * Using extra channels would also require matching channels to be + * available on the CSI (and ISP) side, which is also unsure although + * some CSI implementations are said to support multiple channels for + * BT656 time-sharing. + * + * We still configure virtual channel numbers to ensure that virtual + * channel 0 only goes to channel 0. + */ + + regmap_write(regmap, SUN6I_MIPI_CSI2_VCDT_RX_REG, + SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(3, 3) | + SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(2, 2) | + SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(1, 1) | + SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(0, 0) | + SUN6I_MIPI_CSI2_VCDT_RX_CH_DT(0, format->data_type)); + + regmap_write(regmap, SUN6I_MIPI_CSI2_CH_INT_PD_REG, + SUN6I_MIPI_CSI2_CH_INT_PD_CLEAR); +} + +/* V4L2 Subdev */ + +static int sun6i_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) +{ + struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); + struct v4l2_subdev *source_subdev = csi2_dev->bridge.source_subdev; + union phy_configure_opts dphy_opts = { 0 }; + struct phy_configure_opts_mipi_dphy *dphy_cfg = &dphy_opts.mipi_dphy; + struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; + const struct sun6i_mipi_csi2_format *format; + struct phy *dphy = csi2_dev->dphy; + struct device *dev = csi2_dev->dev; + struct v4l2_ctrl *ctrl; + unsigned int lanes_count = + csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; + unsigned long pixel_rate; + int ret; + + if (!source_subdev) + return -ENODEV; + + if (!on) { + ret = v4l2_subdev_call(source_subdev, video, s_stream, 0); + goto disable; + } + + /* Runtime PM */ + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* Sensor Pixel Rate */ + + ctrl = v4l2_ctrl_find(source_subdev->ctrl_handler, V4L2_CID_PIXEL_RATE); + if (!ctrl) { + dev_err(dev, "missing sensor pixel rate\n"); + ret = -ENODEV; + goto error_pm; + } + + pixel_rate = (unsigned long)v4l2_ctrl_g_ctrl_int64(ctrl); + if (!pixel_rate) { + dev_err(dev, "missing (zero) sensor pixel rate\n"); + ret = -ENODEV; + goto error_pm; + } + + /* D-PHY */ + + if (!lanes_count) { + dev_err(dev, "missing (zero) MIPI CSI-2 lanes count\n"); + ret = -ENODEV; + goto error_pm; + } + + format = sun6i_mipi_csi2_format_find(mbus_format->code); + if (WARN_ON(!format)) { + ret = -ENODEV; + goto error_pm; + } + + phy_mipi_dphy_get_default_config(pixel_rate, format->bpp, lanes_count, + dphy_cfg); + + /* + * Note that our hardware is using DDR, which is not taken in account by + * phy_mipi_dphy_get_default_config when calculating hs_clk_rate from + * the pixel rate, lanes count and bpp. + * + * The resulting clock rate is basically the symbol rate over the whole + * link. The actual clock rate is calculated with division by two since + * DDR samples both on rising and falling edges. + */ + + dev_dbg(dev, "A31 MIPI CSI-2 config:\n"); + dev_dbg(dev, "%ld pixels/s, %u bits/pixel, %u lanes, %lu Hz clock\n", + pixel_rate, format->bpp, lanes_count, + dphy_cfg->hs_clk_rate / 2); + + ret = phy_reset(dphy); + if (ret) { + dev_err(dev, "failed to reset MIPI D-PHY\n"); + goto error_pm; + } + + ret = phy_configure(dphy, &dphy_opts); + if (ret) { + dev_err(dev, "failed to configure MIPI D-PHY\n"); + goto error_pm; + } + + /* Controller */ + + sun6i_mipi_csi2_configure(csi2_dev); + sun6i_mipi_csi2_enable(csi2_dev); + + /* D-PHY */ + + ret = phy_power_on(dphy); + if (ret) { + dev_err(dev, "failed to power on MIPI D-PHY\n"); + goto error_pm; + } + + /* Source */ + + ret = v4l2_subdev_call(source_subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto disable; + + return 0; + +disable: + if (!on) + ret = 0; + phy_power_off(dphy); + sun6i_mipi_csi2_disable(csi2_dev); + +error_pm: + pm_runtime_put(dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops sun6i_mipi_csi2_video_ops = { + .s_stream = sun6i_mipi_csi2_s_stream, +}; + +static void +sun6i_mipi_csi2_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) +{ + if (!sun6i_mipi_csi2_format_find(mbus_format->code)) + mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code; + + mbus_format->field = V4L2_FIELD_NONE; + mbus_format->colorspace = V4L2_COLORSPACE_RAW; + mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT; + mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_mipi_csi2_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state) +{ + struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); + unsigned int pad = SUN6I_MIPI_CSI2_PAD_SINK; + struct v4l2_mbus_framefmt *mbus_format = + v4l2_subdev_get_try_format(subdev, state, pad); + struct mutex *lock = &csi2_dev->bridge.lock; + + mutex_lock(lock); + + mbus_format->code = sun6i_mipi_csi2_formats[0].mbus_code; + mbus_format->width = 640; + mbus_format->height = 480; + + sun6i_mipi_csi2_mbus_format_prepare(mbus_format); + + mutex_unlock(lock); + + return 0; +} + +static int +sun6i_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code_enum) +{ + if (code_enum->index >= ARRAY_SIZE(sun6i_mipi_csi2_formats)) + return -EINVAL; + + code_enum->code = sun6i_mipi_csi2_formats[code_enum->index].mbus_code; + + return 0; +} + +static int sun6i_mipi_csi2_get_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi2_dev->bridge.lock; + + mutex_lock(lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *mbus_format = *v4l2_subdev_get_try_format(subdev, state, + format->pad); + else + *mbus_format = csi2_dev->bridge.mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static int sun6i_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_mipi_csi2_device *csi2_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi2_dev->bridge.lock; + + mutex_lock(lock); + + sun6i_mipi_csi2_mbus_format_prepare(mbus_format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(subdev, state, format->pad) = + *mbus_format; + else + csi2_dev->bridge.mbus_format = *mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops sun6i_mipi_csi2_pad_ops = { + .init_cfg = sun6i_mipi_csi2_init_cfg, + .enum_mbus_code = sun6i_mipi_csi2_enum_mbus_code, + .get_fmt = sun6i_mipi_csi2_get_fmt, + .set_fmt = sun6i_mipi_csi2_set_fmt, +}; + +static const struct v4l2_subdev_ops sun6i_mipi_csi2_subdev_ops = { + .video = &sun6i_mipi_csi2_video_ops, + .pad = &sun6i_mipi_csi2_pad_ops, +}; + +/* Media Entity */ + +static const struct media_entity_operations sun6i_mipi_csi2_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* V4L2 Async */ + +static int +sun6i_mipi_csi2_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *remote_subdev, + struct v4l2_async_subdev *async_subdev) +{ + struct v4l2_subdev *subdev = notifier->sd; + struct sun6i_mipi_csi2_device *csi2_dev = + container_of(notifier, struct sun6i_mipi_csi2_device, + bridge.notifier); + struct media_entity *sink_entity = &subdev->entity; + struct media_entity *source_entity = &remote_subdev->entity; + struct device *dev = csi2_dev->dev; + int sink_pad_index = 0; + int source_pad_index; + int ret; + + ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "missing source pad in external entity %s\n", + source_entity->name); + return -EINVAL; + } + + source_pad_index = ret; + + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name, + source_pad_index, sink_entity->name, sink_pad_index); + + ret = media_create_pad_link(source_entity, source_pad_index, + sink_entity, sink_pad_index, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "failed to create %s:%u -> %s:%u link\n", + source_entity->name, source_pad_index, + sink_entity->name, sink_pad_index); + return ret; + } + + csi2_dev->bridge.source_subdev = remote_subdev; + + return 0; +} + +static const struct v4l2_async_notifier_operations +sun6i_mipi_csi2_notifier_ops = { + .bound = sun6i_mipi_csi2_notifier_bound, +}; + +/* Bridge */ + +static int +sun6i_mipi_csi2_bridge_source_setup(struct sun6i_mipi_csi2_device *csi2_dev) +{ + struct v4l2_async_notifier *notifier = &csi2_dev->bridge.notifier; + struct v4l2_fwnode_endpoint *endpoint = &csi2_dev->bridge.endpoint; + struct v4l2_async_subdev *subdev_async; + struct fwnode_handle *handle; + struct device *dev = csi2_dev->dev; + int ret; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!handle) + return -ENODEV; + + endpoint->bus_type = V4L2_MBUS_CSI2_DPHY; + + ret = v4l2_fwnode_endpoint_parse(handle, endpoint); + if (ret) + goto complete; + + subdev_async = + v4l2_async_nf_add_fwnode_remote(notifier, handle, + struct v4l2_async_subdev); + if (IS_ERR(subdev_async)) + ret = PTR_ERR(subdev_async); + +complete: + fwnode_handle_put(handle); + + return ret; +} + +static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) +{ + struct sun6i_mipi_csi2_bridge *bridge = &csi2_dev->bridge; + struct v4l2_subdev *subdev = &bridge->subdev; + struct v4l2_async_notifier *notifier = &bridge->notifier; + struct media_pad *pads = bridge->pads; + struct device *dev = csi2_dev->dev; + bool notifier_registered = false; + int ret; + + mutex_init(&bridge->lock); + + /* V4L2 Subdev */ + + v4l2_subdev_init(subdev, &sun6i_mipi_csi2_subdev_ops); + strscpy(subdev->name, SUN6I_MIPI_CSI2_NAME, sizeof(subdev->name)); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + subdev->owner = THIS_MODULE; + subdev->dev = dev; + + v4l2_set_subdevdata(subdev, csi2_dev); + + /* Media Entity */ + + subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + subdev->entity.ops = &sun6i_mipi_csi2_entity_ops; + + /* Media Pads */ + + pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&subdev->entity, SUN6I_MIPI_CSI2_PAD_COUNT, + pads); + if (ret) + return ret; + + /* V4L2 Async */ + + v4l2_async_nf_init(notifier); + notifier->ops = &sun6i_mipi_csi2_notifier_ops; + + ret = sun6i_mipi_csi2_bridge_source_setup(csi2_dev); + if (ret && ret != -ENODEV) + goto error_v4l2_notifier_cleanup; + + /* Only register the notifier when a sensor is connected. */ + if (ret != -ENODEV) { + ret = v4l2_async_subdev_nf_register(subdev, notifier); + if (ret < 0) + goto error_v4l2_notifier_cleanup; + + notifier_registered = true; + } + + /* V4L2 Subdev */ + + ret = v4l2_async_register_subdev(subdev); + if (ret < 0) + goto error_v4l2_notifier_unregister; + + return 0; + +error_v4l2_notifier_unregister: + if (notifier_registered) + v4l2_async_nf_unregister(notifier); + +error_v4l2_notifier_cleanup: + v4l2_async_nf_cleanup(notifier); + + media_entity_cleanup(&subdev->entity); + + return ret; +} + +static void +sun6i_mipi_csi2_bridge_cleanup(struct sun6i_mipi_csi2_device *csi2_dev) +{ + struct v4l2_subdev *subdev = &csi2_dev->bridge.subdev; + struct v4l2_async_notifier *notifier = &csi2_dev->bridge.notifier; + + v4l2_async_unregister_subdev(subdev); + v4l2_async_nf_unregister(notifier); + v4l2_async_nf_cleanup(notifier); + media_entity_cleanup(&subdev->entity); +} + +/* Platform */ + +static int sun6i_mipi_csi2_suspend(struct device *dev) +{ + struct sun6i_mipi_csi2_device *csi2_dev = dev_get_drvdata(dev); + + clk_disable_unprepare(csi2_dev->clock_mod); + reset_control_assert(csi2_dev->reset); + + return 0; +} + +static int sun6i_mipi_csi2_resume(struct device *dev) +{ + struct sun6i_mipi_csi2_device *csi2_dev = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(csi2_dev->reset); + if (ret) { + dev_err(dev, "failed to deassert reset\n"); + return ret; + } + + ret = clk_prepare_enable(csi2_dev->clock_mod); + if (ret) { + dev_err(dev, "failed to enable module clock\n"); + goto error_reset; + } + + return 0; + +error_reset: + reset_control_assert(csi2_dev->reset); + + return ret; +} + +static const struct dev_pm_ops sun6i_mipi_csi2_pm_ops = { + .runtime_suspend = sun6i_mipi_csi2_suspend, + .runtime_resume = sun6i_mipi_csi2_resume, +}; + +static const struct regmap_config sun6i_mipi_csi2_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x400, +}; + +static int +sun6i_mipi_csi2_resources_setup(struct sun6i_mipi_csi2_device *csi2_dev, + struct platform_device *platform_dev) +{ + struct device *dev = csi2_dev->dev; + void __iomem *io_base; + int ret; + + /* Registers */ + + io_base = devm_platform_ioremap_resource(platform_dev, 0); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + csi2_dev->regmap = + devm_regmap_init_mmio_clk(dev, "bus", io_base, + &sun6i_mipi_csi2_regmap_config); + if (IS_ERR(csi2_dev->regmap)) { + dev_err(dev, "failed to init register map\n"); + return PTR_ERR(csi2_dev->regmap); + } + + /* Clock */ + + csi2_dev->clock_mod = devm_clk_get(dev, "mod"); + if (IS_ERR(csi2_dev->clock_mod)) { + dev_err(dev, "failed to acquire mod clock\n"); + return PTR_ERR(csi2_dev->clock_mod); + } + + ret = clk_set_rate_exclusive(csi2_dev->clock_mod, 297000000); + if (ret) { + dev_err(dev, "failed to set mod clock rate\n"); + return ret; + } + + /* Reset */ + + csi2_dev->reset = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(csi2_dev->reset)) { + dev_err(dev, "failed to get reset controller\n"); + ret = PTR_ERR(csi2_dev->reset); + goto error_clock_rate_exclusive; + } + + /* D-PHY */ + + csi2_dev->dphy = devm_phy_get(dev, "dphy"); + if (IS_ERR(csi2_dev->dphy)) { + dev_err(dev, "failed to get MIPI D-PHY\n"); + ret = PTR_ERR(csi2_dev->dphy); + goto error_clock_rate_exclusive; + } + + ret = phy_init(csi2_dev->dphy); + if (ret) { + dev_err(dev, "failed to initialize MIPI D-PHY\n"); + goto error_clock_rate_exclusive; + } + + /* Runtime PM */ + + pm_runtime_enable(dev); + + return 0; + +error_clock_rate_exclusive: + clk_rate_exclusive_put(csi2_dev->clock_mod); + + return ret; +} + +static void +sun6i_mipi_csi2_resources_cleanup(struct sun6i_mipi_csi2_device *csi2_dev) +{ + pm_runtime_disable(csi2_dev->dev); + phy_exit(csi2_dev->dphy); + clk_rate_exclusive_put(csi2_dev->clock_mod); +} + +static int sun6i_mipi_csi2_probe(struct platform_device *platform_dev) +{ + struct sun6i_mipi_csi2_device *csi2_dev; + struct device *dev = &platform_dev->dev; + int ret; + + csi2_dev = devm_kzalloc(dev, sizeof(*csi2_dev), GFP_KERNEL); + if (!csi2_dev) + return -ENOMEM; + + csi2_dev->dev = dev; + platform_set_drvdata(platform_dev, csi2_dev); + + ret = sun6i_mipi_csi2_resources_setup(csi2_dev, platform_dev); + if (ret) + return ret; + + ret = sun6i_mipi_csi2_bridge_setup(csi2_dev); + if (ret) + goto error_resources; + + return 0; + +error_resources: + sun6i_mipi_csi2_resources_cleanup(csi2_dev); + + return ret; +} + +static int sun6i_mipi_csi2_remove(struct platform_device *platform_dev) +{ + struct sun6i_mipi_csi2_device *csi2_dev = + platform_get_drvdata(platform_dev); + + sun6i_mipi_csi2_bridge_cleanup(csi2_dev); + sun6i_mipi_csi2_resources_cleanup(csi2_dev); + + return 0; +} + +static const struct of_device_id sun6i_mipi_csi2_of_match[] = { + { .compatible = "allwinner,sun6i-a31-mipi-csi2" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sun6i_mipi_csi2_of_match); + +static struct platform_driver sun6i_mipi_csi2_platform_driver = { + .probe = sun6i_mipi_csi2_probe, + .remove = sun6i_mipi_csi2_remove, + .driver = { + .name = SUN6I_MIPI_CSI2_NAME, + .of_match_table = of_match_ptr(sun6i_mipi_csi2_of_match), + .pm = &sun6i_mipi_csi2_pm_ops, + }, +}; +module_platform_driver(sun6i_mipi_csi2_platform_driver); + +MODULE_DESCRIPTION("Allwinner A31 MIPI CSI-2 Controller Driver"); +MODULE_AUTHOR("Paul Kocialkowski "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h new file mode 100644 index 000000000..24b15e34b --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2020-2022 Bootlin + * Author: Paul Kocialkowski + */ + +#ifndef _SUN6I_MIPI_CSI2_H_ +#define _SUN6I_MIPI_CSI2_H_ + +#include +#include +#include +#include +#include + +#define SUN6I_MIPI_CSI2_NAME "sun6i-mipi-csi2" + +enum sun6i_mipi_csi2_pad { + SUN6I_MIPI_CSI2_PAD_SINK = 0, + SUN6I_MIPI_CSI2_PAD_SOURCE = 1, + SUN6I_MIPI_CSI2_PAD_COUNT = 2, +}; + +struct sun6i_mipi_csi2_format { + u32 mbus_code; + u8 data_type; + u32 bpp; +}; + +struct sun6i_mipi_csi2_bridge { + struct v4l2_subdev subdev; + struct media_pad pads[SUN6I_MIPI_CSI2_PAD_COUNT]; + struct v4l2_fwnode_endpoint endpoint; + struct v4l2_async_notifier notifier; + struct v4l2_mbus_framefmt mbus_format; + struct mutex lock; /* Mbus format lock. */ + + struct v4l2_subdev *source_subdev; +}; + +struct sun6i_mipi_csi2_device { + struct device *dev; + + struct regmap *regmap; + struct clk *clock_mod; + struct reset_control *reset; + struct phy *dphy; + + struct sun6i_mipi_csi2_bridge bridge; +}; + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2_reg.h b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2_reg.h new file mode 100644 index 000000000..d9c92cf2b --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2_reg.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2020-2022 Bootlin + * Author: Paul Kocialkowski + */ + +#ifndef _SUN6I_MIPI_CSI2_REG_H_ +#define _SUN6I_MIPI_CSI2_REG_H_ + +#define SUN6I_MIPI_CSI2_CTL_REG 0x0 +#define SUN6I_MIPI_CSI2_CTL_RESET_N BIT(31) +#define SUN6I_MIPI_CSI2_CTL_VERSION_EN BIT(30) +#define SUN6I_MIPI_CSI2_CTL_UNPK_EN BIT(1) +#define SUN6I_MIPI_CSI2_CTL_EN BIT(0) + +#define SUN6I_MIPI_CSI2_CFG_REG 0x4 +#define SUN6I_MIPI_CSI2_CFG_CHANNEL_MODE(v) ((((v) - 1) << 8) & \ + GENMASK(9, 8)) +#define SUN6I_MIPI_CSI2_CFG_LANE_COUNT(v) (((v) - 1) & GENMASK(1, 0)) + +#define SUN6I_MIPI_CSI2_VCDT_RX_REG 0x8 +#define SUN6I_MIPI_CSI2_VCDT_RX_CH_VC(ch, vc) (((vc) & GENMASK(1, 0)) << \ + ((ch) * 8 + 6)) +#define SUN6I_MIPI_CSI2_VCDT_RX_CH_DT(ch, t) (((t) & GENMASK(5, 0)) << \ + ((ch) * 8)) +#define SUN6I_MIPI_CSI2_RX_PKT_NUM_REG 0xc + +#define SUN6I_MIPI_CSI2_VERSION_REG 0x3c + +#define SUN6I_MIPI_CSI2_CH_CFG_REG 0x40 +#define SUN6I_MIPI_CSI2_CH_INT_EN_REG 0x50 +#define SUN6I_MIPI_CSI2_CH_INT_EN_EOT_ERR BIT(29) +#define SUN6I_MIPI_CSI2_CH_INT_EN_CHKSUM_ERR BIT(28) +#define SUN6I_MIPI_CSI2_CH_INT_EN_ECC_WRN BIT(27) +#define SUN6I_MIPI_CSI2_CH_INT_EN_ECC_ERR BIT(26) +#define SUN6I_MIPI_CSI2_CH_INT_EN_LINE_SYNC_ERR BIT(25) +#define SUN6I_MIPI_CSI2_CH_INT_EN_FRAME_SYNC_ERR BIT(24) +#define SUN6I_MIPI_CSI2_CH_INT_EN_EMB_DATA BIT(18) +#define SUN6I_MIPI_CSI2_CH_INT_EN_PF BIT(17) +#define SUN6I_MIPI_CSI2_CH_INT_EN_PH_UPDATE BIT(16) +#define SUN6I_MIPI_CSI2_CH_INT_EN_LINE_START_SYNC BIT(11) +#define SUN6I_MIPI_CSI2_CH_INT_EN_LINE_END_SYNC BIT(10) +#define SUN6I_MIPI_CSI2_CH_INT_EN_FRAME_START_SYNC BIT(9) +#define SUN6I_MIPI_CSI2_CH_INT_EN_FRAME_END_SYNC BIT(8) +#define SUN6I_MIPI_CSI2_CH_INT_EN_FIFO_OVER BIT(0) + +#define SUN6I_MIPI_CSI2_CH_INT_PD_REG 0x58 +#define SUN6I_MIPI_CSI2_CH_INT_PD_CLEAR 0xff +#define SUN6I_MIPI_CSI2_CH_INT_PD_EOT_ERR BIT(29) +#define SUN6I_MIPI_CSI2_CH_INT_PD_CHKSUM_ERR BIT(28) +#define SUN6I_MIPI_CSI2_CH_INT_PD_ECC_WRN BIT(27) +#define SUN6I_MIPI_CSI2_CH_INT_PD_ECC_ERR BIT(26) +#define SUN6I_MIPI_CSI2_CH_INT_PD_LINE_SYNC_ERR BIT(25) +#define SUN6I_MIPI_CSI2_CH_INT_PD_FRAME_SYNC_ERR BIT(24) +#define SUN6I_MIPI_CSI2_CH_INT_PD_EMB_DATA BIT(18) +#define SUN6I_MIPI_CSI2_CH_INT_PD_PF BIT(17) +#define SUN6I_MIPI_CSI2_CH_INT_PD_PH_UPDATE BIT(16) +#define SUN6I_MIPI_CSI2_CH_INT_PD_LINE_START_SYNC BIT(11) +#define SUN6I_MIPI_CSI2_CH_INT_PD_LINE_END_SYNC BIT(10) +#define SUN6I_MIPI_CSI2_CH_INT_PD_FRAME_START_SYNC BIT(9) +#define SUN6I_MIPI_CSI2_CH_INT_PD_FRAME_END_SYNC BIT(8) +#define SUN6I_MIPI_CSI2_CH_INT_PD_FIFO_OVER BIT(0) + +#define SUN6I_MIPI_CSI2_CH_DT_TRIGGER_REG 0x60 +#define SUN6I_MIPI_CSI2_CH_CUR_PH_REG 0x70 +#define SUN6I_MIPI_CSI2_CH_ECC_REG 0x74 +#define SUN6I_MIPI_CSI2_CH_CKS_REG 0x78 +#define SUN6I_MIPI_CSI2_CH_FRAME_NUM_REG 0x7c +#define SUN6I_MIPI_CSI2_CH_LINE_NUM_REG 0x80 + +#define SUN6I_MIPI_CSI2_CH_OFFSET 0x100 + +#define SUN6I_MIPI_CSI2_CH_REG(reg, ch) \ + (SUN6I_MIPI_CSI2_CH_OFFSET * (ch) + (reg)) + +#endif diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Kconfig b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Kconfig new file mode 100644 index 000000000..47a8c0fb7 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Kconfig @@ -0,0 +1,13 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SUN8I_A83T_MIPI_CSI2 + tristate "Allwinner A83T MIPI CSI-2 Controller and D-PHY Driver" + depends on V4L_PLATFORM_DRIVERS && VIDEO_DEV + depends on ARCH_SUNXI || COMPILE_TEST + depends on PM && COMMON_CLK && RESET_CONTROLLER + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select V4L2_FWNODE + select REGMAP_MMIO + select GENERIC_PHY_MIPI_DPHY + help + Support for the Allwinner A83T MIPI CSI-2 controller and D-PHY. diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Makefile b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Makefile new file mode 100644 index 000000000..1427d15a8 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +sun8i-a83t-mipi-csi2-y += sun8i_a83t_mipi_csi2.o sun8i_a83t_dphy.o + +obj-$(CONFIG_VIDEO_SUN8I_A83T_MIPI_CSI2) += sun8i-a83t-mipi-csi2.o diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.c b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.c new file mode 100644 index 000000000..24bbcc850 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020-2022 Bootlin + * Author: Paul Kocialkowski + */ + +#include +#include + +#include "sun8i_a83t_dphy.h" +#include "sun8i_a83t_mipi_csi2.h" + +static int sun8i_a83t_dphy_configure(struct phy *dphy, + union phy_configure_opts *opts) +{ + return phy_mipi_dphy_config_validate(&opts->mipi_dphy); +} + +static int sun8i_a83t_dphy_power_on(struct phy *dphy) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = phy_get_drvdata(dphy); + struct regmap *regmap = csi2_dev->regmap; + + regmap_write(regmap, SUN8I_A83T_DPHY_CTRL_REG, + SUN8I_A83T_DPHY_CTRL_RESET_N | + SUN8I_A83T_DPHY_CTRL_SHUTDOWN_N); + + regmap_write(regmap, SUN8I_A83T_DPHY_ANA0_REG, + SUN8I_A83T_DPHY_ANA0_REXT_EN | + SUN8I_A83T_DPHY_ANA0_RINT(2) | + SUN8I_A83T_DPHY_ANA0_SNK(2)); + + return 0; +}; + +static int sun8i_a83t_dphy_power_off(struct phy *dphy) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = phy_get_drvdata(dphy); + struct regmap *regmap = csi2_dev->regmap; + + regmap_write(regmap, SUN8I_A83T_DPHY_CTRL_REG, 0); + + return 0; +}; + +static const struct phy_ops sun8i_a83t_dphy_ops = { + .configure = sun8i_a83t_dphy_configure, + .power_on = sun8i_a83t_dphy_power_on, + .power_off = sun8i_a83t_dphy_power_off, +}; + +int sun8i_a83t_dphy_register(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + struct device *dev = csi2_dev->dev; + struct phy_provider *phy_provider; + + csi2_dev->dphy = devm_phy_create(dev, NULL, &sun8i_a83t_dphy_ops); + if (IS_ERR(csi2_dev->dphy)) { + dev_err(dev, "failed to create D-PHY\n"); + return PTR_ERR(csi2_dev->dphy); + } + + phy_set_drvdata(csi2_dev->dphy, csi2_dev); + + phy_provider = devm_of_phy_provider_register(dev, of_phy_simple_xlate); + if (IS_ERR(phy_provider)) { + dev_err(dev, "failed to register D-PHY provider\n"); + return PTR_ERR(phy_provider); + } + + return 0; +} diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.h b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.h new file mode 100644 index 000000000..9ab709060 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_dphy.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2020 Kévin L'hôpital + * Copyright 2020-2022 Bootlin + * Author: Paul Kocialkowski + */ + +#ifndef _SUN8I_A83T_DPHY_H_ +#define _SUN8I_A83T_DPHY_H_ + +#include "sun8i_a83t_mipi_csi2.h" + +#define SUN8I_A83T_DPHY_CTRL_REG 0x10 +#define SUN8I_A83T_DPHY_CTRL_INIT_VALUE 0xb8df698e +#define SUN8I_A83T_DPHY_CTRL_RESET_N BIT(31) +#define SUN8I_A83T_DPHY_CTRL_SHUTDOWN_N BIT(15) +#define SUN8I_A83T_DPHY_CTRL_DEBUG BIT(8) +#define SUN8I_A83T_DPHY_STATUS_REG 0x14 +#define SUN8I_A83T_DPHY_STATUS_CLK_STOP BIT(10) +#define SUN8I_A83T_DPHY_STATUS_CLK_ULPS BIT(9) +#define SUN8I_A83T_DPHY_STATUS_HSCLK BIT(8) +#define SUN8I_A83T_DPHY_STATUS_D3_STOP BIT(7) +#define SUN8I_A83T_DPHY_STATUS_D2_STOP BIT(6) +#define SUN8I_A83T_DPHY_STATUS_D1_STOP BIT(5) +#define SUN8I_A83T_DPHY_STATUS_D0_STOP BIT(4) +#define SUN8I_A83T_DPHY_STATUS_D3_ULPS BIT(3) +#define SUN8I_A83T_DPHY_STATUS_D2_ULPS BIT(2) +#define SUN8I_A83T_DPHY_STATUS_D1_ULPS BIT(1) +#define SUN8I_A83T_DPHY_STATUS_D0_ULPS BIT(0) + +#define SUN8I_A83T_DPHY_ANA0_REG 0x30 +#define SUN8I_A83T_DPHY_ANA0_REXT_EN BIT(31) +#define SUN8I_A83T_DPHY_ANA0_REXT BIT(30) +#define SUN8I_A83T_DPHY_ANA0_RINT(v) (((v) << 28) & GENMASK(29, 28)) +#define SUN8I_A83T_DPHY_ANA0_SNK(v) (((v) << 20) & GENMASK(22, 20)) + +int sun8i_a83t_dphy_register(struct sun8i_a83t_mipi_csi2_device *csi2_dev); + +#endif diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c new file mode 100644 index 000000000..d993c09a4 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c @@ -0,0 +1,838 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2020 Kévin L'hôpital + * Copyright 2020-2022 Bootlin + * Author: Paul Kocialkowski + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sun8i_a83t_dphy.h" +#include "sun8i_a83t_mipi_csi2.h" +#include "sun8i_a83t_mipi_csi2_reg.h" + +/* Format */ + +static const struct sun8i_a83t_mipi_csi2_format +sun8i_a83t_mipi_csi2_formats[] = { + { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .data_type = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .data_type = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .data_type = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .data_type = MIPI_CSI2_DT_RAW8, + .bpp = 8, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .data_type = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .data_type = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .data_type = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .data_type = MIPI_CSI2_DT_RAW10, + .bpp = 10, + }, +}; + +static const struct sun8i_a83t_mipi_csi2_format * +sun8i_a83t_mipi_csi2_format_find(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun8i_a83t_mipi_csi2_formats); i++) + if (sun8i_a83t_mipi_csi2_formats[i].mbus_code == mbus_code) + return &sun8i_a83t_mipi_csi2_formats[i]; + + return NULL; +} + +/* Controller */ + +static void +sun8i_a83t_mipi_csi2_init(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + struct regmap *regmap = csi2_dev->regmap; + + /* + * The Allwinner BSP sets various magic values on a bunch of registers. + * This is apparently a necessary initialization process that will cause + * the capture to fail with unsolicited interrupts hitting if skipped. + * + * Most of the registers are set to proper values later, except for the + * two reserved registers. They are said to hold a "hardware lock" + * value, without more information available. + */ + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_CTRL_REG, 0); + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_CTRL_REG, + SUN8I_A83T_MIPI_CSI2_CTRL_INIT_VALUE); + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_RX_PKT_NUM_REG, 0); + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_RX_PKT_NUM_REG, + SUN8I_A83T_MIPI_CSI2_RX_PKT_NUM_INIT_VALUE); + + regmap_write(regmap, SUN8I_A83T_DPHY_CTRL_REG, 0); + regmap_write(regmap, SUN8I_A83T_DPHY_CTRL_REG, + SUN8I_A83T_DPHY_CTRL_INIT_VALUE); + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_RSVD1_REG, 0); + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_RSVD1_REG, + SUN8I_A83T_MIPI_CSI2_RSVD1_HW_LOCK_VALUE); + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_RSVD2_REG, 0); + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_RSVD2_REG, + SUN8I_A83T_MIPI_CSI2_RSVD2_HW_LOCK_VALUE); + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_CFG_REG, 0); + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_CFG_REG, + SUN8I_A83T_MIPI_CSI2_CFG_INIT_VALUE); +} + +static void +sun8i_a83t_mipi_csi2_enable(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + struct regmap *regmap = csi2_dev->regmap; + + regmap_update_bits(regmap, SUN8I_A83T_MIPI_CSI2_CFG_REG, + SUN8I_A83T_MIPI_CSI2_CFG_SYNC_EN, + SUN8I_A83T_MIPI_CSI2_CFG_SYNC_EN); +} + +static void +sun8i_a83t_mipi_csi2_disable(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + struct regmap *regmap = csi2_dev->regmap; + + regmap_update_bits(regmap, SUN8I_A83T_MIPI_CSI2_CFG_REG, + SUN8I_A83T_MIPI_CSI2_CFG_SYNC_EN, 0); + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_CTRL_REG, 0); +} + +static void +sun8i_a83t_mipi_csi2_configure(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + struct regmap *regmap = csi2_dev->regmap; + unsigned int lanes_count = + csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; + struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; + const struct sun8i_a83t_mipi_csi2_format *format; + struct device *dev = csi2_dev->dev; + u32 version = 0; + + format = sun8i_a83t_mipi_csi2_format_find(mbus_format->code); + if (WARN_ON(!format)) + return; + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_CTRL_REG, + SUN8I_A83T_MIPI_CSI2_CTRL_RESET_N); + + regmap_read(regmap, SUN8I_A83T_MIPI_CSI2_VERSION_REG, &version); + + dev_dbg(dev, "A83T MIPI CSI-2 version: %04x\n", version); + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_CFG_REG, + SUN8I_A83T_MIPI_CSI2_CFG_UNPKT_EN | + SUN8I_A83T_MIPI_CSI2_CFG_SYNC_DLY_CYCLE(8) | + SUN8I_A83T_MIPI_CSI2_CFG_N_CHANNEL(1) | + SUN8I_A83T_MIPI_CSI2_CFG_N_LANE(lanes_count)); + + /* + * Only a single virtual channel (index 0) is currently supported. + * While the registers do mention multiple physical channels being + * available (which can be configured to match a specific virtual + * channel or data type), it's unclear whether channels > 0 are actually + * connected and available and the reference source code only makes use + * of channel 0. + * + * Using extra channels would also require matching channels to be + * available on the CSI (and ISP) side, which is also unsure although + * some CSI implementations are said to support multiple channels for + * BT656 time-sharing. + * + * We still configure virtual channel numbers to ensure that virtual + * channel 0 only goes to channel 0. + */ + + regmap_write(regmap, SUN8I_A83T_MIPI_CSI2_VCDT0_REG, + SUN8I_A83T_MIPI_CSI2_VCDT0_CH_VC(3, 3) | + SUN8I_A83T_MIPI_CSI2_VCDT0_CH_VC(2, 2) | + SUN8I_A83T_MIPI_CSI2_VCDT0_CH_VC(1, 1) | + SUN8I_A83T_MIPI_CSI2_VCDT0_CH_VC(0, 0) | + SUN8I_A83T_MIPI_CSI2_VCDT0_CH_DT(0, format->data_type)); +} + +/* V4L2 Subdev */ + +static int sun8i_a83t_mipi_csi2_s_stream(struct v4l2_subdev *subdev, int on) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = + v4l2_get_subdevdata(subdev); + struct v4l2_subdev *source_subdev = csi2_dev->bridge.source_subdev; + union phy_configure_opts dphy_opts = { 0 }; + struct phy_configure_opts_mipi_dphy *dphy_cfg = &dphy_opts.mipi_dphy; + struct v4l2_mbus_framefmt *mbus_format = &csi2_dev->bridge.mbus_format; + const struct sun8i_a83t_mipi_csi2_format *format; + struct phy *dphy = csi2_dev->dphy; + struct device *dev = csi2_dev->dev; + struct v4l2_ctrl *ctrl; + unsigned int lanes_count = + csi2_dev->bridge.endpoint.bus.mipi_csi2.num_data_lanes; + unsigned long pixel_rate; + int ret; + + if (!source_subdev) + return -ENODEV; + + if (!on) { + ret = v4l2_subdev_call(source_subdev, video, s_stream, 0); + goto disable; + } + + /* Runtime PM */ + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* Sensor pixel rate */ + + ctrl = v4l2_ctrl_find(source_subdev->ctrl_handler, V4L2_CID_PIXEL_RATE); + if (!ctrl) { + dev_err(dev, "missing sensor pixel rate\n"); + ret = -ENODEV; + goto error_pm; + } + + pixel_rate = (unsigned long)v4l2_ctrl_g_ctrl_int64(ctrl); + if (!pixel_rate) { + dev_err(dev, "missing (zero) sensor pixel rate\n"); + ret = -ENODEV; + goto error_pm; + } + + /* D-PHY */ + + if (!lanes_count) { + dev_err(dev, "missing (zero) MIPI CSI-2 lanes count\n"); + ret = -ENODEV; + goto error_pm; + } + + format = sun8i_a83t_mipi_csi2_format_find(mbus_format->code); + if (WARN_ON(!format)) { + ret = -ENODEV; + goto error_pm; + } + + phy_mipi_dphy_get_default_config(pixel_rate, format->bpp, lanes_count, + dphy_cfg); + + /* + * Note that our hardware is using DDR, which is not taken in account by + * phy_mipi_dphy_get_default_config when calculating hs_clk_rate from + * the pixel rate, lanes count and bpp. + * + * The resulting clock rate is basically the symbol rate over the whole + * link. The actual clock rate is calculated with division by two since + * DDR samples both on rising and falling edges. + */ + + dev_dbg(dev, "A83T MIPI CSI-2 config:\n"); + dev_dbg(dev, "%ld pixels/s, %u bits/pixel, %u lanes, %lu Hz clock\n", + pixel_rate, format->bpp, lanes_count, + dphy_cfg->hs_clk_rate / 2); + + ret = phy_reset(dphy); + if (ret) { + dev_err(dev, "failed to reset MIPI D-PHY\n"); + goto error_pm; + } + + ret = phy_configure(dphy, &dphy_opts); + if (ret) { + dev_err(dev, "failed to configure MIPI D-PHY\n"); + goto error_pm; + } + + /* Controller */ + + sun8i_a83t_mipi_csi2_configure(csi2_dev); + sun8i_a83t_mipi_csi2_enable(csi2_dev); + + /* D-PHY */ + + ret = phy_power_on(dphy); + if (ret) { + dev_err(dev, "failed to power on MIPI D-PHY\n"); + goto error_pm; + } + + /* Source */ + + ret = v4l2_subdev_call(source_subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto disable; + + return 0; + +disable: + if (!on) + ret = 0; + phy_power_off(dphy); + sun8i_a83t_mipi_csi2_disable(csi2_dev); + +error_pm: + pm_runtime_put(dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops +sun8i_a83t_mipi_csi2_video_ops = { + .s_stream = sun8i_a83t_mipi_csi2_s_stream, +}; + +static void +sun8i_a83t_mipi_csi2_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) +{ + if (!sun8i_a83t_mipi_csi2_format_find(mbus_format->code)) + mbus_format->code = sun8i_a83t_mipi_csi2_formats[0].mbus_code; + + mbus_format->field = V4L2_FIELD_NONE; + mbus_format->colorspace = V4L2_COLORSPACE_RAW; + mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT; + mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun8i_a83t_mipi_csi2_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = + v4l2_get_subdevdata(subdev); + unsigned int pad = SUN8I_A83T_MIPI_CSI2_PAD_SINK; + struct v4l2_mbus_framefmt *mbus_format = + v4l2_subdev_get_try_format(subdev, state, pad); + struct mutex *lock = &csi2_dev->bridge.lock; + + mutex_lock(lock); + + mbus_format->code = sun8i_a83t_mipi_csi2_formats[0].mbus_code; + mbus_format->width = 640; + mbus_format->height = 480; + + sun8i_a83t_mipi_csi2_mbus_format_prepare(mbus_format); + + mutex_unlock(lock); + + return 0; +} + +static int +sun8i_a83t_mipi_csi2_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code_enum) +{ + if (code_enum->index >= ARRAY_SIZE(sun8i_a83t_mipi_csi2_formats)) + return -EINVAL; + + code_enum->code = + sun8i_a83t_mipi_csi2_formats[code_enum->index].mbus_code; + + return 0; +} + +static int sun8i_a83t_mipi_csi2_get_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = + v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi2_dev->bridge.lock; + + mutex_lock(lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *mbus_format = *v4l2_subdev_get_try_format(subdev, state, + format->pad); + else + *mbus_format = csi2_dev->bridge.mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static int sun8i_a83t_mipi_csi2_set_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = + v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi2_dev->bridge.lock; + + mutex_lock(lock); + + sun8i_a83t_mipi_csi2_mbus_format_prepare(mbus_format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(subdev, state, format->pad) = + *mbus_format; + else + csi2_dev->bridge.mbus_format = *mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops sun8i_a83t_mipi_csi2_pad_ops = { + .init_cfg = sun8i_a83t_mipi_csi2_init_cfg, + .enum_mbus_code = sun8i_a83t_mipi_csi2_enum_mbus_code, + .get_fmt = sun8i_a83t_mipi_csi2_get_fmt, + .set_fmt = sun8i_a83t_mipi_csi2_set_fmt, +}; + +static const struct v4l2_subdev_ops sun8i_a83t_mipi_csi2_subdev_ops = { + .video = &sun8i_a83t_mipi_csi2_video_ops, + .pad = &sun8i_a83t_mipi_csi2_pad_ops, +}; + +/* Media Entity */ + +static const struct media_entity_operations sun8i_a83t_mipi_csi2_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* V4L2 Async */ + +static int +sun8i_a83t_mipi_csi2_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *remote_subdev, + struct v4l2_async_subdev *async_subdev) +{ + struct v4l2_subdev *subdev = notifier->sd; + struct sun8i_a83t_mipi_csi2_device *csi2_dev = + container_of(notifier, struct sun8i_a83t_mipi_csi2_device, + bridge.notifier); + struct media_entity *sink_entity = &subdev->entity; + struct media_entity *source_entity = &remote_subdev->entity; + struct device *dev = csi2_dev->dev; + int sink_pad_index = 0; + int source_pad_index; + int ret; + + ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "missing source pad in external entity %s\n", + source_entity->name); + return -EINVAL; + } + + source_pad_index = ret; + + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name, + source_pad_index, sink_entity->name, sink_pad_index); + + ret = media_create_pad_link(source_entity, source_pad_index, + sink_entity, sink_pad_index, + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_err(dev, "failed to create %s:%u -> %s:%u link\n", + source_entity->name, source_pad_index, + sink_entity->name, sink_pad_index); + return ret; + } + + csi2_dev->bridge.source_subdev = remote_subdev; + + return 0; +} + +static const struct v4l2_async_notifier_operations +sun8i_a83t_mipi_csi2_notifier_ops = { + .bound = sun8i_a83t_mipi_csi2_notifier_bound, +}; + +/* Bridge */ + +static int +sun8i_a83t_mipi_csi2_bridge_source_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + struct v4l2_async_notifier *notifier = &csi2_dev->bridge.notifier; + struct v4l2_fwnode_endpoint *endpoint = &csi2_dev->bridge.endpoint; + struct v4l2_async_subdev *subdev_async; + struct fwnode_handle *handle; + struct device *dev = csi2_dev->dev; + int ret; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!handle) + return -ENODEV; + + endpoint->bus_type = V4L2_MBUS_CSI2_DPHY; + + ret = v4l2_fwnode_endpoint_parse(handle, endpoint); + if (ret) + goto complete; + + subdev_async = + v4l2_async_nf_add_fwnode_remote(notifier, handle, + struct v4l2_async_subdev); + if (IS_ERR(subdev_async)) + ret = PTR_ERR(subdev_async); + +complete: + fwnode_handle_put(handle); + + return ret; +} + +static int +sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + struct sun8i_a83t_mipi_csi2_bridge *bridge = &csi2_dev->bridge; + struct v4l2_subdev *subdev = &bridge->subdev; + struct v4l2_async_notifier *notifier = &bridge->notifier; + struct media_pad *pads = bridge->pads; + struct device *dev = csi2_dev->dev; + bool notifier_registered = false; + int ret; + + mutex_init(&bridge->lock); + + /* V4L2 Subdev */ + + v4l2_subdev_init(subdev, &sun8i_a83t_mipi_csi2_subdev_ops); + strscpy(subdev->name, SUN8I_A83T_MIPI_CSI2_NAME, sizeof(subdev->name)); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + subdev->owner = THIS_MODULE; + subdev->dev = dev; + + v4l2_set_subdevdata(subdev, csi2_dev); + + /* Media Entity */ + + subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + subdev->entity.ops = &sun8i_a83t_mipi_csi2_entity_ops; + + /* Media Pads */ + + pads[SUN8I_A83T_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN8I_A83T_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&subdev->entity, + SUN8I_A83T_MIPI_CSI2_PAD_COUNT, pads); + if (ret) + return ret; + + /* V4L2 Async */ + + v4l2_async_nf_init(notifier); + notifier->ops = &sun8i_a83t_mipi_csi2_notifier_ops; + + ret = sun8i_a83t_mipi_csi2_bridge_source_setup(csi2_dev); + if (ret && ret != -ENODEV) + goto error_v4l2_notifier_cleanup; + + /* Only register the notifier when a sensor is connected. */ + if (ret != -ENODEV) { + ret = v4l2_async_subdev_nf_register(subdev, notifier); + if (ret < 0) + goto error_v4l2_notifier_cleanup; + + notifier_registered = true; + } + + /* V4L2 Subdev */ + + ret = v4l2_async_register_subdev(subdev); + if (ret < 0) + goto error_v4l2_notifier_unregister; + + return 0; + +error_v4l2_notifier_unregister: + if (notifier_registered) + v4l2_async_nf_unregister(notifier); + +error_v4l2_notifier_cleanup: + v4l2_async_nf_cleanup(notifier); + + media_entity_cleanup(&subdev->entity); + + return ret; +} + +static void +sun8i_a83t_mipi_csi2_bridge_cleanup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + struct v4l2_subdev *subdev = &csi2_dev->bridge.subdev; + struct v4l2_async_notifier *notifier = &csi2_dev->bridge.notifier; + + v4l2_async_unregister_subdev(subdev); + v4l2_async_nf_unregister(notifier); + v4l2_async_nf_cleanup(notifier); + media_entity_cleanup(&subdev->entity); +} + +/* Platform */ + +static int sun8i_a83t_mipi_csi2_suspend(struct device *dev) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = dev_get_drvdata(dev); + + clk_disable_unprepare(csi2_dev->clock_misc); + clk_disable_unprepare(csi2_dev->clock_mipi); + clk_disable_unprepare(csi2_dev->clock_mod); + reset_control_assert(csi2_dev->reset); + + return 0; +} + +static int sun8i_a83t_mipi_csi2_resume(struct device *dev) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(csi2_dev->reset); + if (ret) { + dev_err(dev, "failed to deassert reset\n"); + return ret; + } + + ret = clk_prepare_enable(csi2_dev->clock_mod); + if (ret) { + dev_err(dev, "failed to enable module clock\n"); + goto error_reset; + } + + ret = clk_prepare_enable(csi2_dev->clock_mipi); + if (ret) { + dev_err(dev, "failed to enable MIPI clock\n"); + goto error_clock_mod; + } + + ret = clk_prepare_enable(csi2_dev->clock_misc); + if (ret) { + dev_err(dev, "failed to enable CSI misc clock\n"); + goto error_clock_mipi; + } + + sun8i_a83t_mipi_csi2_init(csi2_dev); + + return 0; + +error_clock_mipi: + clk_disable_unprepare(csi2_dev->clock_mipi); + +error_clock_mod: + clk_disable_unprepare(csi2_dev->clock_mod); + +error_reset: + reset_control_assert(csi2_dev->reset); + + return ret; +} + +static const struct dev_pm_ops sun8i_a83t_mipi_csi2_pm_ops = { + .runtime_suspend = sun8i_a83t_mipi_csi2_suspend, + .runtime_resume = sun8i_a83t_mipi_csi2_resume, +}; + +static const struct regmap_config sun8i_a83t_mipi_csi2_regmap_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x120, +}; + +static int +sun8i_a83t_mipi_csi2_resources_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev, + struct platform_device *platform_dev) +{ + struct device *dev = csi2_dev->dev; + void __iomem *io_base; + int ret; + + /* Registers */ + + io_base = devm_platform_ioremap_resource(platform_dev, 0); + if (IS_ERR(io_base)) + return PTR_ERR(io_base); + + csi2_dev->regmap = + devm_regmap_init_mmio_clk(dev, "bus", io_base, + &sun8i_a83t_mipi_csi2_regmap_config); + if (IS_ERR(csi2_dev->regmap)) { + dev_err(dev, "failed to init register map\n"); + return PTR_ERR(csi2_dev->regmap); + } + + /* Clocks */ + + csi2_dev->clock_mod = devm_clk_get(dev, "mod"); + if (IS_ERR(csi2_dev->clock_mod)) { + dev_err(dev, "failed to acquire mod clock\n"); + return PTR_ERR(csi2_dev->clock_mod); + } + + ret = clk_set_rate_exclusive(csi2_dev->clock_mod, 297000000); + if (ret) { + dev_err(dev, "failed to set mod clock rate\n"); + return ret; + } + + csi2_dev->clock_mipi = devm_clk_get(dev, "mipi"); + if (IS_ERR(csi2_dev->clock_mipi)) { + dev_err(dev, "failed to acquire mipi clock\n"); + ret = PTR_ERR(csi2_dev->clock_mipi); + goto error_clock_rate_exclusive; + } + + csi2_dev->clock_misc = devm_clk_get(dev, "misc"); + if (IS_ERR(csi2_dev->clock_misc)) { + dev_err(dev, "failed to acquire misc clock\n"); + ret = PTR_ERR(csi2_dev->clock_misc); + goto error_clock_rate_exclusive; + } + + /* Reset */ + + csi2_dev->reset = devm_reset_control_get_shared(dev, NULL); + if (IS_ERR(csi2_dev->reset)) { + dev_err(dev, "failed to get reset controller\n"); + ret = PTR_ERR(csi2_dev->reset); + goto error_clock_rate_exclusive; + } + + /* D-PHY */ + + ret = sun8i_a83t_dphy_register(csi2_dev); + if (ret) { + dev_err(dev, "failed to initialize MIPI D-PHY\n"); + goto error_clock_rate_exclusive; + } + + /* Runtime PM */ + + pm_runtime_enable(dev); + + return 0; + +error_clock_rate_exclusive: + clk_rate_exclusive_put(csi2_dev->clock_mod); + + return ret; +} + +static void +sun8i_a83t_mipi_csi2_resources_cleanup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) +{ + pm_runtime_disable(csi2_dev->dev); + phy_exit(csi2_dev->dphy); + clk_rate_exclusive_put(csi2_dev->clock_mod); +} + +static int sun8i_a83t_mipi_csi2_probe(struct platform_device *platform_dev) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev; + struct device *dev = &platform_dev->dev; + int ret; + + csi2_dev = devm_kzalloc(dev, sizeof(*csi2_dev), GFP_KERNEL); + if (!csi2_dev) + return -ENOMEM; + + csi2_dev->dev = dev; + platform_set_drvdata(platform_dev, csi2_dev); + + ret = sun8i_a83t_mipi_csi2_resources_setup(csi2_dev, platform_dev); + if (ret) + return ret; + + ret = sun8i_a83t_mipi_csi2_bridge_setup(csi2_dev); + if (ret) + goto error_resources; + + return 0; + +error_resources: + sun8i_a83t_mipi_csi2_resources_cleanup(csi2_dev); + + return ret; +} + +static int sun8i_a83t_mipi_csi2_remove(struct platform_device *platform_dev) +{ + struct sun8i_a83t_mipi_csi2_device *csi2_dev = + platform_get_drvdata(platform_dev); + + sun8i_a83t_mipi_csi2_bridge_cleanup(csi2_dev); + sun8i_a83t_mipi_csi2_resources_cleanup(csi2_dev); + + return 0; +} + +static const struct of_device_id sun8i_a83t_mipi_csi2_of_match[] = { + { .compatible = "allwinner,sun8i-a83t-mipi-csi2" }, + {}, +}; +MODULE_DEVICE_TABLE(of, sun8i_a83t_mipi_csi2_of_match); + +static struct platform_driver sun8i_a83t_mipi_csi2_platform_driver = { + .probe = sun8i_a83t_mipi_csi2_probe, + .remove = sun8i_a83t_mipi_csi2_remove, + .driver = { + .name = SUN8I_A83T_MIPI_CSI2_NAME, + .of_match_table = of_match_ptr(sun8i_a83t_mipi_csi2_of_match), + .pm = &sun8i_a83t_mipi_csi2_pm_ops, + }, +}; +module_platform_driver(sun8i_a83t_mipi_csi2_platform_driver); + +MODULE_DESCRIPTION("Allwinner A83T MIPI CSI-2 and D-PHY Controller Driver"); +MODULE_AUTHOR("Paul Kocialkowski "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h new file mode 100644 index 000000000..f1e64c534 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.h @@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2020 Kévin L'hôpital + * Copyright 2020-2022 Bootlin + * Author: Paul Kocialkowski + */ + +#ifndef _SUN8I_A83T_MIPI_CSI2_H_ +#define _SUN8I_A83T_MIPI_CSI2_H_ + +#include +#include +#include +#include +#include + +#define SUN8I_A83T_MIPI_CSI2_NAME "sun8i-a83t-mipi-csi2" + +enum sun8i_a83t_mipi_csi2_pad { + SUN8I_A83T_MIPI_CSI2_PAD_SINK = 0, + SUN8I_A83T_MIPI_CSI2_PAD_SOURCE = 1, + SUN8I_A83T_MIPI_CSI2_PAD_COUNT = 2, +}; + +struct sun8i_a83t_mipi_csi2_format { + u32 mbus_code; + u8 data_type; + u32 bpp; +}; + +struct sun8i_a83t_mipi_csi2_bridge { + struct v4l2_subdev subdev; + struct media_pad pads[SUN8I_A83T_MIPI_CSI2_PAD_COUNT]; + struct v4l2_fwnode_endpoint endpoint; + struct v4l2_async_notifier notifier; + struct v4l2_mbus_framefmt mbus_format; + struct mutex lock; /* Mbus format lock. */ + + struct v4l2_subdev *source_subdev; +}; + +struct sun8i_a83t_mipi_csi2_device { + struct device *dev; + + struct regmap *regmap; + struct clk *clock_mod; + struct clk *clock_mipi; + struct clk *clock_misc; + struct reset_control *reset; + struct phy *dphy; + + struct sun8i_a83t_mipi_csi2_bridge bridge; +}; + +#endif diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2_reg.h b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2_reg.h new file mode 100644 index 000000000..2cfc9eb49 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2_reg.h @@ -0,0 +1,151 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2020 Kévin L'hôpital + * Copyright 2020-2022 Bootlin + * Author: Paul Kocialkowski + */ + +#ifndef _SUN8I_A83T_MIPI_CSI2_REG_H_ +#define _SUN8I_A83T_MIPI_CSI2_REG_H_ + +#define SUN8I_A83T_MIPI_CSI2_VERSION_REG 0x0 +#define SUN8I_A83T_MIPI_CSI2_CTRL_REG 0x4 +#define SUN8I_A83T_MIPI_CSI2_CTRL_INIT_VALUE 0xb8c39bec +#define SUN8I_A83T_MIPI_CSI2_CTRL_RESET_N BIT(31) +#define SUN8I_A83T_MIPI_CSI2_RX_PKT_NUM_REG 0x8 +#define SUN8I_A83T_MIPI_CSI2_RX_PKT_NUM_INIT_VALUE 0xb8d257f8 +#define SUN8I_A83T_MIPI_CSI2_RSVD0_REG 0xc + +#define SUN8I_A83T_MIPI_CSI2_RSVD1_REG 0x18 +#define SUN8I_A83T_MIPI_CSI2_RSVD1_HW_LOCK_VALUE 0xb8c8a30c +#define SUN8I_A83T_MIPI_CSI2_RSVD2_REG 0x1c +#define SUN8I_A83T_MIPI_CSI2_RSVD2_HW_LOCK_VALUE 0xb8df8ad7 +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_REG 0x20 +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_ECC_ERR_DBL BIT(28) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LINE_CKSM_ERR_VC3 BIT(27) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LINE_CKSM_ERR_VC2 BIT(26) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LINE_CKSM_ERR_VC1 BIT(25) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LINE_CKSM_ERR_VC0 BIT(24) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LINE_SEQ_ERR_DT3 BIT(23) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LINE_SEQ_ERR_DT2 BIT(22) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LINE_SEQ_ERR_DT1 BIT(21) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LINE_SEQ_ERR_DT0 BIT(20) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LS_LE_ERR_DT3 BIT(19) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LS_LE_ERR_DT2 BIT(18) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LS_LE_ERR_DT1 BIT(17) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_LS_LE_ERR_DT0 BIT(16) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_CRC_ERR_VC3 BIT(15) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_CRC_ERR_VC2 BIT(14) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_CRC_ERR_VC1 BIT(13) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_CRC_ERR_VC0 BIT(12) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_FRM_SEQ_ERR_VC3 BIT(11) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_FRM_SEQ_ERR_VC2 BIT(10) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_FRM_SEQ_ERR_VC1 BIT(9) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_FRM_SEQ_ERR_VC0 BIT(8) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_FS_FE_ERR_VC3 BIT(7) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_FS_FE_ERR_VC2 BIT(6) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_FS_FE_ERR_VC1 BIT(5) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_FS_FE_ERR_VC0 BIT(4) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_SOT_SYNC_ERR_3 BIT(3) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_SOT_SYNC_ERR_2 BIT(2) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_SOT_SYNC_ERR_1 BIT(1) +#define SUN8I_A83T_MIPI_CSI2_INT_STA0_SOT_SYNC_ERR_0 BIT(0) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_REG 0x24 +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_LINE_SEQ_ERR_DT7 BIT(23) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_LINE_SEQ_ERR_DT6 BIT(22) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_LINE_SEQ_ERR_DT5 BIT(21) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_LINE_SEQ_ERR_DT4 BIT(20) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_LS_LE_ERR_DT7 BIT(19) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_LS_LE_ERR_DT6 BIT(18) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_LS_LE_ERR_DT5 BIT(17) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_LS_LE_ERR_DT4 BIT(16) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_DT_ERR_VC3 BIT(15) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_DT_ERR_VC2 BIT(14) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_DT_ERR_VC1 BIT(13) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_DT_ERR_VC0 BIT(12) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_ECC_ERR1_VC3 BIT(11) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_ECC_ERR1_VC2 BIT(10) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_ECC_ERR1_VC1 BIT(9) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_ECC_ERR1_VC0 BIT(8) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_SOT_ERR_3 BIT(7) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_SOT_ERR_2 BIT(6) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_SOT_ERR_1 BIT(5) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_SOT_ERR_0 BIT(4) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_ESC_ENTRY_ERR_3 BIT(3) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_ESC_ENTRY_ERR_2 BIT(2) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_ESC_ENTRY_ERR_1 BIT(1) +#define SUN8I_A83T_MIPI_CSI2_INT_STA1_ESC_ENTRY_ERR_0 BIT(0) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_REG 0x28 +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_ECC_ERR_DBL BIT(28) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_CKSM_ERR_VC3 BIT(27) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_CKSM_ERR_VC2 BIT(26) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_CKSM_ERR_VC1 BIT(25) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_CKSM_ERR_VC0 BIT(24) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_LINE_SEQ_ERR_DT3 BIT(23) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_LINE_SEQ_ERR_DT2 BIT(22) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_LINE_SEQ_ERR_DT1 BIT(21) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_LINE_SEQ_ERR_DT0 BIT(20) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_LS_LE_ERR_DT3 BIT(19) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_LS_LE_ERR_DT2 BIT(18) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_LS_LE_ERR_DT1 BIT(17) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_LS_LE_ERR_DT0 BIT(16) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_CRC_ERR_VC3 BIT(15) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_CRC_ERR_VC2 BIT(14) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_CRC_ERR_VC1 BIT(13) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_CRC_ERR_VC0 BIT(12) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_FRM_SEQ_ERR_VC3 BIT(11) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_FRM_SEQ_ERR_VC2 BIT(10) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_FRM_SEQ_ERR_VC1 BIT(9) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_FRM_SEQ_ERR_VC0 BIT(8) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_FS_FE_ERR_VC3 BIT(7) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_FS_FE_ERR_VC2 BIT(6) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_FS_FE_ERR_VC1 BIT(5) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_FS_FE_ERR_VC0 BIT(4) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_SOT_SYNC_ERR_3 BIT(3) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_SOT_SYNC_ERR_2 BIT(2) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_SOT_SYNC_ERR_1 BIT(1) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK0_SOT_SYNC_ERR_0 BIT(0) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_REG 0x2c +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_DT_ERR_VC3 BIT(15) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_DT_ERR_VC2 BIT(14) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_DT_ERR_VC1 BIT(13) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_DT_ERR_VC0 BIT(12) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_ECC_ERR1_VC3 BIT(11) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_ECC_ERR1_VC2 BIT(10) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_ECC_ERR1_VC1 BIT(9) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_ECC_ERR1_VC0 BIT(8) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_SOT_ERR_3 BIT(7) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_SOT_ERR_2 BIT(6) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_SOT_ERR_1 BIT(5) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_SOT_ERR_0 BIT(4) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_ESC_ENTRY_ERR_3 BIT(3) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_ESC_ENTRY_ERR_2 BIT(2) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_ESC_ENTRY_ERR_1 BIT(1) +#define SUN8I_A83T_MIPI_CSI2_INT_MSK1_ESC_ENTRY_ERR_0 BIT(0) + +#define SUN8I_A83T_MIPI_CSI2_CFG_REG 0x100 +#define SUN8I_A83T_MIPI_CSI2_CFG_INIT_VALUE 0xb8c64f24 +#define SUN8I_A83T_MIPI_CSI2_CFG_SYNC_EN BIT(31) +#define SUN8I_A83T_MIPI_CSI2_CFG_BYPASS_ECC_EN BIT(29) +#define SUN8I_A83T_MIPI_CSI2_CFG_UNPKT_EN BIT(28) +#define SUN8I_A83T_MIPI_CSI2_CFG_NONE_UNPKT_RX_MODE BIT(27) +#define SUN8I_A83T_MIPI_CSI2_CFG_YC_SWAB BIT(26) +#define SUN8I_A83T_MIPI_CSI2_CFG_N_BYTE BIT(24) +#define SUN8I_A83T_MIPI_CSI2_CFG_SYNC_DLY_CYCLE(v) (((v) << 18) & \ + GENMASK(22, 18)) +#define SUN8I_A83T_MIPI_CSI2_CFG_N_CHANNEL(v) ((((v) - 1) << 16) & \ + GENMASK(17, 16)) +#define SUN8I_A83T_MIPI_CSI2_CFG_N_LANE(v) ((((v) - 1) << 4) & \ + GENMASK(5, 4)) +#define SUN8I_A83T_MIPI_CSI2_VCDT0_REG 0x104 +#define SUN8I_A83T_MIPI_CSI2_VCDT0_CH_VC(ch, vc) (((vc) & GENMASK(1, 0)) << \ + ((ch) * 8 + 6)) +#define SUN8I_A83T_MIPI_CSI2_VCDT0_CH_DT(ch, t) (((t) & GENMASK(5, 0)) << \ + ((ch) * 8)) +#define SUN8I_A83T_MIPI_CSI2_VCDT1_REG 0x108 +#define SUN8I_A83T_MIPI_CSI2_VCDT1_CH_VC(ch, vc) (((vc) & GENMASK(1, 0)) << \ + (((ch) - 4) * 8 + 6)) +#define SUN8I_A83T_MIPI_CSI2_VCDT1_CH_DT(ch, t) (((t) & GENMASK(5, 0)) << \ + (((ch) - 4) * 8)) + +#endif diff --git a/drivers/media/platform/sunxi/sun8i-di/Kconfig b/drivers/media/platform/sunxi/sun8i-di/Kconfig new file mode 100644 index 000000000..f68839691 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-di/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_SUN8I_DEINTERLACE + tristate "Allwinner Deinterlace driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_SUNXI || COMPILE_TEST + depends on COMMON_CLK && RESET_CONTROLLER && OF + depends on PM + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + Support for the Allwinner deinterlace unit with scaling + capability found on some SoCs, like H3. + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/sunxi/sun8i-di/Makefile b/drivers/media/platform/sunxi/sun8i-di/Makefile new file mode 100644 index 000000000..109f7e544 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-di/Makefile @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_VIDEO_SUN8I_DEINTERLACE) += sun8i-di.o diff --git a/drivers/media/platform/sunxi/sun8i-di/sun8i-di.c b/drivers/media/platform/sunxi/sun8i-di/sun8i-di.c new file mode 100644 index 000000000..aa65d70b6 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-di/sun8i-di.c @@ -0,0 +1,1016 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Allwinner sun8i deinterlacer with scaler driver + * + * Copyright (C) 2019 Jernej Skrabec + * + * Based on vim2m driver. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "sun8i-di.h" + +#define FLAG_SIZE (DEINTERLACE_MAX_WIDTH * DEINTERLACE_MAX_HEIGHT / 4) + +static u32 deinterlace_formats[] = { + V4L2_PIX_FMT_NV12, + V4L2_PIX_FMT_NV21, +}; + +static inline u32 deinterlace_read(struct deinterlace_dev *dev, u32 reg) +{ + return readl(dev->base + reg); +} + +static inline void deinterlace_write(struct deinterlace_dev *dev, + u32 reg, u32 value) +{ + writel(value, dev->base + reg); +} + +static inline void deinterlace_set_bits(struct deinterlace_dev *dev, + u32 reg, u32 bits) +{ + writel(readl(dev->base + reg) | bits, dev->base + reg); +} + +static inline void deinterlace_clr_set_bits(struct deinterlace_dev *dev, + u32 reg, u32 clr, u32 set) +{ + u32 val = readl(dev->base + reg); + + val &= ~clr; + val |= set; + + writel(val, dev->base + reg); +} + +static void deinterlace_device_run(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + struct deinterlace_dev *dev = ctx->dev; + u32 size, stride, width, height, val; + struct vb2_v4l2_buffer *src, *dst; + unsigned int hstep, vstep; + dma_addr_t addr; + + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_copy_metadata(src, dst, true); + + deinterlace_write(dev, DEINTERLACE_MOD_ENABLE, + DEINTERLACE_MOD_ENABLE_EN); + + if (ctx->field) { + deinterlace_write(dev, DEINTERLACE_TILE_FLAG0, + ctx->flag1_buf_dma); + deinterlace_write(dev, DEINTERLACE_TILE_FLAG1, + ctx->flag2_buf_dma); + } else { + deinterlace_write(dev, DEINTERLACE_TILE_FLAG0, + ctx->flag2_buf_dma); + deinterlace_write(dev, DEINTERLACE_TILE_FLAG1, + ctx->flag1_buf_dma); + } + deinterlace_write(dev, DEINTERLACE_FLAG_LINE_STRIDE, 0x200); + + width = ctx->src_fmt.width; + height = ctx->src_fmt.height; + stride = ctx->src_fmt.bytesperline; + size = stride * height; + + addr = vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0); + deinterlace_write(dev, DEINTERLACE_BUF_ADDR0, addr); + deinterlace_write(dev, DEINTERLACE_BUF_ADDR1, addr + size); + deinterlace_write(dev, DEINTERLACE_BUF_ADDR2, 0); + + deinterlace_write(dev, DEINTERLACE_LINE_STRIDE0, stride); + deinterlace_write(dev, DEINTERLACE_LINE_STRIDE1, stride); + + deinterlace_write(dev, DEINTERLACE_CH0_IN_SIZE, + DEINTERLACE_SIZE(width, height)); + deinterlace_write(dev, DEINTERLACE_CH1_IN_SIZE, + DEINTERLACE_SIZE(width / 2, height / 2)); + + val = DEINTERLACE_IN_FMT_FMT(DEINTERLACE_IN_FMT_YUV420) | + DEINTERLACE_IN_FMT_MOD(DEINTERLACE_MODE_UV_COMBINED); + switch (ctx->src_fmt.pixelformat) { + case V4L2_PIX_FMT_NV12: + val |= DEINTERLACE_IN_FMT_PS(DEINTERLACE_PS_UVUV); + break; + case V4L2_PIX_FMT_NV21: + val |= DEINTERLACE_IN_FMT_PS(DEINTERLACE_PS_VUVU); + break; + } + deinterlace_write(dev, DEINTERLACE_IN_FMT, val); + + if (ctx->prev) + addr = vb2_dma_contig_plane_dma_addr(&ctx->prev->vb2_buf, 0); + + deinterlace_write(dev, DEINTERLACE_PRELUMA, addr); + deinterlace_write(dev, DEINTERLACE_PRECHROMA, addr + size); + + val = DEINTERLACE_OUT_FMT_FMT(DEINTERLACE_OUT_FMT_YUV420SP); + switch (ctx->src_fmt.pixelformat) { + case V4L2_PIX_FMT_NV12: + val |= DEINTERLACE_OUT_FMT_PS(DEINTERLACE_PS_UVUV); + break; + case V4L2_PIX_FMT_NV21: + val |= DEINTERLACE_OUT_FMT_PS(DEINTERLACE_PS_VUVU); + break; + } + deinterlace_write(dev, DEINTERLACE_OUT_FMT, val); + + width = ctx->dst_fmt.width; + height = ctx->dst_fmt.height; + stride = ctx->dst_fmt.bytesperline; + size = stride * height; + + deinterlace_write(dev, DEINTERLACE_CH0_OUT_SIZE, + DEINTERLACE_SIZE(width, height)); + deinterlace_write(dev, DEINTERLACE_CH1_OUT_SIZE, + DEINTERLACE_SIZE(width / 2, height / 2)); + + deinterlace_write(dev, DEINTERLACE_WB_LINE_STRIDE0, stride); + deinterlace_write(dev, DEINTERLACE_WB_LINE_STRIDE1, stride); + + addr = vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0); + deinterlace_write(dev, DEINTERLACE_WB_ADDR0, addr); + deinterlace_write(dev, DEINTERLACE_WB_ADDR1, addr + size); + deinterlace_write(dev, DEINTERLACE_WB_ADDR2, 0); + + hstep = (ctx->src_fmt.width << 16) / ctx->dst_fmt.width; + vstep = (ctx->src_fmt.height << 16) / ctx->dst_fmt.height; + deinterlace_write(dev, DEINTERLACE_CH0_HORZ_FACT, hstep); + deinterlace_write(dev, DEINTERLACE_CH0_VERT_FACT, vstep); + deinterlace_write(dev, DEINTERLACE_CH1_HORZ_FACT, hstep); + deinterlace_write(dev, DEINTERLACE_CH1_VERT_FACT, vstep); + + deinterlace_clr_set_bits(dev, DEINTERLACE_FIELD_CTRL, + DEINTERLACE_FIELD_CTRL_FIELD_CNT_MSK, + DEINTERLACE_FIELD_CTRL_FIELD_CNT(ctx->field)); + + deinterlace_set_bits(dev, DEINTERLACE_FRM_CTRL, + DEINTERLACE_FRM_CTRL_START); + + deinterlace_set_bits(dev, DEINTERLACE_FRM_CTRL, + DEINTERLACE_FRM_CTRL_REG_READY); + + deinterlace_set_bits(dev, DEINTERLACE_INT_ENABLE, + DEINTERLACE_INT_ENABLE_WB_EN); + + deinterlace_set_bits(dev, DEINTERLACE_FRM_CTRL, + DEINTERLACE_FRM_CTRL_WB_EN); +} + +static int deinterlace_job_ready(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + + return v4l2_m2m_num_src_bufs_ready(ctx->fh.m2m_ctx) >= 1 && + v4l2_m2m_num_dst_bufs_ready(ctx->fh.m2m_ctx) >= 2; +} + +static void deinterlace_job_abort(void *priv) +{ + struct deinterlace_ctx *ctx = priv; + + /* Will cancel the transaction in the next interrupt handler */ + ctx->aborting = 1; +} + +static irqreturn_t deinterlace_irq(int irq, void *data) +{ + struct deinterlace_dev *dev = data; + struct vb2_v4l2_buffer *src, *dst; + enum vb2_buffer_state state; + struct deinterlace_ctx *ctx; + unsigned int val; + + ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + if (!ctx) { + v4l2_err(&dev->v4l2_dev, + "Instance released before the end of transaction\n"); + return IRQ_NONE; + } + + val = deinterlace_read(dev, DEINTERLACE_INT_STATUS); + if (!(val & DEINTERLACE_INT_STATUS_WRITEBACK)) + return IRQ_NONE; + + deinterlace_write(dev, DEINTERLACE_INT_ENABLE, 0); + deinterlace_set_bits(dev, DEINTERLACE_INT_STATUS, + DEINTERLACE_INT_STATUS_WRITEBACK); + deinterlace_write(dev, DEINTERLACE_MOD_ENABLE, 0); + deinterlace_clr_set_bits(dev, DEINTERLACE_FRM_CTRL, + DEINTERLACE_FRM_CTRL_START, 0); + + val = deinterlace_read(dev, DEINTERLACE_STATUS); + if (val & DEINTERLACE_STATUS_WB_ERROR) + state = VB2_BUF_STATE_ERROR; + else + state = VB2_BUF_STATE_DONE; + + dst = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(dst, state); + + if (ctx->field != ctx->first_field || ctx->aborting) { + ctx->field = ctx->first_field; + + src = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + if (ctx->prev) + v4l2_m2m_buf_done(ctx->prev, state); + ctx->prev = src; + + v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx); + } else { + ctx->field = !ctx->first_field; + deinterlace_device_run(ctx); + } + + return IRQ_HANDLED; +} + +static void deinterlace_init(struct deinterlace_dev *dev) +{ + u32 val; + int i; + + deinterlace_write(dev, DEINTERLACE_BYPASS, + DEINTERLACE_BYPASS_CSC); + deinterlace_write(dev, DEINTERLACE_WB_LINE_STRIDE_CTRL, + DEINTERLACE_WB_LINE_STRIDE_CTRL_EN); + deinterlace_set_bits(dev, DEINTERLACE_FRM_CTRL, + DEINTERLACE_FRM_CTRL_OUT_CTRL); + deinterlace_write(dev, DEINTERLACE_AGTH_SEL, + DEINTERLACE_AGTH_SEL_LINEBUF); + + val = DEINTERLACE_CTRL_EN | + DEINTERLACE_CTRL_MODE_MIXED | + DEINTERLACE_CTRL_DIAG_INTP_EN | + DEINTERLACE_CTRL_TEMP_DIFF_EN; + deinterlace_write(dev, DEINTERLACE_CTRL, val); + + deinterlace_clr_set_bits(dev, DEINTERLACE_LUMA_TH, + DEINTERLACE_LUMA_TH_MIN_LUMA_MSK, + DEINTERLACE_LUMA_TH_MIN_LUMA(4)); + + deinterlace_clr_set_bits(dev, DEINTERLACE_SPAT_COMP, + DEINTERLACE_SPAT_COMP_TH2_MSK, + DEINTERLACE_SPAT_COMP_TH2(5)); + + deinterlace_clr_set_bits(dev, DEINTERLACE_TEMP_DIFF, + DEINTERLACE_TEMP_DIFF_AMBIGUITY_TH_MSK, + DEINTERLACE_TEMP_DIFF_AMBIGUITY_TH(5)); + + val = DEINTERLACE_DIAG_INTP_TH0(60) | + DEINTERLACE_DIAG_INTP_TH1(0) | + DEINTERLACE_DIAG_INTP_TH3(30); + deinterlace_write(dev, DEINTERLACE_DIAG_INTP, val); + + deinterlace_clr_set_bits(dev, DEINTERLACE_CHROMA_DIFF, + DEINTERLACE_CHROMA_DIFF_TH_MSK, + DEINTERLACE_CHROMA_DIFF_TH(5)); + + /* neutral filter coefficients */ + deinterlace_set_bits(dev, DEINTERLACE_FRM_CTRL, + DEINTERLACE_FRM_CTRL_COEF_ACCESS); + readl_poll_timeout(dev->base + DEINTERLACE_STATUS, val, + val & DEINTERLACE_STATUS_COEF_STATUS, 2, 40); + + for (i = 0; i < 32; i++) { + deinterlace_write(dev, DEINTERLACE_CH0_HORZ_COEF0 + i * 4, + DEINTERLACE_IDENTITY_COEF); + deinterlace_write(dev, DEINTERLACE_CH0_VERT_COEF + i * 4, + DEINTERLACE_IDENTITY_COEF); + deinterlace_write(dev, DEINTERLACE_CH1_HORZ_COEF0 + i * 4, + DEINTERLACE_IDENTITY_COEF); + deinterlace_write(dev, DEINTERLACE_CH1_VERT_COEF + i * 4, + DEINTERLACE_IDENTITY_COEF); + } + + deinterlace_clr_set_bits(dev, DEINTERLACE_FRM_CTRL, + DEINTERLACE_FRM_CTRL_COEF_ACCESS, 0); +} + +static inline struct deinterlace_ctx *deinterlace_file2ctx(struct file *file) +{ + return container_of(file->private_data, struct deinterlace_ctx, fh); +} + +static bool deinterlace_check_format(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(deinterlace_formats); i++) + if (deinterlace_formats[i] == pixelformat) + return true; + + return false; +} + +static void deinterlace_prepare_format(struct v4l2_pix_format *pix_fmt) +{ + unsigned int height = pix_fmt->height; + unsigned int width = pix_fmt->width; + unsigned int bytesperline; + unsigned int sizeimage; + + width = clamp(width, DEINTERLACE_MIN_WIDTH, + DEINTERLACE_MAX_WIDTH); + height = clamp(height, DEINTERLACE_MIN_HEIGHT, + DEINTERLACE_MAX_HEIGHT); + + bytesperline = ALIGN(width, 2); + /* luma */ + sizeimage = bytesperline * height; + /* chroma */ + sizeimage += bytesperline * height / 2; + + pix_fmt->width = width; + pix_fmt->height = height; + pix_fmt->bytesperline = bytesperline; + pix_fmt->sizeimage = sizeimage; +} + +static int deinterlace_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, DEINTERLACE_NAME, sizeof(cap->driver)); + strscpy(cap->card, DEINTERLACE_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", DEINTERLACE_NAME); + + return 0; +} + +static int deinterlace_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index < ARRAY_SIZE(deinterlace_formats)) { + f->pixelformat = deinterlace_formats[f->index]; + + return 0; + } + + return -EINVAL; +} + +static int deinterlace_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index != 0) + return -EINVAL; + + if (!deinterlace_check_format(fsize->pixel_format)) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = DEINTERLACE_MIN_WIDTH; + fsize->stepwise.min_height = DEINTERLACE_MIN_HEIGHT; + fsize->stepwise.max_width = DEINTERLACE_MAX_WIDTH; + fsize->stepwise.max_height = DEINTERLACE_MAX_HEIGHT; + fsize->stepwise.step_width = 2; + fsize->stepwise.step_height = 1; + + return 0; +} + +static int deinterlace_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); + + f->fmt.pix = ctx->dst_fmt; + + return 0; +} + +static int deinterlace_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); + + f->fmt.pix = ctx->src_fmt; + + return 0; +} + +static int deinterlace_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + if (!deinterlace_check_format(f->fmt.pix.pixelformat)) + f->fmt.pix.pixelformat = deinterlace_formats[0]; + + if (f->fmt.pix.field != V4L2_FIELD_NONE) + f->fmt.pix.field = V4L2_FIELD_NONE; + + deinterlace_prepare_format(&f->fmt.pix); + + return 0; +} + +static int deinterlace_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + if (!deinterlace_check_format(f->fmt.pix.pixelformat)) + f->fmt.pix.pixelformat = deinterlace_formats[0]; + + if (f->fmt.pix.field != V4L2_FIELD_INTERLACED_TB && + f->fmt.pix.field != V4L2_FIELD_INTERLACED_BT && + f->fmt.pix.field != V4L2_FIELD_INTERLACED) + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + + deinterlace_prepare_format(&f->fmt.pix); + + return 0; +} + +static int deinterlace_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); + struct vb2_queue *vq; + int ret; + + ret = deinterlace_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) + return -EBUSY; + + ctx->dst_fmt = f->fmt.pix; + + return 0; +} + +static int deinterlace_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct deinterlace_ctx *ctx = deinterlace_file2ctx(file); + struct vb2_queue *vq; + int ret; + + ret = deinterlace_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) + return -EBUSY; + + ctx->src_fmt = f->fmt.pix; + + /* Propagate colorspace information to capture. */ + ctx->dst_fmt.colorspace = f->fmt.pix.colorspace; + ctx->dst_fmt.xfer_func = f->fmt.pix.xfer_func; + ctx->dst_fmt.ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->dst_fmt.quantization = f->fmt.pix.quantization; + + return 0; +} + +static const struct v4l2_ioctl_ops deinterlace_ioctl_ops = { + .vidioc_querycap = deinterlace_querycap, + + .vidioc_enum_framesizes = deinterlace_enum_framesizes, + + .vidioc_enum_fmt_vid_cap = deinterlace_enum_fmt, + .vidioc_g_fmt_vid_cap = deinterlace_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = deinterlace_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = deinterlace_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = deinterlace_enum_fmt, + .vidioc_g_fmt_vid_out = deinterlace_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = deinterlace_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = deinterlace_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, +}; + +static int deinterlace_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + pix_fmt = &ctx->src_fmt; + else + pix_fmt = &ctx->dst_fmt; + + if (*nplanes) { + if (sizes[0] < pix_fmt->sizeimage) + return -EINVAL; + } else { + sizes[0] = pix_fmt->sizeimage; + *nplanes = 1; + } + + return 0; +} + +static int deinterlace_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + pix_fmt = &ctx->src_fmt; + else + pix_fmt = &ctx->dst_fmt; + + if (vb2_plane_size(vb, 0) < pix_fmt->sizeimage) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, pix_fmt->sizeimage); + + return 0; +} + +static void deinterlace_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void deinterlace_queue_cleanup(struct vb2_queue *vq, u32 state) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf; + + do { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (vbuf) + v4l2_m2m_buf_done(vbuf, state); + } while (vbuf); + + if (V4L2_TYPE_IS_OUTPUT(vq->type) && ctx->prev) + v4l2_m2m_buf_done(ctx->prev, state); +} + +static int deinterlace_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); + struct device *dev = ctx->dev->dev; + int ret; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "Failed to enable module\n"); + + goto err_runtime_get; + } + + ctx->first_field = + ctx->src_fmt.field == V4L2_FIELD_INTERLACED_BT; + ctx->field = ctx->first_field; + + ctx->prev = NULL; + ctx->aborting = 0; + + ctx->flag1_buf = dma_alloc_coherent(dev, FLAG_SIZE, + &ctx->flag1_buf_dma, + GFP_KERNEL); + if (!ctx->flag1_buf) { + ret = -ENOMEM; + + goto err_no_mem1; + } + + ctx->flag2_buf = dma_alloc_coherent(dev, FLAG_SIZE, + &ctx->flag2_buf_dma, + GFP_KERNEL); + if (!ctx->flag2_buf) { + ret = -ENOMEM; + + goto err_no_mem2; + } + } + + return 0; + +err_no_mem2: + dma_free_coherent(dev, FLAG_SIZE, ctx->flag1_buf, + ctx->flag1_buf_dma); +err_no_mem1: + pm_runtime_put(dev); +err_runtime_get: + deinterlace_queue_cleanup(vq, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void deinterlace_stop_streaming(struct vb2_queue *vq) +{ + struct deinterlace_ctx *ctx = vb2_get_drv_priv(vq); + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + struct device *dev = ctx->dev->dev; + + dma_free_coherent(dev, FLAG_SIZE, ctx->flag1_buf, + ctx->flag1_buf_dma); + dma_free_coherent(dev, FLAG_SIZE, ctx->flag2_buf, + ctx->flag2_buf_dma); + + pm_runtime_put(dev); + } + + deinterlace_queue_cleanup(vq, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops deinterlace_qops = { + .queue_setup = deinterlace_queue_setup, + .buf_prepare = deinterlace_buf_prepare, + .buf_queue = deinterlace_buf_queue, + .start_streaming = deinterlace_start_streaming, + .stop_streaming = deinterlace_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int deinterlace_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct deinterlace_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->min_buffers_needed = 1; + src_vq->ops = &deinterlace_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->dev_mutex; + src_vq->dev = ctx->dev->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->min_buffers_needed = 2; + dst_vq->ops = &deinterlace_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->dev_mutex; + dst_vq->dev = ctx->dev->dev; + + ret = vb2_queue_init(dst_vq); + if (ret) + return ret; + + return 0; +} + +static int deinterlace_open(struct file *file) +{ + struct deinterlace_dev *dev = video_drvdata(file); + struct deinterlace_ctx *ctx = NULL; + int ret; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + mutex_unlock(&dev->dev_mutex); + return -ENOMEM; + } + + /* default output format */ + ctx->src_fmt.pixelformat = deinterlace_formats[0]; + ctx->src_fmt.field = V4L2_FIELD_INTERLACED; + ctx->src_fmt.width = 640; + ctx->src_fmt.height = 480; + deinterlace_prepare_format(&ctx->src_fmt); + + /* default capture format */ + ctx->dst_fmt.pixelformat = deinterlace_formats[0]; + ctx->dst_fmt.field = V4L2_FIELD_NONE; + ctx->dst_fmt.width = 640; + ctx->dst_fmt.height = 480; + deinterlace_prepare_format(&ctx->dst_fmt); + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, + &deinterlace_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto err_free; + } + + v4l2_fh_add(&ctx->fh); + + mutex_unlock(&dev->dev_mutex); + + return 0; + +err_free: + kfree(ctx); + mutex_unlock(&dev->dev_mutex); + + return ret; +} + +static int deinterlace_release(struct file *file) +{ + struct deinterlace_dev *dev = video_drvdata(file); + struct deinterlace_ctx *ctx = container_of(file->private_data, + struct deinterlace_ctx, fh); + + mutex_lock(&dev->dev_mutex); + + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + kfree(ctx); + + mutex_unlock(&dev->dev_mutex); + + return 0; +} + +static const struct v4l2_file_operations deinterlace_fops = { + .owner = THIS_MODULE, + .open = deinterlace_open, + .release = deinterlace_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device deinterlace_video_device = { + .name = DEINTERLACE_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &deinterlace_fops, + .ioctl_ops = &deinterlace_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops deinterlace_m2m_ops = { + .device_run = deinterlace_device_run, + .job_ready = deinterlace_job_ready, + .job_abort = deinterlace_job_abort, +}; + +static int deinterlace_probe(struct platform_device *pdev) +{ + struct deinterlace_dev *dev; + struct video_device *vfd; + int irq, ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->vfd = deinterlace_video_device; + dev->dev = &pdev->dev; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq; + + ret = devm_request_irq(dev->dev, irq, deinterlace_irq, + 0, dev_name(dev->dev), dev); + if (ret) { + dev_err(dev->dev, "Failed to request IRQ\n"); + + return ret; + } + + dev->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->base)) + return PTR_ERR(dev->base); + + dev->bus_clk = devm_clk_get(dev->dev, "bus"); + if (IS_ERR(dev->bus_clk)) { + dev_err(dev->dev, "Failed to get bus clock\n"); + + return PTR_ERR(dev->bus_clk); + } + + dev->mod_clk = devm_clk_get(dev->dev, "mod"); + if (IS_ERR(dev->mod_clk)) { + dev_err(dev->dev, "Failed to get mod clock\n"); + + return PTR_ERR(dev->mod_clk); + } + + dev->ram_clk = devm_clk_get(dev->dev, "ram"); + if (IS_ERR(dev->ram_clk)) { + dev_err(dev->dev, "Failed to get ram clock\n"); + + return PTR_ERR(dev->ram_clk); + } + + dev->rstc = devm_reset_control_get(dev->dev, NULL); + if (IS_ERR(dev->rstc)) { + dev_err(dev->dev, "Failed to get reset control\n"); + + return PTR_ERR(dev->rstc); + } + + mutex_init(&dev->dev_mutex); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) { + dev_err(dev->dev, "Failed to register V4L2 device\n"); + + return ret; + } + + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + snprintf(vfd->name, sizeof(vfd->name), "%s", + deinterlace_video_device.name); + video_set_drvdata(vfd, dev); + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + + goto err_v4l2; + } + + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + dev->m2m_dev = v4l2_m2m_init(&deinterlace_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, + "Failed to initialize V4L2 M2M device\n"); + ret = PTR_ERR(dev->m2m_dev); + + goto err_video; + } + + platform_set_drvdata(pdev, dev); + + pm_runtime_enable(dev->dev); + + return 0; + +err_video: + video_unregister_device(&dev->vfd); +err_v4l2: + v4l2_device_unregister(&dev->v4l2_dev); + + return ret; +} + +static int deinterlace_remove(struct platform_device *pdev) +{ + struct deinterlace_dev *dev = platform_get_drvdata(pdev); + + v4l2_m2m_release(dev->m2m_dev); + video_unregister_device(&dev->vfd); + v4l2_device_unregister(&dev->v4l2_dev); + + pm_runtime_force_suspend(&pdev->dev); + + return 0; +} + +static int deinterlace_runtime_resume(struct device *device) +{ + struct deinterlace_dev *dev = dev_get_drvdata(device); + int ret; + + ret = clk_set_rate_exclusive(dev->mod_clk, 300000000); + if (ret) { + dev_err(dev->dev, "Failed to set exclusive mod clock rate\n"); + + return ret; + } + + ret = clk_prepare_enable(dev->bus_clk); + if (ret) { + dev_err(dev->dev, "Failed to enable bus clock\n"); + + goto err_exclusive_rate; + } + + ret = clk_prepare_enable(dev->mod_clk); + if (ret) { + dev_err(dev->dev, "Failed to enable mod clock\n"); + + goto err_bus_clk; + } + + ret = clk_prepare_enable(dev->ram_clk); + if (ret) { + dev_err(dev->dev, "Failed to enable ram clock\n"); + + goto err_mod_clk; + } + + ret = reset_control_deassert(dev->rstc); + if (ret) { + dev_err(dev->dev, "Failed to apply reset\n"); + + goto err_ram_clk; + } + + deinterlace_init(dev); + + return 0; + +err_ram_clk: + clk_disable_unprepare(dev->ram_clk); +err_mod_clk: + clk_disable_unprepare(dev->mod_clk); +err_bus_clk: + clk_disable_unprepare(dev->bus_clk); +err_exclusive_rate: + clk_rate_exclusive_put(dev->mod_clk); + + return ret; +} + +static int deinterlace_runtime_suspend(struct device *device) +{ + struct deinterlace_dev *dev = dev_get_drvdata(device); + + reset_control_assert(dev->rstc); + + clk_disable_unprepare(dev->ram_clk); + clk_disable_unprepare(dev->mod_clk); + clk_disable_unprepare(dev->bus_clk); + clk_rate_exclusive_put(dev->mod_clk); + + return 0; +} + +static const struct of_device_id deinterlace_dt_match[] = { + { .compatible = "allwinner,sun8i-h3-deinterlace" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, deinterlace_dt_match); + +static const struct dev_pm_ops deinterlace_pm_ops = { + .runtime_resume = deinterlace_runtime_resume, + .runtime_suspend = deinterlace_runtime_suspend, +}; + +static struct platform_driver deinterlace_driver = { + .probe = deinterlace_probe, + .remove = deinterlace_remove, + .driver = { + .name = DEINTERLACE_NAME, + .of_match_table = deinterlace_dt_match, + .pm = &deinterlace_pm_ops, + }, +}; +module_platform_driver(deinterlace_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jernej Skrabec "); +MODULE_DESCRIPTION("Allwinner Deinterlace driver"); diff --git a/drivers/media/platform/sunxi/sun8i-di/sun8i-di.h b/drivers/media/platform/sunxi/sun8i-di/sun8i-di.h new file mode 100644 index 000000000..0254251d8 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-di/sun8i-di.h @@ -0,0 +1,237 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Allwinner Deinterlace driver + * + * Copyright (C) 2019 Jernej Skrabec + */ + +#ifndef _SUN8I_DEINTERLACE_H_ +#define _SUN8I_DEINTERLACE_H_ + +#include +#include +#include +#include + +#include + +#define DEINTERLACE_NAME "sun8i-di" + +#define DEINTERLACE_MOD_ENABLE 0x00 +#define DEINTERLACE_MOD_ENABLE_EN BIT(0) + +#define DEINTERLACE_FRM_CTRL 0x04 +#define DEINTERLACE_FRM_CTRL_REG_READY BIT(0) +#define DEINTERLACE_FRM_CTRL_WB_EN BIT(2) +#define DEINTERLACE_FRM_CTRL_OUT_CTRL BIT(11) +#define DEINTERLACE_FRM_CTRL_START BIT(16) +#define DEINTERLACE_FRM_CTRL_COEF_ACCESS BIT(23) + +#define DEINTERLACE_BYPASS 0x08 +#define DEINTERLACE_BYPASS_CSC BIT(1) + +#define DEINTERLACE_AGTH_SEL 0x0c +#define DEINTERLACE_AGTH_SEL_LINEBUF BIT(8) + +#define DEINTERLACE_LINT_CTRL 0x10 +#define DEINTERLACE_TRD_PRELUMA 0x1c +#define DEINTERLACE_BUF_ADDR0 0x20 +#define DEINTERLACE_BUF_ADDR1 0x24 +#define DEINTERLACE_BUF_ADDR2 0x28 + +#define DEINTERLACE_FIELD_CTRL 0x2c +#define DEINTERLACE_FIELD_CTRL_FIELD_CNT(v) ((v) & 0xff) +#define DEINTERLACE_FIELD_CTRL_FIELD_CNT_MSK (0xff) + +#define DEINTERLACE_TB_OFFSET0 0x30 +#define DEINTERLACE_TB_OFFSET1 0x34 +#define DEINTERLACE_TB_OFFSET2 0x38 +#define DEINTERLACE_TRD_PRECHROMA 0x3c +#define DEINTERLACE_LINE_STRIDE0 0x40 +#define DEINTERLACE_LINE_STRIDE1 0x44 +#define DEINTERLACE_LINE_STRIDE2 0x48 + +#define DEINTERLACE_IN_FMT 0x4c +#define DEINTERLACE_IN_FMT_PS(v) ((v) & 3) +#define DEINTERLACE_IN_FMT_FMT(v) (((v) & 7) << 4) +#define DEINTERLACE_IN_FMT_MOD(v) (((v) & 7) << 8) + +#define DEINTERLACE_WB_ADDR0 0x50 +#define DEINTERLACE_WB_ADDR1 0x54 +#define DEINTERLACE_WB_ADDR2 0x58 + +#define DEINTERLACE_OUT_FMT 0x5c +#define DEINTERLACE_OUT_FMT_FMT(v) ((v) & 0xf) +#define DEINTERLACE_OUT_FMT_PS(v) (((v) & 3) << 5) + +#define DEINTERLACE_INT_ENABLE 0x60 +#define DEINTERLACE_INT_ENABLE_WB_EN BIT(7) + +#define DEINTERLACE_INT_STATUS 0x64 +#define DEINTERLACE_INT_STATUS_WRITEBACK BIT(7) + +#define DEINTERLACE_STATUS 0x68 +#define DEINTERLACE_STATUS_COEF_STATUS BIT(11) +#define DEINTERLACE_STATUS_WB_ERROR BIT(12) + +#define DEINTERLACE_CSC_COEF 0x70 /* 12 registers */ + +#define DEINTERLACE_CTRL 0xa0 +#define DEINTERLACE_CTRL_EN BIT(0) +#define DEINTERLACE_CTRL_FLAG_OUT_EN BIT(8) +#define DEINTERLACE_CTRL_MODE_PASSTROUGH (0 << 16) +#define DEINTERLACE_CTRL_MODE_WEAVE (1 << 16) +#define DEINTERLACE_CTRL_MODE_BOB (2 << 16) +#define DEINTERLACE_CTRL_MODE_MIXED (3 << 16) +#define DEINTERLACE_CTRL_DIAG_INTP_EN BIT(24) +#define DEINTERLACE_CTRL_TEMP_DIFF_EN BIT(25) + +#define DEINTERLACE_DIAG_INTP 0xa4 +#define DEINTERLACE_DIAG_INTP_TH0(v) ((v) & 0x7f) +#define DEINTERLACE_DIAG_INTP_TH0_MSK (0x7f) +#define DEINTERLACE_DIAG_INTP_TH1(v) (((v) & 0x7f) << 8) +#define DEINTERLACE_DIAG_INTP_TH1_MSK (0x7f << 8) +#define DEINTERLACE_DIAG_INTP_TH3(v) (((v) & 0xff) << 24) +#define DEINTERLACE_DIAG_INTP_TH3_MSK (0xff << 24) + +#define DEINTERLACE_TEMP_DIFF 0xa8 +#define DEINTERLACE_TEMP_DIFF_SAD_CENTRAL_TH(v) ((v) & 0x7f) +#define DEINTERLACE_TEMP_DIFF_SAD_CENTRAL_TH_MSK (0x7f) +#define DEINTERLACE_TEMP_DIFF_AMBIGUITY_TH(v) (((v) & 0x7f) << 8) +#define DEINTERLACE_TEMP_DIFF_AMBIGUITY_TH_MSK (0x7f << 8) +#define DEINTERLACE_TEMP_DIFF_DIRECT_DITHER_TH(v) (((v) & 0x7ff) << 16) +#define DEINTERLACE_TEMP_DIFF_DIRECT_DITHER_TH_MSK (0x7ff << 16) + +#define DEINTERLACE_LUMA_TH 0xac +#define DEINTERLACE_LUMA_TH_MIN_LUMA(v) ((v) & 0xff) +#define DEINTERLACE_LUMA_TH_MIN_LUMA_MSK (0xff) +#define DEINTERLACE_LUMA_TH_MAX_LUMA(v) (((v) & 0xff) << 8) +#define DEINTERLACE_LUMA_TH_MAX_LUMA_MSK (0xff << 8) +#define DEINTERLACE_LUMA_TH_AVG_LUMA_SHIFT(v) (((v) & 0xff) << 16) +#define DEINTERLACE_LUMA_TH_AVG_LUMA_SHIFT_MSK (0xff << 16) +#define DEINTERLACE_LUMA_TH_PIXEL_STATIC(v) (((v) & 3) << 24) +#define DEINTERLACE_LUMA_TH_PIXEL_STATIC_MSK (3 << 24) + +#define DEINTERLACE_SPAT_COMP 0xb0 +#define DEINTERLACE_SPAT_COMP_TH2(v) ((v) & 0xff) +#define DEINTERLACE_SPAT_COMP_TH2_MSK (0xff) +#define DEINTERLACE_SPAT_COMP_TH3(v) (((v) & 0xff) << 16) +#define DEINTERLACE_SPAT_COMP_TH3_MSK (0xff << 16) + +#define DEINTERLACE_CHROMA_DIFF 0xb4 +#define DEINTERLACE_CHROMA_DIFF_TH(v) ((v) & 0xff) +#define DEINTERLACE_CHROMA_DIFF_TH_MSK (0xff) +#define DEINTERLACE_CHROMA_DIFF_LUMA(v) (((v) & 0x3f) << 16) +#define DEINTERLACE_CHROMA_DIFF_LUMA_MSK (0x3f << 16) +#define DEINTERLACE_CHROMA_DIFF_CHROMA(v) (((v) & 0x3f) << 24) +#define DEINTERLACE_CHROMA_DIFF_CHROMA_MSK (0x3f << 24) + +#define DEINTERLACE_PRELUMA 0xb8 +#define DEINTERLACE_PRECHROMA 0xbc +#define DEINTERLACE_TILE_FLAG0 0xc0 +#define DEINTERLACE_TILE_FLAG1 0xc4 +#define DEINTERLACE_FLAG_LINE_STRIDE 0xc8 +#define DEINTERLACE_FLAG_SEQ 0xcc + +#define DEINTERLACE_WB_LINE_STRIDE_CTRL 0xd0 +#define DEINTERLACE_WB_LINE_STRIDE_CTRL_EN BIT(0) + +#define DEINTERLACE_WB_LINE_STRIDE0 0xd4 +#define DEINTERLACE_WB_LINE_STRIDE1 0xd8 +#define DEINTERLACE_WB_LINE_STRIDE2 0xdc +#define DEINTERLACE_TRD_CTRL 0xe0 +#define DEINTERLACE_TRD_BUF_ADDR0 0xe4 +#define DEINTERLACE_TRD_BUF_ADDR1 0xe8 +#define DEINTERLACE_TRD_BUF_ADDR2 0xec +#define DEINTERLACE_TRD_TB_OFF0 0xf0 +#define DEINTERLACE_TRD_TB_OFF1 0xf4 +#define DEINTERLACE_TRD_TB_OFF2 0xf8 +#define DEINTERLACE_TRD_WB_STRIDE 0xfc +#define DEINTERLACE_CH0_IN_SIZE 0x100 +#define DEINTERLACE_CH0_OUT_SIZE 0x104 +#define DEINTERLACE_CH0_HORZ_FACT 0x108 +#define DEINTERLACE_CH0_VERT_FACT 0x10c +#define DEINTERLACE_CH0_HORZ_PHASE 0x110 +#define DEINTERLACE_CH0_VERT_PHASE0 0x114 +#define DEINTERLACE_CH0_VERT_PHASE1 0x118 +#define DEINTERLACE_CH0_HORZ_TAP0 0x120 +#define DEINTERLACE_CH0_HORZ_TAP1 0x124 +#define DEINTERLACE_CH0_VERT_TAP 0x128 +#define DEINTERLACE_CH1_IN_SIZE 0x200 +#define DEINTERLACE_CH1_OUT_SIZE 0x204 +#define DEINTERLACE_CH1_HORZ_FACT 0x208 +#define DEINTERLACE_CH1_VERT_FACT 0x20c +#define DEINTERLACE_CH1_HORZ_PHASE 0x210 +#define DEINTERLACE_CH1_VERT_PHASE0 0x214 +#define DEINTERLACE_CH1_VERT_PHASE1 0x218 +#define DEINTERLACE_CH1_HORZ_TAP0 0x220 +#define DEINTERLACE_CH1_HORZ_TAP1 0x224 +#define DEINTERLACE_CH1_VERT_TAP 0x228 +#define DEINTERLACE_CH0_HORZ_COEF0 0x400 /* 32 registers */ +#define DEINTERLACE_CH0_HORZ_COEF1 0x480 /* 32 registers */ +#define DEINTERLACE_CH0_VERT_COEF 0x500 /* 32 registers */ +#define DEINTERLACE_CH1_HORZ_COEF0 0x600 /* 32 registers */ +#define DEINTERLACE_CH1_HORZ_COEF1 0x680 /* 32 registers */ +#define DEINTERLACE_CH1_VERT_COEF 0x700 /* 32 registers */ +#define DEINTERLACE_CH3_HORZ_COEF0 0x800 /* 32 registers */ +#define DEINTERLACE_CH3_HORZ_COEF1 0x880 /* 32 registers */ +#define DEINTERLACE_CH3_VERT_COEF 0x900 /* 32 registers */ + +#define DEINTERLACE_MIN_WIDTH 2U +#define DEINTERLACE_MIN_HEIGHT 2U +#define DEINTERLACE_MAX_WIDTH 2048U +#define DEINTERLACE_MAX_HEIGHT 1100U + +#define DEINTERLACE_MODE_UV_COMBINED 2 + +#define DEINTERLACE_IN_FMT_YUV420 2 + +#define DEINTERLACE_OUT_FMT_YUV420SP 13 + +#define DEINTERLACE_PS_UVUV 0 +#define DEINTERLACE_PS_VUVU 1 + +#define DEINTERLACE_IDENTITY_COEF 0x4000 + +#define DEINTERLACE_SIZE(w, h) (((h) - 1) << 16 | ((w) - 1)) + +struct deinterlace_ctx { + struct v4l2_fh fh; + struct deinterlace_dev *dev; + + struct v4l2_pix_format src_fmt; + struct v4l2_pix_format dst_fmt; + + void *flag1_buf; + dma_addr_t flag1_buf_dma; + + void *flag2_buf; + dma_addr_t flag2_buf_dma; + + struct vb2_v4l2_buffer *prev; + + unsigned int first_field; + unsigned int field; + + int aborting; +}; + +struct deinterlace_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; + struct device *dev; + struct v4l2_m2m_dev *m2m_dev; + + /* Device file mutex */ + struct mutex dev_mutex; + + void __iomem *base; + + struct clk *bus_clk; + struct clk *mod_clk; + struct clk *ram_clk; + + struct reset_control *rstc; +}; + +#endif diff --git a/drivers/media/platform/sunxi/sun8i-rotate/Kconfig b/drivers/media/platform/sunxi/sun8i-rotate/Kconfig new file mode 100644 index 000000000..ee2c1f248 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-rotate/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only + +config VIDEO_SUN8I_ROTATE + tristate "Allwinner DE2 rotation driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_SUNXI || COMPILE_TEST + depends on COMMON_CLK && RESET_CONTROLLER && OF + depends on PM + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help + Support for the Allwinner DE2 rotation unit. + To compile this driver as a module choose m here. diff --git a/drivers/media/platform/sunxi/sun8i-rotate/Makefile b/drivers/media/platform/sunxi/sun8i-rotate/Makefile new file mode 100644 index 000000000..40f9cf398 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-rotate/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0 +sun8i-rotate-y += sun8i_rotate.o +sun8i-rotate-y += sun8i_formats.o + +obj-$(CONFIG_VIDEO_SUN8I_ROTATE) += sun8i-rotate.o diff --git a/drivers/media/platform/sunxi/sun8i-rotate/sun8i-formats.h b/drivers/media/platform/sunxi/sun8i-rotate/sun8i-formats.h new file mode 100644 index 000000000..697cd5fad --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-rotate/sun8i-formats.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (C) 2020 Jernej Skrabec */ + +#ifndef _SUN8I_FORMATS_H_ +#define _SUN8I_FORMATS_H_ + +#include + +#define ROTATE_FLAG_YUV BIT(0) +#define ROTATE_FLAG_OUTPUT BIT(1) + +struct rotate_format { + u32 fourcc; + u32 hw_format; + int planes; + int bpp[3]; + int hsub; + int vsub; + unsigned int flags; +}; + +const struct rotate_format *rotate_find_format(u32 pixelformat); +int rotate_enum_fmt(struct v4l2_fmtdesc *f, bool dst); + +#endif diff --git a/drivers/media/platform/sunxi/sun8i-rotate/sun8i-rotate.h b/drivers/media/platform/sunxi/sun8i-rotate/sun8i-rotate.h new file mode 100644 index 000000000..32ade97ba --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-rotate/sun8i-rotate.h @@ -0,0 +1,135 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Allwinner DE2 rotation driver + * + * Copyright (C) 2020 Jernej Skrabec + */ + +#ifndef _SUN8I_ROTATE_H_ +#define _SUN8I_ROTATE_H_ + +#include +#include +#include +#include +#include + +#include + +#define ROTATE_NAME "sun8i-rotate" + +#define ROTATE_GLB_CTL 0x00 +#define ROTATE_GLB_CTL_START BIT(31) +#define ROTATE_GLB_CTL_RESET BIT(30) +#define ROTATE_GLB_CTL_BURST_LEN(x) ((x) << 16) +#define ROTATE_GLB_CTL_HFLIP BIT(7) +#define ROTATE_GLB_CTL_VFLIP BIT(6) +#define ROTATE_GLB_CTL_ROTATION(x) ((x) << 4) +#define ROTATE_GLB_CTL_MODE(x) ((x) << 0) + +#define ROTATE_INT 0x04 +#define ROTATE_INT_FINISH_IRQ_EN BIT(16) +#define ROTATE_INT_FINISH_IRQ BIT(0) + +#define ROTATE_IN_FMT 0x20 +#define ROTATE_IN_FMT_FORMAT(x) ((x) << 0) + +#define ROTATE_IN_SIZE 0x24 +#define ROTATE_IN_PITCH0 0x30 +#define ROTATE_IN_PITCH1 0x34 +#define ROTATE_IN_PITCH2 0x38 +#define ROTATE_IN_ADDRL0 0x40 +#define ROTATE_IN_ADDRH0 0x44 +#define ROTATE_IN_ADDRL1 0x48 +#define ROTATE_IN_ADDRH1 0x4c +#define ROTATE_IN_ADDRL2 0x50 +#define ROTATE_IN_ADDRH2 0x54 +#define ROTATE_OUT_SIZE 0x84 +#define ROTATE_OUT_PITCH0 0x90 +#define ROTATE_OUT_PITCH1 0x94 +#define ROTATE_OUT_PITCH2 0x98 +#define ROTATE_OUT_ADDRL0 0xA0 +#define ROTATE_OUT_ADDRH0 0xA4 +#define ROTATE_OUT_ADDRL1 0xA8 +#define ROTATE_OUT_ADDRH1 0xAC +#define ROTATE_OUT_ADDRL2 0xB0 +#define ROTATE_OUT_ADDRH2 0xB4 + +#define ROTATE_BURST_8 0x07 +#define ROTATE_BURST_16 0x0f +#define ROTATE_BURST_32 0x1f +#define ROTATE_BURST_64 0x3f + +#define ROTATE_MODE_COPY_ROTATE 0x01 + +#define ROTATE_FORMAT_ARGB32 0x00 +#define ROTATE_FORMAT_ABGR32 0x01 +#define ROTATE_FORMAT_RGBA32 0x02 +#define ROTATE_FORMAT_BGRA32 0x03 +#define ROTATE_FORMAT_XRGB32 0x04 +#define ROTATE_FORMAT_XBGR32 0x05 +#define ROTATE_FORMAT_RGBX32 0x06 +#define ROTATE_FORMAT_BGRX32 0x07 +#define ROTATE_FORMAT_RGB24 0x08 +#define ROTATE_FORMAT_BGR24 0x09 +#define ROTATE_FORMAT_RGB565 0x0a +#define ROTATE_FORMAT_BGR565 0x0b +#define ROTATE_FORMAT_ARGB4444 0x0c +#define ROTATE_FORMAT_ABGR4444 0x0d +#define ROTATE_FORMAT_RGBA4444 0x0e +#define ROTATE_FORMAT_BGRA4444 0x0f +#define ROTATE_FORMAT_ARGB1555 0x10 +#define ROTATE_FORMAT_ABGR1555 0x11 +#define ROTATE_FORMAT_RGBA5551 0x12 +#define ROTATE_FORMAT_BGRA5551 0x13 + +#define ROTATE_FORMAT_YUYV 0x20 +#define ROTATE_FORMAT_UYVY 0x21 +#define ROTATE_FORMAT_YVYU 0x22 +#define ROTATE_FORMAT_VYUV 0x23 +#define ROTATE_FORMAT_NV61 0x24 +#define ROTATE_FORMAT_NV16 0x25 +#define ROTATE_FORMAT_YUV422P 0x26 +#define ROTATE_FORMAT_NV21 0x28 +#define ROTATE_FORMAT_NV12 0x29 +#define ROTATE_FORMAT_YUV420P 0x2A + +#define ROTATE_SIZE(w, h) (((h) - 1) << 16 | ((w) - 1)) + +#define ROTATE_MIN_WIDTH 8U +#define ROTATE_MIN_HEIGHT 8U +#define ROTATE_MAX_WIDTH 4096U +#define ROTATE_MAX_HEIGHT 4096U + +struct rotate_ctx { + struct v4l2_fh fh; + struct rotate_dev *dev; + + struct v4l2_pix_format src_fmt; + struct v4l2_pix_format dst_fmt; + + struct v4l2_ctrl_handler ctrl_handler; + + u32 hflip; + u32 vflip; + u32 rotate; +}; + +struct rotate_dev { + struct v4l2_device v4l2_dev; + struct video_device vfd; + struct device *dev; + struct v4l2_m2m_dev *m2m_dev; + + /* Device file mutex */ + struct mutex dev_mutex; + + void __iomem *base; + + struct clk *bus_clk; + struct clk *mod_clk; + + struct reset_control *rstc; +}; + +#endif diff --git a/drivers/media/platform/sunxi/sun8i-rotate/sun8i_formats.c b/drivers/media/platform/sunxi/sun8i-rotate/sun8i_formats.c new file mode 100644 index 000000000..cebfbc5de --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-rotate/sun8i_formats.c @@ -0,0 +1,273 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (C) 2020 Jernej Skrabec */ + +#include "sun8i-formats.h" +#include "sun8i-rotate.h" + +/* + * Formats not included in array: + * ROTATE_FORMAT_BGR565 + * ROTATE_FORMAT_VYUV + */ + +static const struct rotate_format rotate_formats[] = { + { + .fourcc = V4L2_PIX_FMT_ARGB32, + .hw_format = ROTATE_FORMAT_ARGB32, + .planes = 1, + .bpp = { 4, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_ABGR32, + .hw_format = ROTATE_FORMAT_ABGR32, + .planes = 1, + .bpp = { 4, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_RGBA32, + .hw_format = ROTATE_FORMAT_RGBA32, + .planes = 1, + .bpp = { 4, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_BGRA32, + .hw_format = ROTATE_FORMAT_BGRA32, + .planes = 1, + .bpp = { 4, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_XRGB32, + .hw_format = ROTATE_FORMAT_XRGB32, + .planes = 1, + .bpp = { 4, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_XBGR32, + .hw_format = ROTATE_FORMAT_XBGR32, + .planes = 1, + .bpp = { 4, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_RGB32, + .hw_format = ROTATE_FORMAT_RGBX32, + .planes = 1, + .bpp = { 4, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_BGR32, + .hw_format = ROTATE_FORMAT_BGRX32, + .planes = 1, + .bpp = { 4, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_RGB24, + .hw_format = ROTATE_FORMAT_RGB24, + .planes = 1, + .bpp = { 3, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_BGR24, + .hw_format = ROTATE_FORMAT_BGR24, + .planes = 1, + .bpp = { 3, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .hw_format = ROTATE_FORMAT_RGB565, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_ARGB444, + .hw_format = ROTATE_FORMAT_ARGB4444, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_ABGR444, + .hw_format = ROTATE_FORMAT_ABGR4444, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_RGBA444, + .hw_format = ROTATE_FORMAT_RGBA4444, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_BGRA444, + .hw_format = ROTATE_FORMAT_BGRA4444, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_ARGB555, + .hw_format = ROTATE_FORMAT_ARGB1555, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_ABGR555, + .hw_format = ROTATE_FORMAT_ABGR1555, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_RGBA555, + .hw_format = ROTATE_FORMAT_RGBA5551, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_BGRA555, + .hw_format = ROTATE_FORMAT_BGRA5551, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 1, + .vsub = 1, + .flags = ROTATE_FLAG_OUTPUT + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .hw_format = ROTATE_FORMAT_YVYU, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 2, + .vsub = 1, + .flags = ROTATE_FLAG_YUV + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .hw_format = ROTATE_FORMAT_UYVY, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 2, + .vsub = 1, + .flags = ROTATE_FLAG_YUV + }, { + .fourcc = V4L2_PIX_FMT_YUYV, + .hw_format = ROTATE_FORMAT_YUYV, + .planes = 1, + .bpp = { 2, 0, 0 }, + .hsub = 2, + .vsub = 1, + .flags = ROTATE_FLAG_YUV + }, { + .fourcc = V4L2_PIX_FMT_NV61, + .hw_format = ROTATE_FORMAT_NV61, + .planes = 2, + .bpp = { 1, 2, 0 }, + .hsub = 2, + .vsub = 1, + .flags = ROTATE_FLAG_YUV + }, { + .fourcc = V4L2_PIX_FMT_NV16, + .hw_format = ROTATE_FORMAT_NV16, + .planes = 2, + .bpp = { 1, 2, 0 }, + .hsub = 2, + .vsub = 1, + .flags = ROTATE_FLAG_YUV + }, { + .fourcc = V4L2_PIX_FMT_YUV422P, + .hw_format = ROTATE_FORMAT_YUV422P, + .planes = 3, + .bpp = { 1, 1, 1 }, + .hsub = 2, + .vsub = 1, + .flags = ROTATE_FLAG_YUV + }, { + .fourcc = V4L2_PIX_FMT_NV21, + .hw_format = ROTATE_FORMAT_NV21, + .planes = 2, + .bpp = { 1, 2, 0 }, + .hsub = 2, + .vsub = 2, + .flags = ROTATE_FLAG_YUV + }, { + .fourcc = V4L2_PIX_FMT_NV12, + .hw_format = ROTATE_FORMAT_NV12, + .planes = 2, + .bpp = { 1, 2, 0 }, + .hsub = 2, + .vsub = 2, + .flags = ROTATE_FLAG_YUV + }, { + .fourcc = V4L2_PIX_FMT_YUV420, + .hw_format = ROTATE_FORMAT_YUV420P, + .planes = 3, + .bpp = { 1, 1, 1 }, + .hsub = 2, + .vsub = 2, + .flags = ROTATE_FLAG_YUV | ROTATE_FLAG_OUTPUT + }, +}; + +const struct rotate_format *rotate_find_format(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rotate_formats); i++) + if (rotate_formats[i].fourcc == pixelformat) + return &rotate_formats[i]; + + return NULL; +} + +int rotate_enum_fmt(struct v4l2_fmtdesc *f, bool dst) +{ + int i, index; + + index = 0; + + for (i = 0; i < ARRAY_SIZE(rotate_formats); i++) { + /* not all formats can be used for capture buffers */ + if (dst && !(rotate_formats[i].flags & ROTATE_FLAG_OUTPUT)) + continue; + + if (index == f->index) { + f->pixelformat = rotate_formats[i].fourcc; + + return 0; + } + + index++; + } + + return -EINVAL; +} diff --git a/drivers/media/platform/sunxi/sun8i-rotate/sun8i_rotate.c b/drivers/media/platform/sunxi/sun8i-rotate/sun8i_rotate.c new file mode 100644 index 000000000..fbcca59a0 --- /dev/null +++ b/drivers/media/platform/sunxi/sun8i-rotate/sun8i_rotate.c @@ -0,0 +1,921 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Allwinner sun8i DE2 rotation driver + * + * Copyright (C) 2020 Jernej Skrabec + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "sun8i-formats.h" +#include "sun8i-rotate.h" + +static inline u32 rotate_read(struct rotate_dev *dev, u32 reg) +{ + return readl(dev->base + reg); +} + +static inline void rotate_write(struct rotate_dev *dev, u32 reg, u32 value) +{ + writel(value, dev->base + reg); +} + +static inline void rotate_set_bits(struct rotate_dev *dev, u32 reg, u32 bits) +{ + writel(readl(dev->base + reg) | bits, dev->base + reg); +} + +static void rotate_calc_addr_pitch(dma_addr_t buffer, + u32 bytesperline, u32 height, + const struct rotate_format *fmt, + dma_addr_t *addr, u32 *pitch) +{ + u32 size; + int i; + + for (i = 0; i < fmt->planes; i++) { + pitch[i] = bytesperline; + addr[i] = buffer; + if (i > 0) + pitch[i] /= fmt->hsub / fmt->bpp[i]; + size = pitch[i] * height; + if (i > 0) + size /= fmt->vsub; + buffer += size; + } +} + +static void rotate_device_run(void *priv) +{ + struct rotate_ctx *ctx = priv; + struct rotate_dev *dev = ctx->dev; + struct vb2_v4l2_buffer *src, *dst; + const struct rotate_format *fmt; + dma_addr_t addr[3]; + u32 val, pitch[3]; + + src = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx); + dst = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx); + + v4l2_m2m_buf_copy_metadata(src, dst, true); + + val = ROTATE_GLB_CTL_MODE(ROTATE_MODE_COPY_ROTATE); + if (ctx->hflip) + val |= ROTATE_GLB_CTL_HFLIP; + if (ctx->vflip) + val |= ROTATE_GLB_CTL_VFLIP; + val |= ROTATE_GLB_CTL_ROTATION(ctx->rotate / 90); + if (ctx->rotate != 90 && ctx->rotate != 270) + val |= ROTATE_GLB_CTL_BURST_LEN(ROTATE_BURST_64); + else + val |= ROTATE_GLB_CTL_BURST_LEN(ROTATE_BURST_8); + rotate_write(dev, ROTATE_GLB_CTL, val); + + fmt = rotate_find_format(ctx->src_fmt.pixelformat); + if (!fmt) + return; + + rotate_write(dev, ROTATE_IN_FMT, ROTATE_IN_FMT_FORMAT(fmt->hw_format)); + + rotate_calc_addr_pitch(vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0), + ctx->src_fmt.bytesperline, ctx->src_fmt.height, + fmt, addr, pitch); + + rotate_write(dev, ROTATE_IN_SIZE, + ROTATE_SIZE(ctx->src_fmt.width, ctx->src_fmt.height)); + + rotate_write(dev, ROTATE_IN_PITCH0, pitch[0]); + rotate_write(dev, ROTATE_IN_PITCH1, pitch[1]); + rotate_write(dev, ROTATE_IN_PITCH2, pitch[2]); + + rotate_write(dev, ROTATE_IN_ADDRL0, addr[0]); + rotate_write(dev, ROTATE_IN_ADDRL1, addr[1]); + rotate_write(dev, ROTATE_IN_ADDRL2, addr[2]); + + rotate_write(dev, ROTATE_IN_ADDRH0, 0); + rotate_write(dev, ROTATE_IN_ADDRH1, 0); + rotate_write(dev, ROTATE_IN_ADDRH2, 0); + + fmt = rotate_find_format(ctx->dst_fmt.pixelformat); + if (!fmt) + return; + + rotate_calc_addr_pitch(vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0), + ctx->dst_fmt.bytesperline, ctx->dst_fmt.height, + fmt, addr, pitch); + + rotate_write(dev, ROTATE_OUT_SIZE, + ROTATE_SIZE(ctx->dst_fmt.width, ctx->dst_fmt.height)); + + rotate_write(dev, ROTATE_OUT_PITCH0, pitch[0]); + rotate_write(dev, ROTATE_OUT_PITCH1, pitch[1]); + rotate_write(dev, ROTATE_OUT_PITCH2, pitch[2]); + + rotate_write(dev, ROTATE_OUT_ADDRL0, addr[0]); + rotate_write(dev, ROTATE_OUT_ADDRL1, addr[1]); + rotate_write(dev, ROTATE_OUT_ADDRL2, addr[2]); + + rotate_write(dev, ROTATE_OUT_ADDRH0, 0); + rotate_write(dev, ROTATE_OUT_ADDRH1, 0); + rotate_write(dev, ROTATE_OUT_ADDRH2, 0); + + rotate_set_bits(dev, ROTATE_INT, ROTATE_INT_FINISH_IRQ_EN); + rotate_set_bits(dev, ROTATE_GLB_CTL, ROTATE_GLB_CTL_START); +} + +static irqreturn_t rotate_irq(int irq, void *data) +{ + struct vb2_v4l2_buffer *buffer; + struct rotate_dev *dev = data; + struct rotate_ctx *ctx; + unsigned int val; + + ctx = v4l2_m2m_get_curr_priv(dev->m2m_dev); + if (!ctx) { + v4l2_err(&dev->v4l2_dev, + "Instance released before the end of transaction\n"); + return IRQ_NONE; + } + + val = rotate_read(dev, ROTATE_INT); + if (!(val & ROTATE_INT_FINISH_IRQ)) + return IRQ_NONE; + + /* clear flag and disable irq */ + rotate_write(dev, ROTATE_INT, ROTATE_INT_FINISH_IRQ); + + buffer = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(buffer, VB2_BUF_STATE_DONE); + + buffer = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + v4l2_m2m_buf_done(buffer, VB2_BUF_STATE_DONE); + + v4l2_m2m_job_finish(ctx->dev->m2m_dev, ctx->fh.m2m_ctx); + + return IRQ_HANDLED; +} + +static inline struct rotate_ctx *rotate_file2ctx(struct file *file) +{ + return container_of(file->private_data, struct rotate_ctx, fh); +} + +static void rotate_prepare_format(struct v4l2_pix_format *pix_fmt) +{ + unsigned int height, width, alignment, sizeimage, size, bpl; + const struct rotate_format *fmt; + int i; + + fmt = rotate_find_format(pix_fmt->pixelformat); + if (!fmt) + return; + + width = ALIGN(pix_fmt->width, fmt->hsub); + height = ALIGN(pix_fmt->height, fmt->vsub); + + /* all pitches have to be 16 byte aligned */ + alignment = 16; + if (fmt->planes > 1) + alignment *= fmt->hsub / fmt->bpp[1]; + bpl = ALIGN(width * fmt->bpp[0], alignment); + + sizeimage = 0; + for (i = 0; i < fmt->planes; i++) { + size = bpl * height; + if (i > 0) { + size *= fmt->bpp[i]; + size /= fmt->hsub; + size /= fmt->vsub; + } + sizeimage += size; + } + + pix_fmt->width = width; + pix_fmt->height = height; + pix_fmt->bytesperline = bpl; + pix_fmt->sizeimage = sizeimage; +} + +static int rotate_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, ROTATE_NAME, sizeof(cap->driver)); + strscpy(cap->card, ROTATE_NAME, sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", ROTATE_NAME); + + return 0; +} + +static int rotate_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return rotate_enum_fmt(f, true); +} + +static int rotate_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + return rotate_enum_fmt(f, false); +} + +static int rotate_enum_framesizes(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + const struct rotate_format *fmt; + + if (fsize->index != 0) + return -EINVAL; + + fmt = rotate_find_format(fsize->pixel_format); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = ROTATE_MIN_WIDTH; + fsize->stepwise.min_height = ROTATE_MIN_HEIGHT; + fsize->stepwise.max_width = ROTATE_MAX_WIDTH; + fsize->stepwise.max_height = ROTATE_MAX_HEIGHT; + fsize->stepwise.step_width = fmt->hsub; + fsize->stepwise.step_height = fmt->vsub; + + return 0; +} + +static int rotate_set_cap_format(struct rotate_ctx *ctx, + struct v4l2_pix_format *f, + u32 rotate) +{ + const struct rotate_format *fmt; + + fmt = rotate_find_format(ctx->src_fmt.pixelformat); + if (!fmt) + return -EINVAL; + + if (fmt->flags & ROTATE_FLAG_YUV) + f->pixelformat = V4L2_PIX_FMT_YUV420; + else + f->pixelformat = ctx->src_fmt.pixelformat; + + f->field = V4L2_FIELD_NONE; + + if (rotate == 90 || rotate == 270) { + f->width = ctx->src_fmt.height; + f->height = ctx->src_fmt.width; + } else { + f->width = ctx->src_fmt.width; + f->height = ctx->src_fmt.height; + } + + rotate_prepare_format(f); + + return 0; +} + +static int rotate_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rotate_ctx *ctx = rotate_file2ctx(file); + + f->fmt.pix = ctx->dst_fmt; + + return 0; +} + +static int rotate_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rotate_ctx *ctx = rotate_file2ctx(file); + + f->fmt.pix = ctx->src_fmt; + + return 0; +} + +static int rotate_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rotate_ctx *ctx = rotate_file2ctx(file); + + return rotate_set_cap_format(ctx, &f->fmt.pix, ctx->rotate); +} + +static int rotate_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + if (!rotate_find_format(f->fmt.pix.pixelformat)) + f->fmt.pix.pixelformat = V4L2_PIX_FMT_ARGB32; + + if (f->fmt.pix.width < ROTATE_MIN_WIDTH) + f->fmt.pix.width = ROTATE_MIN_WIDTH; + if (f->fmt.pix.height < ROTATE_MIN_HEIGHT) + f->fmt.pix.height = ROTATE_MIN_HEIGHT; + + if (f->fmt.pix.width > ROTATE_MAX_WIDTH) + f->fmt.pix.width = ROTATE_MAX_WIDTH; + if (f->fmt.pix.height > ROTATE_MAX_HEIGHT) + f->fmt.pix.height = ROTATE_MAX_HEIGHT; + + f->fmt.pix.field = V4L2_FIELD_NONE; + + rotate_prepare_format(&f->fmt.pix); + + return 0; +} + +static int rotate_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rotate_ctx *ctx = rotate_file2ctx(file); + struct vb2_queue *vq; + int ret; + + ret = rotate_try_fmt_vid_cap(file, priv, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) + return -EBUSY; + + ctx->dst_fmt = f->fmt.pix; + + return 0; +} + +static int rotate_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rotate_ctx *ctx = rotate_file2ctx(file); + struct vb2_queue *vq; + int ret; + + ret = rotate_try_fmt_vid_out(file, priv, f); + if (ret) + return ret; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type); + if (vb2_is_busy(vq)) + return -EBUSY; + + /* + * Capture queue has to be also checked, because format and size + * depends on output format and size. + */ + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (vb2_is_busy(vq)) + return -EBUSY; + + ctx->src_fmt = f->fmt.pix; + + /* Propagate colorspace information to capture. */ + ctx->dst_fmt.colorspace = f->fmt.pix.colorspace; + ctx->dst_fmt.xfer_func = f->fmt.pix.xfer_func; + ctx->dst_fmt.ycbcr_enc = f->fmt.pix.ycbcr_enc; + ctx->dst_fmt.quantization = f->fmt.pix.quantization; + + return rotate_set_cap_format(ctx, &ctx->dst_fmt, ctx->rotate); +} + +static const struct v4l2_ioctl_ops rotate_ioctl_ops = { + .vidioc_querycap = rotate_querycap, + + .vidioc_enum_framesizes = rotate_enum_framesizes, + + .vidioc_enum_fmt_vid_cap = rotate_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = rotate_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = rotate_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = rotate_s_fmt_vid_cap, + + .vidioc_enum_fmt_vid_out = rotate_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = rotate_g_fmt_vid_out, + .vidioc_try_fmt_vid_out = rotate_try_fmt_vid_out, + .vidioc_s_fmt_vid_out = rotate_s_fmt_vid_out, + + .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs, + .vidioc_querybuf = v4l2_m2m_ioctl_querybuf, + .vidioc_qbuf = v4l2_m2m_ioctl_qbuf, + .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf, + .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf, + .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs, + .vidioc_expbuf = v4l2_m2m_ioctl_expbuf, + + .vidioc_streamon = v4l2_m2m_ioctl_streamon, + .vidioc_streamoff = v4l2_m2m_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static int rotate_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct rotate_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + pix_fmt = &ctx->src_fmt; + else + pix_fmt = &ctx->dst_fmt; + + if (*nplanes) { + if (sizes[0] < pix_fmt->sizeimage) + return -EINVAL; + } else { + sizes[0] = pix_fmt->sizeimage; + *nplanes = 1; + } + + return 0; +} + +static int rotate_buf_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct rotate_ctx *ctx = vb2_get_drv_priv(vq); + struct v4l2_pix_format *pix_fmt; + + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + pix_fmt = &ctx->src_fmt; + else + pix_fmt = &ctx->dst_fmt; + + if (vb2_plane_size(vb, 0) < pix_fmt->sizeimage) + return -EINVAL; + + vb2_set_plane_payload(vb, 0, pix_fmt->sizeimage); + + return 0; +} + +static void rotate_buf_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct rotate_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + + v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf); +} + +static void rotate_queue_cleanup(struct vb2_queue *vq, u32 state) +{ + struct rotate_ctx *ctx = vb2_get_drv_priv(vq); + struct vb2_v4l2_buffer *vbuf; + + do { + if (V4L2_TYPE_IS_OUTPUT(vq->type)) + vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx); + else + vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx); + + if (vbuf) + v4l2_m2m_buf_done(vbuf, state); + } while (vbuf); +} + +static int rotate_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + struct rotate_ctx *ctx = vb2_get_drv_priv(vq); + struct device *dev = ctx->dev->dev; + int ret; + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) { + dev_err(dev, "Failed to enable module\n"); + + return ret; + } + } + + return 0; +} + +static void rotate_stop_streaming(struct vb2_queue *vq) +{ + if (V4L2_TYPE_IS_OUTPUT(vq->type)) { + struct rotate_ctx *ctx = vb2_get_drv_priv(vq); + + pm_runtime_put(ctx->dev->dev); + } + + rotate_queue_cleanup(vq, VB2_BUF_STATE_ERROR); +} + +static const struct vb2_ops rotate_qops = { + .queue_setup = rotate_queue_setup, + .buf_prepare = rotate_buf_prepare, + .buf_queue = rotate_buf_queue, + .start_streaming = rotate_start_streaming, + .stop_streaming = rotate_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +static int rotate_queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) +{ + struct rotate_ctx *ctx = priv; + int ret; + + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP | VB2_DMABUF; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->min_buffers_needed = 1; + src_vq->ops = &rotate_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + src_vq->lock = &ctx->dev->dev_mutex; + src_vq->dev = ctx->dev->dev; + + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP | VB2_DMABUF; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->min_buffers_needed = 2; + dst_vq->ops = &rotate_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY; + dst_vq->lock = &ctx->dev->dev_mutex; + dst_vq->dev = ctx->dev->dev; + + ret = vb2_queue_init(dst_vq); + if (ret) + return ret; + + return 0; +} + +static int rotate_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct rotate_ctx *ctx = container_of(ctrl->handler, + struct rotate_ctx, + ctrl_handler); + struct v4l2_pix_format fmt; + + switch (ctrl->id) { + case V4L2_CID_HFLIP: + ctx->hflip = ctrl->val; + break; + case V4L2_CID_VFLIP: + ctx->vflip = ctrl->val; + break; + case V4L2_CID_ROTATE: + rotate_set_cap_format(ctx, &fmt, ctrl->val); + + /* Check if capture format needs to be changed */ + if (fmt.width != ctx->dst_fmt.width || + fmt.height != ctx->dst_fmt.height || + fmt.bytesperline != ctx->dst_fmt.bytesperline || + fmt.sizeimage != ctx->dst_fmt.sizeimage) { + struct vb2_queue *vq; + + vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, + V4L2_BUF_TYPE_VIDEO_CAPTURE); + if (vb2_is_busy(vq)) + return -EBUSY; + + rotate_set_cap_format(ctx, &ctx->dst_fmt, ctrl->val); + } + + ctx->rotate = ctrl->val; + break; + default: + return -EINVAL; + } + + return 0; +} + +static const struct v4l2_ctrl_ops rotate_ctrl_ops = { + .s_ctrl = rotate_s_ctrl, +}; + +static int rotate_setup_ctrls(struct rotate_ctx *ctx) +{ + v4l2_ctrl_handler_init(&ctx->ctrl_handler, 3); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rotate_ctrl_ops, + V4L2_CID_HFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rotate_ctrl_ops, + V4L2_CID_VFLIP, 0, 1, 1, 0); + + v4l2_ctrl_new_std(&ctx->ctrl_handler, &rotate_ctrl_ops, + V4L2_CID_ROTATE, 0, 270, 90, 0); + + if (ctx->ctrl_handler.error) { + int err = ctx->ctrl_handler.error; + + v4l2_err(&ctx->dev->v4l2_dev, "control setup failed!\n"); + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + + return err; + } + + return v4l2_ctrl_handler_setup(&ctx->ctrl_handler); +} + +static int rotate_open(struct file *file) +{ + struct rotate_dev *dev = video_drvdata(file); + struct rotate_ctx *ctx = NULL; + int ret; + + if (mutex_lock_interruptible(&dev->dev_mutex)) + return -ERESTARTSYS; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + mutex_unlock(&dev->dev_mutex); + return -ENOMEM; + } + + /* default output format */ + ctx->src_fmt.pixelformat = V4L2_PIX_FMT_ARGB32; + ctx->src_fmt.field = V4L2_FIELD_NONE; + ctx->src_fmt.width = 640; + ctx->src_fmt.height = 480; + rotate_prepare_format(&ctx->src_fmt); + + /* default capture format */ + rotate_set_cap_format(ctx, &ctx->dst_fmt, ctx->rotate); + + v4l2_fh_init(&ctx->fh, video_devdata(file)); + file->private_data = &ctx->fh; + ctx->dev = dev; + + ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, + &rotate_queue_init); + if (IS_ERR(ctx->fh.m2m_ctx)) { + ret = PTR_ERR(ctx->fh.m2m_ctx); + goto err_free; + } + + v4l2_fh_add(&ctx->fh); + + ret = rotate_setup_ctrls(ctx); + if (ret) + goto err_free; + + ctx->fh.ctrl_handler = &ctx->ctrl_handler; + + mutex_unlock(&dev->dev_mutex); + + return 0; + +err_free: + kfree(ctx); + mutex_unlock(&dev->dev_mutex); + + return ret; +} + +static int rotate_release(struct file *file) +{ + struct rotate_dev *dev = video_drvdata(file); + struct rotate_ctx *ctx = container_of(file->private_data, + struct rotate_ctx, fh); + + mutex_lock(&dev->dev_mutex); + + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + v4l2_fh_del(&ctx->fh); + v4l2_fh_exit(&ctx->fh); + v4l2_m2m_ctx_release(ctx->fh.m2m_ctx); + + kfree(ctx); + + mutex_unlock(&dev->dev_mutex); + + return 0; +} + +static const struct v4l2_file_operations rotate_fops = { + .owner = THIS_MODULE, + .open = rotate_open, + .release = rotate_release, + .poll = v4l2_m2m_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = v4l2_m2m_fop_mmap, +}; + +static const struct video_device rotate_video_device = { + .name = ROTATE_NAME, + .vfl_dir = VFL_DIR_M2M, + .fops = &rotate_fops, + .ioctl_ops = &rotate_ioctl_ops, + .minor = -1, + .release = video_device_release_empty, + .device_caps = V4L2_CAP_VIDEO_M2M | V4L2_CAP_STREAMING, +}; + +static const struct v4l2_m2m_ops rotate_m2m_ops = { + .device_run = rotate_device_run, +}; + +static int rotate_probe(struct platform_device *pdev) +{ + struct rotate_dev *dev; + struct video_device *vfd; + int irq, ret; + + dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + dev->vfd = rotate_video_device; + dev->dev = &pdev->dev; + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) + return irq; + + ret = devm_request_irq(dev->dev, irq, rotate_irq, + 0, dev_name(dev->dev), dev); + if (ret) { + dev_err(dev->dev, "Failed to request IRQ\n"); + + return ret; + } + + dev->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dev->base)) + return PTR_ERR(dev->base); + + dev->bus_clk = devm_clk_get(dev->dev, "bus"); + if (IS_ERR(dev->bus_clk)) { + dev_err(dev->dev, "Failed to get bus clock\n"); + + return PTR_ERR(dev->bus_clk); + } + + dev->mod_clk = devm_clk_get(dev->dev, "mod"); + if (IS_ERR(dev->mod_clk)) { + dev_err(dev->dev, "Failed to get mod clock\n"); + + return PTR_ERR(dev->mod_clk); + } + + dev->rstc = devm_reset_control_get(dev->dev, NULL); + if (IS_ERR(dev->rstc)) { + dev_err(dev->dev, "Failed to get reset control\n"); + + return PTR_ERR(dev->rstc); + } + + mutex_init(&dev->dev_mutex); + + ret = v4l2_device_register(&pdev->dev, &dev->v4l2_dev); + if (ret) { + dev_err(dev->dev, "Failed to register V4L2 device\n"); + + return ret; + } + + vfd = &dev->vfd; + vfd->lock = &dev->dev_mutex; + vfd->v4l2_dev = &dev->v4l2_dev; + + snprintf(vfd->name, sizeof(vfd->name), "%s", + rotate_video_device.name); + video_set_drvdata(vfd, dev); + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, 0); + if (ret) { + v4l2_err(&dev->v4l2_dev, "Failed to register video device\n"); + + goto err_v4l2; + } + + v4l2_info(&dev->v4l2_dev, + "Device registered as /dev/video%d\n", vfd->num); + + dev->m2m_dev = v4l2_m2m_init(&rotate_m2m_ops); + if (IS_ERR(dev->m2m_dev)) { + v4l2_err(&dev->v4l2_dev, + "Failed to initialize V4L2 M2M device\n"); + ret = PTR_ERR(dev->m2m_dev); + + goto err_video; + } + + platform_set_drvdata(pdev, dev); + + pm_runtime_enable(dev->dev); + + return 0; + +err_video: + video_unregister_device(&dev->vfd); +err_v4l2: + v4l2_device_unregister(&dev->v4l2_dev); + + return ret; +} + +static int rotate_remove(struct platform_device *pdev) +{ + struct rotate_dev *dev = platform_get_drvdata(pdev); + + v4l2_m2m_release(dev->m2m_dev); + video_unregister_device(&dev->vfd); + v4l2_device_unregister(&dev->v4l2_dev); + + pm_runtime_force_suspend(&pdev->dev); + + return 0; +} + +static int rotate_runtime_resume(struct device *device) +{ + struct rotate_dev *dev = dev_get_drvdata(device); + int ret; + + ret = clk_prepare_enable(dev->bus_clk); + if (ret) { + dev_err(dev->dev, "Failed to enable bus clock\n"); + + return ret; + } + + ret = clk_prepare_enable(dev->mod_clk); + if (ret) { + dev_err(dev->dev, "Failed to enable mod clock\n"); + + goto err_bus_clk; + } + + ret = reset_control_deassert(dev->rstc); + if (ret) { + dev_err(dev->dev, "Failed to apply reset\n"); + + goto err_mod_clk; + } + + return 0; + +err_mod_clk: + clk_disable_unprepare(dev->mod_clk); +err_bus_clk: + clk_disable_unprepare(dev->bus_clk); + + return ret; +} + +static int rotate_runtime_suspend(struct device *device) +{ + struct rotate_dev *dev = dev_get_drvdata(device); + + reset_control_assert(dev->rstc); + + clk_disable_unprepare(dev->mod_clk); + clk_disable_unprepare(dev->bus_clk); + + return 0; +} + +static const struct of_device_id rotate_dt_match[] = { + { .compatible = "allwinner,sun8i-a83t-de2-rotate" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, rotate_dt_match); + +static const struct dev_pm_ops rotate_pm_ops = { + .runtime_resume = rotate_runtime_resume, + .runtime_suspend = rotate_runtime_suspend, +}; + +static struct platform_driver rotate_driver = { + .probe = rotate_probe, + .remove = rotate_remove, + .driver = { + .name = ROTATE_NAME, + .of_match_table = rotate_dt_match, + .pm = &rotate_pm_ops, + }, +}; +module_platform_driver(rotate_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Jernej Skrabec "); +MODULE_DESCRIPTION("Allwinner DE2 rotate driver"); diff --git a/drivers/media/platform/ti/Kconfig b/drivers/media/platform/ti/Kconfig new file mode 100644 index 000000000..e1ab56c3b --- /dev/null +++ b/drivers/media/platform/ti/Kconfig @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0-only + +comment "Texas Instruments drivers" + +# TI VIDEO PORT Helper Modules +# These will be selected by VPE and VIP +config VIDEO_TI_VPDMA + tristate + +config VIDEO_TI_SC + tristate + +config VIDEO_TI_CSC + tristate + +# V4L drivers + +config VIDEO_TI_CAL + tristate "TI CAL (Camera Adaptation Layer) driver" + depends on VIDEO_DEV + depends on V4L_PLATFORM_DRIVERS + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + depends on SOC_DRA7XX || ARCH_K3 || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Support for the TI CAL (Camera Adaptation Layer) block + found on DRA72X SoC. + In TI Technical Reference Manual this module is referred as + Camera Interface Subsystem (CAMSS). + +config VIDEO_TI_CAL_MC + bool "Media Controller centric mode by default" + depends on VIDEO_TI_CAL + default n + help + Enables Media Controller centric mode by default. + + If set, CAL driver will start in Media Controller mode by + default. Note that this behavior can be overridden via + module parameter 'mc_api'. + +# Mem2mem drivers + +config VIDEO_TI_VPE + tristate "TI VPE (Video Processing Engine) driver" + depends on V4L_MEM2MEM_DRIVERS + depends on VIDEO_DEV + depends on SOC_DRA7XX || COMPILE_TEST + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + select VIDEO_TI_VPDMA + select VIDEO_TI_SC + select VIDEO_TI_CSC + help + Support for the TI VPE(Video Processing Engine) block + found on DRA7XX SoC. + +config VIDEO_TI_VPE_DEBUG + bool "VPE debug messages" + depends on VIDEO_TI_VPE + help + Enable debug messages on VPE driver. + +source "drivers/media/platform/ti/am437x/Kconfig" +source "drivers/media/platform/ti/davinci/Kconfig" +source "drivers/media/platform/ti/omap/Kconfig" +source "drivers/media/platform/ti/omap3isp/Kconfig" diff --git a/drivers/media/platform/ti/Makefile b/drivers/media/platform/ti/Makefile new file mode 100644 index 000000000..98c5fe5c4 --- /dev/null +++ b/drivers/media/platform/ti/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +obj-y += am437x/ +obj-y += cal/ +obj-y += vpe/ +obj-y += davinci/ +obj-y += omap/ +obj-y += omap3isp/ diff --git a/drivers/media/platform/ti/am437x/Kconfig b/drivers/media/platform/ti/am437x/Kconfig new file mode 100644 index 000000000..2e24fff7e --- /dev/null +++ b/drivers/media/platform/ti/am437x/Kconfig @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_AM437X_VPFE + tristate "TI AM437x VPFE video capture driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on SOC_AM43XX || COMPILE_TEST + select MEDIA_CONTROLLER + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Support for AM437x Video Processing Front End based Video + Capture Driver. + + To compile this driver as a module, choose M here. The module + will be called am437x-vpfe. diff --git a/drivers/media/platform/ti/am437x/Makefile b/drivers/media/platform/ti/am437x/Makefile new file mode 100644 index 000000000..541043487 --- /dev/null +++ b/drivers/media/platform/ti/am437x/Makefile @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-only +# Makefile for AM437x VPFE driver + +obj-$(CONFIG_VIDEO_AM437X_VPFE) += am437x-vpfe.o diff --git a/drivers/media/platform/ti/am437x/am437x-vpfe.c b/drivers/media/platform/ti/am437x/am437x-vpfe.c new file mode 100644 index 000000000..dffac89cb --- /dev/null +++ b/drivers/media/platform/ti/am437x/am437x-vpfe.c @@ -0,0 +1,2642 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI VPFE capture Driver + * + * Copyright (C) 2013 - 2014 Texas Instruments, Inc. + * + * Benoit Parrot + * Lad, Prabhakar + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "am437x-vpfe.h" + +#define VPFE_MODULE_NAME "vpfe" +#define VPFE_VERSION "0.1.0" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-8"); + +#define vpfe_dbg(level, dev, fmt, arg...) \ + v4l2_dbg(level, debug, &dev->v4l2_dev, fmt, ##arg) +#define vpfe_info(dev, fmt, arg...) \ + v4l2_info(&dev->v4l2_dev, fmt, ##arg) +#define vpfe_err(dev, fmt, arg...) \ + v4l2_err(&dev->v4l2_dev, fmt, ##arg) + +/* standard information */ +struct vpfe_standard { + v4l2_std_id std_id; + unsigned int width; + unsigned int height; + struct v4l2_fract pixelaspect; + int frame_format; +}; + +static const struct vpfe_standard vpfe_standards[] = { + {V4L2_STD_525_60, 720, 480, {11, 10}, 1}, + {V4L2_STD_625_50, 720, 576, {54, 59}, 1}, +}; + +static struct vpfe_fmt formats[VPFE_NUM_FORMATS] = { + { + .fourcc = V4L2_PIX_FMT_YUYV, + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .bitsperpixel = 16, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .bitsperpixel = 16, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .bitsperpixel = 16, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .bitsperpixel = 16, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .bitsperpixel = 8, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .bitsperpixel = 8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .bitsperpixel = 8, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .bitsperpixel = 8, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .bitsperpixel = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB565X, + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .bitsperpixel = 16, + }, +}; + +static int __subdev_get_format(struct vpfe_device *vpfe, + struct v4l2_mbus_framefmt *fmt); +static int vpfe_calc_format_size(struct vpfe_device *vpfe, + const struct vpfe_fmt *fmt, + struct v4l2_format *f); + +static struct vpfe_fmt *find_format_by_code(struct vpfe_device *vpfe, + unsigned int code) +{ + struct vpfe_fmt *fmt; + unsigned int k; + + for (k = 0; k < vpfe->num_active_fmt; k++) { + fmt = vpfe->active_fmt[k]; + if (fmt->code == code) + return fmt; + } + + return NULL; +} + +static struct vpfe_fmt *find_format_by_pix(struct vpfe_device *vpfe, + unsigned int pixelformat) +{ + struct vpfe_fmt *fmt; + unsigned int k; + + for (k = 0; k < vpfe->num_active_fmt; k++) { + fmt = vpfe->active_fmt[k]; + if (fmt->fourcc == pixelformat) + return fmt; + } + + return NULL; +} + +static unsigned int __get_bytesperpixel(struct vpfe_device *vpfe, + const struct vpfe_fmt *fmt) +{ + struct vpfe_subdev_info *sdinfo = vpfe->current_subdev; + unsigned int bus_width = sdinfo->vpfe_param.bus_width; + u32 bpp, bus_width_bytes, clocksperpixel; + + bus_width_bytes = ALIGN(bus_width, 8) >> 3; + clocksperpixel = DIV_ROUND_UP(fmt->bitsperpixel, bus_width); + bpp = clocksperpixel * bus_width_bytes; + + return bpp; +} + +/* Print Four-character-code (FOURCC) */ +static char *print_fourcc(u32 fmt) +{ + static char code[5]; + + code[0] = (unsigned char)(fmt & 0xff); + code[1] = (unsigned char)((fmt >> 8) & 0xff); + code[2] = (unsigned char)((fmt >> 16) & 0xff); + code[3] = (unsigned char)((fmt >> 24) & 0xff); + code[4] = '\0'; + + return code; +} + +static inline u32 vpfe_reg_read(struct vpfe_ccdc *ccdc, u32 offset) +{ + return ioread32(ccdc->ccdc_cfg.base_addr + offset); +} + +static inline void vpfe_reg_write(struct vpfe_ccdc *ccdc, u32 val, u32 offset) +{ + iowrite32(val, ccdc->ccdc_cfg.base_addr + offset); +} + +static inline struct vpfe_device *to_vpfe(struct vpfe_ccdc *ccdc) +{ + return container_of(ccdc, struct vpfe_device, ccdc); +} + +static inline +struct vpfe_cap_buffer *to_vpfe_buffer(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct vpfe_cap_buffer, vb); +} + +static inline void vpfe_pcr_enable(struct vpfe_ccdc *ccdc, int flag) +{ + vpfe_reg_write(ccdc, !!flag, VPFE_PCR); +} + +static void vpfe_config_enable(struct vpfe_ccdc *ccdc, int flag) +{ + unsigned int cfg; + + if (!flag) { + cfg = vpfe_reg_read(ccdc, VPFE_CONFIG); + cfg &= ~(VPFE_CONFIG_EN_ENABLE << VPFE_CONFIG_EN_SHIFT); + } else { + cfg = VPFE_CONFIG_EN_ENABLE << VPFE_CONFIG_EN_SHIFT; + } + + vpfe_reg_write(ccdc, cfg, VPFE_CONFIG); +} + +static void vpfe_ccdc_setwin(struct vpfe_ccdc *ccdc, + struct v4l2_rect *image_win, + enum ccdc_frmfmt frm_fmt, + int bpp) +{ + int horz_start, horz_nr_pixels; + int vert_start, vert_nr_lines; + int val, mid_img; + + /* + * ppc - per pixel count. indicates how many pixels per cell + * output to SDRAM. example, for ycbcr, it is one y and one c, so 2. + * raw capture this is 1 + */ + horz_start = image_win->left * bpp; + horz_nr_pixels = (image_win->width * bpp) - 1; + vpfe_reg_write(ccdc, (horz_start << VPFE_HORZ_INFO_SPH_SHIFT) | + horz_nr_pixels, VPFE_HORZ_INFO); + + vert_start = image_win->top; + + if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + vert_nr_lines = (image_win->height >> 1) - 1; + vert_start >>= 1; + /* configure VDINT0 */ + val = (vert_start << VPFE_VDINT_VDINT0_SHIFT); + } else { + vert_nr_lines = image_win->height - 1; + /* + * configure VDINT0 and VDINT1. VDINT1 will be at half + * of image height + */ + mid_img = vert_start + (image_win->height / 2); + val = (vert_start << VPFE_VDINT_VDINT0_SHIFT) | + (mid_img & VPFE_VDINT_VDINT1_MASK); + } + + vpfe_reg_write(ccdc, val, VPFE_VDINT); + + vpfe_reg_write(ccdc, (vert_start << VPFE_VERT_START_SLV0_SHIFT) | + vert_start, VPFE_VERT_START); + vpfe_reg_write(ccdc, vert_nr_lines, VPFE_VERT_LINES); +} + +static void vpfe_reg_dump(struct vpfe_ccdc *ccdc) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + + vpfe_dbg(3, vpfe, "ALAW: 0x%x\n", vpfe_reg_read(ccdc, VPFE_ALAW)); + vpfe_dbg(3, vpfe, "CLAMP: 0x%x\n", vpfe_reg_read(ccdc, VPFE_CLAMP)); + vpfe_dbg(3, vpfe, "DCSUB: 0x%x\n", vpfe_reg_read(ccdc, VPFE_DCSUB)); + vpfe_dbg(3, vpfe, "BLKCMP: 0x%x\n", vpfe_reg_read(ccdc, VPFE_BLKCMP)); + vpfe_dbg(3, vpfe, "COLPTN: 0x%x\n", vpfe_reg_read(ccdc, VPFE_COLPTN)); + vpfe_dbg(3, vpfe, "SDOFST: 0x%x\n", vpfe_reg_read(ccdc, VPFE_SDOFST)); + vpfe_dbg(3, vpfe, "SYN_MODE: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_SYNMODE)); + vpfe_dbg(3, vpfe, "HSIZE_OFF: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_HSIZE_OFF)); + vpfe_dbg(3, vpfe, "HORZ_INFO: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_HORZ_INFO)); + vpfe_dbg(3, vpfe, "VERT_START: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_VERT_START)); + vpfe_dbg(3, vpfe, "VERT_LINES: 0x%x\n", + vpfe_reg_read(ccdc, VPFE_VERT_LINES)); +} + +static int +vpfe_ccdc_validate_param(struct vpfe_ccdc *ccdc, + struct vpfe_ccdc_config_params_raw *ccdcparam) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + u8 max_gamma, max_data; + + if (!ccdcparam->alaw.enable) + return 0; + + max_gamma = ccdc_gamma_width_max_bit(ccdcparam->alaw.gamma_wd); + max_data = ccdc_data_size_max_bit(ccdcparam->data_sz); + + if (ccdcparam->alaw.gamma_wd > VPFE_CCDC_GAMMA_BITS_09_0 || + ccdcparam->data_sz > VPFE_CCDC_DATA_8BITS || + max_gamma > max_data) { + vpfe_dbg(1, vpfe, "Invalid data line select\n"); + return -EINVAL; + } + + return 0; +} + +static void +vpfe_ccdc_update_raw_params(struct vpfe_ccdc *ccdc, + struct vpfe_ccdc_config_params_raw *raw_params) +{ + struct vpfe_ccdc_config_params_raw *config_params = + &ccdc->ccdc_cfg.bayer.config_params; + + *config_params = *raw_params; +} + +/* + * vpfe_ccdc_restore_defaults() + * This function will write defaults to all CCDC registers + */ +static void vpfe_ccdc_restore_defaults(struct vpfe_ccdc *ccdc) +{ + int i; + + /* Disable CCDC */ + vpfe_pcr_enable(ccdc, 0); + + /* set all registers to default value */ + for (i = 4; i <= 0x94; i += 4) + vpfe_reg_write(ccdc, 0, i); + + vpfe_reg_write(ccdc, VPFE_NO_CULLING, VPFE_CULLING); + vpfe_reg_write(ccdc, VPFE_CCDC_GAMMA_BITS_11_2, VPFE_ALAW); +} + +static int vpfe_ccdc_close(struct vpfe_ccdc *ccdc, struct device *dev) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + u32 dma_cntl, pcr; + + pcr = vpfe_reg_read(ccdc, VPFE_PCR); + if (pcr) + vpfe_dbg(1, vpfe, "VPFE_PCR is still set (%x)", pcr); + + dma_cntl = vpfe_reg_read(ccdc, VPFE_DMA_CNTL); + if ((dma_cntl & VPFE_DMA_CNTL_OVERFLOW)) + vpfe_dbg(1, vpfe, "VPFE_DMA_CNTL_OVERFLOW is still set (%x)", + dma_cntl); + + /* Disable CCDC by resetting all register to default POR values */ + vpfe_ccdc_restore_defaults(ccdc); + + /* Disabled the module at the CONFIG level */ + vpfe_config_enable(ccdc, 0); + + pm_runtime_put_sync(dev); + return 0; +} + +static int vpfe_ccdc_set_params(struct vpfe_ccdc *ccdc, void __user *params) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + struct vpfe_ccdc_config_params_raw raw_params; + int x; + + if (ccdc->ccdc_cfg.if_type != VPFE_RAW_BAYER) + return -EINVAL; + + x = copy_from_user(&raw_params, params, sizeof(raw_params)); + if (x) { + vpfe_dbg(1, vpfe, + "%s: error in copying ccdc params, %d\n", + __func__, x); + return -EFAULT; + } + + if (!vpfe_ccdc_validate_param(ccdc, &raw_params)) { + vpfe_ccdc_update_raw_params(ccdc, &raw_params); + return 0; + } + + return -EINVAL; +} + +/* + * vpfe_ccdc_config_ycbcr() + * This function will configure CCDC for YCbCr video capture + */ +static void vpfe_ccdc_config_ycbcr(struct vpfe_ccdc *ccdc) +{ + struct ccdc_params_ycbcr *params = &ccdc->ccdc_cfg.ycbcr; + u32 syn_mode; + + /* + * first restore the CCDC registers to default values + * This is important since we assume default values to be set in + * a lot of registers that we didn't touch + */ + vpfe_ccdc_restore_defaults(ccdc); + + /* + * configure pixel format, frame format, configure video frame + * format, enable output to SDRAM, enable internal timing generator + * and 8bit pack mode + */ + syn_mode = (((params->pix_fmt & VPFE_SYN_MODE_INPMOD_MASK) << + VPFE_SYN_MODE_INPMOD_SHIFT) | + ((params->frm_fmt & VPFE_SYN_FLDMODE_MASK) << + VPFE_SYN_FLDMODE_SHIFT) | VPFE_VDHDEN_ENABLE | + VPFE_WEN_ENABLE | VPFE_DATA_PACK_ENABLE); + + /* setup BT.656 sync mode */ + if (params->bt656_enable) { + vpfe_reg_write(ccdc, VPFE_REC656IF_BT656_EN, VPFE_REC656IF); + + /* + * configure the FID, VD, HD pin polarity, + * fld,hd pol positive, vd negative, 8-bit data + */ + syn_mode |= VPFE_SYN_MODE_VD_POL_NEGATIVE; + if (ccdc->ccdc_cfg.if_type == VPFE_BT656_10BIT) + syn_mode |= VPFE_SYN_MODE_10BITS; + else + syn_mode |= VPFE_SYN_MODE_8BITS; + } else { + /* y/c external sync mode */ + syn_mode |= (((params->fid_pol & VPFE_FID_POL_MASK) << + VPFE_FID_POL_SHIFT) | + ((params->hd_pol & VPFE_HD_POL_MASK) << + VPFE_HD_POL_SHIFT) | + ((params->vd_pol & VPFE_VD_POL_MASK) << + VPFE_VD_POL_SHIFT)); + } + vpfe_reg_write(ccdc, syn_mode, VPFE_SYNMODE); + + /* configure video window */ + vpfe_ccdc_setwin(ccdc, ¶ms->win, + params->frm_fmt, params->bytesperpixel); + + /* + * configure the order of y cb cr in SDRAM, and disable latch + * internal register on vsync + */ + if (ccdc->ccdc_cfg.if_type == VPFE_BT656_10BIT) + vpfe_reg_write(ccdc, + (params->pix_order << VPFE_CCDCFG_Y8POS_SHIFT) | + VPFE_LATCH_ON_VSYNC_DISABLE | + VPFE_CCDCFG_BW656_10BIT, VPFE_CCDCFG); + else + vpfe_reg_write(ccdc, + (params->pix_order << VPFE_CCDCFG_Y8POS_SHIFT) | + VPFE_LATCH_ON_VSYNC_DISABLE, VPFE_CCDCFG); + + /* + * configure the horizontal line offset. This should be a + * on 32 byte boundary. So clear LSB 5 bits + */ + vpfe_reg_write(ccdc, params->bytesperline, VPFE_HSIZE_OFF); + + /* configure the memory line offset */ + if (params->buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) + /* two fields are interleaved in memory */ + vpfe_reg_write(ccdc, VPFE_SDOFST_FIELD_INTERLEAVED, + VPFE_SDOFST); +} + +static void +vpfe_ccdc_config_black_clamp(struct vpfe_ccdc *ccdc, + struct vpfe_ccdc_black_clamp *bclamp) +{ + u32 val; + + if (!bclamp->enable) { + /* configure DCSub */ + val = (bclamp->dc_sub) & VPFE_BLK_DC_SUB_MASK; + vpfe_reg_write(ccdc, val, VPFE_DCSUB); + vpfe_reg_write(ccdc, VPFE_CLAMP_DEFAULT_VAL, VPFE_CLAMP); + return; + } + /* + * Configure gain, Start pixel, No of line to be avg, + * No of pixel/line to be avg, & Enable the Black clamping + */ + val = ((bclamp->sgain & VPFE_BLK_SGAIN_MASK) | + ((bclamp->start_pixel & VPFE_BLK_ST_PXL_MASK) << + VPFE_BLK_ST_PXL_SHIFT) | + ((bclamp->sample_ln & VPFE_BLK_SAMPLE_LINE_MASK) << + VPFE_BLK_SAMPLE_LINE_SHIFT) | + ((bclamp->sample_pixel & VPFE_BLK_SAMPLE_LN_MASK) << + VPFE_BLK_SAMPLE_LN_SHIFT) | VPFE_BLK_CLAMP_ENABLE); + vpfe_reg_write(ccdc, val, VPFE_CLAMP); + /* If Black clamping is enable then make dcsub 0 */ + vpfe_reg_write(ccdc, VPFE_DCSUB_DEFAULT_VAL, VPFE_DCSUB); +} + +static void +vpfe_ccdc_config_black_compense(struct vpfe_ccdc *ccdc, + struct vpfe_ccdc_black_compensation *bcomp) +{ + u32 val; + + val = ((bcomp->b & VPFE_BLK_COMP_MASK) | + ((bcomp->gb & VPFE_BLK_COMP_MASK) << + VPFE_BLK_COMP_GB_COMP_SHIFT) | + ((bcomp->gr & VPFE_BLK_COMP_MASK) << + VPFE_BLK_COMP_GR_COMP_SHIFT) | + ((bcomp->r & VPFE_BLK_COMP_MASK) << + VPFE_BLK_COMP_R_COMP_SHIFT)); + vpfe_reg_write(ccdc, val, VPFE_BLKCMP); +} + +/* + * vpfe_ccdc_config_raw() + * This function will configure CCDC for Raw capture mode + */ +static void vpfe_ccdc_config_raw(struct vpfe_ccdc *ccdc) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + struct vpfe_ccdc_config_params_raw *config_params = + &ccdc->ccdc_cfg.bayer.config_params; + struct ccdc_params_raw *params = &ccdc->ccdc_cfg.bayer; + unsigned int syn_mode; + unsigned int val; + + /* Reset CCDC */ + vpfe_ccdc_restore_defaults(ccdc); + + /* Disable latching function registers on VSYNC */ + vpfe_reg_write(ccdc, VPFE_LATCH_ON_VSYNC_DISABLE, VPFE_CCDCFG); + + /* + * Configure the vertical sync polarity(SYN_MODE.VDPOL), + * horizontal sync polarity (SYN_MODE.HDPOL), frame id polarity + * (SYN_MODE.FLDPOL), frame format(progressive or interlace), + * data size(SYNMODE.DATSIZ), &pixel format (Input mode), output + * SDRAM, enable internal timing generator + */ + syn_mode = (((params->vd_pol & VPFE_VD_POL_MASK) << VPFE_VD_POL_SHIFT) | + ((params->hd_pol & VPFE_HD_POL_MASK) << VPFE_HD_POL_SHIFT) | + ((params->fid_pol & VPFE_FID_POL_MASK) << + VPFE_FID_POL_SHIFT) | ((params->frm_fmt & + VPFE_FRM_FMT_MASK) << VPFE_FRM_FMT_SHIFT) | + ((config_params->data_sz & VPFE_DATA_SZ_MASK) << + VPFE_DATA_SZ_SHIFT) | ((params->pix_fmt & + VPFE_PIX_FMT_MASK) << VPFE_PIX_FMT_SHIFT) | + VPFE_WEN_ENABLE | VPFE_VDHDEN_ENABLE); + + /* Enable and configure aLaw register if needed */ + if (config_params->alaw.enable) { + val = ((config_params->alaw.gamma_wd & + VPFE_ALAW_GAMMA_WD_MASK) | VPFE_ALAW_ENABLE); + vpfe_reg_write(ccdc, val, VPFE_ALAW); + vpfe_dbg(3, vpfe, "\nWriting 0x%x to ALAW...\n", val); + } + + /* Configure video window */ + vpfe_ccdc_setwin(ccdc, ¶ms->win, params->frm_fmt, + params->bytesperpixel); + + /* Configure Black Clamp */ + vpfe_ccdc_config_black_clamp(ccdc, &config_params->blk_clamp); + + /* Configure Black level compensation */ + vpfe_ccdc_config_black_compense(ccdc, &config_params->blk_comp); + + /* If data size is 8 bit then pack the data */ + if ((config_params->data_sz == VPFE_CCDC_DATA_8BITS) || + config_params->alaw.enable) + syn_mode |= VPFE_DATA_PACK_ENABLE; + + /* + * Configure Horizontal offset register. If pack 8 is enabled then + * 1 pixel will take 1 byte + */ + vpfe_reg_write(ccdc, params->bytesperline, VPFE_HSIZE_OFF); + + vpfe_dbg(3, vpfe, "Writing %d (%x) to HSIZE_OFF\n", + params->bytesperline, params->bytesperline); + + /* Set value for SDOFST */ + if (params->frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (params->image_invert_enable) { + /* For interlace inverse mode */ + vpfe_reg_write(ccdc, VPFE_INTERLACED_IMAGE_INVERT, + VPFE_SDOFST); + } else { + /* For interlace non inverse mode */ + vpfe_reg_write(ccdc, VPFE_INTERLACED_NO_IMAGE_INVERT, + VPFE_SDOFST); + } + } else if (params->frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { + vpfe_reg_write(ccdc, VPFE_PROGRESSIVE_NO_IMAGE_INVERT, + VPFE_SDOFST); + } + + vpfe_reg_write(ccdc, syn_mode, VPFE_SYNMODE); + + vpfe_reg_dump(ccdc); +} + +static inline int +vpfe_ccdc_set_buftype(struct vpfe_ccdc *ccdc, + enum ccdc_buftype buf_type) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc->ccdc_cfg.bayer.buf_type = buf_type; + else + ccdc->ccdc_cfg.ycbcr.buf_type = buf_type; + + return 0; +} + +static inline enum ccdc_buftype vpfe_ccdc_get_buftype(struct vpfe_ccdc *ccdc) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc->ccdc_cfg.bayer.buf_type; + + return ccdc->ccdc_cfg.ycbcr.buf_type; +} + +static int vpfe_ccdc_set_pixel_format(struct vpfe_ccdc *ccdc, u32 pixfmt) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + + vpfe_dbg(1, vpfe, "%s: if_type: %d, pixfmt:%s\n", + __func__, ccdc->ccdc_cfg.if_type, print_fourcc(pixfmt)); + + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) { + ccdc->ccdc_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; + /* + * Need to clear it in case it was left on + * after the last capture. + */ + ccdc->ccdc_cfg.bayer.config_params.alaw.enable = 0; + + switch (pixfmt) { + case V4L2_PIX_FMT_SBGGR8: + ccdc->ccdc_cfg.bayer.config_params.alaw.enable = 1; + break; + + case V4L2_PIX_FMT_YUYV: + case V4L2_PIX_FMT_UYVY: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_RGB565X: + break; + + case V4L2_PIX_FMT_SBGGR16: + default: + return -EINVAL; + } + } else { + switch (pixfmt) { + case V4L2_PIX_FMT_YUYV: + ccdc->ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_YCBYCR; + break; + + case V4L2_PIX_FMT_UYVY: + ccdc->ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + break; + + default: + return -EINVAL; + } + } + + return 0; +} + +static u32 vpfe_ccdc_get_pixel_format(struct vpfe_ccdc *ccdc) +{ + u32 pixfmt; + + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) { + pixfmt = V4L2_PIX_FMT_YUYV; + } else { + if (ccdc->ccdc_cfg.ycbcr.pix_order == CCDC_PIXORDER_YCBYCR) + pixfmt = V4L2_PIX_FMT_YUYV; + else + pixfmt = V4L2_PIX_FMT_UYVY; + } + + return pixfmt; +} + +static int +vpfe_ccdc_set_image_window(struct vpfe_ccdc *ccdc, + struct v4l2_rect *win, unsigned int bpp) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) { + ccdc->ccdc_cfg.bayer.win = *win; + ccdc->ccdc_cfg.bayer.bytesperpixel = bpp; + ccdc->ccdc_cfg.bayer.bytesperline = ALIGN(win->width * bpp, 32); + } else { + ccdc->ccdc_cfg.ycbcr.win = *win; + ccdc->ccdc_cfg.ycbcr.bytesperpixel = bpp; + ccdc->ccdc_cfg.ycbcr.bytesperline = ALIGN(win->width * bpp, 32); + } + + return 0; +} + +static inline void +vpfe_ccdc_get_image_window(struct vpfe_ccdc *ccdc, + struct v4l2_rect *win) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + *win = ccdc->ccdc_cfg.bayer.win; + else + *win = ccdc->ccdc_cfg.ycbcr.win; +} + +static inline unsigned int vpfe_ccdc_get_line_length(struct vpfe_ccdc *ccdc) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc->ccdc_cfg.bayer.bytesperline; + + return ccdc->ccdc_cfg.ycbcr.bytesperline; +} + +static inline int +vpfe_ccdc_set_frame_format(struct vpfe_ccdc *ccdc, + enum ccdc_frmfmt frm_fmt) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + ccdc->ccdc_cfg.bayer.frm_fmt = frm_fmt; + else + ccdc->ccdc_cfg.ycbcr.frm_fmt = frm_fmt; + + return 0; +} + +static inline enum ccdc_frmfmt +vpfe_ccdc_get_frame_format(struct vpfe_ccdc *ccdc) +{ + if (ccdc->ccdc_cfg.if_type == VPFE_RAW_BAYER) + return ccdc->ccdc_cfg.bayer.frm_fmt; + + return ccdc->ccdc_cfg.ycbcr.frm_fmt; +} + +static inline int vpfe_ccdc_getfid(struct vpfe_ccdc *ccdc) +{ + return (vpfe_reg_read(ccdc, VPFE_SYNMODE) >> 15) & 1; +} + +static inline void vpfe_set_sdr_addr(struct vpfe_ccdc *ccdc, unsigned long addr) +{ + vpfe_reg_write(ccdc, addr & 0xffffffe0, VPFE_SDR_ADDR); +} + +static int vpfe_ccdc_set_hw_if_params(struct vpfe_ccdc *ccdc, + struct vpfe_hw_if_param *params) +{ + struct vpfe_device *vpfe = to_vpfe(ccdc); + + ccdc->ccdc_cfg.if_type = params->if_type; + + switch (params->if_type) { + case VPFE_BT656: + case VPFE_YCBCR_SYNC_16: + case VPFE_YCBCR_SYNC_8: + case VPFE_BT656_10BIT: + ccdc->ccdc_cfg.ycbcr.vd_pol = params->vdpol; + ccdc->ccdc_cfg.ycbcr.hd_pol = params->hdpol; + break; + + case VPFE_RAW_BAYER: + ccdc->ccdc_cfg.bayer.vd_pol = params->vdpol; + ccdc->ccdc_cfg.bayer.hd_pol = params->hdpol; + if (params->bus_width == 10) + ccdc->ccdc_cfg.bayer.config_params.data_sz = + VPFE_CCDC_DATA_10BITS; + else + ccdc->ccdc_cfg.bayer.config_params.data_sz = + VPFE_CCDC_DATA_8BITS; + vpfe_dbg(1, vpfe, "params.bus_width: %d\n", + params->bus_width); + vpfe_dbg(1, vpfe, "config_params.data_sz: %d\n", + ccdc->ccdc_cfg.bayer.config_params.data_sz); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static void vpfe_clear_intr(struct vpfe_ccdc *ccdc, int vdint) +{ + unsigned int vpfe_int_status; + + vpfe_int_status = vpfe_reg_read(ccdc, VPFE_IRQ_STS); + + switch (vdint) { + /* VD0 interrupt */ + case VPFE_VDINT0: + vpfe_int_status &= ~VPFE_VDINT0; + vpfe_int_status |= VPFE_VDINT0; + break; + + /* VD1 interrupt */ + case VPFE_VDINT1: + vpfe_int_status &= ~VPFE_VDINT1; + vpfe_int_status |= VPFE_VDINT1; + break; + + /* VD2 interrupt */ + case VPFE_VDINT2: + vpfe_int_status &= ~VPFE_VDINT2; + vpfe_int_status |= VPFE_VDINT2; + break; + + /* Clear all interrupts */ + default: + vpfe_int_status &= ~(VPFE_VDINT0 | + VPFE_VDINT1 | + VPFE_VDINT2); + vpfe_int_status |= (VPFE_VDINT0 | + VPFE_VDINT1 | + VPFE_VDINT2); + break; + } + /* Clear specific VDINT from the status register */ + vpfe_reg_write(ccdc, vpfe_int_status, VPFE_IRQ_STS); + + vpfe_int_status = vpfe_reg_read(ccdc, VPFE_IRQ_STS); + + /* Acknowledge that we are done with all interrupts */ + vpfe_reg_write(ccdc, 1, VPFE_IRQ_EOI); +} + +static void vpfe_ccdc_config_defaults(struct vpfe_ccdc *ccdc) +{ + ccdc->ccdc_cfg.if_type = VPFE_RAW_BAYER; + + ccdc->ccdc_cfg.ycbcr.pix_fmt = CCDC_PIXFMT_YCBCR_8BIT; + ccdc->ccdc_cfg.ycbcr.frm_fmt = CCDC_FRMFMT_INTERLACED; + ccdc->ccdc_cfg.ycbcr.fid_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.ycbcr.vd_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.ycbcr.hd_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.ycbcr.pix_order = CCDC_PIXORDER_CBYCRY; + ccdc->ccdc_cfg.ycbcr.buf_type = CCDC_BUFTYPE_FLD_INTERLEAVED; + + ccdc->ccdc_cfg.ycbcr.win.left = 0; + ccdc->ccdc_cfg.ycbcr.win.top = 0; + ccdc->ccdc_cfg.ycbcr.win.width = 720; + ccdc->ccdc_cfg.ycbcr.win.height = 576; + ccdc->ccdc_cfg.ycbcr.bt656_enable = 1; + + ccdc->ccdc_cfg.bayer.pix_fmt = CCDC_PIXFMT_RAW; + ccdc->ccdc_cfg.bayer.frm_fmt = CCDC_FRMFMT_PROGRESSIVE; + ccdc->ccdc_cfg.bayer.fid_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.bayer.vd_pol = VPFE_PINPOL_POSITIVE; + ccdc->ccdc_cfg.bayer.hd_pol = VPFE_PINPOL_POSITIVE; + + ccdc->ccdc_cfg.bayer.win.left = 0; + ccdc->ccdc_cfg.bayer.win.top = 0; + ccdc->ccdc_cfg.bayer.win.width = 800; + ccdc->ccdc_cfg.bayer.win.height = 600; + ccdc->ccdc_cfg.bayer.config_params.data_sz = VPFE_CCDC_DATA_8BITS; + ccdc->ccdc_cfg.bayer.config_params.alaw.gamma_wd = + VPFE_CCDC_GAMMA_BITS_09_0; +} + +/* + * vpfe_get_ccdc_image_format - Get image parameters based on CCDC settings + */ +static int vpfe_get_ccdc_image_format(struct vpfe_device *vpfe, + struct v4l2_format *f) +{ + struct v4l2_rect image_win; + enum ccdc_buftype buf_type; + enum ccdc_frmfmt frm_fmt; + + memset(f, 0, sizeof(*f)); + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vpfe_ccdc_get_image_window(&vpfe->ccdc, &image_win); + f->fmt.pix.width = image_win.width; + f->fmt.pix.height = image_win.height; + f->fmt.pix.bytesperline = vpfe_ccdc_get_line_length(&vpfe->ccdc); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height; + buf_type = vpfe_ccdc_get_buftype(&vpfe->ccdc); + f->fmt.pix.pixelformat = vpfe_ccdc_get_pixel_format(&vpfe->ccdc); + frm_fmt = vpfe_ccdc_get_frame_format(&vpfe->ccdc); + + if (frm_fmt == CCDC_FRMFMT_PROGRESSIVE) { + f->fmt.pix.field = V4L2_FIELD_NONE; + } else if (frm_fmt == CCDC_FRMFMT_INTERLACED) { + if (buf_type == CCDC_BUFTYPE_FLD_INTERLEAVED) { + f->fmt.pix.field = V4L2_FIELD_INTERLACED; + } else if (buf_type == CCDC_BUFTYPE_FLD_SEPARATED) { + f->fmt.pix.field = V4L2_FIELD_SEQ_TB; + } else { + vpfe_err(vpfe, "Invalid buf_type\n"); + return -EINVAL; + } + } else { + vpfe_err(vpfe, "Invalid frm_fmt\n"); + return -EINVAL; + } + return 0; +} + +static int vpfe_config_ccdc_image_format(struct vpfe_device *vpfe) +{ + enum ccdc_frmfmt frm_fmt = CCDC_FRMFMT_INTERLACED; + u32 bpp; + int ret = 0; + + vpfe_dbg(1, vpfe, "pixelformat: %s\n", + print_fourcc(vpfe->fmt.fmt.pix.pixelformat)); + + if (vpfe_ccdc_set_pixel_format(&vpfe->ccdc, + vpfe->fmt.fmt.pix.pixelformat) < 0) { + vpfe_err(vpfe, "couldn't set pix format in ccdc\n"); + return -EINVAL; + } + + /* configure the image window */ + bpp = __get_bytesperpixel(vpfe, vpfe->current_vpfe_fmt); + vpfe_ccdc_set_image_window(&vpfe->ccdc, &vpfe->crop, bpp); + + switch (vpfe->fmt.fmt.pix.field) { + case V4L2_FIELD_INTERLACED: + /* do nothing, since it is default */ + ret = vpfe_ccdc_set_buftype( + &vpfe->ccdc, + CCDC_BUFTYPE_FLD_INTERLEAVED); + break; + + case V4L2_FIELD_NONE: + frm_fmt = CCDC_FRMFMT_PROGRESSIVE; + /* buffer type only applicable for interlaced scan */ + break; + + case V4L2_FIELD_SEQ_TB: + ret = vpfe_ccdc_set_buftype( + &vpfe->ccdc, + CCDC_BUFTYPE_FLD_SEPARATED); + break; + + default: + return -EINVAL; + } + + if (ret) + return ret; + + return vpfe_ccdc_set_frame_format(&vpfe->ccdc, frm_fmt); +} + +/* + * vpfe_config_image_format() + * For a given standard, this functions sets up the default + * pix format & crop values in the vpfe device and ccdc. It first + * starts with defaults based values from the standard table. + * It then checks if sub device supports get_fmt and then override the + * values based on that.Sets crop values to match with scan resolution + * starting at 0,0. It calls vpfe_config_ccdc_image_format() set the + * values in ccdc + */ +static int vpfe_config_image_format(struct vpfe_device *vpfe, + v4l2_std_id std_id) +{ + struct vpfe_fmt *fmt; + struct v4l2_mbus_framefmt mbus_fmt; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(vpfe_standards); i++) { + if (vpfe_standards[i].std_id & std_id) { + vpfe->std_info.active_pixels = + vpfe_standards[i].width; + vpfe->std_info.active_lines = + vpfe_standards[i].height; + vpfe->std_info.frame_format = + vpfe_standards[i].frame_format; + vpfe->std_index = i; + + break; + } + } + + if (i == ARRAY_SIZE(vpfe_standards)) { + vpfe_err(vpfe, "standard not supported\n"); + return -EINVAL; + } + + ret = __subdev_get_format(vpfe, &mbus_fmt); + if (ret) + return ret; + + fmt = find_format_by_code(vpfe, mbus_fmt.code); + if (!fmt) { + vpfe_dbg(3, vpfe, "mbus code format (0x%08x) not found.\n", + mbus_fmt.code); + return -EINVAL; + } + + /* Save current subdev format */ + v4l2_fill_pix_format(&vpfe->fmt.fmt.pix, &mbus_fmt); + vpfe->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vpfe->fmt.fmt.pix.pixelformat = fmt->fourcc; + vpfe_calc_format_size(vpfe, fmt, &vpfe->fmt); + vpfe->current_vpfe_fmt = fmt; + + /* Update the crop window based on found values */ + vpfe->crop.top = 0; + vpfe->crop.left = 0; + vpfe->crop.width = mbus_fmt.width; + vpfe->crop.height = mbus_fmt.height; + + return vpfe_config_ccdc_image_format(vpfe); +} + +static int vpfe_initialize_device(struct vpfe_device *vpfe) +{ + struct vpfe_subdev_info *sdinfo; + int ret; + + sdinfo = &vpfe->cfg->sub_devs[0]; + sdinfo->sd = vpfe->sd[0]; + vpfe->current_input = 0; + vpfe->std_index = 0; + /* Configure the default format information */ + ret = vpfe_config_image_format(vpfe, + vpfe_standards[vpfe->std_index].std_id); + if (ret) + return ret; + + ret = pm_runtime_resume_and_get(vpfe->pdev); + if (ret < 0) + return ret; + + vpfe_config_enable(&vpfe->ccdc, 1); + + vpfe_ccdc_restore_defaults(&vpfe->ccdc); + + /* Clear all VPFE interrupts */ + vpfe_clear_intr(&vpfe->ccdc, -1); + + return ret; +} + +/* + * vpfe_release : This function is based on the vb2_fop_release + * helper function. + * It has been augmented to handle module power management, + * by disabling/enabling h/w module fcntl clock when necessary. + */ +static int vpfe_release(struct file *file) +{ + struct vpfe_device *vpfe = video_drvdata(file); + bool fh_singular; + int ret; + + mutex_lock(&vpfe->lock); + + /* Save the singular status before we call the clean-up helper */ + fh_singular = v4l2_fh_is_singular_file(file); + + /* the release helper will cleanup any on-going streaming */ + ret = _vb2_fop_release(file, NULL); + + /* + * If this was the last open file. + * Then de-initialize hw module. + */ + if (fh_singular) + vpfe_ccdc_close(&vpfe->ccdc, vpfe->pdev); + + mutex_unlock(&vpfe->lock); + + return ret; +} + +/* + * vpfe_open : This function is based on the v4l2_fh_open helper function. + * It has been augmented to handle module power management, + * by disabling/enabling h/w module fcntl clock when necessary. + */ +static int vpfe_open(struct file *file) +{ + struct vpfe_device *vpfe = video_drvdata(file); + int ret; + + mutex_lock(&vpfe->lock); + + ret = v4l2_fh_open(file); + if (ret) { + vpfe_err(vpfe, "v4l2_fh_open failed\n"); + goto unlock; + } + + if (!v4l2_fh_is_singular_file(file)) + goto unlock; + + if (vpfe_initialize_device(vpfe)) { + v4l2_fh_release(file); + ret = -ENODEV; + } + +unlock: + mutex_unlock(&vpfe->lock); + return ret; +} + +/** + * vpfe_schedule_next_buffer: set next buffer address for capture + * @vpfe : ptr to vpfe device + * + * This function will get next buffer from the dma queue and + * set the buffer address in the vpfe register for capture. + * the buffer is marked active + */ +static void vpfe_schedule_next_buffer(struct vpfe_device *vpfe) +{ + dma_addr_t addr; + + spin_lock(&vpfe->dma_queue_lock); + if (list_empty(&vpfe->dma_queue)) { + spin_unlock(&vpfe->dma_queue_lock); + return; + } + + vpfe->next_frm = list_entry(vpfe->dma_queue.next, + struct vpfe_cap_buffer, list); + list_del(&vpfe->next_frm->list); + spin_unlock(&vpfe->dma_queue_lock); + + addr = vb2_dma_contig_plane_dma_addr(&vpfe->next_frm->vb.vb2_buf, 0); + vpfe_set_sdr_addr(&vpfe->ccdc, addr); +} + +static inline void vpfe_schedule_bottom_field(struct vpfe_device *vpfe) +{ + dma_addr_t addr; + + addr = vb2_dma_contig_plane_dma_addr(&vpfe->next_frm->vb.vb2_buf, 0) + + vpfe->field_off; + + vpfe_set_sdr_addr(&vpfe->ccdc, addr); +} + +/* + * vpfe_process_buffer_complete: process a completed buffer + * @vpfe : ptr to vpfe device + * + * This function time stamp the buffer and mark it as DONE. It also + * wake up any process waiting on the QUEUE and set the next buffer + * as current + */ +static inline void vpfe_process_buffer_complete(struct vpfe_device *vpfe) +{ + vpfe->cur_frm->vb.vb2_buf.timestamp = ktime_get_ns(); + vpfe->cur_frm->vb.field = vpfe->fmt.fmt.pix.field; + vpfe->cur_frm->vb.sequence = vpfe->sequence++; + vb2_buffer_done(&vpfe->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE); + vpfe->cur_frm = vpfe->next_frm; +} + +static void vpfe_handle_interlaced_irq(struct vpfe_device *vpfe, + enum v4l2_field field) +{ + int fid; + + /* interlaced or TB capture check which field + * we are in hardware + */ + fid = vpfe_ccdc_getfid(&vpfe->ccdc); + + /* switch the software maintained field id */ + vpfe->field ^= 1; + if (fid == vpfe->field) { + /* we are in-sync here,continue */ + if (fid == 0) { + /* + * One frame is just being captured. If the + * next frame is available, release the + * current frame and move on + */ + if (vpfe->cur_frm != vpfe->next_frm) + vpfe_process_buffer_complete(vpfe); + + if (vpfe->stopping) + return; + + /* + * based on whether the two fields are stored + * interleave or separately in memory, + * reconfigure the CCDC memory address + */ + if (field == V4L2_FIELD_SEQ_TB) + vpfe_schedule_bottom_field(vpfe); + } else { + /* + * if one field is just being captured configure + * the next frame get the next frame from the empty + * queue if no frame is available hold on to the + * current buffer + */ + if (vpfe->cur_frm == vpfe->next_frm) + vpfe_schedule_next_buffer(vpfe); + } + } else if (fid == 0) { + /* + * out of sync. Recover from any hardware out-of-sync. + * May loose one frame + */ + vpfe->field = fid; + } +} + +/* + * vpfe_isr : ISR handler for vpfe capture (VINT0) + * @irq: irq number + * @dev_id: dev_id ptr + * + * It changes status of the captured buffer, takes next buffer from the queue + * and sets its address in VPFE registers + */ +static irqreturn_t vpfe_isr(int irq, void *dev) +{ + struct vpfe_device *vpfe = (struct vpfe_device *)dev; + enum v4l2_field field = vpfe->fmt.fmt.pix.field; + int intr_status, stopping = vpfe->stopping; + + intr_status = vpfe_reg_read(&vpfe->ccdc, VPFE_IRQ_STS); + + if (intr_status & VPFE_VDINT0) { + if (field == V4L2_FIELD_NONE) { + if (vpfe->cur_frm != vpfe->next_frm) + vpfe_process_buffer_complete(vpfe); + } else { + vpfe_handle_interlaced_irq(vpfe, field); + } + if (stopping) { + vpfe->stopping = false; + complete(&vpfe->capture_stop); + } + } + + if (intr_status & VPFE_VDINT1 && !stopping) { + if (field == V4L2_FIELD_NONE && + vpfe->cur_frm == vpfe->next_frm) + vpfe_schedule_next_buffer(vpfe); + } + + vpfe_clear_intr(&vpfe->ccdc, intr_status); + + return IRQ_HANDLED; +} + +static inline void vpfe_detach_irq(struct vpfe_device *vpfe) +{ + unsigned int intr = VPFE_VDINT0; + enum ccdc_frmfmt frame_format; + + frame_format = vpfe_ccdc_get_frame_format(&vpfe->ccdc); + if (frame_format == CCDC_FRMFMT_PROGRESSIVE) + intr |= VPFE_VDINT1; + + vpfe_reg_write(&vpfe->ccdc, intr, VPFE_IRQ_EN_CLR); +} + +static inline void vpfe_attach_irq(struct vpfe_device *vpfe) +{ + unsigned int intr = VPFE_VDINT0; + enum ccdc_frmfmt frame_format; + + frame_format = vpfe_ccdc_get_frame_format(&vpfe->ccdc); + if (frame_format == CCDC_FRMFMT_PROGRESSIVE) + intr |= VPFE_VDINT1; + + vpfe_reg_write(&vpfe->ccdc, intr, VPFE_IRQ_EN_SET); +} + +static int vpfe_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + strscpy(cap->driver, VPFE_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, "TI AM437x VPFE", sizeof(cap->card)); + snprintf(cap->bus_info, sizeof(cap->bus_info), + "platform:%s", vpfe->v4l2_dev.name); + return 0; +} + +/* get the format set at output pad of the adjacent subdev */ +static int __subdev_get_format(struct vpfe_device *vpfe, + struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_subdev *sd = vpfe->current_subdev->sd; + struct v4l2_subdev_format sd_fmt; + struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; + int ret; + + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + sd_fmt.pad = 0; + + ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sd_fmt); + if (ret) + return ret; + + *fmt = *mbus_fmt; + + vpfe_dbg(1, vpfe, "%s: %dx%d code:%04X\n", __func__, + fmt->width, fmt->height, fmt->code); + + return 0; +} + +/* set the format at output pad of the adjacent subdev */ +static int __subdev_set_format(struct vpfe_device *vpfe, + struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_subdev *sd = vpfe->current_subdev->sd; + struct v4l2_subdev_format sd_fmt; + struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; + int ret; + + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + sd_fmt.pad = 0; + *mbus_fmt = *fmt; + + ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &sd_fmt); + if (ret) + return ret; + + vpfe_dbg(1, vpfe, "%s %dx%d code:%04X\n", __func__, + fmt->width, fmt->height, fmt->code); + + return 0; +} + +static int vpfe_calc_format_size(struct vpfe_device *vpfe, + const struct vpfe_fmt *fmt, + struct v4l2_format *f) +{ + u32 bpp; + + if (!fmt) { + vpfe_dbg(3, vpfe, "No vpfe_fmt provided!\n"); + return -EINVAL; + } + + bpp = __get_bytesperpixel(vpfe, fmt); + + /* pitch should be 32 bytes aligned */ + f->fmt.pix.bytesperline = ALIGN(f->fmt.pix.width * bpp, 32); + f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * + f->fmt.pix.height; + + vpfe_dbg(3, vpfe, "%s: fourcc: %s size: %dx%d bpl:%d img_size:%d\n", + __func__, print_fourcc(f->fmt.pix.pixelformat), + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); + + return 0; +} + +static int vpfe_g_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + *fmt = vpfe->fmt; + + return 0; +} + +static int vpfe_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + struct vpfe_fmt *fmt; + + sdinfo = vpfe->current_subdev; + if (!sdinfo->sd) + return -EINVAL; + + if (f->index >= vpfe->num_active_fmt) + return -EINVAL; + + fmt = vpfe->active_fmt[f->index]; + + f->pixelformat = fmt->fourcc; + + vpfe_dbg(1, vpfe, "%s: mbus index: %d code: %x pixelformat: %s\n", + __func__, f->index, fmt->code, print_fourcc(fmt->fourcc)); + + return 0; +} + +static int vpfe_try_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct v4l2_subdev *sd = vpfe->current_subdev->sd; + const struct vpfe_fmt *fmt; + struct v4l2_subdev_frame_size_enum fse; + int ret, found; + + fmt = find_format_by_pix(vpfe, f->fmt.pix.pixelformat); + if (!fmt) { + /* default to first entry */ + vpfe_dbg(3, vpfe, "Invalid pixel code: %x, default used instead\n", + f->fmt.pix.pixelformat); + fmt = vpfe->active_fmt[0]; + f->fmt.pix.pixelformat = fmt->fourcc; + } + + f->fmt.pix.field = vpfe->fmt.fmt.pix.field; + + /* check for/find a valid width/height */ + ret = 0; + found = false; + fse.pad = 0; + fse.code = fmt->code; + fse.which = V4L2_SUBDEV_FORMAT_ACTIVE; + for (fse.index = 0; ; fse.index++) { + ret = v4l2_subdev_call(sd, pad, enum_frame_size, + NULL, &fse); + if (ret) + break; + + if (f->fmt.pix.width == fse.max_width && + f->fmt.pix.height == fse.max_height) { + found = true; + break; + } else if (f->fmt.pix.width >= fse.min_width && + f->fmt.pix.width <= fse.max_width && + f->fmt.pix.height >= fse.min_height && + f->fmt.pix.height <= fse.max_height) { + found = true; + break; + } + } + + if (!found) { + /* use existing values as default */ + f->fmt.pix.width = vpfe->fmt.fmt.pix.width; + f->fmt.pix.height = vpfe->fmt.fmt.pix.height; + } + + /* + * Use current colorspace for now, it will get + * updated properly during s_fmt + */ + f->fmt.pix.colorspace = vpfe->fmt.fmt.pix.colorspace; + return vpfe_calc_format_size(vpfe, fmt, f); +} + +static int vpfe_s_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_fmt *f; + struct v4l2_mbus_framefmt mbus_fmt; + int ret; + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + + ret = vpfe_try_fmt(file, priv, fmt); + if (ret < 0) + return ret; + + f = find_format_by_pix(vpfe, fmt->fmt.pix.pixelformat); + + v4l2_fill_mbus_format(&mbus_fmt, &fmt->fmt.pix, f->code); + + ret = __subdev_set_format(vpfe, &mbus_fmt); + if (ret) + return ret; + + /* Just double check nothing has gone wrong */ + if (mbus_fmt.code != f->code) { + vpfe_dbg(3, vpfe, + "%s subdev changed format on us, this should not happen\n", + __func__); + return -EINVAL; + } + + v4l2_fill_pix_format(&vpfe->fmt.fmt.pix, &mbus_fmt); + vpfe->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vpfe->fmt.fmt.pix.pixelformat = f->fourcc; + vpfe_calc_format_size(vpfe, f, &vpfe->fmt); + *fmt = vpfe->fmt; + vpfe->current_vpfe_fmt = f; + + /* Update the crop window based on found values */ + vpfe->crop.width = fmt->fmt.pix.width; + vpfe->crop.height = fmt->fmt.pix.height; + + /* set image capture parameters in the ccdc */ + return vpfe_config_ccdc_image_format(vpfe); +} + +static int vpfe_enum_size(struct file *file, void *priv, + struct v4l2_frmsizeenum *fsize) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct v4l2_subdev_frame_size_enum fse = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_subdev *sd = vpfe->current_subdev->sd; + struct vpfe_fmt *fmt; + int ret; + + /* check for valid format */ + fmt = find_format_by_pix(vpfe, fsize->pixel_format); + if (!fmt) { + vpfe_dbg(3, vpfe, "Invalid pixel code: %x\n", + fsize->pixel_format); + return -EINVAL; + } + + memset(fsize->reserved, 0x0, sizeof(fsize->reserved)); + + fse.index = fsize->index; + fse.pad = 0; + fse.code = fmt->code; + ret = v4l2_subdev_call(sd, pad, enum_frame_size, NULL, &fse); + if (ret) + return ret; + + vpfe_dbg(1, vpfe, "%s: index: %d code: %x W:[%d,%d] H:[%d,%d]\n", + __func__, fse.index, fse.code, fse.min_width, fse.max_width, + fse.min_height, fse.max_height); + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.max_width; + fsize->discrete.height = fse.max_height; + + vpfe_dbg(1, vpfe, "%s: index: %d pixformat: %s size: %dx%d\n", + __func__, fsize->index, print_fourcc(fsize->pixel_format), + fsize->discrete.width, fsize->discrete.height); + + return 0; +} + +/* + * vpfe_get_subdev_input_index - Get subdev index and subdev input index for a + * given app input index + */ +static int +vpfe_get_subdev_input_index(struct vpfe_device *vpfe, + int *subdev_index, + int *subdev_input_index, + int app_input_index) +{ + int i, j = 0; + + for (i = 0; i < ARRAY_SIZE(vpfe->cfg->asd); i++) { + if (app_input_index < (j + 1)) { + *subdev_index = i; + *subdev_input_index = app_input_index - j; + return 0; + } + j++; + } + return -EINVAL; +} + +/* + * vpfe_get_app_input - Get app input index for a given subdev input index + * driver stores the input index of the current sub device and translate it + * when application request the current input + */ +static int vpfe_get_app_input_index(struct vpfe_device *vpfe, + int *app_input_index) +{ + struct vpfe_config *cfg = vpfe->cfg; + struct vpfe_subdev_info *sdinfo; + struct i2c_client *client; + struct i2c_client *curr_client; + int i, j = 0; + + curr_client = v4l2_get_subdevdata(vpfe->current_subdev->sd); + for (i = 0; i < ARRAY_SIZE(vpfe->cfg->asd); i++) { + sdinfo = &cfg->sub_devs[i]; + client = v4l2_get_subdevdata(sdinfo->sd); + if (client->addr == curr_client->addr && + client->adapter->nr == curr_client->adapter->nr) { + if (vpfe->current_input >= 1) + return -1; + *app_input_index = j + vpfe->current_input; + return 0; + } + j++; + } + return -EINVAL; +} + +static int vpfe_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + int subdev, index; + + if (vpfe_get_subdev_input_index(vpfe, &subdev, &index, + inp->index) < 0) { + vpfe_dbg(1, vpfe, + "input information not found for the subdev\n"); + return -EINVAL; + } + sdinfo = &vpfe->cfg->sub_devs[subdev]; + *inp = sdinfo->inputs[index]; + + return 0; +} + +static int vpfe_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + return vpfe_get_app_input_index(vpfe, index); +} + +/* Assumes caller is holding vpfe_dev->lock */ +static int vpfe_set_input(struct vpfe_device *vpfe, unsigned int index) +{ + int subdev_index = 0, inp_index = 0; + struct vpfe_subdev_info *sdinfo; + struct vpfe_route *route; + u32 input, output; + int ret; + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + ret = vpfe_get_subdev_input_index(vpfe, + &subdev_index, + &inp_index, + index); + if (ret < 0) { + vpfe_err(vpfe, "invalid input index: %d\n", index); + goto get_out; + } + + sdinfo = &vpfe->cfg->sub_devs[subdev_index]; + sdinfo->sd = vpfe->sd[subdev_index]; + route = &sdinfo->routes[inp_index]; + if (route && sdinfo->can_route) { + input = route->input; + output = route->output; + if (sdinfo->sd) { + ret = v4l2_subdev_call(sdinfo->sd, video, + s_routing, input, output, 0); + if (ret) { + vpfe_err(vpfe, "s_routing failed\n"); + ret = -EINVAL; + goto get_out; + } + } + + } + + vpfe->current_subdev = sdinfo; + if (sdinfo->sd) + vpfe->v4l2_dev.ctrl_handler = sdinfo->sd->ctrl_handler; + vpfe->current_input = index; + vpfe->std_index = 0; + + /* set the bus/interface parameter for the sub device in ccdc */ + ret = vpfe_ccdc_set_hw_if_params(&vpfe->ccdc, &sdinfo->vpfe_param); + if (ret) + return ret; + + /* set the default image parameters in the device */ + return vpfe_config_image_format(vpfe, + vpfe_standards[vpfe->std_index].std_id); + +get_out: + return ret; +} + +static int vpfe_s_input(struct file *file, void *priv, unsigned int index) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + return vpfe_set_input(vpfe, index); +} + +static int vpfe_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + + sdinfo = vpfe->current_subdev; + if (!(sdinfo->inputs[0].capabilities & V4L2_IN_CAP_STD)) + return -ENODATA; + + /* Call querystd function of decoder device */ + return v4l2_device_call_until_err(&vpfe->v4l2_dev, sdinfo->grp_id, + video, querystd, std_id); +} + +static int vpfe_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + int ret; + + sdinfo = vpfe->current_subdev; + if (!(sdinfo->inputs[0].capabilities & V4L2_IN_CAP_STD)) + return -ENODATA; + + /* if trying to set the same std then nothing to do */ + if (vpfe_standards[vpfe->std_index].std_id == std_id) + return 0; + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + ret = -EBUSY; + return ret; + } + + ret = v4l2_device_call_until_err(&vpfe->v4l2_dev, sdinfo->grp_id, + video, s_std, std_id); + if (ret < 0) { + vpfe_err(vpfe, "Failed to set standard\n"); + return ret; + } + ret = vpfe_config_image_format(vpfe, std_id); + + return ret; +} + +static int vpfe_g_std(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct vpfe_subdev_info *sdinfo; + + sdinfo = vpfe->current_subdev; + if (sdinfo->inputs[0].capabilities != V4L2_IN_CAP_STD) + return -ENODATA; + + *std_id = vpfe_standards[vpfe->std_index].std_id; + + return 0; +} + +/* + * vpfe_calculate_offsets : This function calculates buffers offset + * for top and bottom field + */ +static void vpfe_calculate_offsets(struct vpfe_device *vpfe) +{ + struct v4l2_rect image_win; + + vpfe_ccdc_get_image_window(&vpfe->ccdc, &image_win); + vpfe->field_off = image_win.height * image_win.width; +} + +/* + * vpfe_queue_setup - Callback function for buffer setup. + * @vq: vb2_queue ptr + * @nbuffers: ptr to number of buffers requested by application + * @nplanes:: contains number of distinct video planes needed to hold a frame + * @sizes[]: contains the size (in bytes) of each plane. + * @alloc_devs: ptr to allocation context + * + * This callback function is called when reqbuf() is called to adjust + * the buffer count and buffer size + */ +static int vpfe_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct vpfe_device *vpfe = vb2_get_drv_priv(vq); + unsigned size = vpfe->fmt.fmt.pix.sizeimage; + + if (vq->num_buffers + *nbuffers < 3) + *nbuffers = 3 - vq->num_buffers; + + if (*nplanes) { + if (sizes[0] < size) + return -EINVAL; + size = sizes[0]; + } + + *nplanes = 1; + sizes[0] = size; + + vpfe_dbg(1, vpfe, + "nbuffers=%d, size=%u\n", *nbuffers, sizes[0]); + + /* Calculate field offset */ + vpfe_calculate_offsets(vpfe); + + return 0; +} + +/* + * vpfe_buffer_prepare : callback function for buffer prepare + * @vb: ptr to vb2_buffer + * + * This is the callback function for buffer prepare when vb2_qbuf() + * function is called. The buffer is prepared and user space virtual address + * or user address is converted into physical address + */ +static int vpfe_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vpfe_device *vpfe = vb2_get_drv_priv(vb->vb2_queue); + + vb2_set_plane_payload(vb, 0, vpfe->fmt.fmt.pix.sizeimage); + + if (vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) + return -EINVAL; + + vbuf->field = vpfe->fmt.fmt.pix.field; + + return 0; +} + +/* + * vpfe_buffer_queue : Callback function to add buffer to DMA queue + * @vb: ptr to vb2_buffer + */ +static void vpfe_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vpfe_device *vpfe = vb2_get_drv_priv(vb->vb2_queue); + struct vpfe_cap_buffer *buf = to_vpfe_buffer(vbuf); + unsigned long flags = 0; + + /* add the buffer to the DMA queue */ + spin_lock_irqsave(&vpfe->dma_queue_lock, flags); + list_add_tail(&buf->list, &vpfe->dma_queue); + spin_unlock_irqrestore(&vpfe->dma_queue_lock, flags); +} + +static void vpfe_return_all_buffers(struct vpfe_device *vpfe, + enum vb2_buffer_state state) +{ + struct vpfe_cap_buffer *buf, *node; + unsigned long flags; + + spin_lock_irqsave(&vpfe->dma_queue_lock, flags); + list_for_each_entry_safe(buf, node, &vpfe->dma_queue, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } + + if (vpfe->cur_frm) + vb2_buffer_done(&vpfe->cur_frm->vb.vb2_buf, state); + + if (vpfe->next_frm && vpfe->next_frm != vpfe->cur_frm) + vb2_buffer_done(&vpfe->next_frm->vb.vb2_buf, state); + + vpfe->cur_frm = NULL; + vpfe->next_frm = NULL; + spin_unlock_irqrestore(&vpfe->dma_queue_lock, flags); +} + +/* + * vpfe_start_streaming : Starts the DMA engine for streaming + * @vb: ptr to vb2_buffer + * @count: number of buffers + */ +static int vpfe_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vpfe_device *vpfe = vb2_get_drv_priv(vq); + struct vpfe_subdev_info *sdinfo; + unsigned long flags; + unsigned long addr; + int ret; + + spin_lock_irqsave(&vpfe->dma_queue_lock, flags); + + vpfe->field = 0; + vpfe->sequence = 0; + + sdinfo = vpfe->current_subdev; + + vpfe_attach_irq(vpfe); + + vpfe->stopping = false; + init_completion(&vpfe->capture_stop); + + if (vpfe->ccdc.ccdc_cfg.if_type == VPFE_RAW_BAYER) + vpfe_ccdc_config_raw(&vpfe->ccdc); + else + vpfe_ccdc_config_ycbcr(&vpfe->ccdc); + + /* Get the next frame from the buffer queue */ + vpfe->next_frm = list_entry(vpfe->dma_queue.next, + struct vpfe_cap_buffer, list); + vpfe->cur_frm = vpfe->next_frm; + /* Remove buffer from the buffer queue */ + list_del(&vpfe->cur_frm->list); + spin_unlock_irqrestore(&vpfe->dma_queue_lock, flags); + + addr = vb2_dma_contig_plane_dma_addr(&vpfe->cur_frm->vb.vb2_buf, 0); + + vpfe_set_sdr_addr(&vpfe->ccdc, (unsigned long)(addr)); + + vpfe_pcr_enable(&vpfe->ccdc, 1); + + ret = v4l2_subdev_call(sdinfo->sd, video, s_stream, 1); + if (ret < 0) { + vpfe_err(vpfe, "Error in attaching interrupt handle\n"); + goto err; + } + + return 0; + +err: + vpfe_return_all_buffers(vpfe, VB2_BUF_STATE_QUEUED); + vpfe_pcr_enable(&vpfe->ccdc, 0); + return ret; +} + +/* + * vpfe_stop_streaming : Stop the DMA engine + * @vq: ptr to vb2_queue + * + * This callback stops the DMA engine and any remaining buffers + * in the DMA queue are released. + */ +static void vpfe_stop_streaming(struct vb2_queue *vq) +{ + struct vpfe_device *vpfe = vb2_get_drv_priv(vq); + struct vpfe_subdev_info *sdinfo; + int ret; + + vpfe_pcr_enable(&vpfe->ccdc, 0); + + /* Wait for the last frame to be captured */ + vpfe->stopping = true; + wait_for_completion_timeout(&vpfe->capture_stop, + msecs_to_jiffies(250)); + + vpfe_detach_irq(vpfe); + + sdinfo = vpfe->current_subdev; + ret = v4l2_subdev_call(sdinfo->sd, video, s_stream, 0); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + vpfe_dbg(1, vpfe, "stream off failed in subdev\n"); + + /* release all active buffers */ + vpfe_return_all_buffers(vpfe, VB2_BUF_STATE_ERROR); +} + +static int vpfe_g_pixelaspect(struct file *file, void *priv, + int type, struct v4l2_fract *f) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + vpfe->std_index >= ARRAY_SIZE(vpfe_standards)) + return -EINVAL; + + *f = vpfe_standards[vpfe->std_index].pixelaspect; + + return 0; +} + +static int +vpfe_g_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct vpfe_device *vpfe = video_drvdata(file); + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + vpfe->std_index >= ARRAY_SIZE(vpfe_standards)) + return -EINVAL; + + switch (s->target) { + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + s->r.left = 0; + s->r.top = 0; + s->r.width = vpfe_standards[vpfe->std_index].width; + s->r.height = vpfe_standards[vpfe->std_index].height; + break; + + case V4L2_SEL_TGT_CROP: + s->r = vpfe->crop; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int +vpfe_s_selection(struct file *file, void *fh, struct v4l2_selection *s) +{ + struct vpfe_device *vpfe = video_drvdata(file); + struct v4l2_rect cr = vpfe->crop; + struct v4l2_rect r = s->r; + u32 bpp; + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + + if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE || + s->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + v4l_bound_align_image(&r.width, 0, cr.width, 0, + &r.height, 0, cr.height, 0, 0); + + r.left = clamp_t(unsigned int, r.left, 0, cr.width - r.width); + r.top = clamp_t(unsigned int, r.top, 0, cr.height - r.height); + + if (s->flags & V4L2_SEL_FLAG_LE && !v4l2_rect_enclosed(&r, &s->r)) + return -ERANGE; + + if (s->flags & V4L2_SEL_FLAG_GE && !v4l2_rect_enclosed(&s->r, &r)) + return -ERANGE; + + s->r = vpfe->crop = r; + + bpp = __get_bytesperpixel(vpfe, vpfe->current_vpfe_fmt); + vpfe_ccdc_set_image_window(&vpfe->ccdc, &r, bpp); + vpfe->fmt.fmt.pix.width = r.width; + vpfe->fmt.fmt.pix.height = r.height; + vpfe->fmt.fmt.pix.bytesperline = + vpfe_ccdc_get_line_length(&vpfe->ccdc); + vpfe->fmt.fmt.pix.sizeimage = vpfe->fmt.fmt.pix.bytesperline * + vpfe->fmt.fmt.pix.height; + + vpfe_dbg(1, vpfe, "cropped (%d,%d)/%dx%d of %dx%d\n", + r.left, r.top, r.width, r.height, cr.width, cr.height); + + return 0; +} + +static long vpfe_ioctl_default(struct file *file, void *priv, + bool valid_prio, unsigned int cmd, void *param) +{ + struct vpfe_device *vpfe = video_drvdata(file); + int ret; + + if (!valid_prio) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + + /* If streaming is started, return error */ + if (vb2_is_busy(&vpfe->buffer_queue)) { + vpfe_err(vpfe, "%s device busy\n", __func__); + return -EBUSY; + } + + switch (cmd) { + case VIDIOC_AM437X_CCDC_CFG: + ret = vpfe_ccdc_set_params(&vpfe->ccdc, (void __user *)param); + if (ret) { + vpfe_dbg(2, vpfe, + "Error setting parameters in CCDC\n"); + return ret; + } + ret = vpfe_get_ccdc_image_format(vpfe, + &vpfe->fmt); + if (ret < 0) { + vpfe_dbg(2, vpfe, + "Invalid image format at CCDC\n"); + return ret; + } + break; + + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +static const struct vb2_ops vpfe_video_qops = { + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .queue_setup = vpfe_queue_setup, + .buf_prepare = vpfe_buffer_prepare, + .buf_queue = vpfe_buffer_queue, + .start_streaming = vpfe_start_streaming, + .stop_streaming = vpfe_stop_streaming, +}; + +/* vpfe capture driver file operations */ +static const struct v4l2_file_operations vpfe_fops = { + .owner = THIS_MODULE, + .open = vpfe_open, + .release = vpfe_release, + .read = vb2_fop_read, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, +}; + +/* vpfe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpfe_ioctl_ops = { + .vidioc_querycap = vpfe_querycap, + .vidioc_enum_fmt_vid_cap = vpfe_enum_fmt, + .vidioc_g_fmt_vid_cap = vpfe_g_fmt, + .vidioc_s_fmt_vid_cap = vpfe_s_fmt, + .vidioc_try_fmt_vid_cap = vpfe_try_fmt, + + .vidioc_enum_framesizes = vpfe_enum_size, + + .vidioc_enum_input = vpfe_enum_input, + .vidioc_g_input = vpfe_g_input, + .vidioc_s_input = vpfe_s_input, + + .vidioc_querystd = vpfe_querystd, + .vidioc_s_std = vpfe_s_std, + .vidioc_g_std = vpfe_g_std, + + .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_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + + .vidioc_g_pixelaspect = vpfe_g_pixelaspect, + .vidioc_g_selection = vpfe_g_selection, + .vidioc_s_selection = vpfe_s_selection, + + .vidioc_default = vpfe_ioctl_default, +}; + +static int +vpfe_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct vpfe_device *vpfe = container_of(notifier->v4l2_dev, + struct vpfe_device, v4l2_dev); + struct vpfe_subdev_info *sdinfo; + struct vpfe_fmt *fmt; + int ret = 0; + bool found = false; + int i, j, k; + + for (i = 0; i < ARRAY_SIZE(vpfe->cfg->asd); i++) { + if (vpfe->cfg->asd[i]->match.fwnode == + asd[i].match.fwnode) { + sdinfo = &vpfe->cfg->sub_devs[i]; + vpfe->sd[i] = subdev; + vpfe->sd[i]->grp_id = sdinfo->grp_id; + found = true; + break; + } + } + + if (!found) { + vpfe_info(vpfe, "sub device (%s) not matched\n", subdev->name); + return -EINVAL; + } + + vpfe->video_dev.tvnorms |= sdinfo->inputs[0].std; + + vpfe->num_active_fmt = 0; + for (j = 0, i = 0; (ret != -EINVAL); ++j) { + struct v4l2_subdev_mbus_code_enum mbus_code = { + .index = j, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + ret = v4l2_subdev_call(subdev, pad, enum_mbus_code, + NULL, &mbus_code); + if (ret) + continue; + + vpfe_dbg(3, vpfe, + "subdev %s: code: %04x idx: %d\n", + subdev->name, mbus_code.code, j); + + for (k = 0; k < ARRAY_SIZE(formats); k++) { + fmt = &formats[k]; + if (mbus_code.code != fmt->code) + continue; + vpfe->active_fmt[i] = fmt; + vpfe_dbg(3, vpfe, + "matched fourcc: %s code: %04x idx: %d\n", + print_fourcc(fmt->fourcc), mbus_code.code, i); + vpfe->num_active_fmt = ++i; + } + } + + if (!i) { + vpfe_err(vpfe, "No suitable format reported by subdev %s\n", + subdev->name); + return -EINVAL; + } + return 0; +} + +static int vpfe_probe_complete(struct vpfe_device *vpfe) +{ + struct video_device *vdev; + struct vb2_queue *q; + int err; + + spin_lock_init(&vpfe->dma_queue_lock); + mutex_init(&vpfe->lock); + + vpfe->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* set first sub device as current one */ + vpfe->current_subdev = &vpfe->cfg->sub_devs[0]; + vpfe->v4l2_dev.ctrl_handler = vpfe->sd[0]->ctrl_handler; + + err = vpfe_set_input(vpfe, 0); + if (err) + goto probe_out; + + /* Initialize videobuf2 queue as per the buffer type */ + q = &vpfe->buffer_queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF | VB2_READ; + q->drv_priv = vpfe; + q->ops = &vpfe_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct vpfe_cap_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &vpfe->lock; + q->min_buffers_needed = 1; + q->dev = vpfe->pdev; + + err = vb2_queue_init(q); + if (err) { + vpfe_err(vpfe, "vb2_queue_init() failed\n"); + goto probe_out; + } + + INIT_LIST_HEAD(&vpfe->dma_queue); + + vdev = &vpfe->video_dev; + strscpy(vdev->name, VPFE_MODULE_NAME, sizeof(vdev->name)); + vdev->release = video_device_release_empty; + vdev->fops = &vpfe_fops; + vdev->ioctl_ops = &vpfe_ioctl_ops; + vdev->v4l2_dev = &vpfe->v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->queue = q; + vdev->lock = &vpfe->lock; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE; + video_set_drvdata(vdev, vpfe); + err = video_register_device(&vpfe->video_dev, VFL_TYPE_VIDEO, -1); + if (err) { + vpfe_err(vpfe, + "Unable to register video device.\n"); + goto probe_out; + } + + return 0; + +probe_out: + v4l2_device_unregister(&vpfe->v4l2_dev); + return err; +} + +static int vpfe_async_complete(struct v4l2_async_notifier *notifier) +{ + struct vpfe_device *vpfe = container_of(notifier->v4l2_dev, + struct vpfe_device, v4l2_dev); + + return vpfe_probe_complete(vpfe); +} + +static const struct v4l2_async_notifier_operations vpfe_async_ops = { + .bound = vpfe_async_bound, + .complete = vpfe_async_complete, +}; + +static struct vpfe_config * +vpfe_get_pdata(struct vpfe_device *vpfe) +{ + struct device_node *endpoint = NULL; + struct device *dev = vpfe->pdev; + struct vpfe_subdev_info *sdinfo; + struct vpfe_config *pdata; + unsigned int flags; + unsigned int i; + int err; + + dev_dbg(dev, "vpfe_get_pdata\n"); + + v4l2_async_nf_init(&vpfe->notifier); + + if (!IS_ENABLED(CONFIG_OF) || !dev->of_node) + return dev->platform_data; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + + for (i = 0; ; i++) { + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + struct device_node *rem; + + endpoint = of_graph_get_next_endpoint(dev->of_node, endpoint); + if (!endpoint) + break; + + sdinfo = &pdata->sub_devs[i]; + sdinfo->grp_id = 0; + + /* we only support camera */ + sdinfo->inputs[0].index = i; + strscpy(sdinfo->inputs[0].name, "Camera", + sizeof(sdinfo->inputs[0].name)); + sdinfo->inputs[0].type = V4L2_INPUT_TYPE_CAMERA; + sdinfo->inputs[0].std = V4L2_STD_ALL; + sdinfo->inputs[0].capabilities = V4L2_IN_CAP_STD; + + sdinfo->can_route = 0; + sdinfo->routes = NULL; + + of_property_read_u32(endpoint, "ti,am437x-vpfe-interface", + &sdinfo->vpfe_param.if_type); + if (sdinfo->vpfe_param.if_type < 0 || + sdinfo->vpfe_param.if_type > 4) { + sdinfo->vpfe_param.if_type = VPFE_RAW_BAYER; + } + + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &bus_cfg); + if (err) { + dev_err(dev, "Could not parse the endpoint\n"); + goto cleanup; + } + + sdinfo->vpfe_param.bus_width = bus_cfg.bus.parallel.bus_width; + + if (sdinfo->vpfe_param.bus_width < 8 || + sdinfo->vpfe_param.bus_width > 16) { + dev_err(dev, "Invalid bus width.\n"); + goto cleanup; + } + + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + sdinfo->vpfe_param.hdpol = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + sdinfo->vpfe_param.vdpol = 1; + + rem = of_graph_get_remote_port_parent(endpoint); + if (!rem) { + dev_err(dev, "Remote device at %pOF not found\n", + endpoint); + goto cleanup; + } + + pdata->asd[i] = v4l2_async_nf_add_fwnode(&vpfe->notifier, + of_fwnode_handle(rem), + struct + v4l2_async_subdev); + of_node_put(rem); + if (IS_ERR(pdata->asd[i])) + goto cleanup; + } + + of_node_put(endpoint); + return pdata; + +cleanup: + v4l2_async_nf_cleanup(&vpfe->notifier); + of_node_put(endpoint); + return NULL; +} + +/* + * vpfe_probe : This function creates device entries by register + * itself to the V4L2 driver and initializes fields of each + * device objects + */ +static int vpfe_probe(struct platform_device *pdev) +{ + struct vpfe_config *vpfe_cfg; + struct vpfe_device *vpfe; + struct vpfe_ccdc *ccdc; + int ret; + + vpfe = devm_kzalloc(&pdev->dev, sizeof(*vpfe), GFP_KERNEL); + if (!vpfe) + return -ENOMEM; + + vpfe->pdev = &pdev->dev; + + vpfe_cfg = vpfe_get_pdata(vpfe); + if (!vpfe_cfg) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + + vpfe->cfg = vpfe_cfg; + ccdc = &vpfe->ccdc; + + ccdc->ccdc_cfg.base_addr = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ccdc->ccdc_cfg.base_addr)) { + ret = PTR_ERR(ccdc->ccdc_cfg.base_addr); + goto probe_out_cleanup; + } + + ret = platform_get_irq(pdev, 0); + if (ret <= 0) { + ret = -ENODEV; + goto probe_out_cleanup; + } + vpfe->irq = ret; + + ret = devm_request_irq(vpfe->pdev, vpfe->irq, vpfe_isr, 0, + "vpfe_capture0", vpfe); + if (ret) { + dev_err(&pdev->dev, "Unable to request interrupt\n"); + ret = -EINVAL; + goto probe_out_cleanup; + } + + ret = v4l2_device_register(&pdev->dev, &vpfe->v4l2_dev); + if (ret) { + vpfe_err(vpfe, + "Unable to register v4l2 device.\n"); + goto probe_out_cleanup; + } + + /* set the driver data in platform device */ + platform_set_drvdata(pdev, vpfe); + /* Enabling module functional clock */ + pm_runtime_enable(&pdev->dev); + + /* for now just enable it here instead of waiting for the open */ + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret < 0) { + vpfe_err(vpfe, "Unable to resume device.\n"); + goto probe_out_v4l2_unregister; + } + + vpfe_ccdc_config_defaults(ccdc); + + pm_runtime_put_sync(&pdev->dev); + + vpfe->sd = devm_kcalloc(&pdev->dev, + ARRAY_SIZE(vpfe->cfg->asd), + sizeof(struct v4l2_subdev *), + GFP_KERNEL); + if (!vpfe->sd) { + ret = -ENOMEM; + goto probe_out_v4l2_unregister; + } + + vpfe->notifier.ops = &vpfe_async_ops; + ret = v4l2_async_nf_register(&vpfe->v4l2_dev, &vpfe->notifier); + if (ret) { + vpfe_err(vpfe, "Error registering async notifier\n"); + ret = -EINVAL; + goto probe_out_v4l2_unregister; + } + + return 0; + +probe_out_v4l2_unregister: + v4l2_device_unregister(&vpfe->v4l2_dev); +probe_out_cleanup: + v4l2_async_nf_cleanup(&vpfe->notifier); + return ret; +} + +/* + * vpfe_remove : It un-register device from V4L2 driver + */ +static int vpfe_remove(struct platform_device *pdev) +{ + struct vpfe_device *vpfe = platform_get_drvdata(pdev); + + pm_runtime_disable(&pdev->dev); + + v4l2_async_nf_unregister(&vpfe->notifier); + v4l2_async_nf_cleanup(&vpfe->notifier); + v4l2_device_unregister(&vpfe->v4l2_dev); + video_unregister_device(&vpfe->video_dev); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP + +static void vpfe_save_context(struct vpfe_ccdc *ccdc) +{ + ccdc->ccdc_ctx[VPFE_PCR >> 2] = vpfe_reg_read(ccdc, VPFE_PCR); + ccdc->ccdc_ctx[VPFE_SYNMODE >> 2] = vpfe_reg_read(ccdc, VPFE_SYNMODE); + ccdc->ccdc_ctx[VPFE_SDOFST >> 2] = vpfe_reg_read(ccdc, VPFE_SDOFST); + ccdc->ccdc_ctx[VPFE_SDR_ADDR >> 2] = vpfe_reg_read(ccdc, VPFE_SDR_ADDR); + ccdc->ccdc_ctx[VPFE_CLAMP >> 2] = vpfe_reg_read(ccdc, VPFE_CLAMP); + ccdc->ccdc_ctx[VPFE_DCSUB >> 2] = vpfe_reg_read(ccdc, VPFE_DCSUB); + ccdc->ccdc_ctx[VPFE_COLPTN >> 2] = vpfe_reg_read(ccdc, VPFE_COLPTN); + ccdc->ccdc_ctx[VPFE_BLKCMP >> 2] = vpfe_reg_read(ccdc, VPFE_BLKCMP); + ccdc->ccdc_ctx[VPFE_VDINT >> 2] = vpfe_reg_read(ccdc, VPFE_VDINT); + ccdc->ccdc_ctx[VPFE_ALAW >> 2] = vpfe_reg_read(ccdc, VPFE_ALAW); + ccdc->ccdc_ctx[VPFE_REC656IF >> 2] = vpfe_reg_read(ccdc, VPFE_REC656IF); + ccdc->ccdc_ctx[VPFE_CCDCFG >> 2] = vpfe_reg_read(ccdc, VPFE_CCDCFG); + ccdc->ccdc_ctx[VPFE_CULLING >> 2] = vpfe_reg_read(ccdc, VPFE_CULLING); + ccdc->ccdc_ctx[VPFE_HD_VD_WID >> 2] = vpfe_reg_read(ccdc, + VPFE_HD_VD_WID); + ccdc->ccdc_ctx[VPFE_PIX_LINES >> 2] = vpfe_reg_read(ccdc, + VPFE_PIX_LINES); + ccdc->ccdc_ctx[VPFE_HORZ_INFO >> 2] = vpfe_reg_read(ccdc, + VPFE_HORZ_INFO); + ccdc->ccdc_ctx[VPFE_VERT_START >> 2] = vpfe_reg_read(ccdc, + VPFE_VERT_START); + ccdc->ccdc_ctx[VPFE_VERT_LINES >> 2] = vpfe_reg_read(ccdc, + VPFE_VERT_LINES); + ccdc->ccdc_ctx[VPFE_HSIZE_OFF >> 2] = vpfe_reg_read(ccdc, + VPFE_HSIZE_OFF); +} + +static int vpfe_suspend(struct device *dev) +{ + struct vpfe_device *vpfe = dev_get_drvdata(dev); + struct vpfe_ccdc *ccdc = &vpfe->ccdc; + + /* only do full suspend if streaming has started */ + if (vb2_start_streaming_called(&vpfe->buffer_queue)) { + /* + * ignore RPM resume errors here, as it is already too late. + * A check like that should happen earlier, either at + * open() or just before start streaming. + */ + pm_runtime_get_sync(dev); + vpfe_config_enable(ccdc, 1); + + /* Save VPFE context */ + vpfe_save_context(ccdc); + + /* Disable CCDC */ + vpfe_pcr_enable(ccdc, 0); + vpfe_config_enable(ccdc, 0); + + /* Disable both master and slave clock */ + pm_runtime_put_sync(dev); + } + + /* Select sleep pin state */ + pinctrl_pm_select_sleep_state(dev); + + return 0; +} + +static void vpfe_restore_context(struct vpfe_ccdc *ccdc) +{ + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_SYNMODE >> 2], VPFE_SYNMODE); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_CULLING >> 2], VPFE_CULLING); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_SDOFST >> 2], VPFE_SDOFST); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_SDR_ADDR >> 2], VPFE_SDR_ADDR); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_CLAMP >> 2], VPFE_CLAMP); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_DCSUB >> 2], VPFE_DCSUB); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_COLPTN >> 2], VPFE_COLPTN); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_BLKCMP >> 2], VPFE_BLKCMP); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_VDINT >> 2], VPFE_VDINT); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_ALAW >> 2], VPFE_ALAW); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_REC656IF >> 2], VPFE_REC656IF); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_CCDCFG >> 2], VPFE_CCDCFG); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_PCR >> 2], VPFE_PCR); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_HD_VD_WID >> 2], + VPFE_HD_VD_WID); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_PIX_LINES >> 2], + VPFE_PIX_LINES); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_HORZ_INFO >> 2], + VPFE_HORZ_INFO); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_VERT_START >> 2], + VPFE_VERT_START); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_VERT_LINES >> 2], + VPFE_VERT_LINES); + vpfe_reg_write(ccdc, ccdc->ccdc_ctx[VPFE_HSIZE_OFF >> 2], + VPFE_HSIZE_OFF); +} + +static int vpfe_resume(struct device *dev) +{ + struct vpfe_device *vpfe = dev_get_drvdata(dev); + struct vpfe_ccdc *ccdc = &vpfe->ccdc; + + /* only do full resume if streaming has started */ + if (vb2_start_streaming_called(&vpfe->buffer_queue)) { + /* Enable both master and slave clock */ + pm_runtime_get_sync(dev); + vpfe_config_enable(ccdc, 1); + + /* Restore VPFE context */ + vpfe_restore_context(ccdc); + + vpfe_config_enable(ccdc, 0); + pm_runtime_put_sync(dev); + } + + /* Select default pin state */ + pinctrl_pm_select_default_state(dev); + + return 0; +} + +#endif + +static SIMPLE_DEV_PM_OPS(vpfe_pm_ops, vpfe_suspend, vpfe_resume); + +static const struct of_device_id vpfe_of_match[] = { + { .compatible = "ti,am437x-vpfe", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, vpfe_of_match); + +static struct platform_driver vpfe_driver = { + .probe = vpfe_probe, + .remove = vpfe_remove, + .driver = { + .name = VPFE_MODULE_NAME, + .pm = &vpfe_pm_ops, + .of_match_table = of_match_ptr(vpfe_of_match), + }, +}; + +module_platform_driver(vpfe_driver); + +MODULE_AUTHOR("Texas Instruments"); +MODULE_DESCRIPTION("TI AM437x VPFE driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VPFE_VERSION); diff --git a/drivers/media/platform/ti/am437x/am437x-vpfe.h b/drivers/media/platform/ti/am437x/am437x-vpfe.h new file mode 100644 index 000000000..f8b4e917b --- /dev/null +++ b/drivers/media/platform/ti/am437x/am437x-vpfe.h @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2014 Texas Instruments, Inc. + * + * Benoit Parrot + * Lad, Prabhakar + */ + +#ifndef AM437X_VPFE_H +#define AM437X_VPFE_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "am437x-vpfe_regs.h" + +enum vpfe_pin_pol { + VPFE_PINPOL_POSITIVE = 0, + VPFE_PINPOL_NEGATIVE, +}; + +enum vpfe_hw_if_type { + /* Raw Bayer */ + VPFE_RAW_BAYER = 0, + /* BT656 - 8 bit */ + VPFE_BT656, + /* BT656 - 10 bit */ + VPFE_BT656_10BIT, + /* YCbCr - 8 bit with external sync */ + VPFE_YCBCR_SYNC_8, + /* YCbCr - 16 bit with external sync */ + VPFE_YCBCR_SYNC_16, +}; + +/* interface description */ +struct vpfe_hw_if_param { + enum vpfe_hw_if_type if_type; + enum vpfe_pin_pol hdpol; + enum vpfe_pin_pol vdpol; + unsigned int bus_width; +}; + +#define VPFE_MAX_SUBDEV 1 +#define VPFE_MAX_INPUTS 1 + +struct vpfe_std_info { + int active_pixels; + int active_lines; + /* current frame format */ + int frame_format; +}; + +struct vpfe_route { + u32 input; + u32 output; +}; + +struct vpfe_subdev_info { + /* Sub device group id */ + int grp_id; + /* inputs available at the sub device */ + struct v4l2_input inputs[VPFE_MAX_INPUTS]; + /* Sub dev routing information for each input */ + struct vpfe_route *routes; + /* check if sub dev supports routing */ + int can_route; + /* ccdc bus/interface configuration */ + struct vpfe_hw_if_param vpfe_param; + struct v4l2_subdev *sd; +}; + +struct vpfe_config { + /* information about each subdev */ + struct vpfe_subdev_info sub_devs[VPFE_MAX_SUBDEV]; + /* Flat array, arranged in groups */ + struct v4l2_async_subdev *asd[VPFE_MAX_SUBDEV]; +}; + +struct vpfe_cap_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +enum ccdc_pixfmt { + CCDC_PIXFMT_RAW = 0, + CCDC_PIXFMT_YCBCR_16BIT, + CCDC_PIXFMT_YCBCR_8BIT, +}; + +enum ccdc_frmfmt { + CCDC_FRMFMT_PROGRESSIVE = 0, + CCDC_FRMFMT_INTERLACED, +}; + +/* PIXEL ORDER IN MEMORY from LSB to MSB */ +/* only applicable for 8-bit input mode */ +enum ccdc_pixorder { + CCDC_PIXORDER_YCBYCR, + CCDC_PIXORDER_CBYCRY, +}; + +enum ccdc_buftype { + CCDC_BUFTYPE_FLD_INTERLEAVED, + CCDC_BUFTYPE_FLD_SEPARATED +}; + + +/* returns the highest bit used for the gamma */ +static inline u8 ccdc_gamma_width_max_bit(enum vpfe_ccdc_gamma_width width) +{ + return 15 - width; +} + +/* returns the highest bit used for this data size */ +static inline u8 ccdc_data_size_max_bit(enum vpfe_ccdc_data_size sz) +{ + return sz == VPFE_CCDC_DATA_8BITS ? 7 : 15 - sz; +} + +/* Structure for CCDC configuration parameters for raw capture mode */ +struct ccdc_params_raw { + /* pixel format */ + enum ccdc_pixfmt pix_fmt; + /* progressive or interlaced frame */ + enum ccdc_frmfmt frm_fmt; + struct v4l2_rect win; + /* Current Format Bytes Per Pixels */ + unsigned int bytesperpixel; + /* Current Format Bytes per Lines + * (Aligned to 32 bytes) used for HORZ_INFO + */ + unsigned int bytesperline; + /* field id polarity */ + enum vpfe_pin_pol fid_pol; + /* vertical sync polarity */ + enum vpfe_pin_pol vd_pol; + /* horizontal sync polarity */ + enum vpfe_pin_pol hd_pol; + /* interleaved or separated fields */ + enum ccdc_buftype buf_type; + /* + * enable to store the image in inverse + * order in memory(bottom to top) + */ + unsigned char image_invert_enable; + /* configurable parameters */ + struct vpfe_ccdc_config_params_raw config_params; +}; + +struct ccdc_params_ycbcr { + /* pixel format */ + enum ccdc_pixfmt pix_fmt; + /* progressive or interlaced frame */ + enum ccdc_frmfmt frm_fmt; + struct v4l2_rect win; + /* Current Format Bytes Per Pixels */ + unsigned int bytesperpixel; + /* Current Format Bytes per Lines + * (Aligned to 32 bytes) used for HORZ_INFO + */ + unsigned int bytesperline; + /* field id polarity */ + enum vpfe_pin_pol fid_pol; + /* vertical sync polarity */ + enum vpfe_pin_pol vd_pol; + /* horizontal sync polarity */ + enum vpfe_pin_pol hd_pol; + /* enable BT.656 embedded sync mode */ + int bt656_enable; + /* cb:y:cr:y or y:cb:y:cr in memory */ + enum ccdc_pixorder pix_order; + /* interleaved or separated fields */ + enum ccdc_buftype buf_type; +}; + +/* + * CCDC operational configuration + */ +struct ccdc_config { + /* CCDC interface type */ + enum vpfe_hw_if_type if_type; + /* Raw Bayer configuration */ + struct ccdc_params_raw bayer; + /* YCbCr configuration */ + struct ccdc_params_ycbcr ycbcr; + /* ccdc base address */ + void __iomem *base_addr; +}; + +struct vpfe_ccdc { + struct ccdc_config ccdc_cfg; + u32 ccdc_ctx[VPFE_REG_END / sizeof(u32)]; +}; + +/* + * struct vpfe_fmt - VPFE media bus format information + * fourcc: V4L2 pixel format code + * code: V4L2 media bus format code + * bitsperpixel: Bits per pixel over the bus + */ +struct vpfe_fmt { + u32 fourcc; + u32 code; + u32 bitsperpixel; +}; + +/* + * When formats[] is modified make sure to adjust this value also. + * Expect compile time warnings if VPFE_NUM_FORMATS is smaller then + * the number of elements in formats[]. + */ +#define VPFE_NUM_FORMATS 10 + +struct vpfe_device { + /* V4l2 specific parameters */ + /* Identifies video device for this channel */ + struct video_device video_dev; + /* sub devices */ + struct v4l2_subdev **sd; + /* vpfe cfg */ + struct vpfe_config *cfg; + /* V4l2 device */ + struct v4l2_device v4l2_dev; + /* parent device */ + struct device *pdev; + /* subdevice async Notifier */ + struct v4l2_async_notifier notifier; + /* Indicates id of the field which is being displayed */ + unsigned field; + unsigned sequence; + /* current interface type */ + struct vpfe_hw_if_param vpfe_if_params; + /* ptr to currently selected sub device */ + struct vpfe_subdev_info *current_subdev; + /* current input at the sub device */ + int current_input; + /* Keeps track of the information about the standard */ + struct vpfe_std_info std_info; + /* std index into std table */ + int std_index; + /* IRQs used when CCDC output to SDRAM */ + unsigned int irq; + /* Pointer pointing to current v4l2_buffer */ + struct vpfe_cap_buffer *cur_frm; + /* Pointer pointing to next v4l2_buffer */ + struct vpfe_cap_buffer *next_frm; + /* Used to store pixel format */ + struct v4l2_format fmt; + /* Used to keep a reference to the current vpfe_fmt */ + struct vpfe_fmt *current_vpfe_fmt; + struct vpfe_fmt *active_fmt[VPFE_NUM_FORMATS]; + unsigned int num_active_fmt; + + /* + * used when IMP is chained to store the crop window which + * is different from the image window + */ + struct v4l2_rect crop; + /* Buffer queue used in vb2 */ + struct vb2_queue buffer_queue; + /* Queue of filled frames */ + struct list_head dma_queue; + /* IRQ lock for DMA queue */ + spinlock_t dma_queue_lock; + /* lock used to access this structure */ + struct mutex lock; + /* + * offset where second field starts from the starting of the + * buffer for field separated YCbCr formats + */ + u32 field_off; + struct vpfe_ccdc ccdc; + int stopping; + struct completion capture_stop; +}; + +#endif /* AM437X_VPFE_H */ diff --git a/drivers/media/platform/ti/am437x/am437x-vpfe_regs.h b/drivers/media/platform/ti/am437x/am437x-vpfe_regs.h new file mode 100644 index 000000000..63ecdca3b --- /dev/null +++ b/drivers/media/platform/ti/am437x/am437x-vpfe_regs.h @@ -0,0 +1,132 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * TI AM437x Image Sensor Interface Registers + * + * Copyright (C) 2013 - 2014 Texas Instruments, Inc. + * + * Benoit Parrot + * Lad, Prabhakar + */ + +#ifndef AM437X_VPFE_REGS_H +#define AM437X_VPFE_REGS_H + +/* VPFE module register offset */ +#define VPFE_REVISION 0x0 +#define VPFE_PCR 0x4 +#define VPFE_SYNMODE 0x8 +#define VPFE_HD_VD_WID 0xc +#define VPFE_PIX_LINES 0x10 +#define VPFE_HORZ_INFO 0x14 +#define VPFE_VERT_START 0x18 +#define VPFE_VERT_LINES 0x1c +#define VPFE_CULLING 0x20 +#define VPFE_HSIZE_OFF 0x24 +#define VPFE_SDOFST 0x28 +#define VPFE_SDR_ADDR 0x2c +#define VPFE_CLAMP 0x30 +#define VPFE_DCSUB 0x34 +#define VPFE_COLPTN 0x38 +#define VPFE_BLKCMP 0x3c +#define VPFE_VDINT 0x48 +#define VPFE_ALAW 0x4c +#define VPFE_REC656IF 0x50 +#define VPFE_CCDCFG 0x54 +#define VPFE_DMA_CNTL 0x98 +#define VPFE_SYSCONFIG 0x104 +#define VPFE_CONFIG 0x108 +#define VPFE_IRQ_EOI 0x110 +#define VPFE_IRQ_STS_RAW 0x114 +#define VPFE_IRQ_STS 0x118 +#define VPFE_IRQ_EN_SET 0x11c +#define VPFE_IRQ_EN_CLR 0x120 +#define VPFE_REG_END 0x124 + +/* Define bit fields within selected registers */ +#define VPFE_FID_POL_MASK 1 +#define VPFE_FID_POL_SHIFT 4 +#define VPFE_HD_POL_MASK 1 +#define VPFE_HD_POL_SHIFT 3 +#define VPFE_VD_POL_MASK 1 +#define VPFE_VD_POL_SHIFT 2 +#define VPFE_HSIZE_OFF_MASK 0xffffffe0 +#define VPFE_32BYTE_ALIGN_VAL 31 +#define VPFE_FRM_FMT_MASK 0x1 +#define VPFE_FRM_FMT_SHIFT 7 +#define VPFE_DATA_SZ_MASK 7 +#define VPFE_DATA_SZ_SHIFT 8 +#define VPFE_PIX_FMT_MASK 3 +#define VPFE_PIX_FMT_SHIFT 12 +#define VPFE_VP2SDR_DISABLE 0xfffbffff +#define VPFE_WEN_ENABLE BIT(17) +#define VPFE_SDR2RSZ_DISABLE 0xfff7ffff +#define VPFE_VDHDEN_ENABLE BIT(16) +#define VPFE_LPF_ENABLE BIT(14) +#define VPFE_ALAW_ENABLE BIT(3) +#define VPFE_ALAW_GAMMA_WD_MASK 7 +#define VPFE_BLK_CLAMP_ENABLE BIT(31) +#define VPFE_BLK_SGAIN_MASK 0x1f +#define VPFE_BLK_ST_PXL_MASK 0x7fff +#define VPFE_BLK_ST_PXL_SHIFT 10 +#define VPFE_BLK_SAMPLE_LN_MASK 7 +#define VPFE_BLK_SAMPLE_LN_SHIFT 28 +#define VPFE_BLK_SAMPLE_LINE_MASK 7 +#define VPFE_BLK_SAMPLE_LINE_SHIFT 25 +#define VPFE_BLK_DC_SUB_MASK 0x03fff +#define VPFE_BLK_COMP_MASK 0xff +#define VPFE_BLK_COMP_GB_COMP_SHIFT 8 +#define VPFE_BLK_COMP_GR_COMP_SHIFT 16 +#define VPFE_BLK_COMP_R_COMP_SHIFT 24 +#define VPFE_LATCH_ON_VSYNC_DISABLE BIT(15) +#define VPFE_DATA_PACK_ENABLE BIT(11) +#define VPFE_HORZ_INFO_SPH_SHIFT 16 +#define VPFE_VERT_START_SLV0_SHIFT 16 +#define VPFE_VDINT_VDINT0_SHIFT 16 +#define VPFE_VDINT_VDINT1_MASK 0xffff +#define VPFE_PPC_RAW 1 +#define VPFE_DCSUB_DEFAULT_VAL 0 +#define VPFE_CLAMP_DEFAULT_VAL 0 +#define VPFE_COLPTN_VAL 0xbb11bb11 +#define VPFE_TWO_BYTES_PER_PIXEL 2 +#define VPFE_INTERLACED_IMAGE_INVERT 0x4b6d +#define VPFE_INTERLACED_NO_IMAGE_INVERT 0x0249 +#define VPFE_PROGRESSIVE_IMAGE_INVERT 0x4000 +#define VPFE_PROGRESSIVE_NO_IMAGE_INVERT 0 +#define VPFE_INTERLACED_HEIGHT_SHIFT 1 +#define VPFE_SYN_MODE_INPMOD_SHIFT 12 +#define VPFE_SYN_MODE_INPMOD_MASK 3 +#define VPFE_SYN_MODE_8BITS (7 << 8) +#define VPFE_SYN_MODE_10BITS (6 << 8) +#define VPFE_SYN_MODE_11BITS (5 << 8) +#define VPFE_SYN_MODE_12BITS (4 << 8) +#define VPFE_SYN_MODE_13BITS (3 << 8) +#define VPFE_SYN_MODE_14BITS (2 << 8) +#define VPFE_SYN_MODE_15BITS (1 << 8) +#define VPFE_SYN_MODE_16BITS (0 << 8) +#define VPFE_SYN_FLDMODE_MASK 1 +#define VPFE_SYN_FLDMODE_SHIFT 7 +#define VPFE_REC656IF_BT656_EN 3 +#define VPFE_SYN_MODE_VD_POL_NEGATIVE BIT(2) +#define VPFE_CCDCFG_Y8POS_SHIFT 11 +#define VPFE_CCDCFG_BW656_10BIT BIT(5) +#define VPFE_SDOFST_FIELD_INTERLEAVED 0x249 +#define VPFE_NO_CULLING 0xffff00ff +#define VPFE_VDINT0 BIT(0) +#define VPFE_VDINT1 BIT(1) +#define VPFE_VDINT2 BIT(2) +#define VPFE_DMA_CNTL_OVERFLOW BIT(31) + +#define VPFE_CONFIG_PCLK_INV_SHIFT 0 +#define VPFE_CONFIG_PCLK_INV_MASK 1 +#define VPFE_CONFIG_PCLK_INV_NOT_INV 0 +#define VPFE_CONFIG_PCLK_INV_INV 1 +#define VPFE_CONFIG_EN_SHIFT 1 +#define VPFE_CONFIG_EN_MASK 2 +#define VPFE_CONFIG_EN_DISABLE 0 +#define VPFE_CONFIG_EN_ENABLE 1 +#define VPFE_CONFIG_ST_SHIFT 2 +#define VPFE_CONFIG_ST_MASK 4 +#define VPFE_CONFIG_ST_OCP_ACTIVE 0 +#define VPFE_CONFIG_ST_OCP_STANDBY 1 + +#endif /* AM437X_VPFE_REGS_H */ diff --git a/drivers/media/platform/ti/cal/Makefile b/drivers/media/platform/ti/cal/Makefile new file mode 100644 index 000000000..45ac35585 --- /dev/null +++ b/drivers/media/platform/ti/cal/Makefile @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_VIDEO_TI_CAL) += ti-cal.o +ti-cal-y := cal.o cal-camerarx.o cal-video.o diff --git a/drivers/media/platform/ti/cal/cal-camerarx.c b/drivers/media/platform/ti/cal/cal-camerarx.c new file mode 100644 index 000000000..16ae52879 --- /dev/null +++ b/drivers/media/platform/ti/cal/cal-camerarx.c @@ -0,0 +1,943 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI Camera Access Layer (CAL) - CAMERARX + * + * Copyright (c) 2015-2020 Texas Instruments Inc. + * + * Authors: + * Benoit Parrot + * Laurent Pinchart + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "cal.h" +#include "cal_regs.h" + +/* ------------------------------------------------------------------ + * I/O Register Accessors + * ------------------------------------------------------------------ + */ + +static inline u32 camerarx_read(struct cal_camerarx *phy, u32 offset) +{ + return ioread32(phy->base + offset); +} + +static inline void camerarx_write(struct cal_camerarx *phy, u32 offset, u32 val) +{ + iowrite32(val, phy->base + offset); +} + +/* ------------------------------------------------------------------ + * CAMERARX Management + * ------------------------------------------------------------------ + */ + +static s64 cal_camerarx_get_ext_link_freq(struct cal_camerarx *phy) +{ + struct v4l2_mbus_config_mipi_csi2 *mipi_csi2 = &phy->endpoint.bus.mipi_csi2; + u32 num_lanes = mipi_csi2->num_data_lanes; + const struct cal_format_info *fmtinfo; + u32 bpp; + s64 freq; + + fmtinfo = cal_format_by_code(phy->formats[CAL_CAMERARX_PAD_SINK].code); + if (!fmtinfo) + return -EINVAL; + + bpp = fmtinfo->bpp; + + freq = v4l2_get_link_freq(phy->source->ctrl_handler, bpp, 2 * num_lanes); + if (freq < 0) { + phy_err(phy, "failed to get link freq for subdev '%s'\n", + phy->source->name); + return freq; + } + + phy_dbg(3, phy, "Source Link Freq: %llu\n", freq); + + return freq; +} + +static void cal_camerarx_lane_config(struct cal_camerarx *phy) +{ + u32 val = cal_read(phy->cal, CAL_CSI2_COMPLEXIO_CFG(phy->instance)); + u32 lane_mask = CAL_CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK; + u32 polarity_mask = CAL_CSI2_COMPLEXIO_CFG_CLOCK_POL_MASK; + struct v4l2_mbus_config_mipi_csi2 *mipi_csi2 = + &phy->endpoint.bus.mipi_csi2; + int lane; + + cal_set_field(&val, mipi_csi2->clock_lane + 1, lane_mask); + cal_set_field(&val, mipi_csi2->lane_polarities[0], polarity_mask); + for (lane = 0; lane < mipi_csi2->num_data_lanes; lane++) { + /* + * Every lane are one nibble apart starting with the + * clock followed by the data lanes so shift masks by 4. + */ + lane_mask <<= 4; + polarity_mask <<= 4; + cal_set_field(&val, mipi_csi2->data_lanes[lane] + 1, lane_mask); + cal_set_field(&val, mipi_csi2->lane_polarities[lane + 1], + polarity_mask); + } + + cal_write(phy->cal, CAL_CSI2_COMPLEXIO_CFG(phy->instance), val); + phy_dbg(3, phy, "CAL_CSI2_COMPLEXIO_CFG(%d) = 0x%08x\n", + phy->instance, val); +} + +static void cal_camerarx_enable(struct cal_camerarx *phy) +{ + u32 num_lanes = phy->cal->data->camerarx[phy->instance].num_lanes; + + regmap_field_write(phy->fields[F_CAMMODE], 0); + /* Always enable all lanes at the phy control level */ + regmap_field_write(phy->fields[F_LANEENABLE], (1 << num_lanes) - 1); + /* F_CSI_MODE is not present on every architecture */ + if (phy->fields[F_CSI_MODE]) + regmap_field_write(phy->fields[F_CSI_MODE], 1); + regmap_field_write(phy->fields[F_CTRLCLKEN], 1); +} + +void cal_camerarx_disable(struct cal_camerarx *phy) +{ + regmap_field_write(phy->fields[F_CTRLCLKEN], 0); +} + +/* + * TCLK values are OK at their reset values + */ +#define TCLK_TERM 0 +#define TCLK_MISS 1 +#define TCLK_SETTLE 14 + +static void cal_camerarx_config(struct cal_camerarx *phy, s64 link_freq) +{ + unsigned int reg0, reg1; + unsigned int ths_term, ths_settle; + + /* DPHY timing configuration */ + + /* THS_TERM: Programmed value = floor(20 ns/DDRClk period) */ + ths_term = div_s64(20 * link_freq, 1000 * 1000 * 1000); + phy_dbg(1, phy, "ths_term: %d (0x%02x)\n", ths_term, ths_term); + + /* THS_SETTLE: Programmed value = floor(105 ns/DDRClk period) + 4 */ + ths_settle = div_s64(105 * link_freq, 1000 * 1000 * 1000) + 4; + phy_dbg(1, phy, "ths_settle: %d (0x%02x)\n", ths_settle, ths_settle); + + reg0 = camerarx_read(phy, CAL_CSI2_PHY_REG0); + cal_set_field(®0, CAL_CSI2_PHY_REG0_HSCLOCKCONFIG_DISABLE, + CAL_CSI2_PHY_REG0_HSCLOCKCONFIG_MASK); + cal_set_field(®0, ths_term, CAL_CSI2_PHY_REG0_THS_TERM_MASK); + cal_set_field(®0, ths_settle, CAL_CSI2_PHY_REG0_THS_SETTLE_MASK); + + phy_dbg(1, phy, "CSI2_%d_REG0 = 0x%08x\n", phy->instance, reg0); + camerarx_write(phy, CAL_CSI2_PHY_REG0, reg0); + + reg1 = camerarx_read(phy, CAL_CSI2_PHY_REG1); + cal_set_field(®1, TCLK_TERM, CAL_CSI2_PHY_REG1_TCLK_TERM_MASK); + cal_set_field(®1, 0xb8, CAL_CSI2_PHY_REG1_DPHY_HS_SYNC_PATTERN_MASK); + cal_set_field(®1, TCLK_MISS, + CAL_CSI2_PHY_REG1_CTRLCLK_DIV_FACTOR_MASK); + cal_set_field(®1, TCLK_SETTLE, CAL_CSI2_PHY_REG1_TCLK_SETTLE_MASK); + + phy_dbg(1, phy, "CSI2_%d_REG1 = 0x%08x\n", phy->instance, reg1); + camerarx_write(phy, CAL_CSI2_PHY_REG1, reg1); +} + +static void cal_camerarx_power(struct cal_camerarx *phy, bool enable) +{ + u32 target_state; + unsigned int i; + + target_state = enable ? CAL_CSI2_COMPLEXIO_CFG_PWR_CMD_STATE_ON : + CAL_CSI2_COMPLEXIO_CFG_PWR_CMD_STATE_OFF; + + cal_write_field(phy->cal, CAL_CSI2_COMPLEXIO_CFG(phy->instance), + target_state, CAL_CSI2_COMPLEXIO_CFG_PWR_CMD_MASK); + + for (i = 0; i < 10; i++) { + u32 current_state; + + current_state = cal_read_field(phy->cal, + CAL_CSI2_COMPLEXIO_CFG(phy->instance), + CAL_CSI2_COMPLEXIO_CFG_PWR_STATUS_MASK); + + if (current_state == target_state) + break; + + usleep_range(1000, 1100); + } + + if (i == 10) + phy_err(phy, "Failed to power %s complexio\n", + enable ? "up" : "down"); +} + +static void cal_camerarx_wait_reset(struct cal_camerarx *phy) +{ + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(750); + while (time_before(jiffies, timeout)) { + if (cal_read_field(phy->cal, + CAL_CSI2_COMPLEXIO_CFG(phy->instance), + CAL_CSI2_COMPLEXIO_CFG_RESET_DONE_MASK) == + CAL_CSI2_COMPLEXIO_CFG_RESET_DONE_RESETCOMPLETED) + break; + usleep_range(500, 5000); + } + + if (cal_read_field(phy->cal, CAL_CSI2_COMPLEXIO_CFG(phy->instance), + CAL_CSI2_COMPLEXIO_CFG_RESET_DONE_MASK) != + CAL_CSI2_COMPLEXIO_CFG_RESET_DONE_RESETCOMPLETED) + phy_err(phy, "Timeout waiting for Complex IO reset done\n"); +} + +static void cal_camerarx_wait_stop_state(struct cal_camerarx *phy) +{ + unsigned long timeout; + + timeout = jiffies + msecs_to_jiffies(750); + while (time_before(jiffies, timeout)) { + if (cal_read_field(phy->cal, + CAL_CSI2_TIMING(phy->instance), + CAL_CSI2_TIMING_FORCE_RX_MODE_IO1_MASK) == 0) + break; + usleep_range(500, 5000); + } + + if (cal_read_field(phy->cal, CAL_CSI2_TIMING(phy->instance), + CAL_CSI2_TIMING_FORCE_RX_MODE_IO1_MASK) != 0) + phy_err(phy, "Timeout waiting for stop state\n"); +} + +static void cal_camerarx_enable_irqs(struct cal_camerarx *phy) +{ + const u32 cio_err_mask = + CAL_CSI2_COMPLEXIO_IRQ_LANE_ERRORS_MASK | + CAL_CSI2_COMPLEXIO_IRQ_FIFO_OVR_MASK | + CAL_CSI2_COMPLEXIO_IRQ_SHORT_PACKET_MASK | + CAL_CSI2_COMPLEXIO_IRQ_ECC_NO_CORRECTION_MASK; + const u32 vc_err_mask = + CAL_CSI2_VC_IRQ_CS_IRQ_MASK(0) | + CAL_CSI2_VC_IRQ_CS_IRQ_MASK(1) | + CAL_CSI2_VC_IRQ_CS_IRQ_MASK(2) | + CAL_CSI2_VC_IRQ_CS_IRQ_MASK(3) | + CAL_CSI2_VC_IRQ_ECC_CORRECTION_IRQ_MASK(0) | + CAL_CSI2_VC_IRQ_ECC_CORRECTION_IRQ_MASK(1) | + CAL_CSI2_VC_IRQ_ECC_CORRECTION_IRQ_MASK(2) | + CAL_CSI2_VC_IRQ_ECC_CORRECTION_IRQ_MASK(3); + + /* Enable CIO & VC error IRQs. */ + cal_write(phy->cal, CAL_HL_IRQENABLE_SET(0), + CAL_HL_IRQ_CIO_MASK(phy->instance) | + CAL_HL_IRQ_VC_MASK(phy->instance)); + cal_write(phy->cal, CAL_CSI2_COMPLEXIO_IRQENABLE(phy->instance), + cio_err_mask); + cal_write(phy->cal, CAL_CSI2_VC_IRQENABLE(phy->instance), + vc_err_mask); +} + +static void cal_camerarx_disable_irqs(struct cal_camerarx *phy) +{ + /* Disable CIO error irqs */ + cal_write(phy->cal, CAL_HL_IRQENABLE_CLR(0), + CAL_HL_IRQ_CIO_MASK(phy->instance) | + CAL_HL_IRQ_VC_MASK(phy->instance)); + cal_write(phy->cal, CAL_CSI2_COMPLEXIO_IRQENABLE(phy->instance), 0); + cal_write(phy->cal, CAL_CSI2_VC_IRQENABLE(phy->instance), 0); +} + +static void cal_camerarx_ppi_enable(struct cal_camerarx *phy) +{ + cal_write_field(phy->cal, CAL_CSI2_PPI_CTRL(phy->instance), + 1, CAL_CSI2_PPI_CTRL_ECC_EN_MASK); + + cal_write_field(phy->cal, CAL_CSI2_PPI_CTRL(phy->instance), + 1, CAL_CSI2_PPI_CTRL_IF_EN_MASK); +} + +static void cal_camerarx_ppi_disable(struct cal_camerarx *phy) +{ + cal_write_field(phy->cal, CAL_CSI2_PPI_CTRL(phy->instance), + 0, CAL_CSI2_PPI_CTRL_IF_EN_MASK); +} + +static int cal_camerarx_start(struct cal_camerarx *phy) +{ + s64 link_freq; + u32 sscounter; + u32 val; + int ret; + + if (phy->enable_count > 0) { + phy->enable_count++; + return 0; + } + + link_freq = cal_camerarx_get_ext_link_freq(phy); + if (link_freq < 0) + return link_freq; + + ret = v4l2_subdev_call(phy->source, core, s_power, 1); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) { + phy_err(phy, "power on failed in subdev\n"); + return ret; + } + + cal_camerarx_enable_irqs(phy); + + /* + * CSI-2 PHY Link Initialization Sequence, according to the DRA74xP / + * DRA75xP / DRA76xP / DRA77xP TRM. The DRA71x / DRA72x and the AM65x / + * DRA80xM TRMs have a slightly simplified sequence. + */ + + /* + * 1. Configure all CSI-2 low level protocol registers to be ready to + * receive signals/data from the CSI-2 PHY. + * + * i.-v. Configure the lanes position and polarity. + */ + cal_camerarx_lane_config(phy); + + /* + * vi.-vii. Configure D-PHY mode, enable the required lanes and + * enable the CAMERARX clock. + */ + cal_camerarx_enable(phy); + + /* + * 2. CSI PHY and link initialization sequence. + * + * a. Deassert the CSI-2 PHY reset. Do not wait for reset completion + * at this point, as it requires the external source to send the + * CSI-2 HS clock. + */ + cal_write_field(phy->cal, CAL_CSI2_COMPLEXIO_CFG(phy->instance), + CAL_CSI2_COMPLEXIO_CFG_RESET_CTRL_OPERATIONAL, + CAL_CSI2_COMPLEXIO_CFG_RESET_CTRL_MASK); + phy_dbg(3, phy, "CAL_CSI2_COMPLEXIO_CFG(%d) = 0x%08x De-assert Complex IO Reset\n", + phy->instance, + cal_read(phy->cal, CAL_CSI2_COMPLEXIO_CFG(phy->instance))); + + /* Dummy read to allow SCP reset to complete. */ + camerarx_read(phy, CAL_CSI2_PHY_REG0); + + /* Program the PHY timing parameters. */ + cal_camerarx_config(phy, link_freq); + + /* + * b. Assert the FORCERXMODE signal. + * + * The stop-state-counter is based on fclk cycles, and we always use + * the x16 and x4 settings, so stop-state-timeout = + * fclk-cycle * 16 * 4 * counter. + * + * Stop-state-timeout must be more than 100us as per CSI-2 spec, so we + * calculate a timeout that's 100us (rounding up). + */ + sscounter = DIV_ROUND_UP(clk_get_rate(phy->cal->fclk), 10000 * 16 * 4); + + val = cal_read(phy->cal, CAL_CSI2_TIMING(phy->instance)); + cal_set_field(&val, 1, CAL_CSI2_TIMING_STOP_STATE_X16_IO1_MASK); + cal_set_field(&val, 1, CAL_CSI2_TIMING_STOP_STATE_X4_IO1_MASK); + cal_set_field(&val, sscounter, + CAL_CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK); + cal_write(phy->cal, CAL_CSI2_TIMING(phy->instance), val); + phy_dbg(3, phy, "CAL_CSI2_TIMING(%d) = 0x%08x Stop States\n", + phy->instance, + cal_read(phy->cal, CAL_CSI2_TIMING(phy->instance))); + + /* Assert the FORCERXMODE signal. */ + cal_write_field(phy->cal, CAL_CSI2_TIMING(phy->instance), + 1, CAL_CSI2_TIMING_FORCE_RX_MODE_IO1_MASK); + phy_dbg(3, phy, "CAL_CSI2_TIMING(%d) = 0x%08x Force RXMODE\n", + phy->instance, + cal_read(phy->cal, CAL_CSI2_TIMING(phy->instance))); + + /* + * c. Connect pull-down on CSI-2 PHY link (using pad control). + * + * This is not required on DRA71x, DRA72x, AM65x and DRA80xM. Not + * implemented. + */ + + /* + * d. Power up the CSI-2 PHY. + * e. Check whether the state status reaches the ON state. + */ + cal_camerarx_power(phy, true); + + /* + * Start the source to enable the CSI-2 HS clock. We can now wait for + * CSI-2 PHY reset to complete. + */ + ret = v4l2_subdev_call(phy->source, video, s_stream, 1); + if (ret) { + v4l2_subdev_call(phy->source, core, s_power, 0); + cal_camerarx_disable_irqs(phy); + phy_err(phy, "stream on failed in subdev\n"); + return ret; + } + + cal_camerarx_wait_reset(phy); + + /* f. Wait for STOPSTATE=1 for all enabled lane modules. */ + cal_camerarx_wait_stop_state(phy); + + phy_dbg(1, phy, "CSI2_%u_REG1 = 0x%08x (bits 31-28 should be set)\n", + phy->instance, camerarx_read(phy, CAL_CSI2_PHY_REG1)); + + /* + * g. Disable pull-down on CSI-2 PHY link (using pad control). + * + * This is not required on DRA71x, DRA72x, AM65x and DRA80xM. Not + * implemented. + */ + + /* Finally, enable the PHY Protocol Interface (PPI). */ + cal_camerarx_ppi_enable(phy); + + phy->enable_count++; + + return 0; +} + +static void cal_camerarx_stop(struct cal_camerarx *phy) +{ + int ret; + + if (--phy->enable_count > 0) + return; + + cal_camerarx_ppi_disable(phy); + + cal_camerarx_disable_irqs(phy); + + cal_camerarx_power(phy, false); + + /* Assert Complex IO Reset */ + cal_write_field(phy->cal, CAL_CSI2_COMPLEXIO_CFG(phy->instance), + CAL_CSI2_COMPLEXIO_CFG_RESET_CTRL, + CAL_CSI2_COMPLEXIO_CFG_RESET_CTRL_MASK); + + phy_dbg(3, phy, "CAL_CSI2_COMPLEXIO_CFG(%d) = 0x%08x Complex IO in Reset\n", + phy->instance, + cal_read(phy->cal, CAL_CSI2_COMPLEXIO_CFG(phy->instance))); + + /* Disable the phy */ + cal_camerarx_disable(phy); + + if (v4l2_subdev_call(phy->source, video, s_stream, 0)) + phy_err(phy, "stream off failed in subdev\n"); + + ret = v4l2_subdev_call(phy->source, core, s_power, 0); + if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) + phy_err(phy, "power off failed in subdev\n"); +} + +/* + * Errata i913: CSI2 LDO Needs to be disabled when module is powered on + * + * Enabling CSI2 LDO shorts it to core supply. It is crucial the 2 CSI2 + * LDOs on the device are disabled if CSI-2 module is powered on + * (0x4845 B304 | 0x4845 B384 [28:27] = 0x1) or in ULPS (0x4845 B304 + * | 0x4845 B384 [28:27] = 0x2) mode. Common concerns include: high + * current draw on the module supply in active mode. + * + * Errata does not apply when CSI-2 module is powered off + * (0x4845 B304 | 0x4845 B384 [28:27] = 0x0). + * + * SW Workaround: + * Set the following register bits to disable the LDO, + * which is essentially CSI2 REG10 bit 6: + * + * Core 0: 0x4845 B828 = 0x0000 0040 + * Core 1: 0x4845 B928 = 0x0000 0040 + */ +void cal_camerarx_i913_errata(struct cal_camerarx *phy) +{ + u32 reg10 = camerarx_read(phy, CAL_CSI2_PHY_REG10); + + cal_set_field(®10, 1, CAL_CSI2_PHY_REG10_I933_LDO_DISABLE_MASK); + + phy_dbg(1, phy, "CSI2_%d_REG10 = 0x%08x\n", phy->instance, reg10); + camerarx_write(phy, CAL_CSI2_PHY_REG10, reg10); +} + +static int cal_camerarx_regmap_init(struct cal_dev *cal, + struct cal_camerarx *phy) +{ + const struct cal_camerarx_data *phy_data; + unsigned int i; + + if (!cal->data) + return -EINVAL; + + phy_data = &cal->data->camerarx[phy->instance]; + + for (i = 0; i < F_MAX_FIELDS; i++) { + struct reg_field field = { + .reg = cal->syscon_camerrx_offset, + .lsb = phy_data->fields[i].lsb, + .msb = phy_data->fields[i].msb, + }; + + /* + * Here we update the reg offset with the + * value found in DT + */ + phy->fields[i] = devm_regmap_field_alloc(cal->dev, + cal->syscon_camerrx, + field); + if (IS_ERR(phy->fields[i])) { + cal_err(cal, "Unable to allocate regmap fields\n"); + return PTR_ERR(phy->fields[i]); + } + } + + return 0; +} + +static int cal_camerarx_parse_dt(struct cal_camerarx *phy) +{ + struct v4l2_fwnode_endpoint *endpoint = &phy->endpoint; + char data_lanes[V4L2_MBUS_CSI2_MAX_DATA_LANES * 2]; + struct device_node *ep_node; + unsigned int i; + int ret; + + /* + * Find the endpoint node for the port corresponding to the PHY + * instance, and parse its CSI-2-related properties. + */ + ep_node = of_graph_get_endpoint_by_regs(phy->cal->dev->of_node, + phy->instance, 0); + if (!ep_node) { + /* + * The endpoint is not mandatory, not all PHY instances need to + * be connected in DT. + */ + phy_dbg(3, phy, "Port has no endpoint\n"); + return 0; + } + + endpoint->bus_type = V4L2_MBUS_CSI2_DPHY; + ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), endpoint); + if (ret < 0) { + phy_err(phy, "Failed to parse endpoint\n"); + goto done; + } + + for (i = 0; i < endpoint->bus.mipi_csi2.num_data_lanes; i++) { + unsigned int lane = endpoint->bus.mipi_csi2.data_lanes[i]; + + if (lane > 4) { + phy_err(phy, "Invalid position %u for data lane %u\n", + lane, i); + ret = -EINVAL; + goto done; + } + + data_lanes[i*2] = '0' + lane; + data_lanes[i*2+1] = ' '; + } + + data_lanes[i*2-1] = '\0'; + + phy_dbg(3, phy, + "CSI-2 bus: clock lane <%u>, data lanes <%s>, flags 0x%08x\n", + endpoint->bus.mipi_csi2.clock_lane, data_lanes, + endpoint->bus.mipi_csi2.flags); + + /* Retrieve the connected device and store it for later use. */ + phy->source_ep_node = of_graph_get_remote_endpoint(ep_node); + phy->source_node = of_graph_get_port_parent(phy->source_ep_node); + if (!phy->source_node) { + phy_dbg(3, phy, "Can't get remote parent\n"); + of_node_put(phy->source_ep_node); + ret = -EINVAL; + goto done; + } + + phy_dbg(1, phy, "Found connected device %pOFn\n", phy->source_node); + +done: + of_node_put(ep_node); + return ret; +} + +int cal_camerarx_get_remote_frame_desc(struct cal_camerarx *phy, + struct v4l2_mbus_frame_desc *desc) +{ + struct media_pad *pad; + int ret; + + if (!phy->source) + return -EPIPE; + + pad = media_pad_remote_pad_first(&phy->pads[CAL_CAMERARX_PAD_SINK]); + if (!pad) + return -EPIPE; + + ret = v4l2_subdev_call(phy->source, pad, get_frame_desc, pad->index, + desc); + if (ret) + return ret; + + if (desc->type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + dev_err(phy->cal->dev, + "Frame descriptor does not describe CSI-2 link"); + return -EINVAL; + } + + return 0; +} + +/* ------------------------------------------------------------------ + * V4L2 Subdev Operations + * ------------------------------------------------------------------ + */ + +static inline struct cal_camerarx *to_cal_camerarx(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cal_camerarx, subdev); +} + +static struct v4l2_mbus_framefmt * +cal_camerarx_get_pad_format(struct cal_camerarx *phy, + struct v4l2_subdev_state *state, + unsigned int pad, u32 which) +{ + switch (which) { + case V4L2_SUBDEV_FORMAT_TRY: + return v4l2_subdev_get_try_format(&phy->subdev, state, pad); + case V4L2_SUBDEV_FORMAT_ACTIVE: + return &phy->formats[pad]; + default: + return NULL; + } +} + +static int cal_camerarx_sd_s_stream(struct v4l2_subdev *sd, int enable) +{ + struct cal_camerarx *phy = to_cal_camerarx(sd); + int ret = 0; + + mutex_lock(&phy->mutex); + + if (enable) + ret = cal_camerarx_start(phy); + else + cal_camerarx_stop(phy); + + mutex_unlock(&phy->mutex); + + return ret; +} + +static int cal_camerarx_sd_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct cal_camerarx *phy = to_cal_camerarx(sd); + int ret = 0; + + mutex_lock(&phy->mutex); + + /* No transcoding, source and sink codes must match. */ + if (cal_rx_pad_is_source(code->pad)) { + struct v4l2_mbus_framefmt *fmt; + + if (code->index > 0) { + ret = -EINVAL; + goto out; + } + + fmt = cal_camerarx_get_pad_format(phy, state, + CAL_CAMERARX_PAD_SINK, + code->which); + code->code = fmt->code; + } else { + if (code->index >= cal_num_formats) { + ret = -EINVAL; + goto out; + } + + code->code = cal_formats[code->index].code; + } + +out: + mutex_unlock(&phy->mutex); + + return ret; +} + +static int cal_camerarx_sd_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + struct cal_camerarx *phy = to_cal_camerarx(sd); + const struct cal_format_info *fmtinfo; + int ret = 0; + + if (fse->index > 0) + return -EINVAL; + + mutex_lock(&phy->mutex); + + /* No transcoding, source and sink formats must match. */ + if (cal_rx_pad_is_source(fse->pad)) { + struct v4l2_mbus_framefmt *fmt; + + fmt = cal_camerarx_get_pad_format(phy, state, + CAL_CAMERARX_PAD_SINK, + fse->which); + if (fse->code != fmt->code) { + ret = -EINVAL; + goto out; + } + + fse->min_width = fmt->width; + fse->max_width = fmt->width; + fse->min_height = fmt->height; + fse->max_height = fmt->height; + } else { + fmtinfo = cal_format_by_code(fse->code); + if (!fmtinfo) { + ret = -EINVAL; + goto out; + } + + fse->min_width = CAL_MIN_WIDTH_BYTES * 8 / ALIGN(fmtinfo->bpp, 8); + fse->max_width = CAL_MAX_WIDTH_BYTES * 8 / ALIGN(fmtinfo->bpp, 8); + fse->min_height = CAL_MIN_HEIGHT_LINES; + fse->max_height = CAL_MAX_HEIGHT_LINES; + } + +out: + mutex_unlock(&phy->mutex); + + return ret; +} + +static int cal_camerarx_sd_get_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct cal_camerarx *phy = to_cal_camerarx(sd); + struct v4l2_mbus_framefmt *fmt; + + mutex_lock(&phy->mutex); + + fmt = cal_camerarx_get_pad_format(phy, state, format->pad, + format->which); + format->format = *fmt; + + mutex_unlock(&phy->mutex); + + return 0; +} + +static int cal_camerarx_sd_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct cal_camerarx *phy = to_cal_camerarx(sd); + const struct cal_format_info *fmtinfo; + struct v4l2_mbus_framefmt *fmt; + unsigned int bpp; + + /* No transcoding, source and sink formats must match. */ + if (cal_rx_pad_is_source(format->pad)) + return cal_camerarx_sd_get_fmt(sd, state, format); + + /* + * Default to the first format if the requested media bus code isn't + * supported. + */ + fmtinfo = cal_format_by_code(format->format.code); + if (!fmtinfo) + fmtinfo = &cal_formats[0]; + + /* Clamp the size, update the code. The colorspace is accepted as-is. */ + bpp = ALIGN(fmtinfo->bpp, 8); + + format->format.width = clamp_t(unsigned int, format->format.width, + CAL_MIN_WIDTH_BYTES * 8 / bpp, + CAL_MAX_WIDTH_BYTES * 8 / bpp); + format->format.height = clamp_t(unsigned int, format->format.height, + CAL_MIN_HEIGHT_LINES, + CAL_MAX_HEIGHT_LINES); + format->format.code = fmtinfo->code; + format->format.field = V4L2_FIELD_NONE; + + /* Store the format and propagate it to the source pad. */ + + mutex_lock(&phy->mutex); + + fmt = cal_camerarx_get_pad_format(phy, state, + CAL_CAMERARX_PAD_SINK, + format->which); + *fmt = format->format; + + fmt = cal_camerarx_get_pad_format(phy, state, + CAL_CAMERARX_PAD_FIRST_SOURCE, + format->which); + *fmt = format->format; + + mutex_unlock(&phy->mutex); + + return 0; +} + +static int cal_camerarx_sd_init_cfg(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_format format = { + .which = state ? V4L2_SUBDEV_FORMAT_TRY + : V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = CAL_CAMERARX_PAD_SINK, + .format = { + .width = 640, + .height = 480, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }, + }; + + return cal_camerarx_sd_set_fmt(sd, state, &format); +} + +static const struct v4l2_subdev_video_ops cal_camerarx_video_ops = { + .s_stream = cal_camerarx_sd_s_stream, +}; + +static const struct v4l2_subdev_pad_ops cal_camerarx_pad_ops = { + .init_cfg = cal_camerarx_sd_init_cfg, + .enum_mbus_code = cal_camerarx_sd_enum_mbus_code, + .enum_frame_size = cal_camerarx_sd_enum_frame_size, + .get_fmt = cal_camerarx_sd_get_fmt, + .set_fmt = cal_camerarx_sd_set_fmt, +}; + +static const struct v4l2_subdev_ops cal_camerarx_subdev_ops = { + .video = &cal_camerarx_video_ops, + .pad = &cal_camerarx_pad_ops, +}; + +static struct media_entity_operations cal_camerarx_media_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* ------------------------------------------------------------------ + * Create and Destroy + * ------------------------------------------------------------------ + */ + +struct cal_camerarx *cal_camerarx_create(struct cal_dev *cal, + unsigned int instance) +{ + struct platform_device *pdev = to_platform_device(cal->dev); + struct cal_camerarx *phy; + struct v4l2_subdev *sd; + unsigned int i; + int ret; + + phy = kzalloc(sizeof(*phy), GFP_KERNEL); + if (!phy) + return ERR_PTR(-ENOMEM); + + phy->cal = cal; + phy->instance = instance; + + spin_lock_init(&phy->vc_lock); + mutex_init(&phy->mutex); + + phy->res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + (instance == 0) ? + "cal_rx_core0" : + "cal_rx_core1"); + phy->base = devm_ioremap_resource(cal->dev, phy->res); + if (IS_ERR(phy->base)) { + cal_err(cal, "failed to ioremap\n"); + ret = PTR_ERR(phy->base); + goto error; + } + + cal_dbg(1, cal, "ioresource %s at %pa - %pa\n", + phy->res->name, &phy->res->start, &phy->res->end); + + ret = cal_camerarx_regmap_init(cal, phy); + if (ret) + goto error; + + ret = cal_camerarx_parse_dt(phy); + if (ret) + goto error; + + /* Initialize the V4L2 subdev and media entity. */ + sd = &phy->subdev; + v4l2_subdev_init(sd, &cal_camerarx_subdev_ops); + sd->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + sd->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + snprintf(sd->name, sizeof(sd->name), "CAMERARX%u", instance); + sd->dev = cal->dev; + + phy->pads[CAL_CAMERARX_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + for (i = CAL_CAMERARX_PAD_FIRST_SOURCE; i < CAL_CAMERARX_NUM_PADS; ++i) + phy->pads[i].flags = MEDIA_PAD_FL_SOURCE; + sd->entity.ops = &cal_camerarx_media_ops; + ret = media_entity_pads_init(&sd->entity, ARRAY_SIZE(phy->pads), + phy->pads); + if (ret) + goto error; + + ret = cal_camerarx_sd_init_cfg(sd, NULL); + if (ret) + goto error; + + ret = v4l2_device_register_subdev(&cal->v4l2_dev, sd); + if (ret) + goto error; + + return phy; + +error: + media_entity_cleanup(&phy->subdev.entity); + kfree(phy); + return ERR_PTR(ret); +} + +void cal_camerarx_destroy(struct cal_camerarx *phy) +{ + if (!phy) + return; + + v4l2_device_unregister_subdev(&phy->subdev); + media_entity_cleanup(&phy->subdev.entity); + of_node_put(phy->source_ep_node); + of_node_put(phy->source_node); + mutex_destroy(&phy->mutex); + kfree(phy); +} diff --git a/drivers/media/platform/ti/cal/cal-video.c b/drivers/media/platform/ti/cal/cal-video.c new file mode 100644 index 000000000..bbfd27197 --- /dev/null +++ b/drivers/media/platform/ti/cal/cal-video.c @@ -0,0 +1,1046 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI Camera Access Layer (CAL) - Video Device + * + * Copyright (c) 2015-2020 Texas Instruments Inc. + * + * Authors: + * Benoit Parrot + * Laurent Pinchart + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "cal.h" + +/* Print Four-character-code (FOURCC) */ +static char *fourcc_to_str(u32 fmt) +{ + static char code[5]; + + code[0] = (unsigned char)(fmt & 0xff); + code[1] = (unsigned char)((fmt >> 8) & 0xff); + code[2] = (unsigned char)((fmt >> 16) & 0xff); + code[3] = (unsigned char)((fmt >> 24) & 0xff); + code[4] = '\0'; + + return code; +} + +/* ------------------------------------------------------------------ + * V4L2 Common IOCTLs + * ------------------------------------------------------------------ + */ + +static int cal_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + strscpy(cap->driver, CAL_MODULE_NAME, sizeof(cap->driver)); + strscpy(cap->card, CAL_MODULE_NAME, sizeof(cap->card)); + + return 0; +} + +static int cal_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cal_ctx *ctx = video_drvdata(file); + + *f = ctx->v_fmt; + + return 0; +} + +/* ------------------------------------------------------------------ + * V4L2 Video Node Centric IOCTLs + * ------------------------------------------------------------------ + */ + +static const struct cal_format_info *find_format_by_pix(struct cal_ctx *ctx, + u32 pixelformat) +{ + const struct cal_format_info *fmtinfo; + unsigned int k; + + for (k = 0; k < ctx->num_active_fmt; k++) { + fmtinfo = ctx->active_fmt[k]; + if (fmtinfo->fourcc == pixelformat) + return fmtinfo; + } + + return NULL; +} + +static const struct cal_format_info *find_format_by_code(struct cal_ctx *ctx, + u32 code) +{ + const struct cal_format_info *fmtinfo; + unsigned int k; + + for (k = 0; k < ctx->num_active_fmt; k++) { + fmtinfo = ctx->active_fmt[k]; + if (fmtinfo->code == code) + return fmtinfo; + } + + return NULL; +} + +static int cal_legacy_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct cal_ctx *ctx = video_drvdata(file); + const struct cal_format_info *fmtinfo; + + if (f->index >= ctx->num_active_fmt) + return -EINVAL; + + fmtinfo = ctx->active_fmt[f->index]; + + f->pixelformat = fmtinfo->fourcc; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + return 0; +} + +static int __subdev_get_format(struct cal_ctx *ctx, + struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_subdev_format sd_fmt; + struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; + int ret; + + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + sd_fmt.pad = 0; + + ret = v4l2_subdev_call(ctx->phy->source, pad, get_fmt, NULL, &sd_fmt); + if (ret) + return ret; + + *fmt = *mbus_fmt; + + ctx_dbg(1, ctx, "%s %dx%d code:%04X\n", __func__, + fmt->width, fmt->height, fmt->code); + + return 0; +} + +static int __subdev_set_format(struct cal_ctx *ctx, + struct v4l2_mbus_framefmt *fmt) +{ + struct v4l2_subdev_format sd_fmt; + struct v4l2_mbus_framefmt *mbus_fmt = &sd_fmt.format; + int ret; + + sd_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE; + sd_fmt.pad = 0; + *mbus_fmt = *fmt; + + ret = v4l2_subdev_call(ctx->phy->source, pad, set_fmt, NULL, &sd_fmt); + if (ret) + return ret; + + ctx_dbg(1, ctx, "%s %dx%d code:%04X\n", __func__, + fmt->width, fmt->height, fmt->code); + + return 0; +} + +static void cal_calc_format_size(struct cal_ctx *ctx, + const struct cal_format_info *fmtinfo, + struct v4l2_format *f) +{ + u32 bpl, max_width; + + /* + * Maximum width is bound by the DMA max width in bytes. + * We need to recalculate the actual maxi width depending on the + * number of bytes per pixels required. + */ + max_width = CAL_MAX_WIDTH_BYTES / (ALIGN(fmtinfo->bpp, 8) >> 3); + v4l_bound_align_image(&f->fmt.pix.width, 48, max_width, 2, + &f->fmt.pix.height, 32, CAL_MAX_HEIGHT_LINES, + 0, 0); + + bpl = (f->fmt.pix.width * ALIGN(fmtinfo->bpp, 8)) >> 3; + f->fmt.pix.bytesperline = ALIGN(bpl, 16); + + f->fmt.pix.sizeimage = f->fmt.pix.height * + f->fmt.pix.bytesperline; + + ctx_dbg(3, ctx, "%s: fourcc: %s size: %dx%d bpl:%d img_size:%d\n", + __func__, fourcc_to_str(f->fmt.pix.pixelformat), + f->fmt.pix.width, f->fmt.pix.height, + f->fmt.pix.bytesperline, f->fmt.pix.sizeimage); +} + +static int cal_legacy_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cal_ctx *ctx = video_drvdata(file); + const struct cal_format_info *fmtinfo; + struct v4l2_subdev_frame_size_enum fse; + int found; + + fmtinfo = find_format_by_pix(ctx, f->fmt.pix.pixelformat); + if (!fmtinfo) { + ctx_dbg(3, ctx, "Fourcc format (0x%08x) not found.\n", + f->fmt.pix.pixelformat); + + /* Just get the first one enumerated */ + fmtinfo = ctx->active_fmt[0]; + f->fmt.pix.pixelformat = fmtinfo->fourcc; + } + + f->fmt.pix.field = ctx->v_fmt.fmt.pix.field; + + /* check for/find a valid width/height */ + found = false; + fse.pad = 0; + fse.code = fmtinfo->code; + fse.which = V4L2_SUBDEV_FORMAT_ACTIVE; + for (fse.index = 0; ; fse.index++) { + int ret; + + ret = v4l2_subdev_call(ctx->phy->source, pad, enum_frame_size, + NULL, &fse); + if (ret) + break; + + if ((f->fmt.pix.width == fse.max_width) && + (f->fmt.pix.height == fse.max_height)) { + found = true; + break; + } else if ((f->fmt.pix.width >= fse.min_width) && + (f->fmt.pix.width <= fse.max_width) && + (f->fmt.pix.height >= fse.min_height) && + (f->fmt.pix.height <= fse.max_height)) { + found = true; + break; + } + } + + if (!found) { + /* use existing values as default */ + f->fmt.pix.width = ctx->v_fmt.fmt.pix.width; + f->fmt.pix.height = ctx->v_fmt.fmt.pix.height; + } + + /* + * Use current colorspace for now, it will get + * updated properly during s_fmt + */ + f->fmt.pix.colorspace = ctx->v_fmt.fmt.pix.colorspace; + cal_calc_format_size(ctx, fmtinfo, f); + return 0; +} + +static int cal_legacy_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cal_ctx *ctx = video_drvdata(file); + struct vb2_queue *q = &ctx->vb_vidq; + struct v4l2_subdev_format sd_fmt = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + .pad = CAL_CAMERARX_PAD_SINK, + }; + const struct cal_format_info *fmtinfo; + int ret; + + if (vb2_is_busy(q)) { + ctx_dbg(3, ctx, "%s device busy\n", __func__); + return -EBUSY; + } + + ret = cal_legacy_try_fmt_vid_cap(file, priv, f); + if (ret < 0) + return ret; + + fmtinfo = find_format_by_pix(ctx, f->fmt.pix.pixelformat); + + v4l2_fill_mbus_format(&sd_fmt.format, &f->fmt.pix, fmtinfo->code); + + ret = __subdev_set_format(ctx, &sd_fmt.format); + if (ret) + return ret; + + /* Just double check nothing has gone wrong */ + if (sd_fmt.format.code != fmtinfo->code) { + ctx_dbg(3, ctx, + "%s subdev changed format on us, this should not happen\n", + __func__); + return -EINVAL; + } + + v4l2_fill_pix_format(&ctx->v_fmt.fmt.pix, &sd_fmt.format); + ctx->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ctx->v_fmt.fmt.pix.pixelformat = fmtinfo->fourcc; + ctx->v_fmt.fmt.pix.field = sd_fmt.format.field; + cal_calc_format_size(ctx, fmtinfo, &ctx->v_fmt); + + v4l2_subdev_call(&ctx->phy->subdev, pad, set_fmt, NULL, &sd_fmt); + + ctx->fmtinfo = fmtinfo; + *f = ctx->v_fmt; + + return 0; +} + +static int cal_legacy_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct cal_ctx *ctx = video_drvdata(file); + const struct cal_format_info *fmtinfo; + struct v4l2_subdev_frame_size_enum fse; + int ret; + + /* check for valid format */ + fmtinfo = find_format_by_pix(ctx, fsize->pixel_format); + if (!fmtinfo) { + ctx_dbg(3, ctx, "Invalid pixel code: %x\n", + fsize->pixel_format); + return -EINVAL; + } + + fse.index = fsize->index; + fse.pad = 0; + fse.code = fmtinfo->code; + fse.which = V4L2_SUBDEV_FORMAT_ACTIVE; + + ret = v4l2_subdev_call(ctx->phy->source, pad, enum_frame_size, NULL, + &fse); + if (ret) + return ret; + + ctx_dbg(1, ctx, "%s: index: %d code: %x W:[%d,%d] H:[%d,%d]\n", + __func__, fse.index, fse.code, fse.min_width, fse.max_width, + fse.min_height, fse.max_height); + + fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE; + fsize->discrete.width = fse.max_width; + fsize->discrete.height = fse.max_height; + + return 0; +} + +static int cal_legacy_enum_input(struct file *file, void *priv, + struct v4l2_input *inp) +{ + if (inp->index > 0) + return -EINVAL; + + inp->type = V4L2_INPUT_TYPE_CAMERA; + sprintf(inp->name, "Camera %u", inp->index); + return 0; +} + +static int cal_legacy_g_input(struct file *file, void *priv, unsigned int *i) +{ + *i = 0; + return 0; +} + +static int cal_legacy_s_input(struct file *file, void *priv, unsigned int i) +{ + return i > 0 ? -EINVAL : 0; +} + +/* timeperframe is arbitrary and continuous */ +static int cal_legacy_enum_frameintervals(struct file *file, void *priv, + struct v4l2_frmivalenum *fival) +{ + struct cal_ctx *ctx = video_drvdata(file); + const struct cal_format_info *fmtinfo; + struct v4l2_subdev_frame_interval_enum fie = { + .index = fival->index, + .width = fival->width, + .height = fival->height, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + int ret; + + fmtinfo = find_format_by_pix(ctx, fival->pixel_format); + if (!fmtinfo) + return -EINVAL; + + fie.code = fmtinfo->code; + ret = v4l2_subdev_call(ctx->phy->source, pad, enum_frame_interval, + NULL, &fie); + if (ret) + return ret; + fival->type = V4L2_FRMIVAL_TYPE_DISCRETE; + fival->discrete = fie.interval; + + return 0; +} + +static int cal_legacy_g_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct cal_ctx *ctx = video_drvdata(file); + + return v4l2_g_parm_cap(video_devdata(file), ctx->phy->source, a); +} + +static int cal_legacy_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) +{ + struct cal_ctx *ctx = video_drvdata(file); + + return v4l2_s_parm_cap(video_devdata(file), ctx->phy->source, a); +} + +static const struct v4l2_ioctl_ops cal_ioctl_legacy_ops = { + .vidioc_querycap = cal_querycap, + .vidioc_enum_fmt_vid_cap = cal_legacy_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = cal_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = cal_legacy_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = cal_legacy_s_fmt_vid_cap, + .vidioc_enum_framesizes = cal_legacy_enum_framesizes, + .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_expbuf = vb2_ioctl_expbuf, + .vidioc_enum_input = cal_legacy_enum_input, + .vidioc_g_input = cal_legacy_g_input, + .vidioc_s_input = cal_legacy_s_input, + .vidioc_enum_frameintervals = cal_legacy_enum_frameintervals, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_log_status = v4l2_ctrl_log_status, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, + .vidioc_g_parm = cal_legacy_g_parm, + .vidioc_s_parm = cal_legacy_s_parm, +}; + +/* ------------------------------------------------------------------ + * V4L2 Media Controller Centric IOCTLs + * ------------------------------------------------------------------ + */ + +static int cal_mc_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + unsigned int i; + unsigned int idx; + + if (f->index >= cal_num_formats) + return -EINVAL; + + idx = 0; + + for (i = 0; i < cal_num_formats; ++i) { + if (f->mbus_code && cal_formats[i].code != f->mbus_code) + continue; + + if (idx == f->index) { + f->pixelformat = cal_formats[i].fourcc; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + return 0; + } + + idx++; + } + + return -EINVAL; +} + +static void cal_mc_try_fmt(struct cal_ctx *ctx, struct v4l2_format *f, + const struct cal_format_info **info) +{ + struct v4l2_pix_format *format = &f->fmt.pix; + const struct cal_format_info *fmtinfo; + unsigned int bpp; + + /* + * Default to the first format if the requested pixel format code isn't + * supported. + */ + fmtinfo = cal_format_by_fourcc(f->fmt.pix.pixelformat); + if (!fmtinfo) + fmtinfo = &cal_formats[0]; + + /* + * Clamp the size, update the pixel format. The field and colorspace are + * accepted as-is, except for V4L2_FIELD_ANY that is turned into + * V4L2_FIELD_NONE. + */ + bpp = ALIGN(fmtinfo->bpp, 8); + + format->width = clamp_t(unsigned int, format->width, + CAL_MIN_WIDTH_BYTES * 8 / bpp, + CAL_MAX_WIDTH_BYTES * 8 / bpp); + format->height = clamp_t(unsigned int, format->height, + CAL_MIN_HEIGHT_LINES, CAL_MAX_HEIGHT_LINES); + format->pixelformat = fmtinfo->fourcc; + + if (format->field == V4L2_FIELD_ANY) + format->field = V4L2_FIELD_NONE; + + /* + * Calculate the number of bytes per line and the image size. The + * hardware stores the stride as a number of 16 bytes words, in a + * signed 15-bit value. Only 14 bits are thus usable. + */ + format->bytesperline = ALIGN(clamp(format->bytesperline, + format->width * bpp / 8, + ((1U << 14) - 1) * 16), 16); + + format->sizeimage = format->height * format->bytesperline; + + format->colorspace = ctx->v_fmt.fmt.pix.colorspace; + + if (info) + *info = fmtinfo; + + ctx_dbg(3, ctx, "%s: %s %ux%u (bytesperline %u sizeimage %u)\n", + __func__, fourcc_to_str(format->pixelformat), + format->width, format->height, + format->bytesperline, format->sizeimage); +} + +static int cal_mc_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cal_ctx *ctx = video_drvdata(file); + + cal_mc_try_fmt(ctx, f, NULL); + return 0; +} + +static int cal_mc_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct cal_ctx *ctx = video_drvdata(file); + const struct cal_format_info *fmtinfo; + + if (vb2_is_busy(&ctx->vb_vidq)) { + ctx_dbg(3, ctx, "%s device busy\n", __func__); + return -EBUSY; + } + + cal_mc_try_fmt(ctx, f, &fmtinfo); + + ctx->v_fmt = *f; + ctx->fmtinfo = fmtinfo; + + return 0; +} + +static int cal_mc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct cal_ctx *ctx = video_drvdata(file); + const struct cal_format_info *fmtinfo; + unsigned int bpp; + + if (fsize->index > 0) + return -EINVAL; + + fmtinfo = cal_format_by_fourcc(fsize->pixel_format); + if (!fmtinfo) { + ctx_dbg(3, ctx, "Invalid pixel format 0x%08x\n", + fsize->pixel_format); + return -EINVAL; + } + + bpp = ALIGN(fmtinfo->bpp, 8); + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = CAL_MIN_WIDTH_BYTES * 8 / bpp; + fsize->stepwise.max_width = CAL_MAX_WIDTH_BYTES * 8 / bpp; + fsize->stepwise.step_width = 64 / bpp; + fsize->stepwise.min_height = CAL_MIN_HEIGHT_LINES; + fsize->stepwise.max_height = CAL_MAX_HEIGHT_LINES; + fsize->stepwise.step_height = 1; + + return 0; +} + +static const struct v4l2_ioctl_ops cal_ioctl_mc_ops = { + .vidioc_querycap = cal_querycap, + .vidioc_enum_fmt_vid_cap = cal_mc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = cal_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = cal_mc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = cal_mc_s_fmt_vid_cap, + .vidioc_enum_framesizes = cal_mc_enum_framesizes, + .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_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_log_status = v4l2_ctrl_log_status, +}; + +/* ------------------------------------------------------------------ + * videobuf2 Common Operations + * ------------------------------------------------------------------ + */ + +static int cal_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct cal_ctx *ctx = vb2_get_drv_priv(vq); + unsigned int size = ctx->v_fmt.fmt.pix.sizeimage; + + if (vq->num_buffers + *nbuffers < 3) + *nbuffers = 3 - vq->num_buffers; + + if (*nplanes) { + if (sizes[0] < size) + return -EINVAL; + size = sizes[0]; + } + + *nplanes = 1; + sizes[0] = size; + + ctx_dbg(3, ctx, "nbuffers=%d, size=%d\n", *nbuffers, sizes[0]); + + return 0; +} + +static int cal_buffer_prepare(struct vb2_buffer *vb) +{ + struct cal_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct cal_buffer *buf = container_of(vb, struct cal_buffer, + vb.vb2_buf); + unsigned long size; + + size = ctx->v_fmt.fmt.pix.sizeimage; + if (vb2_plane_size(vb, 0) < size) { + ctx_err(ctx, + "data will not fit into plane (%lu < %lu)\n", + vb2_plane_size(vb, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(&buf->vb.vb2_buf, 0, size); + return 0; +} + +static void cal_buffer_queue(struct vb2_buffer *vb) +{ + struct cal_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct cal_buffer *buf = container_of(vb, struct cal_buffer, + vb.vb2_buf); + unsigned long flags; + + /* recheck locking */ + spin_lock_irqsave(&ctx->dma.lock, flags); + list_add_tail(&buf->list, &ctx->dma.queue); + spin_unlock_irqrestore(&ctx->dma.lock, flags); +} + +static void cal_release_buffers(struct cal_ctx *ctx, + enum vb2_buffer_state state) +{ + struct cal_buffer *buf, *tmp; + + /* Release all queued buffers. */ + spin_lock_irq(&ctx->dma.lock); + + list_for_each_entry_safe(buf, tmp, &ctx->dma.queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + + if (ctx->dma.pending) { + vb2_buffer_done(&ctx->dma.pending->vb.vb2_buf, state); + ctx->dma.pending = NULL; + } + + if (ctx->dma.active) { + vb2_buffer_done(&ctx->dma.active->vb.vb2_buf, state); + ctx->dma.active = NULL; + } + + spin_unlock_irq(&ctx->dma.lock); +} + +/* ------------------------------------------------------------------ + * videobuf2 Operations + * ------------------------------------------------------------------ + */ + +static int cal_video_check_format(struct cal_ctx *ctx) +{ + const struct v4l2_mbus_framefmt *format; + struct media_pad *remote_pad; + + remote_pad = media_pad_remote_pad_first(&ctx->pad); + if (!remote_pad) + return -ENODEV; + + format = &ctx->phy->formats[remote_pad->index]; + + if (ctx->fmtinfo->code != format->code || + ctx->v_fmt.fmt.pix.height != format->height || + ctx->v_fmt.fmt.pix.width != format->width || + ctx->v_fmt.fmt.pix.field != format->field) + return -EPIPE; + + return 0; +} + +static int cal_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct cal_ctx *ctx = vb2_get_drv_priv(vq); + struct cal_buffer *buf; + dma_addr_t addr; + int ret; + + ret = video_device_pipeline_alloc_start(&ctx->vdev); + if (ret < 0) { + ctx_err(ctx, "Failed to start media pipeline: %d\n", ret); + goto error_release_buffers; + } + + /* + * Verify that the currently configured format matches the output of + * the connected CAMERARX. + */ + ret = cal_video_check_format(ctx); + if (ret < 0) { + ctx_dbg(3, ctx, + "Format mismatch between CAMERARX and video node\n"); + goto error_pipeline; + } + + ret = cal_ctx_prepare(ctx); + if (ret) { + ctx_err(ctx, "Failed to prepare context: %d\n", ret); + goto error_pipeline; + } + + spin_lock_irq(&ctx->dma.lock); + buf = list_first_entry(&ctx->dma.queue, struct cal_buffer, list); + ctx->dma.active = buf; + list_del(&buf->list); + spin_unlock_irq(&ctx->dma.lock); + + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + + ret = pm_runtime_resume_and_get(ctx->cal->dev); + if (ret < 0) + goto error_pipeline; + + cal_ctx_set_dma_addr(ctx, addr); + cal_ctx_start(ctx); + + ret = v4l2_subdev_call(&ctx->phy->subdev, video, s_stream, 1); + if (ret) + goto error_stop; + + if (cal_debug >= 4) + cal_quickdump_regs(ctx->cal); + + return 0; + +error_stop: + cal_ctx_stop(ctx); + pm_runtime_put_sync(ctx->cal->dev); + cal_ctx_unprepare(ctx); + +error_pipeline: + video_device_pipeline_stop(&ctx->vdev); +error_release_buffers: + cal_release_buffers(ctx, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void cal_stop_streaming(struct vb2_queue *vq) +{ + struct cal_ctx *ctx = vb2_get_drv_priv(vq); + + cal_ctx_stop(ctx); + + v4l2_subdev_call(&ctx->phy->subdev, video, s_stream, 0); + + pm_runtime_put_sync(ctx->cal->dev); + + cal_ctx_unprepare(ctx); + + cal_release_buffers(ctx, VB2_BUF_STATE_ERROR); + + video_device_pipeline_stop(&ctx->vdev); +} + +static const struct vb2_ops cal_video_qops = { + .queue_setup = cal_queue_setup, + .buf_prepare = cal_buffer_prepare, + .buf_queue = cal_buffer_queue, + .start_streaming = cal_start_streaming, + .stop_streaming = cal_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* ------------------------------------------------------------------ + * V4L2 Initialization and Registration + * ------------------------------------------------------------------ + */ + +static const struct v4l2_file_operations cal_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */ + .mmap = vb2_fop_mmap, +}; + +static int cal_ctx_v4l2_init_formats(struct cal_ctx *ctx) +{ + struct v4l2_mbus_framefmt mbus_fmt; + const struct cal_format_info *fmtinfo; + unsigned int i, j, k; + int ret = 0; + + /* Enumerate sub device formats and enable all matching local formats */ + ctx->active_fmt = devm_kcalloc(ctx->cal->dev, cal_num_formats, + sizeof(*ctx->active_fmt), GFP_KERNEL); + if (!ctx->active_fmt) + return -ENOMEM; + + ctx->num_active_fmt = 0; + + for (j = 0, i = 0; ; ++j) { + struct v4l2_subdev_mbus_code_enum mbus_code = { + .index = j, + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + + ret = v4l2_subdev_call(ctx->phy->source, pad, enum_mbus_code, + NULL, &mbus_code); + if (ret == -EINVAL) + break; + + if (ret) { + ctx_err(ctx, "Error enumerating mbus codes in subdev %s: %d\n", + ctx->phy->source->name, ret); + return ret; + } + + ctx_dbg(2, ctx, + "subdev %s: code: %04x idx: %u\n", + ctx->phy->source->name, mbus_code.code, j); + + for (k = 0; k < cal_num_formats; k++) { + fmtinfo = &cal_formats[k]; + + if (mbus_code.code == fmtinfo->code) { + ctx->active_fmt[i] = fmtinfo; + ctx_dbg(2, ctx, + "matched fourcc: %s: code: %04x idx: %u\n", + fourcc_to_str(fmtinfo->fourcc), + fmtinfo->code, i); + ctx->num_active_fmt = ++i; + } + } + } + + if (i == 0) { + ctx_err(ctx, "No suitable format reported by subdev %s\n", + ctx->phy->source->name); + return -EINVAL; + } + + ret = __subdev_get_format(ctx, &mbus_fmt); + if (ret) + return ret; + + fmtinfo = find_format_by_code(ctx, mbus_fmt.code); + if (!fmtinfo) { + ctx_dbg(3, ctx, "mbus code format (0x%08x) not found.\n", + mbus_fmt.code); + return -EINVAL; + } + + /* Save current format */ + v4l2_fill_pix_format(&ctx->v_fmt.fmt.pix, &mbus_fmt); + ctx->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + ctx->v_fmt.fmt.pix.pixelformat = fmtinfo->fourcc; + cal_calc_format_size(ctx, fmtinfo, &ctx->v_fmt); + ctx->fmtinfo = fmtinfo; + + return 0; +} + +static int cal_ctx_v4l2_init_mc_format(struct cal_ctx *ctx) +{ + const struct cal_format_info *fmtinfo; + struct v4l2_pix_format *pix_fmt = &ctx->v_fmt.fmt.pix; + + fmtinfo = cal_format_by_code(MEDIA_BUS_FMT_UYVY8_2X8); + if (!fmtinfo) + return -EINVAL; + + pix_fmt->width = 640; + pix_fmt->height = 480; + pix_fmt->field = V4L2_FIELD_NONE; + pix_fmt->colorspace = V4L2_COLORSPACE_SRGB; + pix_fmt->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix_fmt->quantization = V4L2_QUANTIZATION_LIM_RANGE; + pix_fmt->xfer_func = V4L2_XFER_FUNC_SRGB; + pix_fmt->pixelformat = fmtinfo->fourcc; + + ctx->v_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + /* Save current format */ + cal_calc_format_size(ctx, fmtinfo, &ctx->v_fmt); + ctx->fmtinfo = fmtinfo; + + return 0; +} + +int cal_ctx_v4l2_register(struct cal_ctx *ctx) +{ + struct video_device *vfd = &ctx->vdev; + int ret; + + if (!cal_mc_api) { + struct v4l2_ctrl_handler *hdl = &ctx->ctrl_handler; + + ret = cal_ctx_v4l2_init_formats(ctx); + if (ret) { + ctx_err(ctx, "Failed to init formats: %d\n", ret); + return ret; + } + + ret = v4l2_ctrl_add_handler(hdl, ctx->phy->source->ctrl_handler, + NULL, true); + if (ret < 0) { + ctx_err(ctx, "Failed to add source ctrl handler\n"); + return ret; + } + } else { + ret = cal_ctx_v4l2_init_mc_format(ctx); + if (ret) { + ctx_err(ctx, "Failed to init format: %d\n", ret); + return ret; + } + } + + ret = video_register_device(vfd, VFL_TYPE_VIDEO, cal_video_nr); + if (ret < 0) { + ctx_err(ctx, "Failed to register video device\n"); + return ret; + } + + ret = media_create_pad_link(&ctx->phy->subdev.entity, + CAL_CAMERARX_PAD_FIRST_SOURCE, + &vfd->entity, 0, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) { + ctx_err(ctx, "Failed to create media link for context %u\n", + ctx->dma_ctx); + video_unregister_device(vfd); + return ret; + } + + ctx_info(ctx, "V4L2 device registered as %s\n", + video_device_node_name(vfd)); + + return 0; +} + +void cal_ctx_v4l2_unregister(struct cal_ctx *ctx) +{ + ctx_dbg(1, ctx, "unregistering %s\n", + video_device_node_name(&ctx->vdev)); + + video_unregister_device(&ctx->vdev); +} + +int cal_ctx_v4l2_init(struct cal_ctx *ctx) +{ + struct video_device *vfd = &ctx->vdev; + struct vb2_queue *q = &ctx->vb_vidq; + int ret; + + INIT_LIST_HEAD(&ctx->dma.queue); + spin_lock_init(&ctx->dma.lock); + mutex_init(&ctx->mutex); + init_waitqueue_head(&ctx->dma.wait); + + /* Initialize the vb2 queue. */ + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->drv_priv = ctx; + q->buf_struct_size = sizeof(struct cal_buffer); + q->ops = &cal_video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->lock = &ctx->mutex; + q->min_buffers_needed = 3; + q->dev = ctx->cal->dev; + + ret = vb2_queue_init(q); + if (ret) + return ret; + + /* Initialize the video device and media entity. */ + vfd->fops = &cal_fops; + vfd->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING + | (cal_mc_api ? V4L2_CAP_IO_MC : 0); + vfd->v4l2_dev = &ctx->cal->v4l2_dev; + vfd->queue = q; + snprintf(vfd->name, sizeof(vfd->name), "CAL output %u", ctx->dma_ctx); + vfd->release = video_device_release_empty; + vfd->ioctl_ops = cal_mc_api ? &cal_ioctl_mc_ops : &cal_ioctl_legacy_ops; + vfd->lock = &ctx->mutex; + video_set_drvdata(vfd, ctx); + + ctx->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vfd->entity, 1, &ctx->pad); + if (ret < 0) + return ret; + + if (!cal_mc_api) { + /* Initialize the control handler. */ + struct v4l2_ctrl_handler *hdl = &ctx->ctrl_handler; + + ret = v4l2_ctrl_handler_init(hdl, 11); + if (ret < 0) { + ctx_err(ctx, "Failed to init ctrl handler\n"); + goto error; + } + + vfd->ctrl_handler = hdl; + } + + return 0; + +error: + media_entity_cleanup(&vfd->entity); + return ret; +} + +void cal_ctx_v4l2_cleanup(struct cal_ctx *ctx) +{ + if (!cal_mc_api) + v4l2_ctrl_handler_free(&ctx->ctrl_handler); + + media_entity_cleanup(&ctx->vdev.entity); +} diff --git a/drivers/media/platform/ti/cal/cal.c b/drivers/media/platform/ti/cal/cal.c new file mode 100644 index 000000000..1236215ec --- /dev/null +++ b/drivers/media/platform/ti/cal/cal.c @@ -0,0 +1,1375 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI Camera Access Layer (CAL) - Driver + * + * Copyright (c) 2015-2020 Texas Instruments Inc. + * + * Authors: + * Benoit Parrot + * Laurent Pinchart + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "cal.h" +#include "cal_regs.h" + +MODULE_DESCRIPTION("TI CAL driver"); +MODULE_AUTHOR("Benoit Parrot, "); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION("0.1.0"); + +int cal_video_nr = -1; +module_param_named(video_nr, cal_video_nr, uint, 0644); +MODULE_PARM_DESC(video_nr, "videoX start number, -1 is autodetect"); + +unsigned int cal_debug; +module_param_named(debug, cal_debug, uint, 0644); +MODULE_PARM_DESC(debug, "activates debug info"); + +#ifdef CONFIG_VIDEO_TI_CAL_MC +#define CAL_MC_API_DEFAULT 1 +#else +#define CAL_MC_API_DEFAULT 0 +#endif + +bool cal_mc_api = CAL_MC_API_DEFAULT; +module_param_named(mc_api, cal_mc_api, bool, 0444); +MODULE_PARM_DESC(mc_api, "activates the MC API"); + +/* ------------------------------------------------------------------ + * Format Handling + * ------------------------------------------------------------------ + */ + +const struct cal_format_info cal_formats[] = { + { + .fourcc = V4L2_PIX_FMT_YUYV, + .code = MEDIA_BUS_FMT_YUYV8_2X8, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_UYVY, + .code = MEDIA_BUS_FMT_UYVY8_2X8, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_YVYU, + .code = MEDIA_BUS_FMT_YVYU8_2X8, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_VYUY, + .code = MEDIA_BUS_FMT_VYUY8_2X8, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB565, /* gggbbbbb rrrrrggg */ + .code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB565X, /* rrrrrggg gggbbbbb */ + .code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB555, /* gggbbbbb arrrrrgg */ + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB555X, /* arrrrrgg gggbbbbb */ + .code = MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE, + .bpp = 16, + }, { + .fourcc = V4L2_PIX_FMT_RGB24, /* rgb */ + .code = MEDIA_BUS_FMT_RGB888_2X12_LE, + .bpp = 24, + }, { + .fourcc = V4L2_PIX_FMT_BGR24, /* bgr */ + .code = MEDIA_BUS_FMT_RGB888_2X12_BE, + .bpp = 24, + }, { + .fourcc = V4L2_PIX_FMT_RGB32, /* argb */ + .code = MEDIA_BUS_FMT_ARGB8888_1X32, + .bpp = 32, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR8, + .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG8, + .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG8, + .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB8, + .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .bpp = 8, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR10, + .code = MEDIA_BUS_FMT_SBGGR10_1X10, + .bpp = 10, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG10, + .code = MEDIA_BUS_FMT_SGBRG10_1X10, + .bpp = 10, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG10, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .bpp = 10, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB10, + .code = MEDIA_BUS_FMT_SRGGB10_1X10, + .bpp = 10, + }, { + .fourcc = V4L2_PIX_FMT_SBGGR12, + .code = MEDIA_BUS_FMT_SBGGR12_1X12, + .bpp = 12, + }, { + .fourcc = V4L2_PIX_FMT_SGBRG12, + .code = MEDIA_BUS_FMT_SGBRG12_1X12, + .bpp = 12, + }, { + .fourcc = V4L2_PIX_FMT_SGRBG12, + .code = MEDIA_BUS_FMT_SGRBG12_1X12, + .bpp = 12, + }, { + .fourcc = V4L2_PIX_FMT_SRGGB12, + .code = MEDIA_BUS_FMT_SRGGB12_1X12, + .bpp = 12, + }, +}; + +const unsigned int cal_num_formats = ARRAY_SIZE(cal_formats); + +const struct cal_format_info *cal_format_by_fourcc(u32 fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(cal_formats); ++i) { + if (cal_formats[i].fourcc == fourcc) + return &cal_formats[i]; + } + + return NULL; +} + +const struct cal_format_info *cal_format_by_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(cal_formats); ++i) { + if (cal_formats[i].code == code) + return &cal_formats[i]; + } + + return NULL; +} + +/* ------------------------------------------------------------------ + * Platform Data + * ------------------------------------------------------------------ + */ + +static const struct cal_camerarx_data dra72x_cal_camerarx[] = { + { + .fields = { + [F_CTRLCLKEN] = { 10, 10 }, + [F_CAMMODE] = { 11, 12 }, + [F_LANEENABLE] = { 13, 16 }, + [F_CSI_MODE] = { 17, 17 }, + }, + .num_lanes = 4, + }, + { + .fields = { + [F_CTRLCLKEN] = { 0, 0 }, + [F_CAMMODE] = { 1, 2 }, + [F_LANEENABLE] = { 3, 4 }, + [F_CSI_MODE] = { 5, 5 }, + }, + .num_lanes = 2, + }, +}; + +static const struct cal_data dra72x_cal_data = { + .camerarx = dra72x_cal_camerarx, + .num_csi2_phy = ARRAY_SIZE(dra72x_cal_camerarx), +}; + +static const struct cal_data dra72x_es1_cal_data = { + .camerarx = dra72x_cal_camerarx, + .num_csi2_phy = ARRAY_SIZE(dra72x_cal_camerarx), + .flags = DRA72_CAL_PRE_ES2_LDO_DISABLE, +}; + +static const struct cal_camerarx_data dra76x_cal_csi_phy[] = { + { + .fields = { + [F_CTRLCLKEN] = { 8, 8 }, + [F_CAMMODE] = { 9, 10 }, + [F_CSI_MODE] = { 11, 11 }, + [F_LANEENABLE] = { 27, 31 }, + }, + .num_lanes = 5, + }, + { + .fields = { + [F_CTRLCLKEN] = { 0, 0 }, + [F_CAMMODE] = { 1, 2 }, + [F_CSI_MODE] = { 3, 3 }, + [F_LANEENABLE] = { 24, 26 }, + }, + .num_lanes = 3, + }, +}; + +static const struct cal_data dra76x_cal_data = { + .camerarx = dra76x_cal_csi_phy, + .num_csi2_phy = ARRAY_SIZE(dra76x_cal_csi_phy), +}; + +static const struct cal_camerarx_data am654_cal_csi_phy[] = { + { + .fields = { + [F_CTRLCLKEN] = { 15, 15 }, + [F_CAMMODE] = { 24, 25 }, + [F_LANEENABLE] = { 0, 4 }, + }, + .num_lanes = 5, + }, +}; + +static const struct cal_data am654_cal_data = { + .camerarx = am654_cal_csi_phy, + .num_csi2_phy = ARRAY_SIZE(am654_cal_csi_phy), +}; + +/* ------------------------------------------------------------------ + * I/O Register Accessors + * ------------------------------------------------------------------ + */ + +void cal_quickdump_regs(struct cal_dev *cal) +{ + unsigned int i; + + cal_info(cal, "CAL Registers @ 0x%pa:\n", &cal->res->start); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4, + (__force const void *)cal->base, + resource_size(cal->res), false); + + for (i = 0; i < cal->data->num_csi2_phy; ++i) { + struct cal_camerarx *phy = cal->phy[i]; + + cal_info(cal, "CSI2 Core %u Registers @ %pa:\n", i, + &phy->res->start); + print_hex_dump(KERN_INFO, "", DUMP_PREFIX_OFFSET, 16, 4, + (__force const void *)phy->base, + resource_size(phy->res), + false); + } +} + +/* ------------------------------------------------------------------ + * Context Management + * ------------------------------------------------------------------ + */ + +#define CAL_MAX_PIX_PROC 4 + +static int cal_reserve_pix_proc(struct cal_dev *cal) +{ + unsigned long ret; + + spin_lock(&cal->v4l2_dev.lock); + + ret = find_first_zero_bit(&cal->reserved_pix_proc_mask, CAL_MAX_PIX_PROC); + + if (ret == CAL_MAX_PIX_PROC) { + spin_unlock(&cal->v4l2_dev.lock); + return -ENOSPC; + } + + cal->reserved_pix_proc_mask |= BIT(ret); + + spin_unlock(&cal->v4l2_dev.lock); + + return ret; +} + +static void cal_release_pix_proc(struct cal_dev *cal, unsigned int pix_proc_num) +{ + spin_lock(&cal->v4l2_dev.lock); + + cal->reserved_pix_proc_mask &= ~BIT(pix_proc_num); + + spin_unlock(&cal->v4l2_dev.lock); +} + +static void cal_ctx_csi2_config(struct cal_ctx *ctx) +{ + u32 val; + + val = cal_read(ctx->cal, CAL_CSI2_CTX(ctx->phy->instance, ctx->csi2_ctx)); + cal_set_field(&val, ctx->cport, CAL_CSI2_CTX_CPORT_MASK); + /* + * DT type: MIPI CSI-2 Specs + * 0x1: All - DT filter is disabled + * 0x24: RGB888 1 pixel = 3 bytes + * 0x2B: RAW10 4 pixels = 5 bytes + * 0x2A: RAW8 1 pixel = 1 byte + * 0x1E: YUV422 2 pixels = 4 bytes + */ + cal_set_field(&val, ctx->datatype, CAL_CSI2_CTX_DT_MASK); + cal_set_field(&val, ctx->vc, CAL_CSI2_CTX_VC_MASK); + cal_set_field(&val, ctx->v_fmt.fmt.pix.height, CAL_CSI2_CTX_LINES_MASK); + cal_set_field(&val, CAL_CSI2_CTX_ATT_PIX, CAL_CSI2_CTX_ATT_MASK); + cal_set_field(&val, CAL_CSI2_CTX_PACK_MODE_LINE, + CAL_CSI2_CTX_PACK_MODE_MASK); + cal_write(ctx->cal, CAL_CSI2_CTX(ctx->phy->instance, ctx->csi2_ctx), val); + ctx_dbg(3, ctx, "CAL_CSI2_CTX(%u, %u) = 0x%08x\n", + ctx->phy->instance, ctx->csi2_ctx, + cal_read(ctx->cal, CAL_CSI2_CTX(ctx->phy->instance, ctx->csi2_ctx))); +} + +static void cal_ctx_pix_proc_config(struct cal_ctx *ctx) +{ + u32 val, extract, pack; + + switch (ctx->fmtinfo->bpp) { + case 8: + extract = CAL_PIX_PROC_EXTRACT_B8; + pack = CAL_PIX_PROC_PACK_B8; + break; + case 10: + extract = CAL_PIX_PROC_EXTRACT_B10_MIPI; + pack = CAL_PIX_PROC_PACK_B16; + break; + case 12: + extract = CAL_PIX_PROC_EXTRACT_B12_MIPI; + pack = CAL_PIX_PROC_PACK_B16; + break; + case 16: + extract = CAL_PIX_PROC_EXTRACT_B16_LE; + pack = CAL_PIX_PROC_PACK_B16; + break; + default: + /* + * If you see this warning then it means that you added + * some new entry in the cal_formats[] array with a different + * bit per pixel values then the one supported below. + * Either add support for the new bpp value below or adjust + * the new entry to use one of the value below. + * + * Instead of failing here just use 8 bpp as a default. + */ + dev_warn_once(ctx->cal->dev, + "%s:%d:%s: bpp:%d unsupported! Overwritten with 8.\n", + __FILE__, __LINE__, __func__, ctx->fmtinfo->bpp); + extract = CAL_PIX_PROC_EXTRACT_B8; + pack = CAL_PIX_PROC_PACK_B8; + break; + } + + val = cal_read(ctx->cal, CAL_PIX_PROC(ctx->pix_proc)); + cal_set_field(&val, extract, CAL_PIX_PROC_EXTRACT_MASK); + cal_set_field(&val, CAL_PIX_PROC_DPCMD_BYPASS, CAL_PIX_PROC_DPCMD_MASK); + cal_set_field(&val, CAL_PIX_PROC_DPCME_BYPASS, CAL_PIX_PROC_DPCME_MASK); + cal_set_field(&val, pack, CAL_PIX_PROC_PACK_MASK); + cal_set_field(&val, ctx->cport, CAL_PIX_PROC_CPORT_MASK); + cal_set_field(&val, 1, CAL_PIX_PROC_EN_MASK); + cal_write(ctx->cal, CAL_PIX_PROC(ctx->pix_proc), val); + ctx_dbg(3, ctx, "CAL_PIX_PROC(%u) = 0x%08x\n", ctx->pix_proc, + cal_read(ctx->cal, CAL_PIX_PROC(ctx->pix_proc))); +} + +static void cal_ctx_wr_dma_config(struct cal_ctx *ctx) +{ + unsigned int stride = ctx->v_fmt.fmt.pix.bytesperline; + u32 val; + + val = cal_read(ctx->cal, CAL_WR_DMA_CTRL(ctx->dma_ctx)); + cal_set_field(&val, ctx->cport, CAL_WR_DMA_CTRL_CPORT_MASK); + cal_set_field(&val, ctx->v_fmt.fmt.pix.height, + CAL_WR_DMA_CTRL_YSIZE_MASK); + cal_set_field(&val, CAL_WR_DMA_CTRL_DTAG_PIX_DAT, + CAL_WR_DMA_CTRL_DTAG_MASK); + cal_set_field(&val, CAL_WR_DMA_CTRL_PATTERN_LINEAR, + CAL_WR_DMA_CTRL_PATTERN_MASK); + cal_set_field(&val, 1, CAL_WR_DMA_CTRL_STALL_RD_MASK); + cal_write(ctx->cal, CAL_WR_DMA_CTRL(ctx->dma_ctx), val); + ctx_dbg(3, ctx, "CAL_WR_DMA_CTRL(%d) = 0x%08x\n", ctx->dma_ctx, + cal_read(ctx->cal, CAL_WR_DMA_CTRL(ctx->dma_ctx))); + + cal_write_field(ctx->cal, CAL_WR_DMA_OFST(ctx->dma_ctx), + stride / 16, CAL_WR_DMA_OFST_MASK); + ctx_dbg(3, ctx, "CAL_WR_DMA_OFST(%d) = 0x%08x\n", ctx->dma_ctx, + cal_read(ctx->cal, CAL_WR_DMA_OFST(ctx->dma_ctx))); + + val = cal_read(ctx->cal, CAL_WR_DMA_XSIZE(ctx->dma_ctx)); + /* 64 bit word means no skipping */ + cal_set_field(&val, 0, CAL_WR_DMA_XSIZE_XSKIP_MASK); + /* + * The XSIZE field is expressed in 64-bit units and prevents overflows + * in case of synchronization issues by limiting the number of bytes + * written per line. + */ + cal_set_field(&val, stride / 8, CAL_WR_DMA_XSIZE_MASK); + cal_write(ctx->cal, CAL_WR_DMA_XSIZE(ctx->dma_ctx), val); + ctx_dbg(3, ctx, "CAL_WR_DMA_XSIZE(%d) = 0x%08x\n", ctx->dma_ctx, + cal_read(ctx->cal, CAL_WR_DMA_XSIZE(ctx->dma_ctx))); +} + +void cal_ctx_set_dma_addr(struct cal_ctx *ctx, dma_addr_t addr) +{ + cal_write(ctx->cal, CAL_WR_DMA_ADDR(ctx->dma_ctx), addr); +} + +static void cal_ctx_wr_dma_enable(struct cal_ctx *ctx) +{ + u32 val = cal_read(ctx->cal, CAL_WR_DMA_CTRL(ctx->dma_ctx)); + + cal_set_field(&val, CAL_WR_DMA_CTRL_MODE_CONST, + CAL_WR_DMA_CTRL_MODE_MASK); + cal_write(ctx->cal, CAL_WR_DMA_CTRL(ctx->dma_ctx), val); +} + +static void cal_ctx_wr_dma_disable(struct cal_ctx *ctx) +{ + u32 val = cal_read(ctx->cal, CAL_WR_DMA_CTRL(ctx->dma_ctx)); + + cal_set_field(&val, CAL_WR_DMA_CTRL_MODE_DIS, + CAL_WR_DMA_CTRL_MODE_MASK); + cal_write(ctx->cal, CAL_WR_DMA_CTRL(ctx->dma_ctx), val); +} + +static bool cal_ctx_wr_dma_stopped(struct cal_ctx *ctx) +{ + bool stopped; + + spin_lock_irq(&ctx->dma.lock); + stopped = ctx->dma.state == CAL_DMA_STOPPED; + spin_unlock_irq(&ctx->dma.lock); + + return stopped; +} + +static int +cal_get_remote_frame_desc_entry(struct cal_camerarx *phy, + struct v4l2_mbus_frame_desc_entry *entry) +{ + struct v4l2_mbus_frame_desc fd; + int ret; + + ret = cal_camerarx_get_remote_frame_desc(phy, &fd); + if (ret) { + if (ret != -ENOIOCTLCMD) + dev_err(phy->cal->dev, + "Failed to get remote frame desc: %d\n", ret); + return ret; + } + + if (fd.num_entries == 0) { + dev_err(phy->cal->dev, + "No streams found in the remote frame descriptor\n"); + + return -ENODEV; + } + + if (fd.num_entries > 1) + dev_dbg(phy->cal->dev, + "Multiple streams not supported in remote frame descriptor, using the first one\n"); + + *entry = fd.entry[0]; + + return 0; +} + +int cal_ctx_prepare(struct cal_ctx *ctx) +{ + struct v4l2_mbus_frame_desc_entry entry; + int ret; + + ret = cal_get_remote_frame_desc_entry(ctx->phy, &entry); + + if (ret == -ENOIOCTLCMD) { + ctx->vc = 0; + ctx->datatype = CAL_CSI2_CTX_DT_ANY; + } else if (!ret) { + ctx_dbg(2, ctx, "Framedesc: len %u, vc %u, dt %#x\n", + entry.length, entry.bus.csi2.vc, entry.bus.csi2.dt); + + ctx->vc = entry.bus.csi2.vc; + ctx->datatype = entry.bus.csi2.dt; + } else { + return ret; + } + + ctx->use_pix_proc = !ctx->fmtinfo->meta; + + if (ctx->use_pix_proc) { + ret = cal_reserve_pix_proc(ctx->cal); + if (ret < 0) { + ctx_err(ctx, "Failed to reserve pix proc: %d\n", ret); + return ret; + } + + ctx->pix_proc = ret; + } + + return 0; +} + +void cal_ctx_unprepare(struct cal_ctx *ctx) +{ + if (ctx->use_pix_proc) + cal_release_pix_proc(ctx->cal, ctx->pix_proc); +} + +void cal_ctx_start(struct cal_ctx *ctx) +{ + struct cal_camerarx *phy = ctx->phy; + + /* + * Reset the frame number & sequence number, but only if the + * virtual channel is not already in use. + */ + + spin_lock(&phy->vc_lock); + + if (phy->vc_enable_count[ctx->vc]++ == 0) { + phy->vc_frame_number[ctx->vc] = 0; + phy->vc_sequence[ctx->vc] = 0; + } + + spin_unlock(&phy->vc_lock); + + ctx->dma.state = CAL_DMA_RUNNING; + + /* Configure the CSI-2, pixel processing and write DMA contexts. */ + cal_ctx_csi2_config(ctx); + if (ctx->use_pix_proc) + cal_ctx_pix_proc_config(ctx); + cal_ctx_wr_dma_config(ctx); + + /* Enable IRQ_WDMA_END and IRQ_WDMA_START. */ + cal_write(ctx->cal, CAL_HL_IRQENABLE_SET(1), + CAL_HL_IRQ_WDMA_END_MASK(ctx->dma_ctx)); + cal_write(ctx->cal, CAL_HL_IRQENABLE_SET(2), + CAL_HL_IRQ_WDMA_START_MASK(ctx->dma_ctx)); + + cal_ctx_wr_dma_enable(ctx); +} + +void cal_ctx_stop(struct cal_ctx *ctx) +{ + struct cal_camerarx *phy = ctx->phy; + long timeout; + + WARN_ON(phy->vc_enable_count[ctx->vc] == 0); + + spin_lock(&phy->vc_lock); + phy->vc_enable_count[ctx->vc]--; + spin_unlock(&phy->vc_lock); + + /* + * Request DMA stop and wait until it completes. If completion times + * out, forcefully disable the DMA. + */ + spin_lock_irq(&ctx->dma.lock); + ctx->dma.state = CAL_DMA_STOP_REQUESTED; + spin_unlock_irq(&ctx->dma.lock); + + timeout = wait_event_timeout(ctx->dma.wait, cal_ctx_wr_dma_stopped(ctx), + msecs_to_jiffies(500)); + if (!timeout) { + ctx_err(ctx, "failed to disable dma cleanly\n"); + cal_ctx_wr_dma_disable(ctx); + } + + /* Disable IRQ_WDMA_END and IRQ_WDMA_START. */ + cal_write(ctx->cal, CAL_HL_IRQENABLE_CLR(1), + CAL_HL_IRQ_WDMA_END_MASK(ctx->dma_ctx)); + cal_write(ctx->cal, CAL_HL_IRQENABLE_CLR(2), + CAL_HL_IRQ_WDMA_START_MASK(ctx->dma_ctx)); + + ctx->dma.state = CAL_DMA_STOPPED; + + /* Disable CSI2 context */ + cal_write(ctx->cal, CAL_CSI2_CTX(ctx->phy->instance, ctx->csi2_ctx), 0); + + /* Disable pix proc */ + if (ctx->use_pix_proc) + cal_write(ctx->cal, CAL_PIX_PROC(ctx->pix_proc), 0); +} + +/* ------------------------------------------------------------------ + * IRQ Handling + * ------------------------------------------------------------------ + */ + +/* + * Track a sequence number for each virtual channel, which is shared by + * all contexts using the same virtual channel. This is done using the + * CSI-2 frame number as a base. + */ +static void cal_update_seq_number(struct cal_ctx *ctx) +{ + struct cal_dev *cal = ctx->cal; + struct cal_camerarx *phy = ctx->phy; + u16 prev_frame_num, frame_num; + u8 vc = ctx->vc; + + frame_num = + cal_read(cal, CAL_CSI2_STATUS(phy->instance, ctx->csi2_ctx)) & + 0xffff; + + if (phy->vc_frame_number[vc] != frame_num) { + prev_frame_num = phy->vc_frame_number[vc]; + + if (prev_frame_num >= frame_num) + phy->vc_sequence[vc] += 1; + else + phy->vc_sequence[vc] += frame_num - prev_frame_num; + + phy->vc_frame_number[vc] = frame_num; + } +} + +static inline void cal_irq_wdma_start(struct cal_ctx *ctx) +{ + spin_lock(&ctx->dma.lock); + + if (ctx->dma.state == CAL_DMA_STOP_REQUESTED) { + /* + * If a stop is requested, disable the write DMA context + * immediately. The CAL_WR_DMA_CTRL_j.MODE field is shadowed, + * the current frame will complete and the DMA will then stop. + */ + cal_ctx_wr_dma_disable(ctx); + ctx->dma.state = CAL_DMA_STOP_PENDING; + } else if (!list_empty(&ctx->dma.queue) && !ctx->dma.pending) { + /* + * Otherwise, if a new buffer is available, queue it to the + * hardware. + */ + struct cal_buffer *buf; + dma_addr_t addr; + + buf = list_first_entry(&ctx->dma.queue, struct cal_buffer, + list); + addr = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + cal_ctx_set_dma_addr(ctx, addr); + + ctx->dma.pending = buf; + list_del(&buf->list); + } + + spin_unlock(&ctx->dma.lock); + + cal_update_seq_number(ctx); +} + +static inline void cal_irq_wdma_end(struct cal_ctx *ctx) +{ + struct cal_buffer *buf = NULL; + + spin_lock(&ctx->dma.lock); + + /* If the DMA context was stopping, it is now stopped. */ + if (ctx->dma.state == CAL_DMA_STOP_PENDING) { + ctx->dma.state = CAL_DMA_STOPPED; + wake_up(&ctx->dma.wait); + } + + /* If a new buffer was queued, complete the current buffer. */ + if (ctx->dma.pending) { + buf = ctx->dma.active; + ctx->dma.active = ctx->dma.pending; + ctx->dma.pending = NULL; + } + + spin_unlock(&ctx->dma.lock); + + if (buf) { + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + buf->vb.field = ctx->v_fmt.fmt.pix.field; + buf->vb.sequence = ctx->phy->vc_sequence[ctx->vc]; + + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + } +} + +static void cal_irq_handle_wdma(struct cal_ctx *ctx, bool start, bool end) +{ + /* + * CAL HW interrupts are inherently racy. If we get both start and end + * interrupts, we don't know what has happened: did the DMA for a single + * frame start and end, or did one frame end and a new frame start? + * + * Usually for normal pixel frames we get the interrupts separately. If + * we do get both, we have to guess. The assumption in the code below is + * that the active vertical area is larger than the blanking vertical + * area, and thus it is more likely that we get the end of the old frame + * and the start of a new frame. + * + * However, for embedded data, which is only a few lines high, we always + * get both interrupts. Here the assumption is that we get both for the + * same frame. + */ + if (ctx->v_fmt.fmt.pix.height < 10) { + if (start) + cal_irq_wdma_start(ctx); + + if (end) + cal_irq_wdma_end(ctx); + } else { + if (end) + cal_irq_wdma_end(ctx); + + if (start) + cal_irq_wdma_start(ctx); + } +} + +static irqreturn_t cal_irq(int irq_cal, void *data) +{ + struct cal_dev *cal = data; + u32 status[3]; + unsigned int i; + + for (i = 0; i < 3; ++i) { + status[i] = cal_read(cal, CAL_HL_IRQSTATUS(i)); + if (status[i]) + cal_write(cal, CAL_HL_IRQSTATUS(i), status[i]); + } + + if (status[0]) { + if (status[0] & CAL_HL_IRQ_OCPO_ERR_MASK) + dev_err_ratelimited(cal->dev, "OCPO ERROR\n"); + + for (i = 0; i < cal->data->num_csi2_phy; ++i) { + if (status[0] & CAL_HL_IRQ_CIO_MASK(i)) { + u32 cio_stat = cal_read(cal, + CAL_CSI2_COMPLEXIO_IRQSTATUS(i)); + + dev_err_ratelimited(cal->dev, + "CIO%u error: %#08x\n", i, cio_stat); + + cal_write(cal, CAL_CSI2_COMPLEXIO_IRQSTATUS(i), + cio_stat); + } + + if (status[0] & CAL_HL_IRQ_VC_MASK(i)) { + u32 vc_stat = cal_read(cal, CAL_CSI2_VC_IRQSTATUS(i)); + + dev_err_ratelimited(cal->dev, + "CIO%u VC error: %#08x\n", + i, vc_stat); + + cal_write(cal, CAL_CSI2_VC_IRQSTATUS(i), vc_stat); + } + } + } + + for (i = 0; i < cal->num_contexts; ++i) { + bool end = !!(status[1] & CAL_HL_IRQ_WDMA_END_MASK(i)); + bool start = !!(status[2] & CAL_HL_IRQ_WDMA_START_MASK(i)); + + if (start || end) + cal_irq_handle_wdma(cal->ctx[i], start, end); + } + + return IRQ_HANDLED; +} + +/* ------------------------------------------------------------------ + * Asynchronous V4L2 subdev binding + * ------------------------------------------------------------------ + */ + +struct cal_v4l2_async_subdev { + struct v4l2_async_subdev asd; /* Must be first */ + struct cal_camerarx *phy; +}; + +static inline struct cal_v4l2_async_subdev * +to_cal_asd(struct v4l2_async_subdev *asd) +{ + return container_of(asd, struct cal_v4l2_async_subdev, asd); +} + +static int cal_async_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + struct cal_camerarx *phy = to_cal_asd(asd)->phy; + int pad; + int ret; + + if (phy->source) { + phy_info(phy, "Rejecting subdev %s (Already set!!)", + subdev->name); + return 0; + } + + phy->source = subdev; + phy_dbg(1, phy, "Using source %s for capture\n", subdev->name); + + pad = media_entity_get_fwnode_pad(&subdev->entity, + of_fwnode_handle(phy->source_ep_node), + MEDIA_PAD_FL_SOURCE); + if (pad < 0) { + phy_err(phy, "Source %s has no connected source pad\n", + subdev->name); + return pad; + } + + ret = media_create_pad_link(&subdev->entity, pad, + &phy->subdev.entity, CAL_CAMERARX_PAD_SINK, + MEDIA_LNK_FL_IMMUTABLE | + MEDIA_LNK_FL_ENABLED); + if (ret) { + phy_err(phy, "Failed to create media link for source %s\n", + subdev->name); + return ret; + } + + return 0; +} + +static int cal_async_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct cal_dev *cal = container_of(notifier, struct cal_dev, notifier); + unsigned int i; + int ret; + + for (i = 0; i < cal->num_contexts; ++i) { + ret = cal_ctx_v4l2_register(cal->ctx[i]); + if (ret) + goto err_ctx_unreg; + } + + if (!cal_mc_api) + return 0; + + ret = v4l2_device_register_subdev_nodes(&cal->v4l2_dev); + if (ret) + goto err_ctx_unreg; + + return 0; + +err_ctx_unreg: + for (; i > 0; --i) { + if (!cal->ctx[i - 1]) + continue; + + cal_ctx_v4l2_unregister(cal->ctx[i - 1]); + } + + return ret; +} + +static const struct v4l2_async_notifier_operations cal_async_notifier_ops = { + .bound = cal_async_notifier_bound, + .complete = cal_async_notifier_complete, +}; + +static int cal_async_notifier_register(struct cal_dev *cal) +{ + unsigned int i; + int ret; + + v4l2_async_nf_init(&cal->notifier); + cal->notifier.ops = &cal_async_notifier_ops; + + for (i = 0; i < cal->data->num_csi2_phy; ++i) { + struct cal_camerarx *phy = cal->phy[i]; + struct cal_v4l2_async_subdev *casd; + struct fwnode_handle *fwnode; + + if (!phy->source_node) + continue; + + fwnode = of_fwnode_handle(phy->source_node); + casd = v4l2_async_nf_add_fwnode(&cal->notifier, + fwnode, + struct cal_v4l2_async_subdev); + if (IS_ERR(casd)) { + phy_err(phy, "Failed to add subdev to notifier\n"); + ret = PTR_ERR(casd); + goto error; + } + + casd->phy = phy; + } + + ret = v4l2_async_nf_register(&cal->v4l2_dev, &cal->notifier); + if (ret) { + cal_err(cal, "Error registering async notifier\n"); + goto error; + } + + return 0; + +error: + v4l2_async_nf_cleanup(&cal->notifier); + return ret; +} + +static void cal_async_notifier_unregister(struct cal_dev *cal) +{ + v4l2_async_nf_unregister(&cal->notifier); + v4l2_async_nf_cleanup(&cal->notifier); +} + +/* ------------------------------------------------------------------ + * Media and V4L2 device handling + * ------------------------------------------------------------------ + */ + +/* + * Register user-facing devices. To be called at the end of the probe function + * when all resources are initialized and ready. + */ +static int cal_media_register(struct cal_dev *cal) +{ + int ret; + + ret = media_device_register(&cal->mdev); + if (ret) { + cal_err(cal, "Failed to register media device\n"); + return ret; + } + + /* + * Register the async notifier. This may trigger registration of the + * V4L2 video devices if all subdevs are ready. + */ + ret = cal_async_notifier_register(cal); + if (ret) { + media_device_unregister(&cal->mdev); + return ret; + } + + return 0; +} + +/* + * Unregister the user-facing devices, but don't free memory yet. To be called + * at the beginning of the remove function, to disallow access from userspace. + */ +static void cal_media_unregister(struct cal_dev *cal) +{ + unsigned int i; + + /* Unregister all the V4L2 video devices. */ + for (i = 0; i < cal->num_contexts; i++) + cal_ctx_v4l2_unregister(cal->ctx[i]); + + cal_async_notifier_unregister(cal); + media_device_unregister(&cal->mdev); +} + +/* + * Initialize the in-kernel objects. To be called at the beginning of the probe + * function, before the V4L2 device is used by the driver. + */ +static int cal_media_init(struct cal_dev *cal) +{ + struct media_device *mdev = &cal->mdev; + int ret; + + mdev->dev = cal->dev; + mdev->hw_revision = cal->revision; + strscpy(mdev->model, "CAL", sizeof(mdev->model)); + media_device_init(mdev); + + /* + * Initialize the V4L2 device (despite the function name, this performs + * initialization, not registration). + */ + cal->v4l2_dev.mdev = mdev; + ret = v4l2_device_register(cal->dev, &cal->v4l2_dev); + if (ret) { + cal_err(cal, "Failed to register V4L2 device\n"); + return ret; + } + + vb2_dma_contig_set_max_seg_size(cal->dev, DMA_BIT_MASK(32)); + + return 0; +} + +/* + * Cleanup the in-kernel objects, freeing memory. To be called at the very end + * of the remove sequence, when nothing (including userspace) can access the + * objects anymore. + */ +static void cal_media_cleanup(struct cal_dev *cal) +{ + v4l2_device_unregister(&cal->v4l2_dev); + media_device_cleanup(&cal->mdev); + + vb2_dma_contig_clear_max_seg_size(cal->dev); +} + +/* ------------------------------------------------------------------ + * Initialization and module stuff + * ------------------------------------------------------------------ + */ + +static struct cal_ctx *cal_ctx_create(struct cal_dev *cal, int inst) +{ + struct cal_ctx *ctx; + int ret; + + ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + + ctx->cal = cal; + ctx->phy = cal->phy[inst]; + ctx->dma_ctx = inst; + ctx->csi2_ctx = inst; + ctx->cport = inst; + + ret = cal_ctx_v4l2_init(ctx); + if (ret) { + kfree(ctx); + return NULL; + } + + return ctx; +} + +static void cal_ctx_destroy(struct cal_ctx *ctx) +{ + cal_ctx_v4l2_cleanup(ctx); + + kfree(ctx); +} + +static const struct of_device_id cal_of_match[] = { + { + .compatible = "ti,dra72-cal", + .data = (void *)&dra72x_cal_data, + }, + { + .compatible = "ti,dra72-pre-es2-cal", + .data = (void *)&dra72x_es1_cal_data, + }, + { + .compatible = "ti,dra76-cal", + .data = (void *)&dra76x_cal_data, + }, + { + .compatible = "ti,am654-cal", + .data = (void *)&am654_cal_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cal_of_match); + +/* Get hardware revision and info. */ + +#define CAL_HL_HWINFO_VALUE 0xa3c90469 + +static void cal_get_hwinfo(struct cal_dev *cal) +{ + u32 hwinfo; + + cal->revision = cal_read(cal, CAL_HL_REVISION); + switch (FIELD_GET(CAL_HL_REVISION_SCHEME_MASK, cal->revision)) { + case CAL_HL_REVISION_SCHEME_H08: + cal_dbg(3, cal, "CAL HW revision %lu.%lu.%lu (0x%08x)\n", + FIELD_GET(CAL_HL_REVISION_MAJOR_MASK, cal->revision), + FIELD_GET(CAL_HL_REVISION_MINOR_MASK, cal->revision), + FIELD_GET(CAL_HL_REVISION_RTL_MASK, cal->revision), + cal->revision); + break; + + case CAL_HL_REVISION_SCHEME_LEGACY: + default: + cal_info(cal, "Unexpected CAL HW revision 0x%08x\n", + cal->revision); + break; + } + + hwinfo = cal_read(cal, CAL_HL_HWINFO); + if (hwinfo != CAL_HL_HWINFO_VALUE) + cal_info(cal, "CAL_HL_HWINFO = 0x%08x, expected 0x%08x\n", + hwinfo, CAL_HL_HWINFO_VALUE); +} + +static int cal_init_camerarx_regmap(struct cal_dev *cal) +{ + struct platform_device *pdev = to_platform_device(cal->dev); + struct device_node *np = cal->dev->of_node; + struct regmap_config config = { }; + struct regmap *syscon; + struct resource *res; + unsigned int offset; + void __iomem *base; + + syscon = syscon_regmap_lookup_by_phandle_args(np, "ti,camerrx-control", + 1, &offset); + if (!IS_ERR(syscon)) { + cal->syscon_camerrx = syscon; + cal->syscon_camerrx_offset = offset; + return 0; + } + + dev_warn(cal->dev, "failed to get ti,camerrx-control: %ld\n", + PTR_ERR(syscon)); + + /* + * Backward DTS compatibility. If syscon entry is not present then + * check if the camerrx_control resource is present. + */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "camerrx_control"); + base = devm_ioremap_resource(cal->dev, res); + if (IS_ERR(base)) { + cal_err(cal, "failed to ioremap camerrx_control\n"); + return PTR_ERR(base); + } + + cal_dbg(1, cal, "ioresource %s at %pa - %pa\n", + res->name, &res->start, &res->end); + + config.reg_bits = 32; + config.reg_stride = 4; + config.val_bits = 32; + config.max_register = resource_size(res) - 4; + + syscon = regmap_init_mmio(NULL, base, &config); + if (IS_ERR(syscon)) { + pr_err("regmap init failed\n"); + return PTR_ERR(syscon); + } + + /* + * In this case the base already point to the direct CM register so no + * need for an offset. + */ + cal->syscon_camerrx = syscon; + cal->syscon_camerrx_offset = 0; + + return 0; +} + +static int cal_probe(struct platform_device *pdev) +{ + struct cal_dev *cal; + bool connected = false; + unsigned int i; + int ret; + int irq; + + cal = devm_kzalloc(&pdev->dev, sizeof(*cal), GFP_KERNEL); + if (!cal) + return -ENOMEM; + + cal->data = of_device_get_match_data(&pdev->dev); + if (!cal->data) { + dev_err(&pdev->dev, "Could not get feature data based on compatible version\n"); + return -ENODEV; + } + + cal->dev = &pdev->dev; + platform_set_drvdata(pdev, cal); + + /* Acquire resources: clocks, CAMERARX regmap, I/O memory and IRQ. */ + cal->fclk = devm_clk_get(&pdev->dev, "fck"); + if (IS_ERR(cal->fclk)) { + dev_err(&pdev->dev, "cannot get CAL fclk\n"); + return PTR_ERR(cal->fclk); + } + + ret = cal_init_camerarx_regmap(cal); + if (ret < 0) + return ret; + + cal->res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "cal_top"); + cal->base = devm_ioremap_resource(&pdev->dev, cal->res); + if (IS_ERR(cal->base)) + return PTR_ERR(cal->base); + + cal_dbg(1, cal, "ioresource %s at %pa - %pa\n", + cal->res->name, &cal->res->start, &cal->res->end); + + irq = platform_get_irq(pdev, 0); + cal_dbg(1, cal, "got irq# %d\n", irq); + ret = devm_request_irq(&pdev->dev, irq, cal_irq, 0, CAL_MODULE_NAME, + cal); + if (ret) + return ret; + + /* Read the revision and hardware info to verify hardware access. */ + pm_runtime_enable(&pdev->dev); + ret = pm_runtime_resume_and_get(&pdev->dev); + if (ret) + goto error_pm_runtime; + + cal_get_hwinfo(cal); + pm_runtime_put_sync(&pdev->dev); + + /* Initialize the media device. */ + ret = cal_media_init(cal); + if (ret < 0) + goto error_pm_runtime; + + /* Create CAMERARX PHYs. */ + for (i = 0; i < cal->data->num_csi2_phy; ++i) { + cal->phy[i] = cal_camerarx_create(cal, i); + if (IS_ERR(cal->phy[i])) { + ret = PTR_ERR(cal->phy[i]); + cal->phy[i] = NULL; + goto error_camerarx; + } + + if (cal->phy[i]->source_node) + connected = true; + } + + if (!connected) { + cal_err(cal, "Neither port is configured, no point in staying up\n"); + ret = -ENODEV; + goto error_camerarx; + } + + /* Create contexts. */ + for (i = 0; i < cal->data->num_csi2_phy; ++i) { + if (!cal->phy[i]->source_node) + continue; + + cal->ctx[cal->num_contexts] = cal_ctx_create(cal, i); + if (!cal->ctx[cal->num_contexts]) { + cal_err(cal, "Failed to create context %u\n", cal->num_contexts); + ret = -ENODEV; + goto error_context; + } + + cal->num_contexts++; + } + + /* Register the media device. */ + ret = cal_media_register(cal); + if (ret) + goto error_context; + + return 0; + +error_context: + for (i = 0; i < cal->num_contexts; i++) + cal_ctx_destroy(cal->ctx[i]); + +error_camerarx: + for (i = 0; i < cal->data->num_csi2_phy; i++) + cal_camerarx_destroy(cal->phy[i]); + + cal_media_cleanup(cal); + +error_pm_runtime: + pm_runtime_disable(&pdev->dev); + + return ret; +} + +static int cal_remove(struct platform_device *pdev) +{ + struct cal_dev *cal = platform_get_drvdata(pdev); + unsigned int i; + int ret; + + cal_dbg(1, cal, "Removing %s\n", CAL_MODULE_NAME); + + ret = pm_runtime_resume_and_get(&pdev->dev); + + cal_media_unregister(cal); + + for (i = 0; i < cal->data->num_csi2_phy; i++) + cal_camerarx_disable(cal->phy[i]); + + for (i = 0; i < cal->num_contexts; i++) + cal_ctx_destroy(cal->ctx[i]); + + for (i = 0; i < cal->data->num_csi2_phy; i++) + cal_camerarx_destroy(cal->phy[i]); + + cal_media_cleanup(cal); + + if (ret >= 0) + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + return 0; +} + +static int cal_runtime_resume(struct device *dev) +{ + struct cal_dev *cal = dev_get_drvdata(dev); + unsigned int i; + u32 val; + + if (cal->data->flags & DRA72_CAL_PRE_ES2_LDO_DISABLE) { + /* + * Apply errata on both port everytime we (re-)enable + * the clock + */ + for (i = 0; i < cal->data->num_csi2_phy; i++) + cal_camerarx_i913_errata(cal->phy[i]); + } + + /* + * Enable global interrupts that are not related to a particular + * CAMERARAX or context. + */ + cal_write(cal, CAL_HL_IRQENABLE_SET(0), CAL_HL_IRQ_OCPO_ERR_MASK); + + val = cal_read(cal, CAL_CTRL); + cal_set_field(&val, CAL_CTRL_BURSTSIZE_BURST128, + CAL_CTRL_BURSTSIZE_MASK); + cal_set_field(&val, 0xf, CAL_CTRL_TAGCNT_MASK); + cal_set_field(&val, CAL_CTRL_POSTED_WRITES_NONPOSTED, + CAL_CTRL_POSTED_WRITES_MASK); + cal_set_field(&val, 0xff, CAL_CTRL_MFLAGL_MASK); + cal_set_field(&val, 0xff, CAL_CTRL_MFLAGH_MASK); + cal_write(cal, CAL_CTRL, val); + cal_dbg(3, cal, "CAL_CTRL = 0x%08x\n", cal_read(cal, CAL_CTRL)); + + return 0; +} + +static const struct dev_pm_ops cal_pm_ops = { + .runtime_resume = cal_runtime_resume, +}; + +static struct platform_driver cal_pdrv = { + .probe = cal_probe, + .remove = cal_remove, + .driver = { + .name = CAL_MODULE_NAME, + .pm = &cal_pm_ops, + .of_match_table = cal_of_match, + }, +}; + +module_platform_driver(cal_pdrv); diff --git a/drivers/media/platform/ti/cal/cal.h b/drivers/media/platform/ti/cal/cal.h new file mode 100644 index 000000000..de73d6d21 --- /dev/null +++ b/drivers/media/platform/ti/cal/cal.h @@ -0,0 +1,349 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * TI Camera Access Layer (CAL) + * + * Copyright (c) 2015-2020 Texas Instruments Inc. + * + * Authors: + * Benoit Parrot + * Laurent Pinchart + */ +#ifndef __TI_CAL_H__ +#define __TI_CAL_H__ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define CAL_MODULE_NAME "cal" +#define CAL_MAX_NUM_CONTEXT 8 +#define CAL_NUM_CSI2_PORTS 2 + +/* + * The width is limited by the size of the CAL_WR_DMA_XSIZE_j.XSIZE field, + * expressed in multiples of 64 bits. The height is limited by the size of the + * CAL_CSI2_CTXi_j.CTXi_LINES and CAL_WR_DMA_CTRL_j.YSIZE fields, expressed in + * lines. + */ +#define CAL_MIN_WIDTH_BYTES 16 +#define CAL_MAX_WIDTH_BYTES (8192 * 8) +#define CAL_MIN_HEIGHT_LINES 1 +#define CAL_MAX_HEIGHT_LINES 16383 + +#define CAL_CAMERARX_PAD_SINK 0 +#define CAL_CAMERARX_PAD_FIRST_SOURCE 1 +#define CAL_CAMERARX_NUM_SOURCE_PADS 1 +#define CAL_CAMERARX_NUM_PADS (1 + CAL_CAMERARX_NUM_SOURCE_PADS) + +static inline bool cal_rx_pad_is_sink(u32 pad) +{ + /* Camera RX has 1 sink pad, and N source pads */ + return pad == 0; +} + +static inline bool cal_rx_pad_is_source(u32 pad) +{ + /* Camera RX has 1 sink pad, and N source pads */ + return pad >= CAL_CAMERARX_PAD_FIRST_SOURCE && + pad <= CAL_CAMERARX_NUM_SOURCE_PADS; +} + +struct device; +struct device_node; +struct resource; +struct regmap; +struct regmap_fied; + +/* CTRL_CORE_CAMERRX_CONTROL register field id */ +enum cal_camerarx_field { + F_CTRLCLKEN, + F_CAMMODE, + F_LANEENABLE, + F_CSI_MODE, + F_MAX_FIELDS, +}; + +enum cal_dma_state { + CAL_DMA_RUNNING, + CAL_DMA_STOP_REQUESTED, + CAL_DMA_STOP_PENDING, + CAL_DMA_STOPPED, +}; + +struct cal_format_info { + u32 fourcc; + u32 code; + /* Bits per pixel */ + u8 bpp; + bool meta; +}; + +/* buffer for one video frame */ +struct cal_buffer { + /* common v4l buffer stuff -- must be first */ + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +/** + * struct cal_dmaqueue - Queue of DMA buffers + */ +struct cal_dmaqueue { + /** + * @lock: Protects all fields in the cal_dmaqueue. + */ + spinlock_t lock; + + /** + * @queue: Buffers queued to the driver and waiting for DMA processing. + * Buffers are added to the list by the vb2 .buffer_queue() operation, + * and move to @pending when they are scheduled for the next frame. + */ + struct list_head queue; + /** + * @pending: Buffer provided to the hardware to DMA the next frame. + * Will move to @active at the end of the current frame. + */ + struct cal_buffer *pending; + /** + * @active: Buffer being DMA'ed to for the current frame. Will be + * retired and given back to vb2 at the end of the current frame if + * a @pending buffer has been scheduled to replace it. + */ + struct cal_buffer *active; + + /** @state: State of the DMA engine. */ + enum cal_dma_state state; + /** @wait: Wait queue to signal a @state transition to CAL_DMA_STOPPED. */ + struct wait_queue_head wait; +}; + +struct cal_camerarx_data { + struct { + unsigned int lsb; + unsigned int msb; + } fields[F_MAX_FIELDS]; + unsigned int num_lanes; +}; + +struct cal_data { + const struct cal_camerarx_data *camerarx; + unsigned int num_csi2_phy; + unsigned int flags; +}; + +/* + * The Camera Adaptation Layer (CAL) module is paired with one or more complex + * I/O PHYs (CAMERARX). It contains multiple instances of CSI-2, processing and + * DMA contexts. + * + * The cal_dev structure represents the whole subsystem, including the CAL and + * the CAMERARX instances. Instances of struct cal_dev are named cal through the + * driver. + * + * The cal_camerarx structure represents one CAMERARX instance. Instances of + * cal_camerarx are named phy through the driver. + * + * The cal_ctx structure represents the combination of one CSI-2 context, one + * processing context and one DMA context. Instance of struct cal_ctx are named + * ctx through the driver. + */ + +struct cal_camerarx { + void __iomem *base; + struct resource *res; + struct regmap_field *fields[F_MAX_FIELDS]; + + struct cal_dev *cal; + unsigned int instance; + + struct v4l2_fwnode_endpoint endpoint; + struct device_node *source_ep_node; + struct device_node *source_node; + struct v4l2_subdev *source; + + struct v4l2_subdev subdev; + struct media_pad pads[CAL_CAMERARX_NUM_PADS]; + struct v4l2_mbus_framefmt formats[CAL_CAMERARX_NUM_PADS]; + + /* protects the vc_* fields below */ + spinlock_t vc_lock; + u8 vc_enable_count[4]; + u16 vc_frame_number[4]; + u32 vc_sequence[4]; + + /* + * Lock for camerarx ops. Protects: + * - formats + * - enable_count + */ + struct mutex mutex; + + unsigned int enable_count; +}; + +struct cal_dev { + struct clk *fclk; + int irq; + void __iomem *base; + struct resource *res; + struct device *dev; + + const struct cal_data *data; + u32 revision; + + /* Control Module handle */ + struct regmap *syscon_camerrx; + u32 syscon_camerrx_offset; + + /* Camera Core Module handle */ + struct cal_camerarx *phy[CAL_NUM_CSI2_PORTS]; + + u32 num_contexts; + struct cal_ctx *ctx[CAL_MAX_NUM_CONTEXT]; + + struct media_device mdev; + struct v4l2_device v4l2_dev; + struct v4l2_async_notifier notifier; + + unsigned long reserved_pix_proc_mask; +}; + +/* + * There is one cal_ctx structure for each camera core context. + */ +struct cal_ctx { + struct v4l2_ctrl_handler ctrl_handler; + struct video_device vdev; + struct media_pad pad; + + struct cal_dev *cal; + struct cal_camerarx *phy; + + /* v4l2_ioctl mutex */ + struct mutex mutex; + + struct cal_dmaqueue dma; + + /* video capture */ + const struct cal_format_info *fmtinfo; + /* Used to store current pixel format */ + struct v4l2_format v_fmt; + + /* Current subdev enumerated format (legacy) */ + const struct cal_format_info **active_fmt; + unsigned int num_active_fmt; + + struct vb2_queue vb_vidq; + u8 dma_ctx; + u8 cport; + u8 csi2_ctx; + u8 pix_proc; + u8 vc; + u8 datatype; + + bool use_pix_proc; +}; + +extern unsigned int cal_debug; +extern int cal_video_nr; +extern bool cal_mc_api; + +#define cal_dbg(level, cal, fmt, arg...) \ + do { \ + if (cal_debug >= (level)) \ + dev_printk(KERN_DEBUG, (cal)->dev, fmt, ##arg); \ + } while (0) +#define cal_info(cal, fmt, arg...) \ + dev_info((cal)->dev, fmt, ##arg) +#define cal_err(cal, fmt, arg...) \ + dev_err((cal)->dev, fmt, ##arg) + +#define ctx_dbg(level, ctx, fmt, arg...) \ + cal_dbg(level, (ctx)->cal, "ctx%u: " fmt, (ctx)->dma_ctx, ##arg) +#define ctx_info(ctx, fmt, arg...) \ + cal_info((ctx)->cal, "ctx%u: " fmt, (ctx)->dma_ctx, ##arg) +#define ctx_err(ctx, fmt, arg...) \ + cal_err((ctx)->cal, "ctx%u: " fmt, (ctx)->dma_ctx, ##arg) + +#define phy_dbg(level, phy, fmt, arg...) \ + cal_dbg(level, (phy)->cal, "phy%u: " fmt, (phy)->instance, ##arg) +#define phy_info(phy, fmt, arg...) \ + cal_info((phy)->cal, "phy%u: " fmt, (phy)->instance, ##arg) +#define phy_err(phy, fmt, arg...) \ + cal_err((phy)->cal, "phy%u: " fmt, (phy)->instance, ##arg) + +static inline u32 cal_read(struct cal_dev *cal, u32 offset) +{ + return ioread32(cal->base + offset); +} + +static inline void cal_write(struct cal_dev *cal, u32 offset, u32 val) +{ + iowrite32(val, cal->base + offset); +} + +static __always_inline u32 cal_read_field(struct cal_dev *cal, u32 offset, u32 mask) +{ + return FIELD_GET(mask, cal_read(cal, offset)); +} + +static inline void cal_write_field(struct cal_dev *cal, u32 offset, u32 value, + u32 mask) +{ + u32 val = cal_read(cal, offset); + + val &= ~mask; + val |= (value << __ffs(mask)) & mask; + cal_write(cal, offset, val); +} + +static inline void cal_set_field(u32 *valp, u32 field, u32 mask) +{ + u32 val = *valp; + + val &= ~mask; + val |= (field << __ffs(mask)) & mask; + *valp = val; +} + +extern const struct cal_format_info cal_formats[]; +extern const unsigned int cal_num_formats; +const struct cal_format_info *cal_format_by_fourcc(u32 fourcc); +const struct cal_format_info *cal_format_by_code(u32 code); + +void cal_quickdump_regs(struct cal_dev *cal); + +int cal_camerarx_get_remote_frame_desc(struct cal_camerarx *phy, + struct v4l2_mbus_frame_desc *desc); +void cal_camerarx_disable(struct cal_camerarx *phy); +void cal_camerarx_i913_errata(struct cal_camerarx *phy); +struct cal_camerarx *cal_camerarx_create(struct cal_dev *cal, + unsigned int instance); +void cal_camerarx_destroy(struct cal_camerarx *phy); + +int cal_ctx_prepare(struct cal_ctx *ctx); +void cal_ctx_unprepare(struct cal_ctx *ctx); +void cal_ctx_set_dma_addr(struct cal_ctx *ctx, dma_addr_t addr); +void cal_ctx_start(struct cal_ctx *ctx); +void cal_ctx_stop(struct cal_ctx *ctx); + +int cal_ctx_v4l2_register(struct cal_ctx *ctx); +void cal_ctx_v4l2_unregister(struct cal_ctx *ctx); +int cal_ctx_v4l2_init(struct cal_ctx *ctx); +void cal_ctx_v4l2_cleanup(struct cal_ctx *ctx); + +#endif /* __TI_CAL_H__ */ diff --git a/drivers/media/platform/ti/cal/cal_regs.h b/drivers/media/platform/ti/cal/cal_regs.h new file mode 100644 index 000000000..40e4f972f --- /dev/null +++ b/drivers/media/platform/ti/cal/cal_regs.h @@ -0,0 +1,463 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * TI CAL camera interface driver + * + * Copyright (c) 2015 Texas Instruments Inc. + * + * Benoit Parrot, + */ + +#ifndef __TI_CAL_REGS_H +#define __TI_CAL_REGS_H + +/* + * struct cal_dev.flags possibilities + * + * DRA72_CAL_PRE_ES2_LDO_DISABLE: + * Errata i913: CSI2 LDO Needs to be disabled when module is powered on + * + * Enabling CSI2 LDO shorts it to core supply. It is crucial the 2 CSI2 + * LDOs on the device are disabled if CSI-2 module is powered on + * (0x4845 B304 | 0x4845 B384 [28:27] = 0x1) or in ULPS (0x4845 B304 + * | 0x4845 B384 [28:27] = 0x2) mode. Common concerns include: high + * current draw on the module supply in active mode. + * + * Errata does not apply when CSI-2 module is powered off + * (0x4845 B304 | 0x4845 B384 [28:27] = 0x0). + * + * SW Workaround: + * Set the following register bits to disable the LDO, + * which is essentially CSI2 REG10 bit 6: + * + * Core 0: 0x4845 B828 = 0x0000 0040 + * Core 1: 0x4845 B928 = 0x0000 0040 + */ +#define DRA72_CAL_PRE_ES2_LDO_DISABLE BIT(0) + +/* CAL register offsets */ + +#define CAL_HL_REVISION 0x0000 +#define CAL_HL_HWINFO 0x0004 +#define CAL_HL_SYSCONFIG 0x0010 +#define CAL_HL_IRQ_EOI 0x001c +#define CAL_HL_IRQSTATUS_RAW(m) (0x20U + (m) * 0x10U) +#define CAL_HL_IRQSTATUS(m) (0x24U + (m) * 0x10U) +#define CAL_HL_IRQENABLE_SET(m) (0x28U + (m) * 0x10U) +#define CAL_HL_IRQENABLE_CLR(m) (0x2cU + (m) * 0x10U) +#define CAL_PIX_PROC(m) (0xc0U + (m) * 0x4U) +#define CAL_CTRL 0x100 +#define CAL_CTRL1 0x104 +#define CAL_LINE_NUMBER_EVT 0x108 +#define CAL_VPORT_CTRL1 0x120 +#define CAL_VPORT_CTRL2 0x124 +#define CAL_BYS_CTRL1 0x130 +#define CAL_BYS_CTRL2 0x134 +#define CAL_RD_DMA_CTRL 0x140 +#define CAL_RD_DMA_PIX_ADDR 0x144 +#define CAL_RD_DMA_PIX_OFST 0x148 +#define CAL_RD_DMA_XSIZE 0x14c +#define CAL_RD_DMA_YSIZE 0x150 +#define CAL_RD_DMA_INIT_ADDR 0x154 +#define CAL_RD_DMA_INIT_OFST 0x168 +#define CAL_RD_DMA_CTRL2 0x16c +#define CAL_WR_DMA_CTRL(m) (0x200U + (m) * 0x10U) +#define CAL_WR_DMA_ADDR(m) (0x204U + (m) * 0x10U) +#define CAL_WR_DMA_OFST(m) (0x208U + (m) * 0x10U) +#define CAL_WR_DMA_XSIZE(m) (0x20cU + (m) * 0x10U) +#define CAL_CSI2_PPI_CTRL(m) (0x300U + (m) * 0x80U) +#define CAL_CSI2_COMPLEXIO_CFG(m) (0x304U + (m) * 0x80U) +#define CAL_CSI2_COMPLEXIO_IRQSTATUS(m) (0x308U + (m) * 0x80U) +#define CAL_CSI2_SHORT_PACKET(m) (0x30cU + (m) * 0x80U) +#define CAL_CSI2_COMPLEXIO_IRQENABLE(m) (0x310U + (m) * 0x80U) +#define CAL_CSI2_TIMING(m) (0x314U + (m) * 0x80U) +#define CAL_CSI2_VC_IRQENABLE(m) (0x318U + (m) * 0x80U) +#define CAL_CSI2_VC_IRQSTATUS(m) (0x328U + (m) * 0x80U) +#define CAL_CSI2_CTX(phy, csi2_ctx) (0x330U + (phy) * 0x80U + (csi2_ctx) * 4) +#define CAL_CSI2_STATUS(phy, csi2_ctx) (0x350U + (phy) * 0x80U + (csi2_ctx) * 4) + +/* CAL CSI2 PHY register offsets */ +#define CAL_CSI2_PHY_REG0 0x000 +#define CAL_CSI2_PHY_REG1 0x004 +#define CAL_CSI2_PHY_REG2 0x008 +#define CAL_CSI2_PHY_REG10 0x028 + +/* CAL Control Module Core Camerrx Control register offsets */ +#define CM_CTRL_CORE_CAMERRX_CONTROL 0x000 + +/********************************************************************* +* Field Definition Macros +*********************************************************************/ + +#define CAL_HL_REVISION_MINOR_MASK GENMASK(5, 0) +#define CAL_HL_REVISION_CUSTOM_MASK GENMASK(7, 6) +#define CAL_HL_REVISION_MAJOR_MASK GENMASK(10, 8) +#define CAL_HL_REVISION_RTL_MASK GENMASK(15, 11) +#define CAL_HL_REVISION_FUNC_MASK GENMASK(27, 16) +#define CAL_HL_REVISION_SCHEME_MASK GENMASK(31, 30) +#define CAL_HL_REVISION_SCHEME_H08 1 +#define CAL_HL_REVISION_SCHEME_LEGACY 0 + +#define CAL_HL_HWINFO_WFIFO_MASK GENMASK(3, 0) +#define CAL_HL_HWINFO_RFIFO_MASK GENMASK(7, 4) +#define CAL_HL_HWINFO_PCTX_MASK GENMASK(12, 8) +#define CAL_HL_HWINFO_WCTX_MASK GENMASK(18, 13) +#define CAL_HL_HWINFO_VFIFO_MASK GENMASK(22, 19) +#define CAL_HL_HWINFO_NCPORT_MASK GENMASK(27, 23) +#define CAL_HL_HWINFO_NPPI_CTXS0_MASK GENMASK(29, 28) +#define CAL_HL_HWINFO_NPPI_CTXS1_MASK GENMASK(31, 30) +#define CAL_HL_HWINFO_NPPI_CONTEXTS_ZERO 0 +#define CAL_HL_HWINFO_NPPI_CONTEXTS_FOUR 1 +#define CAL_HL_HWINFO_NPPI_CONTEXTS_EIGHT 2 +#define CAL_HL_HWINFO_NPPI_CONTEXTS_RESERVED 3 + +#define CAL_HL_SYSCONFIG_SOFTRESET_MASK BIT(0) +#define CAL_HL_SYSCONFIG_SOFTRESET_DONE 0x0 +#define CAL_HL_SYSCONFIG_SOFTRESET_PENDING 0x1 +#define CAL_HL_SYSCONFIG_SOFTRESET_NOACTION 0x0 +#define CAL_HL_SYSCONFIG_SOFTRESET_RESET 0x1 +#define CAL_HL_SYSCONFIG_IDLE_MASK GENMASK(3, 2) +#define CAL_HL_SYSCONFIG_IDLEMODE_FORCE 0 +#define CAL_HL_SYSCONFIG_IDLEMODE_NO 1 +#define CAL_HL_SYSCONFIG_IDLEMODE_SMART1 2 +#define CAL_HL_SYSCONFIG_IDLEMODE_SMART2 3 + +#define CAL_HL_IRQ_EOI_LINE_NUMBER_MASK BIT(0) +#define CAL_HL_IRQ_EOI_LINE_NUMBER_READ0 0 +#define CAL_HL_IRQ_EOI_LINE_NUMBER_EOI0 0 + +#define CAL_HL_IRQ_WDMA_END_MASK(m) BIT(m) +#define CAL_HL_IRQ_WDMA_START_MASK(m) BIT(m) + +#define CAL_HL_IRQ_OCPO_ERR_MASK BIT(6) + +#define CAL_HL_IRQ_CIO_MASK(i) BIT(16 + (i) * 8) +#define CAL_HL_IRQ_VC_MASK(i) BIT(17 + (i) * 8) + +#define CAL_PIX_PROC_EN_MASK BIT(0) +#define CAL_PIX_PROC_EXTRACT_MASK GENMASK(4, 1) +#define CAL_PIX_PROC_EXTRACT_B6 0x0 +#define CAL_PIX_PROC_EXTRACT_B7 0x1 +#define CAL_PIX_PROC_EXTRACT_B8 0x2 +#define CAL_PIX_PROC_EXTRACT_B10 0x3 +#define CAL_PIX_PROC_EXTRACT_B10_MIPI 0x4 +#define CAL_PIX_PROC_EXTRACT_B12 0x5 +#define CAL_PIX_PROC_EXTRACT_B12_MIPI 0x6 +#define CAL_PIX_PROC_EXTRACT_B14 0x7 +#define CAL_PIX_PROC_EXTRACT_B14_MIPI 0x8 +#define CAL_PIX_PROC_EXTRACT_B16_BE 0x9 +#define CAL_PIX_PROC_EXTRACT_B16_LE 0xa +#define CAL_PIX_PROC_DPCMD_MASK GENMASK(9, 5) +#define CAL_PIX_PROC_DPCMD_BYPASS 0x0 +#define CAL_PIX_PROC_DPCMD_DPCM_10_8_1 0x2 +#define CAL_PIX_PROC_DPCMD_DPCM_12_8_1 0x8 +#define CAL_PIX_PROC_DPCMD_DPCM_10_7_1 0x4 +#define CAL_PIX_PROC_DPCMD_DPCM_10_7_2 0x5 +#define CAL_PIX_PROC_DPCMD_DPCM_10_6_1 0x6 +#define CAL_PIX_PROC_DPCMD_DPCM_10_6_2 0x7 +#define CAL_PIX_PROC_DPCMD_DPCM_12_7_1 0xa +#define CAL_PIX_PROC_DPCMD_DPCM_12_6_1 0xc +#define CAL_PIX_PROC_DPCMD_DPCM_14_10 0xe +#define CAL_PIX_PROC_DPCMD_DPCM_14_8_1 0x10 +#define CAL_PIX_PROC_DPCMD_DPCM_16_12_1 0x12 +#define CAL_PIX_PROC_DPCMD_DPCM_16_10_1 0x14 +#define CAL_PIX_PROC_DPCMD_DPCM_16_8_1 0x16 +#define CAL_PIX_PROC_DPCME_MASK GENMASK(15, 11) +#define CAL_PIX_PROC_DPCME_BYPASS 0x0 +#define CAL_PIX_PROC_DPCME_DPCM_10_8_1 0x2 +#define CAL_PIX_PROC_DPCME_DPCM_12_8_1 0x8 +#define CAL_PIX_PROC_DPCME_DPCM_14_10 0xe +#define CAL_PIX_PROC_DPCME_DPCM_14_8_1 0x10 +#define CAL_PIX_PROC_DPCME_DPCM_16_12_1 0x12 +#define CAL_PIX_PROC_DPCME_DPCM_16_10_1 0x14 +#define CAL_PIX_PROC_DPCME_DPCM_16_8_1 0x16 +#define CAL_PIX_PROC_PACK_MASK GENMASK(18, 16) +#define CAL_PIX_PROC_PACK_B8 0x0 +#define CAL_PIX_PROC_PACK_B10_MIPI 0x2 +#define CAL_PIX_PROC_PACK_B12 0x3 +#define CAL_PIX_PROC_PACK_B12_MIPI 0x4 +#define CAL_PIX_PROC_PACK_B16 0x5 +#define CAL_PIX_PROC_PACK_ARGB 0x6 +#define CAL_PIX_PROC_CPORT_MASK GENMASK(23, 19) + +#define CAL_CTRL_POSTED_WRITES_MASK BIT(0) +#define CAL_CTRL_POSTED_WRITES_NONPOSTED 0 +#define CAL_CTRL_POSTED_WRITES 1 +#define CAL_CTRL_TAGCNT_MASK GENMASK(4, 1) +#define CAL_CTRL_BURSTSIZE_MASK GENMASK(6, 5) +#define CAL_CTRL_BURSTSIZE_BURST16 0x0 +#define CAL_CTRL_BURSTSIZE_BURST32 0x1 +#define CAL_CTRL_BURSTSIZE_BURST64 0x2 +#define CAL_CTRL_BURSTSIZE_BURST128 0x3 +#define CAL_CTRL_LL_FORCE_STATE_MASK GENMASK(12, 7) +#define CAL_CTRL_MFLAGL_MASK GENMASK(20, 13) +#define CAL_CTRL_PWRSCPCLK_MASK BIT(21) +#define CAL_CTRL_PWRSCPCLK_AUTO 0 +#define CAL_CTRL_PWRSCPCLK_FORCE 1 +#define CAL_CTRL_RD_DMA_STALL_MASK BIT(22) +#define CAL_CTRL_MFLAGH_MASK GENMASK(31, 24) + +#define CAL_CTRL1_PPI_GROUPING_MASK GENMASK(1, 0) +#define CAL_CTRL1_PPI_GROUPING_DISABLED 0 +#define CAL_CTRL1_PPI_GROUPING_RESERVED 1 +#define CAL_CTRL1_PPI_GROUPING_0 2 +#define CAL_CTRL1_PPI_GROUPING_1 3 +#define CAL_CTRL1_INTERLEAVE01_MASK GENMASK(3, 2) +#define CAL_CTRL1_INTERLEAVE01_DISABLED 0 +#define CAL_CTRL1_INTERLEAVE01_PIX1 1 +#define CAL_CTRL1_INTERLEAVE01_PIX4 2 +#define CAL_CTRL1_INTERLEAVE01_RESERVED 3 +#define CAL_CTRL1_INTERLEAVE23_MASK GENMASK(5, 4) +#define CAL_CTRL1_INTERLEAVE23_DISABLED 0 +#define CAL_CTRL1_INTERLEAVE23_PIX1 1 +#define CAL_CTRL1_INTERLEAVE23_PIX4 2 +#define CAL_CTRL1_INTERLEAVE23_RESERVED 3 + +#define CAL_LINE_NUMBER_EVT_CPORT_MASK GENMASK(4, 0) +#define CAL_LINE_NUMBER_EVT_MASK GENMASK(29, 16) + +#define CAL_VPORT_CTRL1_PCLK_MASK GENMASK(16, 0) +#define CAL_VPORT_CTRL1_XBLK_MASK GENMASK(24, 17) +#define CAL_VPORT_CTRL1_YBLK_MASK GENMASK(30, 25) +#define CAL_VPORT_CTRL1_WIDTH_MASK BIT(31) +#define CAL_VPORT_CTRL1_WIDTH_ONE 0 +#define CAL_VPORT_CTRL1_WIDTH_TWO 1 + +#define CAL_VPORT_CTRL2_CPORT_MASK GENMASK(4, 0) +#define CAL_VPORT_CTRL2_FREERUNNING_MASK BIT(15) +#define CAL_VPORT_CTRL2_FREERUNNING_GATED 0 +#define CAL_VPORT_CTRL2_FREERUNNING_FREE 1 +#define CAL_VPORT_CTRL2_FS_RESETS_MASK BIT(16) +#define CAL_VPORT_CTRL2_FS_RESETS_NO 0 +#define CAL_VPORT_CTRL2_FS_RESETS_YES 1 +#define CAL_VPORT_CTRL2_FSM_RESET_MASK BIT(17) +#define CAL_VPORT_CTRL2_FSM_RESET_NOEFFECT 0 +#define CAL_VPORT_CTRL2_FSM_RESET 1 +#define CAL_VPORT_CTRL2_RDY_THR_MASK GENMASK(31, 18) + +#define CAL_BYS_CTRL1_PCLK_MASK GENMASK(16, 0) +#define CAL_BYS_CTRL1_XBLK_MASK GENMASK(24, 17) +#define CAL_BYS_CTRL1_YBLK_MASK GENMASK(30, 25) +#define CAL_BYS_CTRL1_BYSINEN_MASK BIT(31) + +#define CAL_BYS_CTRL2_CPORTIN_MASK GENMASK(4, 0) +#define CAL_BYS_CTRL2_CPORTOUT_MASK GENMASK(9, 5) +#define CAL_BYS_CTRL2_DUPLICATEDDATA_MASK BIT(10) +#define CAL_BYS_CTRL2_DUPLICATEDDATA_NO 0 +#define CAL_BYS_CTRL2_DUPLICATEDDATA_YES 1 +#define CAL_BYS_CTRL2_FREERUNNING_MASK BIT(11) +#define CAL_BYS_CTRL2_FREERUNNING_NO 0 +#define CAL_BYS_CTRL2_FREERUNNING_YES 1 + +#define CAL_RD_DMA_CTRL_GO_MASK BIT(0) +#define CAL_RD_DMA_CTRL_GO_DIS 0 +#define CAL_RD_DMA_CTRL_GO_EN 1 +#define CAL_RD_DMA_CTRL_GO_IDLE 0 +#define CAL_RD_DMA_CTRL_GO_BUSY 1 +#define CAL_RD_DMA_CTRL_INIT_MASK BIT(1) +#define CAL_RD_DMA_CTRL_BW_LIMITER_MASK GENMASK(10, 2) +#define CAL_RD_DMA_CTRL_OCP_TAG_CNT_MASK GENMASK(14, 11) +#define CAL_RD_DMA_CTRL_PCLK_MASK GENMASK(31, 15) + +#define CAL_RD_DMA_PIX_ADDR_MASK GENMASK(31, 3) + +#define CAL_RD_DMA_PIX_OFST_MASK GENMASK(31, 4) + +#define CAL_RD_DMA_XSIZE_MASK GENMASK(31, 19) + +#define CAL_RD_DMA_YSIZE_MASK GENMASK(29, 16) + +#define CAL_RD_DMA_INIT_ADDR_MASK GENMASK(31, 3) + +#define CAL_RD_DMA_INIT_OFST_MASK GENMASK(31, 3) + +#define CAL_RD_DMA_CTRL2_CIRC_MODE_MASK GENMASK(2, 0) +#define CAL_RD_DMA_CTRL2_CIRC_MODE_DIS 0 +#define CAL_RD_DMA_CTRL2_CIRC_MODE_ONE 1 +#define CAL_RD_DMA_CTRL2_CIRC_MODE_FOUR 2 +#define CAL_RD_DMA_CTRL2_CIRC_MODE_SIXTEEN 3 +#define CAL_RD_DMA_CTRL2_CIRC_MODE_SIXTYFOUR 4 +#define CAL_RD_DMA_CTRL2_CIRC_MODE_RESERVED 5 +#define CAL_RD_DMA_CTRL2_ICM_CSTART_MASK BIT(3) +#define CAL_RD_DMA_CTRL2_PATTERN_MASK GENMASK(5, 4) +#define CAL_RD_DMA_CTRL2_PATTERN_LINEAR 0 +#define CAL_RD_DMA_CTRL2_PATTERN_YUV420 1 +#define CAL_RD_DMA_CTRL2_PATTERN_RD2SKIP2 2 +#define CAL_RD_DMA_CTRL2_PATTERN_RD2SKIP4 3 +#define CAL_RD_DMA_CTRL2_BYSOUT_LE_WAIT_MASK BIT(6) +#define CAL_RD_DMA_CTRL2_BYSOUT_LE_WAIT_FREERUNNING 0 +#define CAL_RD_DMA_CTRL2_BYSOUT_LE_WAIT_WAITFORBYSOUT 1 +#define CAL_RD_DMA_CTRL2_CIRC_SIZE_MASK GENMASK(29, 16) + +#define CAL_WR_DMA_CTRL_MODE_MASK GENMASK(2, 0) +#define CAL_WR_DMA_CTRL_MODE_DIS 0 +#define CAL_WR_DMA_CTRL_MODE_SHD 1 +#define CAL_WR_DMA_CTRL_MODE_CNT 2 +#define CAL_WR_DMA_CTRL_MODE_CNT_INIT 3 +#define CAL_WR_DMA_CTRL_MODE_CONST 4 +#define CAL_WR_DMA_CTRL_MODE_RESERVED 5 +#define CAL_WR_DMA_CTRL_PATTERN_MASK GENMASK(4, 3) +#define CAL_WR_DMA_CTRL_PATTERN_LINEAR 0 +#define CAL_WR_DMA_CTRL_PATTERN_WR2SKIP2 2 +#define CAL_WR_DMA_CTRL_PATTERN_WR2SKIP4 3 +#define CAL_WR_DMA_CTRL_PATTERN_RESERVED 1 +#define CAL_WR_DMA_CTRL_ICM_PSTART_MASK BIT(5) +#define CAL_WR_DMA_CTRL_DTAG_MASK GENMASK(8, 6) +#define CAL_WR_DMA_CTRL_DTAG_ATT_HDR 0 +#define CAL_WR_DMA_CTRL_DTAG_ATT_DAT 1 +#define CAL_WR_DMA_CTRL_DTAG 2 +#define CAL_WR_DMA_CTRL_DTAG_PIX_HDR 3 +#define CAL_WR_DMA_CTRL_DTAG_PIX_DAT 4 +#define CAL_WR_DMA_CTRL_DTAG_D5 5 +#define CAL_WR_DMA_CTRL_DTAG_D6 6 +#define CAL_WR_DMA_CTRL_DTAG_D7 7 +#define CAL_WR_DMA_CTRL_CPORT_MASK GENMASK(13, 9) +#define CAL_WR_DMA_CTRL_STALL_RD_MASK BIT(14) +#define CAL_WR_DMA_CTRL_YSIZE_MASK GENMASK(31, 18) + +#define CAL_WR_DMA_ADDR_MASK GENMASK(31, 4) + +#define CAL_WR_DMA_OFST_MASK GENMASK(18, 4) +#define CAL_WR_DMA_OFST_CIRC_MODE_MASK GENMASK(23, 22) +#define CAL_WR_DMA_OFST_CIRC_MODE_ONE 1 +#define CAL_WR_DMA_OFST_CIRC_MODE_FOUR 2 +#define CAL_WR_DMA_OFST_CIRC_MODE_SIXTYFOUR 3 +#define CAL_WR_DMA_OFST_CIRC_MODE_DISABLED 0 +#define CAL_WR_DMA_OFST_CIRC_SIZE_MASK GENMASK(31, 24) + +#define CAL_WR_DMA_XSIZE_XSKIP_MASK GENMASK(15, 3) +#define CAL_WR_DMA_XSIZE_MASK GENMASK(31, 19) + +#define CAL_CSI2_PPI_CTRL_IF_EN_MASK BIT(0) +#define CAL_CSI2_PPI_CTRL_ECC_EN_MASK BIT(2) +#define CAL_CSI2_PPI_CTRL_FRAME_MASK BIT(3) +#define CAL_CSI2_PPI_CTRL_FRAME_IMMEDIATE 0 +#define CAL_CSI2_PPI_CTRL_FRAME 1 + +#define CAL_CSI2_COMPLEXIO_CFG_CLOCK_POSITION_MASK GENMASK(2, 0) +#define CAL_CSI2_COMPLEXIO_CFG_POSITION_5 5 +#define CAL_CSI2_COMPLEXIO_CFG_POSITION_4 4 +#define CAL_CSI2_COMPLEXIO_CFG_POSITION_3 3 +#define CAL_CSI2_COMPLEXIO_CFG_POSITION_2 2 +#define CAL_CSI2_COMPLEXIO_CFG_POSITION_1 1 +#define CAL_CSI2_COMPLEXIO_CFG_POSITION_NOT_USED 0 +#define CAL_CSI2_COMPLEXIO_CFG_CLOCK_POL_MASK BIT(3) +#define CAL_CSI2_COMPLEXIO_CFG_POL_PLUSMINUS 0 +#define CAL_CSI2_COMPLEXIO_CFG_POL_MINUSPLUS 1 +#define CAL_CSI2_COMPLEXIO_CFG_DATA1_POSITION_MASK GENMASK(6, 4) +#define CAL_CSI2_COMPLEXIO_CFG_DATA1_POL_MASK BIT(7) +#define CAL_CSI2_COMPLEXIO_CFG_DATA2_POSITION_MASK GENMASK(10, 8) +#define CAL_CSI2_COMPLEXIO_CFG_DATA2_POL_MASK BIT(11) +#define CAL_CSI2_COMPLEXIO_CFG_DATA3_POSITION_MASK GENMASK(14, 12) +#define CAL_CSI2_COMPLEXIO_CFG_DATA3_POL_MASK BIT(15) +#define CAL_CSI2_COMPLEXIO_CFG_DATA4_POSITION_MASK GENMASK(18, 16) +#define CAL_CSI2_COMPLEXIO_CFG_DATA4_POL_MASK BIT(19) +#define CAL_CSI2_COMPLEXIO_CFG_PWR_AUTO_MASK BIT(24) +#define CAL_CSI2_COMPLEXIO_CFG_PWR_STATUS_MASK GENMASK(26, 25) +#define CAL_CSI2_COMPLEXIO_CFG_PWR_STATUS_STATE_OFF 0 +#define CAL_CSI2_COMPLEXIO_CFG_PWR_STATUS_STATE_ON 1 +#define CAL_CSI2_COMPLEXIO_CFG_PWR_STATUS_STATE_ULP 2 +#define CAL_CSI2_COMPLEXIO_CFG_PWR_CMD_MASK GENMASK(28, 27) +#define CAL_CSI2_COMPLEXIO_CFG_PWR_CMD_STATE_OFF 0 +#define CAL_CSI2_COMPLEXIO_CFG_PWR_CMD_STATE_ON 1 +#define CAL_CSI2_COMPLEXIO_CFG_PWR_CMD_STATE_ULP 2 +#define CAL_CSI2_COMPLEXIO_CFG_RESET_DONE_MASK BIT(29) +#define CAL_CSI2_COMPLEXIO_CFG_RESET_DONE_RESETCOMPLETED 1 +#define CAL_CSI2_COMPLEXIO_CFG_RESET_DONE_RESETONGOING 0 +#define CAL_CSI2_COMPLEXIO_CFG_RESET_CTRL_MASK BIT(30) +#define CAL_CSI2_COMPLEXIO_CFG_RESET_CTRL 0 +#define CAL_CSI2_COMPLEXIO_CFG_RESET_CTRL_OPERATIONAL 1 + +#define CAL_CSI2_SHORT_PACKET_MASK GENMASK(23, 0) + +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTHS1_MASK BIT(0) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTHS2_MASK BIT(1) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTHS3_MASK BIT(2) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTHS4_MASK BIT(3) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTHS5_MASK BIT(4) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS1_MASK BIT(5) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS2_MASK BIT(6) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS3_MASK BIT(7) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS4_MASK BIT(8) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRSOTSYNCHS5_MASK BIT(9) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRESC1_MASK BIT(10) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRESC2_MASK BIT(11) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRESC3_MASK BIT(12) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRESC4_MASK BIT(13) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRESC5_MASK BIT(14) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRCONTROL1_MASK BIT(15) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRCONTROL2_MASK BIT(16) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRCONTROL3_MASK BIT(17) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRCONTROL4_MASK BIT(18) +#define CAL_CSI2_COMPLEXIO_IRQ_ERRCONTROL5_MASK BIT(19) +#define CAL_CSI2_COMPLEXIO_IRQ_LANE_ERRORS_MASK GENMASK(19, 0) +#define CAL_CSI2_COMPLEXIO_IRQ_STATEULPM1_MASK BIT(20) +#define CAL_CSI2_COMPLEXIO_IRQ_STATEULPM2_MASK BIT(21) +#define CAL_CSI2_COMPLEXIO_IRQ_STATEULPM3_MASK BIT(22) +#define CAL_CSI2_COMPLEXIO_IRQ_STATEULPM4_MASK BIT(23) +#define CAL_CSI2_COMPLEXIO_IRQ_STATEULPM5_MASK BIT(24) +#define CAL_CSI2_COMPLEXIO_IRQ_STATEALLULPMENTER_MASK BIT(25) +#define CAL_CSI2_COMPLEXIO_IRQ_STATEALLULPMEXIT_MASK BIT(26) +#define CAL_CSI2_COMPLEXIO_IRQ_FIFO_OVR_MASK BIT(27) +#define CAL_CSI2_COMPLEXIO_IRQ_SHORT_PACKET_MASK BIT(28) +#define CAL_CSI2_COMPLEXIO_IRQ_ECC_NO_CORRECTION_MASK BIT(30) + +#define CAL_CSI2_TIMING_STOP_STATE_COUNTER_IO1_MASK GENMASK(12, 0) +#define CAL_CSI2_TIMING_STOP_STATE_X4_IO1_MASK BIT(13) +#define CAL_CSI2_TIMING_STOP_STATE_X16_IO1_MASK BIT(14) +#define CAL_CSI2_TIMING_FORCE_RX_MODE_IO1_MASK BIT(15) + +#define CAL_CSI2_VC_IRQ_FS_IRQ_MASK(n) BIT(0 + ((n) * 8)) +#define CAL_CSI2_VC_IRQ_FE_IRQ_MASK(n) BIT(1 + ((n) * 8)) +#define CAL_CSI2_VC_IRQ_LS_IRQ_MASK(n) BIT(2 + ((n) * 8)) +#define CAL_CSI2_VC_IRQ_LE_IRQ_MASK(n) BIT(3 + ((n) * 8)) +#define CAL_CSI2_VC_IRQ_CS_IRQ_MASK(n) BIT(4 + ((n) * 8)) +#define CAL_CSI2_VC_IRQ_ECC_CORRECTION_IRQ_MASK(n) BIT(5 + ((n) * 8)) + +#define CAL_CSI2_CTX_DT_MASK GENMASK(5, 0) +#define CAL_CSI2_CTX_DT_DISABLED 0 +#define CAL_CSI2_CTX_DT_ANY 1 +#define CAL_CSI2_CTX_VC_MASK GENMASK(7, 6) +#define CAL_CSI2_CTX_CPORT_MASK GENMASK(12, 8) +#define CAL_CSI2_CTX_ATT_MASK BIT(13) +#define CAL_CSI2_CTX_ATT_PIX 0 +#define CAL_CSI2_CTX_ATT 1 +#define CAL_CSI2_CTX_PACK_MODE_MASK BIT(14) +#define CAL_CSI2_CTX_PACK_MODE_LINE 0 +#define CAL_CSI2_CTX_PACK_MODE_FRAME 1 +#define CAL_CSI2_CTX_LINES_MASK GENMASK(29, 16) + +#define CAL_CSI2_STATUS_FRAME_MASK GENMASK(15, 0) + +#define CAL_CSI2_PHY_REG0_THS_SETTLE_MASK GENMASK(7, 0) +#define CAL_CSI2_PHY_REG0_THS_TERM_MASK GENMASK(15, 8) +#define CAL_CSI2_PHY_REG0_HSCLOCKCONFIG_MASK BIT(24) +#define CAL_CSI2_PHY_REG0_HSCLOCKCONFIG_DISABLE 1 +#define CAL_CSI2_PHY_REG0_HSCLOCKCONFIG_ENABLE 0 + +#define CAL_CSI2_PHY_REG1_TCLK_SETTLE_MASK GENMASK(7, 0) +#define CAL_CSI2_PHY_REG1_CTRLCLK_DIV_FACTOR_MASK GENMASK(9, 8) +#define CAL_CSI2_PHY_REG1_DPHY_HS_SYNC_PATTERN_MASK GENMASK(17, 10) +#define CAL_CSI2_PHY_REG1_TCLK_TERM_MASK GENMASK(24, 18) +#define CAL_CSI2_PHY_REG1_CLOCK_MISS_DETECTOR_STATUS_MASK BIT(25) +#define CAL_CSI2_PHY_REG1_CLOCK_MISS_DETECTOR_STATUS_ERROR 1 +#define CAL_CSI2_PHY_REG1_CLOCK_MISS_DETECTOR_STATUS_SUCCESS 0 +#define CAL_CSI2_PHY_REG1_RESET_DONE_STATUS_MASK GENMASK(29, 28) + +#define CAL_CSI2_PHY_REG10_I933_LDO_DISABLE_MASK BIT(6) + +#define CAL_CSI2_PHY_REG2_CCP2_SYNC_PATTERN_MASK GENMASK(23, 0) +#define CAL_CSI2_PHY_REG2_TRIGGER_CMD_RXTRIGESC3_MASK GENMASK(25, 24) +#define CAL_CSI2_PHY_REG2_TRIGGER_CMD_RXTRIGESC2_MASK GENMASK(27, 26) +#define CAL_CSI2_PHY_REG2_TRIGGER_CMD_RXTRIGESC1_MASK GENMASK(29, 28) +#define CAL_CSI2_PHY_REG2_TRIGGER_CMD_RXTRIGESC0_MASK GENMASK(31, 30) + +#define CM_CAMERRX_CTRL_CSI1_CTRLCLKEN_MASK BIT(0) +#define CM_CAMERRX_CTRL_CSI1_CAMMODE_MASK GENMASK(2, 1) +#define CM_CAMERRX_CTRL_CSI1_LANEENABLE_MASK GENMASK(4, 3) +#define CM_CAMERRX_CTRL_CSI1_MODE_MASK BIT(5) +#define CM_CAMERRX_CTRL_CSI0_CTRLCLKEN_MASK BIT(10) +#define CM_CAMERRX_CTRL_CSI0_CAMMODE_MASK GENMASK(12, 11) +#define CM_CAMERRX_CTRL_CSI0_LANEENABLE_MASK GENMASK(16, 13) +#define CM_CAMERRX_CTRL_CSI0_MODE_MASK BIT(17) + +#endif diff --git a/drivers/media/platform/ti/davinci/Kconfig b/drivers/media/platform/ti/davinci/Kconfig new file mode 100644 index 000000000..96d4bed7f --- /dev/null +++ b/drivers/media/platform/ti/davinci/Kconfig @@ -0,0 +1,49 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_DAVINCI_VPIF_DISPLAY + tristate "TI DaVinci VPIF V4L2-Display driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_DAVINCI || COMPILE_TEST + depends on I2C + select VIDEOBUF2_DMA_CONTIG + select VIDEO_ADV7343 if MEDIA_SUBDRV_AUTOSELECT + select VIDEO_THS7303 if MEDIA_SUBDRV_AUTOSELECT + help + Enables Davinci VPIF module used for display devices. + This module is used for display on TI DM6467/DA850/OMAPL138 + SoCs. + + To compile this driver as a module, choose M here. There will + be two modules called vpif.ko and vpif_display.ko + +config VIDEO_DAVINCI_VPIF_CAPTURE + tristate "TI DaVinci VPIF video capture driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_DAVINCI || COMPILE_TEST + depends on I2C + select VIDEOBUF2_DMA_CONTIG + select V4L2_FWNODE + help + Enables Davinci VPIF module used for capture devices. + This module is used for capture on TI DM6467/DA850/OMAPL138 + SoCs. + + To compile this driver as a module, choose M here. There will + be two modules called vpif.ko and vpif_capture.ko + +config VIDEO_DAVINCI_VPBE_DISPLAY + tristate "TI DaVinci VPBE V4L2-Display driver" + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV + depends on ARCH_DAVINCI || COMPILE_TEST + depends on I2C + select VIDEOBUF2_DMA_CONTIG + help + Enables Davinci VPBE module used for display devices. + This module is used for display on TI DM644x/DM365/DM355 + based display devices. + + To compile this driver as a module, choose M here. There will + be five modules created called vpss.ko, vpbe.ko, vpbe_osd.ko, + vpbe_venc.ko and vpbe_display.ko diff --git a/drivers/media/platform/ti/davinci/Makefile b/drivers/media/platform/ti/davinci/Makefile new file mode 100644 index 000000000..b20a91653 --- /dev/null +++ b/drivers/media/platform/ti/davinci/Makefile @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the davinci video device drivers. +# + +#VPIF Display driver +obj-$(CONFIG_VIDEO_DAVINCI_VPIF_DISPLAY) += vpif.o vpif_display.o +#VPIF Capture driver +obj-$(CONFIG_VIDEO_DAVINCI_VPIF_CAPTURE) += vpif.o vpif_capture.o + +obj-$(CONFIG_VIDEO_DAVINCI_VPBE_DISPLAY) += vpss.o vpbe.o vpbe_osd.o \ + vpbe_venc.o vpbe_display.o diff --git a/drivers/media/platform/ti/davinci/vpbe.c b/drivers/media/platform/ti/davinci/vpbe.c new file mode 100644 index 000000000..509ecc846 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpbe.c @@ -0,0 +1,840 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 Texas Instruments Inc + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define VPBE_DEFAULT_OUTPUT "Composite" +#define VPBE_DEFAULT_MODE "ntsc" + +static char *def_output = VPBE_DEFAULT_OUTPUT; +static char *def_mode = VPBE_DEFAULT_MODE; +static int debug; + +module_param(def_output, charp, S_IRUGO); +module_param(def_mode, charp, S_IRUGO); +module_param(debug, int, 0644); + +MODULE_PARM_DESC(def_output, "vpbe output name (default:Composite)"); +MODULE_PARM_DESC(def_mode, "vpbe output mode name (default:ntsc"); +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +MODULE_DESCRIPTION("TI DMXXX VPBE Display controller"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); + +/** + * vpbe_current_encoder_info - Get config info for current encoder + * @vpbe_dev: vpbe device ptr + * + * Return ptr to current encoder config info + */ +static struct encoder_config_info* +vpbe_current_encoder_info(struct vpbe_device *vpbe_dev) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + int index = vpbe_dev->current_sd_index; + + return ((index == 0) ? &cfg->venc : + &cfg->ext_encoders[index-1]); +} + +/** + * vpbe_find_encoder_sd_index - Given a name find encoder sd index + * + * @cfg: ptr to vpbe cfg + * @index: index used by application + * + * Return sd index of the encoder + */ +static int vpbe_find_encoder_sd_index(struct vpbe_config *cfg, + int index) +{ + char *encoder_name = cfg->outputs[index].subdev_name; + int i; + + /* Venc is always first */ + if (!strcmp(encoder_name, cfg->venc.module_name)) + return 0; + + for (i = 0; i < cfg->num_ext_encoders; i++) { + if (!strcmp(encoder_name, + cfg->ext_encoders[i].module_name)) + return i+1; + } + + return -EINVAL; +} + +/** + * vpbe_enum_outputs - enumerate outputs + * @vpbe_dev: vpbe device ptr + * @output: ptr to v4l2_output structure + * + * Enumerates the outputs available at the vpbe display + * returns the status, -EINVAL if end of output list + */ +static int vpbe_enum_outputs(struct vpbe_device *vpbe_dev, + struct v4l2_output *output) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + unsigned int temp_index = output->index; + + if (temp_index >= cfg->num_outputs) + return -EINVAL; + + *output = cfg->outputs[temp_index].output; + output->index = temp_index; + + return 0; +} + +static int vpbe_get_mode_info(struct vpbe_device *vpbe_dev, char *mode, + int output_index) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + struct vpbe_enc_mode_info var; + int curr_output = output_index; + int i; + + if (!mode) + return -EINVAL; + + for (i = 0; i < cfg->outputs[curr_output].num_modes; i++) { + var = cfg->outputs[curr_output].modes[i]; + if (!strcmp(mode, var.name)) { + vpbe_dev->current_timings = var; + return 0; + } + } + + return -EINVAL; +} + +static int vpbe_get_current_mode_info(struct vpbe_device *vpbe_dev, + struct vpbe_enc_mode_info *mode_info) +{ + if (!mode_info) + return -EINVAL; + + *mode_info = vpbe_dev->current_timings; + + return 0; +} + +/* Get std by std id */ +static int vpbe_get_std_info(struct vpbe_device *vpbe_dev, + v4l2_std_id std_id) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + struct vpbe_enc_mode_info var; + int curr_output = vpbe_dev->current_out_index; + int i; + + for (i = 0; i < vpbe_dev->cfg->outputs[curr_output].num_modes; i++) { + var = cfg->outputs[curr_output].modes[i]; + if ((var.timings_type & VPBE_ENC_STD) && + (var.std_id & std_id)) { + vpbe_dev->current_timings = var; + return 0; + } + } + + return -EINVAL; +} + +static int vpbe_get_std_info_by_name(struct vpbe_device *vpbe_dev, + char *std_name) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + struct vpbe_enc_mode_info var; + int curr_output = vpbe_dev->current_out_index; + int i; + + for (i = 0; i < vpbe_dev->cfg->outputs[curr_output].num_modes; i++) { + var = cfg->outputs[curr_output].modes[i]; + if (!strcmp(var.name, std_name)) { + vpbe_dev->current_timings = var; + return 0; + } + } + + return -EINVAL; +} + +/** + * vpbe_set_output - Set output + * @vpbe_dev: vpbe device ptr + * @index: index of output + * + * Set vpbe output to the output specified by the index + */ +static int vpbe_set_output(struct vpbe_device *vpbe_dev, int index) +{ + struct encoder_config_info *curr_enc_info = + vpbe_current_encoder_info(vpbe_dev); + struct vpbe_config *cfg = vpbe_dev->cfg; + struct venc_platform_data *venc_device = vpbe_dev->venc_device; + int enc_out_index; + int sd_index; + int ret; + + if (index >= cfg->num_outputs) + return -EINVAL; + + mutex_lock(&vpbe_dev->lock); + + sd_index = vpbe_dev->current_sd_index; + enc_out_index = cfg->outputs[index].output.index; + /* + * Currently we switch the encoder based on output selected + * by the application. If media controller is implemented later + * there is will be an API added to setup_link between venc + * and external encoder. So in that case below comparison always + * match and encoder will not be switched. But if application + * chose not to use media controller, then this provides current + * way of switching encoder at the venc output. + */ + if (strcmp(curr_enc_info->module_name, + cfg->outputs[index].subdev_name)) { + /* Need to switch the encoder at the output */ + sd_index = vpbe_find_encoder_sd_index(cfg, index); + if (sd_index < 0) { + ret = -EINVAL; + goto unlock; + } + + ret = venc_device->setup_if_config(cfg->outputs[index].if_params); + if (ret) + goto unlock; + } + + /* Set output at the encoder */ + ret = v4l2_subdev_call(vpbe_dev->encoders[sd_index], video, + s_routing, 0, enc_out_index, 0); + if (ret) + goto unlock; + + /* + * It is assumed that venc or external encoder will set a default + * mode in the sub device. For external encoder or LCD pannel output, + * we also need to set up the lcd port for the required mode. So setup + * the lcd port for the default mode that is configured in the board + * arch/arm/mach-davinci/board-dm355-evm.setup file for the external + * encoder. + */ + ret = vpbe_get_mode_info(vpbe_dev, + cfg->outputs[index].default_mode, index); + if (!ret) { + struct osd_state *osd_device = vpbe_dev->osd_device; + + osd_device->ops.set_left_margin(osd_device, + vpbe_dev->current_timings.left_margin); + osd_device->ops.set_top_margin(osd_device, + vpbe_dev->current_timings.upper_margin); + vpbe_dev->current_sd_index = sd_index; + vpbe_dev->current_out_index = index; + } +unlock: + mutex_unlock(&vpbe_dev->lock); + return ret; +} + +static int vpbe_set_default_output(struct vpbe_device *vpbe_dev) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + int i; + + for (i = 0; i < cfg->num_outputs; i++) { + if (!strcmp(def_output, + cfg->outputs[i].output.name)) { + int ret = vpbe_set_output(vpbe_dev, i); + + if (!ret) + vpbe_dev->current_out_index = i; + return ret; + } + } + return 0; +} + +/** + * vpbe_get_output - Get output + * @vpbe_dev: vpbe device ptr + * + * return current vpbe output to the index + */ +static unsigned int vpbe_get_output(struct vpbe_device *vpbe_dev) +{ + return vpbe_dev->current_out_index; +} + +/* + * vpbe_s_dv_timings - Set the given preset timings in the encoder + * + * Sets the timings if supported by the current encoder. Return the status. + * 0 - success & -EINVAL on error + */ +static int vpbe_s_dv_timings(struct vpbe_device *vpbe_dev, + struct v4l2_dv_timings *dv_timings) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + int out_index = vpbe_dev->current_out_index; + struct vpbe_output *output = &cfg->outputs[out_index]; + int sd_index = vpbe_dev->current_sd_index; + int ret, i; + + + if (!(cfg->outputs[out_index].output.capabilities & + V4L2_OUT_CAP_DV_TIMINGS)) + return -ENODATA; + + for (i = 0; i < output->num_modes; i++) { + if (output->modes[i].timings_type == VPBE_ENC_DV_TIMINGS && + !memcmp(&output->modes[i].dv_timings, + dv_timings, sizeof(*dv_timings))) + break; + } + if (i >= output->num_modes) + return -EINVAL; + vpbe_dev->current_timings = output->modes[i]; + mutex_lock(&vpbe_dev->lock); + + ret = v4l2_subdev_call(vpbe_dev->encoders[sd_index], video, + s_dv_timings, dv_timings); + if (!ret && vpbe_dev->amp) { + /* Call amplifier subdevice */ + ret = v4l2_subdev_call(vpbe_dev->amp, video, + s_dv_timings, dv_timings); + } + /* set the lcd controller output for the given mode */ + if (!ret) { + struct osd_state *osd_device = vpbe_dev->osd_device; + + osd_device->ops.set_left_margin(osd_device, + vpbe_dev->current_timings.left_margin); + osd_device->ops.set_top_margin(osd_device, + vpbe_dev->current_timings.upper_margin); + } + mutex_unlock(&vpbe_dev->lock); + + return ret; +} + +/* + * vpbe_g_dv_timings - Get the timings in the current encoder + * + * Get the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_g_dv_timings(struct vpbe_device *vpbe_dev, + struct v4l2_dv_timings *dv_timings) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + int out_index = vpbe_dev->current_out_index; + + if (!(cfg->outputs[out_index].output.capabilities & + V4L2_OUT_CAP_DV_TIMINGS)) + return -ENODATA; + + if (vpbe_dev->current_timings.timings_type & + VPBE_ENC_DV_TIMINGS) { + *dv_timings = vpbe_dev->current_timings.dv_timings; + return 0; + } + + return -EINVAL; +} + +/* + * vpbe_enum_dv_timings - Enumerate the dv timings in the current encoder + * + * Get the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_enum_dv_timings(struct vpbe_device *vpbe_dev, + struct v4l2_enum_dv_timings *timings) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + int out_index = vpbe_dev->current_out_index; + struct vpbe_output *output = &cfg->outputs[out_index]; + int j = 0; + int i; + + if (!(output->output.capabilities & V4L2_OUT_CAP_DV_TIMINGS)) + return -ENODATA; + + for (i = 0; i < output->num_modes; i++) { + if (output->modes[i].timings_type == VPBE_ENC_DV_TIMINGS) { + if (j == timings->index) + break; + j++; + } + } + + if (i == output->num_modes) + return -EINVAL; + timings->timings = output->modes[i].dv_timings; + return 0; +} + +/* + * vpbe_s_std - Set the given standard in the encoder + * + * Sets the standard if supported by the current encoder. Return the status. + * 0 - success & -EINVAL on error + */ +static int vpbe_s_std(struct vpbe_device *vpbe_dev, v4l2_std_id std_id) +{ + struct vpbe_config *cfg = vpbe_dev->cfg; + int out_index = vpbe_dev->current_out_index; + int sd_index = vpbe_dev->current_sd_index; + int ret; + + if (!(cfg->outputs[out_index].output.capabilities & + V4L2_OUT_CAP_STD)) + return -ENODATA; + + ret = vpbe_get_std_info(vpbe_dev, std_id); + if (ret) + return ret; + + mutex_lock(&vpbe_dev->lock); + + ret = v4l2_subdev_call(vpbe_dev->encoders[sd_index], video, + s_std_output, std_id); + /* set the lcd controller output for the given mode */ + if (!ret) { + struct osd_state *osd_device = vpbe_dev->osd_device; + + osd_device->ops.set_left_margin(osd_device, + vpbe_dev->current_timings.left_margin); + osd_device->ops.set_top_margin(osd_device, + vpbe_dev->current_timings.upper_margin); + } + mutex_unlock(&vpbe_dev->lock); + + return ret; +} + +/* + * vpbe_g_std - Get the standard in the current encoder + * + * Get the standard in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_g_std(struct vpbe_device *vpbe_dev, v4l2_std_id *std_id) +{ + struct vpbe_enc_mode_info *cur_timings = &vpbe_dev->current_timings; + struct vpbe_config *cfg = vpbe_dev->cfg; + int out_index = vpbe_dev->current_out_index; + + if (!(cfg->outputs[out_index].output.capabilities & V4L2_OUT_CAP_STD)) + return -ENODATA; + + if (cur_timings->timings_type & VPBE_ENC_STD) { + *std_id = cur_timings->std_id; + return 0; + } + + return -EINVAL; +} + +/* + * vpbe_set_mode - Set mode in the current encoder using mode info + * + * Use the mode string to decide what timings to set in the encoder + * This is typically useful when fbset command is used to change the current + * timings by specifying a string to indicate the timings. + */ +static int vpbe_set_mode(struct vpbe_device *vpbe_dev, + struct vpbe_enc_mode_info *mode_info) +{ + struct vpbe_enc_mode_info *preset_mode = NULL; + struct vpbe_config *cfg = vpbe_dev->cfg; + struct v4l2_dv_timings dv_timings; + struct osd_state *osd_device; + int out_index = vpbe_dev->current_out_index; + int i; + + if (!mode_info || !mode_info->name) + return -EINVAL; + + for (i = 0; i < cfg->outputs[out_index].num_modes; i++) { + if (!strcmp(mode_info->name, + cfg->outputs[out_index].modes[i].name)) { + preset_mode = &cfg->outputs[out_index].modes[i]; + /* + * it may be one of the 3 timings type. Check and + * invoke right API + */ + if (preset_mode->timings_type & VPBE_ENC_STD) + return vpbe_s_std(vpbe_dev, + preset_mode->std_id); + if (preset_mode->timings_type & + VPBE_ENC_DV_TIMINGS) { + dv_timings = + preset_mode->dv_timings; + return vpbe_s_dv_timings(vpbe_dev, &dv_timings); + } + } + } + + /* Only custom timing should reach here */ + if (!preset_mode) + return -EINVAL; + + mutex_lock(&vpbe_dev->lock); + + osd_device = vpbe_dev->osd_device; + vpbe_dev->current_timings = *preset_mode; + osd_device->ops.set_left_margin(osd_device, + vpbe_dev->current_timings.left_margin); + osd_device->ops.set_top_margin(osd_device, + vpbe_dev->current_timings.upper_margin); + + mutex_unlock(&vpbe_dev->lock); + return 0; +} + +static int vpbe_set_default_mode(struct vpbe_device *vpbe_dev) +{ + int ret; + + ret = vpbe_get_std_info_by_name(vpbe_dev, def_mode); + if (ret) + return ret; + + /* set the default mode in the encoder */ + return vpbe_set_mode(vpbe_dev, &vpbe_dev->current_timings); +} + +static int platform_device_get(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vpbe_device *vpbe_dev = data; + + if (strstr(pdev->name, "vpbe-osd")) + vpbe_dev->osd_device = platform_get_drvdata(pdev); + if (strstr(pdev->name, "vpbe-venc")) + vpbe_dev->venc_device = dev_get_platdata(&pdev->dev); + + return 0; +} + +/** + * vpbe_initialize() - Initialize the vpbe display controller + * @dev: Master and slave device ptr + * @vpbe_dev: vpbe device ptr + * + * Master frame buffer device drivers calls this to initialize vpbe + * display controller. This will then registers v4l2 device and the sub + * devices and sets a current encoder sub device for display. v4l2 display + * device driver is the master and frame buffer display device driver is + * the slave. Frame buffer display driver checks the initialized during + * probe and exit if not initialized. Returns status. + */ +static int vpbe_initialize(struct device *dev, struct vpbe_device *vpbe_dev) +{ + struct encoder_config_info *enc_info; + struct amp_config_info *amp_info; + struct v4l2_subdev **enc_subdev; + struct osd_state *osd_device; + struct i2c_adapter *i2c_adap; + int num_encoders; + int ret = 0; + int err; + int i; + + /* + * v4l2 abd FBDev frame buffer devices will get the vpbe_dev pointer + * from the platform device by iteration of platform drivers and + * matching with device name + */ + if (!vpbe_dev || !dev) { + printk(KERN_ERR "Null device pointers.\n"); + return -ENODEV; + } + + if (vpbe_dev->initialized) + return 0; + + mutex_lock(&vpbe_dev->lock); + + if (strcmp(vpbe_dev->cfg->module_name, "dm644x-vpbe-display") != 0) { + /* We have dac clock available for platform */ + vpbe_dev->dac_clk = clk_get(vpbe_dev->pdev, "vpss_dac"); + if (IS_ERR(vpbe_dev->dac_clk)) { + ret = PTR_ERR(vpbe_dev->dac_clk); + goto fail_mutex_unlock; + } + if (clk_prepare_enable(vpbe_dev->dac_clk)) { + ret = -ENODEV; + clk_put(vpbe_dev->dac_clk); + goto fail_mutex_unlock; + } + } + + /* first enable vpss clocks */ + vpss_enable_clock(VPSS_VPBE_CLOCK, 1); + + /* First register a v4l2 device */ + ret = v4l2_device_register(dev, &vpbe_dev->v4l2_dev); + if (ret) { + v4l2_err(dev->driver, + "Unable to register v4l2 device.\n"); + goto fail_clk_put; + } + v4l2_info(&vpbe_dev->v4l2_dev, "vpbe v4l2 device registered\n"); + + err = bus_for_each_dev(&platform_bus_type, NULL, vpbe_dev, + platform_device_get); + if (err < 0) { + ret = err; + goto fail_dev_unregister; + } + + vpbe_dev->venc = venc_sub_dev_init(&vpbe_dev->v4l2_dev, + vpbe_dev->cfg->venc.module_name); + /* register venc sub device */ + if (!vpbe_dev->venc) { + v4l2_err(&vpbe_dev->v4l2_dev, + "vpbe unable to init venc sub device\n"); + ret = -ENODEV; + goto fail_dev_unregister; + } + /* initialize osd device */ + osd_device = vpbe_dev->osd_device; + if (osd_device->ops.initialize) { + err = osd_device->ops.initialize(osd_device); + if (err) { + v4l2_err(&vpbe_dev->v4l2_dev, + "unable to initialize the OSD device"); + ret = -ENOMEM; + goto fail_dev_unregister; + } + } + + /* + * Register any external encoders that are configured. At index 0 we + * store venc sd index. + */ + num_encoders = vpbe_dev->cfg->num_ext_encoders + 1; + vpbe_dev->encoders = kmalloc_array(num_encoders, + sizeof(*vpbe_dev->encoders), + GFP_KERNEL); + if (!vpbe_dev->encoders) { + ret = -ENOMEM; + goto fail_dev_unregister; + } + + i2c_adap = i2c_get_adapter(vpbe_dev->cfg->i2c_adapter_id); + for (i = 0; i < (vpbe_dev->cfg->num_ext_encoders + 1); i++) { + if (i == 0) { + /* venc is at index 0 */ + enc_subdev = &vpbe_dev->encoders[i]; + *enc_subdev = vpbe_dev->venc; + continue; + } + enc_info = &vpbe_dev->cfg->ext_encoders[i]; + if (enc_info->is_i2c) { + enc_subdev = &vpbe_dev->encoders[i]; + *enc_subdev = v4l2_i2c_new_subdev_board( + &vpbe_dev->v4l2_dev, i2c_adap, + &enc_info->board_info, NULL); + if (*enc_subdev) + v4l2_info(&vpbe_dev->v4l2_dev, + "v4l2 sub device %s registered\n", + enc_info->module_name); + else { + v4l2_err(&vpbe_dev->v4l2_dev, "encoder %s failed to register", + enc_info->module_name); + ret = -ENODEV; + goto fail_kfree_encoders; + } + } else + v4l2_warn(&vpbe_dev->v4l2_dev, "non-i2c encoders currently not supported"); + } + /* Add amplifier subdevice for dm365 */ + if ((strcmp(vpbe_dev->cfg->module_name, "dm365-vpbe-display") == 0) && + vpbe_dev->cfg->amp) { + amp_info = vpbe_dev->cfg->amp; + if (amp_info->is_i2c) { + vpbe_dev->amp = v4l2_i2c_new_subdev_board( + &vpbe_dev->v4l2_dev, i2c_adap, + &_info->board_info, NULL); + if (!vpbe_dev->amp) { + v4l2_err(&vpbe_dev->v4l2_dev, + "amplifier %s failed to register", + amp_info->module_name); + ret = -ENODEV; + goto fail_kfree_encoders; + } + v4l2_info(&vpbe_dev->v4l2_dev, + "v4l2 sub device %s registered\n", + amp_info->module_name); + } else { + vpbe_dev->amp = NULL; + v4l2_warn(&vpbe_dev->v4l2_dev, "non-i2c amplifiers currently not supported"); + } + } else { + vpbe_dev->amp = NULL; + } + + /* set the current encoder and output to that of venc by default */ + vpbe_dev->current_sd_index = 0; + vpbe_dev->current_out_index = 0; + + mutex_unlock(&vpbe_dev->lock); + + printk(KERN_NOTICE "Setting default output to %s\n", def_output); + ret = vpbe_set_default_output(vpbe_dev); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, "Failed to set default output %s", + def_output); + goto fail_kfree_amp; + } + + printk(KERN_NOTICE "Setting default mode to %s\n", def_mode); + ret = vpbe_set_default_mode(vpbe_dev); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, "Failed to set default mode %s", + def_mode); + goto fail_kfree_amp; + } + vpbe_dev->initialized = 1; + /* TBD handling of bootargs for default output and mode */ + return 0; + +fail_kfree_amp: + mutex_lock(&vpbe_dev->lock); + kfree(vpbe_dev->amp); +fail_kfree_encoders: + kfree(vpbe_dev->encoders); +fail_dev_unregister: + v4l2_device_unregister(&vpbe_dev->v4l2_dev); +fail_clk_put: + if (strcmp(vpbe_dev->cfg->module_name, "dm644x-vpbe-display") != 0) { + clk_disable_unprepare(vpbe_dev->dac_clk); + clk_put(vpbe_dev->dac_clk); + } +fail_mutex_unlock: + mutex_unlock(&vpbe_dev->lock); + return ret; +} + +/** + * vpbe_deinitialize() - de-initialize the vpbe display controller + * @dev: Master and slave device ptr + * @vpbe_dev: vpbe device ptr + * + * vpbe_master and slave frame buffer devices calls this to de-initialize + * the display controller. It is called when master and slave device + * driver modules are removed and no longer requires the display controller. + */ +static void vpbe_deinitialize(struct device *dev, struct vpbe_device *vpbe_dev) +{ + v4l2_device_unregister(&vpbe_dev->v4l2_dev); + if (strcmp(vpbe_dev->cfg->module_name, "dm644x-vpbe-display") != 0) { + clk_disable_unprepare(vpbe_dev->dac_clk); + clk_put(vpbe_dev->dac_clk); + } + + kfree(vpbe_dev->amp); + kfree(vpbe_dev->encoders); + vpbe_dev->initialized = 0; + /* disable vpss clocks */ + vpss_enable_clock(VPSS_VPBE_CLOCK, 0); +} + +static const struct vpbe_device_ops vpbe_dev_ops = { + .enum_outputs = vpbe_enum_outputs, + .set_output = vpbe_set_output, + .get_output = vpbe_get_output, + .s_dv_timings = vpbe_s_dv_timings, + .g_dv_timings = vpbe_g_dv_timings, + .enum_dv_timings = vpbe_enum_dv_timings, + .s_std = vpbe_s_std, + .g_std = vpbe_g_std, + .initialize = vpbe_initialize, + .deinitialize = vpbe_deinitialize, + .get_mode_info = vpbe_get_current_mode_info, + .set_mode = vpbe_set_mode, +}; + +static int vpbe_probe(struct platform_device *pdev) +{ + struct vpbe_device *vpbe_dev; + struct vpbe_config *cfg; + + if (!pdev->dev.platform_data) { + v4l2_err(pdev->dev.driver, "No platform data\n"); + return -ENODEV; + } + cfg = pdev->dev.platform_data; + + if (!cfg->module_name[0] || + !cfg->osd.module_name[0] || + !cfg->venc.module_name[0]) { + v4l2_err(pdev->dev.driver, "vpbe display module names not defined\n"); + return -EINVAL; + } + + vpbe_dev = kzalloc(sizeof(*vpbe_dev), GFP_KERNEL); + if (!vpbe_dev) + return -ENOMEM; + + vpbe_dev->cfg = cfg; + vpbe_dev->ops = vpbe_dev_ops; + vpbe_dev->pdev = &pdev->dev; + + if (cfg->outputs->num_modes > 0) + vpbe_dev->current_timings = vpbe_dev->cfg->outputs[0].modes[0]; + else { + kfree(vpbe_dev); + return -ENODEV; + } + + /* set the driver data in platform device */ + platform_set_drvdata(pdev, vpbe_dev); + mutex_init(&vpbe_dev->lock); + + return 0; +} + +static int vpbe_remove(struct platform_device *device) +{ + struct vpbe_device *vpbe_dev = platform_get_drvdata(device); + + kfree(vpbe_dev); + + return 0; +} + +static struct platform_driver vpbe_driver = { + .driver = { + .name = "vpbe_controller", + }, + .probe = vpbe_probe, + .remove = vpbe_remove, +}; + +module_platform_driver(vpbe_driver); diff --git a/drivers/media/platform/ti/davinci/vpbe_display.c b/drivers/media/platform/ti/davinci/vpbe_display.c new file mode 100644 index 000000000..9ea708175 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpbe_display.c @@ -0,0 +1,1510 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 Texas Instruments Incorporated - https://www.ti.com/ + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vpbe_venc_regs.h" + +#define VPBE_DISPLAY_DRIVER "vpbe-v4l2" + +static int debug; + +#define VPBE_DEFAULT_NUM_BUFS 3 + +module_param(debug, int, 0644); + +static int vpbe_set_osd_display_params(struct vpbe_display *disp_dev, + struct vpbe_layer *layer); + +static int venc_is_second_field(struct vpbe_display *disp_dev) +{ + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + int ret, val; + + ret = v4l2_subdev_call(vpbe_dev->venc, + core, + command, + VENC_GET_FLD, + &val); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in getting Field ID 0\n"); + return 1; + } + return val; +} + +static void vpbe_isr_even_field(struct vpbe_display *disp_obj, + struct vpbe_layer *layer) +{ + if (layer->cur_frm == layer->next_frm) + return; + + layer->cur_frm->vb.vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&layer->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE); + /* Make cur_frm pointing to next_frm */ + layer->cur_frm = layer->next_frm; +} + +static void vpbe_isr_odd_field(struct vpbe_display *disp_obj, + struct vpbe_layer *layer) +{ + struct osd_state *osd_device = disp_obj->osd_device; + unsigned long addr; + + spin_lock(&disp_obj->dma_queue_lock); + if (list_empty(&layer->dma_queue) || + (layer->cur_frm != layer->next_frm)) { + spin_unlock(&disp_obj->dma_queue_lock); + return; + } + /* + * one field is displayed configure + * the next frame if it is available + * otherwise hold on current frame + * Get next from the buffer queue + */ + layer->next_frm = list_entry(layer->dma_queue.next, + struct vpbe_disp_buffer, list); + /* Remove that from the buffer queue */ + list_del(&layer->next_frm->list); + spin_unlock(&disp_obj->dma_queue_lock); + /* Mark state of the frame to active */ + layer->next_frm->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + addr = vb2_dma_contig_plane_dma_addr(&layer->next_frm->vb.vb2_buf, 0); + osd_device->ops.start_layer(osd_device, + layer->layer_info.id, + addr, + disp_obj->cbcr_ofst); +} + +/* interrupt service routine */ +static irqreturn_t venc_isr(int irq, void *arg) +{ + struct vpbe_display *disp_dev = (struct vpbe_display *)arg; + struct vpbe_layer *layer; + static unsigned last_event; + unsigned event = 0; + int fid; + int i; + + if (!arg || !disp_dev->dev[0]) + return IRQ_HANDLED; + + if (venc_is_second_field(disp_dev)) + event |= VENC_SECOND_FIELD; + else + event |= VENC_FIRST_FIELD; + + if (event == (last_event & ~VENC_END_OF_FRAME)) { + /* + * If the display is non-interlaced, then we need to flag the + * end-of-frame event at every interrupt regardless of the + * value of the FIDST bit. We can conclude that the display is + * non-interlaced if the value of the FIDST bit is unchanged + * from the previous interrupt. + */ + event |= VENC_END_OF_FRAME; + } else if (event == VENC_SECOND_FIELD) { + /* end-of-frame for interlaced display */ + event |= VENC_END_OF_FRAME; + } + last_event = event; + + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + layer = disp_dev->dev[i]; + + if (!vb2_start_streaming_called(&layer->buffer_queue)) + continue; + + if (layer->layer_first_int) { + layer->layer_first_int = 0; + continue; + } + /* Check the field format */ + if ((V4L2_FIELD_NONE == layer->pix_fmt.field) && + (event & VENC_END_OF_FRAME)) { + /* Progressive mode */ + + vpbe_isr_even_field(disp_dev, layer); + vpbe_isr_odd_field(disp_dev, layer); + } else { + /* Interlaced mode */ + + layer->field_id ^= 1; + if (event & VENC_FIRST_FIELD) + fid = 0; + else + fid = 1; + + /* + * If field id does not match with store + * field id + */ + if (fid != layer->field_id) { + /* Make them in sync */ + layer->field_id = fid; + continue; + } + /* + * device field id and local field id are + * in sync. If this is even field + */ + if (0 == fid) + vpbe_isr_even_field(disp_dev, layer); + else /* odd field */ + vpbe_isr_odd_field(disp_dev, layer); + } + } + + return IRQ_HANDLED; +} + +/* + * vpbe_buffer_prepare() + * This is the callback function called from vb2_qbuf() function + * the buffer is prepared and user space virtual address is converted into + * physical address + */ +static int vpbe_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + struct vpbe_layer *layer = vb2_get_drv_priv(q); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + unsigned long addr; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "vpbe_buffer_prepare\n"); + + vb2_set_plane_payload(vb, 0, layer->pix_fmt.sizeimage); + if (vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) + return -EINVAL; + + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (!IS_ALIGNED(addr, 8)) { + v4l2_err(&vpbe_dev->v4l2_dev, + "buffer_prepare:offset is not aligned to 32 bytes\n"); + return -EINVAL; + } + return 0; +} + +/* + * vpbe_buffer_setup() + * This function allocates memory for the buffers + */ +static int +vpbe_buffer_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) + +{ + /* Get the file handle object and layer object */ + struct vpbe_layer *layer = vb2_get_drv_priv(vq); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_buffer_setup\n"); + + /* Store number of buffers allocated in numbuffer member */ + if (vq->num_buffers + *nbuffers < VPBE_DEFAULT_NUM_BUFS) + *nbuffers = VPBE_DEFAULT_NUM_BUFS - vq->num_buffers; + + if (*nplanes) + return sizes[0] < layer->pix_fmt.sizeimage ? -EINVAL : 0; + + *nplanes = 1; + sizes[0] = layer->pix_fmt.sizeimage; + + return 0; +} + +/* + * vpbe_buffer_queue() + * This function adds the buffer to DMA queue + */ +static void vpbe_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + /* Get the file handle object and layer object */ + struct vpbe_disp_buffer *buf = container_of(vbuf, + struct vpbe_disp_buffer, vb); + struct vpbe_layer *layer = vb2_get_drv_priv(vb->vb2_queue); + struct vpbe_display *disp = layer->disp_dev; + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + unsigned long flags; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "vpbe_buffer_queue\n"); + + /* add the buffer to the DMA queue */ + spin_lock_irqsave(&disp->dma_queue_lock, flags); + list_add_tail(&buf->list, &layer->dma_queue); + spin_unlock_irqrestore(&disp->dma_queue_lock, flags); +} + +static int vpbe_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vpbe_layer *layer = vb2_get_drv_priv(vq); + struct osd_state *osd_device = layer->disp_dev->osd_device; + int ret; + + osd_device->ops.disable_layer(osd_device, layer->layer_info.id); + + /* Get the next frame from the buffer queue */ + layer->next_frm = layer->cur_frm = list_entry(layer->dma_queue.next, + struct vpbe_disp_buffer, list); + /* Remove buffer from the buffer queue */ + list_del(&layer->cur_frm->list); + /* Mark state of the current frame to active */ + layer->cur_frm->vb.vb2_buf.state = VB2_BUF_STATE_ACTIVE; + /* Initialize field_id and started member */ + layer->field_id = 0; + + /* Set parameters in OSD and VENC */ + ret = vpbe_set_osd_display_params(layer->disp_dev, layer); + if (ret < 0) { + struct vpbe_disp_buffer *buf, *tmp; + + vb2_buffer_done(&layer->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_QUEUED); + list_for_each_entry_safe(buf, tmp, &layer->dma_queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, + VB2_BUF_STATE_QUEUED); + } + + return ret; + } + + /* + * if request format is yuv420 semiplanar, need to + * enable both video windows + */ + layer->layer_first_int = 1; + + return ret; +} + +static void vpbe_stop_streaming(struct vb2_queue *vq) +{ + struct vpbe_layer *layer = vb2_get_drv_priv(vq); + struct osd_state *osd_device = layer->disp_dev->osd_device; + struct vpbe_display *disp = layer->disp_dev; + unsigned long flags; + + if (!vb2_is_streaming(vq)) + return; + + osd_device->ops.disable_layer(osd_device, layer->layer_info.id); + + /* release all active buffers */ + spin_lock_irqsave(&disp->dma_queue_lock, flags); + if (layer->cur_frm == layer->next_frm) { + vb2_buffer_done(&layer->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } else { + if (layer->cur_frm) + vb2_buffer_done(&layer->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + if (layer->next_frm) + vb2_buffer_done(&layer->next_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } + + while (!list_empty(&layer->dma_queue)) { + layer->next_frm = list_entry(layer->dma_queue.next, + struct vpbe_disp_buffer, list); + list_del(&layer->next_frm->list); + vb2_buffer_done(&layer->next_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&disp->dma_queue_lock, flags); +} + +static const struct vb2_ops video_qops = { + .queue_setup = vpbe_buffer_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = vpbe_buffer_prepare, + .start_streaming = vpbe_start_streaming, + .stop_streaming = vpbe_stop_streaming, + .buf_queue = vpbe_buffer_queue, +}; + +static +struct vpbe_layer* +_vpbe_display_get_other_win_layer(struct vpbe_display *disp_dev, + struct vpbe_layer *layer) +{ + enum vpbe_display_device_id thiswin, otherwin; + thiswin = layer->device_id; + + otherwin = (thiswin == VPBE_DISPLAY_DEVICE_0) ? + VPBE_DISPLAY_DEVICE_1 : VPBE_DISPLAY_DEVICE_0; + return disp_dev->dev[otherwin]; +} + +static int vpbe_set_osd_display_params(struct vpbe_display *disp_dev, + struct vpbe_layer *layer) +{ + struct osd_layer_config *cfg = &layer->layer_info.config; + struct osd_state *osd_device = disp_dev->osd_device; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + unsigned long addr; + int ret; + + addr = vb2_dma_contig_plane_dma_addr(&layer->cur_frm->vb.vb2_buf, 0); + /* Set address in the display registers */ + osd_device->ops.start_layer(osd_device, + layer->layer_info.id, + addr, + disp_dev->cbcr_ofst); + + ret = osd_device->ops.enable_layer(osd_device, + layer->layer_info.id, 0); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in enabling osd window layer 0\n"); + return -1; + } + + /* Enable the window */ + layer->layer_info.enable = 1; + if (cfg->pixfmt == PIXFMT_NV12) { + struct vpbe_layer *otherlayer = + _vpbe_display_get_other_win_layer(disp_dev, layer); + + ret = osd_device->ops.enable_layer(osd_device, + otherlayer->layer_info.id, 1); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in enabling osd window layer 1\n"); + return -1; + } + otherlayer->layer_info.enable = 1; + } + return 0; +} + +static void +vpbe_disp_calculate_scale_factor(struct vpbe_display *disp_dev, + struct vpbe_layer *layer, + int expected_xsize, int expected_ysize) +{ + struct display_layer_info *layer_info = &layer->layer_info; + struct v4l2_pix_format *pixfmt = &layer->pix_fmt; + struct osd_layer_config *cfg = &layer->layer_info.config; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + int calculated_xsize; + int h_exp = 0; + int v_exp = 0; + int h_scale; + int v_scale; + + v4l2_std_id standard_id = vpbe_dev->current_timings.std_id; + + /* + * Application initially set the image format. Current display + * size is obtained from the vpbe display controller. expected_xsize + * and expected_ysize are set through S_SELECTION ioctl. Based on this, + * driver will calculate the scale factors for vertical and + * horizontal direction so that the image is displayed scaled + * and expanded. Application uses expansion to display the image + * in a square pixel. Otherwise it is displayed using displays + * pixel aspect ratio.It is expected that application chooses + * the crop coordinates for cropped or scaled display. if crop + * size is less than the image size, it is displayed cropped or + * it is displayed scaled and/or expanded. + * + * to begin with, set the crop window same as expected. Later we + * will override with scaled window size + */ + + cfg->xsize = pixfmt->width; + cfg->ysize = pixfmt->height; + layer_info->h_zoom = ZOOM_X1; /* no horizontal zoom */ + layer_info->v_zoom = ZOOM_X1; /* no horizontal zoom */ + layer_info->h_exp = H_EXP_OFF; /* no horizontal zoom */ + layer_info->v_exp = V_EXP_OFF; /* no horizontal zoom */ + + if (pixfmt->width < expected_xsize) { + h_scale = vpbe_dev->current_timings.xres / pixfmt->width; + if (h_scale < 2) + h_scale = 1; + else if (h_scale >= 4) + h_scale = 4; + else + h_scale = 2; + cfg->xsize *= h_scale; + if (cfg->xsize < expected_xsize) { + if ((standard_id & V4L2_STD_525_60) || + (standard_id & V4L2_STD_625_50)) { + calculated_xsize = (cfg->xsize * + VPBE_DISPLAY_H_EXP_RATIO_N) / + VPBE_DISPLAY_H_EXP_RATIO_D; + if (calculated_xsize <= expected_xsize) { + h_exp = 1; + cfg->xsize = calculated_xsize; + } + } + } + if (h_scale == 2) + layer_info->h_zoom = ZOOM_X2; + else if (h_scale == 4) + layer_info->h_zoom = ZOOM_X4; + if (h_exp) + layer_info->h_exp = H_EXP_9_OVER_8; + } else { + /* no scaling, only cropping. Set display area to crop area */ + cfg->xsize = expected_xsize; + } + + if (pixfmt->height < expected_ysize) { + v_scale = expected_ysize / pixfmt->height; + if (v_scale < 2) + v_scale = 1; + else if (v_scale >= 4) + v_scale = 4; + else + v_scale = 2; + cfg->ysize *= v_scale; + if (cfg->ysize < expected_ysize) { + if ((standard_id & V4L2_STD_625_50)) { + calculated_xsize = (cfg->ysize * + VPBE_DISPLAY_V_EXP_RATIO_N) / + VPBE_DISPLAY_V_EXP_RATIO_D; + if (calculated_xsize <= expected_ysize) { + v_exp = 1; + cfg->ysize = calculated_xsize; + } + } + } + if (v_scale == 2) + layer_info->v_zoom = ZOOM_X2; + else if (v_scale == 4) + layer_info->v_zoom = ZOOM_X4; + if (v_exp) + layer_info->v_exp = V_EXP_6_OVER_5; + } else { + /* no scaling, only cropping. Set display area to crop area */ + cfg->ysize = expected_ysize; + } + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "crop display xsize = %d, ysize = %d\n", + cfg->xsize, cfg->ysize); +} + +static void vpbe_disp_adj_position(struct vpbe_display *disp_dev, + struct vpbe_layer *layer, + int top, int left) +{ + struct osd_layer_config *cfg = &layer->layer_info.config; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + + cfg->xpos = min((unsigned int)left, + vpbe_dev->current_timings.xres - cfg->xsize); + cfg->ypos = min((unsigned int)top, + vpbe_dev->current_timings.yres - cfg->ysize); + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "new xpos = %d, ypos = %d\n", + cfg->xpos, cfg->ypos); +} + +static void vpbe_disp_check_window_params(struct vpbe_display *disp_dev, + struct v4l2_rect *c) +{ + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + + if ((c->width == 0) || + ((c->width + c->left) > vpbe_dev->current_timings.xres)) + c->width = vpbe_dev->current_timings.xres - c->left; + + if ((c->height == 0) || ((c->height + c->top) > + vpbe_dev->current_timings.yres)) + c->height = vpbe_dev->current_timings.yres - c->top; + + /* window height must be even for interlaced display */ + if (vpbe_dev->current_timings.interlaced) + c->height &= (~0x01); + +} + +/* + * vpbe_try_format() + * If user application provides width and height, and have bytesperline set + * to zero, driver calculates bytesperline and sizeimage based on hardware + * limits. + */ +static int vpbe_try_format(struct vpbe_display *disp_dev, + struct v4l2_pix_format *pixfmt, int check) +{ + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + int min_height = 1; + int min_width = 32; + int max_height; + int max_width; + int bpp; + + if ((pixfmt->pixelformat != V4L2_PIX_FMT_UYVY) && + (pixfmt->pixelformat != V4L2_PIX_FMT_NV12)) + /* choose default as V4L2_PIX_FMT_UYVY */ + pixfmt->pixelformat = V4L2_PIX_FMT_UYVY; + + /* Check the field format */ + if ((pixfmt->field != V4L2_FIELD_INTERLACED) && + (pixfmt->field != V4L2_FIELD_NONE)) { + if (vpbe_dev->current_timings.interlaced) + pixfmt->field = V4L2_FIELD_INTERLACED; + else + pixfmt->field = V4L2_FIELD_NONE; + } + + if (pixfmt->field == V4L2_FIELD_INTERLACED) + min_height = 2; + + if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) + bpp = 1; + else + bpp = 2; + + max_width = vpbe_dev->current_timings.xres; + max_height = vpbe_dev->current_timings.yres; + + min_width /= bpp; + + if (!pixfmt->width || (pixfmt->width < min_width) || + (pixfmt->width > max_width)) { + pixfmt->width = vpbe_dev->current_timings.xres; + } + + if (!pixfmt->height || (pixfmt->height < min_height) || + (pixfmt->height > max_height)) { + pixfmt->height = vpbe_dev->current_timings.yres; + } + + if (pixfmt->bytesperline < (pixfmt->width * bpp)) + pixfmt->bytesperline = pixfmt->width * bpp; + + /* Make the bytesperline 32 byte aligned */ + pixfmt->bytesperline = ((pixfmt->width * bpp + 31) & ~31); + + if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height + + (pixfmt->bytesperline * pixfmt->height >> 1); + else + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + + return 0; +} + +static int vpbe_display_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + + snprintf(cap->driver, sizeof(cap->driver), "%s", + dev_name(vpbe_dev->pdev)); + strscpy(cap->card, vpbe_dev->cfg->module_name, sizeof(cap->card)); + + return 0; +} + +static int vpbe_display_s_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_display *disp_dev = layer->disp_dev; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct osd_layer_config *cfg = &layer->layer_info.config; + struct osd_state *osd_device = disp_dev->osd_device; + struct v4l2_rect rect = sel->r; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_S_SELECTION, layer id = %d\n", layer->device_id); + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + if (rect.top < 0) + rect.top = 0; + if (rect.left < 0) + rect.left = 0; + + vpbe_disp_check_window_params(disp_dev, &rect); + + osd_device->ops.get_layer_config(osd_device, + layer->layer_info.id, cfg); + + vpbe_disp_calculate_scale_factor(disp_dev, layer, + rect.width, + rect.height); + vpbe_disp_adj_position(disp_dev, layer, rect.top, + rect.left); + ret = osd_device->ops.set_layer_config(osd_device, + layer->layer_info.id, cfg); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in set layer config:\n"); + return -EINVAL; + } + + /* apply zooming and h or v expansion */ + osd_device->ops.set_zoom(osd_device, + layer->layer_info.id, + layer->layer_info.h_zoom, + layer->layer_info.v_zoom); + ret = osd_device->ops.set_vid_expansion(osd_device, + layer->layer_info.h_exp, + layer->layer_info.v_exp); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in set vid expansion:\n"); + return -EINVAL; + } + + if ((layer->layer_info.h_zoom != ZOOM_X1) || + (layer->layer_info.v_zoom != ZOOM_X1) || + (layer->layer_info.h_exp != H_EXP_OFF) || + (layer->layer_info.v_exp != V_EXP_OFF)) + /* Enable expansion filter */ + osd_device->ops.set_interpolation_filter(osd_device, 1); + else + osd_device->ops.set_interpolation_filter(osd_device, 0); + + sel->r = rect; + return 0; +} + +static int vpbe_display_g_selection(struct file *file, void *priv, + struct v4l2_selection *sel) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct osd_layer_config *cfg = &layer->layer_info.config; + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + struct osd_state *osd_device = layer->disp_dev->osd_device; + struct v4l2_rect *rect = &sel->r; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_G_SELECTION, layer id = %d\n", + layer->device_id); + + if (sel->type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + osd_device->ops.get_layer_config(osd_device, + layer->layer_info.id, cfg); + rect->top = cfg->ypos; + rect->left = cfg->xpos; + rect->width = cfg->xsize; + rect->height = cfg->ysize; + break; + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + rect->left = 0; + rect->top = 0; + rect->width = vpbe_dev->current_timings.xres; + rect->height = vpbe_dev->current_timings.yres; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int vpbe_display_g_pixelaspect(struct file *file, void *priv, + int type, struct v4l2_fract *f) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_CROPCAP ioctl\n"); + + if (type != V4L2_BUF_TYPE_VIDEO_OUTPUT) + return -EINVAL; + + *f = vpbe_dev->current_timings.aspect; + return 0; +} + +static int vpbe_display_g_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_G_FMT, layer id = %d\n", + layer->device_id); + + /* If buffer type is video output */ + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { + v4l2_err(&vpbe_dev->v4l2_dev, "invalid type\n"); + return -EINVAL; + } + /* Fill in the information about format */ + fmt->fmt.pix = layer->pix_fmt; + + return 0; +} + +static int vpbe_display_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_ENUM_FMT, layer id = %d\n", + layer->device_id); + if (fmt->index > 1) { + v4l2_err(&vpbe_dev->v4l2_dev, "Invalid format index\n"); + return -EINVAL; + } + + /* Fill in the information about format */ + if (fmt->index == 0) + fmt->pixelformat = V4L2_PIX_FMT_UYVY; + else + fmt->pixelformat = V4L2_PIX_FMT_NV12; + + return 0; +} + +static int vpbe_display_s_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_display *disp_dev = layer->disp_dev; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct osd_layer_config *cfg = &layer->layer_info.config; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + struct osd_state *osd_device = disp_dev->osd_device; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "VIDIOC_S_FMT, layer id = %d\n", + layer->device_id); + + if (vb2_is_busy(&layer->buffer_queue)) + return -EBUSY; + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "invalid type\n"); + return -EINVAL; + } + /* Check for valid pixel format */ + ret = vpbe_try_format(disp_dev, pixfmt, 1); + if (ret) + return ret; + + /* YUV420 is requested, check availability of the + other video window */ + + layer->pix_fmt = *pixfmt; + if (pixfmt->pixelformat == V4L2_PIX_FMT_NV12) { + struct vpbe_layer *otherlayer; + + otherlayer = _vpbe_display_get_other_win_layer(disp_dev, layer); + /* if other layer is available, only + * claim it, do not configure it + */ + ret = osd_device->ops.request_layer(osd_device, + otherlayer->layer_info.id); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Display Manager failed to allocate layer\n"); + return -EBUSY; + } + } + + /* Get osd layer config */ + osd_device->ops.get_layer_config(osd_device, + layer->layer_info.id, cfg); + /* Store the pixel format in the layer object */ + cfg->xsize = pixfmt->width; + cfg->ysize = pixfmt->height; + cfg->line_length = pixfmt->bytesperline; + cfg->ypos = 0; + cfg->xpos = 0; + cfg->interlaced = vpbe_dev->current_timings.interlaced; + + if (V4L2_PIX_FMT_UYVY == pixfmt->pixelformat) + cfg->pixfmt = PIXFMT_YCBCRI; + + /* Change of the default pixel format for both video windows */ + if (V4L2_PIX_FMT_NV12 == pixfmt->pixelformat) { + struct vpbe_layer *otherlayer; + cfg->pixfmt = PIXFMT_NV12; + otherlayer = _vpbe_display_get_other_win_layer(disp_dev, + layer); + otherlayer->layer_info.config.pixfmt = PIXFMT_NV12; + } + + /* Set the layer config in the osd window */ + ret = osd_device->ops.set_layer_config(osd_device, + layer->layer_info.id, cfg); + if (ret < 0) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Error in S_FMT params:\n"); + return -EINVAL; + } + + /* Readback and fill the local copy of current pix format */ + osd_device->ops.get_layer_config(osd_device, + layer->layer_info.id, cfg); + + return 0; +} + +static int vpbe_display_try_fmt(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_display *disp_dev = layer->disp_dev; + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_TRY_FMT\n"); + + if (V4L2_BUF_TYPE_VIDEO_OUTPUT != fmt->type) { + v4l2_err(&vpbe_dev->v4l2_dev, "invalid type\n"); + return -EINVAL; + } + + /* Check for valid field format */ + return vpbe_try_format(disp_dev, pixfmt, 0); + +} + +/* + * vpbe_display_s_std - Set the given standard in the encoder + * + * Sets the standard if supported by the current encoder. Return the status. + * 0 - success & -EINVAL on error + */ +static int vpbe_display_s_std(struct file *file, void *priv, + v4l2_std_id std_id) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_STD\n"); + + if (vb2_is_busy(&layer->buffer_queue)) + return -EBUSY; + + if (vpbe_dev->ops.s_std) { + ret = vpbe_dev->ops.s_std(vpbe_dev, std_id); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Failed to set standard for sub devices\n"); + return -EINVAL; + } + } else { + return -EINVAL; + } + + return 0; +} + +/* + * vpbe_display_g_std - Get the standard in the current encoder + * + * Get the standard in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int vpbe_display_g_std(struct file *file, void *priv, + v4l2_std_id *std_id) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_STD\n"); + + /* Get the standard from the current encoder */ + if (vpbe_dev->current_timings.timings_type & VPBE_ENC_STD) { + *std_id = vpbe_dev->current_timings.std_id; + return 0; + } + + return -EINVAL; +} + +/* + * vpbe_display_enum_output - enumerate outputs + * + * Enumerates the outputs available at the vpbe display + * returns the status, -EINVAL if end of output list + */ +static int vpbe_display_enum_output(struct file *file, void *priv, + struct v4l2_output *output) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_ENUM_OUTPUT\n"); + + /* Enumerate outputs */ + if (!vpbe_dev->ops.enum_outputs) + return -EINVAL; + + ret = vpbe_dev->ops.enum_outputs(vpbe_dev, output); + if (ret) { + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "Failed to enumerate outputs\n"); + return -EINVAL; + } + + return 0; +} + +/* + * vpbe_display_s_output - Set output to + * the output specified by the index + */ +static int vpbe_display_s_output(struct file *file, void *priv, + unsigned int i) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_OUTPUT\n"); + + if (vb2_is_busy(&layer->buffer_queue)) + return -EBUSY; + + if (!vpbe_dev->ops.set_output) + return -EINVAL; + + ret = vpbe_dev->ops.set_output(vpbe_dev, i); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Failed to set output for sub devices\n"); + return -EINVAL; + } + + return 0; +} + +/* + * vpbe_display_g_output - Get output from subdevice + * for a given by the index + */ +static int vpbe_display_g_output(struct file *file, void *priv, + unsigned int *i) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_OUTPUT\n"); + /* Get the standard from the current encoder */ + *i = vpbe_dev->current_out_index; + + return 0; +} + +/* + * vpbe_display_enum_dv_timings - Enumerate the dv timings + * + * enum the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_ENUM_DV_TIMINGS\n"); + + /* Enumerate outputs */ + if (!vpbe_dev->ops.enum_dv_timings) + return -EINVAL; + + ret = vpbe_dev->ops.enum_dv_timings(vpbe_dev, timings); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Failed to enumerate dv timings info\n"); + return -EINVAL; + } + + return 0; +} + +/* + * vpbe_display_s_dv_timings - Set the dv timings + * + * Set the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_s_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + int ret; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_S_DV_TIMINGS\n"); + + if (vb2_is_busy(&layer->buffer_queue)) + return -EBUSY; + + /* Set the given standard in the encoder */ + if (!vpbe_dev->ops.s_dv_timings) + return -EINVAL; + + ret = vpbe_dev->ops.s_dv_timings(vpbe_dev, timings); + if (ret) { + v4l2_err(&vpbe_dev->v4l2_dev, + "Failed to set the dv timings info\n"); + return -EINVAL; + } + + return 0; +} + +/* + * vpbe_display_g_dv_timings - Set the dv timings + * + * Get the timings in the current encoder. Return the status. 0 - success + * -EINVAL on error + */ +static int +vpbe_display_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *dv_timings) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_device *vpbe_dev = layer->disp_dev->vpbe_dev; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "VIDIOC_G_DV_TIMINGS\n"); + + /* Get the given standard in the encoder */ + + if (vpbe_dev->current_timings.timings_type & + VPBE_ENC_DV_TIMINGS) { + *dv_timings = vpbe_dev->current_timings.dv_timings; + } else { + return -EINVAL; + } + + return 0; +} + +/* + * vpbe_display_open() + * It creates object of file handle structure and stores it in private_data + * member of filepointer + */ +static int vpbe_display_open(struct file *file) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct vpbe_display *disp_dev = layer->disp_dev; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct osd_state *osd_device = disp_dev->osd_device; + int err; + + /* creating context for file descriptor */ + err = v4l2_fh_open(file); + if (err) { + v4l2_err(&vpbe_dev->v4l2_dev, "v4l2_fh_open failed\n"); + return err; + } + + /* leaving if layer is already initialized */ + if (!v4l2_fh_is_singular_file(file)) + return err; + + if (!layer->usrs) { + if (mutex_lock_interruptible(&layer->opslock)) + return -ERESTARTSYS; + /* First claim the layer for this device */ + err = osd_device->ops.request_layer(osd_device, + layer->layer_info.id); + mutex_unlock(&layer->opslock); + if (err < 0) { + /* Couldn't get layer */ + v4l2_err(&vpbe_dev->v4l2_dev, + "Display Manager failed to allocate layer\n"); + v4l2_fh_release(file); + return -EINVAL; + } + } + /* Increment layer usrs counter */ + layer->usrs++; + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, + "vpbe display device opened successfully\n"); + return 0; +} + +/* + * vpbe_display_release() + * This function deletes buffer queue, frees the buffers and the davinci + * display file * handle + */ +static int vpbe_display_release(struct file *file) +{ + struct vpbe_layer *layer = video_drvdata(file); + struct osd_layer_config *cfg = &layer->layer_info.config; + struct vpbe_display *disp_dev = layer->disp_dev; + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + struct osd_state *osd_device = disp_dev->osd_device; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_release\n"); + + mutex_lock(&layer->opslock); + + osd_device->ops.disable_layer(osd_device, + layer->layer_info.id); + /* Decrement layer usrs counter */ + layer->usrs--; + /* If this file handle has initialize encoder device, reset it */ + if (!layer->usrs) { + if (cfg->pixfmt == PIXFMT_NV12) { + struct vpbe_layer *otherlayer; + otherlayer = + _vpbe_display_get_other_win_layer(disp_dev, layer); + osd_device->ops.disable_layer(osd_device, + otherlayer->layer_info.id); + osd_device->ops.release_layer(osd_device, + otherlayer->layer_info.id); + } + osd_device->ops.disable_layer(osd_device, + layer->layer_info.id); + osd_device->ops.release_layer(osd_device, + layer->layer_info.id); + } + + _vb2_fop_release(file, NULL); + mutex_unlock(&layer->opslock); + + disp_dev->cbcr_ofst = 0; + + return 0; +} + +/* vpbe capture ioctl operations */ +static const struct v4l2_ioctl_ops vpbe_ioctl_ops = { + .vidioc_querycap = vpbe_display_querycap, + .vidioc_g_fmt_vid_out = vpbe_display_g_fmt, + .vidioc_enum_fmt_vid_out = vpbe_display_enum_fmt, + .vidioc_s_fmt_vid_out = vpbe_display_s_fmt, + .vidioc_try_fmt_vid_out = vpbe_display_try_fmt, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, + + .vidioc_g_pixelaspect = vpbe_display_g_pixelaspect, + .vidioc_g_selection = vpbe_display_g_selection, + .vidioc_s_selection = vpbe_display_s_selection, + + .vidioc_s_std = vpbe_display_s_std, + .vidioc_g_std = vpbe_display_g_std, + + .vidioc_enum_output = vpbe_display_enum_output, + .vidioc_s_output = vpbe_display_s_output, + .vidioc_g_output = vpbe_display_g_output, + + .vidioc_s_dv_timings = vpbe_display_s_dv_timings, + .vidioc_g_dv_timings = vpbe_display_g_dv_timings, + .vidioc_enum_dv_timings = vpbe_display_enum_dv_timings, +}; + +static const struct v4l2_file_operations vpbe_fops = { + .owner = THIS_MODULE, + .open = vpbe_display_open, + .release = vpbe_display_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, +}; + +static int vpbe_device_get(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct vpbe_display *vpbe_disp = data; + + if (strcmp("vpbe_controller", pdev->name) == 0) + vpbe_disp->vpbe_dev = platform_get_drvdata(pdev); + + if (strstr(pdev->name, "vpbe-osd")) + vpbe_disp->osd_device = platform_get_drvdata(pdev); + + return 0; +} + +static int init_vpbe_layer(int i, struct vpbe_display *disp_dev, + struct platform_device *pdev) +{ + struct vpbe_layer *vpbe_display_layer = NULL; + struct video_device *vbd = NULL; + + /* Allocate memory for four plane display objects */ + disp_dev->dev[i] = kzalloc(sizeof(*disp_dev->dev[i]), GFP_KERNEL); + if (!disp_dev->dev[i]) + return -ENOMEM; + + spin_lock_init(&disp_dev->dev[i]->irqlock); + mutex_init(&disp_dev->dev[i]->opslock); + + /* Get the pointer to the layer object */ + vpbe_display_layer = disp_dev->dev[i]; + vbd = &vpbe_display_layer->video_dev; + /* Initialize field of video device */ + vbd->release = video_device_release_empty; + vbd->fops = &vpbe_fops; + vbd->ioctl_ops = &vpbe_ioctl_ops; + vbd->minor = -1; + vbd->v4l2_dev = &disp_dev->vpbe_dev->v4l2_dev; + vbd->lock = &vpbe_display_layer->opslock; + vbd->vfl_dir = VFL_DIR_TX; + vbd->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + + if (disp_dev->vpbe_dev->current_timings.timings_type & + VPBE_ENC_STD) + vbd->tvnorms = (V4L2_STD_525_60 | V4L2_STD_625_50); + + snprintf(vbd->name, sizeof(vbd->name), + "DaVinci_VPBE Display_DRIVER_V%d.%d.%d", + (VPBE_DISPLAY_VERSION_CODE >> 16) & 0xff, + (VPBE_DISPLAY_VERSION_CODE >> 8) & 0xff, + (VPBE_DISPLAY_VERSION_CODE) & 0xff); + + vpbe_display_layer->device_id = i; + + vpbe_display_layer->layer_info.id = + ((i == VPBE_DISPLAY_DEVICE_0) ? WIN_VID0 : WIN_VID1); + + + return 0; +} + +static int register_device(struct vpbe_layer *vpbe_display_layer, + struct vpbe_display *disp_dev, + struct platform_device *pdev) +{ + int err; + + v4l2_info(&disp_dev->vpbe_dev->v4l2_dev, + "Trying to register VPBE display device.\n"); + v4l2_info(&disp_dev->vpbe_dev->v4l2_dev, + "layer=%p,layer->video_dev=%p\n", + vpbe_display_layer, + &vpbe_display_layer->video_dev); + + vpbe_display_layer->video_dev.queue = &vpbe_display_layer->buffer_queue; + err = video_register_device(&vpbe_display_layer->video_dev, + VFL_TYPE_VIDEO, + -1); + if (err) + return -ENODEV; + + vpbe_display_layer->disp_dev = disp_dev; + /* set the driver data in platform device */ + platform_set_drvdata(pdev, disp_dev); + video_set_drvdata(&vpbe_display_layer->video_dev, + vpbe_display_layer); + + return 0; +} + + + +/* + * vpbe_display_probe() + * This function creates device entries by register itself to the V4L2 driver + * and initializes fields of each layer objects + */ +static int vpbe_display_probe(struct platform_device *pdev) +{ + struct vpbe_display *disp_dev; + struct v4l2_device *v4l2_dev; + struct resource *res = NULL; + struct vb2_queue *q; + int k; + int i; + int err; + int irq; + + printk(KERN_DEBUG "vpbe_display_probe\n"); + /* Allocate memory for vpbe_display */ + disp_dev = devm_kzalloc(&pdev->dev, sizeof(*disp_dev), GFP_KERNEL); + if (!disp_dev) + return -ENOMEM; + + spin_lock_init(&disp_dev->dma_queue_lock); + /* + * Scan all the platform devices to find the vpbe + * controller device and get the vpbe_dev object + */ + err = bus_for_each_dev(&platform_bus_type, NULL, disp_dev, + vpbe_device_get); + if (err < 0) + return err; + + v4l2_dev = &disp_dev->vpbe_dev->v4l2_dev; + /* Initialize the vpbe display controller */ + if (disp_dev->vpbe_dev->ops.initialize) { + err = disp_dev->vpbe_dev->ops.initialize(&pdev->dev, + disp_dev->vpbe_dev); + if (err) { + v4l2_err(v4l2_dev, "Error initing vpbe\n"); + err = -ENOMEM; + goto probe_out; + } + } + + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + if (init_vpbe_layer(i, disp_dev, pdev)) { + err = -ENODEV; + goto probe_out; + } + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + v4l2_err(v4l2_dev, "Unable to get VENC interrupt resource\n"); + err = -ENODEV; + goto probe_out; + } + + irq = res->start; + err = devm_request_irq(&pdev->dev, irq, venc_isr, 0, + VPBE_DISPLAY_DRIVER, disp_dev); + if (err) { + v4l2_err(v4l2_dev, "VPBE IRQ request failed\n"); + goto probe_out; + } + + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + /* initialize vb2 queue */ + q = &disp_dev->dev[i]->buffer_queue; + memset(q, 0, sizeof(*q)); + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = disp_dev->dev[i]; + q->ops = &video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct vpbe_disp_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 1; + q->lock = &disp_dev->dev[i]->opslock; + q->dev = disp_dev->vpbe_dev->pdev; + err = vb2_queue_init(q); + if (err) { + v4l2_err(v4l2_dev, "vb2_queue_init() failed\n"); + goto probe_out; + } + + INIT_LIST_HEAD(&disp_dev->dev[i]->dma_queue); + + if (register_device(disp_dev->dev[i], disp_dev, pdev)) { + err = -ENODEV; + goto probe_out; + } + } + + v4l2_dbg(1, debug, v4l2_dev, + "Successfully completed the probing of vpbe v4l2 device\n"); + + return 0; + +probe_out: + for (k = 0; k < VPBE_DISPLAY_MAX_DEVICES; k++) { + /* Unregister video device */ + if (disp_dev->dev[k]) { + video_unregister_device(&disp_dev->dev[k]->video_dev); + kfree(disp_dev->dev[k]); + } + } + return err; +} + +/* + * vpbe_display_remove() + * It un-register hardware layer from V4L2 driver + */ +static int vpbe_display_remove(struct platform_device *pdev) +{ + struct vpbe_layer *vpbe_display_layer; + struct vpbe_display *disp_dev = platform_get_drvdata(pdev); + struct vpbe_device *vpbe_dev = disp_dev->vpbe_dev; + int i; + + v4l2_dbg(1, debug, &vpbe_dev->v4l2_dev, "vpbe_display_remove\n"); + + /* deinitialize the vpbe display controller */ + if (vpbe_dev->ops.deinitialize) + vpbe_dev->ops.deinitialize(&pdev->dev, vpbe_dev); + /* un-register device */ + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + /* Get the pointer to the layer object */ + vpbe_display_layer = disp_dev->dev[i]; + /* Unregister video device */ + video_unregister_device(&vpbe_display_layer->video_dev); + + } + for (i = 0; i < VPBE_DISPLAY_MAX_DEVICES; i++) { + kfree(disp_dev->dev[i]); + disp_dev->dev[i] = NULL; + } + + return 0; +} + +static struct platform_driver vpbe_display_driver = { + .driver = { + .name = VPBE_DISPLAY_DRIVER, + .bus = &platform_bus_type, + }, + .probe = vpbe_display_probe, + .remove = vpbe_display_remove, +}; + +module_platform_driver(vpbe_display_driver); + +MODULE_DESCRIPTION("TI DM644x/DM355/DM365 VPBE Display controller"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Texas Instruments"); diff --git a/drivers/media/platform/ti/davinci/vpbe_osd.c b/drivers/media/platform/ti/davinci/vpbe_osd.c new file mode 100644 index 000000000..32f7ef547 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpbe_osd.c @@ -0,0 +1,1582 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2007-2010 Texas Instruments Inc + * Copyright (C) 2007 MontaVista Software, Inc. + * + * Andy Lowe (alowe@mvista.com), MontaVista Software + * - Initial version + * Murali Karicheri (mkaricheri@gmail.com), Texas Instruments Ltd. + * - ported to sub device interface + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "vpbe_osd_regs.h" + +#define MODULE_NAME "davinci-vpbe-osd" + +static const struct platform_device_id vpbe_osd_devtype[] = { + { + .name = DM644X_VPBE_OSD_SUBDEV_NAME, + .driver_data = VPBE_VERSION_1, + }, { + .name = DM365_VPBE_OSD_SUBDEV_NAME, + .driver_data = VPBE_VERSION_2, + }, { + .name = DM355_VPBE_OSD_SUBDEV_NAME, + .driver_data = VPBE_VERSION_3, + }, + { + /* sentinel */ + } +}; + +MODULE_DEVICE_TABLE(platform, vpbe_osd_devtype); + +/* register access routines */ +static inline u32 __always_unused osd_read(struct osd_state *sd, u32 offset) +{ + struct osd_state *osd = sd; + + return readl(osd->osd_base + offset); +} + +static inline u32 osd_write(struct osd_state *sd, u32 val, u32 offset) +{ + struct osd_state *osd = sd; + + writel(val, osd->osd_base + offset); + + return val; +} + +static inline u32 osd_set(struct osd_state *sd, u32 mask, u32 offset) +{ + struct osd_state *osd = sd; + + void __iomem *addr = osd->osd_base + offset; + u32 val = readl(addr) | mask; + + writel(val, addr); + + return val; +} + +static inline u32 osd_clear(struct osd_state *sd, u32 mask, u32 offset) +{ + struct osd_state *osd = sd; + + void __iomem *addr = osd->osd_base + offset; + u32 val = readl(addr) & ~mask; + + writel(val, addr); + + return val; +} + +static inline u32 osd_modify(struct osd_state *sd, u32 mask, u32 val, + u32 offset) +{ + struct osd_state *osd = sd; + + void __iomem *addr = osd->osd_base + offset; + u32 new_val = (readl(addr) & ~mask) | (val & mask); + + writel(new_val, addr); + + return new_val; +} + +/* define some macros for layer and pixfmt classification */ +#define is_osd_win(layer) (((layer) == WIN_OSD0) || ((layer) == WIN_OSD1)) +#define is_vid_win(layer) (((layer) == WIN_VID0) || ((layer) == WIN_VID1)) +#define is_rgb_pixfmt(pixfmt) \ + (((pixfmt) == PIXFMT_RGB565) || ((pixfmt) == PIXFMT_RGB888)) +#define is_yc_pixfmt(pixfmt) \ + (((pixfmt) == PIXFMT_YCBCRI) || ((pixfmt) == PIXFMT_YCRCBI) || \ + ((pixfmt) == PIXFMT_NV12)) +#define MAX_WIN_SIZE OSD_VIDWIN0XP_V0X +#define MAX_LINE_LENGTH (OSD_VIDWIN0OFST_V0LO << 5) + +/** + * _osd_dm6446_vid0_pingpong() - field inversion fix for DM6446 + * @sd: ptr to struct osd_state + * @field_inversion: inversion flag + * @fb_base_phys: frame buffer address + * @lconfig: ptr to layer config + * + * This routine implements a workaround for the field signal inversion silicon + * erratum described in Advisory 1.3.8 for the DM6446. The fb_base_phys and + * lconfig parameters apply to the vid0 window. This routine should be called + * whenever the vid0 layer configuration or start address is modified, or when + * the OSD field inversion setting is modified. + * Returns: 1 if the ping-pong buffers need to be toggled in the vsync isr, or + * 0 otherwise + */ +static int _osd_dm6446_vid0_pingpong(struct osd_state *sd, + int field_inversion, + unsigned long fb_base_phys, + const struct osd_layer_config *lconfig) +{ + struct osd_platform_data *pdata; + + pdata = (struct osd_platform_data *)sd->dev->platform_data; + if (pdata != NULL && pdata->field_inv_wa_enable) { + + if (!field_inversion || !lconfig->interlaced) { + osd_write(sd, fb_base_phys & ~0x1F, OSD_VIDWIN0ADR); + osd_write(sd, fb_base_phys & ~0x1F, OSD_PPVWIN0ADR); + osd_modify(sd, OSD_MISCCTL_PPSW | OSD_MISCCTL_PPRV, 0, + OSD_MISCCTL); + return 0; + } else { + unsigned miscctl = OSD_MISCCTL_PPRV; + + osd_write(sd, + (fb_base_phys & ~0x1F) - lconfig->line_length, + OSD_VIDWIN0ADR); + osd_write(sd, + (fb_base_phys & ~0x1F) + lconfig->line_length, + OSD_PPVWIN0ADR); + osd_modify(sd, + OSD_MISCCTL_PPSW | OSD_MISCCTL_PPRV, miscctl, + OSD_MISCCTL); + + return 1; + } + } + + return 0; +} + +static void _osd_set_field_inversion(struct osd_state *sd, int enable) +{ + unsigned fsinv = 0; + + if (enable) + fsinv = OSD_MODE_FSINV; + + osd_modify(sd, OSD_MODE_FSINV, fsinv, OSD_MODE); +} + +static void _osd_set_blink_attribute(struct osd_state *sd, int enable, + enum osd_blink_interval blink) +{ + u32 osdatrmd = 0; + + if (enable) { + osdatrmd |= OSD_OSDATRMD_BLNK; + osdatrmd |= blink << OSD_OSDATRMD_BLNKINT_SHIFT; + } + /* caller must ensure that OSD1 is configured in attribute mode */ + osd_modify(sd, OSD_OSDATRMD_BLNKINT | OSD_OSDATRMD_BLNK, osdatrmd, + OSD_OSDATRMD); +} + +static void _osd_set_rom_clut(struct osd_state *sd, + enum osd_rom_clut rom_clut) +{ + if (rom_clut == ROM_CLUT0) + osd_clear(sd, OSD_MISCCTL_RSEL, OSD_MISCCTL); + else + osd_set(sd, OSD_MISCCTL_RSEL, OSD_MISCCTL); +} + +static void _osd_set_palette_map(struct osd_state *sd, + enum osd_win_layer osdwin, + unsigned char pixel_value, + unsigned char clut_index, + enum osd_pix_format pixfmt) +{ + static const int map_2bpp[] = { 0, 5, 10, 15 }; + static const int map_1bpp[] = { 0, 15 }; + int bmp_offset; + int bmp_shift; + int bmp_mask; + int bmp_reg; + + switch (pixfmt) { + case PIXFMT_1BPP: + bmp_reg = map_1bpp[pixel_value & 0x1]; + break; + case PIXFMT_2BPP: + bmp_reg = map_2bpp[pixel_value & 0x3]; + break; + case PIXFMT_4BPP: + bmp_reg = pixel_value & 0xf; + break; + default: + return; + } + + switch (osdwin) { + case OSDWIN_OSD0: + bmp_offset = OSD_W0BMP01 + (bmp_reg >> 1) * sizeof(u32); + break; + case OSDWIN_OSD1: + bmp_offset = OSD_W1BMP01 + (bmp_reg >> 1) * sizeof(u32); + break; + default: + return; + } + + if (bmp_reg & 1) { + bmp_shift = 8; + bmp_mask = 0xff << 8; + } else { + bmp_shift = 0; + bmp_mask = 0xff; + } + + osd_modify(sd, bmp_mask, clut_index << bmp_shift, bmp_offset); +} + +static void _osd_set_rec601_attenuation(struct osd_state *sd, + enum osd_win_layer osdwin, int enable) +{ + switch (osdwin) { + case OSDWIN_OSD0: + osd_modify(sd, OSD_OSDWIN0MD_ATN0E, + enable ? OSD_OSDWIN0MD_ATN0E : 0, + OSD_OSDWIN0MD); + if (sd->vpbe_type == VPBE_VERSION_1) + osd_modify(sd, OSD_OSDWIN0MD_ATN0E, + enable ? OSD_OSDWIN0MD_ATN0E : 0, + OSD_OSDWIN0MD); + else if ((sd->vpbe_type == VPBE_VERSION_3) || + (sd->vpbe_type == VPBE_VERSION_2)) + osd_modify(sd, OSD_EXTMODE_ATNOSD0EN, + enable ? OSD_EXTMODE_ATNOSD0EN : 0, + OSD_EXTMODE); + break; + case OSDWIN_OSD1: + osd_modify(sd, OSD_OSDWIN1MD_ATN1E, + enable ? OSD_OSDWIN1MD_ATN1E : 0, + OSD_OSDWIN1MD); + if (sd->vpbe_type == VPBE_VERSION_1) + osd_modify(sd, OSD_OSDWIN1MD_ATN1E, + enable ? OSD_OSDWIN1MD_ATN1E : 0, + OSD_OSDWIN1MD); + else if ((sd->vpbe_type == VPBE_VERSION_3) || + (sd->vpbe_type == VPBE_VERSION_2)) + osd_modify(sd, OSD_EXTMODE_ATNOSD1EN, + enable ? OSD_EXTMODE_ATNOSD1EN : 0, + OSD_EXTMODE); + break; + } +} + +static void _osd_set_blending_factor(struct osd_state *sd, + enum osd_win_layer osdwin, + enum osd_blending_factor blend) +{ + switch (osdwin) { + case OSDWIN_OSD0: + osd_modify(sd, OSD_OSDWIN0MD_BLND0, + blend << OSD_OSDWIN0MD_BLND0_SHIFT, OSD_OSDWIN0MD); + break; + case OSDWIN_OSD1: + osd_modify(sd, OSD_OSDWIN1MD_BLND1, + blend << OSD_OSDWIN1MD_BLND1_SHIFT, OSD_OSDWIN1MD); + break; + } +} + +static void _osd_enable_rgb888_pixblend(struct osd_state *sd, + enum osd_win_layer osdwin) +{ + + osd_modify(sd, OSD_MISCCTL_BLDSEL, 0, OSD_MISCCTL); + switch (osdwin) { + case OSDWIN_OSD0: + osd_modify(sd, OSD_EXTMODE_OSD0BLDCHR, + OSD_EXTMODE_OSD0BLDCHR, OSD_EXTMODE); + break; + case OSDWIN_OSD1: + osd_modify(sd, OSD_EXTMODE_OSD1BLDCHR, + OSD_EXTMODE_OSD1BLDCHR, OSD_EXTMODE); + break; + } +} + +static void _osd_enable_color_key(struct osd_state *sd, + enum osd_win_layer osdwin, + unsigned colorkey, + enum osd_pix_format pixfmt) +{ + switch (pixfmt) { + case PIXFMT_1BPP: + case PIXFMT_2BPP: + case PIXFMT_4BPP: + case PIXFMT_8BPP: + if (sd->vpbe_type == VPBE_VERSION_3) { + switch (osdwin) { + case OSDWIN_OSD0: + osd_modify(sd, OSD_TRANSPBMPIDX_BMP0, + colorkey << + OSD_TRANSPBMPIDX_BMP0_SHIFT, + OSD_TRANSPBMPIDX); + break; + case OSDWIN_OSD1: + osd_modify(sd, OSD_TRANSPBMPIDX_BMP1, + colorkey << + OSD_TRANSPBMPIDX_BMP1_SHIFT, + OSD_TRANSPBMPIDX); + break; + } + } + break; + case PIXFMT_RGB565: + if (sd->vpbe_type == VPBE_VERSION_1) + osd_write(sd, colorkey & OSD_TRANSPVAL_RGBTRANS, + OSD_TRANSPVAL); + else if (sd->vpbe_type == VPBE_VERSION_3) + osd_write(sd, colorkey & OSD_TRANSPVALL_RGBL, + OSD_TRANSPVALL); + break; + case PIXFMT_YCBCRI: + case PIXFMT_YCRCBI: + if (sd->vpbe_type == VPBE_VERSION_3) + osd_modify(sd, OSD_TRANSPVALU_Y, colorkey, + OSD_TRANSPVALU); + break; + case PIXFMT_RGB888: + if (sd->vpbe_type == VPBE_VERSION_3) { + osd_write(sd, colorkey & OSD_TRANSPVALL_RGBL, + OSD_TRANSPVALL); + osd_modify(sd, OSD_TRANSPVALU_RGBU, colorkey >> 16, + OSD_TRANSPVALU); + } + break; + default: + break; + } + + switch (osdwin) { + case OSDWIN_OSD0: + osd_set(sd, OSD_OSDWIN0MD_TE0, OSD_OSDWIN0MD); + break; + case OSDWIN_OSD1: + osd_set(sd, OSD_OSDWIN1MD_TE1, OSD_OSDWIN1MD); + break; + } +} + +static void _osd_disable_color_key(struct osd_state *sd, + enum osd_win_layer osdwin) +{ + switch (osdwin) { + case OSDWIN_OSD0: + osd_clear(sd, OSD_OSDWIN0MD_TE0, OSD_OSDWIN0MD); + break; + case OSDWIN_OSD1: + osd_clear(sd, OSD_OSDWIN1MD_TE1, OSD_OSDWIN1MD); + break; + } +} + +static void _osd_set_osd_clut(struct osd_state *sd, + enum osd_win_layer osdwin, + enum osd_clut clut) +{ + u32 winmd = 0; + + switch (osdwin) { + case OSDWIN_OSD0: + if (clut == RAM_CLUT) + winmd |= OSD_OSDWIN0MD_CLUTS0; + osd_modify(sd, OSD_OSDWIN0MD_CLUTS0, winmd, OSD_OSDWIN0MD); + break; + case OSDWIN_OSD1: + if (clut == RAM_CLUT) + winmd |= OSD_OSDWIN1MD_CLUTS1; + osd_modify(sd, OSD_OSDWIN1MD_CLUTS1, winmd, OSD_OSDWIN1MD); + break; + } +} + +static void _osd_set_zoom(struct osd_state *sd, enum osd_layer layer, + enum osd_zoom_factor h_zoom, + enum osd_zoom_factor v_zoom) +{ + u32 winmd = 0; + + switch (layer) { + case WIN_OSD0: + winmd |= (h_zoom << OSD_OSDWIN0MD_OHZ0_SHIFT); + winmd |= (v_zoom << OSD_OSDWIN0MD_OVZ0_SHIFT); + osd_modify(sd, OSD_OSDWIN0MD_OHZ0 | OSD_OSDWIN0MD_OVZ0, winmd, + OSD_OSDWIN0MD); + break; + case WIN_VID0: + winmd |= (h_zoom << OSD_VIDWINMD_VHZ0_SHIFT); + winmd |= (v_zoom << OSD_VIDWINMD_VVZ0_SHIFT); + osd_modify(sd, OSD_VIDWINMD_VHZ0 | OSD_VIDWINMD_VVZ0, winmd, + OSD_VIDWINMD); + break; + case WIN_OSD1: + winmd |= (h_zoom << OSD_OSDWIN1MD_OHZ1_SHIFT); + winmd |= (v_zoom << OSD_OSDWIN1MD_OVZ1_SHIFT); + osd_modify(sd, OSD_OSDWIN1MD_OHZ1 | OSD_OSDWIN1MD_OVZ1, winmd, + OSD_OSDWIN1MD); + break; + case WIN_VID1: + winmd |= (h_zoom << OSD_VIDWINMD_VHZ1_SHIFT); + winmd |= (v_zoom << OSD_VIDWINMD_VVZ1_SHIFT); + osd_modify(sd, OSD_VIDWINMD_VHZ1 | OSD_VIDWINMD_VVZ1, winmd, + OSD_VIDWINMD); + break; + } +} + +static void _osd_disable_layer(struct osd_state *sd, enum osd_layer layer) +{ + switch (layer) { + case WIN_OSD0: + osd_clear(sd, OSD_OSDWIN0MD_OACT0, OSD_OSDWIN0MD); + break; + case WIN_VID0: + osd_clear(sd, OSD_VIDWINMD_ACT0, OSD_VIDWINMD); + break; + case WIN_OSD1: + /* disable attribute mode as well as disabling the window */ + osd_clear(sd, OSD_OSDWIN1MD_OASW | OSD_OSDWIN1MD_OACT1, + OSD_OSDWIN1MD); + break; + case WIN_VID1: + osd_clear(sd, OSD_VIDWINMD_ACT1, OSD_VIDWINMD); + break; + } +} + +static void osd_disable_layer(struct osd_state *sd, enum osd_layer layer) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + unsigned long flags; + + spin_lock_irqsave(&osd->lock, flags); + + if (!win->is_enabled) { + spin_unlock_irqrestore(&osd->lock, flags); + return; + } + win->is_enabled = 0; + + _osd_disable_layer(sd, layer); + + spin_unlock_irqrestore(&osd->lock, flags); +} + +static void _osd_enable_attribute_mode(struct osd_state *sd) +{ + /* enable attribute mode for OSD1 */ + osd_set(sd, OSD_OSDWIN1MD_OASW, OSD_OSDWIN1MD); +} + +static void _osd_enable_layer(struct osd_state *sd, enum osd_layer layer) +{ + switch (layer) { + case WIN_OSD0: + osd_set(sd, OSD_OSDWIN0MD_OACT0, OSD_OSDWIN0MD); + break; + case WIN_VID0: + osd_set(sd, OSD_VIDWINMD_ACT0, OSD_VIDWINMD); + break; + case WIN_OSD1: + /* enable OSD1 and disable attribute mode */ + osd_modify(sd, OSD_OSDWIN1MD_OASW | OSD_OSDWIN1MD_OACT1, + OSD_OSDWIN1MD_OACT1, OSD_OSDWIN1MD); + break; + case WIN_VID1: + osd_set(sd, OSD_VIDWINMD_ACT1, OSD_VIDWINMD); + break; + } +} + +static int osd_enable_layer(struct osd_state *sd, enum osd_layer layer, + int otherwin) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + struct osd_layer_config *cfg = &win->lconfig; + unsigned long flags; + + spin_lock_irqsave(&osd->lock, flags); + + /* + * use otherwin flag to know this is the other vid window + * in YUV420 mode, if is, skip this check + */ + if (!otherwin && (!win->is_allocated || + !win->fb_base_phys || + !cfg->line_length || + !cfg->xsize || + !cfg->ysize)) { + spin_unlock_irqrestore(&osd->lock, flags); + return -1; + } + + if (win->is_enabled) { + spin_unlock_irqrestore(&osd->lock, flags); + return 0; + } + win->is_enabled = 1; + + if (cfg->pixfmt != PIXFMT_OSD_ATTR) + _osd_enable_layer(sd, layer); + else { + _osd_enable_attribute_mode(sd); + _osd_set_blink_attribute(sd, osd->is_blinking, osd->blink); + } + + spin_unlock_irqrestore(&osd->lock, flags); + + return 0; +} + +#define OSD_SRC_ADDR_HIGH4 0x7800000 +#define OSD_SRC_ADDR_HIGH7 0x7F0000 +#define OSD_SRCADD_OFSET_SFT 23 +#define OSD_SRCADD_ADD_SFT 16 +#define OSD_WINADL_MASK 0xFFFF +#define OSD_WINOFST_MASK 0x1000 +#define VPBE_REG_BASE 0x80000000 + +static void _osd_start_layer(struct osd_state *sd, enum osd_layer layer, + unsigned long fb_base_phys, + unsigned long cbcr_ofst) +{ + + if (sd->vpbe_type == VPBE_VERSION_1) { + switch (layer) { + case WIN_OSD0: + osd_write(sd, fb_base_phys & ~0x1F, OSD_OSDWIN0ADR); + break; + case WIN_VID0: + osd_write(sd, fb_base_phys & ~0x1F, OSD_VIDWIN0ADR); + break; + case WIN_OSD1: + osd_write(sd, fb_base_phys & ~0x1F, OSD_OSDWIN1ADR); + break; + case WIN_VID1: + osd_write(sd, fb_base_phys & ~0x1F, OSD_VIDWIN1ADR); + break; + } + } else if (sd->vpbe_type == VPBE_VERSION_3) { + unsigned long fb_offset_32 = + (fb_base_phys - VPBE_REG_BASE) >> 5; + + switch (layer) { + case WIN_OSD0: + osd_modify(sd, OSD_OSDWINADH_O0AH, + fb_offset_32 >> (OSD_SRCADD_ADD_SFT - + OSD_OSDWINADH_O0AH_SHIFT), + OSD_OSDWINADH); + osd_write(sd, fb_offset_32 & OSD_OSDWIN0ADL_O0AL, + OSD_OSDWIN0ADL); + break; + case WIN_VID0: + osd_modify(sd, OSD_VIDWINADH_V0AH, + fb_offset_32 >> (OSD_SRCADD_ADD_SFT - + OSD_VIDWINADH_V0AH_SHIFT), + OSD_VIDWINADH); + osd_write(sd, fb_offset_32 & OSD_VIDWIN0ADL_V0AL, + OSD_VIDWIN0ADL); + break; + case WIN_OSD1: + osd_modify(sd, OSD_OSDWINADH_O1AH, + fb_offset_32 >> (OSD_SRCADD_ADD_SFT - + OSD_OSDWINADH_O1AH_SHIFT), + OSD_OSDWINADH); + osd_write(sd, fb_offset_32 & OSD_OSDWIN1ADL_O1AL, + OSD_OSDWIN1ADL); + break; + case WIN_VID1: + osd_modify(sd, OSD_VIDWINADH_V1AH, + fb_offset_32 >> (OSD_SRCADD_ADD_SFT - + OSD_VIDWINADH_V1AH_SHIFT), + OSD_VIDWINADH); + osd_write(sd, fb_offset_32 & OSD_VIDWIN1ADL_V1AL, + OSD_VIDWIN1ADL); + break; + } + } else if (sd->vpbe_type == VPBE_VERSION_2) { + struct osd_window_state *win = &sd->win[layer]; + unsigned long fb_offset_32, cbcr_offset_32; + + fb_offset_32 = fb_base_phys - VPBE_REG_BASE; + if (cbcr_ofst) + cbcr_offset_32 = cbcr_ofst; + else + cbcr_offset_32 = win->lconfig.line_length * + win->lconfig.ysize; + cbcr_offset_32 += fb_offset_32; + fb_offset_32 = fb_offset_32 >> 5; + cbcr_offset_32 = cbcr_offset_32 >> 5; + /* + * DM365: start address is 27-bit long address b26 - b23 are + * in offset register b12 - b9, and * bit 26 has to be '1' + */ + if (win->lconfig.pixfmt == PIXFMT_NV12) { + switch (layer) { + case WIN_VID0: + case WIN_VID1: + /* Y is in VID0 */ + osd_modify(sd, OSD_VIDWIN0OFST_V0AH, + ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> + (OSD_SRCADD_OFSET_SFT - + OSD_WINOFST_AH_SHIFT)) | + OSD_WINOFST_MASK, OSD_VIDWIN0OFST); + osd_modify(sd, OSD_VIDWINADH_V0AH, + (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> + (OSD_SRCADD_ADD_SFT - + OSD_VIDWINADH_V0AH_SHIFT), + OSD_VIDWINADH); + osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, + OSD_VIDWIN0ADL); + /* CbCr is in VID1 */ + osd_modify(sd, OSD_VIDWIN1OFST_V1AH, + ((cbcr_offset_32 & + OSD_SRC_ADDR_HIGH4) >> + (OSD_SRCADD_OFSET_SFT - + OSD_WINOFST_AH_SHIFT)) | + OSD_WINOFST_MASK, OSD_VIDWIN1OFST); + osd_modify(sd, OSD_VIDWINADH_V1AH, + (cbcr_offset_32 & + OSD_SRC_ADDR_HIGH7) >> + (OSD_SRCADD_ADD_SFT - + OSD_VIDWINADH_V1AH_SHIFT), + OSD_VIDWINADH); + osd_write(sd, cbcr_offset_32 & OSD_WINADL_MASK, + OSD_VIDWIN1ADL); + break; + default: + break; + } + } + + switch (layer) { + case WIN_OSD0: + osd_modify(sd, OSD_OSDWIN0OFST_O0AH, + ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> + (OSD_SRCADD_OFSET_SFT - + OSD_WINOFST_AH_SHIFT)) | OSD_WINOFST_MASK, + OSD_OSDWIN0OFST); + osd_modify(sd, OSD_OSDWINADH_O0AH, + (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> + (OSD_SRCADD_ADD_SFT - + OSD_OSDWINADH_O0AH_SHIFT), OSD_OSDWINADH); + osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, + OSD_OSDWIN0ADL); + break; + case WIN_VID0: + if (win->lconfig.pixfmt != PIXFMT_NV12) { + osd_modify(sd, OSD_VIDWIN0OFST_V0AH, + ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> + (OSD_SRCADD_OFSET_SFT - + OSD_WINOFST_AH_SHIFT)) | + OSD_WINOFST_MASK, OSD_VIDWIN0OFST); + osd_modify(sd, OSD_VIDWINADH_V0AH, + (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> + (OSD_SRCADD_ADD_SFT - + OSD_VIDWINADH_V0AH_SHIFT), + OSD_VIDWINADH); + osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, + OSD_VIDWIN0ADL); + } + break; + case WIN_OSD1: + osd_modify(sd, OSD_OSDWIN1OFST_O1AH, + ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> + (OSD_SRCADD_OFSET_SFT - + OSD_WINOFST_AH_SHIFT)) | OSD_WINOFST_MASK, + OSD_OSDWIN1OFST); + osd_modify(sd, OSD_OSDWINADH_O1AH, + (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> + (OSD_SRCADD_ADD_SFT - + OSD_OSDWINADH_O1AH_SHIFT), + OSD_OSDWINADH); + osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, + OSD_OSDWIN1ADL); + break; + case WIN_VID1: + if (win->lconfig.pixfmt != PIXFMT_NV12) { + osd_modify(sd, OSD_VIDWIN1OFST_V1AH, + ((fb_offset_32 & OSD_SRC_ADDR_HIGH4) >> + (OSD_SRCADD_OFSET_SFT - + OSD_WINOFST_AH_SHIFT)) | + OSD_WINOFST_MASK, OSD_VIDWIN1OFST); + osd_modify(sd, OSD_VIDWINADH_V1AH, + (fb_offset_32 & OSD_SRC_ADDR_HIGH7) >> + (OSD_SRCADD_ADD_SFT - + OSD_VIDWINADH_V1AH_SHIFT), + OSD_VIDWINADH); + osd_write(sd, fb_offset_32 & OSD_WINADL_MASK, + OSD_VIDWIN1ADL); + } + break; + } + } +} + +static void osd_start_layer(struct osd_state *sd, enum osd_layer layer, + unsigned long fb_base_phys, + unsigned long cbcr_ofst) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + struct osd_layer_config *cfg = &win->lconfig; + unsigned long flags; + + spin_lock_irqsave(&osd->lock, flags); + + win->fb_base_phys = fb_base_phys & ~0x1F; + _osd_start_layer(sd, layer, fb_base_phys, cbcr_ofst); + + if (layer == WIN_VID0) { + osd->pingpong = + _osd_dm6446_vid0_pingpong(sd, osd->field_inversion, + win->fb_base_phys, + cfg); + } + + spin_unlock_irqrestore(&osd->lock, flags); +} + +static void osd_get_layer_config(struct osd_state *sd, enum osd_layer layer, + struct osd_layer_config *lconfig) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + unsigned long flags; + + spin_lock_irqsave(&osd->lock, flags); + + *lconfig = win->lconfig; + + spin_unlock_irqrestore(&osd->lock, flags); +} + +/** + * try_layer_config() - Try a specific configuration for the layer + * @sd: ptr to struct osd_state + * @layer: layer to configure + * @lconfig: layer configuration to try + * + * If the requested lconfig is completely rejected and the value of lconfig on + * exit is the current lconfig, then try_layer_config() returns 1. Otherwise, + * try_layer_config() returns 0. A return value of 0 does not necessarily mean + * that the value of lconfig on exit is identical to the value of lconfig on + * entry, but merely that it represents a change from the current lconfig. + */ +static int try_layer_config(struct osd_state *sd, enum osd_layer layer, + struct osd_layer_config *lconfig) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + int bad_config = 0; + + /* verify that the pixel format is compatible with the layer */ + switch (lconfig->pixfmt) { + case PIXFMT_1BPP: + case PIXFMT_2BPP: + case PIXFMT_4BPP: + case PIXFMT_8BPP: + case PIXFMT_RGB565: + if (osd->vpbe_type == VPBE_VERSION_1) + bad_config = !is_vid_win(layer); + break; + case PIXFMT_YCBCRI: + case PIXFMT_YCRCBI: + bad_config = !is_vid_win(layer); + break; + case PIXFMT_RGB888: + if (osd->vpbe_type == VPBE_VERSION_1) + bad_config = !is_vid_win(layer); + else if ((osd->vpbe_type == VPBE_VERSION_3) || + (osd->vpbe_type == VPBE_VERSION_2)) + bad_config = !is_osd_win(layer); + break; + case PIXFMT_NV12: + if (osd->vpbe_type != VPBE_VERSION_2) + bad_config = 1; + else + bad_config = is_osd_win(layer); + break; + case PIXFMT_OSD_ATTR: + bad_config = (layer != WIN_OSD1); + break; + default: + bad_config = 1; + break; + } + if (bad_config) { + /* + * The requested pixel format is incompatible with the layer, + * so keep the current layer configuration. + */ + *lconfig = win->lconfig; + return bad_config; + } + + /* DM6446: */ + /* only one OSD window at a time can use RGB pixel formats */ + if ((osd->vpbe_type == VPBE_VERSION_1) && + is_osd_win(layer) && is_rgb_pixfmt(lconfig->pixfmt)) { + enum osd_pix_format pixfmt; + + if (layer == WIN_OSD0) + pixfmt = osd->win[WIN_OSD1].lconfig.pixfmt; + else + pixfmt = osd->win[WIN_OSD0].lconfig.pixfmt; + + if (is_rgb_pixfmt(pixfmt)) { + /* + * The other OSD window is already configured for an + * RGB, so keep the current layer configuration. + */ + *lconfig = win->lconfig; + return 1; + } + } + + /* DM6446: only one video window at a time can use RGB888 */ + if ((osd->vpbe_type == VPBE_VERSION_1) && is_vid_win(layer) && + lconfig->pixfmt == PIXFMT_RGB888) { + enum osd_pix_format pixfmt; + + if (layer == WIN_VID0) + pixfmt = osd->win[WIN_VID1].lconfig.pixfmt; + else + pixfmt = osd->win[WIN_VID0].lconfig.pixfmt; + + if (pixfmt == PIXFMT_RGB888) { + /* + * The other video window is already configured for + * RGB888, so keep the current layer configuration. + */ + *lconfig = win->lconfig; + return 1; + } + } + + /* window dimensions must be non-zero */ + if (!lconfig->line_length || !lconfig->xsize || !lconfig->ysize) { + *lconfig = win->lconfig; + return 1; + } + + /* round line_length up to a multiple of 32 */ + lconfig->line_length = ((lconfig->line_length + 31) / 32) * 32; + lconfig->line_length = + min(lconfig->line_length, (unsigned)MAX_LINE_LENGTH); + lconfig->xsize = min(lconfig->xsize, (unsigned)MAX_WIN_SIZE); + lconfig->ysize = min(lconfig->ysize, (unsigned)MAX_WIN_SIZE); + lconfig->xpos = min(lconfig->xpos, (unsigned)MAX_WIN_SIZE); + lconfig->ypos = min(lconfig->ypos, (unsigned)MAX_WIN_SIZE); + lconfig->interlaced = (lconfig->interlaced != 0); + if (lconfig->interlaced) { + /* ysize and ypos must be even for interlaced displays */ + lconfig->ysize &= ~1; + lconfig->ypos &= ~1; + } + + return 0; +} + +static void _osd_disable_vid_rgb888(struct osd_state *sd) +{ + /* + * The DM6446 supports RGB888 pixel format in a single video window. + * This routine disables RGB888 pixel format for both video windows. + * The caller must ensure that neither video window is currently + * configured for RGB888 pixel format. + */ + if (sd->vpbe_type == VPBE_VERSION_1) + osd_clear(sd, OSD_MISCCTL_RGBEN, OSD_MISCCTL); +} + +static void _osd_enable_vid_rgb888(struct osd_state *sd, + enum osd_layer layer) +{ + /* + * The DM6446 supports RGB888 pixel format in a single video window. + * This routine enables RGB888 pixel format for the specified video + * window. The caller must ensure that the other video window is not + * currently configured for RGB888 pixel format, as this routine will + * disable RGB888 pixel format for the other window. + */ + if (sd->vpbe_type == VPBE_VERSION_1) { + if (layer == WIN_VID0) + osd_modify(sd, OSD_MISCCTL_RGBEN | OSD_MISCCTL_RGBWIN, + OSD_MISCCTL_RGBEN, OSD_MISCCTL); + else if (layer == WIN_VID1) + osd_modify(sd, OSD_MISCCTL_RGBEN | OSD_MISCCTL_RGBWIN, + OSD_MISCCTL_RGBEN | OSD_MISCCTL_RGBWIN, + OSD_MISCCTL); + } +} + +static void _osd_set_cbcr_order(struct osd_state *sd, + enum osd_pix_format pixfmt) +{ + /* + * The caller must ensure that all windows using YC pixfmt use the same + * Cb/Cr order. + */ + if (pixfmt == PIXFMT_YCBCRI) + osd_clear(sd, OSD_MODE_CS, OSD_MODE); + else if (pixfmt == PIXFMT_YCRCBI) + osd_set(sd, OSD_MODE_CS, OSD_MODE); +} + +static void _osd_set_layer_config(struct osd_state *sd, enum osd_layer layer, + const struct osd_layer_config *lconfig) +{ + u32 winmd = 0, winmd_mask = 0, bmw = 0; + + _osd_set_cbcr_order(sd, lconfig->pixfmt); + + switch (layer) { + case WIN_OSD0: + if (sd->vpbe_type == VPBE_VERSION_1) { + winmd_mask |= OSD_OSDWIN0MD_RGB0E; + if (lconfig->pixfmt == PIXFMT_RGB565) + winmd |= OSD_OSDWIN0MD_RGB0E; + } else if ((sd->vpbe_type == VPBE_VERSION_3) || + (sd->vpbe_type == VPBE_VERSION_2)) { + winmd_mask |= OSD_OSDWIN0MD_BMP0MD; + switch (lconfig->pixfmt) { + case PIXFMT_RGB565: + winmd |= (1 << + OSD_OSDWIN0MD_BMP0MD_SHIFT); + break; + case PIXFMT_RGB888: + winmd |= (2 << OSD_OSDWIN0MD_BMP0MD_SHIFT); + _osd_enable_rgb888_pixblend(sd, OSDWIN_OSD0); + break; + case PIXFMT_YCBCRI: + case PIXFMT_YCRCBI: + winmd |= (3 << OSD_OSDWIN0MD_BMP0MD_SHIFT); + break; + default: + break; + } + } + + winmd_mask |= OSD_OSDWIN0MD_BMW0 | OSD_OSDWIN0MD_OFF0; + + switch (lconfig->pixfmt) { + case PIXFMT_1BPP: + bmw = 0; + break; + case PIXFMT_2BPP: + bmw = 1; + break; + case PIXFMT_4BPP: + bmw = 2; + break; + case PIXFMT_8BPP: + bmw = 3; + break; + default: + break; + } + winmd |= (bmw << OSD_OSDWIN0MD_BMW0_SHIFT); + + if (lconfig->interlaced) + winmd |= OSD_OSDWIN0MD_OFF0; + + osd_modify(sd, winmd_mask, winmd, OSD_OSDWIN0MD); + osd_write(sd, lconfig->line_length >> 5, OSD_OSDWIN0OFST); + osd_write(sd, lconfig->xpos, OSD_OSDWIN0XP); + osd_write(sd, lconfig->xsize, OSD_OSDWIN0XL); + if (lconfig->interlaced) { + osd_write(sd, lconfig->ypos >> 1, OSD_OSDWIN0YP); + osd_write(sd, lconfig->ysize >> 1, OSD_OSDWIN0YL); + } else { + osd_write(sd, lconfig->ypos, OSD_OSDWIN0YP); + osd_write(sd, lconfig->ysize, OSD_OSDWIN0YL); + } + break; + case WIN_VID0: + winmd_mask |= OSD_VIDWINMD_VFF0; + if (lconfig->interlaced) + winmd |= OSD_VIDWINMD_VFF0; + + osd_modify(sd, winmd_mask, winmd, OSD_VIDWINMD); + osd_write(sd, lconfig->line_length >> 5, OSD_VIDWIN0OFST); + osd_write(sd, lconfig->xpos, OSD_VIDWIN0XP); + osd_write(sd, lconfig->xsize, OSD_VIDWIN0XL); + /* + * For YUV420P format the register contents are + * duplicated in both VID registers + */ + if ((sd->vpbe_type == VPBE_VERSION_2) && + (lconfig->pixfmt == PIXFMT_NV12)) { + /* other window also */ + if (lconfig->interlaced) { + winmd_mask |= OSD_VIDWINMD_VFF1; + winmd |= OSD_VIDWINMD_VFF1; + osd_modify(sd, winmd_mask, winmd, + OSD_VIDWINMD); + } + + osd_modify(sd, OSD_MISCCTL_S420D, + OSD_MISCCTL_S420D, OSD_MISCCTL); + osd_write(sd, lconfig->line_length >> 5, + OSD_VIDWIN1OFST); + osd_write(sd, lconfig->xpos, OSD_VIDWIN1XP); + osd_write(sd, lconfig->xsize, OSD_VIDWIN1XL); + /* + * if NV21 pixfmt and line length not 32B + * aligned (e.g. NTSC), Need to set window + * X pixel size to be 32B aligned as well + */ + if (lconfig->xsize % 32) { + osd_write(sd, + ((lconfig->xsize + 31) & ~31), + OSD_VIDWIN1XL); + osd_write(sd, + ((lconfig->xsize + 31) & ~31), + OSD_VIDWIN0XL); + } + } else if ((sd->vpbe_type == VPBE_VERSION_2) && + (lconfig->pixfmt != PIXFMT_NV12)) { + osd_modify(sd, OSD_MISCCTL_S420D, ~OSD_MISCCTL_S420D, + OSD_MISCCTL); + } + + if (lconfig->interlaced) { + osd_write(sd, lconfig->ypos >> 1, OSD_VIDWIN0YP); + osd_write(sd, lconfig->ysize >> 1, OSD_VIDWIN0YL); + if ((sd->vpbe_type == VPBE_VERSION_2) && + lconfig->pixfmt == PIXFMT_NV12) { + osd_write(sd, lconfig->ypos >> 1, + OSD_VIDWIN1YP); + osd_write(sd, lconfig->ysize >> 1, + OSD_VIDWIN1YL); + } + } else { + osd_write(sd, lconfig->ypos, OSD_VIDWIN0YP); + osd_write(sd, lconfig->ysize, OSD_VIDWIN0YL); + if ((sd->vpbe_type == VPBE_VERSION_2) && + lconfig->pixfmt == PIXFMT_NV12) { + osd_write(sd, lconfig->ypos, OSD_VIDWIN1YP); + osd_write(sd, lconfig->ysize, OSD_VIDWIN1YL); + } + } + break; + case WIN_OSD1: + /* + * The caller must ensure that OSD1 is disabled prior to + * switching from a normal mode to attribute mode or from + * attribute mode to a normal mode. + */ + if (lconfig->pixfmt == PIXFMT_OSD_ATTR) { + if (sd->vpbe_type == VPBE_VERSION_1) { + winmd_mask |= OSD_OSDWIN1MD_ATN1E | + OSD_OSDWIN1MD_RGB1E | OSD_OSDWIN1MD_CLUTS1 | + OSD_OSDWIN1MD_BLND1 | OSD_OSDWIN1MD_TE1; + } else { + winmd_mask |= OSD_OSDWIN1MD_BMP1MD | + OSD_OSDWIN1MD_CLUTS1 | OSD_OSDWIN1MD_BLND1 | + OSD_OSDWIN1MD_TE1; + } + } else { + if (sd->vpbe_type == VPBE_VERSION_1) { + winmd_mask |= OSD_OSDWIN1MD_RGB1E; + if (lconfig->pixfmt == PIXFMT_RGB565) + winmd |= OSD_OSDWIN1MD_RGB1E; + } else if ((sd->vpbe_type == VPBE_VERSION_3) + || (sd->vpbe_type == VPBE_VERSION_2)) { + winmd_mask |= OSD_OSDWIN1MD_BMP1MD; + switch (lconfig->pixfmt) { + case PIXFMT_RGB565: + winmd |= + (1 << OSD_OSDWIN1MD_BMP1MD_SHIFT); + break; + case PIXFMT_RGB888: + winmd |= + (2 << OSD_OSDWIN1MD_BMP1MD_SHIFT); + _osd_enable_rgb888_pixblend(sd, + OSDWIN_OSD1); + break; + case PIXFMT_YCBCRI: + case PIXFMT_YCRCBI: + winmd |= + (3 << OSD_OSDWIN1MD_BMP1MD_SHIFT); + break; + default: + break; + } + } + + winmd_mask |= OSD_OSDWIN1MD_BMW1; + switch (lconfig->pixfmt) { + case PIXFMT_1BPP: + bmw = 0; + break; + case PIXFMT_2BPP: + bmw = 1; + break; + case PIXFMT_4BPP: + bmw = 2; + break; + case PIXFMT_8BPP: + bmw = 3; + break; + default: + break; + } + winmd |= (bmw << OSD_OSDWIN1MD_BMW1_SHIFT); + } + + winmd_mask |= OSD_OSDWIN1MD_OFF1; + if (lconfig->interlaced) + winmd |= OSD_OSDWIN1MD_OFF1; + + osd_modify(sd, winmd_mask, winmd, OSD_OSDWIN1MD); + osd_write(sd, lconfig->line_length >> 5, OSD_OSDWIN1OFST); + osd_write(sd, lconfig->xpos, OSD_OSDWIN1XP); + osd_write(sd, lconfig->xsize, OSD_OSDWIN1XL); + if (lconfig->interlaced) { + osd_write(sd, lconfig->ypos >> 1, OSD_OSDWIN1YP); + osd_write(sd, lconfig->ysize >> 1, OSD_OSDWIN1YL); + } else { + osd_write(sd, lconfig->ypos, OSD_OSDWIN1YP); + osd_write(sd, lconfig->ysize, OSD_OSDWIN1YL); + } + break; + case WIN_VID1: + winmd_mask |= OSD_VIDWINMD_VFF1; + if (lconfig->interlaced) + winmd |= OSD_VIDWINMD_VFF1; + + osd_modify(sd, winmd_mask, winmd, OSD_VIDWINMD); + osd_write(sd, lconfig->line_length >> 5, OSD_VIDWIN1OFST); + osd_write(sd, lconfig->xpos, OSD_VIDWIN1XP); + osd_write(sd, lconfig->xsize, OSD_VIDWIN1XL); + /* + * For YUV420P format the register contents are + * duplicated in both VID registers + */ + if (sd->vpbe_type == VPBE_VERSION_2) { + if (lconfig->pixfmt == PIXFMT_NV12) { + /* other window also */ + if (lconfig->interlaced) { + winmd_mask |= OSD_VIDWINMD_VFF0; + winmd |= OSD_VIDWINMD_VFF0; + osd_modify(sd, winmd_mask, winmd, + OSD_VIDWINMD); + } + osd_modify(sd, OSD_MISCCTL_S420D, + OSD_MISCCTL_S420D, OSD_MISCCTL); + osd_write(sd, lconfig->line_length >> 5, + OSD_VIDWIN0OFST); + osd_write(sd, lconfig->xpos, OSD_VIDWIN0XP); + osd_write(sd, lconfig->xsize, OSD_VIDWIN0XL); + } else { + osd_modify(sd, OSD_MISCCTL_S420D, + ~OSD_MISCCTL_S420D, OSD_MISCCTL); + } + } + + if (lconfig->interlaced) { + osd_write(sd, lconfig->ypos >> 1, OSD_VIDWIN1YP); + osd_write(sd, lconfig->ysize >> 1, OSD_VIDWIN1YL); + if ((sd->vpbe_type == VPBE_VERSION_2) && + lconfig->pixfmt == PIXFMT_NV12) { + osd_write(sd, lconfig->ypos >> 1, + OSD_VIDWIN0YP); + osd_write(sd, lconfig->ysize >> 1, + OSD_VIDWIN0YL); + } + } else { + osd_write(sd, lconfig->ypos, OSD_VIDWIN1YP); + osd_write(sd, lconfig->ysize, OSD_VIDWIN1YL); + if ((sd->vpbe_type == VPBE_VERSION_2) && + lconfig->pixfmt == PIXFMT_NV12) { + osd_write(sd, lconfig->ypos, OSD_VIDWIN0YP); + osd_write(sd, lconfig->ysize, OSD_VIDWIN0YL); + } + } + break; + } +} + +static int osd_set_layer_config(struct osd_state *sd, enum osd_layer layer, + struct osd_layer_config *lconfig) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + struct osd_layer_config *cfg = &win->lconfig; + unsigned long flags; + int reject_config; + + spin_lock_irqsave(&osd->lock, flags); + + reject_config = try_layer_config(sd, layer, lconfig); + if (reject_config) { + spin_unlock_irqrestore(&osd->lock, flags); + return reject_config; + } + + /* update the current Cb/Cr order */ + if (is_yc_pixfmt(lconfig->pixfmt)) + osd->yc_pixfmt = lconfig->pixfmt; + + /* + * If we are switching OSD1 from normal mode to attribute mode or from + * attribute mode to normal mode, then we must disable the window. + */ + if (layer == WIN_OSD1) { + if (((lconfig->pixfmt == PIXFMT_OSD_ATTR) && + (cfg->pixfmt != PIXFMT_OSD_ATTR)) || + ((lconfig->pixfmt != PIXFMT_OSD_ATTR) && + (cfg->pixfmt == PIXFMT_OSD_ATTR))) { + win->is_enabled = 0; + _osd_disable_layer(sd, layer); + } + } + + _osd_set_layer_config(sd, layer, lconfig); + + if (layer == WIN_OSD1) { + struct osd_osdwin_state *osdwin_state = + &osd->osdwin[OSDWIN_OSD1]; + + if ((lconfig->pixfmt != PIXFMT_OSD_ATTR) && + (cfg->pixfmt == PIXFMT_OSD_ATTR)) { + /* + * We just switched OSD1 from attribute mode to normal + * mode, so we must initialize the CLUT select, the + * blend factor, transparency colorkey enable, and + * attenuation enable (DM6446 only) bits in the + * OSDWIN1MD register. + */ + _osd_set_osd_clut(sd, OSDWIN_OSD1, + osdwin_state->clut); + _osd_set_blending_factor(sd, OSDWIN_OSD1, + osdwin_state->blend); + if (osdwin_state->colorkey_blending) { + _osd_enable_color_key(sd, OSDWIN_OSD1, + osdwin_state-> + colorkey, + lconfig->pixfmt); + } else + _osd_disable_color_key(sd, OSDWIN_OSD1); + _osd_set_rec601_attenuation(sd, OSDWIN_OSD1, + osdwin_state-> + rec601_attenuation); + } else if ((lconfig->pixfmt == PIXFMT_OSD_ATTR) && + (cfg->pixfmt != PIXFMT_OSD_ATTR)) { + /* + * We just switched OSD1 from normal mode to attribute + * mode, so we must initialize the blink enable and + * blink interval bits in the OSDATRMD register. + */ + _osd_set_blink_attribute(sd, osd->is_blinking, + osd->blink); + } + } + + /* + * If we just switched to a 1-, 2-, or 4-bits-per-pixel bitmap format + * then configure a default palette map. + */ + if ((lconfig->pixfmt != cfg->pixfmt) && + ((lconfig->pixfmt == PIXFMT_1BPP) || + (lconfig->pixfmt == PIXFMT_2BPP) || + (lconfig->pixfmt == PIXFMT_4BPP))) { + enum osd_win_layer osdwin = + ((layer == WIN_OSD0) ? OSDWIN_OSD0 : OSDWIN_OSD1); + struct osd_osdwin_state *osdwin_state = + &osd->osdwin[osdwin]; + unsigned char clut_index; + unsigned char clut_entries = 0; + + switch (lconfig->pixfmt) { + case PIXFMT_1BPP: + clut_entries = 2; + break; + case PIXFMT_2BPP: + clut_entries = 4; + break; + case PIXFMT_4BPP: + clut_entries = 16; + break; + default: + break; + } + /* + * The default palette map maps the pixel value to the clut + * index, i.e. pixel value 0 maps to clut entry 0, pixel value + * 1 maps to clut entry 1, etc. + */ + for (clut_index = 0; clut_index < 16; clut_index++) { + osdwin_state->palette_map[clut_index] = clut_index; + if (clut_index < clut_entries) { + _osd_set_palette_map(sd, osdwin, clut_index, + clut_index, + lconfig->pixfmt); + } + } + } + + *cfg = *lconfig; + /* DM6446: configure the RGB888 enable and window selection */ + if (osd->win[WIN_VID0].lconfig.pixfmt == PIXFMT_RGB888) + _osd_enable_vid_rgb888(sd, WIN_VID0); + else if (osd->win[WIN_VID1].lconfig.pixfmt == PIXFMT_RGB888) + _osd_enable_vid_rgb888(sd, WIN_VID1); + else + _osd_disable_vid_rgb888(sd); + + if (layer == WIN_VID0) { + osd->pingpong = + _osd_dm6446_vid0_pingpong(sd, osd->field_inversion, + win->fb_base_phys, + cfg); + } + + spin_unlock_irqrestore(&osd->lock, flags); + + return 0; +} + +static void osd_init_layer(struct osd_state *sd, enum osd_layer layer) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + enum osd_win_layer osdwin; + struct osd_osdwin_state *osdwin_state; + struct osd_layer_config *cfg = &win->lconfig; + unsigned long flags; + + spin_lock_irqsave(&osd->lock, flags); + + win->is_enabled = 0; + _osd_disable_layer(sd, layer); + + win->h_zoom = ZOOM_X1; + win->v_zoom = ZOOM_X1; + _osd_set_zoom(sd, layer, win->h_zoom, win->v_zoom); + + win->fb_base_phys = 0; + _osd_start_layer(sd, layer, win->fb_base_phys, 0); + + cfg->line_length = 0; + cfg->xsize = 0; + cfg->ysize = 0; + cfg->xpos = 0; + cfg->ypos = 0; + cfg->interlaced = 0; + switch (layer) { + case WIN_OSD0: + case WIN_OSD1: + osdwin = (layer == WIN_OSD0) ? OSDWIN_OSD0 : OSDWIN_OSD1; + osdwin_state = &osd->osdwin[osdwin]; + /* + * Other code relies on the fact that OSD windows default to a + * bitmap pixel format when they are deallocated, so don't + * change this default pixel format. + */ + cfg->pixfmt = PIXFMT_8BPP; + _osd_set_layer_config(sd, layer, cfg); + osdwin_state->clut = RAM_CLUT; + _osd_set_osd_clut(sd, osdwin, osdwin_state->clut); + osdwin_state->colorkey_blending = 0; + _osd_disable_color_key(sd, osdwin); + osdwin_state->blend = OSD_8_VID_0; + _osd_set_blending_factor(sd, osdwin, osdwin_state->blend); + osdwin_state->rec601_attenuation = 0; + _osd_set_rec601_attenuation(sd, osdwin, + osdwin_state-> + rec601_attenuation); + if (osdwin == OSDWIN_OSD1) { + osd->is_blinking = 0; + osd->blink = BLINK_X1; + } + break; + case WIN_VID0: + case WIN_VID1: + cfg->pixfmt = osd->yc_pixfmt; + _osd_set_layer_config(sd, layer, cfg); + break; + } + + spin_unlock_irqrestore(&osd->lock, flags); +} + +static void osd_release_layer(struct osd_state *sd, enum osd_layer layer) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + unsigned long flags; + + spin_lock_irqsave(&osd->lock, flags); + + if (!win->is_allocated) { + spin_unlock_irqrestore(&osd->lock, flags); + return; + } + + spin_unlock_irqrestore(&osd->lock, flags); + osd_init_layer(sd, layer); + spin_lock_irqsave(&osd->lock, flags); + + win->is_allocated = 0; + + spin_unlock_irqrestore(&osd->lock, flags); +} + +static int osd_request_layer(struct osd_state *sd, enum osd_layer layer) +{ + struct osd_state *osd = sd; + struct osd_window_state *win = &osd->win[layer]; + unsigned long flags; + + spin_lock_irqsave(&osd->lock, flags); + + if (win->is_allocated) { + spin_unlock_irqrestore(&osd->lock, flags); + return -1; + } + win->is_allocated = 1; + + spin_unlock_irqrestore(&osd->lock, flags); + + return 0; +} + +static void _osd_init(struct osd_state *sd) +{ + osd_write(sd, 0, OSD_MODE); + osd_write(sd, 0, OSD_VIDWINMD); + osd_write(sd, 0, OSD_OSDWIN0MD); + osd_write(sd, 0, OSD_OSDWIN1MD); + osd_write(sd, 0, OSD_RECTCUR); + osd_write(sd, 0, OSD_MISCCTL); + if (sd->vpbe_type == VPBE_VERSION_3) { + osd_write(sd, 0, OSD_VBNDRY); + osd_write(sd, 0, OSD_EXTMODE); + osd_write(sd, OSD_MISCCTL_DMANG, OSD_MISCCTL); + } +} + +static void osd_set_left_margin(struct osd_state *sd, u32 val) +{ + osd_write(sd, val, OSD_BASEPX); +} + +static void osd_set_top_margin(struct osd_state *sd, u32 val) +{ + osd_write(sd, val, OSD_BASEPY); +} + +static int osd_initialize(struct osd_state *osd) +{ + if (osd == NULL) + return -ENODEV; + _osd_init(osd); + + /* set default Cb/Cr order */ + osd->yc_pixfmt = PIXFMT_YCBCRI; + + if (osd->vpbe_type == VPBE_VERSION_3) { + /* + * ROM CLUT1 on the DM355 is similar (identical?) to ROM CLUT0 + * on the DM6446, so make ROM_CLUT1 the default on the DM355. + */ + osd->rom_clut = ROM_CLUT1; + } + + _osd_set_field_inversion(osd, osd->field_inversion); + _osd_set_rom_clut(osd, osd->rom_clut); + + osd_init_layer(osd, WIN_OSD0); + osd_init_layer(osd, WIN_VID0); + osd_init_layer(osd, WIN_OSD1); + osd_init_layer(osd, WIN_VID1); + + return 0; +} + +static const struct vpbe_osd_ops osd_ops = { + .initialize = osd_initialize, + .request_layer = osd_request_layer, + .release_layer = osd_release_layer, + .enable_layer = osd_enable_layer, + .disable_layer = osd_disable_layer, + .set_layer_config = osd_set_layer_config, + .get_layer_config = osd_get_layer_config, + .start_layer = osd_start_layer, + .set_left_margin = osd_set_left_margin, + .set_top_margin = osd_set_top_margin, +}; + +static int osd_probe(struct platform_device *pdev) +{ + const struct platform_device_id *pdev_id; + struct osd_state *osd; + struct resource *res; + + pdev_id = platform_get_device_id(pdev); + if (!pdev_id) + return -EINVAL; + + osd = devm_kzalloc(&pdev->dev, sizeof(struct osd_state), GFP_KERNEL); + if (osd == NULL) + return -ENOMEM; + + + osd->dev = &pdev->dev; + osd->vpbe_type = pdev_id->driver_data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + osd->osd_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(osd->osd_base)) + return PTR_ERR(osd->osd_base); + + osd->osd_base_phys = res->start; + osd->osd_size = resource_size(res); + spin_lock_init(&osd->lock); + osd->ops = osd_ops; + platform_set_drvdata(pdev, osd); + dev_notice(osd->dev, "OSD sub device probe success\n"); + + return 0; +} + +static int osd_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver osd_driver = { + .probe = osd_probe, + .remove = osd_remove, + .driver = { + .name = MODULE_NAME, + }, + .id_table = vpbe_osd_devtype +}; + +module_platform_driver(osd_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("DaVinci OSD Manager Driver"); +MODULE_AUTHOR("Texas Instruments"); diff --git a/drivers/media/platform/ti/davinci/vpbe_osd_regs.h b/drivers/media/platform/ti/davinci/vpbe_osd_regs.h new file mode 100644 index 000000000..cecd5991d --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpbe_osd_regs.h @@ -0,0 +1,352 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2006-2010 Texas Instruments Inc + */ +#ifndef _VPBE_OSD_REGS_H +#define _VPBE_OSD_REGS_H + +/* VPBE Global Registers */ +#define VPBE_PID 0x0 +#define VPBE_PCR 0x4 + +/* VPSS CLock Registers */ +#define VPSSCLK_PID 0x00 +#define VPSSCLK_CLKCTRL 0x04 + +/* VPSS Buffer Logic Registers */ +#define VPSSBL_PID 0x00 +#define VPSSBL_PCR 0x04 +#define VPSSBL_BCR 0x08 +#define VPSSBL_INTSTAT 0x0C +#define VPSSBL_INTSEL 0x10 +#define VPSSBL_EVTSEL 0x14 +#define VPSSBL_MEMCTRL 0x18 +#define VPSSBL_CCDCMUX 0x1C + +/* DM365 ISP5 system configuration */ +#define ISP5_PID 0x0 +#define ISP5_PCCR 0x4 +#define ISP5_BCR 0x8 +#define ISP5_INTSTAT 0xC +#define ISP5_INTSEL1 0x10 +#define ISP5_INTSEL2 0x14 +#define ISP5_INTSEL3 0x18 +#define ISP5_EVTSEL 0x1c +#define ISP5_CCDCMUX 0x20 + +/* VPBE On-Screen Display Subsystem Registers (OSD) */ +#define OSD_MODE 0x00 +#define OSD_VIDWINMD 0x04 +#define OSD_OSDWIN0MD 0x08 +#define OSD_OSDWIN1MD 0x0C +#define OSD_OSDATRMD 0x0C +#define OSD_RECTCUR 0x10 +#define OSD_VIDWIN0OFST 0x18 +#define OSD_VIDWIN1OFST 0x1C +#define OSD_OSDWIN0OFST 0x20 +#define OSD_OSDWIN1OFST 0x24 +#define OSD_VIDWINADH 0x28 +#define OSD_VIDWIN0ADL 0x2C +#define OSD_VIDWIN0ADR 0x2C +#define OSD_VIDWIN1ADL 0x30 +#define OSD_VIDWIN1ADR 0x30 +#define OSD_OSDWINADH 0x34 +#define OSD_OSDWIN0ADL 0x38 +#define OSD_OSDWIN0ADR 0x38 +#define OSD_OSDWIN1ADL 0x3C +#define OSD_OSDWIN1ADR 0x3C +#define OSD_BASEPX 0x40 +#define OSD_BASEPY 0x44 +#define OSD_VIDWIN0XP 0x48 +#define OSD_VIDWIN0YP 0x4C +#define OSD_VIDWIN0XL 0x50 +#define OSD_VIDWIN0YL 0x54 +#define OSD_VIDWIN1XP 0x58 +#define OSD_VIDWIN1YP 0x5C +#define OSD_VIDWIN1XL 0x60 +#define OSD_VIDWIN1YL 0x64 +#define OSD_OSDWIN0XP 0x68 +#define OSD_OSDWIN0YP 0x6C +#define OSD_OSDWIN0XL 0x70 +#define OSD_OSDWIN0YL 0x74 +#define OSD_OSDWIN1XP 0x78 +#define OSD_OSDWIN1YP 0x7C +#define OSD_OSDWIN1XL 0x80 +#define OSD_OSDWIN1YL 0x84 +#define OSD_CURXP 0x88 +#define OSD_CURYP 0x8C +#define OSD_CURXL 0x90 +#define OSD_CURYL 0x94 +#define OSD_W0BMP01 0xA0 +#define OSD_W0BMP23 0xA4 +#define OSD_W0BMP45 0xA8 +#define OSD_W0BMP67 0xAC +#define OSD_W0BMP89 0xB0 +#define OSD_W0BMPAB 0xB4 +#define OSD_W0BMPCD 0xB8 +#define OSD_W0BMPEF 0xBC +#define OSD_W1BMP01 0xC0 +#define OSD_W1BMP23 0xC4 +#define OSD_W1BMP45 0xC8 +#define OSD_W1BMP67 0xCC +#define OSD_W1BMP89 0xD0 +#define OSD_W1BMPAB 0xD4 +#define OSD_W1BMPCD 0xD8 +#define OSD_W1BMPEF 0xDC +#define OSD_VBNDRY 0xE0 +#define OSD_EXTMODE 0xE4 +#define OSD_MISCCTL 0xE8 +#define OSD_CLUTRAMYCB 0xEC +#define OSD_CLUTRAMCR 0xF0 +#define OSD_TRANSPVAL 0xF4 +#define OSD_TRANSPVALL 0xF4 +#define OSD_TRANSPVALU 0xF8 +#define OSD_TRANSPBMPIDX 0xFC +#define OSD_PPVWIN0ADR 0xFC + +/* bit definitions */ +#define VPBE_PCR_VENC_DIV (1 << 1) +#define VPBE_PCR_CLK_OFF (1 << 0) + +#define VPSSBL_INTSTAT_HSSIINT (1 << 14) +#define VPSSBL_INTSTAT_CFALDINT (1 << 13) +#define VPSSBL_INTSTAT_IPIPE_INT5 (1 << 12) +#define VPSSBL_INTSTAT_IPIPE_INT4 (1 << 11) +#define VPSSBL_INTSTAT_IPIPE_INT3 (1 << 10) +#define VPSSBL_INTSTAT_IPIPE_INT2 (1 << 9) +#define VPSSBL_INTSTAT_IPIPE_INT1 (1 << 8) +#define VPSSBL_INTSTAT_IPIPE_INT0 (1 << 7) +#define VPSSBL_INTSTAT_IPIPEIFINT (1 << 6) +#define VPSSBL_INTSTAT_OSDINT (1 << 5) +#define VPSSBL_INTSTAT_VENCINT (1 << 4) +#define VPSSBL_INTSTAT_H3AINT (1 << 3) +#define VPSSBL_INTSTAT_CCDC_VDINT2 (1 << 2) +#define VPSSBL_INTSTAT_CCDC_VDINT1 (1 << 1) +#define VPSSBL_INTSTAT_CCDC_VDINT0 (1 << 0) + +/* DM365 ISP5 bit definitions */ +#define ISP5_INTSTAT_VENCINT (1 << 21) +#define ISP5_INTSTAT_OSDINT (1 << 20) + +/* VMOD TVTYP options for HDMD=0 */ +#define SDTV_NTSC 0 +#define SDTV_PAL 1 +/* VMOD TVTYP options for HDMD=1 */ +#define HDTV_525P 0 +#define HDTV_625P 1 +#define HDTV_1080I 2 +#define HDTV_720P 3 + +#define OSD_MODE_CS (1 << 15) +#define OSD_MODE_OVRSZ (1 << 14) +#define OSD_MODE_OHRSZ (1 << 13) +#define OSD_MODE_EF (1 << 12) +#define OSD_MODE_VVRSZ (1 << 11) +#define OSD_MODE_VHRSZ (1 << 10) +#define OSD_MODE_FSINV (1 << 9) +#define OSD_MODE_BCLUT (1 << 8) +#define OSD_MODE_CABG_SHIFT 0 +#define OSD_MODE_CABG (0xff << 0) + +#define OSD_VIDWINMD_VFINV (1 << 15) +#define OSD_VIDWINMD_V1EFC (1 << 14) +#define OSD_VIDWINMD_VHZ1_SHIFT 12 +#define OSD_VIDWINMD_VHZ1 (3 << 12) +#define OSD_VIDWINMD_VVZ1_SHIFT 10 +#define OSD_VIDWINMD_VVZ1 (3 << 10) +#define OSD_VIDWINMD_VFF1 (1 << 9) +#define OSD_VIDWINMD_ACT1 (1 << 8) +#define OSD_VIDWINMD_V0EFC (1 << 6) +#define OSD_VIDWINMD_VHZ0_SHIFT 4 +#define OSD_VIDWINMD_VHZ0 (3 << 4) +#define OSD_VIDWINMD_VVZ0_SHIFT 2 +#define OSD_VIDWINMD_VVZ0 (3 << 2) +#define OSD_VIDWINMD_VFF0 (1 << 1) +#define OSD_VIDWINMD_ACT0 (1 << 0) + +#define OSD_OSDWIN0MD_ATN0E (1 << 14) +#define OSD_OSDWIN0MD_RGB0E (1 << 13) +#define OSD_OSDWIN0MD_BMP0MD_SHIFT 13 +#define OSD_OSDWIN0MD_BMP0MD (3 << 13) +#define OSD_OSDWIN0MD_CLUTS0 (1 << 12) +#define OSD_OSDWIN0MD_OHZ0_SHIFT 10 +#define OSD_OSDWIN0MD_OHZ0 (3 << 10) +#define OSD_OSDWIN0MD_OVZ0_SHIFT 8 +#define OSD_OSDWIN0MD_OVZ0 (3 << 8) +#define OSD_OSDWIN0MD_BMW0_SHIFT 6 +#define OSD_OSDWIN0MD_BMW0 (3 << 6) +#define OSD_OSDWIN0MD_BLND0_SHIFT 3 +#define OSD_OSDWIN0MD_BLND0 (7 << 3) +#define OSD_OSDWIN0MD_TE0 (1 << 2) +#define OSD_OSDWIN0MD_OFF0 (1 << 1) +#define OSD_OSDWIN0MD_OACT0 (1 << 0) + +#define OSD_OSDWIN1MD_OASW (1 << 15) +#define OSD_OSDWIN1MD_ATN1E (1 << 14) +#define OSD_OSDWIN1MD_RGB1E (1 << 13) +#define OSD_OSDWIN1MD_BMP1MD_SHIFT 13 +#define OSD_OSDWIN1MD_BMP1MD (3 << 13) +#define OSD_OSDWIN1MD_CLUTS1 (1 << 12) +#define OSD_OSDWIN1MD_OHZ1_SHIFT 10 +#define OSD_OSDWIN1MD_OHZ1 (3 << 10) +#define OSD_OSDWIN1MD_OVZ1_SHIFT 8 +#define OSD_OSDWIN1MD_OVZ1 (3 << 8) +#define OSD_OSDWIN1MD_BMW1_SHIFT 6 +#define OSD_OSDWIN1MD_BMW1 (3 << 6) +#define OSD_OSDWIN1MD_BLND1_SHIFT 3 +#define OSD_OSDWIN1MD_BLND1 (7 << 3) +#define OSD_OSDWIN1MD_TE1 (1 << 2) +#define OSD_OSDWIN1MD_OFF1 (1 << 1) +#define OSD_OSDWIN1MD_OACT1 (1 << 0) + +#define OSD_OSDATRMD_OASW (1 << 15) +#define OSD_OSDATRMD_OHZA_SHIFT 10 +#define OSD_OSDATRMD_OHZA (3 << 10) +#define OSD_OSDATRMD_OVZA_SHIFT 8 +#define OSD_OSDATRMD_OVZA (3 << 8) +#define OSD_OSDATRMD_BLNKINT_SHIFT 6 +#define OSD_OSDATRMD_BLNKINT (3 << 6) +#define OSD_OSDATRMD_OFFA (1 << 1) +#define OSD_OSDATRMD_BLNK (1 << 0) + +#define OSD_RECTCUR_RCAD_SHIFT 8 +#define OSD_RECTCUR_RCAD (0xff << 8) +#define OSD_RECTCUR_CLUTSR (1 << 7) +#define OSD_RECTCUR_RCHW_SHIFT 4 +#define OSD_RECTCUR_RCHW (7 << 4) +#define OSD_RECTCUR_RCVW_SHIFT 1 +#define OSD_RECTCUR_RCVW (7 << 1) +#define OSD_RECTCUR_RCACT (1 << 0) + +#define OSD_VIDWIN0OFST_V0LO (0x1ff << 0) + +#define OSD_VIDWIN1OFST_V1LO (0x1ff << 0) + +#define OSD_OSDWIN0OFST_O0LO (0x1ff << 0) + +#define OSD_OSDWIN1OFST_O1LO (0x1ff << 0) + +#define OSD_WINOFST_AH_SHIFT 9 + +#define OSD_VIDWIN0OFST_V0AH (0xf << 9) +#define OSD_VIDWIN1OFST_V1AH (0xf << 9) +#define OSD_OSDWIN0OFST_O0AH (0xf << 9) +#define OSD_OSDWIN1OFST_O1AH (0xf << 9) + +#define OSD_VIDWINADH_V1AH_SHIFT 8 +#define OSD_VIDWINADH_V1AH (0x7f << 8) +#define OSD_VIDWINADH_V0AH_SHIFT 0 +#define OSD_VIDWINADH_V0AH (0x7f << 0) + +#define OSD_VIDWIN0ADL_V0AL (0xffff << 0) + +#define OSD_VIDWIN1ADL_V1AL (0xffff << 0) + +#define OSD_OSDWINADH_O1AH_SHIFT 8 +#define OSD_OSDWINADH_O1AH (0x7f << 8) +#define OSD_OSDWINADH_O0AH_SHIFT 0 +#define OSD_OSDWINADH_O0AH (0x7f << 0) + +#define OSD_OSDWIN0ADL_O0AL (0xffff << 0) + +#define OSD_OSDWIN1ADL_O1AL (0xffff << 0) + +#define OSD_BASEPX_BPX (0x3ff << 0) + +#define OSD_BASEPY_BPY (0x1ff << 0) + +#define OSD_VIDWIN0XP_V0X (0x7ff << 0) + +#define OSD_VIDWIN0YP_V0Y (0x7ff << 0) + +#define OSD_VIDWIN0XL_V0W (0x7ff << 0) + +#define OSD_VIDWIN0YL_V0H (0x7ff << 0) + +#define OSD_VIDWIN1XP_V1X (0x7ff << 0) + +#define OSD_VIDWIN1YP_V1Y (0x7ff << 0) + +#define OSD_VIDWIN1XL_V1W (0x7ff << 0) + +#define OSD_VIDWIN1YL_V1H (0x7ff << 0) + +#define OSD_OSDWIN0XP_W0X (0x7ff << 0) + +#define OSD_OSDWIN0YP_W0Y (0x7ff << 0) + +#define OSD_OSDWIN0XL_W0W (0x7ff << 0) + +#define OSD_OSDWIN0YL_W0H (0x7ff << 0) + +#define OSD_OSDWIN1XP_W1X (0x7ff << 0) + +#define OSD_OSDWIN1YP_W1Y (0x7ff << 0) + +#define OSD_OSDWIN1XL_W1W (0x7ff << 0) + +#define OSD_OSDWIN1YL_W1H (0x7ff << 0) + +#define OSD_CURXP_RCSX (0x7ff << 0) + +#define OSD_CURYP_RCSY (0x7ff << 0) + +#define OSD_CURXL_RCSW (0x7ff << 0) + +#define OSD_CURYL_RCSH (0x7ff << 0) + +#define OSD_EXTMODE_EXPMDSEL (1 << 15) +#define OSD_EXTMODE_SCRNHEXP_SHIFT 13 +#define OSD_EXTMODE_SCRNHEXP (3 << 13) +#define OSD_EXTMODE_SCRNVEXP (1 << 12) +#define OSD_EXTMODE_OSD1BLDCHR (1 << 11) +#define OSD_EXTMODE_OSD0BLDCHR (1 << 10) +#define OSD_EXTMODE_ATNOSD1EN (1 << 9) +#define OSD_EXTMODE_ATNOSD0EN (1 << 8) +#define OSD_EXTMODE_OSDHRSZ15 (1 << 7) +#define OSD_EXTMODE_VIDHRSZ15 (1 << 6) +#define OSD_EXTMODE_ZMFILV1HEN (1 << 5) +#define OSD_EXTMODE_ZMFILV1VEN (1 << 4) +#define OSD_EXTMODE_ZMFILV0HEN (1 << 3) +#define OSD_EXTMODE_ZMFILV0VEN (1 << 2) +#define OSD_EXTMODE_EXPFILHEN (1 << 1) +#define OSD_EXTMODE_EXPFILVEN (1 << 0) + +#define OSD_MISCCTL_BLDSEL (1 << 15) +#define OSD_MISCCTL_S420D (1 << 14) +#define OSD_MISCCTL_BMAPT (1 << 13) +#define OSD_MISCCTL_DM365M (1 << 12) +#define OSD_MISCCTL_RGBEN (1 << 7) +#define OSD_MISCCTL_RGBWIN (1 << 6) +#define OSD_MISCCTL_DMANG (1 << 6) +#define OSD_MISCCTL_TMON (1 << 5) +#define OSD_MISCCTL_RSEL (1 << 4) +#define OSD_MISCCTL_CPBSY (1 << 3) +#define OSD_MISCCTL_PPSW (1 << 2) +#define OSD_MISCCTL_PPRV (1 << 1) + +#define OSD_CLUTRAMYCB_Y_SHIFT 8 +#define OSD_CLUTRAMYCB_Y (0xff << 8) +#define OSD_CLUTRAMYCB_CB_SHIFT 0 +#define OSD_CLUTRAMYCB_CB (0xff << 0) + +#define OSD_CLUTRAMCR_CR_SHIFT 8 +#define OSD_CLUTRAMCR_CR (0xff << 8) +#define OSD_CLUTRAMCR_CADDR_SHIFT 0 +#define OSD_CLUTRAMCR_CADDR (0xff << 0) + +#define OSD_TRANSPVAL_RGBTRANS (0xffff << 0) + +#define OSD_TRANSPVALL_RGBL (0xffff << 0) + +#define OSD_TRANSPVALU_Y_SHIFT 8 +#define OSD_TRANSPVALU_Y (0xff << 8) +#define OSD_TRANSPVALU_RGBU_SHIFT 0 +#define OSD_TRANSPVALU_RGBU (0xff << 0) + +#define OSD_TRANSPBMPIDX_BMP1_SHIFT 8 +#define OSD_TRANSPBMPIDX_BMP1 (0xff << 8) +#define OSD_TRANSPBMPIDX_BMP0_SHIFT 0 +#define OSD_TRANSPBMPIDX_BMP0 0xff + +#endif /* _DAVINCI_VPBE_H_ */ diff --git a/drivers/media/platform/ti/davinci/vpbe_venc.c b/drivers/media/platform/ti/davinci/vpbe_venc.c new file mode 100644 index 000000000..4c8e31de1 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpbe_venc.c @@ -0,0 +1,676 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2010 Texas Instruments Inc + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +#include "vpbe_venc_regs.h" + +#define MODULE_NAME "davinci-vpbe-venc" + +static const struct platform_device_id vpbe_venc_devtype[] = { + { + .name = DM644X_VPBE_VENC_SUBDEV_NAME, + .driver_data = VPBE_VERSION_1, + }, { + .name = DM365_VPBE_VENC_SUBDEV_NAME, + .driver_data = VPBE_VERSION_2, + }, { + .name = DM355_VPBE_VENC_SUBDEV_NAME, + .driver_data = VPBE_VERSION_3, + }, + { + /* sentinel */ + } +}; + +MODULE_DEVICE_TABLE(platform, vpbe_venc_devtype); + +static int debug = 2; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Debug level 0-2"); + +struct venc_state { + struct v4l2_subdev sd; + struct venc_callback *callback; + struct venc_platform_data *pdata; + struct device *pdev; + u32 output; + v4l2_std_id std; + spinlock_t lock; + void __iomem *venc_base; + void __iomem *vdaccfg_reg; + enum vpbe_version venc_type; +}; + +static inline struct venc_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct venc_state, sd); +} + +static inline u32 venc_read(struct v4l2_subdev *sd, u32 offset) +{ + struct venc_state *venc = to_state(sd); + + return readl(venc->venc_base + offset); +} + +static inline u32 venc_write(struct v4l2_subdev *sd, u32 offset, u32 val) +{ + struct venc_state *venc = to_state(sd); + + writel(val, (venc->venc_base + offset)); + + return val; +} + +static inline u32 venc_modify(struct v4l2_subdev *sd, u32 offset, + u32 val, u32 mask) +{ + u32 new_val = (venc_read(sd, offset) & ~mask) | (val & mask); + + venc_write(sd, offset, new_val); + + return new_val; +} + +static inline u32 vdaccfg_write(struct v4l2_subdev *sd, u32 val) +{ + struct venc_state *venc = to_state(sd); + + writel(val, venc->vdaccfg_reg); + + val = readl(venc->vdaccfg_reg); + + return val; +} + +#define VDAC_COMPONENT 0x543 +#define VDAC_S_VIDEO 0x210 +/* This function sets the dac of the VPBE for various outputs + */ +static int venc_set_dac(struct v4l2_subdev *sd, u32 out_index) +{ + switch (out_index) { + case 0: + v4l2_dbg(debug, 1, sd, "Setting output to Composite\n"); + venc_write(sd, VENC_DACSEL, 0); + break; + case 1: + v4l2_dbg(debug, 1, sd, "Setting output to Component\n"); + venc_write(sd, VENC_DACSEL, VDAC_COMPONENT); + break; + case 2: + v4l2_dbg(debug, 1, sd, "Setting output to S-video\n"); + venc_write(sd, VENC_DACSEL, VDAC_S_VIDEO); + break; + default: + return -EINVAL; + } + + return 0; +} + +static void venc_enabledigitaloutput(struct v4l2_subdev *sd, int benable) +{ + struct venc_state *venc = to_state(sd); + + v4l2_dbg(debug, 2, sd, "venc_enabledigitaloutput\n"); + + if (benable) { + venc_write(sd, VENC_VMOD, 0); + venc_write(sd, VENC_CVBS, 0); + venc_write(sd, VENC_LCDOUT, 0); + venc_write(sd, VENC_HSPLS, 0); + venc_write(sd, VENC_HSTART, 0); + venc_write(sd, VENC_HVALID, 0); + venc_write(sd, VENC_HINT, 0); + venc_write(sd, VENC_VSPLS, 0); + venc_write(sd, VENC_VSTART, 0); + venc_write(sd, VENC_VVALID, 0); + venc_write(sd, VENC_VINT, 0); + venc_write(sd, VENC_YCCCTL, 0); + venc_write(sd, VENC_DACSEL, 0); + + } else { + venc_write(sd, VENC_VMOD, 0); + /* disable VCLK output pin enable */ + venc_write(sd, VENC_VIDCTL, 0x141); + + /* Disable output sync pins */ + venc_write(sd, VENC_SYNCCTL, 0); + + /* Disable DCLOCK */ + venc_write(sd, VENC_DCLKCTL, 0); + venc_write(sd, VENC_DRGBX1, 0x0000057C); + + /* Disable LCD output control (accepting default polarity) */ + venc_write(sd, VENC_LCDOUT, 0); + if (venc->venc_type != VPBE_VERSION_3) + venc_write(sd, VENC_CMPNT, 0x100); + venc_write(sd, VENC_HSPLS, 0); + venc_write(sd, VENC_HINT, 0); + venc_write(sd, VENC_HSTART, 0); + venc_write(sd, VENC_HVALID, 0); + + venc_write(sd, VENC_VSPLS, 0); + venc_write(sd, VENC_VINT, 0); + venc_write(sd, VENC_VSTART, 0); + venc_write(sd, VENC_VVALID, 0); + + venc_write(sd, VENC_HSDLY, 0); + venc_write(sd, VENC_VSDLY, 0); + + venc_write(sd, VENC_YCCCTL, 0); + venc_write(sd, VENC_VSTARTA, 0); + + /* Set OSD clock and OSD Sync Adavance registers */ + venc_write(sd, VENC_OSDCLK0, 1); + venc_write(sd, VENC_OSDCLK1, 2); + } +} + +static void +venc_enable_vpss_clock(int venc_type, + enum vpbe_enc_timings_type type, + unsigned int pclock) +{ + if (venc_type == VPBE_VERSION_1) + return; + + if (venc_type == VPBE_VERSION_2 && (type == VPBE_ENC_STD || (type == + VPBE_ENC_DV_TIMINGS && pclock <= 27000000))) { + vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1); + vpss_enable_clock(VPSS_VPBE_CLOCK, 1); + return; + } + + if (venc_type == VPBE_VERSION_3 && type == VPBE_ENC_STD) + vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 0); +} + +#define VDAC_CONFIG_SD_V3 0x0E21A6B6 +#define VDAC_CONFIG_SD_V2 0x081141CF +/* + * setting NTSC mode + */ +static int venc_set_ntsc(struct v4l2_subdev *sd) +{ + struct venc_state *venc = to_state(sd); + struct venc_platform_data *pdata = venc->pdata; + + v4l2_dbg(debug, 2, sd, "venc_set_ntsc\n"); + + /* Setup clock at VPSS & VENC for SD */ + vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1); + if (pdata->setup_clock(VPBE_ENC_STD, V4L2_STD_525_60) < 0) + return -EINVAL; + + venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_STD, V4L2_STD_525_60); + venc_enabledigitaloutput(sd, 0); + + if (venc->venc_type == VPBE_VERSION_3) { + venc_write(sd, VENC_CLKCTL, 0x01); + venc_write(sd, VENC_VIDCTL, 0); + vdaccfg_write(sd, VDAC_CONFIG_SD_V3); + } else if (venc->venc_type == VPBE_VERSION_2) { + venc_write(sd, VENC_CLKCTL, 0x01); + venc_write(sd, VENC_VIDCTL, 0); + vdaccfg_write(sd, VDAC_CONFIG_SD_V2); + } else { + /* to set VENC CLK DIV to 1 - final clock is 54 MHz */ + venc_modify(sd, VENC_VIDCTL, 0, 1 << 1); + /* Set REC656 Mode */ + venc_write(sd, VENC_YCCCTL, 0x1); + venc_modify(sd, VENC_VDPRO, 0, VENC_VDPRO_DAFRQ); + venc_modify(sd, VENC_VDPRO, 0, VENC_VDPRO_DAUPS); + } + + venc_write(sd, VENC_VMOD, 0); + venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), + VENC_VMOD_VIE); + venc_modify(sd, VENC_VMOD, (0 << VENC_VMOD_VMD), VENC_VMOD_VMD); + venc_modify(sd, VENC_VMOD, (0 << VENC_VMOD_TVTYP_SHIFT), + VENC_VMOD_TVTYP); + venc_write(sd, VENC_DACTST, 0x0); + venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + + return 0; +} + +/* + * setting PAL mode + */ +static int venc_set_pal(struct v4l2_subdev *sd) +{ + struct venc_state *venc = to_state(sd); + + v4l2_dbg(debug, 2, sd, "venc_set_pal\n"); + + /* Setup clock at VPSS & VENC for SD */ + vpss_enable_clock(VPSS_VENC_CLOCK_SEL, 1); + if (venc->pdata->setup_clock(VPBE_ENC_STD, V4L2_STD_625_50) < 0) + return -EINVAL; + + venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_STD, V4L2_STD_625_50); + venc_enabledigitaloutput(sd, 0); + + if (venc->venc_type == VPBE_VERSION_3) { + venc_write(sd, VENC_CLKCTL, 0x1); + venc_write(sd, VENC_VIDCTL, 0); + vdaccfg_write(sd, VDAC_CONFIG_SD_V3); + } else if (venc->venc_type == VPBE_VERSION_2) { + venc_write(sd, VENC_CLKCTL, 0x1); + venc_write(sd, VENC_VIDCTL, 0); + vdaccfg_write(sd, VDAC_CONFIG_SD_V2); + } else { + /* to set VENC CLK DIV to 1 - final clock is 54 MHz */ + venc_modify(sd, VENC_VIDCTL, 0, 1 << 1); + /* Set REC656 Mode */ + venc_write(sd, VENC_YCCCTL, 0x1); + } + + venc_modify(sd, VENC_SYNCCTL, 1 << VENC_SYNCCTL_OVD_SHIFT, + VENC_SYNCCTL_OVD); + venc_write(sd, VENC_VMOD, 0); + venc_modify(sd, VENC_VMOD, + (1 << VENC_VMOD_VIE_SHIFT), + VENC_VMOD_VIE); + venc_modify(sd, VENC_VMOD, + (0 << VENC_VMOD_VMD), VENC_VMOD_VMD); + venc_modify(sd, VENC_VMOD, + (1 << VENC_VMOD_TVTYP_SHIFT), + VENC_VMOD_TVTYP); + venc_write(sd, VENC_DACTST, 0x0); + venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + + return 0; +} + +#define VDAC_CONFIG_HD_V2 0x081141EF +/* + * venc_set_480p59_94 + * + * This function configures the video encoder to EDTV(525p) component setting. + */ +static int venc_set_480p59_94(struct v4l2_subdev *sd) +{ + struct venc_state *venc = to_state(sd); + struct venc_platform_data *pdata = venc->pdata; + + v4l2_dbg(debug, 2, sd, "venc_set_480p59_94\n"); + if (venc->venc_type != VPBE_VERSION_1 && + venc->venc_type != VPBE_VERSION_2) + return -EINVAL; + + /* Setup clock at VPSS & VENC for SD */ + if (pdata->setup_clock(VPBE_ENC_DV_TIMINGS, 27000000) < 0) + return -EINVAL; + + venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_DV_TIMINGS, 27000000); + venc_enabledigitaloutput(sd, 0); + + if (venc->venc_type == VPBE_VERSION_2) + vdaccfg_write(sd, VDAC_CONFIG_HD_V2); + venc_write(sd, VENC_OSDCLK0, 0); + venc_write(sd, VENC_OSDCLK1, 1); + + if (venc->venc_type == VPBE_VERSION_1) { + venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAFRQ, + VENC_VDPRO_DAFRQ); + venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAUPS, + VENC_VDPRO_DAUPS); + } + + venc_write(sd, VENC_VMOD, 0); + venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), + VENC_VMOD_VIE); + venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD); + venc_modify(sd, VENC_VMOD, (HDTV_525P << VENC_VMOD_TVTYP_SHIFT), + VENC_VMOD_TVTYP); + venc_modify(sd, VENC_VMOD, VENC_VMOD_VDMD_YCBCR8 << + VENC_VMOD_VDMD_SHIFT, VENC_VMOD_VDMD); + + venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + + return 0; +} + +/* + * venc_set_625p + * + * This function configures the video encoder to HDTV(625p) component setting + */ +static int venc_set_576p50(struct v4l2_subdev *sd) +{ + struct venc_state *venc = to_state(sd); + struct venc_platform_data *pdata = venc->pdata; + + v4l2_dbg(debug, 2, sd, "venc_set_576p50\n"); + + if (venc->venc_type != VPBE_VERSION_1 && + venc->venc_type != VPBE_VERSION_2) + return -EINVAL; + /* Setup clock at VPSS & VENC for SD */ + if (pdata->setup_clock(VPBE_ENC_DV_TIMINGS, 27000000) < 0) + return -EINVAL; + + venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_DV_TIMINGS, 27000000); + venc_enabledigitaloutput(sd, 0); + + if (venc->venc_type == VPBE_VERSION_2) + vdaccfg_write(sd, VDAC_CONFIG_HD_V2); + + venc_write(sd, VENC_OSDCLK0, 0); + venc_write(sd, VENC_OSDCLK1, 1); + + if (venc->venc_type == VPBE_VERSION_1) { + venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAFRQ, + VENC_VDPRO_DAFRQ); + venc_modify(sd, VENC_VDPRO, VENC_VDPRO_DAUPS, + VENC_VDPRO_DAUPS); + } + + venc_write(sd, VENC_VMOD, 0); + venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), + VENC_VMOD_VIE); + venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD); + venc_modify(sd, VENC_VMOD, (HDTV_625P << VENC_VMOD_TVTYP_SHIFT), + VENC_VMOD_TVTYP); + + venc_modify(sd, VENC_VMOD, VENC_VMOD_VDMD_YCBCR8 << + VENC_VMOD_VDMD_SHIFT, VENC_VMOD_VDMD); + venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + + return 0; +} + +/* + * venc_set_720p60_internal - Setup 720p60 in venc for dm365 only + */ +static int venc_set_720p60_internal(struct v4l2_subdev *sd) +{ + struct venc_state *venc = to_state(sd); + struct venc_platform_data *pdata = venc->pdata; + + if (pdata->setup_clock(VPBE_ENC_DV_TIMINGS, 74250000) < 0) + return -EINVAL; + + venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_DV_TIMINGS, 74250000); + venc_enabledigitaloutput(sd, 0); + + venc_write(sd, VENC_OSDCLK0, 0); + venc_write(sd, VENC_OSDCLK1, 1); + + venc_write(sd, VENC_VMOD, 0); + /* DM365 component HD mode */ + venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), + VENC_VMOD_VIE); + venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD); + venc_modify(sd, VENC_VMOD, (HDTV_720P << VENC_VMOD_TVTYP_SHIFT), + VENC_VMOD_TVTYP); + venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + venc_write(sd, VENC_XHINTVL, 0); + return 0; +} + +/* + * venc_set_1080i30_internal - Setup 1080i30 in venc for dm365 only + */ +static int venc_set_1080i30_internal(struct v4l2_subdev *sd) +{ + struct venc_state *venc = to_state(sd); + struct venc_platform_data *pdata = venc->pdata; + + if (pdata->setup_clock(VPBE_ENC_DV_TIMINGS, 74250000) < 0) + return -EINVAL; + + venc_enable_vpss_clock(venc->venc_type, VPBE_ENC_DV_TIMINGS, 74250000); + venc_enabledigitaloutput(sd, 0); + + venc_write(sd, VENC_OSDCLK0, 0); + venc_write(sd, VENC_OSDCLK1, 1); + + + venc_write(sd, VENC_VMOD, 0); + /* DM365 component HD mode */ + venc_modify(sd, VENC_VMOD, (1 << VENC_VMOD_VIE_SHIFT), + VENC_VMOD_VIE); + venc_modify(sd, VENC_VMOD, VENC_VMOD_HDMD, VENC_VMOD_HDMD); + venc_modify(sd, VENC_VMOD, (HDTV_1080I << VENC_VMOD_TVTYP_SHIFT), + VENC_VMOD_TVTYP); + venc_modify(sd, VENC_VMOD, VENC_VMOD_VENC, VENC_VMOD_VENC); + venc_write(sd, VENC_XHINTVL, 0); + return 0; +} + +static int venc_s_std_output(struct v4l2_subdev *sd, v4l2_std_id norm) +{ + v4l2_dbg(debug, 1, sd, "venc_s_std_output\n"); + + if (norm & V4L2_STD_525_60) + return venc_set_ntsc(sd); + else if (norm & V4L2_STD_625_50) + return venc_set_pal(sd); + + return -EINVAL; +} + +static int venc_s_dv_timings(struct v4l2_subdev *sd, + struct v4l2_dv_timings *dv_timings) +{ + struct venc_state *venc = to_state(sd); + u32 height = dv_timings->bt.height; + int ret; + + v4l2_dbg(debug, 1, sd, "venc_s_dv_timings\n"); + + if (height == 576) + return venc_set_576p50(sd); + else if (height == 480) + return venc_set_480p59_94(sd); + else if ((height == 720) && + (venc->venc_type == VPBE_VERSION_2)) { + /* TBD setup internal 720p mode here */ + ret = venc_set_720p60_internal(sd); + /* for DM365 VPBE, there is DAC inside */ + vdaccfg_write(sd, VDAC_CONFIG_HD_V2); + return ret; + } else if ((height == 1080) && + (venc->venc_type == VPBE_VERSION_2)) { + /* TBD setup internal 1080i mode here */ + ret = venc_set_1080i30_internal(sd); + /* for DM365 VPBE, there is DAC inside */ + vdaccfg_write(sd, VDAC_CONFIG_HD_V2); + return ret; + } + return -EINVAL; +} + +static int venc_s_routing(struct v4l2_subdev *sd, u32 input, u32 output, + u32 config) +{ + struct venc_state *venc = to_state(sd); + int ret; + + v4l2_dbg(debug, 1, sd, "venc_s_routing\n"); + + ret = venc_set_dac(sd, output); + if (!ret) + venc->output = output; + + return ret; +} + +static long venc_command(struct v4l2_subdev *sd, unsigned int cmd, void *arg) +{ + u32 val; + + switch (cmd) { + case VENC_GET_FLD: + val = venc_read(sd, VENC_VSTAT); + *((int *)arg) = ((val & VENC_VSTAT_FIDST) == + VENC_VSTAT_FIDST); + break; + default: + v4l2_err(sd, "Wrong IOCTL cmd\n"); + break; + } + + return 0; +} + +static const struct v4l2_subdev_core_ops venc_core_ops = { + .command = venc_command, +}; + +static const struct v4l2_subdev_video_ops venc_video_ops = { + .s_routing = venc_s_routing, + .s_std_output = venc_s_std_output, + .s_dv_timings = venc_s_dv_timings, +}; + +static const struct v4l2_subdev_ops venc_ops = { + .core = &venc_core_ops, + .video = &venc_video_ops, +}; + +static int venc_initialize(struct v4l2_subdev *sd) +{ + struct venc_state *venc = to_state(sd); + int ret; + + /* Set default to output to composite and std to NTSC */ + venc->output = 0; + venc->std = V4L2_STD_525_60; + + ret = venc_s_routing(sd, 0, venc->output, 0); + if (ret < 0) { + v4l2_err(sd, "Error setting output during init\n"); + return -EINVAL; + } + + ret = venc_s_std_output(sd, venc->std); + if (ret < 0) { + v4l2_err(sd, "Error setting std during init\n"); + return -EINVAL; + } + + return ret; +} + +static int venc_device_get(struct device *dev, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct venc_state **venc = data; + + if (strstr(pdev->name, "vpbe-venc") != NULL) + *venc = platform_get_drvdata(pdev); + + return 0; +} + +struct v4l2_subdev *venc_sub_dev_init(struct v4l2_device *v4l2_dev, + const char *venc_name) +{ + struct venc_state *venc = NULL; + + bus_for_each_dev(&platform_bus_type, NULL, &venc, + venc_device_get); + if (venc == NULL) + return NULL; + + v4l2_subdev_init(&venc->sd, &venc_ops); + + strscpy(venc->sd.name, venc_name, sizeof(venc->sd.name)); + if (v4l2_device_register_subdev(v4l2_dev, &venc->sd) < 0) { + v4l2_err(v4l2_dev, + "vpbe unable to register venc sub device\n"); + return NULL; + } + if (venc_initialize(&venc->sd)) { + v4l2_err(v4l2_dev, + "vpbe venc initialization failed\n"); + return NULL; + } + + return &venc->sd; +} +EXPORT_SYMBOL(venc_sub_dev_init); + +static int venc_probe(struct platform_device *pdev) +{ + const struct platform_device_id *pdev_id; + struct venc_state *venc; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "No platform data for VENC sub device"); + return -EINVAL; + } + + pdev_id = platform_get_device_id(pdev); + if (!pdev_id) + return -EINVAL; + + venc = devm_kzalloc(&pdev->dev, sizeof(struct venc_state), GFP_KERNEL); + if (venc == NULL) + return -ENOMEM; + + venc->venc_type = pdev_id->driver_data; + venc->pdev = &pdev->dev; + venc->pdata = pdev->dev.platform_data; + + venc->venc_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(venc->venc_base)) + return PTR_ERR(venc->venc_base); + + if (venc->venc_type != VPBE_VERSION_1) { + venc->vdaccfg_reg = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(venc->vdaccfg_reg)) + return PTR_ERR(venc->vdaccfg_reg); + } + spin_lock_init(&venc->lock); + platform_set_drvdata(pdev, venc); + dev_notice(venc->pdev, "VENC sub device probe success\n"); + + return 0; +} + +static int venc_remove(struct platform_device *pdev) +{ + return 0; +} + +static struct platform_driver venc_driver = { + .probe = venc_probe, + .remove = venc_remove, + .driver = { + .name = MODULE_NAME, + }, + .id_table = vpbe_venc_devtype +}; + +module_platform_driver(venc_driver); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VPBE VENC Driver"); +MODULE_AUTHOR("Texas Instruments"); diff --git a/drivers/media/platform/ti/davinci/vpbe_venc_regs.h b/drivers/media/platform/ti/davinci/vpbe_venc_regs.h new file mode 100644 index 000000000..29d8fc3af --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpbe_venc_regs.h @@ -0,0 +1,165 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2006-2010 Texas Instruments Inc + */ +#ifndef _VPBE_VENC_REGS_H +#define _VPBE_VENC_REGS_H + +/* VPBE Video Encoder / Digital LCD Subsystem Registers (VENC) */ +#define VENC_VMOD 0x00 +#define VENC_VIDCTL 0x04 +#define VENC_VDPRO 0x08 +#define VENC_SYNCCTL 0x0C +#define VENC_HSPLS 0x10 +#define VENC_VSPLS 0x14 +#define VENC_HINT 0x18 +#define VENC_HSTART 0x1C +#define VENC_HVALID 0x20 +#define VENC_VINT 0x24 +#define VENC_VSTART 0x28 +#define VENC_VVALID 0x2C +#define VENC_HSDLY 0x30 +#define VENC_VSDLY 0x34 +#define VENC_YCCCTL 0x38 +#define VENC_RGBCTL 0x3C +#define VENC_RGBCLP 0x40 +#define VENC_LINECTL 0x44 +#define VENC_CULLLINE 0x48 +#define VENC_LCDOUT 0x4C +#define VENC_BRTS 0x50 +#define VENC_BRTW 0x54 +#define VENC_ACCTL 0x58 +#define VENC_PWMP 0x5C +#define VENC_PWMW 0x60 +#define VENC_DCLKCTL 0x64 +#define VENC_DCLKPTN0 0x68 +#define VENC_DCLKPTN1 0x6C +#define VENC_DCLKPTN2 0x70 +#define VENC_DCLKPTN3 0x74 +#define VENC_DCLKPTN0A 0x78 +#define VENC_DCLKPTN1A 0x7C +#define VENC_DCLKPTN2A 0x80 +#define VENC_DCLKPTN3A 0x84 +#define VENC_DCLKHS 0x88 +#define VENC_DCLKHSA 0x8C +#define VENC_DCLKHR 0x90 +#define VENC_DCLKVS 0x94 +#define VENC_DCLKVR 0x98 +#define VENC_CAPCTL 0x9C +#define VENC_CAPDO 0xA0 +#define VENC_CAPDE 0xA4 +#define VENC_ATR0 0xA8 +#define VENC_ATR1 0xAC +#define VENC_ATR2 0xB0 +#define VENC_VSTAT 0xB8 +#define VENC_RAMADR 0xBC +#define VENC_RAMPORT 0xC0 +#define VENC_DACTST 0xC4 +#define VENC_YCOLVL 0xC8 +#define VENC_SCPROG 0xCC +#define VENC_CVBS 0xDC +#define VENC_CMPNT 0xE0 +#define VENC_ETMG0 0xE4 +#define VENC_ETMG1 0xE8 +#define VENC_ETMG2 0xEC +#define VENC_ETMG3 0xF0 +#define VENC_DACSEL 0xF4 +#define VENC_ARGBX0 0x100 +#define VENC_ARGBX1 0x104 +#define VENC_ARGBX2 0x108 +#define VENC_ARGBX3 0x10C +#define VENC_ARGBX4 0x110 +#define VENC_DRGBX0 0x114 +#define VENC_DRGBX1 0x118 +#define VENC_DRGBX2 0x11C +#define VENC_DRGBX3 0x120 +#define VENC_DRGBX4 0x124 +#define VENC_VSTARTA 0x128 +#define VENC_OSDCLK0 0x12C +#define VENC_OSDCLK1 0x130 +#define VENC_HVLDCL0 0x134 +#define VENC_HVLDCL1 0x138 +#define VENC_OSDHADV 0x13C +#define VENC_CLKCTL 0x140 +#define VENC_GAMCTL 0x144 +#define VENC_XHINTVL 0x174 + +/* bit definitions */ +#define VPBE_PCR_VENC_DIV (1 << 1) +#define VPBE_PCR_CLK_OFF (1 << 0) + +#define VENC_VMOD_VDMD_SHIFT 12 +#define VENC_VMOD_VDMD_YCBCR16 0 +#define VENC_VMOD_VDMD_YCBCR8 1 +#define VENC_VMOD_VDMD_RGB666 2 +#define VENC_VMOD_VDMD_RGB8 3 +#define VENC_VMOD_VDMD_EPSON 4 +#define VENC_VMOD_VDMD_CASIO 5 +#define VENC_VMOD_VDMD_UDISPQVGA 6 +#define VENC_VMOD_VDMD_STNLCD 7 +#define VENC_VMOD_VIE_SHIFT 1 +#define VENC_VMOD_VDMD (7 << 12) +#define VENC_VMOD_ITLCL (1 << 11) +#define VENC_VMOD_ITLC (1 << 10) +#define VENC_VMOD_NSIT (1 << 9) +#define VENC_VMOD_HDMD (1 << 8) +#define VENC_VMOD_TVTYP_SHIFT 6 +#define VENC_VMOD_TVTYP (3 << 6) +#define VENC_VMOD_SLAVE (1 << 5) +#define VENC_VMOD_VMD (1 << 4) +#define VENC_VMOD_BLNK (1 << 3) +#define VENC_VMOD_VIE (1 << 1) +#define VENC_VMOD_VENC (1 << 0) + +/* VMOD TVTYP options for HDMD=0 */ +#define SDTV_NTSC 0 +#define SDTV_PAL 1 +/* VMOD TVTYP options for HDMD=1 */ +#define HDTV_525P 0 +#define HDTV_625P 1 +#define HDTV_1080I 2 +#define HDTV_720P 3 + +#define VENC_VIDCTL_VCLKP (1 << 14) +#define VENC_VIDCTL_VCLKE_SHIFT 13 +#define VENC_VIDCTL_VCLKE (1 << 13) +#define VENC_VIDCTL_VCLKZ_SHIFT 12 +#define VENC_VIDCTL_VCLKZ (1 << 12) +#define VENC_VIDCTL_SYDIR_SHIFT 8 +#define VENC_VIDCTL_SYDIR (1 << 8) +#define VENC_VIDCTL_DOMD_SHIFT 4 +#define VENC_VIDCTL_DOMD (3 << 4) +#define VENC_VIDCTL_YCDIR_SHIFT 0 +#define VENC_VIDCTL_YCDIR (1 << 0) + +#define VENC_VDPRO_ATYCC_SHIFT 5 +#define VENC_VDPRO_ATYCC (1 << 5) +#define VENC_VDPRO_ATCOM_SHIFT 4 +#define VENC_VDPRO_ATCOM (1 << 4) +#define VENC_VDPRO_DAFRQ (1 << 3) +#define VENC_VDPRO_DAUPS (1 << 2) +#define VENC_VDPRO_CUPS (1 << 1) +#define VENC_VDPRO_YUPS (1 << 0) + +#define VENC_SYNCCTL_VPL_SHIFT 3 +#define VENC_SYNCCTL_VPL (1 << 3) +#define VENC_SYNCCTL_HPL_SHIFT 2 +#define VENC_SYNCCTL_HPL (1 << 2) +#define VENC_SYNCCTL_SYEV_SHIFT 1 +#define VENC_SYNCCTL_SYEV (1 << 1) +#define VENC_SYNCCTL_SYEH_SHIFT 0 +#define VENC_SYNCCTL_SYEH (1 << 0) +#define VENC_SYNCCTL_OVD_SHIFT 14 +#define VENC_SYNCCTL_OVD (1 << 14) + +#define VENC_DCLKCTL_DCKEC_SHIFT 11 +#define VENC_DCLKCTL_DCKEC (1 << 11) +#define VENC_DCLKCTL_DCKPW_SHIFT 0 +#define VENC_DCLKCTL_DCKPW (0x3f << 0) + +#define VENC_VSTAT_FIDST (1 << 4) + +#define VENC_CMPNT_MRGB_SHIFT 14 +#define VENC_CMPNT_MRGB (1 << 14) + +#endif /* _VPBE_VENC_REGS_H */ diff --git a/drivers/media/platform/ti/davinci/vpif.c b/drivers/media/platform/ti/davinci/vpif.c new file mode 100644 index 000000000..da27da4c1 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpif.c @@ -0,0 +1,610 @@ +/* + * vpif - Video Port Interface driver + * VPIF is a receiver and transmitter for video data. It has two channels(0, 1) + * that receiving video byte stream and two channels(2, 3) for video output. + * The hardware supports SDTV, HDTV formats, raw data capture. + * Currently, the driver supports NTSC and PAL standards. + * + * Copyright (C) 2009 Texas Instruments Incorporated - https://www.ti.com/ + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vpif.h" + +MODULE_DESCRIPTION("TI DaVinci Video Port Interface driver"); +MODULE_LICENSE("GPL"); + +#define VPIF_DRIVER_NAME "vpif" +MODULE_ALIAS("platform:" VPIF_DRIVER_NAME); + +#define VPIF_CH0_MAX_MODES 22 +#define VPIF_CH1_MAX_MODES 2 +#define VPIF_CH2_MAX_MODES 15 +#define VPIF_CH3_MAX_MODES 2 + +struct vpif_data { + struct platform_device *capture; + struct platform_device *display; +}; + +DEFINE_SPINLOCK(vpif_lock); +EXPORT_SYMBOL_GPL(vpif_lock); + +void __iomem *vpif_base; +EXPORT_SYMBOL_GPL(vpif_base); + +/* + * vpif_ch_params: video standard configuration parameters for vpif + * + * The table must include all presets from supported subdevices. + */ +const struct vpif_channel_config_params vpif_ch_params[] = { + /* HDTV formats */ + { + .name = "480p59_94", + .width = 720, + .height = 480, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 138-8, + .sav2eav = 720, + .l1 = 1, + .l3 = 43, + .l5 = 523, + .vsize = 525, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_720X480P59_94, + }, + { + .name = "576p50", + .width = 720, + .height = 576, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 144-8, + .sav2eav = 720, + .l1 = 1, + .l3 = 45, + .l5 = 621, + .vsize = 625, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_720X576P50, + }, + { + .name = "720p50", + .width = 1280, + .height = 720, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 700-8, + .sav2eav = 1280, + .l1 = 1, + .l3 = 26, + .l5 = 746, + .vsize = 750, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1280X720P50, + }, + { + .name = "720p60", + .width = 1280, + .height = 720, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 370 - 8, + .sav2eav = 1280, + .l1 = 1, + .l3 = 26, + .l5 = 746, + .vsize = 750, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1280X720P60, + }, + { + .name = "1080I50", + .width = 1920, + .height = 1080, + .frm_fmt = 0, + .ycmux_mode = 0, + .eav2sav = 720 - 8, + .sav2eav = 1920, + .l1 = 1, + .l3 = 21, + .l5 = 561, + .l7 = 563, + .l9 = 584, + .l11 = 1124, + .vsize = 1125, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1920X1080I50, + }, + { + .name = "1080I60", + .width = 1920, + .height = 1080, + .frm_fmt = 0, + .ycmux_mode = 0, + .eav2sav = 280 - 8, + .sav2eav = 1920, + .l1 = 1, + .l3 = 21, + .l5 = 561, + .l7 = 563, + .l9 = 584, + .l11 = 1124, + .vsize = 1125, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1920X1080I60, + }, + { + .name = "1080p60", + .width = 1920, + .height = 1080, + .frm_fmt = 1, + .ycmux_mode = 0, + .eav2sav = 280 - 8, + .sav2eav = 1920, + .l1 = 1, + .l3 = 42, + .l5 = 1122, + .vsize = 1125, + .capture_format = 0, + .vbi_supported = 0, + .hd_sd = 1, + .dv_timings = V4L2_DV_BT_CEA_1920X1080P60, + }, + + /* SDTV formats */ + { + .name = "NTSC_M", + .width = 720, + .height = 480, + .frm_fmt = 0, + .ycmux_mode = 1, + .eav2sav = 268, + .sav2eav = 1440, + .l1 = 1, + .l3 = 23, + .l5 = 263, + .l7 = 266, + .l9 = 286, + .l11 = 525, + .vsize = 525, + .capture_format = 0, + .vbi_supported = 1, + .hd_sd = 0, + .stdid = V4L2_STD_525_60, + }, + { + .name = "PAL_BDGHIK", + .width = 720, + .height = 576, + .frm_fmt = 0, + .ycmux_mode = 1, + .eav2sav = 280, + .sav2eav = 1440, + .l1 = 1, + .l3 = 23, + .l5 = 311, + .l7 = 313, + .l9 = 336, + .l11 = 624, + .vsize = 625, + .capture_format = 0, + .vbi_supported = 1, + .hd_sd = 0, + .stdid = V4L2_STD_625_50, + }, +}; +EXPORT_SYMBOL_GPL(vpif_ch_params); + +const unsigned int vpif_ch_params_count = ARRAY_SIZE(vpif_ch_params); +EXPORT_SYMBOL_GPL(vpif_ch_params_count); + +static inline void vpif_wr_bit(u32 reg, u32 bit, u32 val) +{ + if (val) + vpif_set_bit(reg, bit); + else + vpif_clr_bit(reg, bit); +} + +/* This structure is used to keep track of VPIF size register's offsets */ +struct vpif_registers { + u32 h_cfg, v_cfg_00, v_cfg_01, v_cfg_02, v_cfg, ch_ctrl; + u32 line_offset, vanc0_strt, vanc0_size, vanc1_strt; + u32 vanc1_size, width_mask, len_mask; + u8 max_modes; +}; + +static const struct vpif_registers vpifregs[VPIF_NUM_CHANNELS] = { + /* Channel0 */ + { + VPIF_CH0_H_CFG, VPIF_CH0_V_CFG_00, VPIF_CH0_V_CFG_01, + VPIF_CH0_V_CFG_02, VPIF_CH0_V_CFG_03, VPIF_CH0_CTRL, + VPIF_CH0_IMG_ADD_OFST, 0, 0, 0, 0, 0x1FFF, 0xFFF, + VPIF_CH0_MAX_MODES, + }, + /* Channel1 */ + { + VPIF_CH1_H_CFG, VPIF_CH1_V_CFG_00, VPIF_CH1_V_CFG_01, + VPIF_CH1_V_CFG_02, VPIF_CH1_V_CFG_03, VPIF_CH1_CTRL, + VPIF_CH1_IMG_ADD_OFST, 0, 0, 0, 0, 0x1FFF, 0xFFF, + VPIF_CH1_MAX_MODES, + }, + /* Channel2 */ + { + VPIF_CH2_H_CFG, VPIF_CH2_V_CFG_00, VPIF_CH2_V_CFG_01, + VPIF_CH2_V_CFG_02, VPIF_CH2_V_CFG_03, VPIF_CH2_CTRL, + VPIF_CH2_IMG_ADD_OFST, VPIF_CH2_VANC0_STRT, VPIF_CH2_VANC0_SIZE, + VPIF_CH2_VANC1_STRT, VPIF_CH2_VANC1_SIZE, 0x7FF, 0x7FF, + VPIF_CH2_MAX_MODES + }, + /* Channel3 */ + { + VPIF_CH3_H_CFG, VPIF_CH3_V_CFG_00, VPIF_CH3_V_CFG_01, + VPIF_CH3_V_CFG_02, VPIF_CH3_V_CFG_03, VPIF_CH3_CTRL, + VPIF_CH3_IMG_ADD_OFST, VPIF_CH3_VANC0_STRT, VPIF_CH3_VANC0_SIZE, + VPIF_CH3_VANC1_STRT, VPIF_CH3_VANC1_SIZE, 0x7FF, 0x7FF, + VPIF_CH3_MAX_MODES + }, +}; + +/* vpif_set_mode_info: + * This function is used to set horizontal and vertical config parameters + * As per the standard in the channel, configure the values of L1, L3, + * L5, L7 L9, L11 in VPIF Register , also write width and height + */ +static void vpif_set_mode_info(const struct vpif_channel_config_params *config, + u8 channel_id, u8 config_channel_id) +{ + u32 value; + + value = (config->eav2sav & vpifregs[config_channel_id].width_mask); + value <<= VPIF_CH_LEN_SHIFT; + value |= (config->sav2eav & vpifregs[config_channel_id].width_mask); + regw(value, vpifregs[channel_id].h_cfg); + + value = (config->l1 & vpifregs[config_channel_id].len_mask); + value <<= VPIF_CH_LEN_SHIFT; + value |= (config->l3 & vpifregs[config_channel_id].len_mask); + regw(value, vpifregs[channel_id].v_cfg_00); + + value = (config->l5 & vpifregs[config_channel_id].len_mask); + value <<= VPIF_CH_LEN_SHIFT; + value |= (config->l7 & vpifregs[config_channel_id].len_mask); + regw(value, vpifregs[channel_id].v_cfg_01); + + value = (config->l9 & vpifregs[config_channel_id].len_mask); + value <<= VPIF_CH_LEN_SHIFT; + value |= (config->l11 & vpifregs[config_channel_id].len_mask); + regw(value, vpifregs[channel_id].v_cfg_02); + + value = (config->vsize & vpifregs[config_channel_id].len_mask); + regw(value, vpifregs[channel_id].v_cfg); +} + +/* config_vpif_params + * Function to set the parameters of a channel + * Mainly modifies the channel ciontrol register + * It sets frame format, yc mux mode + */ +static void config_vpif_params(struct vpif_params *vpifparams, + u8 channel_id, u8 found) +{ + const struct vpif_channel_config_params *config = &vpifparams->std_info; + u32 value, ch_nip, reg; + u8 start, end; + int i; + + start = channel_id; + end = channel_id + found; + + for (i = start; i < end; i++) { + reg = vpifregs[i].ch_ctrl; + if (channel_id < 2) + ch_nip = VPIF_CAPTURE_CH_NIP; + else + ch_nip = VPIF_DISPLAY_CH_NIP; + + vpif_wr_bit(reg, ch_nip, config->frm_fmt); + vpif_wr_bit(reg, VPIF_CH_YC_MUX_BIT, config->ycmux_mode); + vpif_wr_bit(reg, VPIF_CH_INPUT_FIELD_FRAME_BIT, + vpifparams->video_params.storage_mode); + + /* Set raster scanning SDR Format */ + vpif_clr_bit(reg, VPIF_CH_SDR_FMT_BIT); + vpif_wr_bit(reg, VPIF_CH_DATA_MODE_BIT, config->capture_format); + + if (channel_id > 1) /* Set the Pixel enable bit */ + vpif_set_bit(reg, VPIF_DISPLAY_PIX_EN_BIT); + else if (config->capture_format) { + /* Set the polarity of various pins */ + vpif_wr_bit(reg, VPIF_CH_FID_POLARITY_BIT, + vpifparams->iface.fid_pol); + vpif_wr_bit(reg, VPIF_CH_V_VALID_POLARITY_BIT, + vpifparams->iface.vd_pol); + vpif_wr_bit(reg, VPIF_CH_H_VALID_POLARITY_BIT, + vpifparams->iface.hd_pol); + + value = regr(reg); + /* Set data width */ + value &= ~(0x3u << + VPIF_CH_DATA_WIDTH_BIT); + value |= ((vpifparams->params.data_sz) << + VPIF_CH_DATA_WIDTH_BIT); + regw(value, reg); + } + + /* Write the pitch in the driver */ + regw((vpifparams->video_params.hpitch), + vpifregs[i].line_offset); + } +} + +/* vpif_set_video_params + * This function is used to set video parameters in VPIF register + */ +int vpif_set_video_params(struct vpif_params *vpifparams, u8 channel_id) +{ + const struct vpif_channel_config_params *config = &vpifparams->std_info; + int found = 1; + + vpif_set_mode_info(config, channel_id, channel_id); + if (!config->ycmux_mode) { + /* YC are on separate channels (HDTV formats) */ + vpif_set_mode_info(config, channel_id + 1, channel_id); + found = 2; + } + + config_vpif_params(vpifparams, channel_id, found); + + regw(0x80, VPIF_REQ_SIZE); + regw(0x01, VPIF_EMULATION_CTRL); + + return found; +} +EXPORT_SYMBOL(vpif_set_video_params); + +void vpif_set_vbi_display_params(struct vpif_vbi_params *vbiparams, + u8 channel_id) +{ + u32 value; + + value = 0x3F8 & (vbiparams->hstart0); + value |= 0x3FFFFFF & ((vbiparams->vstart0) << 16); + regw(value, vpifregs[channel_id].vanc0_strt); + + value = 0x3F8 & (vbiparams->hstart1); + value |= 0x3FFFFFF & ((vbiparams->vstart1) << 16); + regw(value, vpifregs[channel_id].vanc1_strt); + + value = 0x3F8 & (vbiparams->hsize0); + value |= 0x3FFFFFF & ((vbiparams->vsize0) << 16); + regw(value, vpifregs[channel_id].vanc0_size); + + value = 0x3F8 & (vbiparams->hsize1); + value |= 0x3FFFFFF & ((vbiparams->vsize1) << 16); + regw(value, vpifregs[channel_id].vanc1_size); + +} +EXPORT_SYMBOL(vpif_set_vbi_display_params); + +int vpif_channel_getfid(u8 channel_id) +{ + return (regr(vpifregs[channel_id].ch_ctrl) & VPIF_CH_FID_MASK) + >> VPIF_CH_FID_SHIFT; +} +EXPORT_SYMBOL(vpif_channel_getfid); + +static void vpif_pdev_release(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + + kfree(pdev); +} + +static int vpif_probe(struct platform_device *pdev) +{ + static struct resource res_irq; + struct platform_device *pdev_capture, *pdev_display; + struct device_node *endpoint = NULL; + struct vpif_data *data; + int ret; + int irq; + + vpif_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(vpif_base)) + return PTR_ERR(vpif_base); + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + platform_set_drvdata(pdev, data); + + pm_runtime_enable(&pdev->dev); + pm_runtime_get(&pdev->dev); + + /* + * If VPIF Node has endpoints, assume "new" DT support, + * where capture and display drivers don't have DT nodes + * so their devices need to be registered manually here + * for their legacy platform_drivers to work. + */ + endpoint = of_graph_get_next_endpoint(pdev->dev.of_node, + endpoint); + if (!endpoint) + return 0; + of_node_put(endpoint); + + /* + * For DT platforms, manually create platform_devices for + * capture/display drivers. + */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto err_put_rpm; + } + res_irq = (struct resource)DEFINE_RES_IRQ_NAMED(irq, of_node_full_name(pdev->dev.of_node)); + res_irq.flags |= irq_get_trigger_type(irq); + + pdev_capture = kzalloc(sizeof(*pdev_capture), GFP_KERNEL); + if (!pdev_capture) { + ret = -ENOMEM; + goto err_put_rpm; + } + + pdev_capture->name = "vpif_capture"; + pdev_capture->id = -1; + pdev_capture->resource = &res_irq; + pdev_capture->num_resources = 1; + pdev_capture->dev.dma_mask = pdev->dev.dma_mask; + pdev_capture->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; + pdev_capture->dev.parent = &pdev->dev; + pdev_capture->dev.release = vpif_pdev_release; + + ret = platform_device_register(pdev_capture); + if (ret) + goto err_put_pdev_capture; + + pdev_display = kzalloc(sizeof(*pdev_display), GFP_KERNEL); + if (!pdev_display) { + ret = -ENOMEM; + goto err_put_pdev_capture; + } + + pdev_display->name = "vpif_display"; + pdev_display->id = -1; + pdev_display->resource = &res_irq; + pdev_display->num_resources = 1; + pdev_display->dev.dma_mask = pdev->dev.dma_mask; + pdev_display->dev.coherent_dma_mask = pdev->dev.coherent_dma_mask; + pdev_display->dev.parent = &pdev->dev; + pdev_display->dev.release = vpif_pdev_release; + + ret = platform_device_register(pdev_display); + if (ret) + goto err_put_pdev_display; + + data->capture = pdev_capture; + data->display = pdev_display; + + return 0; + +err_put_pdev_display: + platform_device_put(pdev_display); +err_put_pdev_capture: + platform_device_put(pdev_capture); +err_put_rpm: + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + kfree(data); + + return ret; +} + +static int vpif_remove(struct platform_device *pdev) +{ + struct vpif_data *data = platform_get_drvdata(pdev); + + if (data->capture) + platform_device_unregister(data->capture); + if (data->display) + platform_device_unregister(data->display); + + pm_runtime_put(&pdev->dev); + pm_runtime_disable(&pdev->dev); + + kfree(data); + + return 0; +} + +#ifdef CONFIG_PM +static int vpif_suspend(struct device *dev) +{ + pm_runtime_put(dev); + return 0; +} + +static int vpif_resume(struct device *dev) +{ + pm_runtime_get(dev); + return 0; +} + +static const struct dev_pm_ops vpif_pm = { + .suspend = vpif_suspend, + .resume = vpif_resume, +}; + +#define vpif_pm_ops (&vpif_pm) +#else +#define vpif_pm_ops NULL +#endif + +#if IS_ENABLED(CONFIG_OF) +static const struct of_device_id vpif_of_match[] = { + { .compatible = "ti,da850-vpif", }, + { /* sentinel */ }, +}; +MODULE_DEVICE_TABLE(of, vpif_of_match); +#endif + +static struct platform_driver vpif_driver = { + .driver = { + .of_match_table = of_match_ptr(vpif_of_match), + .name = VPIF_DRIVER_NAME, + .pm = vpif_pm_ops, + }, + .remove = vpif_remove, + .probe = vpif_probe, +}; + +static void vpif_exit(void) +{ + platform_driver_unregister(&vpif_driver); +} + +static int __init vpif_init(void) +{ + return platform_driver_register(&vpif_driver); +} +subsys_initcall(vpif_init); +module_exit(vpif_exit); + diff --git a/drivers/media/platform/ti/davinci/vpif.h b/drivers/media/platform/ti/davinci/vpif.h new file mode 100644 index 000000000..52ecc2562 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpif.h @@ -0,0 +1,679 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * VPIF header file + * + * Copyright (C) 2009 Texas Instruments Incorporated - https://www.ti.com/ + */ + +#ifndef VPIF_H +#define VPIF_H + +#include +#include +#include + +/* Maximum channel allowed */ +#define VPIF_NUM_CHANNELS (4) +#define VPIF_CAPTURE_NUM_CHANNELS (2) +#define VPIF_DISPLAY_NUM_CHANNELS (2) + +/* Macros to read/write registers */ +extern void __iomem *vpif_base; +extern spinlock_t vpif_lock; + +#define regr(reg) readl((reg) + vpif_base) +#define regw(value, reg) writel(value, (reg + vpif_base)) + +/* Register Address Offsets */ +#define VPIF_PID (0x0000) +#define VPIF_CH0_CTRL (0x0004) +#define VPIF_CH1_CTRL (0x0008) +#define VPIF_CH2_CTRL (0x000C) +#define VPIF_CH3_CTRL (0x0010) + +#define VPIF_INTEN (0x0020) +#define VPIF_INTEN_SET (0x0024) +#define VPIF_INTEN_CLR (0x0028) +#define VPIF_STATUS (0x002C) +#define VPIF_STATUS_CLR (0x0030) +#define VPIF_EMULATION_CTRL (0x0034) +#define VPIF_REQ_SIZE (0x0038) + +#define VPIF_CH0_TOP_STRT_ADD_LUMA (0x0040) +#define VPIF_CH0_BTM_STRT_ADD_LUMA (0x0044) +#define VPIF_CH0_TOP_STRT_ADD_CHROMA (0x0048) +#define VPIF_CH0_BTM_STRT_ADD_CHROMA (0x004c) +#define VPIF_CH0_TOP_STRT_ADD_HANC (0x0050) +#define VPIF_CH0_BTM_STRT_ADD_HANC (0x0054) +#define VPIF_CH0_TOP_STRT_ADD_VANC (0x0058) +#define VPIF_CH0_BTM_STRT_ADD_VANC (0x005c) +#define VPIF_CH0_SP_CFG (0x0060) +#define VPIF_CH0_IMG_ADD_OFST (0x0064) +#define VPIF_CH0_HANC_ADD_OFST (0x0068) +#define VPIF_CH0_H_CFG (0x006c) +#define VPIF_CH0_V_CFG_00 (0x0070) +#define VPIF_CH0_V_CFG_01 (0x0074) +#define VPIF_CH0_V_CFG_02 (0x0078) +#define VPIF_CH0_V_CFG_03 (0x007c) + +#define VPIF_CH1_TOP_STRT_ADD_LUMA (0x0080) +#define VPIF_CH1_BTM_STRT_ADD_LUMA (0x0084) +#define VPIF_CH1_TOP_STRT_ADD_CHROMA (0x0088) +#define VPIF_CH1_BTM_STRT_ADD_CHROMA (0x008c) +#define VPIF_CH1_TOP_STRT_ADD_HANC (0x0090) +#define VPIF_CH1_BTM_STRT_ADD_HANC (0x0094) +#define VPIF_CH1_TOP_STRT_ADD_VANC (0x0098) +#define VPIF_CH1_BTM_STRT_ADD_VANC (0x009c) +#define VPIF_CH1_SP_CFG (0x00a0) +#define VPIF_CH1_IMG_ADD_OFST (0x00a4) +#define VPIF_CH1_HANC_ADD_OFST (0x00a8) +#define VPIF_CH1_H_CFG (0x00ac) +#define VPIF_CH1_V_CFG_00 (0x00b0) +#define VPIF_CH1_V_CFG_01 (0x00b4) +#define VPIF_CH1_V_CFG_02 (0x00b8) +#define VPIF_CH1_V_CFG_03 (0x00bc) + +#define VPIF_CH2_TOP_STRT_ADD_LUMA (0x00c0) +#define VPIF_CH2_BTM_STRT_ADD_LUMA (0x00c4) +#define VPIF_CH2_TOP_STRT_ADD_CHROMA (0x00c8) +#define VPIF_CH2_BTM_STRT_ADD_CHROMA (0x00cc) +#define VPIF_CH2_TOP_STRT_ADD_HANC (0x00d0) +#define VPIF_CH2_BTM_STRT_ADD_HANC (0x00d4) +#define VPIF_CH2_TOP_STRT_ADD_VANC (0x00d8) +#define VPIF_CH2_BTM_STRT_ADD_VANC (0x00dc) +#define VPIF_CH2_SP_CFG (0x00e0) +#define VPIF_CH2_IMG_ADD_OFST (0x00e4) +#define VPIF_CH2_HANC_ADD_OFST (0x00e8) +#define VPIF_CH2_H_CFG (0x00ec) +#define VPIF_CH2_V_CFG_00 (0x00f0) +#define VPIF_CH2_V_CFG_01 (0x00f4) +#define VPIF_CH2_V_CFG_02 (0x00f8) +#define VPIF_CH2_V_CFG_03 (0x00fc) +#define VPIF_CH2_HANC0_STRT (0x0100) +#define VPIF_CH2_HANC0_SIZE (0x0104) +#define VPIF_CH2_HANC1_STRT (0x0108) +#define VPIF_CH2_HANC1_SIZE (0x010c) +#define VPIF_CH2_VANC0_STRT (0x0110) +#define VPIF_CH2_VANC0_SIZE (0x0114) +#define VPIF_CH2_VANC1_STRT (0x0118) +#define VPIF_CH2_VANC1_SIZE (0x011c) + +#define VPIF_CH3_TOP_STRT_ADD_LUMA (0x0140) +#define VPIF_CH3_BTM_STRT_ADD_LUMA (0x0144) +#define VPIF_CH3_TOP_STRT_ADD_CHROMA (0x0148) +#define VPIF_CH3_BTM_STRT_ADD_CHROMA (0x014c) +#define VPIF_CH3_TOP_STRT_ADD_HANC (0x0150) +#define VPIF_CH3_BTM_STRT_ADD_HANC (0x0154) +#define VPIF_CH3_TOP_STRT_ADD_VANC (0x0158) +#define VPIF_CH3_BTM_STRT_ADD_VANC (0x015c) +#define VPIF_CH3_SP_CFG (0x0160) +#define VPIF_CH3_IMG_ADD_OFST (0x0164) +#define VPIF_CH3_HANC_ADD_OFST (0x0168) +#define VPIF_CH3_H_CFG (0x016c) +#define VPIF_CH3_V_CFG_00 (0x0170) +#define VPIF_CH3_V_CFG_01 (0x0174) +#define VPIF_CH3_V_CFG_02 (0x0178) +#define VPIF_CH3_V_CFG_03 (0x017c) +#define VPIF_CH3_HANC0_STRT (0x0180) +#define VPIF_CH3_HANC0_SIZE (0x0184) +#define VPIF_CH3_HANC1_STRT (0x0188) +#define VPIF_CH3_HANC1_SIZE (0x018c) +#define VPIF_CH3_VANC0_STRT (0x0190) +#define VPIF_CH3_VANC0_SIZE (0x0194) +#define VPIF_CH3_VANC1_STRT (0x0198) +#define VPIF_CH3_VANC1_SIZE (0x019c) + +#define VPIF_IODFT_CTRL (0x01c0) + +/* Functions for bit Manipulation */ +static inline void vpif_set_bit(u32 reg, u32 bit) +{ + regw((regr(reg)) | (0x01 << bit), reg); +} + +static inline void vpif_clr_bit(u32 reg, u32 bit) +{ + regw(((regr(reg)) & ~(0x01 << bit)), reg); +} + +/* Macro for Generating mask */ +#ifdef GENERATE_MASK +#undef GENERATE_MASK +#endif + +#define GENERATE_MASK(bits, pos) \ + ((((0xFFFFFFFF) << (32 - bits)) >> (32 - bits)) << pos) + +/* Bit positions in the channel control registers */ +#define VPIF_CH_DATA_MODE_BIT (2) +#define VPIF_CH_YC_MUX_BIT (3) +#define VPIF_CH_SDR_FMT_BIT (4) +#define VPIF_CH_HANC_EN_BIT (8) +#define VPIF_CH_VANC_EN_BIT (9) + +#define VPIF_CAPTURE_CH_NIP (10) +#define VPIF_DISPLAY_CH_NIP (11) + +#define VPIF_DISPLAY_PIX_EN_BIT (10) + +#define VPIF_CH_INPUT_FIELD_FRAME_BIT (12) + +#define VPIF_CH_FID_POLARITY_BIT (15) +#define VPIF_CH_V_VALID_POLARITY_BIT (14) +#define VPIF_CH_H_VALID_POLARITY_BIT (13) +#define VPIF_CH_DATA_WIDTH_BIT (28) + +#define VPIF_CH_CLK_EDGE_CTRL_BIT (31) + +/* Mask various length */ +#define VPIF_CH_EAVSAV_MASK GENERATE_MASK(13, 0) +#define VPIF_CH_LEN_MASK GENERATE_MASK(12, 0) +#define VPIF_CH_WIDTH_MASK GENERATE_MASK(13, 0) +#define VPIF_CH_LEN_SHIFT (16) + +/* VPIF masks for registers */ +#define VPIF_REQ_SIZE_MASK (0x1ff) + +/* bit posotion of interrupt vpif_ch_intr register */ +#define VPIF_INTEN_FRAME_CH0 (0x00000001) +#define VPIF_INTEN_FRAME_CH1 (0x00000002) +#define VPIF_INTEN_FRAME_CH2 (0x00000004) +#define VPIF_INTEN_FRAME_CH3 (0x00000008) + +/* bit position of clock and channel enable in vpif_chn_ctrl register */ + +#define VPIF_CH0_CLK_EN (0x00000002) +#define VPIF_CH0_EN (0x00000001) +#define VPIF_CH1_CLK_EN (0x00000002) +#define VPIF_CH1_EN (0x00000001) +#define VPIF_CH2_CLK_EN (0x00000002) +#define VPIF_CH2_EN (0x00000001) +#define VPIF_CH3_CLK_EN (0x00000002) +#define VPIF_CH3_EN (0x00000001) +#define VPIF_CH_CLK_EN (0x00000002) +#define VPIF_CH_EN (0x00000001) + +#define VPIF_INT_TOP (0x00) +#define VPIF_INT_BOTTOM (0x01) +#define VPIF_INT_BOTH (0x02) + +#define VPIF_CH0_INT_CTRL_SHIFT (6) +#define VPIF_CH1_INT_CTRL_SHIFT (6) +#define VPIF_CH2_INT_CTRL_SHIFT (6) +#define VPIF_CH3_INT_CTRL_SHIFT (6) +#define VPIF_CH_INT_CTRL_SHIFT (6) + +#define VPIF_CH2_CLIP_ANC_EN 14 +#define VPIF_CH2_CLIP_ACTIVE_EN 13 + +#define VPIF_CH3_CLIP_ANC_EN 14 +#define VPIF_CH3_CLIP_ACTIVE_EN 13 + +/* enabled interrupt on both the fields on vpid_ch0_ctrl register */ +#define channel0_intr_assert() (regw((regr(VPIF_CH0_CTRL)|\ + (VPIF_INT_BOTH << VPIF_CH0_INT_CTRL_SHIFT)), VPIF_CH0_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch1_ctrl register */ +#define channel1_intr_assert() (regw((regr(VPIF_CH1_CTRL)|\ + (VPIF_INT_BOTH << VPIF_CH1_INT_CTRL_SHIFT)), VPIF_CH1_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch0_ctrl register */ +#define channel2_intr_assert() (regw((regr(VPIF_CH2_CTRL)|\ + (VPIF_INT_BOTH << VPIF_CH2_INT_CTRL_SHIFT)), VPIF_CH2_CTRL)) + +/* enabled interrupt on both the fields on vpid_ch1_ctrl register */ +#define channel3_intr_assert() (regw((regr(VPIF_CH3_CTRL)|\ + (VPIF_INT_BOTH << VPIF_CH3_INT_CTRL_SHIFT)), VPIF_CH3_CTRL)) + +#define VPIF_CH_FID_MASK (0x20) +#define VPIF_CH_FID_SHIFT (5) + +#define VPIF_NTSC_VBI_START_FIELD0 (1) +#define VPIF_NTSC_VBI_START_FIELD1 (263) +#define VPIF_PAL_VBI_START_FIELD0 (624) +#define VPIF_PAL_VBI_START_FIELD1 (311) + +#define VPIF_NTSC_HBI_START_FIELD0 (1) +#define VPIF_NTSC_HBI_START_FIELD1 (263) +#define VPIF_PAL_HBI_START_FIELD0 (624) +#define VPIF_PAL_HBI_START_FIELD1 (311) + +#define VPIF_NTSC_VBI_COUNT_FIELD0 (20) +#define VPIF_NTSC_VBI_COUNT_FIELD1 (19) +#define VPIF_PAL_VBI_COUNT_FIELD0 (24) +#define VPIF_PAL_VBI_COUNT_FIELD1 (25) + +#define VPIF_NTSC_HBI_COUNT_FIELD0 (263) +#define VPIF_NTSC_HBI_COUNT_FIELD1 (262) +#define VPIF_PAL_HBI_COUNT_FIELD0 (312) +#define VPIF_PAL_HBI_COUNT_FIELD1 (313) + +#define VPIF_NTSC_VBI_SAMPLES_PER_LINE (720) +#define VPIF_PAL_VBI_SAMPLES_PER_LINE (720) +#define VPIF_NTSC_HBI_SAMPLES_PER_LINE (268) +#define VPIF_PAL_HBI_SAMPLES_PER_LINE (280) + +#define VPIF_CH_VANC_EN (0x20) +#define VPIF_DMA_REQ_SIZE (0x080) +#define VPIF_EMULATION_DISABLE (0x01) + +extern u8 irq_vpif_capture_channel[VPIF_NUM_CHANNELS]; + +/* inline function to enable/disable channel0 */ +static inline void enable_channel0(int enable) +{ + if (enable) + regw((regr(VPIF_CH0_CTRL) | (VPIF_CH0_EN)), VPIF_CH0_CTRL); + else + regw((regr(VPIF_CH0_CTRL) & (~VPIF_CH0_EN)), VPIF_CH0_CTRL); +} + +/* inline function to enable/disable channel1 */ +static inline void enable_channel1(int enable) +{ + if (enable) + regw((regr(VPIF_CH1_CTRL) | (VPIF_CH1_EN)), VPIF_CH1_CTRL); + else + regw((regr(VPIF_CH1_CTRL) & (~VPIF_CH1_EN)), VPIF_CH1_CTRL); +} + +/* inline function to enable interrupt for channel0 */ +static inline void channel0_intr_enable(int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&vpif_lock, flags); + + if (enable) { + regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + + regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH0), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH0), + VPIF_INTEN_SET); + } else { + regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH0)), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH0), + VPIF_INTEN_SET); + } + spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable interrupt for channel1 */ +static inline void channel1_intr_enable(int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&vpif_lock, flags); + + if (enable) { + regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + + regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH1), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH1), + VPIF_INTEN_SET); + } else { + regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH1)), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH1), + VPIF_INTEN_SET); + } + spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to set buffer addresses in case of Y/C non mux mode */ +static inline void ch0_set_video_buf_addr_yc_nmux(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH0_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH0_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH1_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH1_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for video data */ +static inline void ch0_set_video_buf_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH0_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH0_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH0_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH0_BTM_STRT_ADD_CHROMA); +} + +static inline void ch1_set_video_buf_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + + regw(top_strt_luma, VPIF_CH1_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH1_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH1_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH1_BTM_STRT_ADD_CHROMA); +} + +static inline void ch0_set_vbi_addr(unsigned long top_vbi, + unsigned long btm_vbi, unsigned long a, unsigned long b) +{ + regw(top_vbi, VPIF_CH0_TOP_STRT_ADD_VANC); + regw(btm_vbi, VPIF_CH0_BTM_STRT_ADD_VANC); +} + +static inline void ch0_set_hbi_addr(unsigned long top_vbi, + unsigned long btm_vbi, unsigned long a, unsigned long b) +{ + regw(top_vbi, VPIF_CH0_TOP_STRT_ADD_HANC); + regw(btm_vbi, VPIF_CH0_BTM_STRT_ADD_HANC); +} + +static inline void ch1_set_vbi_addr(unsigned long top_vbi, + unsigned long btm_vbi, unsigned long a, unsigned long b) +{ + regw(top_vbi, VPIF_CH1_TOP_STRT_ADD_VANC); + regw(btm_vbi, VPIF_CH1_BTM_STRT_ADD_VANC); +} + +static inline void ch1_set_hbi_addr(unsigned long top_vbi, + unsigned long btm_vbi, unsigned long a, unsigned long b) +{ + regw(top_vbi, VPIF_CH1_TOP_STRT_ADD_HANC); + regw(btm_vbi, VPIF_CH1_BTM_STRT_ADD_HANC); +} + +/* Inline function to enable raw vbi in the given channel */ +static inline void disable_raw_feature(u8 channel_id, u8 index) +{ + u32 ctrl_reg; + if (0 == channel_id) + ctrl_reg = VPIF_CH0_CTRL; + else + ctrl_reg = VPIF_CH1_CTRL; + + if (1 == index) + vpif_clr_bit(ctrl_reg, VPIF_CH_VANC_EN_BIT); + else + vpif_clr_bit(ctrl_reg, VPIF_CH_HANC_EN_BIT); +} + +static inline void enable_raw_feature(u8 channel_id, u8 index) +{ + u32 ctrl_reg; + if (0 == channel_id) + ctrl_reg = VPIF_CH0_CTRL; + else + ctrl_reg = VPIF_CH1_CTRL; + + if (1 == index) + vpif_set_bit(ctrl_reg, VPIF_CH_VANC_EN_BIT); + else + vpif_set_bit(ctrl_reg, VPIF_CH_HANC_EN_BIT); +} + +/* inline function to enable/disable channel2 */ +static inline void enable_channel2(int enable) +{ + if (enable) { + regw((regr(VPIF_CH2_CTRL) | (VPIF_CH2_CLK_EN)), VPIF_CH2_CTRL); + regw((regr(VPIF_CH2_CTRL) | (VPIF_CH2_EN)), VPIF_CH2_CTRL); + } else { + regw((regr(VPIF_CH2_CTRL) & (~VPIF_CH2_CLK_EN)), VPIF_CH2_CTRL); + regw((regr(VPIF_CH2_CTRL) & (~VPIF_CH2_EN)), VPIF_CH2_CTRL); + } +} + +/* inline function to enable/disable channel3 */ +static inline void enable_channel3(int enable) +{ + if (enable) { + regw((regr(VPIF_CH3_CTRL) | (VPIF_CH3_CLK_EN)), VPIF_CH3_CTRL); + regw((regr(VPIF_CH3_CTRL) | (VPIF_CH3_EN)), VPIF_CH3_CTRL); + } else { + regw((regr(VPIF_CH3_CTRL) & (~VPIF_CH3_CLK_EN)), VPIF_CH3_CTRL); + regw((regr(VPIF_CH3_CTRL) & (~VPIF_CH3_EN)), VPIF_CH3_CTRL); + } +} + +/* inline function to enable interrupt for channel2 */ +static inline void channel2_intr_enable(int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&vpif_lock, flags); + + if (enable) { + regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH2), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH2), + VPIF_INTEN_SET); + } else { + regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH2)), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH2), + VPIF_INTEN_SET); + } + spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable interrupt for channel3 */ +static inline void channel3_intr_enable(int enable) +{ + unsigned long flags; + + spin_lock_irqsave(&vpif_lock, flags); + + if (enable) { + regw((regr(VPIF_INTEN) | 0x10), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | 0x10), VPIF_INTEN_SET); + + regw((regr(VPIF_INTEN) | VPIF_INTEN_FRAME_CH3), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH3), + VPIF_INTEN_SET); + } else { + regw((regr(VPIF_INTEN) & (~VPIF_INTEN_FRAME_CH3)), VPIF_INTEN); + regw((regr(VPIF_INTEN_SET) | VPIF_INTEN_FRAME_CH3), + VPIF_INTEN_SET); + } + spin_unlock_irqrestore(&vpif_lock, flags); +} + +/* inline function to enable raw vbi data for channel2 */ +static inline void channel2_raw_enable(int enable, u8 index) +{ + u32 mask; + + if (1 == index) + mask = VPIF_CH_VANC_EN_BIT; + else + mask = VPIF_CH_HANC_EN_BIT; + + if (enable) + vpif_set_bit(VPIF_CH2_CTRL, mask); + else + vpif_clr_bit(VPIF_CH2_CTRL, mask); +} + +/* inline function to enable raw vbi data for channel3*/ +static inline void channel3_raw_enable(int enable, u8 index) +{ + u32 mask; + + if (1 == index) + mask = VPIF_CH_VANC_EN_BIT; + else + mask = VPIF_CH_HANC_EN_BIT; + + if (enable) + vpif_set_bit(VPIF_CH3_CTRL, mask); + else + vpif_clr_bit(VPIF_CH3_CTRL, mask); +} + +/* function to enable clipping (for both active and blanking regions) on ch 2 */ +static inline void channel2_clipping_enable(int enable) +{ + if (enable) { + vpif_set_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ANC_EN); + vpif_set_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ACTIVE_EN); + } else { + vpif_clr_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ANC_EN); + vpif_clr_bit(VPIF_CH2_CTRL, VPIF_CH2_CLIP_ACTIVE_EN); + } +} + +/* function to enable clipping (for both active and blanking regions) on ch 3 */ +static inline void channel3_clipping_enable(int enable) +{ + if (enable) { + vpif_set_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ANC_EN); + vpif_set_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ACTIVE_EN); + } else { + vpif_clr_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ANC_EN); + vpif_clr_bit(VPIF_CH3_CTRL, VPIF_CH3_CLIP_ACTIVE_EN); + } +} + +/* inline function to set buffer addresses in case of Y/C non mux mode */ +static inline void ch2_set_video_buf_addr_yc_nmux(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH3_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH3_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for video data */ +static inline void ch2_set_video_buf_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH2_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH2_BTM_STRT_ADD_CHROMA); +} + +static inline void ch3_set_video_buf_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH3_TOP_STRT_ADD_LUMA); + regw(btm_strt_luma, VPIF_CH3_BTM_STRT_ADD_LUMA); + regw(top_strt_chroma, VPIF_CH3_TOP_STRT_ADD_CHROMA); + regw(btm_strt_chroma, VPIF_CH3_BTM_STRT_ADD_CHROMA); +} + +/* inline function to set buffer addresses in VPIF registers for vbi data */ +static inline void ch2_set_vbi_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH2_TOP_STRT_ADD_VANC); + regw(btm_strt_luma, VPIF_CH2_BTM_STRT_ADD_VANC); +} + +static inline void ch3_set_vbi_addr(unsigned long top_strt_luma, + unsigned long btm_strt_luma, + unsigned long top_strt_chroma, + unsigned long btm_strt_chroma) +{ + regw(top_strt_luma, VPIF_CH3_TOP_STRT_ADD_VANC); + regw(btm_strt_luma, VPIF_CH3_BTM_STRT_ADD_VANC); +} + +static inline int vpif_intr_status(int channel) +{ + int status = 0; + int mask; + + if (channel < 0 || channel > 3) + return 0; + + mask = 1 << channel; + status = regr(VPIF_STATUS) & mask; + regw(status, VPIF_STATUS_CLR); + + return status; +} + +#define VPIF_MAX_NAME (30) + +/* This structure will store size parameters as per the mode selected by user */ +struct vpif_channel_config_params { + char name[VPIF_MAX_NAME]; /* Name of the mode */ + u16 width; /* Indicates width of the image */ + u16 height; /* Indicates height of the image */ + u8 frm_fmt; /* Interlaced (0) or progressive (1) */ + u8 ycmux_mode; /* This mode requires one (0) or two (1) + channels */ + u16 eav2sav; /* length of eav 2 sav */ + u16 sav2eav; /* length of sav 2 eav */ + u16 l1, l3, l5, l7, l9, l11; /* Other parameter configurations */ + u16 vsize; /* Vertical size of the image */ + u8 capture_format; /* Indicates whether capture format + * is in BT or in CCD/CMOS */ + u8 vbi_supported; /* Indicates whether this mode + * supports capturing vbi or not */ + u8 hd_sd; /* HDTV (1) or SDTV (0) format */ + v4l2_std_id stdid; /* SDTV format */ + struct v4l2_dv_timings dv_timings; /* HDTV format */ +}; + +extern const unsigned int vpif_ch_params_count; +extern const struct vpif_channel_config_params vpif_ch_params[]; + +struct vpif_video_params; +struct vpif_params; +struct vpif_vbi_params; + +int vpif_set_video_params(struct vpif_params *vpifparams, u8 channel_id); +void vpif_set_vbi_display_params(struct vpif_vbi_params *vbiparams, + u8 channel_id); +int vpif_channel_getfid(u8 channel_id); + +enum data_size { + _8BITS = 0, + _10BITS, + _12BITS, +}; + +/* Structure for vpif parameters for raw vbi data */ +struct vpif_vbi_params { + __u32 hstart0; /* Horizontal start of raw vbi data for first field */ + __u32 vstart0; /* Vertical start of raw vbi data for first field */ + __u32 hsize0; /* Horizontal size of raw vbi data for first field */ + __u32 vsize0; /* Vertical size of raw vbi data for first field */ + __u32 hstart1; /* Horizontal start of raw vbi data for second field */ + __u32 vstart1; /* Vertical start of raw vbi data for second field */ + __u32 hsize1; /* Horizontal size of raw vbi data for second field */ + __u32 vsize1; /* Vertical size of raw vbi data for second field */ +}; + +/* structure for vpif parameters */ +struct vpif_video_params { + __u8 storage_mode; /* Indicates field or frame mode */ + unsigned long hpitch; + v4l2_std_id stdid; +}; + +struct vpif_params { + struct vpif_interface iface; + struct vpif_video_params video_params; + struct vpif_channel_config_params std_info; + union param { + struct vpif_vbi_params vbi_params; + enum data_size data_sz; + } params; +}; + +#endif /* End of #ifndef VPIF_H */ diff --git a/drivers/media/platform/ti/davinci/vpif_capture.c b/drivers/media/platform/ti/davinci/vpif_capture.c new file mode 100644 index 000000000..580723333 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpif_capture.c @@ -0,0 +1,1821 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2009 Texas Instruments Inc + * Copyright (C) 2014 Lad, Prabhakar + * + * TODO : add support for VBI & HBI data service + * add static buffer allocation + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "vpif.h" +#include "vpif_capture.h" + +MODULE_DESCRIPTION("TI DaVinci VPIF Capture driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VPIF_CAPTURE_VERSION); + +#define vpif_err(fmt, arg...) v4l2_err(&vpif_obj.v4l2_dev, fmt, ## arg) +#define vpif_dbg(level, debug, fmt, arg...) \ + v4l2_dbg(level, debug, &vpif_obj.v4l2_dev, fmt, ## arg) + +static int debug = 1; + +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +#define VPIF_DRIVER_NAME "vpif_capture" +MODULE_ALIAS("platform:" VPIF_DRIVER_NAME); + +/* global variables */ +static struct vpif_device vpif_obj = { {NULL} }; +static struct device *vpif_dev; +static void vpif_calculate_offsets(struct channel_obj *ch); +static void vpif_config_addr(struct channel_obj *ch, int muxmode); + +static u8 channel_first_int[VPIF_NUMBER_OF_OBJECTS][2] = { {1, 1} }; + +/* Is set to 1 in case of SDTV formats, 2 in case of HDTV formats. */ +static int ycmux_mode; + +static inline +struct vpif_cap_buffer *to_vpif_buffer(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct vpif_cap_buffer, vb); +} + +/** + * vpif_buffer_prepare : callback function for buffer prepare + * @vb: ptr to vb2_buffer + * + * This is the callback function for buffer prepare when vb2_qbuf() + * function is called. The buffer is prepared and user space virtual address + * or user address is converted into physical address + */ +static int vpif_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vb2_queue *q = vb->vb2_queue; + struct channel_obj *ch = vb2_get_drv_priv(q); + struct common_obj *common; + unsigned long addr; + + vpif_dbg(2, debug, "vpif_buffer_prepare\n"); + + common = &ch->common[VPIF_VIDEO_INDEX]; + + vb2_set_plane_payload(vb, 0, common->fmt.fmt.pix.sizeimage); + if (vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) + return -EINVAL; + + vbuf->field = common->fmt.fmt.pix.field; + + addr = vb2_dma_contig_plane_dma_addr(vb, 0); + if (!IS_ALIGNED((addr + common->ytop_off), 8) || + !IS_ALIGNED((addr + common->ybtm_off), 8) || + !IS_ALIGNED((addr + common->ctop_off), 8) || + !IS_ALIGNED((addr + common->cbtm_off), 8)) { + vpif_dbg(1, debug, "offset is not aligned\n"); + return -EINVAL; + } + + return 0; +} + +/** + * vpif_buffer_queue_setup : Callback function for buffer setup. + * @vq: vb2_queue ptr + * @nbuffers: ptr to number of buffers requested by application + * @nplanes: contains number of distinct video planes needed to hold a frame + * @sizes: contains the size (in bytes) of each plane. + * @alloc_devs: ptr to allocation context + * + * This callback function is called when reqbuf() is called to adjust + * the buffer count and buffer size + */ +static int vpif_buffer_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct channel_obj *ch = vb2_get_drv_priv(vq); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + unsigned size = common->fmt.fmt.pix.sizeimage; + + vpif_dbg(2, debug, "vpif_buffer_setup\n"); + + if (*nplanes) { + if (sizes[0] < size) + return -EINVAL; + size = sizes[0]; + } + + if (vq->num_buffers + *nbuffers < 3) + *nbuffers = 3 - vq->num_buffers; + + *nplanes = 1; + sizes[0] = size; + + /* Calculate the offset for Y and C data in the buffer */ + vpif_calculate_offsets(ch); + + return 0; +} + +/** + * vpif_buffer_queue : Callback function to add buffer to DMA queue + * @vb: ptr to vb2_buffer + */ +static void vpif_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct channel_obj *ch = vb2_get_drv_priv(vb->vb2_queue); + struct vpif_cap_buffer *buf = to_vpif_buffer(vbuf); + struct common_obj *common; + unsigned long flags; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + vpif_dbg(2, debug, "vpif_buffer_queue\n"); + + spin_lock_irqsave(&common->irqlock, flags); + /* add the buffer to the DMA queue */ + list_add_tail(&buf->list, &common->dma_queue); + spin_unlock_irqrestore(&common->irqlock, flags); +} + +/** + * vpif_start_streaming : Starts the DMA engine for streaming + * @vq: ptr to vb2_buffer + * @count: number of buffers + */ +static int vpif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vpif_capture_config *vpif_config_data = + vpif_dev->platform_data; + struct channel_obj *ch = vb2_get_drv_priv(vq); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_params *vpif = &ch->vpifparams; + struct vpif_cap_buffer *buf, *tmp; + unsigned long addr, flags; + int ret; + + /* Initialize field_id */ + ch->field_id = 0; + + /* configure 1 or 2 channel mode */ + if (vpif_config_data->setup_input_channel_mode) { + ret = vpif_config_data-> + setup_input_channel_mode(vpif->std_info.ycmux_mode); + if (ret < 0) { + vpif_dbg(1, debug, "can't set vpif channel mode\n"); + goto err; + } + } + + ret = v4l2_subdev_call(ch->sd, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) { + vpif_dbg(1, debug, "stream on failed in subdev\n"); + goto err; + } + + /* Call vpif_set_params function to set the parameters and addresses */ + ret = vpif_set_video_params(vpif, ch->channel_id); + if (ret < 0) { + vpif_dbg(1, debug, "can't set video params\n"); + goto err; + } + + ycmux_mode = ret; + vpif_config_addr(ch, ret); + + /* Get the next frame from the buffer queue */ + spin_lock_irqsave(&common->irqlock, flags); + common->cur_frm = common->next_frm = list_entry(common->dma_queue.next, + struct vpif_cap_buffer, list); + /* Remove buffer from the buffer queue */ + list_del(&common->cur_frm->list); + spin_unlock_irqrestore(&common->irqlock, flags); + + addr = vb2_dma_contig_plane_dma_addr(&common->cur_frm->vb.vb2_buf, 0); + + common->set_addr(addr + common->ytop_off, + addr + common->ybtm_off, + addr + common->ctop_off, + addr + common->cbtm_off); + + /** + * Set interrupt for both the fields in VPIF Register enable channel in + * VPIF register + */ + channel_first_int[VPIF_VIDEO_INDEX][ch->channel_id] = 1; + if (VPIF_CHANNEL0_VIDEO == ch->channel_id) { + channel0_intr_assert(); + channel0_intr_enable(1); + enable_channel0(1); + } + if (VPIF_CHANNEL1_VIDEO == ch->channel_id || + ycmux_mode == 2) { + channel1_intr_assert(); + channel1_intr_enable(1); + enable_channel1(1); + } + + return 0; + +err: + spin_lock_irqsave(&common->irqlock, flags); + list_for_each_entry_safe(buf, tmp, &common->dma_queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&common->irqlock, flags); + + return ret; +} + +/** + * vpif_stop_streaming : Stop the DMA engine + * @vq: ptr to vb2_queue + * + * This callback stops the DMA engine and any remaining buffers + * in the DMA queue are released. + */ +static void vpif_stop_streaming(struct vb2_queue *vq) +{ + struct channel_obj *ch = vb2_get_drv_priv(vq); + struct common_obj *common; + unsigned long flags; + int ret; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + /* Disable channel as per its device type and channel id */ + if (VPIF_CHANNEL0_VIDEO == ch->channel_id) { + enable_channel0(0); + channel0_intr_enable(0); + } + if (VPIF_CHANNEL1_VIDEO == ch->channel_id || + ycmux_mode == 2) { + enable_channel1(0); + channel1_intr_enable(0); + } + + ycmux_mode = 0; + + ret = v4l2_subdev_call(ch->sd, video, s_stream, 0); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) + vpif_dbg(1, debug, "stream off failed in subdev\n"); + + /* release all active buffers */ + if (common->cur_frm == common->next_frm) { + vb2_buffer_done(&common->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } else { + if (common->cur_frm) + vb2_buffer_done(&common->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + if (common->next_frm) + vb2_buffer_done(&common->next_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } + + spin_lock_irqsave(&common->irqlock, flags); + while (!list_empty(&common->dma_queue)) { + common->next_frm = list_entry(common->dma_queue.next, + struct vpif_cap_buffer, list); + list_del(&common->next_frm->list); + vb2_buffer_done(&common->next_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&common->irqlock, flags); +} + +static const struct vb2_ops video_qops = { + .queue_setup = vpif_buffer_queue_setup, + .buf_prepare = vpif_buffer_prepare, + .start_streaming = vpif_start_streaming, + .stop_streaming = vpif_stop_streaming, + .buf_queue = vpif_buffer_queue, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/** + * vpif_process_buffer_complete: process a completed buffer + * @common: ptr to common channel object + * + * This function time stamp the buffer and mark it as DONE. It also + * wake up any process waiting on the QUEUE and set the next buffer + * as current + */ +static void vpif_process_buffer_complete(struct common_obj *common) +{ + common->cur_frm->vb.vb2_buf.timestamp = ktime_get_ns(); + vb2_buffer_done(&common->cur_frm->vb.vb2_buf, VB2_BUF_STATE_DONE); + /* Make curFrm pointing to nextFrm */ + common->cur_frm = common->next_frm; +} + +/** + * vpif_schedule_next_buffer: set next buffer address for capture + * @common : ptr to common channel object + * + * This function will get next buffer from the dma queue and + * set the buffer address in the vpif register for capture. + * the buffer is marked active + */ +static void vpif_schedule_next_buffer(struct common_obj *common) +{ + unsigned long addr = 0; + + spin_lock(&common->irqlock); + common->next_frm = list_entry(common->dma_queue.next, + struct vpif_cap_buffer, list); + /* Remove that buffer from the buffer queue */ + list_del(&common->next_frm->list); + spin_unlock(&common->irqlock); + addr = vb2_dma_contig_plane_dma_addr(&common->next_frm->vb.vb2_buf, 0); + + /* Set top and bottom field addresses in VPIF registers */ + common->set_addr(addr + common->ytop_off, + addr + common->ybtm_off, + addr + common->ctop_off, + addr + common->cbtm_off); +} + +/** + * vpif_channel_isr : ISR handler for vpif capture + * @irq: irq number + * @dev_id: dev_id ptr + * + * It changes status of the captured buffer, takes next buffer from the queue + * and sets its address in VPIF registers + */ +static irqreturn_t vpif_channel_isr(int irq, void *dev_id) +{ + struct vpif_device *dev = &vpif_obj; + struct common_obj *common; + struct channel_obj *ch; + int channel_id; + int fid = -1, i; + + channel_id = *(int *)(dev_id); + if (!vpif_intr_status(channel_id)) + return IRQ_NONE; + + ch = dev->dev[channel_id]; + + for (i = 0; i < VPIF_NUMBER_OF_OBJECTS; i++) { + common = &ch->common[i]; + /* skip If streaming is not started in this channel */ + /* Check the field format */ + if (1 == ch->vpifparams.std_info.frm_fmt || + common->fmt.fmt.pix.field == V4L2_FIELD_NONE) { + /* Progressive mode */ + spin_lock(&common->irqlock); + if (list_empty(&common->dma_queue)) { + spin_unlock(&common->irqlock); + continue; + } + spin_unlock(&common->irqlock); + + if (!channel_first_int[i][channel_id]) + vpif_process_buffer_complete(common); + + channel_first_int[i][channel_id] = 0; + + vpif_schedule_next_buffer(common); + + + channel_first_int[i][channel_id] = 0; + } else { + /** + * Interlaced mode. If it is first interrupt, ignore + * it + */ + if (channel_first_int[i][channel_id]) { + channel_first_int[i][channel_id] = 0; + continue; + } + if (0 == i) { + ch->field_id ^= 1; + /* Get field id from VPIF registers */ + fid = vpif_channel_getfid(ch->channel_id); + if (fid != ch->field_id) { + /** + * If field id does not match stored + * field id, make them in sync + */ + if (0 == fid) + ch->field_id = fid; + return IRQ_HANDLED; + } + } + /* device field id and local field id are in sync */ + if (0 == fid) { + /* this is even field */ + if (common->cur_frm == common->next_frm) + continue; + + /* mark the current buffer as done */ + vpif_process_buffer_complete(common); + } else if (1 == fid) { + /* odd field */ + spin_lock(&common->irqlock); + if (list_empty(&common->dma_queue) || + (common->cur_frm != common->next_frm)) { + spin_unlock(&common->irqlock); + continue; + } + spin_unlock(&common->irqlock); + + vpif_schedule_next_buffer(common); + } + } + } + return IRQ_HANDLED; +} + +/** + * vpif_update_std_info() - update standard related info + * @ch: ptr to channel object + * + * For a given standard selected by application, update values + * in the device data structures + */ +static int vpif_update_std_info(struct channel_obj *ch) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_params *vpifparams = &ch->vpifparams; + const struct vpif_channel_config_params *config; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + struct video_obj *vid_ch = &ch->video; + int index; + struct v4l2_pix_format *pixfmt = &common->fmt.fmt.pix; + + vpif_dbg(2, debug, "vpif_update_std_info\n"); + + /* + * if called after try_fmt or g_fmt, there will already be a size + * so use that by default. + */ + if (pixfmt->width && pixfmt->height) { + if (pixfmt->field == V4L2_FIELD_ANY || + pixfmt->field == V4L2_FIELD_NONE) + pixfmt->field = V4L2_FIELD_NONE; + + vpifparams->iface.if_type = VPIF_IF_BT656; + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10 || + pixfmt->pixelformat == V4L2_PIX_FMT_SBGGR8) + vpifparams->iface.if_type = VPIF_IF_RAW_BAYER; + + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10) + vpifparams->params.data_sz = 1; /* 10 bits/pixel. */ + + /* + * For raw formats from camera sensors, we don't need + * the std_info from table lookup, so nothing else to do here. + */ + if (vpifparams->iface.if_type == VPIF_IF_RAW_BAYER) { + memset(std_info, 0, sizeof(struct vpif_channel_config_params)); + vpifparams->std_info.capture_format = 1; /* CCD/raw mode */ + return 0; + } + } + + for (index = 0; index < vpif_ch_params_count; index++) { + config = &vpif_ch_params[index]; + if (config->hd_sd == 0) { + vpif_dbg(2, debug, "SD format\n"); + if (config->stdid & vid_ch->stdid) { + memcpy(std_info, config, sizeof(*config)); + break; + } + } else { + vpif_dbg(2, debug, "HD format\n"); + if (!memcmp(&config->dv_timings, &vid_ch->dv_timings, + sizeof(vid_ch->dv_timings))) { + memcpy(std_info, config, sizeof(*config)); + break; + } + } + } + + /* standard not found */ + if (index == vpif_ch_params_count) + return -EINVAL; + + common->fmt.fmt.pix.width = std_info->width; + common->width = std_info->width; + common->fmt.fmt.pix.height = std_info->height; + common->height = std_info->height; + common->fmt.fmt.pix.sizeimage = common->height * common->width * 2; + common->fmt.fmt.pix.bytesperline = std_info->width; + vpifparams->video_params.hpitch = std_info->width; + vpifparams->video_params.storage_mode = std_info->frm_fmt; + + if (vid_ch->stdid) + common->fmt.fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + else + common->fmt.fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + if (ch->vpifparams.std_info.frm_fmt) + common->fmt.fmt.pix.field = V4L2_FIELD_NONE; + else + common->fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + + if (ch->vpifparams.iface.if_type == VPIF_IF_RAW_BAYER) + common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_SBGGR8; + else + common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_NV16; + + common->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + return 0; +} + +/** + * vpif_calculate_offsets : This function calculates buffers offsets + * @ch : ptr to channel object + * + * This function calculates buffer offsets for Y and C in the top and + * bottom field + */ +static void vpif_calculate_offsets(struct channel_obj *ch) +{ + unsigned int hpitch, sizeimage; + struct video_obj *vid_ch = &(ch->video); + struct vpif_params *vpifparams = &ch->vpifparams; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + enum v4l2_field field = common->fmt.fmt.pix.field; + + vpif_dbg(2, debug, "vpif_calculate_offsets\n"); + + if (V4L2_FIELD_ANY == field) { + if (vpifparams->std_info.frm_fmt) + vid_ch->buf_field = V4L2_FIELD_NONE; + else + vid_ch->buf_field = V4L2_FIELD_INTERLACED; + } else + vid_ch->buf_field = common->fmt.fmt.pix.field; + + sizeimage = common->fmt.fmt.pix.sizeimage; + + hpitch = common->fmt.fmt.pix.bytesperline; + + if ((V4L2_FIELD_NONE == vid_ch->buf_field) || + (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { + /* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ + common->ytop_off = 0; + common->ybtm_off = hpitch; + common->ctop_off = sizeimage / 2; + common->cbtm_off = sizeimage / 2 + hpitch; + } else if (V4L2_FIELD_SEQ_TB == vid_ch->buf_field) { + /* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ + common->ytop_off = 0; + common->ybtm_off = sizeimage / 4; + common->ctop_off = sizeimage / 2; + common->cbtm_off = common->ctop_off + sizeimage / 4; + } else if (V4L2_FIELD_SEQ_BT == vid_ch->buf_field) { + /* Calculate offsets for Y top, Y Bottom, C top and C Bottom */ + common->ybtm_off = 0; + common->ytop_off = sizeimage / 4; + common->cbtm_off = sizeimage / 2; + common->ctop_off = common->cbtm_off + sizeimage / 4; + } + if ((V4L2_FIELD_NONE == vid_ch->buf_field) || + (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) + vpifparams->video_params.storage_mode = 1; + else + vpifparams->video_params.storage_mode = 0; + + if (1 == vpifparams->std_info.frm_fmt) + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline; + else { + if ((field == V4L2_FIELD_ANY) + || (field == V4L2_FIELD_INTERLACED)) + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline * 2; + else + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline; + } + + ch->vpifparams.video_params.stdid = vpifparams->std_info.stdid; +} + +/** + * vpif_config_addr() - function to configure buffer address in vpif + * @ch: channel ptr + * @muxmode: channel mux mode + */ +static void vpif_config_addr(struct channel_obj *ch, int muxmode) +{ + struct common_obj *common; + + vpif_dbg(2, debug, "vpif_config_addr\n"); + + common = &(ch->common[VPIF_VIDEO_INDEX]); + + if (VPIF_CHANNEL1_VIDEO == ch->channel_id) + common->set_addr = ch1_set_video_buf_addr; + else if (2 == muxmode) + common->set_addr = ch0_set_video_buf_addr_yc_nmux; + else + common->set_addr = ch0_set_video_buf_addr; +} + +/** + * vpif_input_to_subdev() - Maps input to sub device + * @vpif_cfg: global config ptr + * @chan_cfg: channel config ptr + * @input_index: Given input index from application + * + * lookup the sub device information for a given input index. + * we report all the inputs to application. inputs table also + * has sub device name for the each input + */ +static int vpif_input_to_subdev( + struct vpif_capture_config *vpif_cfg, + struct vpif_capture_chan_config *chan_cfg, + int input_index) +{ + struct vpif_subdev_info *subdev_info; + const char *subdev_name; + int i; + + vpif_dbg(2, debug, "vpif_input_to_subdev\n"); + + if (!chan_cfg) + return -1; + if (input_index >= chan_cfg->input_count) + return -1; + subdev_name = chan_cfg->inputs[input_index].subdev_name; + if (!subdev_name) + return -1; + + /* loop through the sub device list to get the sub device info */ + for (i = 0; i < vpif_cfg->subdev_count; i++) { + subdev_info = &vpif_cfg->subdev_info[i]; + if (subdev_info && !strcmp(subdev_info->name, subdev_name)) + return i; + } + return -1; +} + +/** + * vpif_set_input() - Select an input + * @vpif_cfg: global config ptr + * @ch: channel + * @index: Given input index from application + * + * Select the given input. + */ +static int vpif_set_input( + struct vpif_capture_config *vpif_cfg, + struct channel_obj *ch, + int index) +{ + struct vpif_capture_chan_config *chan_cfg = + &vpif_cfg->chan_config[ch->channel_id]; + struct vpif_subdev_info *subdev_info = NULL; + struct v4l2_subdev *sd = NULL; + u32 input = 0, output = 0; + int sd_index; + int ret; + + sd_index = vpif_input_to_subdev(vpif_cfg, chan_cfg, index); + if (sd_index >= 0) { + sd = vpif_obj.sd[sd_index]; + subdev_info = &vpif_cfg->subdev_info[sd_index]; + } else { + /* no subdevice, no input to setup */ + return 0; + } + + /* first setup input path from sub device to vpif */ + if (sd && vpif_cfg->setup_input_path) { + ret = vpif_cfg->setup_input_path(ch->channel_id, + subdev_info->name); + if (ret < 0) { + vpif_dbg(1, debug, "couldn't setup input path for the" \ + " sub device %s, for input index %d\n", + subdev_info->name, index); + return ret; + } + } + + if (sd) { + input = chan_cfg->inputs[index].input_route; + output = chan_cfg->inputs[index].output_route; + ret = v4l2_subdev_call(sd, video, s_routing, + input, output, 0); + if (ret < 0 && ret != -ENOIOCTLCMD) { + vpif_dbg(1, debug, "Failed to set input\n"); + return ret; + } + } + ch->input_idx = index; + ch->sd = sd; + /* copy interface parameters to vpif */ + ch->vpifparams.iface = chan_cfg->vpif_if; + + /* update tvnorms from the sub device input info */ + ch->video_dev.tvnorms = chan_cfg->inputs[index].input.std; + return 0; +} + +/** + * vpif_querystd() - querystd handler + * @file: file ptr + * @priv: file handle + * @std_id: ptr to std id + * + * This function is called to detect standard at the selected input + */ +static int vpif_querystd(struct file *file, void *priv, v4l2_std_id *std_id) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + int ret; + + vpif_dbg(2, debug, "vpif_querystd\n"); + + /* Call querystd function of decoder device */ + ret = v4l2_subdev_call(ch->sd, video, querystd, std_id); + + if (ret == -ENOIOCTLCMD || ret == -ENODEV) + return -ENODATA; + if (ret) { + vpif_dbg(1, debug, "Failed to query standard for sub devices\n"); + return ret; + } + + return 0; +} + +/** + * vpif_g_std() - get STD handler + * @file: file ptr + * @priv: file handle + * @std: ptr to std id + */ +static int vpif_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_capture_chan_config *chan_cfg; + struct v4l2_input input; + + vpif_dbg(2, debug, "vpif_g_std\n"); + + if (!config->chan_config[ch->channel_id].inputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + input = chan_cfg->inputs[ch->input_idx].input; + if (input.capabilities != V4L2_IN_CAP_STD) + return -ENODATA; + + *std = ch->video.stdid; + return 0; +} + +/** + * vpif_s_std() - set STD handler + * @file: file ptr + * @priv: file handle + * @std_id: ptr to std id + */ +static int vpif_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_capture_chan_config *chan_cfg; + struct v4l2_input input; + int ret; + + vpif_dbg(2, debug, "vpif_s_std\n"); + + if (!config->chan_config[ch->channel_id].inputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + input = chan_cfg->inputs[ch->input_idx].input; + if (input.capabilities != V4L2_IN_CAP_STD) + return -ENODATA; + + if (vb2_is_busy(&common->buffer_queue)) + return -EBUSY; + + /* Call encoder subdevice function to set the standard */ + ch->video.stdid = std_id; + memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + + /* Get the information about the standard */ + if (vpif_update_std_info(ch)) { + vpif_err("Error getting the standard info\n"); + return -EINVAL; + } + + /* set standard in the sub device */ + ret = v4l2_subdev_call(ch->sd, video, s_std, std_id); + if (ret && ret != -ENOIOCTLCMD && ret != -ENODEV) { + vpif_dbg(1, debug, "Failed to set standard for sub devices\n"); + return ret; + } + return 0; +} + +/** + * vpif_enum_input() - ENUMINPUT handler + * @file: file ptr + * @priv: file handle + * @input: ptr to input structure + */ +static int vpif_enum_input(struct file *file, void *priv, + struct v4l2_input *input) +{ + + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_capture_chan_config *chan_cfg; + + chan_cfg = &config->chan_config[ch->channel_id]; + + if (input->index >= chan_cfg->input_count) + return -EINVAL; + + memcpy(input, &chan_cfg->inputs[input->index].input, + sizeof(*input)); + return 0; +} + +/** + * vpif_g_input() - Get INPUT handler + * @file: file ptr + * @priv: file handle + * @index: ptr to input index + */ +static int vpif_g_input(struct file *file, void *priv, unsigned int *index) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + + *index = ch->input_idx; + return 0; +} + +/** + * vpif_s_input() - Set INPUT handler + * @file: file ptr + * @priv: file handle + * @index: input index + */ +static int vpif_s_input(struct file *file, void *priv, unsigned int index) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_capture_chan_config *chan_cfg; + + chan_cfg = &config->chan_config[ch->channel_id]; + + if (index >= chan_cfg->input_count) + return -EINVAL; + + if (vb2_is_busy(&common->buffer_queue)) + return -EBUSY; + + return vpif_set_input(config, ch, index); +} + +/** + * vpif_enum_fmt_vid_cap() - ENUM_FMT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to V4L2 format descriptor + */ +static int vpif_enum_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + + if (fmt->index != 0) { + vpif_dbg(1, debug, "Invalid format index\n"); + return -EINVAL; + } + + /* Fill in the information about format */ + if (ch->vpifparams.iface.if_type == VPIF_IF_RAW_BAYER) + fmt->pixelformat = V4L2_PIX_FMT_SBGGR8; + else + fmt->pixelformat = V4L2_PIX_FMT_NV16; + return 0; +} + +/** + * vpif_try_fmt_vid_cap() - TRY_FMT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_try_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + struct common_obj *common = &(ch->common[VPIF_VIDEO_INDEX]); + + common->fmt = *fmt; + vpif_update_std_info(ch); + + pixfmt->field = common->fmt.fmt.pix.field; + pixfmt->colorspace = common->fmt.fmt.pix.colorspace; + pixfmt->bytesperline = common->fmt.fmt.pix.width; + pixfmt->width = common->fmt.fmt.pix.width; + pixfmt->height = common->fmt.fmt.pix.height; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height * 2; + if (pixfmt->pixelformat == V4L2_PIX_FMT_SGRBG10) { + pixfmt->bytesperline = common->fmt.fmt.pix.width * 2; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; + } + + dev_dbg(vpif_dev, "%s: %d x %d; pitch=%d pixelformat=0x%08x, field=%d, size=%d\n", __func__, + pixfmt->width, pixfmt->height, + pixfmt->bytesperline, pixfmt->pixelformat, + pixfmt->field, pixfmt->sizeimage); + + return 0; +} + + +/** + * vpif_g_fmt_vid_cap() - Set INPUT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_g_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct v4l2_pix_format *pix_fmt = &fmt->fmt.pix; + struct v4l2_subdev_format format = { + .which = V4L2_SUBDEV_FORMAT_ACTIVE, + }; + struct v4l2_mbus_framefmt *mbus_fmt = &format.format; + int ret; + + /* Check the validity of the buffer type */ + if (common->fmt.type != fmt->type) + return -EINVAL; + + /* By default, use currently set fmt */ + *fmt = common->fmt; + + /* If subdev has get_fmt, use that to override */ + ret = v4l2_subdev_call(ch->sd, pad, get_fmt, NULL, &format); + if (!ret && mbus_fmt->code) { + v4l2_fill_pix_format(pix_fmt, mbus_fmt); + pix_fmt->bytesperline = pix_fmt->width; + if (mbus_fmt->code == MEDIA_BUS_FMT_SGRBG10_1X10) { + /* e.g. mt9v032 */ + pix_fmt->pixelformat = V4L2_PIX_FMT_SGRBG10; + pix_fmt->bytesperline = pix_fmt->width * 2; + } else if (mbus_fmt->code == MEDIA_BUS_FMT_UYVY8_2X8) { + /* e.g. tvp514x */ + pix_fmt->pixelformat = V4L2_PIX_FMT_NV16; + pix_fmt->bytesperline = pix_fmt->width * 2; + } else { + dev_warn(vpif_dev, "%s: Unhandled media-bus format 0x%x\n", + __func__, mbus_fmt->code); + } + pix_fmt->sizeimage = pix_fmt->bytesperline * pix_fmt->height; + dev_dbg(vpif_dev, "%s: %d x %d; pitch=%d, pixelformat=0x%08x, code=0x%x, field=%d, size=%d\n", __func__, + pix_fmt->width, pix_fmt->height, + pix_fmt->bytesperline, pix_fmt->pixelformat, + mbus_fmt->code, pix_fmt->field, pix_fmt->sizeimage); + + common->fmt = *fmt; + vpif_update_std_info(ch); + } + + return 0; +} + +/** + * vpif_s_fmt_vid_cap() - Set FMT handler + * @file: file ptr + * @priv: file handle + * @fmt: ptr to v4l2 format structure + */ +static int vpif_s_fmt_vid_cap(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + int ret; + + vpif_dbg(2, debug, "%s\n", __func__); + + if (vb2_is_busy(&common->buffer_queue)) + return -EBUSY; + + ret = vpif_try_fmt_vid_cap(file, priv, fmt); + if (ret) + return ret; + + /* store the format in the channel object */ + common->fmt = *fmt; + return 0; +} + +/** + * vpif_querycap() - QUERYCAP handler + * @file: file ptr + * @priv: file handle + * @cap: ptr to v4l2_capability structure + */ +static int vpif_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + + strscpy(cap->driver, VPIF_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, config->card_name, sizeof(cap->card)); + + return 0; +} + +/** + * vpif_enum_dv_timings() - ENUM_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_capture_chan_config *chan_cfg; + struct v4l2_input input; + int ret; + + if (!config->chan_config[ch->channel_id].inputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + input = chan_cfg->inputs[ch->input_idx].input; + if (input.capabilities != V4L2_IN_CAP_DV_TIMINGS) + return -ENODATA; + + timings->pad = 0; + + ret = v4l2_subdev_call(ch->sd, pad, enum_dv_timings, timings); + if (ret == -ENOIOCTLCMD || ret == -ENODEV) + return -EINVAL; + + return ret; +} + +/** + * vpif_query_dv_timings() - QUERY_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_query_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_capture_chan_config *chan_cfg; + struct v4l2_input input; + int ret; + + if (!config->chan_config[ch->channel_id].inputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + input = chan_cfg->inputs[ch->input_idx].input; + if (input.capabilities != V4L2_IN_CAP_DV_TIMINGS) + return -ENODATA; + + ret = v4l2_subdev_call(ch->sd, video, query_dv_timings, timings); + if (ret == -ENOIOCTLCMD || ret == -ENODEV) + return -ENODATA; + + return ret; +} + +/** + * vpif_s_dv_timings() - S_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_s_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_params *vpifparams = &ch->vpifparams; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct video_obj *vid_ch = &ch->video; + struct v4l2_bt_timings *bt = &vid_ch->dv_timings.bt; + struct vpif_capture_chan_config *chan_cfg; + struct v4l2_input input; + int ret; + + if (!config->chan_config[ch->channel_id].inputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + input = chan_cfg->inputs[ch->input_idx].input; + if (input.capabilities != V4L2_IN_CAP_DV_TIMINGS) + return -ENODATA; + + if (timings->type != V4L2_DV_BT_656_1120) { + vpif_dbg(2, debug, "Timing type not defined\n"); + return -EINVAL; + } + + if (vb2_is_busy(&common->buffer_queue)) + return -EBUSY; + + /* Configure subdevice timings, if any */ + ret = v4l2_subdev_call(ch->sd, video, s_dv_timings, timings); + if (ret == -ENOIOCTLCMD || ret == -ENODEV) + ret = 0; + if (ret < 0) { + vpif_dbg(2, debug, "Error setting custom DV timings\n"); + return ret; + } + + if (!(timings->bt.width && timings->bt.height && + (timings->bt.hbackporch || + timings->bt.hfrontporch || + timings->bt.hsync) && + timings->bt.vfrontporch && + (timings->bt.vbackporch || + timings->bt.vsync))) { + vpif_dbg(2, debug, "Timings for width, height, horizontal back porch, horizontal sync, horizontal front porch, vertical back porch, vertical sync and vertical back porch must be defined\n"); + return -EINVAL; + } + + vid_ch->dv_timings = *timings; + + /* Configure video port timings */ + + std_info->eav2sav = V4L2_DV_BT_BLANKING_WIDTH(bt) - 8; + std_info->sav2eav = bt->width; + + std_info->l1 = 1; + std_info->l3 = bt->vsync + bt->vbackporch + 1; + + std_info->vsize = V4L2_DV_BT_FRAME_HEIGHT(bt); + if (bt->interlaced) { + if (bt->il_vbackporch || bt->il_vfrontporch || bt->il_vsync) { + std_info->l5 = std_info->vsize/2 - + (bt->vfrontporch - 1); + std_info->l7 = std_info->vsize/2 + 1; + std_info->l9 = std_info->l7 + bt->il_vsync + + bt->il_vbackporch + 1; + std_info->l11 = std_info->vsize - + (bt->il_vfrontporch - 1); + } else { + vpif_dbg(2, debug, "Required timing values for interlaced BT format missing\n"); + return -EINVAL; + } + } else { + std_info->l5 = std_info->vsize - (bt->vfrontporch - 1); + } + strscpy(std_info->name, "Custom timings BT656/1120", + sizeof(std_info->name)); + std_info->width = bt->width; + std_info->height = bt->height; + std_info->frm_fmt = bt->interlaced ? 0 : 1; + std_info->ycmux_mode = 0; + std_info->capture_format = 0; + std_info->vbi_supported = 0; + std_info->hd_sd = 1; + std_info->stdid = 0; + + vid_ch->stdid = 0; + return 0; +} + +/** + * vpif_g_dv_timings() - G_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_capture_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct video_obj *vid_ch = &ch->video; + struct vpif_capture_chan_config *chan_cfg; + struct v4l2_input input; + + if (!config->chan_config[ch->channel_id].inputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + input = chan_cfg->inputs[ch->input_idx].input; + if (input.capabilities != V4L2_IN_CAP_DV_TIMINGS) + return -ENODATA; + + *timings = vid_ch->dv_timings; + + return 0; +} + +/* + * vpif_log_status() - Status information + * @file: file ptr + * @priv: file handle + * + * Returns zero. + */ +static int vpif_log_status(struct file *filep, void *priv) +{ + /* status for sub devices */ + v4l2_device_call_all(&vpif_obj.v4l2_dev, 0, core, log_status); + + return 0; +} + +/* vpif capture ioctl operations */ +static const struct v4l2_ioctl_ops vpif_ioctl_ops = { + .vidioc_querycap = vpif_querycap, + .vidioc_enum_fmt_vid_cap = vpif_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vpif_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vpif_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vpif_try_fmt_vid_cap, + + .vidioc_enum_input = vpif_enum_input, + .vidioc_s_input = vpif_s_input, + .vidioc_g_input = vpif_g_input, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_querystd = vpif_querystd, + .vidioc_s_std = vpif_s_std, + .vidioc_g_std = vpif_g_std, + + .vidioc_enum_dv_timings = vpif_enum_dv_timings, + .vidioc_query_dv_timings = vpif_query_dv_timings, + .vidioc_s_dv_timings = vpif_s_dv_timings, + .vidioc_g_dv_timings = vpif_g_dv_timings, + + .vidioc_log_status = vpif_log_status, +}; + +/* vpif file operations */ +static const struct v4l2_file_operations vpif_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll +}; + +/** + * initialize_vpif() - Initialize vpif data structures + * + * Allocate memory for data structures and initialize them + */ +static int initialize_vpif(void) +{ + int err, i, j; + int free_channel_objects_index; + + /* Allocate memory for six channel objects */ + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + vpif_obj.dev[i] = + kzalloc(sizeof(*vpif_obj.dev[i]), GFP_KERNEL); + /* If memory allocation fails, return error */ + if (!vpif_obj.dev[i]) { + free_channel_objects_index = i; + err = -ENOMEM; + goto vpif_init_free_channel_objects; + } + } + return 0; + +vpif_init_free_channel_objects: + for (j = 0; j < free_channel_objects_index; j++) + kfree(vpif_obj.dev[j]); + return err; +} + +static inline void free_vpif_objs(void) +{ + int i; + + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) + kfree(vpif_obj.dev[i]); +} + +static int vpif_async_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_subdev *asd) +{ + int i; + + for (i = 0; i < vpif_obj.config->asd_sizes[0]; i++) { + struct v4l2_async_subdev *_asd = vpif_obj.config->asd[i]; + const struct fwnode_handle *fwnode = _asd->match.fwnode; + + if (fwnode == subdev->fwnode) { + vpif_obj.sd[i] = subdev; + vpif_obj.config->chan_config->inputs[i].subdev_name = + (char *)to_of_node(subdev->fwnode)->full_name; + vpif_dbg(2, debug, + "%s: setting input %d subdev_name = %s\n", + __func__, i, + vpif_obj.config->chan_config->inputs[i].subdev_name); + return 0; + } + } + + for (i = 0; i < vpif_obj.config->subdev_count; i++) + if (!strcmp(vpif_obj.config->subdev_info[i].name, + subdev->name)) { + vpif_obj.sd[i] = subdev; + return 0; + } + + return -EINVAL; +} + +static int vpif_probe_complete(void) +{ + struct common_obj *common; + struct video_device *vdev; + struct channel_obj *ch; + struct vb2_queue *q; + int j, err, k; + + for (j = 0; j < VPIF_CAPTURE_MAX_DEVICES; j++) { + ch = vpif_obj.dev[j]; + ch->channel_id = j; + common = &(ch->common[VPIF_VIDEO_INDEX]); + spin_lock_init(&common->irqlock); + mutex_init(&common->lock); + + /* select input 0 */ + err = vpif_set_input(vpif_obj.config, ch, 0); + if (err) + goto probe_out; + + /* set initial format */ + ch->video.stdid = V4L2_STD_525_60; + memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + common->fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + vpif_update_std_info(ch); + + /* Initialize vb2 queue */ + q = &common->buffer_queue; + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = ch; + q->ops = &video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct vpif_cap_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 1; + q->lock = &common->lock; + q->dev = vpif_dev; + + err = vb2_queue_init(q); + if (err) { + vpif_err("vpif_capture: vb2_queue_init() failed\n"); + goto probe_out; + } + + INIT_LIST_HEAD(&common->dma_queue); + + /* Initialize the video_device structure */ + vdev = &ch->video_dev; + strscpy(vdev->name, VPIF_DRIVER_NAME, sizeof(vdev->name)); + vdev->release = video_device_release_empty; + vdev->fops = &vpif_fops; + vdev->ioctl_ops = &vpif_ioctl_ops; + vdev->v4l2_dev = &vpif_obj.v4l2_dev; + vdev->vfl_dir = VFL_DIR_RX; + vdev->queue = q; + vdev->lock = &common->lock; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + video_set_drvdata(&ch->video_dev, ch); + err = video_register_device(vdev, + VFL_TYPE_VIDEO, (j ? 1 : 0)); + if (err) + goto probe_out; + } + + v4l2_info(&vpif_obj.v4l2_dev, "VPIF capture driver initialized\n"); + return 0; + +probe_out: + for (k = 0; k < j; k++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[k]; + /* Unregister video device */ + video_unregister_device(&ch->video_dev); + } + + return err; +} + +static int vpif_async_complete(struct v4l2_async_notifier *notifier) +{ + return vpif_probe_complete(); +} + +static const struct v4l2_async_notifier_operations vpif_async_ops = { + .bound = vpif_async_bound, + .complete = vpif_async_complete, +}; + +static struct vpif_capture_config * +vpif_capture_get_pdata(struct platform_device *pdev) +{ + struct device_node *endpoint = NULL; + struct device_node *rem = NULL; + struct vpif_capture_config *pdata; + struct vpif_subdev_info *sdinfo; + struct vpif_capture_chan_config *chan; + unsigned int i; + + v4l2_async_nf_init(&vpif_obj.notifier); + + /* + * DT boot: OF node from parent device contains + * video ports & endpoints data. + */ + if (pdev->dev.parent && pdev->dev.parent->of_node) + pdev->dev.of_node = pdev->dev.parent->of_node; + if (!IS_ENABLED(CONFIG_OF) || !pdev->dev.of_node) + return pdev->dev.platform_data; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return NULL; + pdata->subdev_info = + devm_kcalloc(&pdev->dev, + VPIF_CAPTURE_NUM_CHANNELS, + sizeof(*pdata->subdev_info), + GFP_KERNEL); + + if (!pdata->subdev_info) + return NULL; + + for (i = 0; i < VPIF_CAPTURE_NUM_CHANNELS; i++) { + struct v4l2_fwnode_endpoint bus_cfg = { .bus_type = 0 }; + unsigned int flags; + int err; + + endpoint = of_graph_get_next_endpoint(pdev->dev.of_node, + endpoint); + if (!endpoint) + break; + + rem = of_graph_get_remote_port_parent(endpoint); + if (!rem) { + dev_dbg(&pdev->dev, "Remote device at %pOF not found\n", + endpoint); + goto done; + } + + sdinfo = &pdata->subdev_info[i]; + chan = &pdata->chan_config[i]; + chan->inputs = devm_kcalloc(&pdev->dev, + VPIF_CAPTURE_NUM_CHANNELS, + sizeof(*chan->inputs), + GFP_KERNEL); + if (!chan->inputs) + goto err_cleanup; + + chan->input_count++; + chan->inputs[i].input.type = V4L2_INPUT_TYPE_CAMERA; + chan->inputs[i].input.std = V4L2_STD_ALL; + chan->inputs[i].input.capabilities = V4L2_IN_CAP_STD; + + err = v4l2_fwnode_endpoint_parse(of_fwnode_handle(endpoint), + &bus_cfg); + if (err) { + dev_err(&pdev->dev, "Could not parse the endpoint\n"); + of_node_put(rem); + goto done; + } + + dev_dbg(&pdev->dev, "Endpoint %pOF, bus_width = %d\n", + endpoint, bus_cfg.bus.parallel.bus_width); + + flags = bus_cfg.bus.parallel.flags; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH) + chan->vpif_if.hd_pol = 1; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH) + chan->vpif_if.vd_pol = 1; + + dev_dbg(&pdev->dev, "Remote device %pOF found\n", rem); + sdinfo->name = rem->full_name; + + pdata->asd[i] = v4l2_async_nf_add_fwnode(&vpif_obj.notifier, + of_fwnode_handle(rem), + struct + v4l2_async_subdev); + if (IS_ERR(pdata->asd[i])) + goto err_cleanup; + + of_node_put(rem); + } + +done: + of_node_put(endpoint); + pdata->asd_sizes[0] = i; + pdata->subdev_count = i; + pdata->card_name = "DA850/OMAP-L138 Video Capture"; + + return pdata; + +err_cleanup: + of_node_put(rem); + of_node_put(endpoint); + v4l2_async_nf_cleanup(&vpif_obj.notifier); + + return NULL; +} + +/** + * vpif_probe : This function probes the vpif capture driver + * @pdev: platform device pointer + * + * This creates device entries by register itself to the V4L2 driver and + * initializes fields of each channel objects + */ +static __init int vpif_probe(struct platform_device *pdev) +{ + struct vpif_subdev_info *subdevdata; + struct i2c_adapter *i2c_adap; + int subdev_count; + int res_idx = 0; + int i, err; + + pdev->dev.platform_data = vpif_capture_get_pdata(pdev); + if (!pdev->dev.platform_data) { + dev_warn(&pdev->dev, "Missing platform data. Giving up.\n"); + return -EINVAL; + } + + vpif_dev = &pdev->dev; + + err = initialize_vpif(); + if (err) { + v4l2_err(vpif_dev->driver, "Error initializing vpif\n"); + goto cleanup; + } + + err = v4l2_device_register(vpif_dev, &vpif_obj.v4l2_dev); + if (err) { + v4l2_err(vpif_dev->driver, "Error registering v4l2 device\n"); + goto vpif_free; + } + + do { + int irq; + + err = platform_get_irq_optional(pdev, res_idx); + if (err < 0 && err != -ENXIO) + goto vpif_unregister; + if (err > 0) + irq = err; + else + break; + + err = devm_request_irq(&pdev->dev, irq, vpif_channel_isr, + IRQF_SHARED, VPIF_DRIVER_NAME, + (void *)(&vpif_obj.dev[res_idx]->channel_id)); + if (err) + goto vpif_unregister; + } while (++res_idx); + + vpif_obj.config = pdev->dev.platform_data; + + subdev_count = vpif_obj.config->subdev_count; + vpif_obj.sd = kcalloc(subdev_count, sizeof(*vpif_obj.sd), GFP_KERNEL); + if (!vpif_obj.sd) { + err = -ENOMEM; + goto vpif_unregister; + } + + if (!vpif_obj.config->asd_sizes[0]) { + int i2c_id = vpif_obj.config->i2c_adapter_id; + + i2c_adap = i2c_get_adapter(i2c_id); + WARN_ON(!i2c_adap); + for (i = 0; i < subdev_count; i++) { + subdevdata = &vpif_obj.config->subdev_info[i]; + vpif_obj.sd[i] = + v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, + i2c_adap, + &subdevdata-> + board_info, + NULL); + + if (!vpif_obj.sd[i]) { + vpif_err("Error registering v4l2 subdevice\n"); + err = -ENODEV; + goto probe_subdev_out; + } + v4l2_info(&vpif_obj.v4l2_dev, + "registered sub device %s\n", + subdevdata->name); + } + err = vpif_probe_complete(); + if (err) + goto probe_subdev_out; + } else { + vpif_obj.notifier.ops = &vpif_async_ops; + err = v4l2_async_nf_register(&vpif_obj.v4l2_dev, + &vpif_obj.notifier); + if (err) { + vpif_err("Error registering async notifier\n"); + err = -EINVAL; + goto probe_subdev_out; + } + } + + return 0; + +probe_subdev_out: + /* free sub devices memory */ + kfree(vpif_obj.sd); +vpif_unregister: + v4l2_device_unregister(&vpif_obj.v4l2_dev); +vpif_free: + free_vpif_objs(); +cleanup: + v4l2_async_nf_cleanup(&vpif_obj.notifier); + + return err; +} + +/** + * vpif_remove() - driver remove handler + * @device: ptr to platform device structure + * + * The vidoe device is unregistered + */ +static int vpif_remove(struct platform_device *device) +{ + struct channel_obj *ch; + int i; + + v4l2_async_nf_unregister(&vpif_obj.notifier); + v4l2_async_nf_cleanup(&vpif_obj.notifier); + v4l2_device_unregister(&vpif_obj.v4l2_dev); + + kfree(vpif_obj.sd); + /* un-register device */ + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + /* Unregister video device */ + video_unregister_device(&ch->video_dev); + kfree(vpif_obj.dev[i]); + } + return 0; +} + +#ifdef CONFIG_PM_SLEEP +/** + * vpif_suspend: vpif device suspend + * @dev: pointer to &struct device + */ +static int vpif_suspend(struct device *dev) +{ + + struct common_obj *common; + struct channel_obj *ch; + int i; + + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + common = &ch->common[VPIF_VIDEO_INDEX]; + + if (!vb2_start_streaming_called(&common->buffer_queue)) + continue; + + mutex_lock(&common->lock); + /* Disable channel */ + if (ch->channel_id == VPIF_CHANNEL0_VIDEO) { + enable_channel0(0); + channel0_intr_enable(0); + } + if (ch->channel_id == VPIF_CHANNEL1_VIDEO || + ycmux_mode == 2) { + enable_channel1(0); + channel1_intr_enable(0); + } + mutex_unlock(&common->lock); + } + + return 0; +} + +/* + * vpif_resume: vpif device suspend + */ +static int vpif_resume(struct device *dev) +{ + struct common_obj *common; + struct channel_obj *ch; + int i; + + for (i = 0; i < VPIF_CAPTURE_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + common = &ch->common[VPIF_VIDEO_INDEX]; + + if (!vb2_start_streaming_called(&common->buffer_queue)) + continue; + + mutex_lock(&common->lock); + /* Enable channel */ + if (ch->channel_id == VPIF_CHANNEL0_VIDEO) { + enable_channel0(1); + channel0_intr_enable(1); + } + if (ch->channel_id == VPIF_CHANNEL1_VIDEO || + ycmux_mode == 2) { + enable_channel1(1); + channel1_intr_enable(1); + } + mutex_unlock(&common->lock); + } + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(vpif_pm_ops, vpif_suspend, vpif_resume); + +static __refdata struct platform_driver vpif_driver = { + .driver = { + .name = VPIF_DRIVER_NAME, + .pm = &vpif_pm_ops, + }, + .probe = vpif_probe, + .remove = vpif_remove, +}; + +module_platform_driver(vpif_driver); diff --git a/drivers/media/platform/ti/davinci/vpif_capture.h b/drivers/media/platform/ti/davinci/vpif_capture.h new file mode 100644 index 000000000..619105650 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpif_capture.h @@ -0,0 +1,107 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2009 Texas Instruments Inc + */ + +#ifndef VPIF_CAPTURE_H +#define VPIF_CAPTURE_H + +/* Header files */ +#include +#include + +#include "vpif.h" + +/* Macros */ +#define VPIF_CAPTURE_VERSION "0.0.2" + +#define VPIF_VALID_FIELD(field) (((V4L2_FIELD_ANY == field) || \ + (V4L2_FIELD_NONE == field)) || \ + (((V4L2_FIELD_INTERLACED == field) || \ + (V4L2_FIELD_SEQ_TB == field)) || \ + (V4L2_FIELD_SEQ_BT == field))) + +#define VPIF_CAPTURE_MAX_DEVICES 2 +#define VPIF_VIDEO_INDEX 0 +#define VPIF_NUMBER_OF_OBJECTS 1 + +/* Enumerated data type to give id to each device per channel */ +enum vpif_channel_id { + VPIF_CHANNEL0_VIDEO = 0, + VPIF_CHANNEL1_VIDEO, +}; + +struct video_obj { + enum v4l2_field buf_field; + /* Currently selected or default standard */ + v4l2_std_id stdid; + struct v4l2_dv_timings dv_timings; +}; + +struct vpif_cap_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct common_obj { + /* Pointer pointing to current v4l2_buffer */ + struct vpif_cap_buffer *cur_frm; + /* Pointer pointing to current v4l2_buffer */ + struct vpif_cap_buffer *next_frm; + /* Used to store pixel format */ + struct v4l2_format fmt; + /* Buffer queue used in vb2 */ + struct vb2_queue buffer_queue; + /* Queue of filled frames */ + struct list_head dma_queue; + /* Protects the dma_queue field */ + spinlock_t irqlock; + /* lock used to access this structure */ + struct mutex lock; + /* Function pointer to set the addresses */ + void (*set_addr) (unsigned long, unsigned long, unsigned long, + unsigned long); + /* offset where Y top starts from the starting of the buffer */ + u32 ytop_off; + /* offset where Y bottom starts from the starting of the buffer */ + u32 ybtm_off; + /* offset where C top starts from the starting of the buffer */ + u32 ctop_off; + /* offset where C bottom starts from the starting of the buffer */ + u32 cbtm_off; + /* Indicates width of the image data */ + u32 width; + /* Indicates height of the image data */ + u32 height; +}; + +struct channel_obj { + /* Identifies video device for this channel */ + struct video_device video_dev; + /* Indicates id of the field which is being displayed */ + u32 field_id; + /* flag to indicate whether decoder is initialized */ + u8 initialized; + /* Identifies channel */ + enum vpif_channel_id channel_id; + /* Current input */ + u32 input_idx; + /* subdev corresponding to the current input, may be NULL */ + struct v4l2_subdev *sd; + /* vpif configuration params */ + struct vpif_params vpifparams; + /* common object array */ + struct common_obj common[VPIF_NUMBER_OF_OBJECTS]; + /* video object */ + struct video_obj video; +}; + +struct vpif_device { + struct v4l2_device v4l2_dev; + struct channel_obj *dev[VPIF_CAPTURE_NUM_CHANNELS]; + struct v4l2_subdev **sd; + struct v4l2_async_notifier notifier; + struct vpif_capture_config *config; +}; + +#endif /* VPIF_CAPTURE_H */ diff --git a/drivers/media/platform/ti/davinci/vpif_display.c b/drivers/media/platform/ti/davinci/vpif_display.c new file mode 100644 index 000000000..b2df81603 --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpif_display.c @@ -0,0 +1,1405 @@ +/* + * vpif-display - VPIF display driver + * Display driver for TI DaVinci VPIF + * + * Copyright (C) 2009 Texas Instruments Incorporated - https://www.ti.com/ + * Copyright (C) 2014 Lad, Prabhakar + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation version 2. + * + * This program is distributed .as is. WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include + +#include + +#include "vpif.h" +#include "vpif_display.h" + +MODULE_DESCRIPTION("TI DaVinci VPIF Display driver"); +MODULE_LICENSE("GPL"); +MODULE_VERSION(VPIF_DISPLAY_VERSION); + +#define VPIF_V4L2_STD (V4L2_STD_525_60 | V4L2_STD_625_50) + +#define vpif_err(fmt, arg...) v4l2_err(&vpif_obj.v4l2_dev, fmt, ## arg) +#define vpif_dbg(level, debug, fmt, arg...) \ + v4l2_dbg(level, debug, &vpif_obj.v4l2_dev, fmt, ## arg) + +static int debug = 1; + +module_param(debug, int, 0644); + +MODULE_PARM_DESC(debug, "Debug level 0-1"); + +#define VPIF_DRIVER_NAME "vpif_display" +MODULE_ALIAS("platform:" VPIF_DRIVER_NAME); + +/* Is set to 1 in case of SDTV formats, 2 in case of HDTV formats. */ +static int ycmux_mode; + +static u8 channel_first_int[VPIF_NUMOBJECTS][2] = { {1, 1} }; + +static struct vpif_device vpif_obj = { {NULL} }; +static struct device *vpif_dev; +static void vpif_calculate_offsets(struct channel_obj *ch); +static void vpif_config_addr(struct channel_obj *ch, int muxmode); + +static inline +struct vpif_disp_buffer *to_vpif_buffer(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct vpif_disp_buffer, vb); +} + +/** + * vpif_buffer_prepare : callback function for buffer prepare + * @vb: ptr to vb2_buffer + * + * This is the callback function for buffer prepare when vb2_qbuf() + * function is called. The buffer is prepared and user space virtual address + * or user address is converted into physical address + */ +static int vpif_buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct channel_obj *ch = vb2_get_drv_priv(vb->vb2_queue); + struct common_obj *common; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + vb2_set_plane_payload(vb, 0, common->fmt.fmt.pix.sizeimage); + if (vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0)) + return -EINVAL; + + vbuf->field = common->fmt.fmt.pix.field; + + if (vb->vb2_queue->type != V4L2_BUF_TYPE_SLICED_VBI_OUTPUT) { + unsigned long addr = vb2_dma_contig_plane_dma_addr(vb, 0); + + if (!ISALIGNED(addr + common->ytop_off) || + !ISALIGNED(addr + common->ybtm_off) || + !ISALIGNED(addr + common->ctop_off) || + !ISALIGNED(addr + common->cbtm_off)) { + vpif_err("buffer offset not aligned to 8 bytes\n"); + return -EINVAL; + } + } + + return 0; +} + +/** + * vpif_buffer_queue_setup : Callback function for buffer setup. + * @vq: vb2_queue ptr + * @nbuffers: ptr to number of buffers requested by application + * @nplanes: contains number of distinct video planes needed to hold a frame + * @sizes: contains the size (in bytes) of each plane. + * @alloc_devs: ptr to allocation context + * + * This callback function is called when reqbuf() is called to adjust + * the buffer count and buffer size + */ +static int vpif_buffer_queue_setup(struct vb2_queue *vq, + unsigned int *nbuffers, unsigned int *nplanes, + unsigned int sizes[], struct device *alloc_devs[]) +{ + struct channel_obj *ch = vb2_get_drv_priv(vq); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + unsigned size = common->fmt.fmt.pix.sizeimage; + + if (*nplanes) { + if (sizes[0] < size) + return -EINVAL; + size = sizes[0]; + } + + if (vq->num_buffers + *nbuffers < 3) + *nbuffers = 3 - vq->num_buffers; + + *nplanes = 1; + sizes[0] = size; + + /* Calculate the offset for Y and C data in the buffer */ + vpif_calculate_offsets(ch); + + return 0; +} + +/** + * vpif_buffer_queue : Callback function to add buffer to DMA queue + * @vb: ptr to vb2_buffer + * + * This callback function queues the buffer to DMA engine + */ +static void vpif_buffer_queue(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vpif_disp_buffer *buf = to_vpif_buffer(vbuf); + struct channel_obj *ch = vb2_get_drv_priv(vb->vb2_queue); + struct common_obj *common; + unsigned long flags; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + /* add the buffer to the DMA queue */ + spin_lock_irqsave(&common->irqlock, flags); + list_add_tail(&buf->list, &common->dma_queue); + spin_unlock_irqrestore(&common->irqlock, flags); +} + +/** + * vpif_start_streaming : Starts the DMA engine for streaming + * @vq: ptr to vb2_buffer + * @count: number of buffers + */ +static int vpif_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct vpif_display_config *vpif_config_data = + vpif_dev->platform_data; + struct channel_obj *ch = vb2_get_drv_priv(vq); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_params *vpif = &ch->vpifparams; + struct vpif_disp_buffer *buf, *tmp; + unsigned long addr, flags; + int ret; + + spin_lock_irqsave(&common->irqlock, flags); + + /* Initialize field_id */ + ch->field_id = 0; + + /* clock settings */ + if (vpif_config_data->set_clock) { + ret = vpif_config_data->set_clock(ch->vpifparams.std_info. + ycmux_mode, ch->vpifparams.std_info.hd_sd); + if (ret < 0) { + vpif_err("can't set clock\n"); + goto err; + } + } + + /* set the parameters and addresses */ + ret = vpif_set_video_params(vpif, ch->channel_id + 2); + if (ret < 0) + goto err; + + ycmux_mode = ret; + vpif_config_addr(ch, ret); + /* Get the next frame from the buffer queue */ + common->next_frm = common->cur_frm = + list_entry(common->dma_queue.next, + struct vpif_disp_buffer, list); + + list_del(&common->cur_frm->list); + spin_unlock_irqrestore(&common->irqlock, flags); + + addr = vb2_dma_contig_plane_dma_addr(&common->cur_frm->vb.vb2_buf, 0); + common->set_addr((addr + common->ytop_off), + (addr + common->ybtm_off), + (addr + common->ctop_off), + (addr + common->cbtm_off)); + + /* + * Set interrupt for both the fields in VPIF + * Register enable channel in VPIF register + */ + channel_first_int[VPIF_VIDEO_INDEX][ch->channel_id] = 1; + if (VPIF_CHANNEL2_VIDEO == ch->channel_id) { + channel2_intr_assert(); + channel2_intr_enable(1); + enable_channel2(1); + if (vpif_config_data->chan_config[VPIF_CHANNEL2_VIDEO].clip_en) + channel2_clipping_enable(1); + } + + if (VPIF_CHANNEL3_VIDEO == ch->channel_id || ycmux_mode == 2) { + channel3_intr_assert(); + channel3_intr_enable(1); + enable_channel3(1); + if (vpif_config_data->chan_config[VPIF_CHANNEL3_VIDEO].clip_en) + channel3_clipping_enable(1); + } + + return 0; + +err: + list_for_each_entry_safe(buf, tmp, &common->dma_queue, list) { + list_del(&buf->list); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&common->irqlock, flags); + + return ret; +} + +/** + * vpif_stop_streaming : Stop the DMA engine + * @vq: ptr to vb2_queue + * + * This callback stops the DMA engine and any remaining buffers + * in the DMA queue are released. + */ +static void vpif_stop_streaming(struct vb2_queue *vq) +{ + struct channel_obj *ch = vb2_get_drv_priv(vq); + struct common_obj *common; + unsigned long flags; + + common = &ch->common[VPIF_VIDEO_INDEX]; + + /* Disable channel */ + if (VPIF_CHANNEL2_VIDEO == ch->channel_id) { + enable_channel2(0); + channel2_intr_enable(0); + } + if (VPIF_CHANNEL3_VIDEO == ch->channel_id || ycmux_mode == 2) { + enable_channel3(0); + channel3_intr_enable(0); + } + + /* release all active buffers */ + spin_lock_irqsave(&common->irqlock, flags); + if (common->cur_frm == common->next_frm) { + vb2_buffer_done(&common->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } else { + if (common->cur_frm) + vb2_buffer_done(&common->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + if (common->next_frm) + vb2_buffer_done(&common->next_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } + + while (!list_empty(&common->dma_queue)) { + common->next_frm = list_entry(common->dma_queue.next, + struct vpif_disp_buffer, list); + list_del(&common->next_frm->list); + vb2_buffer_done(&common->next_frm->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&common->irqlock, flags); +} + +static const struct vb2_ops video_qops = { + .queue_setup = vpif_buffer_queue_setup, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .buf_prepare = vpif_buffer_prepare, + .start_streaming = vpif_start_streaming, + .stop_streaming = vpif_stop_streaming, + .buf_queue = vpif_buffer_queue, +}; + +static void process_progressive_mode(struct common_obj *common) +{ + unsigned long addr; + + spin_lock(&common->irqlock); + /* Get the next buffer from buffer queue */ + common->next_frm = list_entry(common->dma_queue.next, + struct vpif_disp_buffer, list); + /* Remove that buffer from the buffer queue */ + list_del(&common->next_frm->list); + spin_unlock(&common->irqlock); + + /* Set top and bottom field addrs in VPIF registers */ + addr = vb2_dma_contig_plane_dma_addr(&common->next_frm->vb.vb2_buf, 0); + common->set_addr(addr + common->ytop_off, + addr + common->ybtm_off, + addr + common->ctop_off, + addr + common->cbtm_off); +} + +static void process_interlaced_mode(int fid, struct common_obj *common) +{ + /* device field id and local field id are in sync */ + /* If this is even field */ + if (0 == fid) { + if (common->cur_frm == common->next_frm) + return; + + /* one frame is displayed If next frame is + * available, release cur_frm and move on */ + /* Copy frame display time */ + common->cur_frm->vb.vb2_buf.timestamp = ktime_get_ns(); + /* Change status of the cur_frm */ + vb2_buffer_done(&common->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_DONE); + /* Make cur_frm pointing to next_frm */ + common->cur_frm = common->next_frm; + + } else if (1 == fid) { /* odd field */ + spin_lock(&common->irqlock); + if (list_empty(&common->dma_queue) + || (common->cur_frm != common->next_frm)) { + spin_unlock(&common->irqlock); + return; + } + spin_unlock(&common->irqlock); + /* one field is displayed configure the next + * frame if it is available else hold on current + * frame */ + /* Get next from the buffer queue */ + process_progressive_mode(common); + } +} + +/* + * vpif_channel_isr: It changes status of the displayed buffer, takes next + * buffer from the queue and sets its address in VPIF registers + */ +static irqreturn_t vpif_channel_isr(int irq, void *dev_id) +{ + struct vpif_device *dev = &vpif_obj; + struct channel_obj *ch; + struct common_obj *common; + int fid = -1, i; + int channel_id; + + channel_id = *(int *)(dev_id); + if (!vpif_intr_status(channel_id + 2)) + return IRQ_NONE; + + ch = dev->dev[channel_id]; + for (i = 0; i < VPIF_NUMOBJECTS; i++) { + common = &ch->common[i]; + /* If streaming is started in this channel */ + + if (1 == ch->vpifparams.std_info.frm_fmt) { + spin_lock(&common->irqlock); + if (list_empty(&common->dma_queue)) { + spin_unlock(&common->irqlock); + continue; + } + spin_unlock(&common->irqlock); + + /* Progressive mode */ + if (!channel_first_int[i][channel_id]) { + /* Mark status of the cur_frm to + * done and unlock semaphore on it */ + common->cur_frm->vb.vb2_buf.timestamp = + ktime_get_ns(); + vb2_buffer_done(&common->cur_frm->vb.vb2_buf, + VB2_BUF_STATE_DONE); + /* Make cur_frm pointing to next_frm */ + common->cur_frm = common->next_frm; + } + + channel_first_int[i][channel_id] = 0; + process_progressive_mode(common); + } else { + /* Interlaced mode */ + /* If it is first interrupt, ignore it */ + + if (channel_first_int[i][channel_id]) { + channel_first_int[i][channel_id] = 0; + continue; + } + + if (0 == i) { + ch->field_id ^= 1; + /* Get field id from VPIF registers */ + fid = vpif_channel_getfid(ch->channel_id + 2); + /* If fid does not match with stored field id */ + if (fid != ch->field_id) { + /* Make them in sync */ + if (0 == fid) + ch->field_id = fid; + + return IRQ_HANDLED; + } + } + process_interlaced_mode(fid, common); + } + } + + return IRQ_HANDLED; +} + +static int vpif_update_std_info(struct channel_obj *ch) +{ + struct video_obj *vid_ch = &ch->video; + struct vpif_params *vpifparams = &ch->vpifparams; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + const struct vpif_channel_config_params *config; + + int i; + + for (i = 0; i < vpif_ch_params_count; i++) { + config = &vpif_ch_params[i]; + if (config->hd_sd == 0) { + vpif_dbg(2, debug, "SD format\n"); + if (config->stdid & vid_ch->stdid) { + memcpy(std_info, config, sizeof(*config)); + break; + } + } + } + + if (i == vpif_ch_params_count) { + vpif_dbg(1, debug, "Format not found\n"); + return -EINVAL; + } + + return 0; +} + +static int vpif_update_resolution(struct channel_obj *ch) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct video_obj *vid_ch = &ch->video; + struct vpif_params *vpifparams = &ch->vpifparams; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + + if (!vid_ch->stdid && !vid_ch->dv_timings.bt.height) + return -EINVAL; + + if (vid_ch->stdid) { + if (vpif_update_std_info(ch)) + return -EINVAL; + } + + common->fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV422P; + common->fmt.fmt.pix.width = std_info->width; + common->fmt.fmt.pix.height = std_info->height; + vpif_dbg(1, debug, "Pixel details: Width = %d,Height = %d\n", + common->fmt.fmt.pix.width, common->fmt.fmt.pix.height); + + /* Set height and width paramateres */ + common->height = std_info->height; + common->width = std_info->width; + common->fmt.fmt.pix.sizeimage = common->height * common->width * 2; + + if (vid_ch->stdid) + common->fmt.fmt.pix.colorspace = V4L2_COLORSPACE_SMPTE170M; + else + common->fmt.fmt.pix.colorspace = V4L2_COLORSPACE_REC709; + + if (ch->vpifparams.std_info.frm_fmt) + common->fmt.fmt.pix.field = V4L2_FIELD_NONE; + else + common->fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; + + return 0; +} + +/* + * vpif_calculate_offsets: This function calculates buffers offset for Y and C + * in the top and bottom field + */ +static void vpif_calculate_offsets(struct channel_obj *ch) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_params *vpifparams = &ch->vpifparams; + enum v4l2_field field = common->fmt.fmt.pix.field; + struct video_obj *vid_ch = &ch->video; + unsigned int hpitch, sizeimage; + + if (V4L2_FIELD_ANY == common->fmt.fmt.pix.field) { + if (ch->vpifparams.std_info.frm_fmt) + vid_ch->buf_field = V4L2_FIELD_NONE; + else + vid_ch->buf_field = V4L2_FIELD_INTERLACED; + } else { + vid_ch->buf_field = common->fmt.fmt.pix.field; + } + + sizeimage = common->fmt.fmt.pix.sizeimage; + + hpitch = common->fmt.fmt.pix.bytesperline; + if ((V4L2_FIELD_NONE == vid_ch->buf_field) || + (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { + common->ytop_off = 0; + common->ybtm_off = hpitch; + common->ctop_off = sizeimage / 2; + common->cbtm_off = sizeimage / 2 + hpitch; + } else if (V4L2_FIELD_SEQ_TB == vid_ch->buf_field) { + common->ytop_off = 0; + common->ybtm_off = sizeimage / 4; + common->ctop_off = sizeimage / 2; + common->cbtm_off = common->ctop_off + sizeimage / 4; + } else if (V4L2_FIELD_SEQ_BT == vid_ch->buf_field) { + common->ybtm_off = 0; + common->ytop_off = sizeimage / 4; + common->cbtm_off = sizeimage / 2; + common->ctop_off = common->cbtm_off + sizeimage / 4; + } + + if ((V4L2_FIELD_NONE == vid_ch->buf_field) || + (V4L2_FIELD_INTERLACED == vid_ch->buf_field)) { + vpifparams->video_params.storage_mode = 1; + } else { + vpifparams->video_params.storage_mode = 0; + } + + if (ch->vpifparams.std_info.frm_fmt == 1) { + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline; + } else { + if ((field == V4L2_FIELD_ANY) || + (field == V4L2_FIELD_INTERLACED)) + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline * 2; + else + vpifparams->video_params.hpitch = + common->fmt.fmt.pix.bytesperline; + } + + ch->vpifparams.video_params.stdid = ch->vpifparams.std_info.stdid; +} + +static void vpif_config_addr(struct channel_obj *ch, int muxmode) +{ + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + if (VPIF_CHANNEL3_VIDEO == ch->channel_id) { + common->set_addr = ch3_set_video_buf_addr; + } else { + if (2 == muxmode) + common->set_addr = ch2_set_video_buf_addr_yc_nmux; + else + common->set_addr = ch2_set_video_buf_addr; + } +} + +/* functions implementing ioctls */ +/** + * vpif_querycap() - QUERYCAP handler + * @file: file ptr + * @priv: file handle + * @cap: ptr to v4l2_capability structure + */ +static int vpif_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct vpif_display_config *config = vpif_dev->platform_data; + + strscpy(cap->driver, VPIF_DRIVER_NAME, sizeof(cap->driver)); + strscpy(cap->card, config->card_name, sizeof(cap->card)); + + return 0; +} + +static int vpif_enum_fmt_vid_out(struct file *file, void *priv, + struct v4l2_fmtdesc *fmt) +{ + if (fmt->index != 0) + return -EINVAL; + + /* Fill in the information about format */ + fmt->pixelformat = V4L2_PIX_FMT_YUV422P; + return 0; +} + +static int vpif_g_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + /* Check the validity of the buffer type */ + if (common->fmt.type != fmt->type) + return -EINVAL; + + if (vpif_update_resolution(ch)) + return -EINVAL; + *fmt = common->fmt; + return 0; +} + +static int vpif_try_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + + /* + * to suppress v4l-compliance warnings silently correct + * the pixelformat + */ + if (pixfmt->pixelformat != V4L2_PIX_FMT_YUV422P) + pixfmt->pixelformat = common->fmt.fmt.pix.pixelformat; + + if (vpif_update_resolution(ch)) + return -EINVAL; + + pixfmt->colorspace = common->fmt.fmt.pix.colorspace; + pixfmt->field = common->fmt.fmt.pix.field; + pixfmt->bytesperline = common->fmt.fmt.pix.width; + pixfmt->width = common->fmt.fmt.pix.width; + pixfmt->height = common->fmt.fmt.pix.height; + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height * 2; + + return 0; +} + +static int vpif_s_fmt_vid_out(struct file *file, void *priv, + struct v4l2_format *fmt) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; + int ret; + + if (vb2_is_busy(&common->buffer_queue)) + return -EBUSY; + + ret = vpif_try_fmt_vid_out(file, priv, fmt); + if (ret) + return ret; + + /* store the pix format in the channel object */ + common->fmt.fmt.pix = *pixfmt; + + /* store the format in the channel object */ + common->fmt = *fmt; + return 0; +} + +static int vpif_s_std(struct file *file, void *priv, v4l2_std_id std_id) +{ + struct vpif_display_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_display_chan_config *chan_cfg; + struct v4l2_output output; + int ret; + + if (!config->chan_config[ch->channel_id].outputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + output = chan_cfg->outputs[ch->output_idx].output; + if (output.capabilities != V4L2_OUT_CAP_STD) + return -ENODATA; + + if (vb2_is_busy(&common->buffer_queue)) + return -EBUSY; + + + if (!(std_id & VPIF_V4L2_STD)) + return -EINVAL; + + /* Call encoder subdevice function to set the standard */ + ch->video.stdid = std_id; + memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + /* Get the information about the standard */ + if (vpif_update_resolution(ch)) + return -EINVAL; + + common->fmt.fmt.pix.bytesperline = common->fmt.fmt.pix.width; + + ret = v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 1, video, + s_std_output, std_id); + if (ret < 0) { + vpif_err("Failed to set output standard\n"); + return ret; + } + + ret = v4l2_device_call_until_err(&vpif_obj.v4l2_dev, 1, video, + s_std, std_id); + if (ret < 0) + vpif_err("Failed to set standard for sub devices\n"); + return ret; +} + +static int vpif_g_std(struct file *file, void *priv, v4l2_std_id *std) +{ + struct vpif_display_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_display_chan_config *chan_cfg; + struct v4l2_output output; + + if (!config->chan_config[ch->channel_id].outputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + output = chan_cfg->outputs[ch->output_idx].output; + if (output.capabilities != V4L2_OUT_CAP_STD) + return -ENODATA; + + *std = ch->video.stdid; + return 0; +} + +static int vpif_enum_output(struct file *file, void *fh, + struct v4l2_output *output) +{ + + struct vpif_display_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_display_chan_config *chan_cfg; + + chan_cfg = &config->chan_config[ch->channel_id]; + if (output->index >= chan_cfg->output_count) { + vpif_dbg(1, debug, "Invalid output index\n"); + return -EINVAL; + } + + *output = chan_cfg->outputs[output->index].output; + return 0; +} + +/** + * vpif_output_to_subdev() - Maps output to sub device + * @vpif_cfg: global config ptr + * @chan_cfg: channel config ptr + * @index: Given output index from application + * + * lookup the sub device information for a given output index. + * we report all the output to application. output table also + * has sub device name for the each output + */ +static int +vpif_output_to_subdev(struct vpif_display_config *vpif_cfg, + struct vpif_display_chan_config *chan_cfg, int index) +{ + struct vpif_subdev_info *subdev_info; + const char *subdev_name; + int i; + + vpif_dbg(2, debug, "vpif_output_to_subdev\n"); + + if (!chan_cfg->outputs) + return -1; + + subdev_name = chan_cfg->outputs[index].subdev_name; + if (!subdev_name) + return -1; + + /* loop through the sub device list to get the sub device info */ + for (i = 0; i < vpif_cfg->subdev_count; i++) { + subdev_info = &vpif_cfg->subdevinfo[i]; + if (!strcmp(subdev_info->name, subdev_name)) + return i; + } + return -1; +} + +/** + * vpif_set_output() - Select an output + * @vpif_cfg: global config ptr + * @ch: channel + * @index: Given output index from application + * + * Select the given output. + */ +static int vpif_set_output(struct vpif_display_config *vpif_cfg, + struct channel_obj *ch, int index) +{ + struct vpif_display_chan_config *chan_cfg = + &vpif_cfg->chan_config[ch->channel_id]; + struct v4l2_subdev *sd = NULL; + u32 input = 0, output = 0; + int sd_index; + int ret; + + sd_index = vpif_output_to_subdev(vpif_cfg, chan_cfg, index); + if (sd_index >= 0) + sd = vpif_obj.sd[sd_index]; + + if (sd) { + input = chan_cfg->outputs[index].input_route; + output = chan_cfg->outputs[index].output_route; + ret = v4l2_subdev_call(sd, video, s_routing, input, output, 0); + if (ret < 0 && ret != -ENOIOCTLCMD) { + vpif_err("Failed to set output\n"); + return ret; + } + + } + ch->output_idx = index; + ch->sd = sd; + if (chan_cfg->outputs) + /* update tvnorms from the sub device output info */ + ch->video_dev.tvnorms = chan_cfg->outputs[index].output.std; + return 0; +} + +static int vpif_s_output(struct file *file, void *priv, unsigned int i) +{ + struct vpif_display_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_display_chan_config *chan_cfg; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + + if (vb2_is_busy(&common->buffer_queue)) + return -EBUSY; + + chan_cfg = &config->chan_config[ch->channel_id]; + + if (i >= chan_cfg->output_count) + return -EINVAL; + + return vpif_set_output(config, ch, i); +} + +static int vpif_g_output(struct file *file, void *priv, unsigned int *i) +{ + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + + *i = ch->output_idx; + + return 0; +} + +/** + * vpif_enum_dv_timings() - ENUM_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: input timings + */ +static int +vpif_enum_dv_timings(struct file *file, void *priv, + struct v4l2_enum_dv_timings *timings) +{ + struct vpif_display_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_display_chan_config *chan_cfg; + struct v4l2_output output; + int ret; + + if (!config->chan_config[ch->channel_id].outputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + output = chan_cfg->outputs[ch->output_idx].output; + if (output.capabilities != V4L2_OUT_CAP_DV_TIMINGS) + return -ENODATA; + + timings->pad = 0; + + ret = v4l2_subdev_call(ch->sd, pad, enum_dv_timings, timings); + if (ret == -ENOIOCTLCMD || ret == -ENODEV) + return -EINVAL; + return ret; +} + +/** + * vpif_s_dv_timings() - S_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_s_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_display_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_params *vpifparams = &ch->vpifparams; + struct common_obj *common = &ch->common[VPIF_VIDEO_INDEX]; + struct vpif_channel_config_params *std_info = &vpifparams->std_info; + struct video_obj *vid_ch = &ch->video; + struct v4l2_bt_timings *bt = &vid_ch->dv_timings.bt; + struct vpif_display_chan_config *chan_cfg; + struct v4l2_output output; + int ret; + + if (!config->chan_config[ch->channel_id].outputs) + return -ENODATA; + + chan_cfg = &config->chan_config[ch->channel_id]; + output = chan_cfg->outputs[ch->output_idx].output; + if (output.capabilities != V4L2_OUT_CAP_DV_TIMINGS) + return -ENODATA; + + if (vb2_is_busy(&common->buffer_queue)) + return -EBUSY; + + if (timings->type != V4L2_DV_BT_656_1120) { + vpif_dbg(2, debug, "Timing type not defined\n"); + return -EINVAL; + } + + /* Configure subdevice timings, if any */ + ret = v4l2_subdev_call(ch->sd, video, s_dv_timings, timings); + if (ret == -ENOIOCTLCMD || ret == -ENODEV) + ret = 0; + if (ret < 0) { + vpif_dbg(2, debug, "Error setting custom DV timings\n"); + return ret; + } + + if (!(timings->bt.width && timings->bt.height && + (timings->bt.hbackporch || + timings->bt.hfrontporch || + timings->bt.hsync) && + timings->bt.vfrontporch && + (timings->bt.vbackporch || + timings->bt.vsync))) { + vpif_dbg(2, debug, "Timings for width, height, horizontal back porch, horizontal sync, horizontal front porch, vertical back porch, vertical sync and vertical back porch must be defined\n"); + return -EINVAL; + } + + vid_ch->dv_timings = *timings; + + /* Configure video port timings */ + + std_info->eav2sav = V4L2_DV_BT_BLANKING_WIDTH(bt) - 8; + std_info->sav2eav = bt->width; + + std_info->l1 = 1; + std_info->l3 = bt->vsync + bt->vbackporch + 1; + + std_info->vsize = V4L2_DV_BT_FRAME_HEIGHT(bt); + if (bt->interlaced) { + if (bt->il_vbackporch || bt->il_vfrontporch || bt->il_vsync) { + std_info->l5 = std_info->vsize/2 - + (bt->vfrontporch - 1); + std_info->l7 = std_info->vsize/2 + 1; + std_info->l9 = std_info->l7 + bt->il_vsync + + bt->il_vbackporch + 1; + std_info->l11 = std_info->vsize - + (bt->il_vfrontporch - 1); + } else { + vpif_dbg(2, debug, "Required timing values for interlaced BT format missing\n"); + return -EINVAL; + } + } else { + std_info->l5 = std_info->vsize - (bt->vfrontporch - 1); + } + strscpy(std_info->name, "Custom timings BT656/1120", + sizeof(std_info->name)); + std_info->width = bt->width; + std_info->height = bt->height; + std_info->frm_fmt = bt->interlaced ? 0 : 1; + std_info->ycmux_mode = 0; + std_info->capture_format = 0; + std_info->vbi_supported = 0; + std_info->hd_sd = 1; + std_info->stdid = 0; + vid_ch->stdid = 0; + + return 0; +} + +/** + * vpif_g_dv_timings() - G_DV_TIMINGS handler + * @file: file ptr + * @priv: file handle + * @timings: digital video timings + */ +static int vpif_g_dv_timings(struct file *file, void *priv, + struct v4l2_dv_timings *timings) +{ + struct vpif_display_config *config = vpif_dev->platform_data; + struct video_device *vdev = video_devdata(file); + struct channel_obj *ch = video_get_drvdata(vdev); + struct vpif_display_chan_config *chan_cfg; + struct video_obj *vid_ch = &ch->video; + struct v4l2_output output; + + if (!config->chan_config[ch->channel_id].outputs) + goto error; + + chan_cfg = &config->chan_config[ch->channel_id]; + output = chan_cfg->outputs[ch->output_idx].output; + + if (output.capabilities != V4L2_OUT_CAP_DV_TIMINGS) + goto error; + + *timings = vid_ch->dv_timings; + + return 0; +error: + return -ENODATA; +} + +/* + * vpif_log_status() - Status information + * @file: file ptr + * @priv: file handle + * + * Returns zero. + */ +static int vpif_log_status(struct file *filep, void *priv) +{ + /* status for sub devices */ + v4l2_device_call_all(&vpif_obj.v4l2_dev, 0, core, log_status); + + return 0; +} + +/* vpif display ioctl operations */ +static const struct v4l2_ioctl_ops vpif_ioctl_ops = { + .vidioc_querycap = vpif_querycap, + .vidioc_enum_fmt_vid_out = vpif_enum_fmt_vid_out, + .vidioc_g_fmt_vid_out = vpif_g_fmt_vid_out, + .vidioc_s_fmt_vid_out = vpif_s_fmt_vid_out, + .vidioc_try_fmt_vid_out = vpif_try_fmt_vid_out, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + + .vidioc_s_std = vpif_s_std, + .vidioc_g_std = vpif_g_std, + + .vidioc_enum_output = vpif_enum_output, + .vidioc_s_output = vpif_s_output, + .vidioc_g_output = vpif_g_output, + + .vidioc_enum_dv_timings = vpif_enum_dv_timings, + .vidioc_s_dv_timings = vpif_s_dv_timings, + .vidioc_g_dv_timings = vpif_g_dv_timings, + + .vidioc_log_status = vpif_log_status, +}; + +static const struct v4l2_file_operations vpif_fops = { + .owner = THIS_MODULE, + .open = v4l2_fh_open, + .release = vb2_fop_release, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll +}; + +/*Configure the channels, buffer sizei, request irq */ +static int initialize_vpif(void) +{ + int free_channel_objects_index; + int err, i, j; + + /* Allocate memory for six channel objects */ + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + vpif_obj.dev[i] = + kzalloc(sizeof(struct channel_obj), GFP_KERNEL); + /* If memory allocation fails, return error */ + if (!vpif_obj.dev[i]) { + free_channel_objects_index = i; + err = -ENOMEM; + goto vpif_init_free_channel_objects; + } + } + + return 0; + +vpif_init_free_channel_objects: + for (j = 0; j < free_channel_objects_index; j++) + kfree(vpif_obj.dev[j]); + return err; +} + +static void free_vpif_objs(void) +{ + int i; + + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) + kfree(vpif_obj.dev[i]); +} + +static int vpif_probe_complete(void) +{ + struct common_obj *common; + struct video_device *vdev; + struct channel_obj *ch; + struct vb2_queue *q; + int j, err, k; + + for (j = 0; j < VPIF_DISPLAY_MAX_DEVICES; j++) { + ch = vpif_obj.dev[j]; + /* Initialize field of the channel objects */ + for (k = 0; k < VPIF_NUMOBJECTS; k++) { + common = &ch->common[k]; + spin_lock_init(&common->irqlock); + mutex_init(&common->lock); + common->set_addr = NULL; + common->ytop_off = 0; + common->ybtm_off = 0; + common->ctop_off = 0; + common->cbtm_off = 0; + common->cur_frm = NULL; + common->next_frm = NULL; + memset(&common->fmt, 0, sizeof(common->fmt)); + } + ch->initialized = 0; + if (vpif_obj.config->subdev_count) + ch->sd = vpif_obj.sd[0]; + ch->channel_id = j; + + memset(&ch->vpifparams, 0, sizeof(ch->vpifparams)); + + ch->common[VPIF_VIDEO_INDEX].fmt.type = + V4L2_BUF_TYPE_VIDEO_OUTPUT; + + /* select output 0 */ + err = vpif_set_output(vpif_obj.config, ch, 0); + if (err) + goto probe_out; + + /* set initial format */ + ch->video.stdid = V4L2_STD_525_60; + memset(&ch->video.dv_timings, 0, sizeof(ch->video.dv_timings)); + vpif_update_resolution(ch); + + /* Initialize vb2 queue */ + q = &common->buffer_queue; + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF; + q->drv_priv = ch; + q->ops = &video_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct vpif_disp_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->min_buffers_needed = 1; + q->lock = &common->lock; + q->dev = vpif_dev; + err = vb2_queue_init(q); + if (err) { + vpif_err("vpif_display: vb2_queue_init() failed\n"); + goto probe_out; + } + + INIT_LIST_HEAD(&common->dma_queue); + + /* register video device */ + vpif_dbg(1, debug, "channel=%p,channel->video_dev=%p\n", + ch, &ch->video_dev); + + /* Initialize the video_device structure */ + vdev = &ch->video_dev; + strscpy(vdev->name, VPIF_DRIVER_NAME, sizeof(vdev->name)); + vdev->release = video_device_release_empty; + vdev->fops = &vpif_fops; + vdev->ioctl_ops = &vpif_ioctl_ops; + vdev->v4l2_dev = &vpif_obj.v4l2_dev; + vdev->vfl_dir = VFL_DIR_TX; + vdev->queue = q; + vdev->lock = &common->lock; + vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; + video_set_drvdata(&ch->video_dev, ch); + err = video_register_device(vdev, VFL_TYPE_VIDEO, + (j ? 3 : 2)); + if (err < 0) + goto probe_out; + } + + return 0; + +probe_out: + for (k = 0; k < j; k++) { + ch = vpif_obj.dev[k]; + video_unregister_device(&ch->video_dev); + } + return err; +} + +/* + * vpif_probe: This function creates device entries by register itself to the + * V4L2 driver and initializes fields of each channel objects + */ +static __init int vpif_probe(struct platform_device *pdev) +{ + struct vpif_subdev_info *subdevdata; + struct i2c_adapter *i2c_adap; + int subdev_count; + int res_idx = 0; + int i, err; + + if (!pdev->dev.platform_data) { + dev_warn(&pdev->dev, "Missing platform data. Giving up.\n"); + return -EINVAL; + } + + vpif_dev = &pdev->dev; + err = initialize_vpif(); + + if (err) { + v4l2_err(vpif_dev->driver, "Error initializing vpif\n"); + return err; + } + + err = v4l2_device_register(vpif_dev, &vpif_obj.v4l2_dev); + if (err) { + v4l2_err(vpif_dev->driver, "Error registering v4l2 device\n"); + goto vpif_free; + } + + do { + int irq; + + err = platform_get_irq_optional(pdev, res_idx); + if (err < 0 && err != -ENXIO) + goto vpif_unregister; + if (err > 0) + irq = err; + else + break; + + err = devm_request_irq(&pdev->dev, irq, vpif_channel_isr, + IRQF_SHARED, VPIF_DRIVER_NAME, + (void *)(&vpif_obj.dev[res_idx]->channel_id)); + if (err) { + vpif_err("VPIF IRQ request failed\n"); + goto vpif_unregister; + } + } while (++res_idx); + + vpif_obj.config = pdev->dev.platform_data; + subdev_count = vpif_obj.config->subdev_count; + subdevdata = vpif_obj.config->subdevinfo; + vpif_obj.sd = kcalloc(subdev_count, sizeof(*vpif_obj.sd), GFP_KERNEL); + if (!vpif_obj.sd) { + err = -ENOMEM; + goto vpif_unregister; + } + + i2c_adap = i2c_get_adapter(vpif_obj.config->i2c_adapter_id); + for (i = 0; i < subdev_count; i++) { + vpif_obj.sd[i] = + v4l2_i2c_new_subdev_board(&vpif_obj.v4l2_dev, + i2c_adap, + &subdevdata[i].board_info, + NULL); + if (!vpif_obj.sd[i]) { + vpif_err("Error registering v4l2 subdevice\n"); + err = -ENODEV; + goto probe_subdev_out; + } + + vpif_obj.sd[i]->grp_id = 1 << i; + } + err = vpif_probe_complete(); + if (err) + goto probe_subdev_out; + + return 0; + +probe_subdev_out: + kfree(vpif_obj.sd); +vpif_unregister: + v4l2_device_unregister(&vpif_obj.v4l2_dev); +vpif_free: + free_vpif_objs(); + + return err; +} + +/* + * vpif_remove: It un-register channels from V4L2 driver + */ +static int vpif_remove(struct platform_device *device) +{ + struct channel_obj *ch; + int i; + + v4l2_device_unregister(&vpif_obj.v4l2_dev); + + kfree(vpif_obj.sd); + /* un-register device */ + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + /* Unregister video device */ + video_unregister_device(&ch->video_dev); + } + free_vpif_objs(); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int vpif_suspend(struct device *dev) +{ + struct common_obj *common; + struct channel_obj *ch; + int i; + + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + common = &ch->common[VPIF_VIDEO_INDEX]; + + if (!vb2_start_streaming_called(&common->buffer_queue)) + continue; + + mutex_lock(&common->lock); + /* Disable channel */ + if (ch->channel_id == VPIF_CHANNEL2_VIDEO) { + enable_channel2(0); + channel2_intr_enable(0); + } + if (ch->channel_id == VPIF_CHANNEL3_VIDEO || + ycmux_mode == 2) { + enable_channel3(0); + channel3_intr_enable(0); + } + mutex_unlock(&common->lock); + } + + return 0; +} + +static int vpif_resume(struct device *dev) +{ + + struct common_obj *common; + struct channel_obj *ch; + int i; + + for (i = 0; i < VPIF_DISPLAY_MAX_DEVICES; i++) { + /* Get the pointer to the channel object */ + ch = vpif_obj.dev[i]; + common = &ch->common[VPIF_VIDEO_INDEX]; + + if (!vb2_start_streaming_called(&common->buffer_queue)) + continue; + + mutex_lock(&common->lock); + /* Enable channel */ + if (ch->channel_id == VPIF_CHANNEL2_VIDEO) { + enable_channel2(1); + channel2_intr_enable(1); + } + if (ch->channel_id == VPIF_CHANNEL3_VIDEO || + ycmux_mode == 2) { + enable_channel3(1); + channel3_intr_enable(1); + } + mutex_unlock(&common->lock); + } + + return 0; +} + +#endif + +static SIMPLE_DEV_PM_OPS(vpif_pm_ops, vpif_suspend, vpif_resume); + +static __refdata struct platform_driver vpif_driver = { + .driver = { + .name = VPIF_DRIVER_NAME, + .pm = &vpif_pm_ops, + }, + .probe = vpif_probe, + .remove = vpif_remove, +}; + +module_platform_driver(vpif_driver); diff --git a/drivers/media/platform/ti/davinci/vpif_display.h b/drivers/media/platform/ti/davinci/vpif_display.h new file mode 100644 index 000000000..dae20053d --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpif_display.h @@ -0,0 +1,116 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * VPIF display header file + * + * Copyright (C) 2009 Texas Instruments Incorporated - https://www.ti.com/ + */ + +#ifndef VPIF_DISPLAY_H +#define VPIF_DISPLAY_H + +/* Header files */ +#include +#include + +#include "vpif.h" + +/* Macros */ +#define VPIF_DISPLAY_VERSION "0.0.2" + +#define VPIF_VALID_FIELD(field) \ + (((V4L2_FIELD_ANY == field) || (V4L2_FIELD_NONE == field)) || \ + (((V4L2_FIELD_INTERLACED == field) || (V4L2_FIELD_SEQ_TB == field)) || \ + (V4L2_FIELD_SEQ_BT == field))) + +#define VPIF_DISPLAY_MAX_DEVICES (2) +#define VPIF_SLICED_BUF_SIZE (256) +#define VPIF_SLICED_MAX_SERVICES (3) +#define VPIF_VIDEO_INDEX (0) +#define VPIF_VBI_INDEX (1) +#define VPIF_HBI_INDEX (2) + +/* Setting it to 1 as HBI/VBI support yet to be added , else 3*/ +#define VPIF_NUMOBJECTS (1) + +/* Macros */ +#define ISALIGNED(a) (0 == ((a) & 7)) + +/* enumerated data types */ +/* Enumerated data type to give id to each device per channel */ +enum vpif_channel_id { + VPIF_CHANNEL2_VIDEO = 0, /* Channel2 Video */ + VPIF_CHANNEL3_VIDEO, /* Channel3 Video */ +}; + +/* structures */ + +struct video_obj { + enum v4l2_field buf_field; + u32 latest_only; /* indicate whether to return + * most recent displayed frame only */ + v4l2_std_id stdid; /* Currently selected or default + * standard */ + struct v4l2_dv_timings dv_timings; +}; + +struct vpif_disp_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; +}; + +struct common_obj { + struct vpif_disp_buffer *cur_frm; /* Pointer pointing to current + * vb2_buffer */ + struct vpif_disp_buffer *next_frm; /* Pointer pointing to next + * vb2_buffer */ + struct v4l2_format fmt; /* Used to store the format */ + struct vb2_queue buffer_queue; /* Buffer queue used in vb2 */ + + struct list_head dma_queue; /* Queue of filled frames */ + spinlock_t irqlock; /* Used for video buffer + * handling */ + + /* channel specific parameters */ + struct mutex lock; /* lock used to access this + * structure */ + u32 ytop_off; /* offset of Y top from the + * starting of the buffer */ + u32 ybtm_off; /* offset of Y bottom from the + * starting of the buffer */ + u32 ctop_off; /* offset of C top from the + * starting of the buffer */ + u32 cbtm_off; /* offset of C bottom from the + * starting of the buffer */ + /* Function pointer to set the addresses */ + void (*set_addr)(unsigned long, unsigned long, + unsigned long, unsigned long); + u32 height; + u32 width; +}; + +struct channel_obj { + /* V4l2 specific parameters */ + struct video_device video_dev; /* Identifies video device for + * this channel */ + u32 field_id; /* Indicates id of the field + * which is being displayed */ + u8 initialized; /* flag to indicate whether + * encoder is initialized */ + u32 output_idx; /* Current output index */ + struct v4l2_subdev *sd; /* Current output subdev(may be NULL) */ + + enum vpif_channel_id channel_id;/* Identifies channel */ + struct vpif_params vpifparams; + struct common_obj common[VPIF_NUMOBJECTS]; + struct video_obj video; +}; + +/* vpif device structure */ +struct vpif_device { + struct v4l2_device v4l2_dev; + struct channel_obj *dev[VPIF_DISPLAY_NUM_CHANNELS]; + struct v4l2_subdev **sd; + struct vpif_display_config *config; +}; + +#endif /* VPIF_DISPLAY_H */ diff --git a/drivers/media/platform/ti/davinci/vpss.c b/drivers/media/platform/ti/davinci/vpss.c new file mode 100644 index 000000000..d15b991ab --- /dev/null +++ b/drivers/media/platform/ti/davinci/vpss.c @@ -0,0 +1,529 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2009 Texas Instruments. + * + * common vpss system module platform driver for all video drivers. + */ +#include +#include +#include +#include +#include + +#include + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("VPSS Driver"); +MODULE_AUTHOR("Texas Instruments"); + +/* DM644x defines */ +#define DM644X_SBL_PCR_VPSS (4) + +#define DM355_VPSSBL_INTSEL 0x10 +#define DM355_VPSSBL_EVTSEL 0x14 +/* vpss BL register offsets */ +#define DM355_VPSSBL_CCDCMUX 0x1c +/* vpss CLK register offsets */ +#define DM355_VPSSCLK_CLKCTRL 0x04 +/* masks and shifts */ +#define VPSS_HSSISEL_SHIFT 4 +/* + * VDINT0 - vpss_int0, VDINT1 - vpss_int1, H3A - vpss_int4, + * IPIPE_INT1_SDR - vpss_int5 + */ +#define DM355_VPSSBL_INTSEL_DEFAULT 0xff83ff10 +/* VENCINT - vpss_int8 */ +#define DM355_VPSSBL_EVTSEL_DEFAULT 0x4 + +#define DM365_ISP5_PCCR 0x04 +#define DM365_ISP5_PCCR_BL_CLK_ENABLE BIT(0) +#define DM365_ISP5_PCCR_ISIF_CLK_ENABLE BIT(1) +#define DM365_ISP5_PCCR_H3A_CLK_ENABLE BIT(2) +#define DM365_ISP5_PCCR_RSZ_CLK_ENABLE BIT(3) +#define DM365_ISP5_PCCR_IPIPE_CLK_ENABLE BIT(4) +#define DM365_ISP5_PCCR_IPIPEIF_CLK_ENABLE BIT(5) +#define DM365_ISP5_PCCR_RSV BIT(6) + +#define DM365_ISP5_BCR 0x08 +#define DM365_ISP5_BCR_ISIF_OUT_ENABLE BIT(1) + +#define DM365_ISP5_INTSEL1 0x10 +#define DM365_ISP5_INTSEL2 0x14 +#define DM365_ISP5_INTSEL3 0x18 +#define DM365_ISP5_CCDCMUX 0x20 +#define DM365_ISP5_PG_FRAME_SIZE 0x28 +#define DM365_VPBE_CLK_CTRL 0x00 + +#define VPSS_CLK_CTRL 0x01c40044 +#define VPSS_CLK_CTRL_VENCCLKEN BIT(3) +#define VPSS_CLK_CTRL_DACCLKEN BIT(4) + +/* + * vpss interrupts. VDINT0 - vpss_int0, VDINT1 - vpss_int1, + * AF - vpss_int3 + */ +#define DM365_ISP5_INTSEL1_DEFAULT 0x0b1f0100 +/* AEW - vpss_int6, RSZ_INT_DMA - vpss_int5 */ +#define DM365_ISP5_INTSEL2_DEFAULT 0x1f0a0f1f +/* VENC - vpss_int8 */ +#define DM365_ISP5_INTSEL3_DEFAULT 0x00000015 + +/* masks and shifts for DM365*/ +#define DM365_CCDC_PG_VD_POL_SHIFT 0 +#define DM365_CCDC_PG_HD_POL_SHIFT 1 + +#define CCD_SRC_SEL_MASK (BIT_MASK(5) | BIT_MASK(4)) +#define CCD_SRC_SEL_SHIFT 4 + +/* Different SoC platforms supported by this driver */ +enum vpss_platform_type { + DM644X, + DM355, + DM365, +}; + +/* + * vpss operations. Depends on platform. Not all functions are available + * on all platforms. The api, first check if a function is available before + * invoking it. In the probe, the function ptrs are initialized based on + * vpss name. vpss name can be "dm355_vpss", "dm644x_vpss" etc. + */ +struct vpss_hw_ops { + /* enable clock */ + int (*enable_clock)(enum vpss_clock_sel clock_sel, int en); + /* select input to ccdc */ + void (*select_ccdc_source)(enum vpss_ccdc_source_sel src_sel); + /* clear wbl overflow bit */ + int (*clear_wbl_overflow)(enum vpss_wbl_sel wbl_sel); + /* set sync polarity */ + void (*set_sync_pol)(struct vpss_sync_pol); + /* set the PG_FRAME_SIZE register*/ + void (*set_pg_frame_size)(struct vpss_pg_frame_size); + /* check and clear interrupt if occurred */ + int (*dma_complete_interrupt)(void); +}; + +/* vpss configuration */ +struct vpss_oper_config { + __iomem void *vpss_regs_base0; + __iomem void *vpss_regs_base1; + __iomem void *vpss_regs_base2; + enum vpss_platform_type platform; + spinlock_t vpss_lock; + struct vpss_hw_ops hw_ops; +}; + +static struct vpss_oper_config oper_cfg; + +/* register access routines */ +static inline u32 bl_regr(u32 offset) +{ + return __raw_readl(oper_cfg.vpss_regs_base0 + offset); +} + +static inline void bl_regw(u32 val, u32 offset) +{ + __raw_writel(val, oper_cfg.vpss_regs_base0 + offset); +} + +static inline u32 vpss_regr(u32 offset) +{ + return __raw_readl(oper_cfg.vpss_regs_base1 + offset); +} + +static inline void vpss_regw(u32 val, u32 offset) +{ + __raw_writel(val, oper_cfg.vpss_regs_base1 + offset); +} + +/* For DM365 only */ +static inline u32 isp5_read(u32 offset) +{ + return __raw_readl(oper_cfg.vpss_regs_base0 + offset); +} + +/* For DM365 only */ +static inline void isp5_write(u32 val, u32 offset) +{ + __raw_writel(val, oper_cfg.vpss_regs_base0 + offset); +} + +static void dm365_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ + u32 temp = isp5_read(DM365_ISP5_CCDCMUX) & ~CCD_SRC_SEL_MASK; + + /* if we are using pattern generator, enable it */ + if (src_sel == VPSS_PGLPBK || src_sel == VPSS_CCDCPG) + temp |= 0x08; + + temp |= (src_sel << CCD_SRC_SEL_SHIFT); + isp5_write(temp, DM365_ISP5_CCDCMUX); +} + +static void dm355_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ + bl_regw(src_sel << VPSS_HSSISEL_SHIFT, DM355_VPSSBL_CCDCMUX); +} + +int vpss_dma_complete_interrupt(void) +{ + if (!oper_cfg.hw_ops.dma_complete_interrupt) + return 2; + return oper_cfg.hw_ops.dma_complete_interrupt(); +} +EXPORT_SYMBOL(vpss_dma_complete_interrupt); + +int vpss_select_ccdc_source(enum vpss_ccdc_source_sel src_sel) +{ + if (!oper_cfg.hw_ops.select_ccdc_source) + return -EINVAL; + + oper_cfg.hw_ops.select_ccdc_source(src_sel); + return 0; +} +EXPORT_SYMBOL(vpss_select_ccdc_source); + +static int dm644x_clear_wbl_overflow(enum vpss_wbl_sel wbl_sel) +{ + u32 mask = 1, val; + + if (wbl_sel < VPSS_PCR_AEW_WBL_0 || + wbl_sel > VPSS_PCR_CCDC_WBL_O) + return -EINVAL; + + /* writing a 0 clear the overflow */ + mask = ~(mask << wbl_sel); + val = bl_regr(DM644X_SBL_PCR_VPSS) & mask; + bl_regw(val, DM644X_SBL_PCR_VPSS); + return 0; +} + +void vpss_set_sync_pol(struct vpss_sync_pol sync) +{ + if (!oper_cfg.hw_ops.set_sync_pol) + return; + + oper_cfg.hw_ops.set_sync_pol(sync); +} +EXPORT_SYMBOL(vpss_set_sync_pol); + +int vpss_clear_wbl_overflow(enum vpss_wbl_sel wbl_sel) +{ + if (!oper_cfg.hw_ops.clear_wbl_overflow) + return -EINVAL; + + return oper_cfg.hw_ops.clear_wbl_overflow(wbl_sel); +} +EXPORT_SYMBOL(vpss_clear_wbl_overflow); + +/* + * dm355_enable_clock - Enable VPSS Clock + * @clock_sel: Clock to be enabled/disabled + * @en: enable/disable flag + * + * This is called to enable or disable a vpss clock + */ +static int dm355_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ + unsigned long flags; + u32 utemp, mask = 0x1, shift = 0; + + switch (clock_sel) { + case VPSS_VPBE_CLOCK: + /* nothing since lsb */ + break; + case VPSS_VENC_CLOCK_SEL: + shift = 2; + break; + case VPSS_CFALD_CLOCK: + shift = 3; + break; + case VPSS_H3A_CLOCK: + shift = 4; + break; + case VPSS_IPIPE_CLOCK: + shift = 5; + break; + case VPSS_CCDC_CLOCK: + shift = 6; + break; + default: + printk(KERN_ERR "dm355_enable_clock: Invalid selector: %d\n", + clock_sel); + return -EINVAL; + } + + spin_lock_irqsave(&oper_cfg.vpss_lock, flags); + utemp = vpss_regr(DM355_VPSSCLK_CLKCTRL); + if (!en) + utemp &= ~(mask << shift); + else + utemp |= (mask << shift); + + vpss_regw(utemp, DM355_VPSSCLK_CLKCTRL); + spin_unlock_irqrestore(&oper_cfg.vpss_lock, flags); + return 0; +} + +static int dm365_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ + unsigned long flags; + u32 utemp, mask = 0x1, shift = 0, offset = DM365_ISP5_PCCR; + u32 (*read)(u32 offset) = isp5_read; + void(*write)(u32 val, u32 offset) = isp5_write; + + switch (clock_sel) { + case VPSS_BL_CLOCK: + break; + case VPSS_CCDC_CLOCK: + shift = 1; + break; + case VPSS_H3A_CLOCK: + shift = 2; + break; + case VPSS_RSZ_CLOCK: + shift = 3; + break; + case VPSS_IPIPE_CLOCK: + shift = 4; + break; + case VPSS_IPIPEIF_CLOCK: + shift = 5; + break; + case VPSS_PCLK_INTERNAL: + shift = 6; + break; + case VPSS_PSYNC_CLOCK_SEL: + shift = 7; + break; + case VPSS_VPBE_CLOCK: + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_VENC_CLOCK_SEL: + shift = 2; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_LDC_CLOCK: + shift = 3; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_FDIF_CLOCK: + shift = 4; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_OSD_CLOCK_SEL: + shift = 6; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + case VPSS_LDC_CLOCK_SEL: + shift = 7; + read = vpss_regr; + write = vpss_regw; + offset = DM365_VPBE_CLK_CTRL; + break; + default: + printk(KERN_ERR "dm365_enable_clock: Invalid selector: %d\n", + clock_sel); + return -1; + } + + spin_lock_irqsave(&oper_cfg.vpss_lock, flags); + utemp = read(offset); + if (!en) { + mask = ~mask; + utemp &= (mask << shift); + } else + utemp |= (mask << shift); + + write(utemp, offset); + spin_unlock_irqrestore(&oper_cfg.vpss_lock, flags); + + return 0; +} + +int vpss_enable_clock(enum vpss_clock_sel clock_sel, int en) +{ + if (!oper_cfg.hw_ops.enable_clock) + return -EINVAL; + + return oper_cfg.hw_ops.enable_clock(clock_sel, en); +} +EXPORT_SYMBOL(vpss_enable_clock); + +void dm365_vpss_set_sync_pol(struct vpss_sync_pol sync) +{ + int val = 0; + val = isp5_read(DM365_ISP5_CCDCMUX); + + val |= (sync.ccdpg_hdpol << DM365_CCDC_PG_HD_POL_SHIFT); + val |= (sync.ccdpg_vdpol << DM365_CCDC_PG_VD_POL_SHIFT); + + isp5_write(val, DM365_ISP5_CCDCMUX); +} +EXPORT_SYMBOL(dm365_vpss_set_sync_pol); + +void vpss_set_pg_frame_size(struct vpss_pg_frame_size frame_size) +{ + if (!oper_cfg.hw_ops.set_pg_frame_size) + return; + + oper_cfg.hw_ops.set_pg_frame_size(frame_size); +} +EXPORT_SYMBOL(vpss_set_pg_frame_size); + +void dm365_vpss_set_pg_frame_size(struct vpss_pg_frame_size frame_size) +{ + int current_reg = ((frame_size.hlpfr >> 1) - 1) << 16; + + current_reg |= (frame_size.pplen - 1); + isp5_write(current_reg, DM365_ISP5_PG_FRAME_SIZE); +} +EXPORT_SYMBOL(dm365_vpss_set_pg_frame_size); + +static int vpss_probe(struct platform_device *pdev) +{ + char *platform_name; + + if (!pdev->dev.platform_data) { + dev_err(&pdev->dev, "no platform data\n"); + return -ENOENT; + } + + platform_name = pdev->dev.platform_data; + if (!strcmp(platform_name, "dm355_vpss")) + oper_cfg.platform = DM355; + else if (!strcmp(platform_name, "dm365_vpss")) + oper_cfg.platform = DM365; + else if (!strcmp(platform_name, "dm644x_vpss")) + oper_cfg.platform = DM644X; + else { + dev_err(&pdev->dev, "vpss driver not supported on this platform\n"); + return -ENODEV; + } + + dev_info(&pdev->dev, "%s vpss probed\n", platform_name); + oper_cfg.vpss_regs_base0 = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(oper_cfg.vpss_regs_base0)) + return PTR_ERR(oper_cfg.vpss_regs_base0); + + if (oper_cfg.platform == DM355 || oper_cfg.platform == DM365) { + oper_cfg.vpss_regs_base1 = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(oper_cfg.vpss_regs_base1)) + return PTR_ERR(oper_cfg.vpss_regs_base1); + } + + if (oper_cfg.platform == DM355) { + oper_cfg.hw_ops.enable_clock = dm355_enable_clock; + oper_cfg.hw_ops.select_ccdc_source = dm355_select_ccdc_source; + /* Setup vpss interrupts */ + bl_regw(DM355_VPSSBL_INTSEL_DEFAULT, DM355_VPSSBL_INTSEL); + bl_regw(DM355_VPSSBL_EVTSEL_DEFAULT, DM355_VPSSBL_EVTSEL); + } else if (oper_cfg.platform == DM365) { + oper_cfg.hw_ops.enable_clock = dm365_enable_clock; + oper_cfg.hw_ops.select_ccdc_source = dm365_select_ccdc_source; + /* Setup vpss interrupts */ + isp5_write((isp5_read(DM365_ISP5_PCCR) | + DM365_ISP5_PCCR_BL_CLK_ENABLE | + DM365_ISP5_PCCR_ISIF_CLK_ENABLE | + DM365_ISP5_PCCR_H3A_CLK_ENABLE | + DM365_ISP5_PCCR_RSZ_CLK_ENABLE | + DM365_ISP5_PCCR_IPIPE_CLK_ENABLE | + DM365_ISP5_PCCR_IPIPEIF_CLK_ENABLE | + DM365_ISP5_PCCR_RSV), DM365_ISP5_PCCR); + isp5_write((isp5_read(DM365_ISP5_BCR) | + DM365_ISP5_BCR_ISIF_OUT_ENABLE), DM365_ISP5_BCR); + isp5_write(DM365_ISP5_INTSEL1_DEFAULT, DM365_ISP5_INTSEL1); + isp5_write(DM365_ISP5_INTSEL2_DEFAULT, DM365_ISP5_INTSEL2); + isp5_write(DM365_ISP5_INTSEL3_DEFAULT, DM365_ISP5_INTSEL3); + } else + oper_cfg.hw_ops.clear_wbl_overflow = dm644x_clear_wbl_overflow; + + pm_runtime_enable(&pdev->dev); + + pm_runtime_get(&pdev->dev); + + spin_lock_init(&oper_cfg.vpss_lock); + dev_info(&pdev->dev, "%s vpss probe success\n", platform_name); + + return 0; +} + +static int vpss_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + return 0; +} + +static int vpss_suspend(struct device *dev) +{ + pm_runtime_put(dev); + return 0; +} + +static int vpss_resume(struct device *dev) +{ + pm_runtime_get(dev); + return 0; +} + +static const struct dev_pm_ops vpss_pm_ops = { + .suspend = vpss_suspend, + .resume = vpss_resume, +}; + +static struct platform_driver vpss_driver = { + .driver = { + .name = "vpss", + .pm = &vpss_pm_ops, + }, + .remove = vpss_remove, + .probe = vpss_probe, +}; + +static void vpss_exit(void) +{ + platform_driver_unregister(&vpss_driver); + iounmap(oper_cfg.vpss_regs_base2); + release_mem_region(VPSS_CLK_CTRL, 4); +} + +static int __init vpss_init(void) +{ + int ret; + + if (!request_mem_region(VPSS_CLK_CTRL, 4, "vpss_clock_control")) + return -EBUSY; + + oper_cfg.vpss_regs_base2 = ioremap(VPSS_CLK_CTRL, 4); + if (unlikely(!oper_cfg.vpss_regs_base2)) { + ret = -ENOMEM; + goto err_ioremap; + } + + writel(VPSS_CLK_CTRL_VENCCLKEN | + VPSS_CLK_CTRL_DACCLKEN, oper_cfg.vpss_regs_base2); + + ret = platform_driver_register(&vpss_driver); + if (ret) + goto err_pd_register; + + return 0; + +err_pd_register: + iounmap(oper_cfg.vpss_regs_base2); +err_ioremap: + release_mem_region(VPSS_CLK_CTRL, 4); + return ret; +} +subsys_initcall(vpss_init); +module_exit(vpss_exit); diff --git a/drivers/media/platform/ti/omap/Kconfig b/drivers/media/platform/ti/omap/Kconfig new file mode 100644 index 000000000..a9dbe1097 --- /dev/null +++ b/drivers/media/platform/ti/omap/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-only +config VIDEO_OMAP2_VOUT_VRFB + bool + default y + depends on VIDEO_OMAP2_VOUT && (OMAP2_VRFB || COMPILE_TEST) + +config VIDEO_OMAP2_VOUT + tristate "OMAP2/OMAP3 V4L2-Display driver" + depends on V4L_PLATFORM_DRIVERS + depends on MMU + depends on FB_OMAP2 || (COMPILE_TEST && FB_OMAP2=n) + depends on ARCH_OMAP2 || ARCH_OMAP3 || COMPILE_TEST + depends on VIDEO_DEV + select VIDEOBUF2_DMA_CONTIG + select OMAP2_VRFB if ARCH_OMAP2 || ARCH_OMAP3 + help + V4L2 Display driver support for OMAP2/3 based boards. diff --git a/drivers/media/platform/ti/omap/Makefile b/drivers/media/platform/ti/omap/Makefile new file mode 100644 index 000000000..b17a0ac10 --- /dev/null +++ b/drivers/media/platform/ti/omap/Makefile @@ -0,0 +1,9 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Makefile for the omap video device drivers. +# + +# OMAP2/3 Display driver +omap-vout-y += omap_vout.o omap_voutlib.o +omap-vout-$(CONFIG_VIDEO_OMAP2_VOUT_VRFB) += omap_vout_vrfb.o +obj-$(CONFIG_VIDEO_OMAP2_VOUT) += omap-vout.o diff --git a/drivers/media/platform/ti/omap/omap_vout.c b/drivers/media/platform/ti/omap/omap_vout.c new file mode 100644 index 000000000..3e0d9af7f --- /dev/null +++ b/drivers/media/platform/ti/omap/omap_vout.c @@ -0,0 +1,1742 @@ +/* + * omap_vout.c + * + * Copyright (C) 2005-2010 Texas Instruments. + * + * This file is licensed under the terms of the GNU General Public License + * version 2. This program is licensed "as is" without any warranty of any + * kind, whether express or implied. + * + * Leveraged code from the OMAP2 camera driver + * Video-for-Linux (Version 2) camera capture driver for + * the OMAP24xx camera controller. + * + * Author: Andy Lowe (source@mvista.com) + * + * Copyright (C) 2004 MontaVista Software, Inc. + * Copyright (C) 2010 Texas Instruments. + * + * History: + * 20-APR-2006 Khasim Modified VRFB based Rotation, + * The image data is always read from 0 degree + * view and written + * to the virtual space of desired rotation angle + * 4-DEC-2006 Jian Changed to support better memory management + * + * 17-Nov-2008 Hardik Changed driver to use video_ioctl2 + * + * 23-Feb-2010 Vaibhav H Modified to use new DSS2 interface + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include